Anyone have any ideas for a millisecond timer / counter?
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.
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
I was looking for for .1 sec and 1 sec counts and this worked better then I thought it would.
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.
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.
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.
'******************************************************************************* 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 phsbpub 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] := 0Thanks 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.
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.
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.
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 := vI'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....
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 := vAndy
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:
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)AndyIf 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 //= 1000This should work up to ~27 seconds intervalls (then the counter rollover will make problems).Andy
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!
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 flagGreat idea SLRM.
Thanks for the feedback.