Shop OBEX P1 Docs P2 Docs Learn Events
Calculating microseconds from CNT difference. — Parallax Forums

Calculating microseconds from CNT difference.

ryfitzger227ryfitzger227 Posts: 99
edited 2014-05-16 16:26 in Propeller 1
Hey guys.

I'm working on a timing project with the Propeller and I've came across a problem I just can't figure out. I've been using the following code to time between two push button presses (like a stopwatch).
waitpne(0,mask1,0) 'wait for start infrared to be broken (tstart=1 or manstop=1)
    ticks := cnt 'get timer value
    
    waitpne(0,mask2,0) 'wait for stop infrared to be broken (tstop=1 or manstop=1)
    
    LONG[tadr] := cnt - ticks 'calculate the final result

Then on the main cog, I'm sending the final result to the computer by using the Extended Full Duplex Serial object. Once it gets to the computer I take the clock cycles difference, divide it by 80 for microseconds, and divide it again by 1,000,000 to put the microseconds into seconds. This works perfect up until the point where the difference becomes a negative (about 26 seconds). I read on another thread that the correct way to do this would be something like this..
if timer < 0
     timer := timer >> 1

I've noticed that this takes away the negative, and gives half of the actual time. The only thing is, the computer doesn't know that it is half and just looks at it like 15 seconds, instead of 30 seconds. I have to find some way to multiply that by two before it's sent to the computer. I tried to just multiply it by 2, but of course that didn't work since the shift right operator is like dividing it by two. It just gives me the same negative number I had before. So all I'm really asking is how do I double the clock cycles result after I use the shift right operator so I can then convert it into microseconds and then later seconds with 6 decimal places. I've tried everything I know to try along with reading the manual. It seems like it would be so simple, but it has really kicked me in the butt!

Thanks.
- Ryan

Comments

  • BasilBasil Posts: 380
    edited 2014-05-14 18:33
    Hey guys.

    I'm working on a timing project with the Propeller and I've came across a problem I just can't figure out. I've been using the following code to time between two push button presses (like a stopwatch).
    waitpne(0,mask1,0) 'wait for start infrared to be broken (tstart=1 or manstop=1)
        ticks := cnt 'get timer value
        
        waitpne(0,mask2,0) 'wait for stop infrared to be broken (tstop=1 or manstop=1)
        
        LONG[tadr] := cnt - ticks 'calculate the final result
    

    Then on the main cog, I'm sending the final result to the computer by using the Extended Full Duplex Serial object. Once it gets to the computer I take the clock cycles difference, divide it by 80 for microseconds, and divide it again by 1,000,000 to put the microseconds into seconds. This works perfect up until the point where the difference becomes a negative (about 26 seconds). I read on another thread that the correct way to do this would be something like this..
    if timer < 0
         timer := timer >> 1
    

    I've noticed that this takes away the negative, and gives half of the actual time. The only thing is, the computer doesn't know that it is half and just looks at it like 15 seconds, instead of 30 seconds. I have to find some way to multiply that by two before it's sent to the computer. I tried to just multiply it by 2, but of course that didn't work since the shift right operator is like dividing it by two. It just gives me the same negative number I had before. So all I'm really asking is how do I double the clock cycles result after I use the shift right operator so I can then convert it into microseconds and then later seconds with 6 decimal places. I've tried everything I know to try along with reading the manual. It seems like it would be so simple, but it has really kicked me in the butt!

    Thanks.
    - Ryan

    Just skimmed through while at traffic lights but doesn't left shift once double the result?
  • kuronekokuroneko Posts: 3,623
    edited 2014-05-14 18:39
    Then on the main cog, I'm sending the final result to the computer by using the Extended Full Duplex Serial object. Once it gets to the computer I take the clock cycles difference, divide it by 80 for microseconds, and divide it again by 1,000,000 to put the microseconds into seconds. This works perfect up until the point where the difference becomes a negative (about 26 seconds).
    Why do you care - on the computer - whether it's negative? Can't you just treat it as an unsigned 32bit value? That said, this will still limit you to about a minute. How much do you need?
  • Duane DegnDuane Degn Posts: 10,588
    edited 2014-05-14 18:44
    Why not do the math in the computer? I don't think Spin has an unsigned long so you'd have to keep track of the highest significant bit some other way (like Phil's "umath" in the OBEX).

    Why not have the computer add POSX (2,147,483,647) to the absolute value of the number if the number is negative?

    Edit: After seeing kuroneko's post, I think there's probably a more elegant solution than what I'm suggesting. I just don't know it.
  • SRLMSRLM Posts: 5,045
    edited 2014-05-14 19:01
    Why not just calculate the difference on the Propeller, and send that? It's good up to ~50 seconds, and is fairly simple:
    start_CNT_ := CNT
    
    // Do some work
    
    elapsed_milliseconds := (CNT - start_CNT_) / (CLKFREQ / 1000);
    
    

    (taken from https://github.com/libpropeller/libpropeller/blob/master/libpropeller/stopwatch/stopwatch.h)
  • ryfitzger227ryfitzger227 Posts: 99
    edited 2014-05-14 19:28
    I'm trying to stay away from the computer as much as possible. Where I would have to put the extra math would be in a place that is very hard to debug. It's not impossible, it would just be a hassle, and so I figured it would be easier with the prop.

    I've tried using left shift before, but it just gave me the same result as multiplying it by 2 - back to the negative number.

    @SRLM
    This is the simplest code snippet of the timing system. Others have pauses in them that get their wait times from the computer in clock cycles. I subtract them out each time from the computer, since it is the only device that actually knows the value. I probably could get it to work like this.. But I'll have to change a couple hundred lines of code on the computer's side.

    Honestly, after what I've seen here I guess the easiest way would be do what Duane said - just add POSX to the absolute value of the negative number. If anybody can think of anything else, please let me know.
  • kuronekokuroneko Posts: 3,623
    edited 2014-05-14 19:39
    Would this work for you?
    CON
      _clkmode = XTAL1|PLL16X
      _xinfreq = 5_000_000
    
    OBJ
      serial: "FullDuplexSerial"
    
    PUB null | us
    
      serial.start(31, 30, %0000, 115200)
      waitcnt(clkfreq*3 + cnt)
    
      us := clkfreq / 1_000_000
      
      serial.dec(calc($70000000, us))
      serial.tx(13)
      serial.dec(calc($FF000000, us))
      serial.tx(13)
      
    PRI calc(delta, scale)
    
      if delta < 0
        return (delta >> 1) / (scale / 2)
    
      return delta / scale
    
  • JonnyMacJonnyMac Posts: 9,105
    edited 2014-05-14 21:12
    I find frequently find myself wanting to time something so I built the attached template. It even accounts for the Spin overhead which is important for very short durations.
  • ryfitzger227ryfitzger227 Posts: 99
    edited 2014-05-15 14:07
    Duane Degn wrote: »
    Why not do the math in the computer? I don't think Spin has an unsigned long so you'd have to keep track of the highest significant bit some other way (like Phil's "umath" in the OBEX).

    Why not have the computer add POSX (2,147,483,647) to the absolute value of the number if the number is negative?

    Edit: After seeing kuroneko's post, I think there's probably a more elegant solution than what I'm suggesting. I just don't know it.

    Duane,

    I tried this just now and it's not giving me the correct results. I'm stopping the timer at around 45 seconds (going by the stopwatch on my phone), but I'm getting 35 seconds as the result. When I do it for 35 seconds, I get the opposite 45 seconds. I caught the data from the Propeller and the clock cycles it sent was -659097472. I double checked the computer, and it's performing the correct math I programmed it to do.

    Absolute value of -659097472 is 659097472. 659097472 + 2,147,483,647 = 2806581119. 2806581119 / 80 = 35082263.9875us. (35.082264 seconds). Do you see what could be going wrong?

    I'm not 100% sure how the timer works after 26 seconds, so I wanted to ask someone else instead of going and just playing with the numbers. Does the timer count up to POSX and then start counting down to 0?

    Thanks.
    - Ryan
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2014-05-15 14:29
    Ryan,

    What kind of precision are you after? Whole seconds, tenths of seconds, milliseconds, or ... ?

    -Phil
  • ryfitzger227ryfitzger227 Posts: 99
    edited 2014-05-15 15:28
    Ryan,

    What kind of precision are you after? Whole seconds, tenths of seconds, milliseconds, or ... ?

    -Phil

    Phil,

    I'd like to have microseconds, if possible.
  • JonnyMacJonnyMac Posts: 9,105
    edited 2014-05-15 15:35
    If you're timing a digital input, you may want to do it in PASM for the best precision. Even though my simple timer (above) accounts for the timing setup, Spin instructions still take on the order of five microseconds so this may blur your accuracy.
  • ryfitzger227ryfitzger227 Posts: 99
    edited 2014-05-15 15:54
    Jon,

    If it's off that much I think I'll be alright. In the beginning I figured I might have to do it in PASM, but figured that it wouldn't be that inaccurate for me to use spin. I don't need the precision to be quite 1 microsecond, and as long as the time it takes doesn't vary I'll be okay. This is a racetrack timing system, so everyone will be running on the same timer - they aren't particularly worried about it being 100% accurate to 1 microsecond as long as the inaccuracy doesn't vary.. If that makes any sense..

    And I am planning on subtracting out the overhead, like you did, at the end after I get this negative thing working.
  • kuronekokuroneko Posts: 3,623
    edited 2014-05-15 16:28
    Did you try post #7?
  • ryfitzger227ryfitzger227 Posts: 99
    edited 2014-05-15 16:50
    kuroneko wrote: »
    Did you try post #7?

    I can't really try post #7, because it's converting it to microseconds and I need clock cycles for my program. The program calculates everything with the clock cycles. I would have to change about 500 lines of code to get that to work. I'm sure it would work - it's just I need clock cycles.
  • kuronekokuroneko Posts: 3,623
    edited 2014-05-15 17:32
    ... it's just I need clock cycles.
    OK, what about pretending you run with 25ns resolution? IOW calculate the delta, always shift one right (positive number, 12.5ns -> 25ns) then send this to the PC (cycle count up to $7FFFFFFF). Changing the expected resolution on the PC side shouldn't be that much work.
  • JonnyMacJonnyMac Posts: 9,105
    edited 2014-05-15 19:11
    And I am planning on subtracting out the overhead, like you did, at the end after I get this negative thing working.

    This "negative thing" is a creation of yours. Many of us have shown you successful techniques. Now... these work as long as your interval is about 26.8 seconds (assuming 80MHz clock) or shorter. Is your interval longer? If it is, no joy with a simple delta calculation.

    I have a couple projects on my desk that need me to keep track of timers where I don't have a cog to spare. I created a dirt simple object to manage timing -- so long as one of the timer methods is called before the 26.8-second limit, all is well. This is not good for microseconds, though; I'm happy with milliseconds.
  • ryfitzger227ryfitzger227 Posts: 99
    edited 2014-05-15 19:15
    kuroneko wrote: »
    ... always shift one right (positive number, 12.5ns -> 25ns) then send this to the PC...

    Well see, this is what I originally tried (I think). I shifted the negative result one to the right and sent it to the computer. It was giving me what looked like half of the actual time. Well just now I had the computer double the clock cycles and then divide by 80 (it's the same thing as having that number and dividing by 40). I'm getting the same weird results as I got when I tried Duane's suggestion - for 45 seconds the math is showing 35 seconds, for 35 seconds the math is showing 45, and now (I just tested this one) 37 seconds is showing up as 32 seconds.

    Edit:

    Okay. So I just did multiple tests without doubling the number on the computer I just left it alone. When I did a test with a time of 37 seconds, it came up on the computer as 16. 16 * 2 = 37. So the number that I'm getting from the propeller is not actually half like I previously stated.
  • ryfitzger227ryfitzger227 Posts: 99
    edited 2014-05-15 19:28
    JonnyMac wrote: »
    Is your interval longer?

    Sometimes yes. I can't be limited to 26.8 seconds. One time the time will be 5 seconds, the next 7 seconds, or even 38 seconds. It's never going to be above 50, but definitely above 26.

    Again. A lot of these things would be successful if I was doing the calculations on the Propeller. I've even made them successful by doing that. What I'm trying to do is send the clock cycles to the computer and have the computer do the calculations. If that is not possible, then that's all I need to know and I'll start working on re-doing the whole software. But if it is possible, then that is what I'd like to do. The fact is I'm getting a negative number after doing oldCnt - newCnt once I go over the 26.8-second limit, or at least that is what is being sent to the computer. All I need to know is what formula I need to use to convert that number into microseconds like I can before the 26.8-second limit.

    I'm very sorry to anyone that I confuse, it's just I'm super confused myself right now.
  • kuronekokuroneko Posts: 3,623
    edited 2014-05-15 19:29
    Let's try this (deltas calculated on the prop side):
    10 sec = $2FAF0800 cycles @80MHz, positive number
    50 sec = $EE6B2800 cycles @80MHz, negative number
    
    What you do now is shift them both right by 1 ($17D78400 and $77359400) and send them to the PC. The PC should now treat those cycle counts as 25ns steps (instead of 12.5ns). So to get the us time on the PC (1us = 40cycles @25ns) you divide by 40 which gives you 1e7us and 5e7us respectively.

    From previous comments it looks like you're using signed arithmetic on the PC so the above should work. If you were to use unsigned arithmetic then you could simply send the raw (unshifted) delta value.
  • JonnyMacJonnyMac Posts: 9,105
    edited 2014-05-15 19:31
    Sometimes yes. I can't be limited to 26.8 seconds. One time the time will be 5 seconds, the next 7 seconds, or even 38 seconds. It's never going to be above 50, but definitely above 26.

    Then you cannot do a simple delta calculation -- once you go past 26.8 seconds (at 80MHz), everything blows up. You need to take a snapshot of cnt at some interval shorter than 26.8 seconds and accumulate a larger unit (I use milliseconds in my simple timer object).

    Another thing to consider is slowing the Propeller clock. If you change the PLL to 8x (40MHz), your delta-calculation interval becomes about 53 seconds.
  • ryfitzger227ryfitzger227 Posts: 99
    edited 2014-05-15 20:04
    Well this is working great - like you have it with set values. But the problem I'm having is when I do the actual timing. I'm using a handheld stopwatch along with the propeller to double check the result and it's not coming up correct. Here's the propeller code I'm using.
    if t1 < 0
       serial.str(string("-")) 'this is just a flag to tell the computer to divide by 40 instead of 80.. it gets deleted before any calculations are made.
       t1 := t1 >> 1
       serial.dec(t1)
    

    I press the start button for the prop timer at the same time I do for the handheld stopwatch. When the stopwatch gets to a time (I've been testing 37 seconds) I press the stop button on the propeller timer. I'm just checking to see if the seconds are the same. This works fine for everything under 26 seconds and the results match. But as soon as I go above 26 seconds the results are not matching.

    Here is my latest test. I again used the handheld stopwatch as a guide. After 37 seconds the prop sent 1314564368 to the computer. That has already been shifted one to the right by the spin code above, so all the computer did was divide by 40. The final time came out as 32 seconds. As far as I can tell, the computer did the math correctly. (1314564368 / 40 = 32864109 us or 32.864109). It just didn't match up with my stopwatch's 37 seconds. I did another test with a duration of 20 seconds according to my stopwatch. The prop sent 1625543616. Since that was under 26 seconds it got divided by 80 and came out as 23319295us - which matches with the stopwatch. Why does the propeller stop matching with the stopwatch after 26 seconds?

    If I'm not making sense, just let me know. This has definitely been the worst experience I've had when trying to learn the propeller and spin.
  • ryfitzger227ryfitzger227 Posts: 99
    edited 2014-05-15 20:11
    JonnyMac wrote: »
    Then you cannot do a simple delta calculation -- once you go past 26.8 seconds (at 80MHz), everything blows up. You need to take a snapshot of cnt at some interval shorter than 26.8 seconds and accumulate a larger unit (I use milliseconds in my simple timer object).

    Another thing to consider is slowing the Propeller clock. If you change the PLL to 8x (40MHz), your delta-calculation interval becomes about 53 seconds.

    I have to show 6 decimal places and use microseconds. Using milliseconds will limit me to 4 decimal places and that is not going to work for this project. I think what I will do is just have the propeller figure out the time and then send the microseconds to the computer. By what you're saying that's probably the only way I'll be able to get the 6 decimal places. I'll just have to redo the software. Everything will work out in the long run.

    Thanks guys for your help!!
  • kuronekokuroneko Posts: 3,623
    edited 2014-05-15 20:20
    Here is my latest test. I again used the handheld stopwatch as a guide. After 37 seconds the prop sent 1314564368 to the computer.
    As you said, this value represents ~32sec. Missing 5 seems rather odd. Can you verify the clock speed (e.g. output a 1Hz signal or similar and track it over a minute)?
    CON
      _clkmode = XTAL1|PLL16X
      _xinfreq = 5_000_000
    
    PUB null : t
    
      dira[16]~~     ' LED
      
      t := cnt
      repeat
        waitcnt(t += clkfreq/2)
        !outa[16]
    
    Also, can you provide the two cnt values you got for the 37sec case?
  • JonnyMacJonnyMac Posts: 9,105
    edited 2014-05-16 08:15
    Why does the propeller stop matching with the stopwatch after 26 seconds?

    Because Spin treats longs as signed values

    I did a little test:
    t1 := cnt
      pause(30000)
      t2 := cnt
    
      t3 := t2 - t1
    
      term.dec(t1)
      term.tx(CR)
      term.dec(t2)
      term.tx(CR)
      term.dec(t3)
      term.tx(CR)
    
      if (t3 < 0)
        term.dec(t3+posx)
        term.tx(CR)
    


    If the delta between start and stop points is negative, adding posx does in fact give you the overshoot past 26.84354560 seconds that posx can hold. On the PC side you may want to create a floating point constant that is 2^31 / 80000000. If the result is negative, add 2^31 to it, divide by 80000000, and then add your floating point constant for 26.8 seconds.


    After a cup of coffee...

    Here's another crack at it that may be easier:
    elapsed := -cnt
      pause(53000)
      elapsed += cnt - 544
    
      term.tx(CR)
      term.str(string("Ticks... "))
      term.dec(elapsed)
      term.tx(CR)
      
      term.str(string("us...... "))
      if (elapsed => 0)
        term.dec(elapsed / US_001)
      else
        term.dec((elapsed >> 1) / (US_001 >> 1)) 
      term.tx(CR)
      
      term.str(string("ms...... "))
      if (elapsed => 0)
        term.dec(elapsed / MS_001)
      else
        term.dec((elapsed >> 1) / (MS_001 >> 1)) 
      term.tx(CR)
    


    I tested this will a pause of 53000 (53 seconds) and it does work as expected.


    After reviewing thread...

    I see that Marko made this suggestion in post #7. You have [had] your answer. JonnyMac out! :)
  • kuronekokuroneko Posts: 3,623
    edited 2014-05-16 16:26
    For the sake of it, the following test program builds up 32bit deltas starting from a reference point. Based on the delta(s) the second value is extracted (treating +ve/-ve values the same).
    CON
      _clkmode = XTAL1|PLL16X
      _xinfreq = 5_000_000
    
    OBJ
      serial: "FullDuplexSerial"
      
    PUB null : n | t, ref, now, sec
    
      serial.start(31, 30, %0000, 115200)
      waitcnt(clkfreq*3 + cnt)
      serial.tx(0)
    
      t := ref := cnt
      repeat
        waitcnt(t += clkfreq)
    
        now := cnt
        serial.hex([COLOR="#FF8C00"]now - ref[/COLOR], 8)
    
        [COLOR="#020FC0"]sec := ((now - ref) >> 1) / (clkfreq / 2)[/COLOR]
        repeat 1 - (sec < 10)
          serial.tx(32)
        serial.dec(sec)
    
        serial.tx(13)
    
    When you look at the output you'll find that there is nothing odd going on when switching from +ve to -ve (above ~26sec).
    6422C4B0 21
    68E778B0 22
    6DAC2CB0 23
    7270E0B0 24
    773594B0 25
    7BFA48B0 26
    [COLOR="#A9A9A9"]80BEFCB0 27
    8583B0B0 28
    8A4864B0 29
    8F0D18B0 30
    93D1CCB0 31
    989680B0 32
    9D5B34B0 33
    A21FE8B0 34
    A6E49CB0 35
    ABA950B0 36
    B06E04B0 37[/COLOR]
    
    IOW it does match which suggests the error is somewhere else.
Sign In or Register to comment.