Shop OBEX P1 Docs P2 Docs Learn Events
Multi cog and timing rising edge. — Parallax Forums

Multi cog and timing rising edge.

TxRxTxRx Posts: 4
edited 2013-11-18 15:43 in Propeller 1
Hi.
I'm quite new to propeller and the SPIN language. What I am trying is to time two incomming signals on two different cogs, the timing frame is small and acuaracy is important. To test the code i tied the two input pins together and pulled the pins high, the code then writes down CNT when the pins go high, the priblem is that the CNT written down is different by 20-100 clock cycles. Any sugestions why this happends?
Code below:

CON
_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000
OBJ
pst : "Parallax Serial Terminal"
VAR
byte Counter
long stack1[50], stack2[50]
PUB Main
cognew(trigger1(3), @stack1)
cognew(trigger2(4), @stack2)
PUB trigger1(PIN)| trigger
repeat
repeat until ina[PIN]
trigger := cnt
common2[0] := trigger
waitcnt(clkfreq * 5 + cnt) 'wait 5 sec before restart


PUB trigger2(PIN)| trigger
repeat
repeat until ina[PIN]
trigger := cnt
common2[1] := trigger'GetCNT(trigger)
waitcnt(clkfreq * 5 + cnt) ' 'wait 5 sec before restart


When i print the two variables back via serial line in one case i got
trigger1: 413511535
trigger2: 413511617
I tought that since the COG is independent and the input is tied tigether the values should be the same, what am I missing?
Any gurus out there that may enlighten me?

Comments

  • SRLMSRLM Posts: 5,045
    edited 2013-11-09 15:54
    Spin is pretty slow, and can't be used for high speed or high precision things. You'll need to either do your design in PropGCC and use CogC code or use Propeller Assembly.

    Btw, to post code you can use the [ code ] tags to preserve formatting.
  • MagIO2MagIO2 Posts: 2,243
    edited 2013-11-09 16:19
    You can also do high accuracy measurements in spin when you use the hardware counters.

    The difference you see in your code is because of each COG having been started at different times. So, one COG is processing the ina when your signal comes, the other one is processing the repeat instruction.
    If synchronize both COGs with a waitcnt the difference should be smaller.
    And using waitpne/waitpeq should also show a smaller difference.

    But smaller difference still includes some inaccuracy, because of the longer runtime of a spin instruction. So, it`s really best to use a counter as this has an accuracy with a difference of a clock cycle. Second best is PASM.
  • kuronekokuroneko Posts: 3,623
    edited 2013-11-10 00:37
    TxRx wrote: »
    When i print the two variables back via serial line in one case i got
    trigger1: 413511535
    trigger2: 413511617
    I tought that since the COG is independent and the input is tied tigether the values should be the same, what am I missing?
    SPIN code is interpreted, i.e. a PASM program (the interpreter) - in your example initiated under the hood by cognew - reads the SPIN bytecode(s) one at a time and acts on it. This aliases any cnt access to the cogs hub window (16n). Furthermore, each cog has its own slot within the hub access window (2*cogid). The measured difference is 82 (16*5 + 2), 80 cycles are due to both cogs not being synchronised, the remaining two cycles are down to their IDs being 1 apart (which is the easy bit). That said, even using waitpxx at SPIN level may not be accurate enough for you (16n granularity; while waitpxx itself is accurate to one cycle you still have to sample cnt to get a reference). However, something like this may work for you:
    CON
      _clkmode = XTAL1|PLL16X            
      _xinfreq = 5_000_000
                             
    OBJ
      pst: "Parallax Serial Terminal"
      
    VAR
      long  storage[2]
      
    PUB Main
    
      storage{0} := constant(|< 3)                  ' |
      storage[1] := constant(|< 4)                  ' communicate pin masks
      cognew(@sample, @storage{0})                  ' |
      cognew(@sample, @storage[1])                  ' start samplers
      repeat while storage{0}|storage[1]            ' ready
    
      pst.start(115200)
      
      repeat
        pst.hex(storage{0}, 8)
        pst.char(32)
        pst.hex(storage[1], 8)
        pst.char(32)
        pst.dec(||(storage[1] - storage{0}))
        pst.char(13)
        waitcnt(clkfreq + cnt)
        
    DAT             org     0
    
    sample          rdlong  mask, par               ' pin mask
                    wrlong  zero, par               ' acknowledge (optional)
    
    loop            waitpne mask, mask              ' disarm
                    waitpeq mask, mask              ' wait for rising edge
                    mov     cnt, cnt                ' sample cnt
                    wrlong  cnt, par                ' report timestamp
                    jmp     #loop                   ' again
    
    mask            res     1
    
    tail            fit
                    
    CON
      zero = $1F0                                   ' par (dst only)
      
    DAT
    
    Update: Another option may be to use a LOGIC counter in mode A & !B but that implies that the pulse on the first line is long enough to include/cover the rising edge on the second one. If so you can probably get away with a SPIN solution provided the recovery time is long enough etc. Effectively you'd setup the counter (clear phsx) then wait for both input pins going high followed by reading phsx which now contains the delta in system clocks.
    +------------------------+
        A          |                        |
            -------+                        +-----------------------
    
                               +------------------------+                       
        B           <--------->|                        |                       
            -------------------+                        +-----------
    
            !A&!B      A&!B        A&B         !A&B
    
  • TxRxTxRx Posts: 4
    edited 2013-11-11 12:02
    I like youre idea about the LOGIC counter kuroneko, but in this case it is not posible to use since A may be triggered before B and the program may also be extended to a input C.
    So the timing needs to be done with 3 inputs and anyone may be triggered before eachother.
    I tried your pasm code and it looks nice(to mee it looks like greek, but I'm working on that part :P )
    One question, why are you using different brackets on the two different storages?
    [FONT=Parallax]  storage{0} := constant(|< 3)                  ' |
    [/FONT][FONT=Parallax]  storage[1] := constant(|< 4)                  ' communicate pin masks[/FONT]
    

  • Mike GreenMike Green Posts: 23,101
    edited 2013-11-11 12:05
    In Spin, "storage" is equivalent to "storage[ 0 ]" and the "{ }" are treated as comment markers, so it's just as if the "{0}" wasn't there.
  • MagIO2MagIO2 Posts: 2,243
    edited 2013-11-11 12:21
    Kuroneko doesn't do things without a good purpose, which might need an extra explaination!
    So, my guess is that storage[ 0 ] is for the spin compiler like long[ @storage + 0 ], which means that there is a runtime addition which only wastes some cycles. You could also skip the [ 0 ] completely, but it makes the code more readable if you show that storage is an array. That's where { 0 } comes into the game, it does not change anything but the readability.
  • jmgjmg Posts: 15,173
    edited 2013-11-11 12:24
    Mike Green wrote: »
    In Spin, "storage" is equivalent to "storage[ 0 ]" and the "{ }" are treated as comment markers, so it's just as if the "{0}" wasn't there.

    is there some code cost to actually writing storage[ 0 ], over storage{0} ?
  • jmgjmg Posts: 15,173
    edited 2013-11-11 12:58
    TxRx wrote: »
    So the timing needs to be done with 3 inputs and anyone may be triggered before each other.

    WAITPxx can have a mask, so you can test 3 pins and WAITPNE until any of those 3 changes, then you can flip the mask to 2 pins, and wait for the stop ?

    If you capture the pin-triplet at the same time as the CNT, you can work out later, which pin-states drove the events
  • kuronekokuroneko Posts: 3,623
    edited 2013-11-11 16:14
    jmg wrote: »
    is there some code cost to actually writing storage[ 0 ], over storage{0} ?
    Two bytes for each occurrence.
  • kuronekokuroneko Posts: 3,623
    edited 2013-11-11 16:21
    TxRx wrote: »
    I like youre idea about the LOGIC counter kuroneko, but in this case it is not posible to use since A may be triggered before B and the program may also be extended to a input C.
    Well, counters can also do A <> B (not equal) so either of two inputs can change first. As for three inputs, maybe there is some magic to cover that with 2 counters (A/B, A/C) but I haven't looked at the details yet. My listed solution would work for 3 inputs but would then use 3 cogs just for timestamp capture which may or may not be acceptable for your project.
  • TxRxTxRx Posts: 4
    edited 2013-11-12 03:31
    Thanks for all inputs so far!
    To use 3 cogs for the inputs is ok in this case, I'm probably only going to end up using 5-6 cogs for this project.

    In the code i also see that you are moving cnt to cnt, is one cnt pointing to local cog RAM? In that case where is that declared in the code?
    [COLOR=#3E3E3E][FONT=Parallax]                
    mov     cnt, cnt                ' sample cnt
    [/FONT][/COLOR][COLOR=#3E3E3E][FONT=Parallax]wrlong  cnt, par                ' report timestamp[/FONT][/COLOR]
    

    Day to day I'm usually coding in C#/Java and this asm coding really makes my brain twitch :innocent:
  • kuronekokuroneko Posts: 3,623
    edited 2013-11-12 04:48
    TxRx wrote: »
    In the code i also see that you are moving cnt to cnt, is one cnt pointing to local cog RAM? In that case where is that declared in the code?
    cnt in the destination slot (left argument/parameter) accesses the shadow register, in the source slot the h/w counter. So in this case it's simply a temporary register. Given that the actual source for wrlong is in the destination slot it's a perfect match. IOW the two lines are equivalent to:
    mov     temp, cnt
    wrlong  temp, par
    
  • kuronekokuroneko Posts: 3,623
    edited 2013-11-15 03:31
    kuroneko wrote: »
    As for three inputs, maybe there is some magic to cover that with 2 counters (A/B, A/C) but I haven't looked at the details yet.
    Turns out that a single cog can handle two inputs with system clock resolution provided that the pulses overlap (to get some downtime for processing). A third (and fourth) input would then require one additional cog (instead of one cog/input).
  • TxRxTxRx Posts: 4
    edited 2013-11-18 12:36
    kuroneko wrote: »
    Turns out that a single cog can handle two inputs with system clock resolution provided that the pulses overlap (to get some downtime for processing). A third (and fourth) input would then require one additional cog (instead of one cog/input).

    nice! I have not looked into this yet.
    My solution so far is as follows:
    dat
    entry         org
    Initialize
                  mov       t2,           #1        wz      'Configure Input pin
                  rdlong    t2,           par               'pin mask             
                  muxz      dira,         t2                'Set pin as Input       
    Loop
                  test      t2,            ina          wz  'Test InpinMask against the I/O port
                  if_z      jmp            #Loop            'If InPin = 0 jump back to Loop, else continue
                  mov       cnt, cnt                        'sample cnt
                  wrlong    cnt, par                        'report timestamp 
                  mov     waitcntTimeVar, loopTimeInCycles  'WAIT COMMAND, NOT WORKING
                  add     waitcntTimeVar, cnt               'WAIT COMMAND, NOT WORKING 
                  waitcnt waitcntTimeVar, loopTimeInCycles  'WAIT COMMAND, NOT WORKING        
                  jmp       #Loop                           'start over again
    
    
    t2            res       1                               'Input Mask
    loopTimeInCycles long 800000
    waitcntTimeVar   res  1
    

    Only problem with this solution is that i wold like to have a variable(appx 200ms) delay before jumping back to Loop. But this delay does not work.. :(
  • kuronekokuroneko Posts: 3,623
    edited 2013-11-18 15:43
    @TxRx: all res statements have to come last (i.e. after code and data). Also, assuming the pin mask is non zero a rdlong t2, par wz will save you one insn (and you could replace t2 by dira).
    dat
    entry         org
    Initialize
                  mov       t2,           #1        wz      'Configure Input pin
                  rdlong    t2,           par               'pin mask             
                  muxz      dira,         t2                'Set pin as Input       
    [COLOR="#FF0000"]Loop
                  test      t2,            ina          wz  'Test InpinMask against the I/O port
                  if_z      jmp            #Loop            'If InPin = 0 jump back to Loop, else continue
    [/COLOR]              mov       cnt, cnt                        'sample cnt
                  wrlong    cnt, par                        'report timestamp 
                  mov     waitcntTimeVar, loopTimeInCycles  'WAIT COMMAND, NOT WORKING
                  add     waitcntTimeVar, cnt               'WAIT COMMAND, NOT WORKING 
                  waitcnt waitcntTimeVar, loopTimeInCycles  'WAIT COMMAND, NOT WORKING        
                  jmp       #Loop                           'start over again
    
    [COLOR="#FFA500"]loopTimeInCycles long 800000[/COLOR]
    [COLOR="#020FC0"]t2            res       1                               'Input Mask[/COLOR]
    waitcntTimeVar   res  1
    
    Note that this solution doesn't give you system clock resolution.
Sign In or Register to comment.