Shop OBEX P1 Docs P2 Docs Learn Events
Measuring time it takes to execute code for accuracy — Parallax Forums

Measuring time it takes to execute code for accuracy

ryfitzger227ryfitzger227 Posts: 99
edited 2013-09-07 10:02 in Propeller 1
I'm creating a timer for a project that I'm working on and I need it to be as accurate as it can be. The resolution is 1uS.

All I'm trying to do is figure out all of my overhead and then subtract that from the final clock cycles. That way I don't have the time it takes to execute the code into my elapsed time.

Here's my code:
PUB main(tadr, tstart, tstop, readyadr, stopadr) : ticks
  
  ticks := cnt
  repeat
    repeat until ina[tstart] == 1 and BYTE[readyadr] == 1
    repeat until ina[tstart] == 0
      ticks := cnt
  
    repeat until ina[tstop] == 1 or BYTE[stopadr] == 1
    repeat until ina[tstop] == 0 or BYTE[stopadr] == 1    
    LONG[tadr] := cnt - ticks
    if BYTE[stopadr] == 1
      BYTE[stopadr] := 0
    BYTE[readyadr] := 0

What's a good way to go about this? (Especially with all the repeat until.. loops).

Another thing. If I find out my overhead will that ever change or will it always be the same? For example if i set my overhead to 2500 clock cycles and subtract that from the final clock cycles elapsed time will it always be 2500 clock cycles or could that change? If so, depending on what?

Thanks for your help.

Comments

  • jmgjmg Posts: 15,173
    edited 2013-06-05 15:01
    I
    Another thing. If I find out my overhead will that ever change or will it always be the same?

    It will if you edit the code ;) , so usually it is best to avoid such skew situations.

    Simple ways to do this are
    * make the capture lines as identical as you can
    * sometimes even the same line can be re-used by applying an index.
  • AribaAriba Posts: 2,690
    edited 2013-06-05 18:01
    The problem is that a repeat loop like:
    repeat until ina[tstop] == 0 or BYTE[stopadr] == 1
    
    takes more than 1 us (more like 10us), so you will not get a resolution of 1 us with that code.

    You can use WAITPEQ/WAITPNE commands, they are very precise, but do not allow the test for abort per
    BYTE[stopadr].
    Here is a possible code:
    PUB main(tadr, tstart, tstop, readyadr, stopadr) : ticks, endticks
      
      repeat
        repeat until ina[tstart] == 1 and BYTE[readyadr] == 1
        waitpne(|<tstart,|<tstart,0)
          ticks := cnt
      
        repeat until ina[tstop] == 1 or BYTE[stopadr] == 1
        if byte[stopadr] <> 1
          waitpne(|<tstop,|<tstop,0)
          endticks := cnt
          LONG[tadr] := endticks - ticks
        BYTE[stopadr] := 0
        BYTE[readyadr] := 0
    
    If you really need the abort while waiting for ina[tstop]==0 then you can do that with a free pin which you incorporate into the mask at WAITPNE.

    Andy
  • ryfitzger227ryfitzger227 Posts: 99
    edited 2013-06-06 11:20
    Ariba wrote: »
    The problem is that a repeat loop like:
    repeat until ina[tstop] == 0 or BYTE[stopadr] == 1
    
    takes more than 1 us (more like 10us), so you will not get a resolution of 1 us with that code.

    You can use WAITPEQ/WAITPNE commands, they are very precise, but do not allow the test for abort per
    BYTE[stopadr].
    Here is a possible code:
    PUB main(tadr, tstart, tstop, readyadr, stopadr) : ticks, endticks
      
      repeat
        repeat until ina[tstart] == 1 and BYTE[readyadr] == 1
        waitpne(|<tstart,|<tstart,0)
          ticks := cnt
      
        repeat until ina[tstop] == 1 or BYTE[stopadr] == 1
        if byte[stopadr] <> 1
          waitpne(|<tstop,|<tstop,0)
          endticks := cnt
          LONG[tadr] := endticks - ticks
        BYTE[stopadr] := 0
        BYTE[readyadr] := 0
    
    If you really need the abort while waiting for ina[tstop]==0 then you can do that with a free pin which you incorporate into the mask at WAITPNE.

    Andy

    Andy,

    I don't quite understand what you're saying. I found the following link http://www.parallax.com/portals/0/help/P8X32A/QnaMobile/Advanced/Content/CodeTeqTopics/CodeExeTime.htm and used the system timer method to calculate the time it took to execute each repeat block. Basically I commented out everything but the line of code I was trying to calculate then used the system timer to calculate the time it took. It took some time to calculate the time so I subtracted 368 clock cycles (the time it took to execute the "calculation code"). It came up with about 1842 clock cycles for all of the repeat until block (besides the one without the or/and in it). Now I just subtract 6944 clock cycles from the final result.

    I did that correctly, right?

    And I can't put the stopt variable somewhere else, the value comes from the serial port.

    - Ryan
  • jmgjmg Posts: 15,173
    edited 2013-06-06 13:59
    .... It came up with about 1842 clock cycles for all of the repeat until block (besides the one without the or/and in it). Now I just subtract 6944 clock cycles from the final result.

    I did that correctly, right?

    And I can't put the stop variable somewhere else, the value comes from the serial port.

    It is not just the path delay you need to consider but also the granularity,
    Andy says this line
    repeat until ina[tstop] == 0 or BYTE[stopadr] == 1
    need ~ 10us, so that is how often it checks a pin, which limits your time-lsb-precision.

    If your serial cog that writes to [stopadr] is changed to instead set/clr a spare pin, then you can sense two (pin) flags in a single WAITPNE opcode, and it will exit on either the Stop, or the sampled pin. ( Granularity on a WAITPNE opcode, is 12.5ns )

    You may be able to combine Ready and Stop ?
  • ryfitzger227ryfitzger227 Posts: 99
    edited 2013-06-11 13:07
    The biggest question here is will that 10us ever change.

    Lets say that it takes 10us to check the pin. Well will that vary?

    I'm worried about accuracy to an extent. I don't want it to be off by a couple milliseconds or so, but 10 us really isn't a problem. I can't have the time it takes to check a pin to vary. Everyone will be timed using the same timer, so it doesn't matter if it's off 10us EVERY time.

    I hope that makes sense.

    - Ryan
  • AribaAriba Posts: 2,690
    edited 2013-06-11 15:24
    10us was just a guess, now I have measured the loop time. With 80 MHz a loop like this:
    repeat until ina[pin]==0 takes 12 us and with two tests: repeat until ina[pin]==0 or byte[xy] > 0 it takes 24us.

    Yes, the time will vary, that's the problem. Spin polls the pin every 24us, one time the change of the pin state happens right before the poll, another time it happens just after the poll and it needs one loop more to detect the change. So the time between pin change and detection can vary from 0 to 24us.
    And you can not compensate this with subtracting a constant value from the measured time, the problem is that the variation occures at the begin and the end pulse, and both vary differently.

    To make it precise you need to poll faster and that is what WAITPNE makes, it polls with 12.5 ns! If the code after WAITPNE is similar for start and stop pulse then you don't need to subtract an "overhead" the delay is the same and compensates itself.

    Andy
  • ryfitzger227ryfitzger227 Posts: 99
    edited 2013-06-29 10:57
    Ariba wrote: »
    The problem is that a repeat loop like:
    repeat until ina[tstop] == 0 or BYTE[stopadr] == 1
    
    takes more than 1 us (more like 10us), so you will not get a resolution of 1 us with that code.

    You can use WAITPEQ/WAITPNE commands, they are very precise, but do not allow the test for abort per
    BYTE[stopadr].
    Here is a possible code:
    PUB main(tadr, tstart, tstop, readyadr, stopadr) : ticks, endticks
      
      repeat
        repeat until ina[tstart] == 1 and BYTE[readyadr] == 1
        waitpne(|<tstart,|<tstart,0)
          ticks := cnt
      
        repeat until ina[tstop] == 1 or BYTE[stopadr] == 1
        if byte[stopadr] <> 1
          waitpne(|<tstop,|<tstop,0)
          endticks := cnt
          LONG[tadr] := endticks - ticks
        BYTE[stopadr] := 0
        BYTE[readyadr] := 0
    
    If you really need the abort while waiting for ina[tstop]==0 then you can do that with a free pin which you incorporate into the mask at WAITPNE.

    Andy

    Sorry for all of the stupid questions, but I think I got it now.

    I took the code you gave me and modified it a little bit to make it work. What I'll do about the stop variable is that the cog that reads the serial port will set a pin high when the serial input says to do so. Jmg said that I could use waitpne to watch both pins (the start or stop infrared input AND the manual stop timer pin). How would I do that with waitpne?

    Here's the code I have so far. I added some comments to better understand what everything does.
    PUB main(tadr, tstart, tstop, readyadr, stopadr) : ticks
      
      ticks := cnt
      
      repeat 'main repeat
    
    
        repeat until BYTE[readyadr] == 1 'Don't monitor pin status until the computer is ready for timing
      
        waitpne(|<tstart,|<tstart,1) 'wait for start infrared to be broken
        waitpne(|<tstart,|<tstart,0) 'wait for start infrated to be reflecting
          ticks := cnt 'get timer value
      
        waitpne(|<tstop,|<tstop,1) 'wait for stop infrared to be broken
        waitpne(|<tstop,|<tstop,0) 'wait for stop infrared to be reflecting 
        LONG[tadr] := cnt - ticks 'calculate the final result
       repeat until BYTE[readyadr] == 0 ' Don't continue until the computer says the timing is over 
    

    Thanks for everyone's help so far!
  • AribaAriba Posts: 2,690
    edited 2013-06-29 18:10
    The third parameter for WAITPNE is not the state, it selects between PORTA and PORTB. There is currently no Propeller with an implemented PORTB, so this parameter is always 0.
    Waitpne works like that:
    WAITPNE(pinstates, pinmask, 0) is a fast version of: REPEAT UNTIL (INA & pinmask) <> pinstates
    so it reads the input pins and sets all pins that are not 1 in the mask to 0. This value is compared to the state.
    The mask says what pins to check, the state says what the state of the checked pins should be (WAITPEQ) or not be (WAITPNE).

    So this is your code with an additional check for a "manstop" pin:
    PUB main(tadr, tstart, tstop, readyadr, manstop) : ticks | mask1, mask2
      
      mask1 := |<tstart + |<manstop   'manstop is the pin number that the serial object sets on manual stop
      mask2 := |<tstop + |<manstop
      
      repeat 'main repeat
    
        repeat until BYTE[readyadr] == 1 'Don't monitor pin status until the computer is ready for timing
      
        waitpne(0,mask1,0) 'wait for start infrared to be broken (tstart=1 or manstop=1)
        waitpne(|<tstart,mask1,0) 'wait for start infrated to be reflecting (tstart=0 or manstop=1)
        ticks := cnt 'get timer value
      
        waitpne(0,mask2,0) 'wait for stop infrared to be broken (tstop=1 or manstop=1)
        waitpne(|<tstop,mask2,0) 'wait for stop infrared to be reflecting (tstop=0 or manstop=1)
        LONG[tadr] := cnt - ticks 'calculate the final result
        if ina[manstop]
          ticks := 0          'return value in case of manual stop
    
        repeat until BYTE[readyadr] == 0 ' Don't continue until the computer says the timing is over
    
    
    And here a little description of how it checks the pins. To make it simple pin0 is the manstop and pin1 is the tstart pin:
    manstop pin-.
    IR pin-----.|
               ||
      waitpne(%00,%11)   waits until one of the pins is 1 (or both)  INA & %11 <> %00
        if not manstop we have %10 at the pins here
      waitpne(%10,%11)   waits until %00 or %01 or %11 (=IR pin low, or manstop=1 or both) INA & %11 <> %10
    

    Andy
  • ryfitzger227ryfitzger227 Posts: 99
    edited 2013-09-04 17:39
    Andy,

    First off. Sorry for reviving an old thread. I'm starting to work on another part of this project. This time I'm trying to get it to stop until BOTH pins 1 and 3 equal 1. Not or - like before - but both pins. I tried using waitpne, but couldn't get it working so I used waitpeq.. That worked. I really just wanted to know if waitpeq polled differently that waitpne. You mentioned earlier that waitpne polled in 12.5ns, so is that true also for waitpeq? If not, how can I make the below code to work for waitpne?
    waitpeq(10, 10, 0)
    

    I've done some extensive reading in waitpeq/waitpne and other spin elements. So maybe I won't be asking too many stupid questions from now on. :lol:

    Thanks for all of your help on this!

    - Ryan
  • AribaAriba Posts: 2,690
    edited 2013-09-05 02:29
    Andy,

    First off. Sorry for reviving an old thread. I'm starting to work on another part of this project. This time I'm trying to get it to stop until BOTH pins 1 and 3 equal 1. Not or - like before - but both pins. I tried using waitpne, but couldn't get it working so I used waitpeq.. That worked. I really just wanted to know if waitpeq polled differently that waitpne. You mentioned earlier that waitpne polled in 12.5ns, so is that true also for waitpeq? If not, how can I make the below code to work for waitpne?
    waitpeq(10, 10, 0)
    


    I've done some extensive reading in waitpeq/waitpne and other spin elements. So maybe I won't be asking too many stupid questions from now on. :lol:

    Thanks for all of your help on this!

    - Ryan
    Yes WAITPNE and WAITPEQ have the same timing. Im pretty sure they use the same hardware on the chip only that one inverses the output of the comparator.
    You can not always achieve the same result with WAITPNE and WAITPEQ otherwise they would not both exist. If you check 2 pins as in your case here, then there are 4 possible states. With WAITPEQ you check for one state out of 4, with WAITPNE you check for 3 states out of 4 (= all but one state).

    Andy
  • LoopyBytelooseLoopyByteloose Posts: 12,537
    edited 2013-09-07 10:02
    My impression is that Spin is just too much overhead for getting down to 1 usec resolution in precise delays.

    Ideally, this should all be done on one Cog in PASM to get that fast. Whenever you have to refer to Hubram, you create a degree of uncertainty about the delay timing.

    Earlier this year, I got deeply involved with a stepper motor program that used increments of small delays from a look up table. Delay timings varied from 1 to 999 increments.

    I used pfth, which is Dave Hein's Forth, and have gotten down to about 35 usec delays with reliable results every time to the exact clock count. But I could not get lower than that because of the overhead of the Forth dictionary calls.

    I strongly suspect that Spin is slower than Forth and that C would also be slower than Forth.

    To get a good 1 usec timing routine in PASM, you would have to account for the clock cycles of each instruction in the the code. Most are 4 clock cycles, but branches can be either 4 or 8 and have to be coded to the longer jumps are not affecting the timing.
Sign In or Register to comment.