Synchronized events for clock driver
Vega256
Posts: 197
in Propeller 1
Hey guys,
For my clock driver, I need some things to happen simultaneously and in synchrony. First, I have a seconds variable that I need incremented every second; this is the clock's base measure. I also need an IO pin to pulse every time seconds is incremented; this will be for the seconds indicator. Posted below is some rudimentary code that I wrote, but it doesn't work; I let the clock run on this code for about 3 days, and on the third day, the time had drifted off by about five minutes (and understandably so). There is execution overhead in the loop, but I'm not sure how to write this so that the aforementioned events happen in tandem.
Also, how do I post code in the new forums?
repeat
if setMode == false
++ seconds
outa [0] := %1
waitcnt (cnt + SEC_LED_HI_TIME)
outa [0] := %0
waitcnt (cnt + SEC_LED_LO_TIME)
else
outa [0] := %0
For my clock driver, I need some things to happen simultaneously and in synchrony. First, I have a seconds variable that I need incremented every second; this is the clock's base measure. I also need an IO pin to pulse every time seconds is incremented; this will be for the seconds indicator. Posted below is some rudimentary code that I wrote, but it doesn't work; I let the clock run on this code for about 3 days, and on the third day, the time had drifted off by about five minutes (and understandably so). There is execution overhead in the loop, but I'm not sure how to write this so that the aforementioned events happen in tandem.
Also, how do I post code in the new forums?
repeat
if setMode == false
++ seconds
outa [0] := %1
waitcnt (cnt + SEC_LED_HI_TIME)
outa [0] := %0
waitcnt (cnt + SEC_LED_LO_TIME)
else
outa [0] := %0
Comments
Using PASM code instead of Spin would allow you to reduce overhead even more to give you finer control.
Example:
CON
_CLKMODE = XTAL1 + PLL16X ' Use low crystal gain, wind up 16x
_XINFREQ = 5_000_000
PIN = 16
VAR
PUB Main
dira[PIN]~~
repeat
wait(500) ' Wait 500ms (.5 sec.)
!outa[Pin] ' Toggle I/O pin.
PUB wait(Time_in_ms) ' This routine has the program wait the specified number of milliseconds.
waitcnt(((clkfreq / 1_000 * Time_in_ms - 3932) #> 381) + cnt)
{{ This will delay/wait the specified number of milliseconds. This is SPIN code, and SPIN is interpreted, so
it takes significantly longer to interpret and execute the waitcnt command than it does with PASM.
The 3932 is to compensate for the time that it takes the interpreter to process the computation portion
of the command. The #>381 insures that you never do a waitcnt with a value less than 381 more than
the current CNT, because if you do that the counter will have already counted past your target value
before the timing starts, and that means you will hang there while the counter loops through it's entire
32-bit range, somewhere around a minute (53 seconds). waitcnt waits for the system counter to be
exactly equal to the target value. }}
Here's an example using parts of your code.
You don't have to worry about the loop overhead this way.
-Phil
In hopes of making it easier for others to use code tags, I'm adding a really short example here. I copied Phil's top post and deleted the code between the tags. To use these tags, first quotes this post. Once you have this text in the editor box you need to click on the "Show Source" icon "<>". While in this html viewer, copy the text between the lines of asterisks. You should then be able to paste the html into another editor box which is also in html mode.*****************************************************************************[code]
Sorry for the thread resurrection, but I just wanted to give you guys an update on the clock.
So, I let it run on the new code for about a week now, and it is about 4 seconds slow as of today. Doing the math, if the clock loses 4 seconds per week, then this about 3.5 minutes per year. This seems like a lot to me, but maybe I'm setting my standards too high.
I'm using the clock on my phone as the reference clock, and the Prop code is posted below.
I wound up giving the blinking task to another cog to further reduce the code overhead on the first cog.
Here's an idea:
pri seconds_clock | t
dira[BLINKER] := 1
seconds := 0
t := cnt repeat repeat while (setmode) ' if setmode is enabled t := cnt ' re-sync timing waitcnt(t += clkfreq) ' wait 1s ++seconds ' update seconds if (blink) ' if blink enabled outa[BLINKER] := seconds ' blink when secods.0 == 1 else outa[BLINKER] := 0
Launch this into its own cog with cognew and a stack of 16 longs (it makes no external calls so that will be plenty). Seconds, setmode, and blink are global variables. This code is called a synchronized loop and the overhead is accounted for; there will be no skew in timing (beyond crystal accuracy). When blink is true the BLINKER output will be on when the seconds value is odd (bit0 is 1).
Thanks for the sample code, but I'm afraid I still don't understand how my code isn't synchronized.
This loop below should be synchronized because the overhead from waitcnt is account for; this is run in one cog.
The loop below is run by a different cog and just pulses the pin when it detects that blink is set true by the first cog; this keeps the blinker cog in sync with the clock cog.
The two cogs are synchronized with each other; that's not an issue. My problem is that the cog that updates the seconds variable doesn't stay in sync with actual time.
4 seconds a week is ~ 6.6ppm, are you sure your clock source is better than that ?
If that precision level is intolerable, then you may need a GPS disciplined timer ?
A good looking one here ? http://www.adafruit.com/products/746
That, too, actually happens outside the loop; I will post the program in it's entirety after work today. Sorry guys, didn't mean to have you all poking around in the dark. Also, thanks for taking the time to see my problem.
A running clock -- that is synchronized and accounts for loop overhead -- can be as easy as what I show below. As in some of your fragments there is a variable called setmode that suspends the clock when true. Note: When manually setting the clock it's a good idea to set millis to 0.
pri clock | ms1, t ms1 := clkfreq / 1000 t := cnt repeat repeat while (setmode) t := cnt waitcnt(t += ms1) if (++millis == 1000) millis := 0 if (++secs == 60) secs := 0 if (++mns == 60) mns := 0 if (++hrs == 24) hrs := 0
And still, I would suggest keeping IO in one cog unless there is some incredibly compelling reason not to. All the cog outputs get OR'd together before hitting the pin, so if any cog makes a pin high that pin will be high no matter what. This can be useful, but can also lead to bugs that are hard to track down.
Finally, may I suggest two things:-- run the little clock code above to see if it's more accurate than yours-- use named pin #s in your code; this helps document the code and makes wiring changes easier
U1 through U4 are decade counters with 10 decoded outputs. U1 and U2 are the minutes counters with U1 counting the ones place of the minutes and U2 counting the tens place of the minutes. U3 and U4 are the hours counters with U3 counting hours 1-10 and U4 counting hours 11-12.
P0 is the seconds indicator. P10 and P11 are the AM and PM indicators, respectively. P7 through P9 are inputs. P7 is the time set input; pressing the set button toggles between time set mode and time display mode. P8 is the minute/hour set select; when in time set mode, pressing the minute/hour button toggles between setting the minutes and hours. P9 is the time increase input; when in time set mode, pressing the time increase button will increment the minutes or hours.
You could reduce the wires, with a common reset to all, a simple carry cascade to tens digits, and then a Clk_Mins and Clk_Hrs (so just 3 wires down from 7) - a reset pulse and a fast/short burst of clocks refreshes the display.
Of course, but do you know what your Crystal accuracy actually is ?eg Do you have a GPS module 1pps that can give you a crystal accuracy number ?With a system that resolves to only minutes, how are you checking claimed accuracy ?
Yes, but you must use a SYNCHRONIZED loop (and put your timing in one place to keep things clean) -- you continue to break that rule hence you break your timing. The fix is easy if you will simply implement it. Phil and I have both provided examples. This is becoming a "You can lead a horse to water..." thing.
I have been using a synchronized loop since you advised it the first time. Maybe I'm unsure of what a synchronized loop is. From what I understand, the code below is a synchronized loop.
The code below is not a synchronized loop.
I have only two cogs: one that counts and the other handles IO, keeping IO in one cog. On Prop start-up, setMode will be false, and the timer will wait for my input from a pushbutton connected to P7. When I press the button, setMode goes true, and the timer will begin counting in 1 second intervals. At the end of every second, blink is set true, and this triggers the blinker. The pulse is set to be high for a period of SEC_LED_HI_TIME, and low for a period of (SEC - SEC_LED_HI_TIME). After the pulse, the blinker resets blink, and the cycle goes on.
I started this code, synchronizing the timer with the seconds timer of my own digital clock. I let this code run for about 15 hours, and the timer is out of phase with the clock. I'm not sure what's going on here.
Please, for the love of Pete, explain to the group what you want the program to do -- as if you were asking somebody else to write the code for you. It is not always obvious from broken code what the programmer intended. If you will explain the goals of your program, and the hardware you want to interact with, I'm sure it will become far simpler.
Being new is not a crime; not explaining what your goals are while asking for help is [in my book].
What is wrong here is your expectations of the precision of crystals. They normally have something like 30 ppm tolerance.
If your LED is 180 degree out of phase to the clock, after 15 hours then this is about 10 ppm if I calculate this right.
There are more precise crystals available but they are expensive. Or you can trimm the crystal frequency with a variable capacitor.
Andy