235 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			235 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include "timekeeper.h"
 | 
						|
 | 
						|
// Local logging tag
 | 
						|
static const char TAG[] = __FILE__;
 | 
						|
 | 
						|
// symbol to display current time source
 | 
						|
const char timeSetSymbols[] = {'G', 'R', 'L', '?'};
 | 
						|
 | 
						|
getExternalTime TimeSourcePtr; // pointer to time source function
 | 
						|
 | 
						|
time_t timeProvider(void) {
 | 
						|
 | 
						|
  ESP_LOGD(TAG, "time synched");
 | 
						|
 | 
						|
  time_t t = 0;
 | 
						|
 | 
						|
#ifdef HAS_GPS
 | 
						|
  t = get_gpstime(); // fetch recent time from last NEMA record
 | 
						|
  if (t) {
 | 
						|
#ifdef HAS_RTC
 | 
						|
    set_rtctime(t); // calibrate RTC
 | 
						|
#endif
 | 
						|
    timeSource = _gps;
 | 
						|
    return t;
 | 
						|
  }
 | 
						|
#endif
 | 
						|
 | 
						|
// no GPS -> fallback to RTC time while trying lora sync
 | 
						|
#ifdef HAS_RTC
 | 
						|
  t = get_rtctime();
 | 
						|
  if (t) {
 | 
						|
    timeSource = _rtc;
 | 
						|
  }
 | 
						|
#endif
 | 
						|
 | 
						|
// kick off asychron lora sync if we have
 | 
						|
#if defined HAS_LORA && defined TIME_SYNC_LORA
 | 
						|
  LMIC_requestNetworkTime(user_request_network_time_callback, &userUTCTime);
 | 
						|
#endif
 | 
						|
 | 
						|
  if (!t)
 | 
						|
    timeSource = _unsynced;
 | 
						|
 | 
						|
  return t;
 | 
						|
 | 
						|
} // timeProvider()
 | 
						|
 | 
						|
// 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
 | 
						|
  clockCycle = timerBegin(1, 8000, true); // set 80 MHz prescaler to 1/10000 sec
 | 
						|
  timerAlarmWrite(clockCycle, 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(clockCycle, &CLOCKIRQ, true);
 | 
						|
  timerAlarmEnable(clockCycle);
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
// interrupt service routine triggered by either pps or esp32 hardware timer
 | 
						|
void IRAM_ATTR CLOCKIRQ(void) {
 | 
						|
 | 
						|
  BaseType_t xHigherPriorityTaskWoken;
 | 
						|
 | 
						|
  time_t t = SyncToPPS(); // calibrates UTC systime, see Time.h
 | 
						|
  xHigherPriorityTaskWoken = pdFALSE;
 | 
						|
 | 
						|
  if (ClockTask != NULL)
 | 
						|
    xTaskNotifyFromISR(ClockTask, uint32_t(t), eSetBits,
 | 
						|
                       &xHigherPriorityTaskWoken);
 | 
						|
 | 
						|
#if defined GPS_INT || defined RTC_INT
 | 
						|
  xSemaphoreGiveFromISR(TimePulse, &xHigherPriorityTaskWoken);
 | 
						|
  TimePulseTick = !TimePulseTick; // flip ticker
 | 
						|
#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 convert gps date/time into time_t
 | 
						|
time_t tmConvert(uint16_t YYYY, uint8_t MM, uint8_t DD, uint8_t hh, uint8_t mm,
 | 
						|
                 uint8_t ss) {
 | 
						|
  tmElements_t tm;
 | 
						|
  tm.Year = CalendarYrToTm(YYYY); // year offset from 1970 in time.h
 | 
						|
  tm.Month = MM;
 | 
						|
  tm.Day = DD;
 | 
						|
  tm.Hour = hh;
 | 
						|
  tm.Minute = mm;
 | 
						|
  tm.Second = ss;
 | 
						|
  return makeTime(tm);
 | 
						|
}
 | 
						|
 | 
						|
// 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 + 2) * framesize * 1000.0 / baud;
 | 
						|
  // +1 ms margin for the startbit +1 ms for pending processing time
 | 
						|
 | 
						|
  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 for setting rtc via SyncProvider!
 | 
						|
 | 
						|
#define nextsec(t) (t + 1)                    // next second
 | 
						|
#define nextmin(t) (t + DCF77_FRAME_SIZE + 1) // next minute
 | 
						|
 | 
						|
  uint32_t printtime;
 | 
						|
  time_t t = *((time_t *)taskparameter); // UTC time seconds
 | 
						|
 | 
						|
  // preload first DCF frame before start
 | 
						|
#ifdef HAS_DCF77
 | 
						|
  uint8_t *DCFpulse; // pointer on array with DCF pulse bits
 | 
						|
  DCFpulse = DCF77_Frame(nextmin(t));
 | 
						|
#endif
 | 
						|
 | 
						|
  // output the next second's pulse after timepulse arrived
 | 
						|
  for (;;) {
 | 
						|
    xTaskNotifyWait(0x00, ULONG_MAX, &printtime,
 | 
						|
                    portMAX_DELAY); // wait for timepulse
 | 
						|
 | 
						|
    // no confident time -> we suppress clock output
 | 
						|
    if (timeStatus() == timeNotSet)
 | 
						|
      continue;
 | 
						|
 | 
						|
    t = time_t(printtime); // UTC time seconds
 | 
						|
 | 
						|
#if defined HAS_IF482
 | 
						|
 | 
						|
    IF482_Pulse(t);
 | 
						|
 | 
						|
#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
 | 
						|
      continue; // no recent frame -> we suppress clock output
 | 
						|
 | 
						|
#endif
 | 
						|
 | 
						|
  } // for
 | 
						|
} // clock_loop()
 | 
						|
 | 
						|
#endif // HAS_IF482 || defined HAS_DCF77
 |