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 daysclktimer
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 backuptm
Routines to manipulate struct tm local and utc timesclk
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
clk/clkgov.c@75:09d473b1a67f, 2020-04-14 (annotated)
- Committer:
- andrewboyson
- Date:
- Tue Apr 14 16:09:53 2020 +0000
- Revision:
- 75:09d473b1a67f
- Parent:
- 74:9d336a47ab84
- Child:
- 76:c2035b7754fe
Removed bug whereby a difference of over 0.5 second prevented the governor from aligning the clock
Who changed what in which revision?
User | Revision | Line number | New 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 | 57:4daf2e423b27 | 8 | #include "time64.h" |
andrewboyson | 71:f621d2127216 | 9 | #include "rtc.h" |
andrewboyson | 47:fd2af868c10a | 10 | |
andrewboyson | 47:fd2af868c10a | 11 | #define GPREG0 (*((volatile unsigned *) 0x40024044)) |
andrewboyson | 47:fd2af868c10a | 12 | |
andrewboyson | 47:fd2af868c10a | 13 | volatile int32_t slew = 0; //ns - up to +/- 2.147s of slew |
andrewboyson | 47:fd2af868c10a | 14 | volatile int32_t ppb = 0; //This gets set to the last recorded ppb in TickInit |
andrewboyson | 47:fd2af868c10a | 15 | |
andrewboyson | 47:fd2af868c10a | 16 | int32_t ClkGovGetSlew() { return slew; } |
andrewboyson | 47:fd2af868c10a | 17 | int32_t ClkGovGetPpb() { return ppb; } |
andrewboyson | 47:fd2af868c10a | 18 | |
andrewboyson | 47:fd2af868c10a | 19 | void ClkGovSetSlew(int32_t value) { slew = value; } |
andrewboyson | 47:fd2af868c10a | 20 | void ClkGovSetPpb (int32_t value) { ppb = value; GPREG0 = ppb; } |
andrewboyson | 47:fd2af868c10a | 21 | void ClkGovInit() |
andrewboyson | 47:fd2af868c10a | 22 | { |
andrewboyson | 71:f621d2127216 | 23 | if (RtcPowerLost()) GPREG0 = 0; |
andrewboyson | 47:fd2af868c10a | 24 | ppb = GPREG0; |
andrewboyson | 47:fd2af868c10a | 25 | } |
andrewboyson | 47:fd2af868c10a | 26 | |
andrewboyson | 47:fd2af868c10a | 27 | //Clock limits |
andrewboyson | 74:9d336a47ab84 | 28 | int ClkGovFreqDivisor = 10; |
andrewboyson | 74:9d336a47ab84 | 29 | int ClkGovFreqChangeMaxPpb = 1000; |
andrewboyson | 74:9d336a47ab84 | 30 | int ClkGovFreqSyncedLimPpb = 1000; |
andrewboyson | 74:9d336a47ab84 | 31 | int ClkGovFreqSyncedHysPpb = 100; |
andrewboyson | 74:9d336a47ab84 | 32 | int ClkGovSlewDivisor = 10; |
andrewboyson | 74:9d336a47ab84 | 33 | int ClkGovSlewChangeMaxMs = 10; |
andrewboyson | 74:9d336a47ab84 | 34 | int ClkGovSlewSyncedLimNs = 10000000; |
andrewboyson | 74:9d336a47ab84 | 35 | int ClkGovSlewSyncedHysNs = 1000000; |
andrewboyson | 74:9d336a47ab84 | 36 | int ClkGovSlewOffsetMaxSecs = 3; |
andrewboyson | 47:fd2af868c10a | 37 | |
andrewboyson | 47:fd2af868c10a | 38 | bool ClkGovTrace = false; |
andrewboyson | 47:fd2af868c10a | 39 | |
andrewboyson | 47:fd2af868c10a | 40 | bool ClkGovIsReceivingTime = false; //This is set from the external source of time |
andrewboyson | 47:fd2af868c10a | 41 | bool ClkGovTimeIsSynced = false; |
andrewboyson | 47:fd2af868c10a | 42 | bool ClkGovRateIsSynced = false; |
andrewboyson | 47:fd2af868c10a | 43 | bool ClkGovIsSynced() { return ClkGovRateIsSynced && ClkGovTimeIsSynced; } |
andrewboyson | 47:fd2af868c10a | 44 | |
andrewboyson | 74:9d336a47ab84 | 45 | static void setTimeIsSyncedFlag(clktime diff) |
andrewboyson | 47:fd2af868c10a | 46 | { |
andrewboyson | 57:4daf2e423b27 | 47 | clktime absDiff = llabs(diff); |
andrewboyson | 57:4daf2e423b27 | 48 | clktime limit = ClkGovSlewSyncedLimNs; |
andrewboyson | 57:4daf2e423b27 | 49 | clktime hysterisis = ClkGovSlewSyncedHysNs; |
andrewboyson | 47:fd2af868c10a | 50 | |
andrewboyson | 47:fd2af868c10a | 51 | if (absDiff < limit - hysterisis) |
andrewboyson | 47:fd2af868c10a | 52 | { |
andrewboyson | 74:9d336a47ab84 | 53 | if (!ClkGovTimeIsSynced) LogTimeF("CLK Time sync acquired\r\n"); |
andrewboyson | 47:fd2af868c10a | 54 | ClkGovTimeIsSynced = true; |
andrewboyson | 47:fd2af868c10a | 55 | } |
andrewboyson | 47:fd2af868c10a | 56 | if (absDiff > limit + hysterisis) |
andrewboyson | 47:fd2af868c10a | 57 | { |
andrewboyson | 74:9d336a47ab84 | 58 | if (ClkGovTimeIsSynced) LogTimeF("CLK Time sync lost (difference = %+lld)\r\n", diff); |
andrewboyson | 47:fd2af868c10a | 59 | ClkGovTimeIsSynced = false; |
andrewboyson | 47:fd2af868c10a | 60 | } |
andrewboyson | 47:fd2af868c10a | 61 | } |
andrewboyson | 74:9d336a47ab84 | 62 | static void setRateIsSyncedFlag(clktime diff) |
andrewboyson | 47:fd2af868c10a | 63 | { |
andrewboyson | 47:fd2af868c10a | 64 | |
andrewboyson | 57:4daf2e423b27 | 65 | clktime absDiff = llabs(diff); |
andrewboyson | 57:4daf2e423b27 | 66 | clktime limit = ClkGovFreqSyncedLimPpb; |
andrewboyson | 57:4daf2e423b27 | 67 | clktime hysteresis = ClkGovFreqSyncedHysPpb; |
andrewboyson | 47:fd2af868c10a | 68 | |
andrewboyson | 52:333a0822a06d | 69 | if (absDiff < limit - hysteresis) |
andrewboyson | 47:fd2af868c10a | 70 | { |
andrewboyson | 74:9d336a47ab84 | 71 | if (!ClkGovRateIsSynced) LogTimeF("CLK Rate sync acquired\r\n"); |
andrewboyson | 47:fd2af868c10a | 72 | ClkGovRateIsSynced = true; |
andrewboyson | 47:fd2af868c10a | 73 | } |
andrewboyson | 52:333a0822a06d | 74 | if (absDiff > limit + hysteresis) |
andrewboyson | 47:fd2af868c10a | 75 | { |
andrewboyson | 74:9d336a47ab84 | 76 | if ( ClkGovRateIsSynced) LogTimeF("CLK Rate sync lost\r\n"); |
andrewboyson | 47:fd2af868c10a | 77 | ClkGovRateIsSynced = false; |
andrewboyson | 47:fd2af868c10a | 78 | } |
andrewboyson | 47:fd2af868c10a | 79 | } |
andrewboyson | 47:fd2af868c10a | 80 | |
andrewboyson | 57:4daf2e423b27 | 81 | static void setSlew(clktime diff) |
andrewboyson | 47:fd2af868c10a | 82 | { |
andrewboyson | 57:4daf2e423b27 | 83 | clktime toAdd = -diff / ClkGovSlewDivisor; |
andrewboyson | 52:333a0822a06d | 84 | int32_t slewMaxTicks = ClkGovSlewChangeMaxMs << CLK_TIME_ONE_MS_ISH_SHIFT; |
andrewboyson | 47:fd2af868c10a | 85 | |
andrewboyson | 47:fd2af868c10a | 86 | if (toAdd > slewMaxTicks) toAdd = slewMaxTicks; |
andrewboyson | 47:fd2af868c10a | 87 | if (toAdd < -slewMaxTicks) toAdd = -slewMaxTicks; |
andrewboyson | 47:fd2af868c10a | 88 | |
andrewboyson | 47:fd2af868c10a | 89 | slew = toAdd; |
andrewboyson | 47:fd2af868c10a | 90 | |
andrewboyson | 74:9d336a47ab84 | 91 | if (ClkGovTrace) LogTimeF("CLK setSlew diff %lld gives slew %lld gives TickSlew %ld\r\n", diff, toAdd, slew); |
andrewboyson | 47:fd2af868c10a | 92 | } |
andrewboyson | 57:4daf2e423b27 | 93 | static void adjustPpb(clktime diff) |
andrewboyson | 47:fd2af868c10a | 94 | { |
andrewboyson | 57:4daf2e423b27 | 95 | clktime toAdd = diff / ClkGovFreqDivisor; |
andrewboyson | 53:2605da6cf1c7 | 96 | int32_t maxAdd = ClkGovFreqChangeMaxPpb; |
andrewboyson | 47:fd2af868c10a | 97 | |
andrewboyson | 47:fd2af868c10a | 98 | if (toAdd > maxAdd) toAdd = maxAdd; |
andrewboyson | 47:fd2af868c10a | 99 | if (toAdd < -maxAdd) toAdd = -maxAdd; |
andrewboyson | 47:fd2af868c10a | 100 | |
andrewboyson | 47:fd2af868c10a | 101 | ClkGovSetPpb(ppb - toAdd); |
andrewboyson | 47:fd2af868c10a | 102 | |
andrewboyson | 74:9d336a47ab84 | 103 | if (ClkGovTrace) LogTimeF("CLK setPpb diff %lld gives toAdd %lld gives TickPpb %ld\r\n", diff, toAdd, ppb); |
andrewboyson | 47:fd2af868c10a | 104 | } |
andrewboyson | 47:fd2af868c10a | 105 | |
andrewboyson | 57:4daf2e423b27 | 106 | static clktime lastIntClock = -1; //-1 indicates invalid value. 0 is a valid value. |
andrewboyson | 57:4daf2e423b27 | 107 | static clktime lastExtClock = -1; |
andrewboyson | 57:4daf2e423b27 | 108 | static void reset(clktime thisExtClock) |
andrewboyson | 47:fd2af868c10a | 109 | { |
andrewboyson | 47:fd2af868c10a | 110 | ClkTimeSet(thisExtClock); |
andrewboyson | 47:fd2af868c10a | 111 | ClkGovSetPpb(0); |
andrewboyson | 47:fd2af868c10a | 112 | lastIntClock = 0; |
andrewboyson | 47:fd2af868c10a | 113 | lastExtClock = 0; |
andrewboyson | 47:fd2af868c10a | 114 | } |
andrewboyson | 47:fd2af868c10a | 115 | |
andrewboyson | 72:8f15a8b142ab | 116 | static void sync(clktime thisExtClock, bool thisExtClockIsComplete) |
andrewboyson | 47:fd2af868c10a | 117 | { |
andrewboyson | 47:fd2af868c10a | 118 | |
andrewboyson | 72:8f15a8b142ab | 119 | if (!ClkTimeIsSet() && thisExtClockIsComplete) //Cold start - only ever true if the RTC was not set. |
andrewboyson | 47:fd2af868c10a | 120 | { |
andrewboyson | 74:9d336a47ab84 | 121 | LogTimeF("CLK cold start of clock so resetting\r\n"); |
andrewboyson | 47:fd2af868c10a | 122 | reset(thisExtClock); |
andrewboyson | 47:fd2af868c10a | 123 | return; |
andrewboyson | 47:fd2af868c10a | 124 | } |
andrewboyson | 47:fd2af868c10a | 125 | |
andrewboyson | 47:fd2af868c10a | 126 | //Get the time at the time of the interrupt |
andrewboyson | 57:4daf2e423b27 | 127 | clktime thisIntClock; |
andrewboyson | 57:4daf2e423b27 | 128 | clktime thisAbsClock; |
andrewboyson | 47:fd2af868c10a | 129 | ClkTimesGetFromSnapshot(&thisIntClock, &thisAbsClock); |
andrewboyson | 47:fd2af868c10a | 130 | |
andrewboyson | 47:fd2af868c10a | 131 | //Calulate the time error |
andrewboyson | 57:4daf2e423b27 | 132 | clktime absDiff = thisAbsClock - thisExtClock; |
andrewboyson | 74:9d336a47ab84 | 133 | if (llabs(absDiff) > ((clktime)1 << (CLK_TIME_ONE_SECOND_SHIFT - 1))) |
andrewboyson | 74:9d336a47ab84 | 134 | { |
andrewboyson | 74:9d336a47ab84 | 135 | LogTimeF("CLK offset %lld (this - ext) is greater than half a second\r\n", absDiff); |
andrewboyson | 74:9d336a47ab84 | 136 | } |
andrewboyson | 57:4daf2e423b27 | 137 | if (llabs(absDiff) > ((clktime)ClkGovSlewOffsetMaxSecs << CLK_TIME_ONE_SECOND_SHIFT)) |
andrewboyson | 47:fd2af868c10a | 138 | { |
andrewboyson | 74:9d336a47ab84 | 139 | LogTimeF("CLK offset is greater than %d seconds so resetting\r\n", ClkGovSlewOffsetMaxSecs); |
andrewboyson | 47:fd2af868c10a | 140 | reset(thisExtClock); |
andrewboyson | 47:fd2af868c10a | 141 | return; |
andrewboyson | 47:fd2af868c10a | 142 | } |
andrewboyson | 47:fd2af868c10a | 143 | setSlew(absDiff); |
andrewboyson | 74:9d336a47ab84 | 144 | setTimeIsSyncedFlag(absDiff); |
andrewboyson | 47:fd2af868c10a | 145 | |
andrewboyson | 47:fd2af868c10a | 146 | //Calculate the rate error |
andrewboyson | 47:fd2af868c10a | 147 | if (lastExtClock > -1) |
andrewboyson | 47:fd2af868c10a | 148 | { |
andrewboyson | 57:4daf2e423b27 | 149 | clktime extPeriod = thisExtClock - lastExtClock; |
andrewboyson | 47:fd2af868c10a | 150 | |
andrewboyson | 57:4daf2e423b27 | 151 | clktime intPeriod = thisIntClock - lastIntClock; |
andrewboyson | 57:4daf2e423b27 | 152 | clktime periodDiff = intPeriod - extPeriod; |
andrewboyson | 47:fd2af868c10a | 153 | |
andrewboyson | 57:4daf2e423b27 | 154 | clktime ppbDiff; |
andrewboyson | 47:fd2af868c10a | 155 | if (extPeriod == CLK_TIME_ONE_SECOND) ppbDiff = periodDiff; //This saves a 64bit shift and division for PPS |
andrewboyson | 47:fd2af868c10a | 156 | else ppbDiff = (periodDiff << CLK_TIME_ONE_SECOND_SHIFT) / extPeriod; |
andrewboyson | 47:fd2af868c10a | 157 | |
andrewboyson | 47:fd2af868c10a | 158 | adjustPpb(ppbDiff); |
andrewboyson | 74:9d336a47ab84 | 159 | setRateIsSyncedFlag(ppbDiff); |
andrewboyson | 47:fd2af868c10a | 160 | } |
andrewboyson | 47:fd2af868c10a | 161 | |
andrewboyson | 47:fd2af868c10a | 162 | //Save last values |
andrewboyson | 47:fd2af868c10a | 163 | lastIntClock = thisIntClock; |
andrewboyson | 47:fd2af868c10a | 164 | lastExtClock = thisExtClock; |
andrewboyson | 47:fd2af868c10a | 165 | } |
andrewboyson | 57:4daf2e423b27 | 166 | void ClkGovSyncPpsI() |
andrewboyson | 54:a3c018ceca77 | 167 | { |
andrewboyson | 57:4daf2e423b27 | 168 | ClkTimeSaveSnapshot(); |
andrewboyson | 57:4daf2e423b27 | 169 | } |
andrewboyson | 70:d04775a75597 | 170 | void ClkGovSyncPpsN(time64 t) //t is number of seconds utc |
andrewboyson | 70:d04775a75597 | 171 | { |
andrewboyson | 70:d04775a75597 | 172 | clktime time; |
andrewboyson | 70:d04775a75597 | 173 | time = (clktime)t << CLK_TIME_ONE_SECOND_SHIFT; |
andrewboyson | 70:d04775a75597 | 174 | time = ClkUtcToTai(time); |
andrewboyson | 72:8f15a8b142ab | 175 | sync(time, false); |
andrewboyson | 70:d04775a75597 | 176 | } |
andrewboyson | 70:d04775a75597 | 177 | void ClkGovSyncPpsZ() |
andrewboyson | 70:d04775a75597 | 178 | { |
andrewboyson | 57:4daf2e423b27 | 179 | clktime time; |
andrewboyson | 54:a3c018ceca77 | 180 | time = ClkNowTai(); |
andrewboyson | 70:d04775a75597 | 181 | time += 1UL << (CLK_TIME_ONE_SECOND_SHIFT - 1); //Add half a second so as to round to nearest rather than round down |
andrewboyson | 70:d04775a75597 | 182 | time >>= CLK_TIME_ONE_SECOND_SHIFT; |
andrewboyson | 70:d04775a75597 | 183 | time <<= CLK_TIME_ONE_SECOND_SHIFT; |
andrewboyson | 72:8f15a8b142ab | 184 | sync(time, true); |
andrewboyson | 54:a3c018ceca77 | 185 | } |
andrewboyson | 54:a3c018ceca77 | 186 | |
andrewboyson | 70:d04775a75597 | 187 | |
andrewboyson | 57:4daf2e423b27 | 188 | void ClkGovSyncTime(clktime time) |
andrewboyson | 57:4daf2e423b27 | 189 | { |
andrewboyson | 57:4daf2e423b27 | 190 | ClkTimeSaveSnapshot(); |
andrewboyson | 72:8f15a8b142ab | 191 | sync(time, true); |
andrewboyson | 57:4daf2e423b27 | 192 | } |