ESP32-PaxCounter/src/timekeeper.cpp

335 lines
10 KiB
C++
Raw Normal View History

2019-02-24 01:44:55 +01:00
#include "timekeeper.h"
2019-02-21 23:17:01 +01:00
2019-03-31 15:56:56 +02:00
#if !(HAS_LORA)
#if (TIME_SYNC_LORASERVER)
#error TIME_SYNC_LORASERVER defined, but device has no LORA configured
2019-03-09 22:08:57 +01:00
#elif (TIME_SYNC_LORAWAN)
#error TIME_SYNC_LORAWAN defined, but device has no LORA configured
#endif
#endif
2019-02-21 23:17:01 +01:00
// Local logging tag
2019-02-27 00:52:27 +01:00
static const char TAG[] = __FILE__;
2019-02-21 23:17:01 +01:00
2019-02-24 15:08:41 +01:00
// symbol to display current time source
2021-03-31 09:44:23 +02:00
const char timeSetSymbols[] = {'G', 'R', 'L', 'S', '?'};
2019-02-24 15:08:41 +01:00
// set Time Zone for user setting from paxcounter.conf
TimeChangeRule myDST = DAYLIGHT_TIME;
TimeChangeRule mySTD = STANDARD_TIME;
Timezone myTZ(myDST, mySTD);
bool volatile TimePulseTick = false;
timesource_t timeSource = _unsynced;
TaskHandle_t ClockTask = NULL;
hw_timer_t *ppsIRQ = NULL;
#ifdef HAS_IF482
2020-02-03 15:28:45 +01:00
#if (HAS_SDS011)
#error cannot use IF482 together with SDS011 (both use UART#2)
#endif
HardwareSerial IF482(2); // use UART #2 (#1 may be in use for serial GPS)
#endif
Ticker timesyncer;
2019-02-24 23:13:15 +01:00
void setTimeSyncIRQ() { xTaskNotify(irqHandlerTask, TIMESYNC_IRQ, eSetBits); }
2019-03-02 13:32:02 +01:00
2019-08-03 12:27:24 +02:00
void calibrateTime(void) {
ESP_LOGD(TAG, "[%0.3f] calibrateTime, timeSource == %d", millis() / 1000.0,
timeSource);
2019-02-25 00:26:46 +01:00
time_t t = 0;
2019-08-03 14:01:25 +02:00
uint16_t t_msec = 0;
2019-02-21 23:17:01 +01:00
2020-03-06 19:24:58 +01:00
// kick off asychronous lora timesync if we have
#if (HAS_LORA) && ((TIME_SYNC_LORASERVER) || (TIME_SYNC_LORAWAN))
2020-03-11 23:49:06 +01:00
timesync_request();
2019-02-21 23:17:01 +01:00
#endif
2019-02-23 23:39:45 +01:00
// if no LORA timesource is available, or if we lost time, then fallback to
// local time source RTS or GPS
if (((!TIME_SYNC_LORASERVER) && (!TIME_SYNC_LORAWAN)) ||
(timeSource == _unsynced)) {
2019-02-21 23:17:01 +01:00
2020-03-06 19:24:58 +01:00
// has RTC -> fallback to RTC time
2019-08-03 14:01:25 +02:00
#ifdef HAS_RTC
2020-03-07 19:41:27 +01:00
t = get_rtctime();
2020-09-29 17:28:27 +02:00
// set time from RTC - method will check if time is valid
setMyTime((uint32_t)t, t_msec, _rtc);
2019-08-03 14:01:25 +02:00
#endif
2020-03-06 19:24:58 +01:00
// no RTC -> fallback to GPS time
#if (HAS_GPS)
2020-03-11 23:49:06 +01:00
t = get_gpstime(&t_msec);
// set time from GPS - method will check if time is valid
setMyTime((uint32_t)t, t_msec, _gps);
2020-03-06 19:24:58 +01:00
#endif
2020-03-11 23:49:06 +01:00
2020-03-07 19:41:27 +01:00
} // fallback
2019-02-24 23:13:15 +01:00
2020-03-07 19:41:27 +01:00
else
// no fallback time source available -> we can't set time
return;
2019-03-02 13:32:02 +01:00
2019-08-03 12:27:24 +02:00
} // calibrateTime()
2019-02-21 23:17:01 +01:00
// adjust system time, calibrate RTC and RTC_INT pps
void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec,
timesource_t mytimesource) {
// called with invalid timesource?
if (mytimesource == _unsynced)
return;
// increment t_sec only if t_msec > 1000
time_t time_to_set = (time_t)(t_sec + t_msec / 1000);
// do we have a valid time?
if (timeIsValid(time_to_set)) {
// if we have msec fraction, then wait until top of second with
// millisecond precision
if (t_msec % 1000) {
time_to_set++;
vTaskDelay(pdMS_TO_TICKS(1000 - t_msec % 1000));
}
ESP_LOGI(TAG, "[%0.3f] UTC time: %d.%03d sec", _seconds(),
time_to_set, t_msec % 1000);
// if we have got an external timesource, set RTC time and shift RTC_INT pulse
// to top of second
#ifdef HAS_RTC
if ((mytimesource == _gps) || (mytimesource == _lora))
set_rtctime(time_to_set);
#endif
// if we have a software pps timer, shift it to top of second
#if (!defined GPS_INT && !defined RTC_INT)
timerWrite(ppsIRQ, 0); // reset pps timer
CLOCKIRQ(); // fire clock pps, this advances time 1 sec
#endif
setTime(time_to_set); // set the time on top of second
timeSource = mytimesource; // set global variable
timesyncer.attach(TIME_SYNC_INTERVAL * 60, setTimeSyncIRQ);
2020-10-05 13:40:22 +02:00
ESP_LOGD(TAG, "[%0.3f] Timesync finished, time was set | source: %c",
_seconds(), timeSetSymbols[mytimesource]);
} else {
timesyncer.attach(TIME_SYNC_INTERVAL_RETRY * 60, setTimeSyncIRQ);
time_t unix_sec_at_compilation = compiledUTC();
ESP_LOGD(TAG, "[%0.3f] Failed to synchronise time from source %c | unix sec obtained from source: %d | unix sec at program compilation: %d",
_seconds(), timeSetSymbols[mytimesource], time_to_set, unix_sec_at_compilation);
}
}
2019-02-21 23:17:01 +01:00
// helper function to setup a pulse per second for time synchronisation
2019-02-24 01:44:55 +01:00
uint8_t timepulse_init() {
2019-02-21 23:17:01 +01:00
// use time pulse from GPS as time base with fixed 1Hz frequency
#ifdef GPS_INT
2019-02-27 22:40:58 +01:00
// setup external interupt pin for rising edge GPS INT
2019-02-21 23:17:01 +01:00
pinMode(GPS_INT, INPUT_PULLDOWN);
// setup external rtc 1Hz clock as pulse per second clock
ESP_LOGI(TAG, "Timepulse: external (GPS)");
return 1; // success
// use pulse from on board RTC chip as time base with fixed frequency
#elif defined RTC_INT
2019-02-27 22:40:58 +01:00
// setup external interupt pin for falling edge RTC INT
2019-02-21 23:17:01 +01:00
pinMode(RTC_INT, INPUT_PULLUP);
// setup external rtc 1Hz clock as pulse per second clock
if (I2C_MUTEX_LOCK()) {
Rtc.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz);
Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeClock);
I2C_MUTEX_UNLOCK();
ESP_LOGI(TAG, "Timepulse: external (RTC)");
return 1; // success
} else {
2019-02-22 22:28:35 +01:00
ESP_LOGE(TAG, "RTC initialization error, I2C bus busy");
2019-02-21 23:17:01 +01:00
return 0; // failure
}
return 1; // success
#else
// use ESP32 hardware timer as time base with adjustable frequency
2019-03-03 17:35:08 +01:00
ppsIRQ = timerBegin(1, 8000, true); // set 80 MHz prescaler to 1/10000 sec
2019-03-03 12:57:00 +01:00
timerAlarmWrite(ppsIRQ, 10000, true); // 1000ms
2019-02-21 23:17:01 +01:00
ESP_LOGI(TAG, "Timepulse: internal (ESP32 hardware timer)");
return 1; // success
#endif
} // timepulse_init
void timepulse_start(void) {
2019-02-21 23:17:01 +01:00
#ifdef GPS_INT // start external clock gps pps line
attachInterrupt(digitalPinToInterrupt(GPS_INT), CLOCKIRQ, RISING);
#elif defined RTC_INT // start external clock rtc
attachInterrupt(digitalPinToInterrupt(RTC_INT), CLOCKIRQ, FALLING);
#else // start internal clock esp32 hardware timer
2019-03-03 12:57:00 +01:00
timerAttachInterrupt(ppsIRQ, &CLOCKIRQ, true);
timerAlarmEnable(ppsIRQ);
2019-02-21 23:17:01 +01:00
#endif
2019-04-13 13:59:30 +02:00
// start cyclic time sync
setTimeSyncIRQ(); // init systime by RTC or GPS or LORA
timesyncer.attach(TIME_SYNC_INTERVAL * 60, setTimeSyncIRQ);
2019-02-21 23:17:01 +01:00
}
// interrupt service routine triggered by either pps or esp32 hardware timer
void IRAM_ATTR CLOCKIRQ(void) {
2019-02-27 22:40:58 +01:00
2019-03-24 00:15:04 +01:00
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
2019-03-02 13:32:02 +01:00
SyncToPPS(); // advance systime, see microTime.h
// advance wall clock, if we have
2019-04-10 21:38:17 +02:00
#if (defined HAS_IF482 || defined HAS_DCF77)
xTaskNotifyFromISR(ClockTask, uint32_t(now()), eSetBits,
&xHigherPriorityTaskWoken);
#endif
2019-02-27 22:40:58 +01:00
// flip time pulse ticker, if needed
2019-03-24 01:05:13 +01:00
#ifdef HAS_DISPLAY
#if (defined GPS_INT || defined RTC_INT)
TimePulseTick = !TimePulseTick; // flip pulse ticker
2019-03-24 01:05:13 +01:00
#endif
2019-02-21 23:17:01 +01:00
#endif
2019-02-27 22:40:58 +01:00
2019-03-02 20:58:06 +01:00
// yield only if we should
if (xHigherPriorityTaskWoken)
portYIELD_FROM_ISR();
2019-02-21 23:17:01 +01:00
}
2019-02-22 22:28:35 +01:00
// helper function to check plausibility of a time
2019-03-02 13:32:02 +01:00
time_t timeIsValid(time_t const t) {
2019-02-22 22:28:35 +01:00
// is it a time in the past? we use compile date to guess
return (t >= compiledUTC() ? t : 0);
2019-02-22 22:28:35 +01:00
}
// helper function to convert compile time to UTC time
time_t compiledUTC(void) {
2019-03-02 13:32:02 +01:00
static time_t t = myTZ.toUTC(RtcDateTime(__DATE__, __TIME__).Epoch32Time());
return t;
2019-02-22 22:28:35 +01:00
}
// helper function to calculate serial transmit time
TickType_t tx_Ticks(uint32_t framesize, unsigned long baud, uint32_t config,
int8_t rxPin, int8_t txPins) {
uint32_t databits = ((config & 0x0c) >> 2) + 5;
uint32_t stopbits = ((config & 0x20) >> 5) + 1;
uint32_t txTime = (databits + stopbits + 1) * framesize * 1000.0 / baud;
// +1 for the startbit
return round(txTime);
}
2019-03-31 19:13:06 +02:00
#if (defined HAS_IF482 || defined HAS_DCF77)
2019-02-21 23:17:01 +01:00
2019-03-31 19:13:06 +02:00
#if (defined HAS_DCF77 && defined HAS_IF482)
2019-02-21 23:17:01 +01:00
#error You must define at most one of IF482 or DCF77!
#endif
void clock_init(void) {
// setup clock output interface
#ifdef HAS_IF482
IF482.begin(HAS_IF482);
#elif defined HAS_DCF77
pinMode(HAS_DCF77, OUTPUT);
#endif
2020-03-04 21:54:26 +01:00
time_t userUTCTime = now();
2019-03-02 20:01:27 +01:00
xTaskCreatePinnedToCore(clock_loop, // task function
"clockloop", // name of task
2048, // stack size of task
(void *)&userUTCTime, // start time as task parameter
2019-04-07 21:54:19 +02:00
4, // priority of the task
2019-03-02 20:01:27 +01:00
&ClockTask, // task handle
1); // CPU core
2019-02-21 23:17:01 +01:00
2020-10-30 12:24:16 +01:00
_ASSERT(ClockTask != NULL); // has clock task started?
2019-02-21 23:17:01 +01:00
} // clock_init
2019-03-02 20:01:27 +01:00
void clock_loop(void *taskparameter) { // ClockTask
2019-02-21 23:17:01 +01:00
2019-03-02 20:01:27 +01:00
// caveat: don't use now() in this task, it will cause a race condition
2019-08-03 18:06:06 +02:00
// due to concurrent access to i2c bus when reading/writing from/to rtc chip!
2019-02-21 23:17:01 +01:00
2019-03-02 21:25:22 +01:00
#define nextmin(t) (t + DCF77_FRAME_SIZE + 1) // next minute
2019-02-21 23:17:01 +01:00
2019-08-03 18:06:06 +02:00
#ifdef HAS_TWO_LED
2019-03-24 01:05:13 +01:00
static bool led1_state = false;
2019-08-03 18:06:06 +02:00
#endif
2019-03-02 20:01:27 +01:00
uint32_t printtime;
2019-03-24 00:15:04 +01:00
time_t t = *((time_t *)taskparameter), last_printtime = 0; // UTC time seconds
2019-02-21 23:17:01 +01:00
#ifdef HAS_DCF77
uint8_t *DCFpulse; // pointer on array with DCF pulse bits
DCFpulse = DCF77_Frame(nextmin(t)); // load first DCF frame before start
#elif defined HAS_IF482
2019-03-24 16:20:39 +01:00
static TickType_t txDelay = pdMS_TO_TICKS(1000 - IF482_SYNC_FIXUP) -
tx_Ticks(IF482_FRAME_SIZE, HAS_IF482);
2019-02-21 23:17:01 +01:00
#endif
2019-04-13 13:59:30 +02:00
// output the next second's pulse/telegram after pps arrived
2019-02-21 23:17:01 +01:00
for (;;) {
2019-04-10 21:38:17 +02:00
// wait for timepulse and store UTC time in seconds got
xTaskNotifyWait(0x00, ULONG_MAX, &printtime, portMAX_DELAY);
t = time_t(printtime);
2019-04-13 13:59:30 +02:00
2019-03-24 00:15:04 +01:00
// no confident or no recent time -> suppress clock output
if ((timeStatus() == timeNotSet) || !(timeIsValid(t)) ||
(t == last_printtime))
2019-03-16 21:01:43 +01:00
continue;
2019-02-21 23:17:01 +01:00
#if defined HAS_IF482
// wait until moment to fire. Normally we won't get notified during this
// timespan, except when next pps pulse arrives while waiting, because pps
// was adjusted by recent time sync
2019-04-10 21:38:17 +02:00
if (xTaskNotifyWait(0x00, ULONG_MAX, &printtime, txDelay) == pdTRUE)
t = time_t(printtime); // new adjusted UTC time seconds
// send IF482 telegram
IF482.print(IF482_Frame(t + 1)); // note: telegram is for *next* second
2019-02-21 23:17:01 +01:00
#elif defined HAS_DCF77
if (second(t) == DCF77_FRAME_SIZE - 1) // is it time to load new frame?
2019-03-02 20:01:27 +01:00
DCFpulse = DCF77_Frame(nextmin(t)); // generate frame for next minute
2019-02-21 23:17:01 +01:00
2019-03-02 20:58:06 +01:00
if (minute(nextmin(t)) == // do we still have a recent frame?
DCFpulse[DCF77_FRAME_SIZE]) // (timepulses could be missed!)
DCF77_Pulse(t, DCFpulse); // then output current second's pulse
2019-04-10 21:38:17 +02:00
// else we have no recent frame, thus suppressing clock output
2019-02-21 23:17:01 +01:00
#endif
2019-04-10 21:38:17 +02:00
// pps blink on secondary LED if we have one
#ifdef HAS_TWO_LED
if (led1_state)
switch_LED1(LED_OFF);
else
switch_LED1(LED_ON);
led1_state = !led1_state;
#endif
last_printtime = t;
2019-02-21 23:17:01 +01:00
} // for
} // clock_loop()
#endif // HAS_IF482 || defined HAS_DCF77