refactored clock controller code
This commit is contained in:
		
							parent
							
								
									91889a2af8
								
							
						
					
					
						commit
						c8978a0745
					
				
							
								
								
									
										15
									
								
								include/clockcontroller.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								include/clockcontroller.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| #ifndef _CLOCKCONTROLLER_H | ||||
| #define _CLOCKCONTROLLER_H | ||||
| 
 | ||||
| #include "globals.h" | ||||
| 
 | ||||
| #ifdef HAS_IF482 | ||||
| #include "if482.h" | ||||
| #elif defined HAS_DCF77 | ||||
| #include "dcf77.h" | ||||
| #endif | ||||
| 
 | ||||
| void clock_init(void); | ||||
| void clock_loop(void *pvParameters); | ||||
| 
 | ||||
| #endif // _CLOCKCONTROLLER_H
 | ||||
| @ -2,16 +2,12 @@ | ||||
| #define _DCF77_H | ||||
| 
 | ||||
| #include "globals.h" | ||||
| #include "rtctime.h" | ||||
| 
 | ||||
| enum dcf_pulses { dcf_off, dcf_zero, dcf_one }; | ||||
| enum dcf_pinstate { dcf_low, dcf_high }; | ||||
| 
 | ||||
| int dcf77_init(void); | ||||
| void dcf77_loop(void *pvParameters); | ||||
| void sendDCF77(void); | ||||
| void DCF_Out(uint8_t startsec); | ||||
| void generateTimeframe(time_t t); | ||||
| void DCF_Pulse(time_t startTime); | ||||
| void DCF77_Frame(time_t t); | ||||
| void set_DCF77_pin(dcf_pinstate state); | ||||
| uint8_t dec2bcd(uint8_t dec, uint8_t startpos, uint8_t endpos, uint8_t pArray[]); | ||||
| 
 | ||||
|  | ||||
| @ -40,11 +40,17 @@ | ||||
| #define SCREEN_MODE (0x80) | ||||
| 
 | ||||
| // I2C bus access control
 | ||||
| #define I2C_MUTEX_LOCK()    xSemaphoreTake(I2Caccess, (3 * DISPLAYREFRESH_MS / portTICK_PERIOD_MS)) == pdTRUE | ||||
| #define I2C_MUTEX_LOCK()                                                       \ | ||||
|   xSemaphoreTake(I2Caccess, (3 * DISPLAYREFRESH_MS / portTICK_PERIOD_MS)) ==   \ | ||||
|       pdTRUE | ||||
| #define I2C_MUTEX_UNLOCK() xSemaphoreGive(I2Caccess) | ||||
| 
 | ||||
| // time pulse frequency 1Hz
 | ||||
| #define PPS (1000) | ||||
| // Clock controller settings
 | ||||
| #define PPS (1000) // on board time pulse frequency in ms
 | ||||
| #define IF482_FRAME_SIZE (17) | ||||
| #define IF482_PULSE_LENGTH (1000) | ||||
| #define DCF77_FRAME_SIZE (60) | ||||
| #define DCF77_PULSE_LENGTH (100) | ||||
| 
 | ||||
| // Struct holding devices's runtime configuration
 | ||||
| typedef struct { | ||||
| @ -66,7 +72,8 @@ typedef struct { | ||||
|   uint8_t runmode;       // 0=normal, 1=update
 | ||||
|   uint8_t payloadmask;   // bitswitches for payload data
 | ||||
|   char version[10];      // Firmware version
 | ||||
|   uint8_t bsecstate[BSEC_MAX_STATE_BLOB_SIZE + 1]; // BSEC state for BME680 sensor
 | ||||
|   uint8_t | ||||
|       bsecstate[BSEC_MAX_STATE_BLOB_SIZE + 1]; // BSEC state for BME680 sensor
 | ||||
| } configData_t; | ||||
| 
 | ||||
| // Struct holding payload for data send queue
 | ||||
| @ -151,12 +158,4 @@ extern Timezone myTZ; | ||||
| #include "bme680mems.h" | ||||
| #endif | ||||
| 
 | ||||
| #ifdef HAS_IF482 | ||||
| #include "if482.h" | ||||
| #endif | ||||
| 
 | ||||
| #ifdef HAS_DCF77 | ||||
| #include "dcf77.h" | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
| @ -2,11 +2,12 @@ | ||||
| #define _IF482_H | ||||
| 
 | ||||
| #include "globals.h" | ||||
| #include "rtctime.h" | ||||
| 
 | ||||
| int if482_init(void); | ||||
| void if482_loop(void *pvParameters); | ||||
| TickType_t tx_time(unsigned long baud, uint32_t config, int8_t rxPin, | ||||
| extern HardwareSerial IF482;  | ||||
| 
 | ||||
| void IF482_Pulse(time_t t); | ||||
| String IF482_Frame(time_t tt); | ||||
| TickType_t tx_Ticks(unsigned long baud, uint32_t config, int8_t rxPin, | ||||
|                    int8_t txPins); | ||||
| 
 | ||||
| #endif | ||||
| @ -17,5 +17,5 @@ | ||||
| #include "led.h" | ||||
| #include "spislave.h" | ||||
| #include "lorawan.h" | ||||
| 
 | ||||
| #include "clockcontroller.h" | ||||
| #endif | ||||
| @ -24,6 +24,7 @@ float get_rtctemp(void); | ||||
| void IRAM_ATTR CLOCKIRQ(); | ||||
| int timepulse_init(void); | ||||
| void timepulse_start(); | ||||
| void sync_clock(void); | ||||
| int sync_TimePulse(void); | ||||
| int sync_SysTime(time_t); | ||||
| 
 | ||||
| #endif // _RTCTIME_H
 | ||||
							
								
								
									
										72
									
								
								src/clockcontroller.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/clockcontroller.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | ||||
| #include "clockcontroller.h" | ||||
| 
 | ||||
| #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 | ||||
| 
 | ||||
| #if (PPS < IF482_PULSE_LENGTH) || (PPS < DCF77_PULSE_LENGTH) | ||||
| #error On board timepulse too fast for clockcontroller | ||||
| #endif | ||||
| 
 | ||||
| // Local logging tag
 | ||||
| static const char TAG[] = "main"; | ||||
| 
 | ||||
| void clock_init(void) { // ClockTask
 | ||||
| 
 | ||||
|   timepulse_init(); // setup timepulse
 | ||||
| 
 | ||||
| // setup output interface
 | ||||
| #ifdef HAS_IF482 | ||||
|   // initialize and configure IF482 Generator
 | ||||
|   IF482.begin(HAS_IF482); | ||||
| #elif defined HAS_DCF77 | ||||
|   // initialize and configure DCF77 output
 | ||||
|   pinMode(HAS_DCF77, OUTPUT); | ||||
|   set_DCF77_pin(dcf_low); | ||||
| #endif | ||||
| 
 | ||||
|   xTaskCreatePinnedToCore(clock_loop,  // task function
 | ||||
|                           "clockloop", // name of task
 | ||||
|                           2048,        // stack size of task
 | ||||
|                           (void *)1,   // task parameter
 | ||||
|                           4,           // priority of the task
 | ||||
|                           &ClockTask,  // task handle
 | ||||
|                           0);          // CPU core
 | ||||
| 
 | ||||
|   assert(ClockTask); // has clock task started?
 | ||||
|   timepulse_start(); // start pulse
 | ||||
| } // clock_init
 | ||||
| 
 | ||||
| void clock_loop(void *pvParameters) { // ClockTask
 | ||||
| 
 | ||||
|   configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check
 | ||||
| 
 | ||||
|   TickType_t wakeTime, txDelay; | ||||
|   uint32_t pulseCycle; | ||||
|   void (*pTimeTx)(time_t); // pointer to time telegram output function
 | ||||
| 
 | ||||
| #ifdef HAS_IF482 | ||||
|   txDelay = pdMS_TO_TICKS(1000) - tx_Ticks(HAS_IF482); | ||||
|   pulseCycle = PPS / IF482_PULSE_LENGTH; | ||||
|   pTimeTx = IF482_Pulse; | ||||
| #elif defined HAS_DCF77 | ||||
|   txDelay = pdMS_TO_TICKS(DCF77_PULSE_LENGTH); | ||||
|   pulseCycle = PPS / DCF77_PULSE_LENGTH; | ||||
|   pTimeTx = DCF_Pulse; | ||||
| #endif | ||||
| 
 | ||||
|   // output time telegram triggered by timepulse
 | ||||
|   for (;;) { | ||||
|     if (timeStatus() == timeSet) // do we have valid time?
 | ||||
|       xTaskNotifyWait(0x00, ULONG_MAX, &wakeTime, | ||||
|                       portMAX_DELAY); // wait for timepulse
 | ||||
|     for (uint8_t i = 1; i <= pulseCycle; i++) { | ||||
|       pTimeTx(now()); | ||||
|       vTaskDelayUntil(&wakeTime, txDelay); | ||||
|     } | ||||
|   } // for
 | ||||
| } // clock_loop()
 | ||||
| 
 | ||||
| #endif // HAS_DCF77 || HAS_IF482
 | ||||
| @ -41,6 +41,7 @@ void doHousekeeping() { | ||||
|   if ((millis() >= nextRTCTimeSync) && (timeStatus() == timeSet)) { | ||||
|     nextRTCTimeSync = millis() + TIME_WRITE_INTERVAL_RTC * | ||||
|                                      60000; // set up next time sync period
 | ||||
|     sync_TimePulse();                       // wait for next start of second
 | ||||
|     if (!set_rtctime(now()))                // epoch time
 | ||||
|       ESP_LOGE(TAG, "RTC set time failure"); | ||||
|     else | ||||
|  | ||||
							
								
								
									
										149
									
								
								src/dcf77.cpp
									
									
									
									
									
								
							
							
						
						
									
										149
									
								
								src/dcf77.cpp
									
									
									
									
									
								
							| @ -8,76 +8,41 @@ https://www-user.tu-chemnitz.de/~heha/viewzip.cgi/hs/Funkuhr.zip/ | ||||
| 
 | ||||
| #ifdef HAS_DCF77 | ||||
| 
 | ||||
| #ifdef IF_482 | ||||
| #error You must define at most one of IF482 or DCF77! | ||||
| #endif | ||||
| 
 | ||||
| #include "dcf77.h" | ||||
| 
 | ||||
| // Local logging tag
 | ||||
| static const char TAG[] = "main"; | ||||
| 
 | ||||
| bool volatile BitsPending = false; | ||||
| 
 | ||||
| #define DCF77_FRAME_SIZE (60) | ||||
| #define DCF77_PULSE_DURATION (100) | ||||
| 
 | ||||
| // array of dcf pulses for three minutes
 | ||||
| uint8_t DCFtimeframe[DCF77_FRAME_SIZE]; | ||||
| uint8_t DCFpulse[DCF77_FRAME_SIZE]; | ||||
| 
 | ||||
| // initialize and configure DCF77 output
 | ||||
| int dcf77_init(void) { | ||||
| // called by timepulse interrupt to ticker out DCF signal
 | ||||
| void DCF_Pulse(time_t startTime) { | ||||
| 
 | ||||
|   pinMode(HAS_DCF77, OUTPUT); | ||||
|   set_DCF77_pin(dcf_low); | ||||
|   timepulse_init(); // setup timepulse
 | ||||
| 
 | ||||
|   xTaskCreatePinnedToCore(dcf77_loop,  // task function
 | ||||
|                           "dcf77loop", // name of task
 | ||||
|                           2048,        // stack size of task
 | ||||
|                           (void *)1,   // parameter of the task
 | ||||
|                           3,           // priority of the task
 | ||||
|                           &ClockTask,  // task handle
 | ||||
|                           0);          // CPU core
 | ||||
| 
 | ||||
|   assert(ClockTask);      // has clock task started?
 | ||||
|   DCF_Out(second(now())); // sync DCF time on next second
 | ||||
|   timepulse_start();      // start pulse
 | ||||
| 
 | ||||
|   return 1; // success
 | ||||
| } // ifdcf77_init
 | ||||
| 
 | ||||
| // called every 100msec by hardware timer to pulse out DCF signal
 | ||||
| void DCF_Out(uint8_t startOffset_sec) { | ||||
| 
 | ||||
|   static uint8_t bit = startOffset_sec; | ||||
|   static uint8_t current_second = second(startTime); | ||||
|   static uint8_t pulse = 0; | ||||
| #ifdef TIME_SYNC_INTERVAL_DCF | ||||
|   static uint32_t nextDCFsync = millis() + TIME_SYNC_INTERVAL_DCF * 60000; | ||||
| #endif | ||||
|   static bool SecondsPending = false; | ||||
| 
 | ||||
|   if (!BitsPending) { | ||||
|     // do we have confident time/date?
 | ||||
|     if ((timeStatus() == timeSet) || (timeStatus() == timeNeedsSync)) { | ||||
|       // prepare frame to send for next minute
 | ||||
|       generateTimeframe(now() + DCF77_FRAME_SIZE + 1); | ||||
|       // kick off output of telegram
 | ||||
|       BitsPending = true; | ||||
|     } else | ||||
|       return; | ||||
|   if (!SecondsPending) { | ||||
|     // prepare dcf timeframe to send for next minute
 | ||||
|     DCF77_Frame(now() + DCF77_FRAME_SIZE + 1); | ||||
|     ESP_LOGD(TAG, "DCF77 minute %d", minute(now() + DCF77_FRAME_SIZE + 1)); | ||||
|     // begin output of dcf timeframe
 | ||||
|     SecondsPending = true; | ||||
|   } | ||||
| 
 | ||||
|   // ticker out current DCF frame
 | ||||
|   if (BitsPending) { | ||||
|   if (SecondsPending) { | ||||
|     switch (pulse++) { | ||||
| 
 | ||||
|     case 0: // start of second -> start of timeframe for logic signal
 | ||||
|       if (DCFtimeframe[bit] != dcf_off) | ||||
|       if (DCFpulse[current_second] != dcf_off) | ||||
|         set_DCF77_pin(dcf_low); | ||||
|       ESP_LOGD(TAG, "DCF77 bit %d", current_second); | ||||
|       break; | ||||
| 
 | ||||
|     case 1: // 100ms after start of second -> end of timeframe for logic 0
 | ||||
|       if (DCFtimeframe[bit] == dcf_zero) | ||||
|       if (DCFpulse[current_second] == dcf_zero) | ||||
|         set_DCF77_pin(dcf_high); | ||||
|       break; | ||||
| 
 | ||||
| @ -87,53 +52,17 @@ void DCF_Out(uint8_t startOffset_sec) { | ||||
| 
 | ||||
|     case 9: // 900ms after start -> last pulse before next second starts
 | ||||
|       pulse = 0; | ||||
|       if (bit++ == (DCF77_FRAME_SIZE - 1)) // end of DCF77 frame (59th second)
 | ||||
|       if (current_second++ >= | ||||
|           (DCF77_FRAME_SIZE - 1)) // end of DCF77 frame (59th second)
 | ||||
|       { | ||||
|         bit = 0; | ||||
|         BitsPending = false; | ||||
| // recalibrate clock after a fixed timespan, do this in 59th second
 | ||||
| #ifdef TIME_SYNC_INTERVAL_DCF | ||||
|         if ((millis() >= nextDCFsync)) { | ||||
|           sync_clock(); // waiting for second 59
 | ||||
|           nextDCFsync = millis() + TIME_SYNC_INTERVAL_DCF * | ||||
|                                        60000; // set up next time sync period
 | ||||
|         } | ||||
| #endif | ||||
|         current_second = 0; | ||||
|         SecondsPending = false; | ||||
|       }; | ||||
|       break; | ||||
| 
 | ||||
|     }; // switch
 | ||||
|   };   // if
 | ||||
| } // DCF_Out()
 | ||||
| 
 | ||||
| void dcf77_loop(void *pvParameters) { | ||||
| 
 | ||||
|   configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check
 | ||||
| 
 | ||||
|   TickType_t wakeTime; | ||||
| 
 | ||||
|   // task remains in blocked state until it is notified by isr
 | ||||
|   for (;;) { | ||||
|     xTaskNotifyWait( | ||||
|         0x00,           // don't clear any bits on entry
 | ||||
|         ULONG_MAX,      // clear all bits on exit
 | ||||
|         &wakeTime,      // receives moment of call from isr
 | ||||
|         portMAX_DELAY); // wait forever (missing error handling here...)
 | ||||
| 
 | ||||
|     // select clock scale
 | ||||
| #if (PPS == DCF77_PULSE_DURATION) // we don't need clock rescaling
 | ||||
|     DCF_Out(0); | ||||
| #elif (PPS > DCF77_PULSE_DURATION) // we need upclocking
 | ||||
|     for (uint8_t i = 1; i <= PPS / DCF77_PULSE_DURATION; i++) { | ||||
|       DCF_Out(0); | ||||
|       vTaskDelayUntil(&wakeTime, pdMS_TO_TICKS(DCF77_PULSE_DURATION)); | ||||
|     } | ||||
| #else // we need downclocking, not yet implemented
 | ||||
| #error Timepulse too fast for DCF77 emulator | ||||
| #endif | ||||
| 
 | ||||
|   } // for
 | ||||
| } // dcf77_loop()
 | ||||
| } // DCF_Pulse()
 | ||||
| 
 | ||||
| // helper function to convert decimal to bcd digit
 | ||||
| uint8_t dec2bcd(uint8_t dec, uint8_t startpos, uint8_t endpos, | ||||
| @ -151,40 +80,38 @@ uint8_t dec2bcd(uint8_t dec, uint8_t startpos, uint8_t endpos, | ||||
|   return parity; | ||||
| } | ||||
| 
 | ||||
| void generateTimeframe(time_t tt) { | ||||
| void DCF77_Frame(time_t tt) { | ||||
| 
 | ||||
|   uint8_t ParityCount; | ||||
|   uint8_t Parity; | ||||
|   time_t t = myTZ.toLocal(tt); // convert to local time
 | ||||
| 
 | ||||
|   // ENCODE HEAD
 | ||||
|   // bits 0..19 initialized with zeros
 | ||||
|   for (int n = 0; n <= 19; n++) | ||||
|     DCFtimeframe[n] = dcf_zero; | ||||
|     DCFpulse[n] = dcf_zero; | ||||
|   // bits 17..18: adjust for DayLightSaving
 | ||||
|   DCFtimeframe[18 - (myTZ.locIsDST(t) ? 1 : 0)] = dcf_one; | ||||
|   DCFpulse[18 - (myTZ.locIsDST(t) ? 1 : 0)] = dcf_one; | ||||
|   // bit 20: must be 1 to indicate time active
 | ||||
|   DCFtimeframe[20] = dcf_one; | ||||
|   DCFpulse[20] = dcf_one; | ||||
| 
 | ||||
|   // ENCODE MINUTE (bits 21..28)
 | ||||
|   ParityCount = dec2bcd(minute(t), 21, 27, DCFtimeframe); | ||||
|   DCFtimeframe[28] = (ParityCount & 1) ? dcf_one : dcf_zero; | ||||
|   Parity = dec2bcd(minute(t), 21, 27, DCFpulse); | ||||
|   DCFpulse[28] = (Parity & 1) ? dcf_one : dcf_zero; | ||||
| 
 | ||||
|   // ENCODE HOUR (bits 29..35)
 | ||||
|   ParityCount = dec2bcd(hour(t), 29, 34, DCFtimeframe); | ||||
|   DCFtimeframe[35] = (ParityCount & 1) ? dcf_one : dcf_zero; | ||||
|   Parity = dec2bcd(hour(t), 29, 34, DCFpulse); | ||||
|   DCFpulse[35] = (Parity & 1) ? dcf_one : dcf_zero; | ||||
| 
 | ||||
|   // ENCODE DATE (bits 36..58)
 | ||||
|   ParityCount = dec2bcd(day(t), 36, 41, DCFtimeframe); | ||||
|   ParityCount += | ||||
|       dec2bcd((weekday(t) - 1) ? (weekday(t) - 1) : 7, 42, 44, DCFtimeframe); | ||||
|   ParityCount += dec2bcd(month(t), 45, 49, DCFtimeframe); | ||||
|   ParityCount += | ||||
|       dec2bcd(year(t) - 2000, 50, 57, | ||||
|               DCFtimeframe); // yes, we have a millenium 3000 bug here ;-)
 | ||||
|   DCFtimeframe[58] = (ParityCount & 1) ? dcf_one : dcf_zero; | ||||
|   Parity = dec2bcd(day(t), 36, 41, DCFpulse); | ||||
|   Parity += dec2bcd((weekday(t) - 1) ? (weekday(t) - 1) : 7, 42, 44, DCFpulse); | ||||
|   Parity += dec2bcd(month(t), 45, 49, DCFpulse); | ||||
|   Parity += dec2bcd(year(t) - 2000, 50, 57, | ||||
|                     DCFpulse); // yes, we have a millenium 3000 bug here ;-)
 | ||||
|   DCFpulse[58] = (Parity & 1) ? dcf_one : dcf_zero; | ||||
| 
 | ||||
|   // ENCODE TAIL (bit 59)
 | ||||
|   DCFtimeframe[59] = dcf_off; | ||||
|   DCFpulse[59] = dcf_off; | ||||
|   // !! missing code here for leap second !!
 | ||||
| 
 | ||||
|   /*
 | ||||
| @ -192,7 +119,7 @@ void generateTimeframe(time_t tt) { | ||||
|     char out[DCF77_FRAME_SIZE + 1]; | ||||
|     uint8_t i; | ||||
|     for (i = 0; i < DCF77_FRAME_SIZE; i++) { | ||||
|       out[i] = DCFtimeframe[i] + '0'; // convert int digit to printable ascii
 | ||||
|       out[i] = DCFpulse[i] + '0'; // convert int digit to printable ascii
 | ||||
|     } | ||||
|     out[DCF77_FRAME_SIZE] = '\0'; // string termination char
 | ||||
|     ESP_LOGD(TAG, "DCF Timeframe = %s", out); | ||||
| @ -219,4 +146,6 @@ void set_DCF77_pin(dcf_pinstate state) { | ||||
|   } // switch
 | ||||
| } // DCF77_pulse
 | ||||
| 
 | ||||
| // helper function calculates next minute
 | ||||
| 
 | ||||
| #endif // HAS_DCF77
 | ||||
| @ -139,7 +139,7 @@ void refreshtheDisplay() { | ||||
|       u8x8.setPowerSave(!cfg.screenon); | ||||
|     } | ||||
| 
 | ||||
|     // if display is switched off we don't refresh it and save time
 | ||||
|     // if display is switched off we don't refresh it to relax cpu
 | ||||
|     if (!DisplayState) | ||||
|       return; | ||||
| 
 | ||||
| @ -220,7 +220,7 @@ void refreshtheDisplay() { | ||||
| #if (!defined HAS_DCF77) && (!defined HAS_IF482) | ||||
|     // update LoRa status display (line 6)
 | ||||
|     u8x8.printf("%-16s", display_line6); | ||||
| #else // we want a time display instead LoRa status
 | ||||
| #else // we want a systime display instead LoRa status
 | ||||
|     time_t t = myTZ.toLocal(now()); | ||||
|     char timeState = | ||||
|         (timeStatus() == timeSet) ? timesyncSymbol : timeNosyncSymbol; | ||||
|  | ||||
| @ -88,8 +88,8 @@ time_t tmConvert_t(uint16_t YYYY, uint8_t MM, uint8_t DD, uint8_t hh, | ||||
| 
 | ||||
| // function to fetch current time from gps
 | ||||
| time_t get_gpstime(void) { | ||||
|   // !! never call now() in this function, this would break this function
 | ||||
|   // to be used as SyncProvider due to recursive call to now()
 | ||||
|   // !! never call now() or delay in this function, this would break this
 | ||||
|   // function to be used as SyncProvider for Time.h
 | ||||
| 
 | ||||
|   if ((gps.time.age() < 1500) && (gps.time.isValid())) { | ||||
|     // get current gps time
 | ||||
| @ -98,9 +98,7 @@ time_t get_gpstime(void) { | ||||
|                     gps.time.hour(), gps.time.minute(), gps.time.second()); | ||||
|     ESP_LOGD(TAG, "GPS time: %4d/%02d/%02d %02d:%02d:%02d", year(t), month(t), | ||||
|              day(t), hour(t), minute(t), second(t)); | ||||
|     // sync on top of next second by timepulse
 | ||||
|     sync_clock(); | ||||
|     return t + 1; | ||||
|     return t; | ||||
|   } else { | ||||
|     ESP_LOGW(TAG, "GPS has no confident time"); | ||||
|     return 0; // sync failure, 0 effects calling SyncProvider() to not set time
 | ||||
|  | ||||
| @ -79,46 +79,21 @@ not evaluated by model BU-190 | ||||
| 
 | ||||
| #ifdef HAS_IF482 | ||||
| 
 | ||||
| #ifdef HAS_DCF77 | ||||
| #error You must define at most one of IF482 or DCF77! | ||||
| #endif | ||||
| 
 | ||||
| #include "if482.h" | ||||
| 
 | ||||
| // Local logging tag
 | ||||
| static const char TAG[] = "main"; | ||||
| 
 | ||||
| #define IF482_FRAME_SIZE (17) | ||||
| #define IF482_PULSE_DURATION (1000) | ||||
| 
 | ||||
| HardwareSerial IF482(2); // use UART #2 (note: #1 may be in use for serial GPS)
 | ||||
| 
 | ||||
| // initialize and configure IF482 Generator
 | ||||
| int if482_init(void) { | ||||
| // called by timepulse interrupt to ticker out DCF signal
 | ||||
| void IF482_Pulse(time_t startTime) { | ||||
|   IF482.print(IF482_Frame(startTime + 1)); // if482 telegram for next second
 | ||||
| } | ||||
| 
 | ||||
|   // open serial interface
 | ||||
|   IF482.begin(HAS_IF482); | ||||
|   // setup timepulse
 | ||||
|   timepulse_init(); | ||||
| String IF482_Frame(time_t startTime) { | ||||
| 
 | ||||
|   // start if482 serial output feed task
 | ||||
|   xTaskCreatePinnedToCore(if482_loop,  // task function
 | ||||
|                           "if482loop", // name of task
 | ||||
|                           2048,        // stack size of task
 | ||||
|                           (void *)1,   // parameter of the task
 | ||||
|                           4,           // priority of the task
 | ||||
|                           &ClockTask,  // task handle
 | ||||
|                           0);          // CPU core
 | ||||
| 
 | ||||
|   assert(ClockTask); // has clock task started?
 | ||||
|   // timepulse_start(); // start pulse
 | ||||
| 
 | ||||
|   return 1; // success
 | ||||
| } // if482_init
 | ||||
| 
 | ||||
| String IF482_Out(time_t tt) { | ||||
| 
 | ||||
|   time_t t = myTZ.toLocal(tt); | ||||
|   time_t t = myTZ.toLocal(startTime); | ||||
|   char mon, buf[14], out[IF482_FRAME_SIZE]; | ||||
| 
 | ||||
|   switch (timeStatus()) { // indicates if time has been set and recently synced
 | ||||
| @ -134,7 +109,7 @@ String IF482_Out(time_t tt) { | ||||
|   } // switch
 | ||||
| 
 | ||||
|   // do we have confident time/date?
 | ||||
|   if ((timeStatus() == timeSet) || (timeStatus() == timeNeedsSync)) | ||||
|   if (timeStatus() == timeSet) | ||||
|     snprintf(buf, sizeof(buf), "%02u%02u%02u%1u%02u%02u%02u", year(t) - 2000, | ||||
|              month(t), day(t), weekday(t), hour(t), minute(t), second(t)); | ||||
|   else | ||||
| @ -146,61 +121,8 @@ String IF482_Out(time_t tt) { | ||||
|   return out; | ||||
| } | ||||
| 
 | ||||
| void if482_loop(void *pvParameters) { | ||||
| 
 | ||||
|   configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check
 | ||||
| 
 | ||||
|   TickType_t wakeTime; | ||||
|   const TickType_t tTx = tx_time(HAS_IF482); // duration of telegram transmit
 | ||||
| 
 | ||||
|   // phase 1: sync task on top of second
 | ||||
| 
 | ||||
|   const TickType_t t0 = xTaskGetTickCount(); // moment of start top of second
 | ||||
|   sync_clock();                              // delay until top of second
 | ||||
|   timepulse_start();                         // start timepulse
 | ||||
| 
 | ||||
|   xTaskNotifyWait( | ||||
|       0x00,           // don't clear any bits on entry
 | ||||
|       ULONG_MAX,      // clear all bits on exit
 | ||||
|       &wakeTime,      // receives moment of call from isr
 | ||||
|       portMAX_DELAY); // wait forever (missing error handling here...)
 | ||||
| 
 | ||||
|   const TickType_t tOffset = wakeTime - t0; | ||||
|   const TickType_t tShot = | ||||
|       (tOffset < tTx) ? (1000 - tOffset - tTx) : (tOffset - tTx); | ||||
| 
 | ||||
|   ESP_LOGI(TAG, "IF482 signal synced with clock, tShot=%dms", tShot); | ||||
| 
 | ||||
|   // phase 2: sync task on time pulse interrupt
 | ||||
|   for (;;) { | ||||
|     xTaskNotifyWait( | ||||
|         0x00,           // don't clear any bits on entry
 | ||||
|         ULONG_MAX,      // clear all bits on exit
 | ||||
|         &wakeTime,      // receives moment of call from isr
 | ||||
|         portMAX_DELAY); // wait forever (missing error handling here...)
 | ||||
| 
 | ||||
| // select clock scale
 | ||||
| #if (PPS == IF482_PULSE_DURATION) // we don't need clock rescaling
 | ||||
|     // wait until it's time to start transmit telegram for next second
 | ||||
|     vTaskDelayUntil(&wakeTime, tShot); // sets waketime to moment of tShot
 | ||||
|     IF482.print(IF482_Out(now() + 1)); | ||||
| 
 | ||||
| #elif (PPS > IF482_PULSE_DURATION) // we need upclocking
 | ||||
|     for (uint8_t i = 1; i <= PPS / IF482_PULSE_DURATION; i++) { | ||||
|       vTaskDelayUntil(&wakeTime, tShot); // sets waketime to moment of shot
 | ||||
|       IF482.print(IF482_Out(now() + 1)); | ||||
|     } | ||||
| 
 | ||||
| #else // we need downclocking, not yet implemented
 | ||||
| #error Timepulse too fast for IF482 generator | ||||
| #endif | ||||
| 
 | ||||
|   } // forever
 | ||||
| 
 | ||||
| } // if482_loop()
 | ||||
| 
 | ||||
| // calculate serial tx time from IF482 serial settings
 | ||||
| TickType_t tx_time(unsigned long baud, uint32_t config, int8_t rxPin, | ||||
| TickType_t tx_Ticks(unsigned long baud, uint32_t config, int8_t rxPin, | ||||
|                     int8_t txPins) { | ||||
| 
 | ||||
|   uint32_t datenbits = ((config & 0x0c) >> 2) + 5; | ||||
|  | ||||
| @ -16,7 +16,7 @@ void irqHandler(void *pvParameters) { | ||||
|         0x00,             // Don't clear any bits on entry
 | ||||
|         ULONG_MAX,        // Clear all bits on exit
 | ||||
|         &InterruptStatus, // Receives the notification value
 | ||||
|         portMAX_DELAY);   // wait forever (missing error handling here...)
 | ||||
|         portMAX_DELAY);   // wait forever
 | ||||
| 
 | ||||
| // button pressed?
 | ||||
| #ifdef HAS_BUTTON | ||||
|  | ||||
| @ -471,7 +471,8 @@ void user_request_network_time_callback(void *pVoidUserUTCTime, | ||||
|   setTime(*pUserUTCTime); | ||||
|   ESP_LOGI(TAG, "LoRaWAN network has set the system time"); | ||||
| #ifdef HAS_RTC | ||||
|   if (!set_rtctime(*pUserUTCTime)) // epoch time
 | ||||
|   sync_TimePulse();                // wait for next start of second
 | ||||
|   if (!set_rtctime(*pUserUTCTime+1)) // epoch time
 | ||||
|     ESP_LOGE(TAG, "RTC set time failure"); | ||||
| #endif | ||||
| } | ||||
							
								
								
									
										11
									
								
								src/main.cpp
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								src/main.cpp
									
									
									
									
									
								
							| @ -338,6 +338,7 @@ void setup() { | ||||
| #ifdef HAS_RTC | ||||
|   strcat_P(features, " RTC"); | ||||
|   assert(rtc_init()); | ||||
|   sync_TimePulse();             // wait for next start of second
 | ||||
|   setSyncProvider(get_rtctime); // sync time now and then
 | ||||
|   if (timeStatus() != timeSet) | ||||
|     ESP_LOGI(TAG, "Unable to sync system time with RTC"); | ||||
| @ -416,6 +417,7 @@ void setup() { | ||||
| #endif // HAS_BUTTON
 | ||||
| 
 | ||||
| #ifdef HAS_GPS | ||||
|   sync_TimePulse();             // wait for next start of second
 | ||||
|   setSyncProvider(get_gpstime); // sync time now and then
 | ||||
|   if (timeStatus() != timeSet) | ||||
|     ESP_LOGI(TAG, "Unable to sync system time with GPS"); | ||||
| @ -429,12 +431,9 @@ void setup() { | ||||
|   setSyncInterval(TIME_SYNC_INTERVAL_GPS * 60); | ||||
| #endif | ||||
| 
 | ||||
| #ifdef HAS_IF482 | ||||
|   ESP_LOGI(TAG, "Starting IF482 Generator..."); | ||||
|   assert(if482_init()); | ||||
| #elif defined HAS_DCF77 | ||||
|   ESP_LOGI(TAG, "Starting DCF77 Generator..."); | ||||
|   assert(dcf77_init()); | ||||
| #if defined HAS_IF482 || defined HAS_DCF77 | ||||
|   ESP_LOGI(TAG, "Starting Clock Controller..."); | ||||
|   clock_init(); | ||||
| #endif | ||||
| 
 | ||||
| } // setup()
 | ||||
|  | ||||
| @ -82,11 +82,10 @@ | ||||
| #define RESPONSE_TIMEOUT_MS             60000   // firmware binary server connection timeout [milliseconds] | ||||
| 
 | ||||
| // settings for syncing time of node and external time sources | ||||
| #define TIME_SYNC_INTERVAL_GPS          5       // sync time each .. minutes from source GPS [default = 5], comment out means off           | ||||
| #define TIME_SYNC_INTERVAL_GPS          5       // sync time each .. minutes from GPS [default = 5], comment out means off           | ||||
| #define TIME_SYNC_INTERVAL_RTC          60      // sync time each .. minutes from RTC [default = 60], comment out means off           | ||||
| #define TIME_WRITE_INTERVAL_RTC         60      // write time each .. minutes from GPS/LORA to RTC [default = 60], comment out means off           | ||||
| //#define TIME_SYNC_INTERVAL_LORA       60      // sync time each .. minutes from LORA network [default = 60], comment out means off | ||||
| #define TIME_SYNC_INTERVAL_DCF          60      // sync DCF signal time each .. minutes from internal time [default = 60], comment out means off     | ||||
| 
 | ||||
| // time zone, see https://github.com/JChristensen/Timezone/blob/master/examples/WorldClock/WorldClock.ino | ||||
| #define DAYLIGHT_TIME                   {"CEST", Last, Sun, Mar, 2, 120}     // Central European Summer Time | ||||
|  | ||||
| @ -16,7 +16,7 @@ int timepulse_init() { | ||||
|   // setup external interupt for active low RTC INT pin
 | ||||
|   pinMode(GPS_INT, INPUT_PULLDOWN); | ||||
|   // setup external rtc 1Hz clock as pulse per second clock
 | ||||
|   ESP_LOGI(TAG, "Time base: GPS timepulse"); | ||||
|   ESP_LOGI(TAG, "Time base: external (GPS)"); | ||||
|   return 1; // success
 | ||||
| 
 | ||||
| // use pulse from on board RTC chip as time base with fixed frequency
 | ||||
| @ -30,7 +30,7 @@ int timepulse_init() { | ||||
|     Rtc.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz); | ||||
|     Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeClock); | ||||
|     I2C_MUTEX_UNLOCK(); | ||||
|     ESP_LOGI(TAG, "Time base: external RTC timepulse"); | ||||
|     ESP_LOGI(TAG, "Time base: external (RTC)"); | ||||
|     return 1; // success
 | ||||
|   } else { | ||||
|     ESP_LOGE(TAG, "I2c bus busy - RTC initialization error"); | ||||
| @ -41,9 +41,9 @@ int timepulse_init() { | ||||
| #else | ||||
|   // use ESP32 hardware timer as time base with adjustable frequency
 | ||||
|   clockCycle = timerBegin(1, 8000, true); // set 80 MHz prescaler to 1/10000 sec
 | ||||
|   timerAttachInterrupt(clockCycle, &CLOCKIRQ, true); | ||||
|   //timerAttachInterrupt(clockCycle, &CLOCKIRQ, true);
 | ||||
|   timerAlarmWrite(clockCycle, 10000, true); // 1000ms
 | ||||
|   ESP_LOGI(TAG, "Time base: ESP32 hardware timer"); | ||||
|   ESP_LOGI(TAG, "Time base: internal (ESP32 hardware timer)"); | ||||
|   return 1; // success
 | ||||
| 
 | ||||
| #endif | ||||
| @ -55,18 +55,30 @@ void timepulse_start(void) { | ||||
| #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 | ||||
| } | ||||
| 
 | ||||
| // helper function to sync time_t of top of a second
 | ||||
| void sync_clock(void) { | ||||
| // helper function to sync systime on start of next second
 | ||||
| int sync_SysTime(time_t t) { | ||||
|   if (sync_TimePulse()) { | ||||
|     setTime(t + 1); | ||||
|     ESP_LOGD(TAG, "Systime synced on timepulse"); | ||||
|     return 1; // success
 | ||||
|   } else | ||||
|     return 0; // failure
 | ||||
| } | ||||
| 
 | ||||
| // helper function to sync moment on timepulse
 | ||||
| int sync_TimePulse(void) { | ||||
|   // sync on top of next second by timepulse
 | ||||
|   if (xSemaphoreTake(TimePulse, pdMS_TO_TICKS(PPS)) == pdTRUE) | ||||
|     ESP_LOGI(TAG, "clock synced by timepulse"); | ||||
|   if (xSemaphoreTake(TimePulse, pdMS_TO_TICKS(PPS)) == pdTRUE) { | ||||
|     return 1; | ||||
|   } // success
 | ||||
|   else | ||||
|     ESP_LOGW(TAG, "Missing timepulse, clock not synced"); | ||||
|   return; | ||||
|     ESP_LOGW(TAG, "Missing timepulse, time not synced"); | ||||
|   return 0; // failure
 | ||||
| } | ||||
| 
 | ||||
| // interrupt service routine triggered by either rtc pps or esp32 hardware
 | ||||
| @ -136,7 +148,6 @@ error: | ||||
| 
 | ||||
| int set_rtctime(time_t t) { // t is seconds epoch time starting 1.1.1970
 | ||||
|   if (I2C_MUTEX_LOCK()) { | ||||
|     sync_clock(); // wait for top of second
 | ||||
|     Rtc.SetDateTime(RtcDateTime(t)); | ||||
|     I2C_MUTEX_UNLOCK(); // release i2c bus access
 | ||||
|     ESP_LOGI(TAG, "RTC calibrated"); | ||||
| @ -151,7 +162,8 @@ int set_rtctime(uint32_t t) { // t is epoch seconds starting 1.1.1970 | ||||
| } | ||||
| 
 | ||||
| time_t get_rtctime(void) { | ||||
|   // never call now() in this function, this would cause a recursion!
 | ||||
|   // !! never call now() or delay in this function, this would break this
 | ||||
|   // function to be used as SyncProvider for Time.h
 | ||||
|   time_t t = 0; | ||||
|   // block i2c bus access
 | ||||
|   if (I2C_MUTEX_LOCK()) { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user