Table execution in SPIN?

Larry MartinLarry Martin Posts: 48
edited January 11 in Propeller 1 Vote Up0Vote Down
I have a SPIN/PASM application with 4 nearly identical cogs. The cogs are adapted from Full Duplex Serial, but know the trigger input and serial protocol. They transmit on trigger and retry autonomously on receivce failures. The cogs differ only by a VERSION line at the end of each one (thanks to Tracy Allen). My SPIN top level has about 10 functions like this:
PUB RdrFlush(pRdrIx)
  case (pRdrIx)
    0:
      UidTid.rxflush
    1:
      Encode1.rxflush
    2:
      Encode2.rxflush
    3:
      Encode3.rxflush

I'm up against memory limits and could use a more compact syntax like <MOD>.rxflush, but SPIN doesn't seem to let me assign a MOD value like UidTid to a variable. I would vastly prefer to do something like:
LONG mod_p
...
mod_p := UidTid
...
mod_p.rxflush

A jump table would also work. Am I right in thinking SPIN doesn't provide either?

Thanks,
Larry

Comments

  • 22 Comments sorted by Date Added Votes
  • You are correct that vanilla Spin does not provide a way to do this. What you're basically asking for is object pointers. There have however been some attempts to provide function pointers that rely on knowledge of how the internals of the Spin virtual machine works.
  • Sorry, I should have said OBJ, not MOD:
    OBJ
      FourRdrs : "FourSerial"      '2: Host, Debug, Barcode, ?
      CompuCog : "Compute" '3 CRC etc.
      UidTid  : "Rfid1" '4 reader 0
      Encode1 : "Rfid2" '5
      Encode2 : "Rfid3" '6
      Encode3 : "Rfid4" '7 FULL
    

    where Rfid1..4 are generated by a batch file before building. The file I maintain is just called rfid.spin.
  • David Betz wrote: »
    You are correct that vanilla Spin does not provide a way to do this. What you're basically asking for is object pointers. There have however been some attempts to provide function pointers that rely on knowledge of how the internals of the Spin virtual machine works.

    Would I need a custom SPIN compiler for that, Dave?

  • mikeologistmikeologist Posts: 337
    edited January 11 Vote Up0Vote Down
    Have you considered making your cogs into a unified object and creating an array of them in a main spin program?
    NVM, that's only the DAT section that is shared.
    Any com port in a storm.
    Floating point numbers will be our downfall; count on it.
    Imagine a world without hypothetical situations.
  • Well, the 4-port object would handle this as
    uarts.rxflush(mod_p)
    in one cog and one instance of the code.

    What is the nature of the triggers and auto-retry? I'm wondering if it would take any modification at all of the 4-port object.

  • David Betz wrote: »
    You are correct that vanilla Spin does not provide a way to do this. What you're basically asking for is object pointers. There have however been some attempts to provide function pointers that rely on knowledge of how the internals of the Spin virtual machine works.

    Would I need a custom SPIN compiler for that, Dave?
    I think function pointers were implemented as an object but I can't recall the details. There wasn't a custom Spin compiler.
  • Tracy,
    What is the nature of the triggers and auto-retry? I'm wondering if it would take any modification at all of the 4-port object.

    No, the existing logic about fills a cog, with 36 words left over. I can't post it, but it:
    * gets a pointer to a chain of command bytes in global memory (multiple commands)
    * gets a pointer to an array of command lengths
    * on trigger, sends the first command and starts looking for reply bytes
    * parses the reply length from received bytes and handles the end of reply correctly
    * checks reply status and resends the command on bad status
    * if status is good, the command length cursor indexes and the next command is sent
    * when the next command length is 0, a Complete flag is sent to SPIN via wrlong
    * with tracing
    all in PASM, in 4 cogs simultaneously, at 115 kbaud. IMHO, it's about maxed out.
  • Here's a callback hack that I wrote:

    https://forums.parallax.com/discussion/86518/callback-hack

    'Not sure I'd recommend using it production code, though. It is, after all, just a hack. :)

    -Phil
    “Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away. -Antoine de Saint-Exupery
  • Thanks, Phil and Dave. I'll look at it but I think I may have solved my problem with a different "callback" hack, found by following Dave's initial hint:
    forums.parallax.com/discussion/120467/procedure-variables-or-callbacks-in-spin

    Based on that article, I have tried changing this:
    PUB SetCmdBufPointer(pRdrIx, pAdr)
      case (pRdrIx)
        0:
          UidTid.UpdateCmdBufPointer(pAdr)
        1:
          Encode1.UpdateCmdBufPointer(pAdr)
        2:
          Encode2.UpdateCmdBufPointer(pAdr)
        3:
          Encode3.UpdateCmdBufPointer(pAdr)
    

    to this:
    PUB SetCmdBufPointer(pRdrIx, pAdr)
      RESULT := UidTid[pRdrIx].UpdateCmdBufPointer(pAdr)
    

    Given this:
    OBJ
      FourRdrs : "FourSerial"      '2: Host, Debug, Barcode, ?
      CompuCog : "Compute" '3 CRC etc.
      UidTid  : "Rfid1" '4 reader 0
      Encode1 : "Rfid2" '5
      Encode2 : "Rfid3" '6
      Encode3 : "Rfid4" '7 FULL
    

    The trick seems to be to put square brackets after a symbol, fooling SPIN into indexing it like an array. Type goes with section, so a VAR symbol can only do VAR stuff, but items indexed off an OBJ symbol can do OBJ stuff.

    It compiles. I'll see if it really works tomorrow.
  • Yes, that should be fine as long as they're all instances of the same object.
  • David Betz wrote: »
    Yes, that should be fine as long as they're all instances of the same object.
    Actually, this won't work. It will always call the methods for Rfid1 even when you pass a 1, 2, or 3 as the array index. It thinks they are all instances of Rfid1 since you're using that as the array base. I'm actually surprised this compiles.

  • You said Rfid1, Rfid2, Rfid3, and Rfid4 are all identical except for a VERSION line to make them each have their own copies of their DAT blocks. Why is it necessary that they all have independent instances of their DAT blocks? Can you move per-object things to VAR blocks?
  • Larry MartinLarry Martin Posts: 48
    edited January 12 Vote Up0Vote Down
    You said Rfid1, Rfid2, Rfid3, and Rfid4 are all identical except for a VERSION line to make them each have their own copies of their DAT blocks. Why is it necessary that they all have independent instances of their DAT blocks? Can you move per-object things to VAR blocks?

    My system has 4 serial connected devices that do the same task with different data, at the same time, on an external trigger. Timing is so tight that each needs its own COG so trigger response and error recovery can happen at the speed of PASM.

    My understanding of the COG mechanism may be a little off, but here goes. I think:
    1. The way to get a COG to execute PASM is to put the PASM in a DAT block and have SPIN call cognew with a pointer to an "entry" label in PASM.
    2. If you have more than one identical instance of an OBJ, then SPIN will combine them, so you have to make the DAT sections unique. Tracy Allen taught me this a couple of years ago, when I was trying to use two instances of the four-serial object.
    3. So I wrote a batch file to copy my RFID.spin to RFID1..4.spin, appending a VERSION line to each one.

    Is it possible instead to run multiple COGs off one OBJ? Something like this:
    cog1 := cognew(@entry, @block1) + 1
    cog2 := cognew(@entry, @block2) + 1
    cog3 := cognew(@entry, @block3) + 1
    cog4 := cognew(@entry, @block4) + 1
    

    Would that lead to 4 COGs running the same PASM against 4 different blocks of global data?
  • ....
    My system has 4 serial connected devices that do the same task with different data, at the same time, on an external trigger. Timing is so tight that each needs its own COG so trigger response and error recovery can happen at the speed of PASM.

    My understanding of the COG mechanism may be a little off, but here goes. I think:
    1. The way to get a COG to execute PASM is to put the PASM in a DAT block and have SPIN call cognew with a pointer to an "entry" label in PASM.
    2. If you have more than one identical instance of an OBJ, then SPIN will combine them, so you have to make the DAT sections unique. Tracy Allen taught me this a couple of years ago, when I was trying to use two instances of the four-serial object.
    3. So I wrote a batch file to copy my RFID.spin to RFID1..4.spin, appending a VERSION line to each one.

    Is it possible instead to run multiple COGs off one OBJ? Something like this:
    cog1 := cognew(@entry, @block1) + 1
    cog2 := cognew(@entry, @block2) + 1
    cog3 := cognew(@entry, @block3) + 1
    cog4 := cognew(@entry, @block4) + 1
    

    Would that lead to 4 COGs running the same PASM against 4 different blocks of global data?

    Yes, that's the idea of the Spin objects. There are spearate base pointers for data and code, and if the code is the same for several objects, the memory needs only to hold one instance, but allocates a sepearate data section for every included object.
    The object must be programmed the right way for that, the FullDuplexSerial supports this.

    If you change a Version line, the object gets 4 times copied into hub-memory, so you waste a lot of memory.

    I think the 4 port-serial is not made for this, that's why the version-line was needed to get more than one independent instance.

    Andy

  • Larry MartinLarry Martin Posts: 48
    edited January 12 Vote Up0Vote Down
    David Betz wrote: »
    David Betz wrote: »
    Yes, that should be fine as long as they're all instances of the same object.
    Actually, this won't work. It will always call the methods for Rfid1 even when you pass a 1, 2, or 3 as the array index. It thinks they are all instances of Rfid1 since you're using that as the array base. I'm actually surprised this compiles.

    Dave, I think you were right the first time. Last night I changed all my CASE statements to indexed accesses to a base OBJ symbol. The system seems to be working today. Actually, it's getting bad results, so there may still be something sneaky going on. But the lights controlled by each COG come on, all four serial streams are working, and my diagnostics show different reasonable values for each COG. The changes gained me back about 100 words.

    Thanks, everybody.
    Larry
  • Andy -
    The object must be programmed the right way for that, the FullDuplexSerial supports this.
    ...
    I think the 4 port-serial is not made for this, that's why the version-line was needed to get more than one independent instance.

    So what feature of the programming allows this to be different? Does the self patching behavior of 4-port drive the need for separate DAT sections enforced by VERSION lines?
  • Andy -
    If you change a Version line, the object gets 4 times copied into hub-memory, so you waste a lot of memory.

    I just spent a couple of hours consolidating the four MODs into one. I gained around 1000 longs, which is great. But the image resets on RAM load, so I only see the safe version in EEPROM. So I guess I got some indexes wrong. I sense a black hole of debugging, so that one is getting put away for a while. Thanks for the idea.
  • David BetzDavid Betz Posts: 11,834
    edited January 12 Vote Up0Vote Down
    David Betz wrote: »
    David Betz wrote: »
    Yes, that should be fine as long as they're all instances of the same object.
    Actually, this won't work. It will always call the methods for Rfid1 even when you pass a 1, 2, or 3 as the array index. It thinks they are all instances of Rfid1 since you're using that as the array base. I'm actually surprised this compiles.

    Dave, I think you were right the first time. Last night I changed all my CASE statements to indexed accesses to a base OBJ symbol. The system seems to be working today. Actually, it's getting bad results, so there may still be something sneaky going on. But the lights controlled by each COG come on, all four serial streams are working, and my diagnostics show different reasonable values for each COG. The changes gained me back about 100 words.

    Thanks, everybody.
    Larry
    If it's working I think it is just by luck. You're certainly only invoking methods from the Rfid1 object. I'd be willing to bet that if you change your code to have every object be an instance of that it will work just as well. Like this:
    OBJ
      FourRdrs : "FourSerial"      '2: Host, Debug, Barcode, ?
      CompuCog : "Compute" '3 CRC etc.
      UidTid  : "Rfid1" '4 reader 0
      Encode1 : "Rfid1" '5
      Encode2 : "Rfid1" '6
      Encode3 : "Rfid1" '7
    
  • I'd be willing to be that if you change your code to have every object be an instance of that it will work just as well.

    Yes, that worked too, and saved 1500 longs! Thank you very much!

    Here are diagnostic dumps of the RX buffer of two different COGs' RX buffers:
    Enc1:
    UHF Encode: 00000000  02 00 16 01 B0 00 04 02  00 00 00 00 00 00 00 DE
    UHF Encode: 00000010  AD 00 BE EF E7 71 00 00  00 00 00 DE AD 00 BE EF
    UHF Encode: 00000020  E7 71 02 00 08 01 B0 00  4C 85 02 00 1F 01 B0 00
    UHF Encode: 00000030  07 02 00 30 00 00 59 00 w 00 00 00 00 01 22 00 01
    UHF Encode: 00000040  01 00 22 C4 00 97 9C 67  ED 02 00 08 01 B0 00 4C
    UHF Encode: 00000050  85 02 00 08 01 B0 00 4C  85 02 00 1F 01 B0 00 07
    UHF Encode: 00000060  02 00 30 00 00 5C 00 00  00 00 00 01 21 00 01 01
    UHF Encode: 00000070  00 22 C4 00 97 79 9E 79  02 00 08 01 B0 00 4C 85
    UHF Encode: 00000080  02 00
    Enc2:
    UHF Encode: 00000000  02 00 08 03 B0 00 F4 30  00 00 00 00 00 DE AD 00
    UHF Encode: 00000010  BE EF F9 51 02 00 08 w03  B0 00 F4 30 02 00 1F 03
    UHF Encode: 00000020  B0 00 07 02 00 30 00 00  4B 00 00 00 00 00 01 18
    UHF Encode: 00000030  00 01 01 00 22 C4 00 96  FB C8 16 02 00 08 03 B0
    UHF Encode: 00000040  00 F4 30 02 00 16 03 B0  00 04 02 00 00 00 00 00
    UHF Encode: 00000050  00 00 DE AD 00 BE EF F9  51 02 00 08 03 B0 00 F4
    UHF Encode: 00000060  30 02 00 1F 03 B0 00 07  02 00 30 00 00 4E 00 00
    UHF Encode: 00000070  00 00 00 01 19 00 01 01  00 22 C4 00 97 14 FD 6C
    UHF Encode: 00000080  02 00
    

    The contents are different and reasonable. So even though there is just one source file, four cogs are running the PASM separately, referenced to 4 different global memory regions. The OBJs are being accessed through array accesses to the first one:
      'These four OBJs are identical except for VERSION lines at the bottom.
      'SPIN will index them like an array, so UidTid[1] is really Encode1 !
      UidTid  : "Rfid" '4 reader 0
      Encode1 : "Rfid" '5
      Encode2 : "Rfid" '6
      Encode3 : "Rfid" '7 FULL
    ...
      startIx := UidTid[pRdrIx].GetLastReplyStartIndex
    

    This is awesome, thanks again.
  • JonnyMacJonnyMac Posts: 5,977
    edited January 12 Vote Up0Vote Down
    I'm sure this is old news to you now, but you can in fact have an array of the same object. I recently wrote a program for an escape room that uses four instances of an IR sensor object (it decodes the data stream from a specific brand of laser-tag weapon). This is the object declaration section from that program.
    obj                              
                                     
    ' main                                                          ' * master Spin cog                   
      time       : "jm_time_80"                                     '   timing and delays (80MHz system) 
      prng       : "jm_prng"                                        '   pseudo-random number generator     
      io         : "jm_io"                                          '   essential io
      outs       : "jm_pwm8"                                        ' * pwm driver for outputs
      sensor[4]  : "jm_milestag-plus_rx"                            ' * Milestag IR input     
      term       : "jm_fullduplexserial"                            ' * serial IO for terminal
    ' background                                                    ' * background inputs scanning, timer 
      
    ' * uses cog when loaded
    
    And here's how I uses that object array -- it's as simple as it seems it should be.
    pub scan_sensors | target, sn
    
      target := -1
    
      repeat sn from 0 to 3                                         ' loop through all
        if (active & (1 << sn))                                     ' if sensor still on
          if (sensor[sn].ready)                                     ' have ir packet?
            if (sensor[sn].get_count == 2)                          ' is it a shot?
              target := sn                                          ' mark hit target
              quit
            else                      
              sensor[sn].enable                                     ' clear near non-hit data
              if (DEBUG == YES)                                     
                term.str(string("Bad packet on sensor "))
                term.tx("0" + sn)
                term.tx(13)     
    
      reset_sensors
      
      return target
    
    It's probably been covered already, but keep in mind that when you use the same object file more than once, the hub code and DAT sections are shared between them, though each instance will get its own set of variables.
    Jon McPhalen
    Hollywood, CA
    It's Jon or JonnyMac -- please do not call me Jonny.
  • sensor[4] : "jm_milestag-plus_rx"
    Even better. Thank you, Jon.
Sign In or Register to comment.