I've gotten a couple questions regarding my fully-automatic drift correction in my firmware, so I thought it would be worth posting a description here. I hope the method will be useful to other clock hackers.
Instead of adjusting the duration of the first second each hour, the clock makes a 1/128 second adjustment at regular intervals. The code defines two global variables: int16_t drift_adjust
specifies how often an adjustment is to be made and uint16_t drift_adjust_timer
stores the number of seconds until the next adjustment. The absolute value of drift_adjust
is the time between adjustments in seconds. If drift_adjust
is positive, the clock is slow; if negative, fast.
This implementation has two advantages: First, the maximum adjustment per second is 1/128 seconds, so no second is noticeably longer or shorter in duration. Second, this method has improved accuracy. For a typical crystal drift of 35ppm, adjusting every abs(drift_adjust)
seconds allows a higher resolution--0.2ppm versus with 2 ppm for the once-per-hour method. And with a drift of 2ppm, typical of a temperature compensated crystal oscillator, the adjustment resolution for this method is 0.0005 ppm.
variable is also updated automatically when the user changes the time, so there is no need to adjust any drift correction constant. After a new time is entered, the clock determines if it is running fast or slow based on the difference between the old and new time. The system uses three global variables:
- Code: Select all | TOGGLE FULL SIZE
int32_t drift_total_seconds; // seconds since time last set
int32_t drift_delta_seconds; // seconds between new and old times
uint16_t drift_delay_timer; // seconds until updating drift adjustment
(1) Every second, drift_total_seconds
is incremented by 1.
(2) When initially setting the time after reset, both drift_total_seconds
are set to zero.
(3) When changing the current time, the clock calculates the difference between the old time and the new time in seconds, and adds the difference to drift_delta_seconds
. Thus, the current drift in ppm is 1,000,000 * drift_delta_seconds / drift_total_seconds
. Because a user might have set an incorrect time by mistake, the clock does not immediately update drift_adjust
. Instead, a delay timer, drift_delay_timer
, is set to 600 (ten minutes). Therefore, when users incorrectly set the time, they may correct the mistake within ten minutes, and the new drift correction will be estimated correctly.
(4) If nonzero, drift_delay_timer
is decremented by one each second. If the value of drift_delay_timer
is zero after the decrement, the clock attempts to determine a new drift correction:
is less than 15, no drift correction is recorded and the values of drift_delta_seconds
are preserved. This somewhat odd behavior prevents a drift adjustment value from being estimated if a user accidently enters the "set time" menu and presses "set" three times to exit.
If abs(drift_delta_seconds) is more than 1200 seconds--20 minutes--no new drift adjustment is calculated and the values of drift_delta_seconds
are set to zero. This behavior prevents the drift adjustment from being erroneously estimated when the clock changes timezones or is set to a wildly incorrect time.
Next, a new drift correction, new_adj
, is estimated by
- Code: Select all | TOGGLE FULL SIZE
// subtract effect of current drift adjustment, if any
int32_t adj_sec = (drift_total_seconds / drift_adjust) >> 7;
drift_total_seconds -= adj_sec;
drift_delta_seconds += adj_sec;
// calculate new drift adjustment
new_adj = (drift_total_seconds / drift_delta_seconds) >> 7;
is at least 39 (under ~200ppm drift), new_adj
is likely a reasonable drift adjustment and is saved in an EEPROM array as a potential value for drift_adjust
(5) The EEPROM contains calculated values for new_adj
corresponding to previous time changes (up to seven). The drift adjustment actually used by the clock--drift_adjust
--is the median of the previously computed new_adj
values in EEPROM. Note that the median must be computed in reciprocal space, so the median of -12, 8, and 15 would be 15 because -1/12 < 1/15 < 1/8. Also note that the median is robust to outliers, so occasionally setting an incorrect time doesn't mess things up.
For more specifics, see the implementation in the time_autodrift(void)
function in time.c
of my xmas-icetube firmware: https://github.com/johngarchie/xmas-icetube/