Shop OBEX P1 Docs P2 Docs Learn Events
Quick timing question using the counter — Parallax Forums

Quick timing question using the counter

CncjerryCncjerry Posts: 64
edited 2012-08-02 16:33 in Propeller 1
Ok, so I tried an arduino first and found that their timer, unless there is an assembly mode to make it faster, was limited to 4us (microseconds) resolution.

So if I use the counter, can I time events at better than 4us?

Thanks

Comments

  • CncjerryCncjerry Posts: 64
    edited 2012-08-01 17:55
    I just did a quck test using some of the serial code to send the data to the serial terminal:/[
    [code] <- what are the code inserts agan??

    Time1:=Cnt
    Time2:=Cnt
    pst.Dec(Time1)
    pst.Chars(pst#NL, 2)
    pst.Dec(Time2)

    [code end] <- as above

    the two values received on the terminal from the successive CNT values differ by 368 as below. Would that be 368 80mhz clock ticks? If so, if I am doing the math correctly, this would be 4.6 microseconds, correct? This was in spin. I have done some playing around with assembly, would that return faster?
    13455035
    13455403
  • kuronekokuroneko Posts: 3,623
    edited 2012-08-01 18:07
    You want [noparse]
    
    [/noparse] tags. As for resolution, sampling cnt with two (normal) consecutive (PASM) insns will give you 4 system clock cycles difference. Counter actions are based on the system clock. So depending on how you time your events you should do better than 4us.                        
  • jmgjmg Posts: 15,183
    edited 2012-08-01 20:19
    Cncjerry wrote: »
    So if I use the counter, can I time events at better than 4us?

    What 'events' ? - Do you mean software timing, or external hardware edge timing ?

    The prop has a 32 bit timer, usually clocked at 80Mhz, so that gives a LSB of 12.5ns on Read and WAITs, but there are some latency details so you cannot read down to very low count-deltas.

    The Prop lacks a direct Edge Capture, but you can WAIT on a pin, and then read a counter, and resolve to 12.5ns, as the WAIT is one-clock-granular once it gets 'primed'.
  • CncjerryCncjerry Posts: 64
    edited 2012-08-01 22:24
    I am building a balistic chronograph and I have it working with the arduino mega board but the resolution of the timer is limited to 4us which resolves to 1% at 4500fps. This is fine, but we can do better!

    So basically an optical sensor array is built into three screens spaced precisely 1ft apart. The sensor raises a pulse as the projectile enters each screen. The code saves the timer on the arduino and then does some simple calculations to get fps and averages across the three screens and if we can get enough timer resolution, then possibly the balistic coefficient which is how much the projectile slows down range.

    I am using the following code to test the assembly timer and I get the same answer for each successive sample for some reason. I would expect them to be off a little, no? Even if I put some junk instructions between the mov start_time,cnt instructions, I still get the same. Any ideas?

    I thnk this is going to work. I had to give-up on my last prop project as I ran out of pins. This one only uses a few, less than 10.

    Thanks.

    oh, and btw: there are fewer idiots on this forum than the arduino forum, by far. Except now that I am here, it went up by 1.

    Jerry
    Var
      LONG Time1
      LONG Time2
      long delta
    CON
       
      _clkmode = xtal1 + pll16x                             ' Crystal and PLL settings.
      _xinfreq = 5_000_000                                  ' 5 MHz crystal (5 MHz x 16 = 80 MHz).
    OBJ
      pst    : "Parallax Serial Terminal"                   ' Serial communication object
    PUB go | value
      pst.Start(115200)   
      pst.Str(String("TEsting"))                          ' Heading
    
     cognew(@Entry, @Time1)      ' Launch a new cog to read time samples 
      repeat                                                                        ' Main loop                        
        value := pst.DecIn                                                          ' Get value
        pst.Chars(pst#NL, 2)                                                         
        pst.Dec(Time1)
        pst.Chars(pst#NL, 2)
        pst.Dec(Time2)
        pst.Chars(pst#NL, 2)
        delta:=Time2-Time1
        pst.Dec(delta)
        pst.Chars(pst#NL, 2)
                                                                  
    DAT
                  org  0
    Entry         mov       addr,par
                  mov       addr2,par
                  add       addr2,#4
                  mov       wait,cnt
                  add       wait,waittime
    loop
                  waitcnt   Wait, waittime                  ' wait for the voltage to decay
                  mov       start_time,CNT
                  mov       stop_time,CNT
                  wrlong    start_time,addr
                  wrlong    stop_time,addr2
                  jmp       #loop
    start_time    LONG      
    stop_time     LONG
    WaitTime      long      80_000_000/40
    wait          res       1
    addr          res       1
    addr2         res       1 
    
  • kuronekokuroneko Posts: 3,623
    edited 2012-08-01 22:34
    Cncjerry wrote: »
    start_time    LONG      [COLOR="#FF0000"]0[/COLOR]
    stop_time     LONG      [COLOR="#FF0000"]0[/COLOR]
    WaitTime      long      80_000_000/40
    wait          res       1
    addr          res       1
    addr2         res       1 
    
    Just give your longs some space (res 1 will do as well, after all other longs). Otherwise they are just an alias for WaitTime.
  • LawsonLawson Posts: 870
    edited 2012-08-01 22:58
    Sounds like you have at least 100uS between triggers. In that case, Spin is plenty fast. Below is the core timing loop I'd use. The Waitpne/waitpeq pair synchronizes to the first edge. Next the symmetrical code to time each pin hides the Spin overhead. Finally I calculate the differences and report the results. I've also attached the whole example code.
    'since we have >100uS between optical trap triggers, spin is plenty fast
      repeat
        waitpne(pin1_mask, pin1_mask,0)                    'wait for the first pin to be low.  (synch to pulse train)
        waitpeq(pin1_mask, pin1_mask,0)                     'wait for the first pin to go high. (first trigger)
        c1 := cnt                   'grab the current value of CNT
        waitpeq(pin2_mask, pin2_mask,0)
        c2 := cnt
        waitpeq(pin3_mask, pin3_mask,0)
        c3 := cnt
        'all data has been captured, spit it out on the serial port
        c3 -= c2                    'c3 is now the number of clocks between the 2nd and 3rd triggers
        c2 -= c1                    'c2 is now the number of clocks between the 2nd and 1st triggers
        pst.str(string("first time difference [nS] "))
        pst.dec(c2*25/2)
        pst.str(string(" Second dif [nS] "))
        pst.dec(c3*25/2)
        pst.char(pst#NL)
    

    Lawson
  • jmgjmg Posts: 15,183
    edited 2012-08-02 02:10
    Cncjerry wrote: »
    So basically an optical sensor array is built into three screens spaced precisely 1ft apart. The sensor raises a pulse as the projectile enters each screen.

    How wide is the pulse, and what sort of variance/slope do the sensors have, and will one edge be more precise than the other ?

    The code in #7 is a nice starting example.

    If your sensor is symmetric in rise/fall, but somewhat variable in width, you could try #7, then try a both edges capture - you may find the average of leading/trailing edges, gives a better variation tolerance, and if you can fire projectiles in both directions you can start to calibrate sensor-sensor skews.
  • CncjerryCncjerry Posts: 64
    edited 2012-08-02 08:18
    There is something going on that I don't understand. It is almost like start_time and stop_time point to the same variable. If I read the cnt and then store it immediately, I get the correct numbers. It must have something to do with post #6. I think that will solve the problem of getting the same value back from cnt. Drove me crazy last night.

    I put a DSO on the pulse but don't remember the timing. It isn't that fast since the DSO I was using was an old Tektronix and it only is about a 20mhz. I think it only stores at 1msp. I am not measuring the pulse at all, just the timing between the three pulses coming to three different pins. I doubt the three screens will all detect the projectile at the same point if you see what I mean so the arduino with 4us resolution is probably not the weak link. As long as it is consistent I should be ok.

    The circuitry seems pretty solid on the detect side and I get amazing results. I looked at using spin but the resolution was 4.6us if I read the cnt successively and since that was slightly worse than the arduino C++, I would just use that board and maybe switch to ASM but I am more familiar with the pasm.

    Thanks for the help and also the timng code in 7, the long allocation in #6 and I like the calibration idea from #8 with firing in both directions. hadn't thought of that one.


    Jerry
  • CncjerryCncjerry Posts: 64
    edited 2012-08-02 11:41
    so the code below is working almost as expected with the exception that the difference between the cnt moves varies. I would have thought it would be constant. If I take the wait out of the middle between the two mov's then I get a constant 4. If I put the wait back in with a count of 200, I get something around 200 + 31. So it will be like 231, then 247, etc. This doesn't impact my project, just curious. I added the wait to ensure the values weren't being accessed by the main cog between prints. It could still be the problem.
    DAT
                  org  0
    Entry         mov       addr,par
                  mov       addr2,par
                  add       addr2,#4
                  mov       wait,cnt
                  add       wait,waittime
    loop
                  mov       start_time,cnt
                  waitcnt   Wait, WaitTime                  ' wait a while  
                  mov       stop_time,cnt
                  wrlong    start_time,addr  
                  wrlong    stop_time,addr2
                   
                  jmp       #loop
    start_time    LONG      0
    stop_time     LONG      0
    WaitTime      long      200
    wait          res       1       
    addr          res       1
    addr2         res       1      
    
    
  • jmgjmg Posts: 15,183
    edited 2012-08-02 14:44
    Cncjerry wrote: »
    I looked at using spin but the resolution was 4.6us if I read the cnt successively and since that was slightly worse than the arduino C++, I would just use that board and maybe switch to ASM but I am more familiar with the pasm.

    Thanks for the help and also the timng code in 7, the long allocation in #6 and I like the calibration idea from #8 with firing in both directions. hadn't thought of that one.

    Read #7 carefully, and especially these lines :
        waitpne(pin1_mask, pin1_mask,0)                    'wait for the first pin to be low.  (synch to pulse train)
        waitpeq(pin1_mask, pin1_mask,0)                     'wait for the first pin to go high. (first trigger)
    

    Note they are WAIT commands, but waitpne/waitpeq act on physical pin levels, not counter based delays.
    Even tho they are in Spin, they use the PASM opcodes (also called waitpne/waitpeq) and so have the same precision as PASM.


    There is one difference between Spin/PASM in this use, which is Spin does have a time-per-line, so has a minimum pulse width.
    requirement impact (but no edge precision impact).

    If you measured 4.6us for two adjacent lines of spin, then the first line in #7 of
        waitpne(pin1_mask, pin1_mask,0)                    'wait for the first pin to be low.  (synch to pulse train)
    

    will wait for seconds if it needs to, before advancing to the next line
        waitpeq(pin1_mask, pin1_mask,0)                     'wait for the first pin to go high. (first trigger)
    

    but if the Pulse width is less than 4.6us ===\___/==== then the second wait condition will be already true, and it will not time from the RISE edge, but actually be paced off the first falling edge.

    The same effect occurs in PASM, but now the software imposed Pulse min is the 6+ SysClks (~ 75ns) of WAITPNE
    Correct Edge resolution is 12.5ns in both cases, once you meet the Pulse Width min.

    The trick to best resolution, is to avoid entirely any software pin-polling, and instead use the hardware as above.

    The prop has a 32 bit counter, so there is no excuse not to edge capture to SysClk precisions.

    Also the code you are quoting, does not seem to read any pins, is it just test code ?
  • jmgjmg Posts: 15,183
    edited 2012-08-02 14:58
    Cncjerry wrote: »
    If I put the wait back in with a count of 200, I get something around 200 + 31. So it will be like 231, then 247, etc. This doesn't impact my project, just curious.

    I've unrolled the loop, and pegged it to the wait, which should make it clearer .
      mov       start_time,cnt
    
      waitcnt   Wait, WaitTime       ' Snap to SysClk + WaitTime
      mov       stop_time,cnt
      wrlong    start_time,addr  
      wrlong    stop_time,addr2
      mov       start_time,cnt
    
      waitcnt   Wait, WaitTime      ' Snap to SysClk + WaitTime
      mov       stop_time,cnt
      wrlong    start_time,addr  
      wrlong    stop_time,addr2
      mov       start_time,cnt      ' Capture counter, again after two variable opcodes
    



    A CNT read straight after a waitcnt is always +waitTime SysClk snapped, but what about the CNT read BEFORE ?
    It is not quite sysclk locked, as the wrlong opcodes can have variable lengths, and that is what your first capture is reading.

    Note what you call stop_time, is actually start-time, (wait locked) and the start_time you write, is from the last loop
  • CncjerryCncjerry Posts: 64
    edited 2012-08-02 16:19
    jmg, yes this is test code. I am just trying to figure out how the prop works with the cnt function. I could have been getting the stop_time updated and then looping around and getting the start_time which could have caused the problem. I changed the code to put a valid_flag to ensure the two times are sequential and it looks clean now.

    So I will be doing a waitpne(pin1_mask, pin1_mask,1) on one pin, then grabbing the cnt, waitpne(pin2_mask, pin2_mask,1) on a second pin, then calculating the delta and flagging Cog0 to do the other calcs.

    I need to measure the time between the two pulses on the two pins, then calc the delta in clocks and convert to microseconds. 1 / microseconds gives me feet per second. Whole numbers are fine.

    So now that the I have an understanding of the cnt timer I can move on to the other minor code.


    Thanks again.


    Jerry
  • LawsonLawson Posts: 870
    edited 2012-08-02 16:33
    Cncjerry wrote: »
    jmg, yes this is test code. I am just trying to figure out how the prop works with the cnt function. I could have been getting the stop_time updated and then looping around and getting the start_time which could have caused the problem. I changed the code to put a valid_flag to ensure the two times are sequential and it looks clean now.

    So I will be doing a waitpne(pin1_mask, pin1_mask,1) on one pin, then grabbing the cnt, waitpne(pin2_mask, pin2_mask,1) on a second pin, then calculating the delta and flagging Cog0 to do the other calcs.

    I need to measure the time between the two pulses on the two pins, then calc the delta in clocks and convert to microseconds. 1 / microseconds gives me feet per second. Whole numbers are fine.

    So now that the I have an understanding of the cnt timer I can move on to the other minor code.


    Thanks again.


    Jerry


    @Jerry: Please try the test code I attached to post #7. If the pins are edited to match your hardware, it should just work. (it assumes the optical triggers are logic low between pulses)

    Second, CNT is REALLY simple. it's just a 32-bit counter that increments every clock and can be read by any core.

    Lawson
Sign In or Register to comment.