Shop OBEX P1 Docs P2 Docs Learn Events
Synchronized events for clock driver — Parallax Forums

Synchronized events for clock driver

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

Comments

  • The following code example should give you a more accurate method of timing in Spin.  See the comments in the wait routine on how it makes waitcnt more accurate by compensating for Spin command overhead.

    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. }}
  • Duane DegnDuane Degn Posts: 10,588
    edited 2015-08-06 21:33
    The waitcnt section of the Propeller Manual covers the topic of precise timekeeping.
    Here's an example using parts of your code.
    CON
    
    _clkmode = xtal1 + pll16x
    _xinfreq = 5_000_000
    
    CLK_FREQ = ((_clkmode - xtal1) >> 6) * _xinfreq
    
    CLOCK_OUTPUT_PIN = 0
    
    SEC_LED_HI_TIME = CLK_FREQ / 2
    SEC_LED_LO_TIME = CLK_FREQ - SEC_LED_HI_TIME
    
    VAR
    
    long seconds
    
    PUB Main | nextTime, setMode
    
    setMode := false
    dira[CLOCK_OUTPUT_PIN] := 1
    
    nextTime := cnt
    
    repeat
    if setMode == false
    seconds++
    outa[CLOCK_OUTPUT_PIN] := 1
    waitcnt(nextTime += SEC_LED_HI_TIME)
    outa[CLOCK_OUTPUT_PIN] := 0
    waitcnt(nextTime += SEC_LED_LO_TIME)
    else
    outa[CLOCK_OUTPUT_PIN] := 0
    
    

    You don't have to worry about the loop overhead this way.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-07-20 03:02
    Simplifying,
    [CLOCK_OUTPUT_PIN]~~
    nextTime := cnt
    repeat
      if (setMode)
        outa[CLOCK_OUTPUT_PIN]~
        nextTime := cnt 'Important!
      else
        seconds++
        waitcnt(nextTime += clkfreq / 2)
        !outa[CLOCK_OUTPUT_PIN]
    

    -Phil
  • Thanks for the examples, guys. What are the code tags for the new forum?
  • To post code, I copied the html Phil used in . I pasted the instructions I added to the sunk thread below.
    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]
  • SapphireSapphire Posts: 496
    edited 2015-07-20 15:34
    [code]
  • Vega256Vega256 Posts: 197
    edited 2015-08-06 21:34
    Hey guys,

    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.
    ...some init code...
    
    time := cnt
    
    repeat
    if setMode == false
    waitcnt (time += SEC)
    ++ seconds
    blink := true
    
    pub startSecondIndicator
    
    repeat
    if blink == true
    blink := false
    outa [0] := %1
    wait (SEC_LED_HI_TIME / 1000)
    outa [0] := %0
    wait (SEC_LED_LO_TIME / 1000)
    
    pub wait (time_in_ms)
    
    waitcnt (((clkfreq / 1_000 * time_in_ms - 3932) #> 381) + cnt)
    
  • JonnyMacJonnyMac Posts: 8,927
    edited 2015-07-26 15:11
    The problem is that your code does not account for the overhead in calls to your wait method. You clock and blinker could run in its own cog with a unified sync point; this would provide the best accuracy.
    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).
  • Vega256Vega256 Posts: 197
    edited 2015-08-06 21:32
    @ JonnyMac,

    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.
    ...some init code...
    
    time := cnt
    
    repeat
    if setMode == false
    waitcnt (time += SEC)
    ++ seconds
    blink := true
    
    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.

    pub startSecondIndicator
    
    repeat
    if blink == true
    blink := false
    outa [0] := %1
    wait (SEC_LED_HI_TIME / 1000)
    outa [0] := %0
    wait (SEC_LED_LO_TIME / 1000)
    
    pub wait (time_in_ms)
    
    waitcnt (((clkfreq / 1_000 * time_in_ms - 3932) #> 381) + cnt)
    
    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.
  • jmgjmg Posts: 15,148
    edited 2015-08-10 06:18
    So, I let it run on the new code for about a week now, and it is about 4 seconds slow as of today.cnt)

    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
  • It would be nice to see the entire program -- the [limited] information you've provided is just not enough to give you a good answer. You have a variable called setMode that interrupts the timing but we don't see its use (outside the timing loop), and you don't re-sync (as Phil and I both have demonstrated) when setMode is true.
  • Vega256Vega256 Posts: 197
    edited 2015-07-27 12:59
    ...and you don't re-sync (as Phil and I both have demonstrated) when setMode is true.

    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.
  • Vega256Vega256 Posts: 197
    edited 2015-08-06 21:31
    Here is the entire program.
    ' Pin Assignments
    
    ' 0  [o] - Seconds clock
    ' 1  [o] - MinOnes clock
    ' 2  [o] - Hours clock
    ' 3  [o] - Hour0 reset control
    ' 4  [o] - Hour1 reset control
    ' 5  [o] - HourA control
    ' 6  [o] - HourB control
    ' 7  [i] - Set time input
    ' 8  [i] - Minute/Hour select input
    ' 9  [i] - Increase time input
    ' 10 [o] - AM
    ' 11 [o] - PM
    ' 12 [o] - Minute set LED
    ' 13 [o] - Hour set LED
    ' 14 [o] - MinTens clock
    ' 15 [o] - MinOnes reset
    ' 16 [o] - MinTens reset
    
    con
    
    _clkmode = xtal1 + pll16x
    _xinfreq = 5_000_000
    
    SEC             = 80_000_000
    MILLISEC        = SEC / 1000
    SEC_LED_HI_TIME = SEC / 4
    SEC_LED_LO_TIME = SEC - SEC_LED_HI_TIME
    
    var
    
    byte  seconds
    byte  minutes
    byte  hour
    
    long  stackSpaceA [16]
    long  stackSpaceB [16]
    long  stackSpaceC [16]
    long  stackSpaceD [16]
    
    byte  setMode
    byte  state
    byte  nextState
    
    byte  hourState
    byte  hourResetState
    byte  amPmState
    
    long  time
    byte  blink
    
    pub start | hourCog
    
    seconds := 0
    minutes := 0
    hour := 0
    
    hourState := %10
    hourResetState := %01
    amPmState := %01
    
    setMode := false
    blink := false
    
    ' Reset all displays
    
    dira [3..4] := %11                ' Set hour reset pins as outputs
    dira [15..16] := %11              ' Set minute reset pins as outputs
    
    outa [3..4] := %11
    outa [15..16] := %11
    waitcnt (cnt + MILLISEC)
    outa [3..4] := %00
    outa [15..16] := %00
    
    cognew (startMinuteClock, @stackSpaceA)
    hourCog := cognew (startHourClock, @stackSpaceB)
    cognew (startControlMonitor (@hourCog), @stackSpaceC)
    cognew (startSecondIndicator, @stackSpaceD)
    
    dira [0] := %1                ' Set seconds clock pin as an output
    
    time := cnt
    
    repeat
    if setMode == false
    waitcnt (time += SEC)
    ++ seconds
    blink := true
    
    pub startSecondIndicator
    
    repeat
    if blink == true
    blink := false
    outa [0] := %1
    wait (SEC_LED_HI_TIME / 1000)
    outa [0] := %0
    wait (SEC_LED_LO_TIME / 1000)
    
    pub wait (time_in_ms)
    
    waitcnt (((clkfreq / 1_000 * time_in_ms - 3932) #> 381) + cnt)
    
    pub startMinuteClock
    
    dira [1] := %1                ' Set minutes clock pins as outputs
    dira [14] := %1
    dira [15..16] := %11
    
    repeat
    repeat until seconds == 60
    seconds := 0
    
    if (minutes + 1) == 60
    outa [15..16] := %11
    waitcnt (cnt + MILLISEC)
    outa [15..16] := %00
    
    else
    outa [1] := %1              ' Increment the ones clock
    waitcnt (cnt + MILLISEC)
    outa [1] := %0
    ' Do tens clock look ahead
    if (minutes + 1) // 10 == 0
    outa [14] := %1              ' Increment the tens clock
    waitcnt (cnt + MILLISEC)
    outa [14] := %0
    
    ++ minutes
    
    pub startHourClock
    
    dira [2] := %1                ' Set hour clock pin as an output
    dira [5..6] := %11               ' Set hour control pins as outputs
    dira [3..4] := %11               ' Set hour reset control pins as outputs
    dira [10..11] := %11               ' Set AM and PM as outputs
    
    outa [5..6] := hourState                ' Enable hour0, disable hour1
    outa [3..4] := hourResetState                ' Enable hours (1-10), disable hours (11-12)
    outa [10..11] := amPmState          ' AM - false, PM - true
    
    repeat
    repeat until minutes == 60
    minutes := 0
    ++ hour
    if hour == 10
    hourState := %01
    hourResetState := %10
    outa [5..6] := hourState
    outa [3..4] := hourResetState
    elseif hour == 12
    hour := 0
    hourState := %10
    hourResetState := %01
    outa [5..6] := hourState
    outa [3..4] := hourResetState
    
    ! amPmState
    outa [10..11] := amPmState         ' Interchange AM/PM
    else
    outa [2] := %1
    waitcnt (cnt + MILLISEC)
    outa [2] := %0
    
    pub startControlMonitor (hourCogAdr) | input
    
    ' 7  [i] - Set time input
    ' 8  [i] - Minute/Hour select input
    ' 9  [i] - Increase time input
    
    dira [7..9] := %000           ' Set time-setting pins as inputs
    dira [12..13] := %11          ' Set minute and hour set LEDs as outputs
    state := 0
    nextState := 0
    
    repeat
    if state == 0               ' Time mode, waiting for set time input
    input := ina [7]
    if input == %1
    setMode := true
    dira [1] := %1          ' Set minute (ones) clk
    dira [2] := %1          ' Set hours clk
    dira [3..4] := %11      ' Set hours reset control
    dira [5..6] := %11      ' Set hours control
    dira [14] := %1         ' Set minute (tens) clk
    dira [15..16] := %11    ' Set minutes reset control
    dira [10..11] := %11    ' Set AM/PM as outputs
    
    outa [5..6] := hourState
    outa [3..4] := hourResetState
    outa [10..11] := amPmState
    
    ' Stop the hour cog
    cogstop (long [hourCogAdr])
    
    outa [12..13] := %10
    nextState := 1
    state := 255
    
    elseif state == 1           ' Minute set mode
    input := ina [7..9]
    if input & %001 == %001
    ' Add more minutes
    if (minutes + 1) == 60
    outa [15..16] := %11
    waitcnt (cnt + MILLISEC)
    outa [15..16] := %00
    minutes := 0
    
    else
    outa [1] := %1              ' Increment the ones clock
    waitcnt (cnt + MILLISEC)
    outa [1] := %0
    ' Do tens clock look ahead
    if (minutes + 1) // 10 == 0
    outa [14] := %1              ' Increment the tens clock
    waitcnt (cnt + MILLISEC)
    outa [14] := %0
    ++ minutes
    nextState := 1
    state := 255
    elseif input & %010 == %010
    ' Switch to hour set mode
    ! outa [12..13]
    nextState := 3
    state := 255
    elseif input & %100 == %100
    ' ' Exit set mode
    nextState := 2
    state := 255
    
    elseif state == 2           ' Exit set mode
    outa [12..13] := %00
    seconds := 0
    time := cnt
    setMode := false
    state := 0
    
    ' Restart hour cog
    long [hourCogAdr] := cognew (startHourClock, @stackSpaceB)
    dira [1] := %0          ' Set minute (ones) clk
    dira [2] := %0          ' Set hours clk
    dira [3..4] := %00      ' Set hours reset control
    dira [5..6] := %00      ' Set hours control
    dira [14] := %0         ' Set minute (tens) clk
    dira [15..16] := %00    ' Set minutes reset control
    dira [10..11] := %00    ' Set AM/PM as outputs
    
    elseif state == 3           ' Hour set mode
    input := ina [7..9]
    if input & %001 == %001
    ' Add more hours
    ++ hour
    if hour == 10
    hourResetState := %10
    hourState := %01
    
    outa [3..4] := hourResetState
    outa [5..6] := hourState
    elseif hour == 12
    hour := 0
    hourResetState := %01
    hourState := %10
    
    outa [3..4] := hourResetState
    outa [5..6] := hourState
    ! amPmState
    outa [10..11] := amPmState
    else
    outa [2] := %1
    waitcnt (cnt + MILLISEC)
    outa [2] := %0
    nextState := 3
    state := 255
    
    elseif input & %010 == %010
    ' Switch to minute set mode
    ! outa [12..13]
    nextState := 1
    state := 255
    elseif input & %100 == %100
    ' Exit set mode
    nextState := 2
    state := 255
    
    elseif state == 255
    ' Debounce inputs
    if ina [7..9] == %000
    state := nextState
    
    
  • JonnyMacJonnyMac Posts: 8,927
    edited 2015-07-27 22:10
    I have no idea what that program wants to do. Looking through, I'm confused why you are using so many cogs, and why all have access to IO pins. I would suggest you simplify things by running a clock in the background and using the foreground (or another cog) to handle IO updates. 
    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       
  • Vega256Vega256 Posts: 197
    edited 2015-07-29 00:59
    So this project is more involved than this, which is why the code doesn't make sense. I didn't think I would have to post the schematic; I thought it was just a loop timing issue. I will post all project details after work.
  • Details matter. Even just a description/specification would be useful. 
    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
  • Vega256Vega256 Posts: 197
    edited 2015-07-31 18:01
    Sorry for the delay. Here is a somewhat detailed block diagram. Omitted from the diagram are the power connections, crystal, and serial lines. Circuit voltage is 5V stepped down to 3.3V from a regulator. Crystal is 5MHz, and the EEPROM configuration is used.

    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.

    QcbeEq8.jpg
  • jmgjmg Posts: 15,148
    Interesting design approach, using external decade counters.You drew a common CLK on Hours, but separate resets ?

    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.

  • Thanks for your help, guys. I've been rewriting and rearranging the code but to no avail; I'll keep at it. One last question before I keep going. Is it even possible for the Prop to keep perfect timing, and when I say perfect timing, I mean up to crystal accuracy. If so, then it's definitely something I'm doing wrong here.
  • It does keep perfect timing.
  • jmgjmg Posts: 15,148
     Is it even possible for the Prop to keep perfect timing, and when I say perfect timing, I mean up to crystal accuracy. If so, then it's definitely something I'm doing wrong here.


    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 ? 
  • JonnyMacJonnyMac Posts: 8,927
    edited 2015-08-03 17:51
    Thanks for your help, guys. I've been rewriting and rearranging the code but to no avail; I'll keep at it. One last question before I keep going. Is it even possible for the Prop to keep perfect timing, and when I say perfect timing, I mean up to crystal accuracy. If so, then it's definitely something I'm doing wrong here.


    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.
  • JonnyMac wrote: »
    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.
    t := cnt
    repeat
      waitcnt (t += duration)
      ...code...
    

    The code below is not a synchronized loop.
    repeat
      waitcnt (duration + cnt)
      ...code...
    
  • JonnyMacJonnyMac Posts: 8,927
    edited 2015-08-06 15:26
    Between all the magic numbers, NO description of what you want the code to do, using multiple cogs to what should be a simple clock, I'm lost.
  • OK, so I went back and gutted out about 9/10 of the original program and left just the seconds timer and blinker. I named my pin numbers, gotten rid of magic numbers, and a description is below. I'm sorry that my code is bad, I know I'm not a good programmer, but I still need help.

    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.
    CON
    
      _clkmode = xtal1 + pll16x
      _xinfreq = 5_000_000
    
      SEC             = 80_000_000
      SEC_LED_HI_TIME = SEC / 6 
    
      NUM_LONGS_STACK = 16
    
      p_secondsClk    = 0
      p_set           = 7
    
    VAR
    
      long  stackSpace [NUM_LONGS_STACK]
    
      long  setMode
      long  blink                 
    
    PUB startTimer | time
    
      setMode := false
      blink := false
    
      cognew (startIOController, @stackSpace)
    
      repeat until setMode == true
    
      time := cnt
      blink := true
    
      repeat
        waitcnt (time += SEC)
        blink := true                 
    
    PUB startIOController | input
    
      dira [p_set] := %0
      dira [p_secondsClk] := %1
    
      input := ina [p_set]
    
      repeat until input == %1
        input := ina [p_set]
    
      setMode := true
    
      repeat
        repeat until blink == true
        blink := false
        outa [p_secondsClk] := %1
        waitcnt (SEC_LED_HI_TIME + cnt)
        outa [p_secondsClk] := %0
    
  • JonnyMacJonnyMac Posts: 8,927
    edited 2015-08-09 22:30
    I'm bowing out after this suggestion.

    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].
  • AribaAriba Posts: 2,682
    edited 2015-08-10 05:05
    Vega256 wrote: »
    ....and when I say perfect timing, I mean up to crystal accuracy. If so, then it's definitely something I'm doing wrong here.
    Vega256 wrote: »
    ...
    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.
    ....

    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
Sign In or Register to comment.