diff --git a/src/timekeeper.cpp b/src/timekeeper.cpp index 85f62864..c6540286 100644 --- a/src/timekeeper.cpp +++ b/src/timekeeper.cpp @@ -16,6 +16,9 @@ static const char TAG[] = __FILE__; const char timeSetSymbols[] = {'G', 'R', 'L', '?'}; #ifdef HAS_IF482 +#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 @@ -317,4 +320,274 @@ void clock_loop(void *taskparameter) { // ClockTask } // for } // clock_loop() +#endif // HAS_IF482 || defined HAS_DCF77 + if (t) { + timeSource = _rtc; + goto finish; + } +#endif + + goto finish; + +finish: + + setMyTime((uint32_t)t, t_msec, timeSource); // set time + +} // calibrateTime() + +// 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_LOGD(TAG, "[%0.3f] UTC epoch time: %d.%03d sec", millis() / 1000.0, + 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, timeSync); + ESP_LOGI(TAG, "[%0.3f] Timesync finished, time was set | source: %c", + millis() / 1000.0, timeSetSymbols[timeSource]); + } else { + timesyncer.attach(TIME_SYNC_INTERVAL_RETRY * 60, timeSync); + ESP_LOGI(TAG, "[%0.3f] Timesync failed, invalid time fetched | source: %c", + millis() / 1000.0, timeSetSymbols[timeSource]); + } +} + +// helper function to setup a pulse per second for time synchronisation +uint8_t timepulse_init() { + +// use time pulse from GPS as time base with fixed 1Hz frequency +#ifdef GPS_INT + + // setup external interupt pin for rising edge GPS INT + 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 + + // setup external interupt pin for falling edge RTC INT + 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 { + ESP_LOGE(TAG, "RTC initialization error, I2C bus busy"); + return 0; // failure + } + return 1; // success + +#else + // use ESP32 hardware timer as time base with adjustable frequency + ppsIRQ = timerBegin(1, 8000, true); // set 80 MHz prescaler to 1/10000 sec + timerAlarmWrite(ppsIRQ, 10000, true); // 1000ms + ESP_LOGI(TAG, "Timepulse: internal (ESP32 hardware timer)"); + return 1; // success + +#endif +} // timepulse_init + +void timepulse_start(void) { + +#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 + timerAttachInterrupt(ppsIRQ, &CLOCKIRQ, true); + timerAlarmEnable(ppsIRQ); +#endif + + // start cyclic time sync + timeSync(); // init systime by RTC or GPS or LORA + timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync); +} + +// interrupt service routine triggered by either pps or esp32 hardware timer +void IRAM_ATTR CLOCKIRQ(void) { + + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + + SyncToPPS(); // advance systime, see microTime.h + +// advance wall clock, if we have +#if (defined HAS_IF482 || defined HAS_DCF77) + xTaskNotifyFromISR(ClockTask, uint32_t(now()), eSetBits, + &xHigherPriorityTaskWoken); +#endif + +// flip time pulse ticker, if needed +#ifdef HAS_DISPLAY +#if (defined GPS_INT || defined RTC_INT) + TimePulseTick = !TimePulseTick; // flip pulse ticker +#endif +#endif + + // yield only if we should + if (xHigherPriorityTaskWoken) + portYIELD_FROM_ISR(); +} + +// helper function to check plausibility of a time +time_t timeIsValid(time_t const t) { + // is it a time in the past? we use compile date to guess + return (t >= compiledUTC() ? t : 0); +} + +// helper function to convert compile time to UTC time +time_t compiledUTC(void) { + static time_t t = myTZ.toUTC(RtcDateTime(__DATE__, __TIME__).Epoch32Time()); + return t; +} + +// 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); +} + +#if (defined HAS_IF482 || defined HAS_DCF77) + +#if (defined HAS_DCF77 && defined HAS_IF482) +#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 + + userUTCTime = now(); + + xTaskCreatePinnedToCore(clock_loop, // task function + "clockloop", // name of task + 2048, // stack size of task + (void *)&userUTCTime, // start time as task parameter + 4, // priority of the task + &ClockTask, // task handle + 1); // CPU core + + assert(ClockTask); // has clock task started? +} // clock_init + +void clock_loop(void *taskparameter) { // ClockTask + + // caveat: don't use now() in this task, it will cause a race condition + // due to concurrent access to i2c bus when reading/writing from/to rtc chip! + +#define nextmin(t) (t + DCF77_FRAME_SIZE + 1) // next minute + +#ifdef HAS_TWO_LED + static bool led1_state = false; +#endif + uint32_t printtime; + time_t t = *((time_t *)taskparameter), last_printtime = 0; // UTC time seconds + +#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 + static TickType_t txDelay = pdMS_TO_TICKS(1000 - IF482_SYNC_FIXUP) - + tx_Ticks(IF482_FRAME_SIZE, HAS_IF482); +#endif + + // output the next second's pulse/telegram after pps arrived + for (;;) { + + // wait for timepulse and store UTC time in seconds got + xTaskNotifyWait(0x00, ULONG_MAX, &printtime, portMAX_DELAY); + t = time_t(printtime); + + // no confident or no recent time -> suppress clock output + if ((timeStatus() == timeNotSet) || !(timeIsValid(t)) || + (t == last_printtime)) + continue; + +#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 + 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 + +#elif defined HAS_DCF77 + + if (second(t) == DCF77_FRAME_SIZE - 1) // is it time to load new frame? + DCFpulse = DCF77_Frame(nextmin(t)); // generate frame for next minute + + 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 + + // else we have no recent frame, thus suppressing clock output + +#endif + +// 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; + + } // for +} // clock_loop() + #endif // HAS_IF482 || defined HAS_DCF77