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.

The

drift_adjust 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
`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 and

drift_delta_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:

If

abs(drift_delta_seconds) is less than 15, no drift correction is recorded and the values of

drift_delta_seconds and

drift_total_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 and

drift_total_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
`// subtract effect of current drift adjustment, if any`

if(drift_adjust) {

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;

If

abs(new_adj) 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/