Shop OBEX P1 Docs P2 Docs Learn Events
F32 as a "Cogject" (Sort Of) — Parallax Forums

F32 as a "Cogject" (Sort Of)

Duane DegnDuane Degn Posts: 10,588
edited 2015-02-06 08:05 in Propeller 1
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:
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.
Sign In or Register to comment.