Uses Timer 0 and the RTC to keep accurate time. It can accept a PPS from an external source like a GPS or a regular time stamp from an external source like an NTP server. It also provides timer functions to 96MHz up to 44 seconds using the CPU clock.

Dependents:   oldheating gps motorhome heating

Description

The clock library provides a number of separate functions:

  • hrtimer An unsigned 32bit high resolution timer which wraps around every 44 seconds from which all the time is derived.
  • mstimer An unsigned 32bit low resolution timer which wraps around every 49 days
  • clktimer A signed 64bit timer (TAI) which doesn't wrap (or not until 2242 when clock time breaks)
  • scan Calculates the max, min and average scan times.
  • rtc A real time clock to provide backup
  • tm Routines to manipulate struct tm local and utc times
  • clk A clock which is synchronised to an external source

High resolution timer

hrtimer uses TIM0 as a 32bit timer which counts at the cpu frequency 96MHz and rolls over after about 44s.
It has an init routine called from ClkInit to start it, thereafter it free runs.
No dependencies.

Millisecond timer

mstimer uses the high resolution timer to count the number of ms since power up. Its unsigned 32bit count rolls over after about 49 days.
It has a main routine called from ClkMain.
Depends on timer.

Clock timer

clktimer uses the signed 64 bit clock time.
Depends on clock and hence hrtimer.

Scan times

scan uses the high resolution timer to calculate the max, min and average scan times.
It has a main routine called from ClkMain.
Depends on hrtimer.

Real time clock

rtc contains routines to save and restore the time in the battery backed real time clock.
Parameters are struct tm.
No dependencies.

Local and UTC manipulation

tm contains

  • the typedef time64 which contains the count of seconds since 1970; just like time_t but based on int64_t to avoid the 2038 problem
  • a number of functions for manipulating time64 and struct tm times

No dependencies.

Clk

clk contains

  • settings
  • functions to save and restore the time to the RTC. Depends on timer, rtc and tm.

clktime increments the time by 1 each second via clk.c from timer.c.
It increments the signed 64 bit time count using the ppb and slew (governed by clkgov.c).
When the time is requested it uses its count and a proportion of the elapsed second from the high resolution timer to calculate the exact time.
See time-formats.text for the clock time format.

clkgov governs the ppb and slew to synchronise the clock time with an external source.
PPB is stored in GPREG0 whenever it is set and retrieved during initialisation.
It takes external time from either:

  • a long term source such as NTP
  • a pulse per second (PPS) such as GPS

clkntp converts clock time to NTP time and vice versa.

clktm converts clock time to struct tm and vice versa

clkutc maintains the era offset (leap seconds count).
The era offset and other information is stored in GPREG1 whenever it is set and retrieved during initialisation.
It contains:

  • the current era offset
  • for the next epoch:
    • its start month (as year and month since 1970)
    • its state: normal; waiting to leap forward; waiting to leap back; leaping forward (second 60)
  • conversion routines between tai and utc (clk time is tai)

Clock time formats

Criteria

Resolution

PPS
We get an interrupt each second which we can resolve to a microsecond. The divisor is 1000. To carry this resolution into the governor we need 1 ppb.
NTP
Suppose we are adding compensation every second, sampling every 4 hours and want to represent 3ms of error with a divisor of 10: that would need a resolution of 23 ppb.
The best temperature compensated crystal oscillators can manage about 1ppm (see Wikipedia) long term or 10 ppb short term.

Lifetime

Needs to keep going during the lifetime of this, or other related, projects. At least a century (so 2100) but more than a few centuries is likely to be pointless

Ease of transforming to NTP, time_t

A count of decimal times - ms, us, ns or ps - can only be transformed using multiplication or division by 1000s. NTP and time_t use binary fractions about a fixed decimal point.

Ease of representing ppm or ppb

A count of decimal times is best but a count of fractions is near enough as 10 bits (1024) is very close to being 1000. As long as it is only needed for a correction such as ppb the approximation would only manifest itself as a 7% error.

The version chosen

1 bit sign, 33 bits for seconds, 30 bits for fraction

+/- 272 years at 1ns or 1 ppb per second
Clock era is 1970

Advantages:

  • adequately representing the freq adjustments for pps
  • simple transformation to NTP and time_t
  • approximates to ns or, with a bit shift, to us or ms
  • adequately covers the next two centuries
  • one unit represents 1 ppb for display

Disadvantage:

  • none

Alternatives considered

1 bit sign, 43 bits for seconds, 20 bits for fraction

+/- 278,731 years at 1us or 1 ppm per second

Advantages:

  • a wide coverage
  • simple transformation to NTP and time_t
  • approximates to us or, with a bitwise shift, to ms
  • one unit represents 1 ppm for display

Disadvantage:

  • not able to reflect the freq adjustments for pps.

1 bit sign, 35bits for seconds, 28bits for fraction

+/- 1089 years at 3ns or 3ppb per second
looks like SSSS SSSS S.FFF FFFF in hex

Advantages:

  • easily represented in hex
  • a wide coverage
  • simple transformation to NTP and time_t

Disadvantage:

  • one unit doesn't approximate to anything simple

32 bits for seconds, 32 bits for fraction

Ntp time with an era of 1900
1900 to 2036 with a resolution of 250ps or 0.25 ppb

Advantages:

  • Already NTP and easily converted to time_t

Disadvantage:

  • Will rollover in 2036

Use 96MHz int64 count

+/- 3044 years with a resolution of 10ns or 10ppb per second

Advantages:

  • a wide coverage

Disadvantage:

  • cannot use simple bit shifts to transform to NTP and time_t
  • not transferable to a system with a different clock rate

Use a count of ns

+/- 292 years at 1ns or 1ppb per second

Advantages:

  • adequately representing the freq adjustments for pps
  • easily usable with ppb and ns
  • a wide coverage

Disadvantage:

  • cannot use simple bit shifts to transform to NTP and time_t
Committer:
andrewboyson
Date:
Thu Feb 21 12:47:15 2019 +0000
Revision:
54:a3c018ceca77
Parent:
53:2605da6cf1c7
Child:
57:4daf2e423b27
Two changes:; Added method to governor module to allow pps to synchronise to the nearest second if the full time is not available.; Added UTC conversion to the governor synchronisation input and to the NTP timestamp conversions.

Who changed what in which revision?

UserRevisionLine numberNew contents of line
andrewboyson 47:fd2af868c10a 1 #include <stdlib.h>
andrewboyson 47:fd2af868c10a 2 #include <stdbool.h>
andrewboyson 47:fd2af868c10a 3
andrewboyson 54:a3c018ceca77 4 #include "log.h"
andrewboyson 54:a3c018ceca77 5 #include "clktime.h"
andrewboyson 54:a3c018ceca77 6 #include "clk.h"
andrewboyson 54:a3c018ceca77 7 #include "clkutc.h"
andrewboyson 47:fd2af868c10a 8
andrewboyson 47:fd2af868c10a 9 #define GPREG0 (*((volatile unsigned *) 0x40024044))
andrewboyson 47:fd2af868c10a 10
andrewboyson 47:fd2af868c10a 11 volatile int32_t slew = 0; //ns - up to +/- 2.147s of slew
andrewboyson 47:fd2af868c10a 12 volatile int32_t ppb = 0; //This gets set to the last recorded ppb in TickInit
andrewboyson 47:fd2af868c10a 13
andrewboyson 47:fd2af868c10a 14 int32_t ClkGovGetSlew() { return slew; }
andrewboyson 47:fd2af868c10a 15 int32_t ClkGovGetPpb() { return ppb; }
andrewboyson 47:fd2af868c10a 16
andrewboyson 47:fd2af868c10a 17 void ClkGovSetSlew(int32_t value) { slew = value; }
andrewboyson 47:fd2af868c10a 18 void ClkGovSetPpb (int32_t value) { ppb = value; GPREG0 = ppb; }
andrewboyson 47:fd2af868c10a 19 void ClkGovInit()
andrewboyson 47:fd2af868c10a 20 {
andrewboyson 47:fd2af868c10a 21 ppb = GPREG0;
andrewboyson 47:fd2af868c10a 22 }
andrewboyson 47:fd2af868c10a 23
andrewboyson 47:fd2af868c10a 24 //Clock limits
andrewboyson 53:2605da6cf1c7 25 int ClkGovFreqDivisor = 10;
andrewboyson 53:2605da6cf1c7 26 int ClkGovFreqChangeMaxPpb = 1000;
andrewboyson 53:2605da6cf1c7 27 int ClkGovFreqSyncedLimPpb = 1000;
andrewboyson 53:2605da6cf1c7 28 int ClkGovFreqSyncedHysPpb = 100;
andrewboyson 53:2605da6cf1c7 29 int ClkGovSlewDivisor = 10;
andrewboyson 53:2605da6cf1c7 30 int ClkGovSlewChangeMaxMs = 10;
andrewboyson 53:2605da6cf1c7 31 int ClkGovSlewSyncedLimNs = 10000000;
andrewboyson 53:2605da6cf1c7 32 int ClkGovSlewSyncedHysNs = 1000000;
andrewboyson 53:2605da6cf1c7 33 int ClkGovSlewOffsetMaxSecs = 3;
andrewboyson 47:fd2af868c10a 34
andrewboyson 47:fd2af868c10a 35 bool ClkGovTrace = false;
andrewboyson 47:fd2af868c10a 36
andrewboyson 47:fd2af868c10a 37 bool ClkGovIsReceivingTime = false; //This is set from the external source of time
andrewboyson 47:fd2af868c10a 38 bool ClkGovTimeIsSynced = false;
andrewboyson 47:fd2af868c10a 39 bool ClkGovRateIsSynced = false;
andrewboyson 47:fd2af868c10a 40 bool ClkGovIsSynced() { return ClkGovRateIsSynced && ClkGovTimeIsSynced; }
andrewboyson 47:fd2af868c10a 41
andrewboyson 47:fd2af868c10a 42 static void setSyncedTime(int64_t diff)
andrewboyson 47:fd2af868c10a 43 {
andrewboyson 47:fd2af868c10a 44 int64_t absDiff = llabs(diff);
andrewboyson 53:2605da6cf1c7 45 int64_t limit = ClkGovSlewSyncedLimNs;
andrewboyson 53:2605da6cf1c7 46 int64_t hysterisis = ClkGovSlewSyncedHysNs;
andrewboyson 47:fd2af868c10a 47
andrewboyson 47:fd2af868c10a 48 if (absDiff < limit - hysterisis)
andrewboyson 47:fd2af868c10a 49 {
andrewboyson 47:fd2af868c10a 50 if (!ClkGovTimeIsSynced) LogTimeF("Time sync acquired\r\n");
andrewboyson 47:fd2af868c10a 51 ClkGovTimeIsSynced = true;
andrewboyson 47:fd2af868c10a 52 }
andrewboyson 47:fd2af868c10a 53 if (absDiff > limit + hysterisis)
andrewboyson 47:fd2af868c10a 54 {
andrewboyson 47:fd2af868c10a 55 if (ClkGovTimeIsSynced) LogTimeF("Time sync lost\r\n");
andrewboyson 47:fd2af868c10a 56 ClkGovTimeIsSynced = false;
andrewboyson 47:fd2af868c10a 57 }
andrewboyson 47:fd2af868c10a 58 }
andrewboyson 47:fd2af868c10a 59 static void setSyncedRate(int64_t diff)
andrewboyson 47:fd2af868c10a 60 {
andrewboyson 47:fd2af868c10a 61
andrewboyson 47:fd2af868c10a 62 int64_t absDiff = llabs(diff);
andrewboyson 53:2605da6cf1c7 63 int64_t limit = ClkGovFreqSyncedLimPpb;
andrewboyson 53:2605da6cf1c7 64 int64_t hysteresis = ClkGovFreqSyncedHysPpb;
andrewboyson 47:fd2af868c10a 65
andrewboyson 52:333a0822a06d 66 if (absDiff < limit - hysteresis)
andrewboyson 47:fd2af868c10a 67 {
andrewboyson 47:fd2af868c10a 68 if (!ClkGovRateIsSynced) LogTimeF("Rate sync acquired\r\n");
andrewboyson 47:fd2af868c10a 69 ClkGovRateIsSynced = true;
andrewboyson 47:fd2af868c10a 70 }
andrewboyson 52:333a0822a06d 71 if (absDiff > limit + hysteresis)
andrewboyson 47:fd2af868c10a 72 {
andrewboyson 47:fd2af868c10a 73 if ( ClkGovRateIsSynced) LogTimeF("Rate sync lost\r\n");
andrewboyson 47:fd2af868c10a 74 ClkGovRateIsSynced = false;
andrewboyson 47:fd2af868c10a 75 }
andrewboyson 47:fd2af868c10a 76 }
andrewboyson 47:fd2af868c10a 77
andrewboyson 47:fd2af868c10a 78 static void setSlew(int64_t diff)
andrewboyson 47:fd2af868c10a 79 {
andrewboyson 47:fd2af868c10a 80 int64_t toAdd = -diff / ClkGovSlewDivisor;
andrewboyson 52:333a0822a06d 81 int32_t slewMaxTicks = ClkGovSlewChangeMaxMs << CLK_TIME_ONE_MS_ISH_SHIFT;
andrewboyson 47:fd2af868c10a 82
andrewboyson 47:fd2af868c10a 83 if (toAdd > slewMaxTicks) toAdd = slewMaxTicks;
andrewboyson 47:fd2af868c10a 84 if (toAdd < -slewMaxTicks) toAdd = -slewMaxTicks;
andrewboyson 47:fd2af868c10a 85
andrewboyson 47:fd2af868c10a 86 slew = toAdd;
andrewboyson 47:fd2af868c10a 87
andrewboyson 47:fd2af868c10a 88 if (ClkGovTrace) LogTimeF("Sync setSlew diff %lld gives slew %lld gives TickSlew %ld\r\n", diff, toAdd, slew);
andrewboyson 47:fd2af868c10a 89 }
andrewboyson 47:fd2af868c10a 90 static void adjustPpb(int64_t diff)
andrewboyson 47:fd2af868c10a 91 {
andrewboyson 53:2605da6cf1c7 92 int64_t toAdd = diff / ClkGovFreqDivisor;
andrewboyson 53:2605da6cf1c7 93 int32_t maxAdd = ClkGovFreqChangeMaxPpb;
andrewboyson 47:fd2af868c10a 94
andrewboyson 47:fd2af868c10a 95 if (toAdd > maxAdd) toAdd = maxAdd;
andrewboyson 47:fd2af868c10a 96 if (toAdd < -maxAdd) toAdd = -maxAdd;
andrewboyson 47:fd2af868c10a 97
andrewboyson 47:fd2af868c10a 98 ClkGovSetPpb(ppb - toAdd);
andrewboyson 47:fd2af868c10a 99
andrewboyson 47:fd2af868c10a 100 if (ClkGovTrace) LogTimeF("Sync setPpb diff %lld gives toAdd %lld gives TickPpb %ld\r\n", diff, toAdd, ppb);
andrewboyson 47:fd2af868c10a 101 }
andrewboyson 47:fd2af868c10a 102
andrewboyson 47:fd2af868c10a 103 static int64_t lastIntClock = -1; //-1 indicates invalid value. 0 is a valid value.
andrewboyson 47:fd2af868c10a 104 static int64_t lastExtClock = -1;
andrewboyson 47:fd2af868c10a 105 static void reset(int64_t thisExtClock)
andrewboyson 47:fd2af868c10a 106 {
andrewboyson 47:fd2af868c10a 107 ClkTimeSet(thisExtClock);
andrewboyson 47:fd2af868c10a 108 ClkGovSetPpb(0);
andrewboyson 47:fd2af868c10a 109 lastIntClock = 0;
andrewboyson 47:fd2af868c10a 110 lastExtClock = 0;
andrewboyson 47:fd2af868c10a 111 }
andrewboyson 47:fd2af868c10a 112
andrewboyson 47:fd2af868c10a 113 static void sync(int64_t thisExtClock)
andrewboyson 47:fd2af868c10a 114 {
andrewboyson 47:fd2af868c10a 115
andrewboyson 47:fd2af868c10a 116 if (!ClkTimeIsSet()) //Cold start - only ever true if the RTC was not set.
andrewboyson 47:fd2af868c10a 117 {
andrewboyson 47:fd2af868c10a 118 LogTimeF("Sync - cold start of clock so resetting\r\n");
andrewboyson 47:fd2af868c10a 119 reset(thisExtClock);
andrewboyson 47:fd2af868c10a 120 return;
andrewboyson 47:fd2af868c10a 121 }
andrewboyson 47:fd2af868c10a 122
andrewboyson 47:fd2af868c10a 123 //Get the time at the time of the interrupt
andrewboyson 47:fd2af868c10a 124 int64_t thisIntClock;
andrewboyson 47:fd2af868c10a 125 int64_t thisAbsClock;
andrewboyson 47:fd2af868c10a 126 ClkTimesGetFromSnapshot(&thisIntClock, &thisAbsClock);
andrewboyson 47:fd2af868c10a 127
andrewboyson 47:fd2af868c10a 128 //Calulate the time error
andrewboyson 47:fd2af868c10a 129 int64_t absDiff = thisAbsClock - thisExtClock;
andrewboyson 52:333a0822a06d 130 if (llabs(absDiff) > ((int64_t)ClkGovSlewOffsetMaxSecs << CLK_TIME_ONE_SECOND_SHIFT))
andrewboyson 47:fd2af868c10a 131 {
andrewboyson 52:333a0822a06d 132 LogTimeF("Sync - offset is greater than %d seconds so resetting\r\n", ClkGovSlewOffsetMaxSecs);
andrewboyson 47:fd2af868c10a 133 reset(thisExtClock);
andrewboyson 47:fd2af868c10a 134 return;
andrewboyson 47:fd2af868c10a 135 }
andrewboyson 47:fd2af868c10a 136 setSlew(absDiff);
andrewboyson 47:fd2af868c10a 137 setSyncedTime(absDiff);
andrewboyson 47:fd2af868c10a 138
andrewboyson 47:fd2af868c10a 139 //Calculate the rate error
andrewboyson 47:fd2af868c10a 140 if (lastExtClock > -1)
andrewboyson 47:fd2af868c10a 141 {
andrewboyson 47:fd2af868c10a 142 int64_t extPeriod = thisExtClock - lastExtClock;
andrewboyson 47:fd2af868c10a 143
andrewboyson 47:fd2af868c10a 144 int64_t intPeriod = thisIntClock - lastIntClock;
andrewboyson 47:fd2af868c10a 145 int64_t periodDiff = intPeriod - extPeriod;
andrewboyson 47:fd2af868c10a 146
andrewboyson 47:fd2af868c10a 147 int64_t ppbDiff;
andrewboyson 47:fd2af868c10a 148 if (extPeriod == CLK_TIME_ONE_SECOND) ppbDiff = periodDiff; //This saves a 64bit shift and division for PPS
andrewboyson 47:fd2af868c10a 149 else ppbDiff = (periodDiff << CLK_TIME_ONE_SECOND_SHIFT) / extPeriod;
andrewboyson 47:fd2af868c10a 150
andrewboyson 47:fd2af868c10a 151 adjustPpb(ppbDiff);
andrewboyson 47:fd2af868c10a 152 setSyncedRate(ppbDiff);
andrewboyson 47:fd2af868c10a 153 }
andrewboyson 47:fd2af868c10a 154
andrewboyson 47:fd2af868c10a 155 //Save last values
andrewboyson 47:fd2af868c10a 156 lastIntClock = thisIntClock;
andrewboyson 47:fd2af868c10a 157 lastExtClock = thisExtClock;
andrewboyson 47:fd2af868c10a 158 }
andrewboyson 47:fd2af868c10a 159 void ClkGovSyncPpsI( ) { ClkTimeSaveSnapshot(); }
andrewboyson 54:a3c018ceca77 160 void ClkGovSyncPpsN(time_t t )
andrewboyson 54:a3c018ceca77 161 {
andrewboyson 54:a3c018ceca77 162 int64_t time;
andrewboyson 54:a3c018ceca77 163 time = (int64_t)t << CLK_TIME_ONE_SECOND_SHIFT;
andrewboyson 54:a3c018ceca77 164 time = ClkUtcToTai(time);
andrewboyson 54:a3c018ceca77 165 sync(time);
andrewboyson 54:a3c018ceca77 166 }
andrewboyson 54:a3c018ceca77 167 void ClkGovSyncPpsZ( )
andrewboyson 54:a3c018ceca77 168 {
andrewboyson 54:a3c018ceca77 169 int64_t time;
andrewboyson 54:a3c018ceca77 170 time = ClkNowTai();
andrewboyson 54:a3c018ceca77 171 time += 1UL << (CLK_TIME_ONE_SECOND_SHIFT - 1); //Add half a second so as to round to nearest rather than round down
andrewboyson 54:a3c018ceca77 172 time >>= CLK_TIME_ONE_SECOND_SHIFT;
andrewboyson 54:a3c018ceca77 173 time <<= CLK_TIME_ONE_SECOND_SHIFT;
andrewboyson 54:a3c018ceca77 174 sync(time);
andrewboyson 54:a3c018ceca77 175 }
andrewboyson 54:a3c018ceca77 176
andrewboyson 47:fd2af868c10a 177 void ClkGovSyncTime(int64_t time ) { ClkTimeSaveSnapshot(); sync(time); }