F32 as a "Cogject" (Sort Of)
Duane Degn
Posts: 10,588
Something I'd wanted to try for a while was to move the PASM sections of objects to a separate memory location than the Propeller main hub RAM. Dr_Acula worked on this sort of scheme as part of a Propeller operating system. I'm not after a full fledged OS, I just want to save some RAM.
I was running out of RAM in my hexapod code and wanted to see if I could load the PASM section of objects to the upper EEPROM and then use these PASM sections, one at a time to load, the Propeller.
The first two objects I tried this technique on were the objects Servo32v9 and F32. The servo code was pretty straightforward, I just added methods to return the first and last longs of the PASM code. Loading F32 to EEPROM was more of a challenge. F32 uses a funky call table which is basically self modifying code with the new line of code being read from the hub. This call table doesn't need to fit inside the cog but it needs to be compiled with the cog in order for the references to be computed correctly.
Here's the call table:
My initial naive thought was to substitute the labels such as "_FAdd" with the cog addresses. I initially started counted the lines of code within the cog to get the addresses but I soon learned, from the "cogject" thread, the Propeller Tool will provide this information once an object has been compiled. So I listed the address corresponding with each of the labels.
I'm guessing not may of you will be surprised to learn the above wouldn't compile. It turns out the command "call" requires multiple steps from the compiler and one can't just insert numbers like I did into a call statement.
F32 already had half of the solution. It includes a "Call_ptr" method which returns the address of the call table. Using this address, I had the parent object print out the values of each of these call commands.
I could then substitute the above table for the original.
I modified the "Start" methods of both objects to receive the address where the PASM code had been temporarily stored. Here's the modified "start" method of F32.
The original PASM section of code was commented out in these modified objects.
The hexapod code required a bit of reworking in order to read the PASM code from the EEPROM into a buffer.
I used a long buffer named "pasmBuffer" which was as large as the largest PASM section used (F32 requires 475 longs). The buffer needs to be long aligned.
I'm using two instances of F32 in the hexapod code.
Once all the PASM cogs had been launched, I then used the same buffer as the stack space for the two additional Spin cogs.
I plan to do something similar with the serial object but first I want to modify the serial object to use a buffer of my choosing. I'm hoping to use part of "pasmBuffer" for the RX and TX buffers of the serial object.
The attached archive includes the modified objects used to load the PASM sections to EEPROM. I have "FromEeprom" versions of the servo and F32 objects but these are pretty trivial. The start methods have been modified as mentioned earlier and the PASM sections commented out. If anyone wants these modified objects let me know and I'll upload them.
The attached code warns about writing to EEPROM but the sections of code which write to EEPROM have been commented out. These lines would need to be restored before the code will write to EEPROM.
I'm storing the PASM code to EEPROM but there's also the option of storing the PASM sections of code to a SD card. If you're running out of RAM in a Spin project, this sort of technique could be used to free up some memory.
BTW, After modifying the code to use PASM sections from EEPROM, my hexapod worked the first time I tried it. I did have trouble with my initial attempt to store the PASM code to EEPROM but I had been using buggy EEPROM code.
I was running out of RAM in my hexapod code and wanted to see if I could load the PASM section of objects to the upper EEPROM and then use these PASM sections, one at a time to load, the Propeller.
The first two objects I tried this technique on were the objects Servo32v9 and F32. The servo code was pretty straightforward, I just added methods to return the first and last longs of the PASM code. Loading F32 to EEPROM was more of a challenge. F32 uses a funky call table which is basically self modifying code with the new line of code being read from the hub. This call table doesn't need to fit inside the cog but it needs to be compiled with the cog in order for the references to be computed correctly.
Here's the call table:
cmdCallTable cmdFAdd call #_FAdd cmdFSub call #_FSub cmdFMul call #_FMul cmdFDiv call #_FDiv cmdFFloat call #_FFloat cmdFTruncRound call #_FTruncRound cmdUintTrunc call #_UintTrunc cmdFSqr call #_FSqr cmdFCmp call #_FCmp cmdFSin call #_Sin cmdFCos call #_Cos cmdFTan call #_Tan cmdFLog2 call #_Log2 cmdFExp2 call #_Exp2 cmdFPow call #_Pow cmdFFrac call #_Frac cmdFMod call #_FMod cmdASinCos call #_ASinCos cmdATan2 call #_ATan2 cmdCeil call #_Ceil cmdFloor call #_Floor
My initial naive thought was to substitute the labels such as "_FAdd" with the cog addresses. I initially started counted the lines of code within the cog to get the addresses but I soon learned, from the "cogject" thread, the Propeller Tool will provide this information once an object has been compiled. So I listed the address corresponding with each of the labels.
cmdCallTable cmdFAdd call #13 '_FAdd cmdFSub call #12 '_FSub cmdFMul call #31 '_FMul cmdFDiv call #51 '_FDiv cmdFFloat call #66 '_FFloat cmdFTruncRound call #72 '_FTruncRound cmdUintTrunc call #96 '_UintTrunc cmdFSqr call #108 '_FSqr cmdFCmp call #129 '_FCmp cmdFSin call #162 '_Sin cmdFCos call #159 '_Cos cmdFTan call #186 '_Tan cmdFLog2 call #195 '_Log2 cmdFExp2 call #$D9 '_Exp2 cmdFPow call #$FE '_Pow cmdFFrac call #$117 '_Frac cmdFMod call #$161 '_FMod cmdASinCos call #$1A8 '_ASinCos cmdATan2 call #$170 '_ATan2 cmdCeil call #$1B9 '_Ceil cmdFloor call #$1BB '_Floor
I'm guessing not may of you will be surprised to learn the above wouldn't compile. It turns out the command "call" requires multiple steps from the compiler and one can't just insert numbers like I did into a call statement.
F32 already had half of the solution. It includes a "Call_ptr" method which returns the address of the call table. Using this address, I had the parent object print out the values of each of these call commands.
cmdFAdd long $5CFC3C0D cmdFSub long $5CFC3C0C cmdFMul long $5CFC641F cmdFDiv long $5CFC8233 cmdFFloat long $5CFC8E42 cmdFTruncRound long $5CFCBE48 cmdUintTrunc long $5CFCD660 cmdFSqr long $5CFD006C cmdFCmp long $5CFD1481 cmdFSin long $5CFD72A2 cmdFCos long $5CFD729F cmdFTan long $5CFD84BA cmdFLog2 long $5CFDB0C3 cmdFExp2 long $5CFDFAD9 cmdFPow long $5CFE2CFE cmdFFrac long $5CFE4117 cmdFMod long $5CFEDF61 cmdASinCos long $5CFF71A8 cmdATan2 long $5CFF1B70 cmdCeil long $5CFF9BB9 cmdFloor long $5CFF9BBB
I could then substitute the above table for the original.
I modified the "Start" methods of both objects to receive the address where the PASM code had been temporarily stored. Here's the modified "start" method of F32.
PUB start(pasmAddress) {{ Start start floating point engine in a new cog. Returns: True (non-zero) if cog started, or False (0) if no cog is available. }} stop f32_Cmd := 0 return cog := cognew(pasmAddress, @f32_Cmd) + 1
The original PASM section of code was commented out in these modified objects.
The hexapod code required a bit of reworking in order to read the PASM code from the EEPROM into a buffer.
I used a long buffer named "pasmBuffer" which was as large as the largest PASM section used (F32 requires 475 longs). The buffer needs to be long aligned.
Eeprom.ToRam(@pasmBuffer, @pasmBuffer + Header#F32_EEPROM_SIZE - 1, Header#F32_EEPROM_START) F32[PATH].Start(@pasmBuffer) F32[IK].Start(@pasmBuffer) Eeprom.ToRam(@pasmBuffer, @pasmBuffer + Header#SERVO_EEPROM_SIZE - 1, Header#SERVO_EEPROM_START) Servo.Start(@pasmBuffer) waitcnt(clkfreq / 20 + cnt) pathStackPtr := @pasmBuffer ikStackPtr := @pasmBuffer + (PATH_STACK_SIZE * 4) cognew(ControlPath, pathStackPtr) cognew(ControlServos(DEFAULT_MINIMUM_LEG, DEFAULT_MAXIMUM_LEG), ikStackPtr)
I'm using two instances of F32 in the hexapod code.
Once all the PASM cogs had been launched, I then used the same buffer as the stack space for the two additional Spin cogs.
I plan to do something similar with the serial object but first I want to modify the serial object to use a buffer of my choosing. I'm hoping to use part of "pasmBuffer" for the RX and TX buffers of the serial object.
The attached archive includes the modified objects used to load the PASM sections to EEPROM. I have "FromEeprom" versions of the servo and F32 objects but these are pretty trivial. The start methods have been modified as mentioned earlier and the PASM sections commented out. If anyone wants these modified objects let me know and I'll upload them.
The attached code warns about writing to EEPROM but the sections of code which write to EEPROM have been commented out. These lines would need to be restored before the code will write to EEPROM.
I'm storing the PASM code to EEPROM but there's also the option of storing the PASM sections of code to a SD card. If you're running out of RAM in a Spin project, this sort of technique could be used to free up some memory.
BTW, After modifying the code to use PASM sections from EEPROM, my hexapod worked the first time I tried it. I did have trouble with my initial attempt to store the PASM code to EEPROM but I had been using buggy EEPROM code.