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:
Fri Oct 18 12:33:56 2019 +0000
Revision:
69:4e48d3859b87
Parent:
57:4daf2e423b27
Child:
70:d04775a75597
Changed clkgovsync routines to accept an offset time in seconds rather than an absolute time.

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