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:
Mon Jul 27 10:30:10 2020 +0000
Revision:
76:c2035b7754fe
Parent:
75:09d473b1a67f
Corrected haveFullTime in sync time PPS

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 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 76:c2035b7754fe 175 sync(time, true);
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 76:c2035b7754fe 184 sync(time, false);
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 }