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:
Tue Feb 12 13:13:27 2019 +0000
Revision:
52:333a0822a06d
Parent:
47:fd2af868c10a
Child:
53:2605da6cf1c7
Refactered some gov setting names

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