Shop OBEX P1 Docs P2 Docs Learn Events
Anyone have any ideas for a millisecond timer / counter? — Parallax Forums

Anyone have any ideas for a millisecond timer / counter?

Don MDon M Posts: 1,652
edited 2013-02-26 21:13 in Propeller 1
In a project I'm working on I had been using jm_softrtc.spin to produce a millisecond and seconds number that I use for data logging. It uses it's own cog and does work great.

Unfortunately I want to add new features to my project and I have run out of cogs so I'm looking for areas that I can squeeze together and eliminate a cog launch or 2 if possible.

So I was wondering if there might be another way of keeping track of milliseconds and seconds. All I need to be able to do is reset it to 0 and get milliseconds and seconds.

Comments

  • 4x5n4x5n Posts: 745
    edited 2013-02-25 19:40
    I've done something similar. Used waitcnt to give me a 1msec "tick" and save that count to a variable (or two depending on what's best for your needs) of course each tick is one msec and 1,000 ticks or milliseconds equal a second.

    I was looking for for .1 sec and 1 sec counts and this worked better then I thought it would.
  • Duane DegnDuane Degn Posts: 10,588
    edited 2013-02-25 19:45
    How accurate do you need your timing to be?

    I've used this method to see if a second has gone by. It wouldn't be hard to modify it to check for a millisecond, if the loop that checks the time takes less than a millisecond to complete.
    PRI CheckClock | {localTime, oneSecond,} daysInMonth
      if (cnt - clockTime) => oneSecond
        
        clockTime += oneSecond
             
        if ++seconds > 59
          seconds := 0      
          if ++minutes > 59
            minutes := 0
            
            if ++mhours > 24
              
              mHours := 0
              if ++date > daysInMonth
                date := 1
                if ++months > 12
                  months := 1
                  years++            
                daysInMonth := lookup(months: 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
                if months == 2
                  if ((years // 4 == 0) and (years // 100 <> 0)) or (years // 400 == 0)
                    daysInMonth := 29
                  else
                    daysInMonth := 28
              if ++dayOfWeek > 7
                dayOfWeek := 1
            amHours := mHours // 12
            if amHours == 0
              amHours := 12          
          if displayMode == _ClockDisplay
            Display(_MinutesPlace)
          if displayMode == _ClockDisplay
            Display(_HoursPlace)
        if displayMode == _ClockDisplay
          Display(_SecondsPlace)
                
    
    

    It's been a while since I've used the above code. I'm not sure it's bug free.
  • ElectricAyeElectricAye Posts: 4,561
    edited 2013-02-25 19:46
    Don M wrote: »
    ...

    Unfortunately I want to add new features to my project and I have run out of cogs so I'm looking for areas that I can squeeze together and eliminate a cog launch or 2 if possible. ....

    Is there any chance you can free up some cogs by temporarily shutting down their functions, use the freed cogs for other purposes, then revert to their original functions as needed? Just because you launch a cog doesn't mean it's stuck forever doing just that one thing.
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2013-02-25 19:57
    Don M wrote: »
    In a project I'm working on I had been using jm_softrtc.spin to produce a millisecond and seconds number that I use for data logging. It uses it's own cog and does work great. Unfortunately I want to add new features to my project and I have run out of cogs so I'm looking for areas that I can squeeze together and eliminate a cog launch or 2 if possible. So I was wondering if there might be another way of keeping track of milliseconds and seconds. All I need to be able to do is reset it to 0 and get milliseconds and seconds.


    Assuming you are checking this at least every 53 seconds (53.6879.. @80MHz) you could just use the 32-bit system counter that is clocked from the system oscillator. Instead of resetting to zero you simply capture the current count and use that. It would be best to update your internal "RTC" at least every 53 seconds but use the CNT for all the fine stuff. BTW, there is always some slack time in any cog that could be used to do this.
  • SRLMSRLM Posts: 5,045
    edited 2013-02-25 20:11
    You can use two counters and a free IO pin to keep track of time. The downside is that the units are not base 10 divisors of 1 second, but base 2. Still, that's where postprocessing software can come in if you need the base 10 divisor. Anyway, here are the functions:
    '*******************************************************************************
    PRI StartSystemTime
    {{**
      * This method uses both system counters to create a slower "system" clock that
      * can be used to keep track of time in the millisecond range.
      *
      * TODO: insert appropriate formulas here.
      *}}
        DIRA[SYS_TIME_PIN]~~                ' sets pin as output
        CTRa := %00100<<26 + SYS_TIME_PIN   ' set oscillation mode on pin
        FRQa := FREQ_COUNTS                 ' set FRequency of first counter                   
    
        CTRB := %01010<<26 + SYS_TIME_PIN   ' at every zero crossing add 1 to phsb
        FRQB := 1
      
        
    PRI GetSystemTime
    {{**
      * Returns the "system" clock value.
      *}}
        return phsb
    
  • JonnyMacJonnyMac Posts: 9,108
    edited 2013-02-25 20:21
    In my standard template I have this method -- math came from Beau, I think.
    pub set_freq(ctrx, px, fx)
    
    '' Sets ctrx to frequency fx on pin px (NCO/SE mode)
    '' -- fx in hz
    '' -- use fx of 0 to stop counter that is running
    
      if (fx > 0)                             
        fx := ($8000_0000 / (clkfreq / fx)) << 1            ' convert freq for NCO mode    
        case ctrx
          "a", "A":
            ctra := ((%00100) << 26) | px                   ' configure ctra for NCO on pin
            frqa := fx                                      ' set frequency
            dira[px] := 1                                   ' make pin an output
         
          "b", "B":                         
            ctrb := ((%00100) << 26) | px  
            frqb := fx                    
            dira[px] := 1
    
      else
        case ctrx
          "a", "A":
            ctra := 0                                       ' disable counter
            outa[px] := 0                                   ' clear pin/driver 
            dira[px] := 0                                  
         
          "b", "B":                         
            ctrb := 0 
            outa[px] := 0  
            dira[px] := 0
    
  • Don MDon M Posts: 1,652
    edited 2013-02-26 05:15
    Is there any chance you can free up some cogs by temporarily shutting down their functions, use the freed cogs for other purposes, then revert to their original functions as needed? Just because you launch a cog doesn't mean it's stuck forever doing just that one thing.

    Thanks for this suggestion. I had thought of doing that and there is one cog I could shut down to make my new feature work and it does work to do so but if I could somehow eliminate another cog that would be even better.
  • jmgjmg Posts: 15,173
    edited 2013-02-26 11:58
    Assuming you are checking this at least every 53 seconds (53.6879.. @80MHz) you could just use the 32-bit system counter that is clocked from the system oscillator. Instead of resetting to zero you simply capture the current count and use that. It would be best to update your internal "RTC" at least every 53 seconds but use the CNT for all the fine stuff. BTW, there is always some slack time in any cog that could be used to do this.

    Expanding on this idea, if you can tolerate binary time-stamps, you can run the CNT plus a SW extension, and if you test the MSB of CNT, and sync that with the LSB of Upper , then you have an aperture effect test, and get a 63 bit counter.
    (ie you have traded-off one bit of range, for better checking )

    You manage manage the 63 bit, via a simple poll and INC if CNT.31 <> Upper.0, and that needs to be faster than 53/2 - usually do this is the fastest cog loop.

    All other cogs can read 63 bits and if they see (CNT.31 <> Upper.0) they read again. (or fix-up locally) as it means the INC loop 'has not quite gotten around to' the inc.
    A 63 bit stamp, is unique for the life of the product.

    Alternatively, if you are storing logs more frequently than 53/2 seconds, you can simply store the 32 bit CNT as a stamp, and do this upper extension in the reader SW as you scan-in all the data.
  • SRLMSRLM Posts: 5,045
    edited 2013-02-26 12:33
    jmg wrote: »
    Alternatively, if you are storing logs more frequently than 53/2 seconds, you can simply store the 32 bit CNT as a stamp, and do this upper extension in the reader SW as you scan-in all the data.

    That's what I'm doing with my current software. The main downside to this approach is that each datapoint is no longer independent: it depends on what came before, and how many rollovers there have been since the start of logging.
  • Don MDon M Posts: 1,652
    edited 2013-02-26 14:14
    Ok so here's a snippet of my existing code where I reset the RTC and store the milliseconds, seconds and data. The data comes in at 9600 baud with polling happening approximately every 20 mSec.
    PUB public_method_name
    
    
      repeat 
        if stop == 1                                        ' Run until stop equals 1
          quit
        repeat 
          d := \serial.rx_check                             ' Master data coming in...
          if d <> -1
            if c == 0                                       
              rtc.reset                                     ' Here is where I'm now reseting the software RTC
              c := 1
            if(d => minval[mode] AND d =< maxval[mode])                      
              flag1 := 1                                    
              buf1[i] := (rtc.seconds | $B000)              ' Store seconds with $B000 flag 
              i++
              buf1[i] := (rtc.millis | $A000)               ' Store milliseconds with $A000 flag
              i++
            if flag1 == 1    
              buf1[i] := (d | $8000)                        ' Store master data                       
              
              i++
            
            if i > 124                                      
             i := 0
             
          s := \serial.rx_check                             ' Slave data coming in...
          if s <> -1
            if flag1 == 1                                   
              buf1[i] := (s | $4000)                        ' Store Slave data
              i++                                           
              mrk := 0                                      ' Reset flag since Slave data was stored
              if (s & $F_00) == $1_00  
                flag1 := 0   
          if i > 124                                        ' reset ring buffer if near end could take two times
            i := 0                                          ' and one data   = 512 - 3 = 509 
              
          if i < 64
            v := 0
          else    
            v := 1
           
         if v <> old
           if v == 0
             wordmove(DataAddress,@buf2,64)
             wordfill(@buf2, 0, 64) 
             old := v
           elseif  v == 1
             wordmove(DataAddress,@buf1,64)
             wordfill(@buf1, 0, 64) 
             old := v
             
    

    I've never messed with the system counter before but I envision assigning some variable to cnt and at the point in my code where the RTC.RESET is and that becomes my "zero"? and then somehow divide up the cnt timer value to be in milliseconds and seconds and store those values when called in the loop and stored to the buffer. I'm also thinking from some of your comments above that there would be a second variable that would increment every time cnt reaches it's 53'rd second in order to keep on counting up into minutes etc?

    I'm gonna need some good advice or examples here....
  • AribaAriba Posts: 2,690
    edited 2013-02-26 15:17
    I have not testet it, but this may work:
    VAR
      long  ticks, millis, seconds
    
    PUB public_method_name
    
      repeat 
        if stop == 1                                        ' Run until stop equals 1
          quit
        repeat 
          d := \serial.rx_check                             ' Master data coming in...
          if d <> -1
            if c == 0                                       
              millis := seconds := 0                        ' Here is where we reseting the software RTC
              ticks := cnt
              c := 1
            if(d => minval[mode] AND d =< maxval[mode])                      
              flag1 := 1                                    
              millis := (cnt-ticks) / (clkfreq/1000)        ' delta time in milliseconds
              if millis > 999
                ticks += clkfreq                            ' add 1 second to ticks to calc further deltas 
                millis -= 1000
                seconds++
              buf1[i] := (seconds | $B000)                  ' Store seconds with $B000 flag 
              i++
              buf1[i] := (millis | $A000)                   ' Store milliseconds with $A000 flag
              i++
            if flag1 == 1    
              buf1[i] := (d | $8000)                        ' Store master data                       
              
              i++
            
            if i > 124                                      
             i := 0
             
          s := \serial.rx_check                             ' Slave data coming in...
          if s <> -1
            if flag1 == 1                                   
              buf1[i] := (s | $4000)                        ' Store Slave data
              i++                                           
              mrk := 0                                      ' Reset flag since Slave data was stored
              if (s & $F_00) == $1_00  
                flag1 := 0   
          if i > 124                                        ' reset ring buffer if near end could take two times
            i := 0                                          ' and one data   = 512 - 3 = 509 
              
          if i < 64
            v := 0
          else    
            v := 1
           
         if v <> old
           if v == 0
             wordmove(DataAddress,@buf2,64)
             wordfill(@buf2, 0, 64) 
             old := v
           elseif  v == 1
             wordmove(DataAddress,@buf1,64)
             wordfill(@buf1, 0, 64) 
             old := v
    

    Andy
  • Don MDon M Posts: 1,652
    edited 2013-02-26 16:06
    Ariba wrote: »
    I have not testet it, but this may work:
    Andy

    Andy I like the simplicity of this. I made this to try and understand it myself. I must be doing something wrong here.
    pub Test2 | ticks, millis, seconds
    
      millis := seconds := 0                                ' Here is where we reseting the software RTC
      
      repeat 50 
    
        'ticks := cnt
        ticks := cnt - ticks                                ' delta time in clockticks
        millis += ticks / (clkfreq/1000)                    ' scale to milliseconds and add
        if millis > 999
          millis -= 1000
          seconds++                                         
        term.dec(seconds)                                   ' Print seconds
        term.tx($2E)
        term.dec(millis)                                    ' Print milliseconds 
        term.tx(13)
        'pause(10)   
    
    

    It prints out the time rather quickly. I think there is some scaling off somewhere because what should be 50.xxxx seconds on the terminal only took a blink of the eye to print out.

    Here's the screen capture:

    RTC 1.JPG
    274 x 856 - 39K
  • Don MDon M Posts: 1,652
    edited 2013-02-26 16:15
    I see you made some changes while I was trying out this code. I put in your changes and it still prints in about a second.
  • AribaAriba Posts: 2,690
    edited 2013-02-26 16:33
    Yes I have changed it a bit. Here is your testcode with the new way to do it.
    This time I have tested it and it works for me.
    CON
      _clkmode  = xtal1 + pll16x
      _xinfreq  = 5_000_000
    
    OBJ
      term : "FullDuplexSerial"
      
    pub Test2 | ticks, millis, seconds
      term.start(31,30,0,115200)
    
      millis := seconds := 0                        ' Here is where we reseting
      ticks := cnt
    
      repeat 50 
    
        waitcnt(clkfreq/3 + cnt)                    ' simulate time between log data
    
        millis := (cnt-ticks) / (clkfreq/1000)      ' scale to milliseconds
        if millis > 999
          ticks += clkfreq                          ' next second
          millis -= 1000
          seconds++                                         
        term.dec(seconds)                           ' Print seconds
        term.tx($2E)
        term.dec(millis)                            ' Print milliseconds 
        term.tx(13)
    
    Andy
  • AribaAriba Posts: 2,690
    edited 2013-02-26 17:05
    Be aware that the code only works correct if the millis/seconds update part is called faster than one time a second.
    If your data logging can have pauses of more than a second you can change the code to:
    if millis > 999
        seconds += millis/1000
        ticks += (millis/1000) * clkfreq
        millis //= 1000
    
    This should work up to ~27 seconds intervalls (then the counter rollover will make problems).

    Andy
  • Don MDon M Posts: 1,652
    edited 2013-02-26 17:14
    Yes it appears to work well. I added in some code I found somewhere that fixes the millis digits to be 3 places. It seems to work.

    Let me know if you see something wrong about it.
    pub Test2 | ticks, millis, seconds, digits, val
    
      millis := seconds := 0                                ' Here is where we reseting the software RTC
      ticks := cnt
    
      repeat  
    
        waitcnt(clkfreq/3 + cnt)                    ' simulate time between log data
     
        millis := (cnt-ticks) / (clkfreq/1000)        ' delta time in milliseconds  
        if millis > 999
          ticks += clkfreq                            ' add 1 second to ticks to calc further deltas 
          millis -= 1000
          seconds++                                         
        term.dec(seconds)                                   ' Print seconds
        term.tx($2E)
        digits := ((millis < 0) & 1) + 1
        if (millis <> $8000_0000)
          val := || millis
          repeat while (val /= 10)
            digits++
        repeat (||3 - digits) #> 0
          term.tx("0")    
        term.dec(millis)                                    ' Print milliseconds 
        term.tx(13)
    

    Thanks a lot for your help!
  • Don MDon M Posts: 1,652
    edited 2013-02-26 17:31
    Ok another question.

    In the code above if the millis is less than 3 digits this:
      repeat (||3 - digits) #> 0
                term.tx("0") 
    

    just prints leading zeros to pad the number. But what if I want to store it in a buffer- how do I "build" the number with any leading zeros before storing into the buffer?
      repeat (||3 - digits) #> 0
                term.tx("0")    <<<<<<<<<<<< what do I put here instead?
      buf1[i] := (millis | $A000)                   ' Store milliseconds with $A000 flag
    
  • JonnyMacJonnyMac Posts: 9,108
    edited 2013-02-26 20:26
    I really liked the idea of using the otherwise unemployed counters -- and whipped up the attached object/demo. It might be helpful for short-duration (about 24 days) timing. It also has an elapsed() method that allows you to easily deal with the delta between events (calls to elapsed). It's simple and in alpha state, but, perhaps, useful.

    Great idea SLRM.
  • kuronekokuroneko Posts: 3,623
    edited 2013-02-26 20:45
    JonnyMac wrote: »
    It's simple and in alpha state, but, perhaps, useful.
    FWIW, it's not a good idea to enable e.g. a POSEGDE counter with frqx <> 0. Wait with frqx and/or phsx setup until after ctrx has been setup. Otherwise the random FF state in the counter may generate an unwanted event.
  • JonnyMacJonnyMac Posts: 9,108
    edited 2013-02-26 21:01
    Oki-doki. I did admit that this was a first-cut. :)

    Thanks for the feedback.
  • kuronekokuroneko Posts: 3,623
    edited 2013-02-26 21:13
    JonnyMac wrote: »
    Oki-doki. I did admit that this was a first-cut. :)
    No problem, just thought I point it out. Below some sample code to demonstrate the issue (apologies for the OT post).
    PUB null
    
      dira[0..1]~~
      outa[0..1] := %01                                     ' prep 1 and 0 bits
    
      ctra := %0_11001_000 << 23 | 0 << 9 | 1               ' LOGIC A == B, set FFs
      ctra := 0                                             ' freeze A & !B state
    
      frqa := 5                                               
      phsa := 0
      ctra := %0_01010_000 << 23 | 0                        ' POSEDGE on pin 0 (zero)
      ctra := 0                                             ' switch off
    
      dira[16..23]~~
      outa[16..23] := phsa                                  ' display result
    
      waitpne(0, 0, 0)
    
Sign In or Register to comment.