v1.7.31 (clockcontroller deeply reworked)
This commit is contained in:
		
							parent
							
								
									c8978a0745
								
							
						
					
					
						commit
						05a3f3b2a8
					
				| @ -11,5 +11,6 @@ | ||||
| 
 | ||||
| void clock_init(void); | ||||
| void clock_loop(void *pvParameters); | ||||
| time_t telegram_time(void); | ||||
| 
 | ||||
| #endif // _CLOCKCONTROLLER_H
 | ||||
| @ -5,15 +5,14 @@ | ||||
| #include "senddata.h" | ||||
| #include "rcommand.h" | ||||
| #include "spislave.h" | ||||
| #include "rtctime.h" | ||||
| #include <lmic.h> | ||||
| 
 | ||||
| #ifdef HAS_BME | ||||
| #include "bme680mems.h" | ||||
| #endif | ||||
| 
 | ||||
| #ifdef HAS_RTC | ||||
| #include "rtctime.h" | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| void doHousekeeping(void); | ||||
| uint64_t uptime(void); | ||||
|  | ||||
| @ -3,11 +3,16 @@ | ||||
| 
 | ||||
| #include "globals.h" | ||||
| 
 | ||||
| #define DCF77_FRAME_SIZE (60) | ||||
| #define DCF77_PULSE_LENGTH (100) | ||||
| 
 | ||||
| extern uint8_t DCFpulse[]; | ||||
| 
 | ||||
| enum dcf_pulses { dcf_off, dcf_zero, dcf_one }; | ||||
| enum dcf_pinstate { dcf_low, dcf_high }; | ||||
| 
 | ||||
| void DCF_Pulse(time_t startTime); | ||||
| void DCF77_Frame(time_t t); | ||||
| void DCF_Pulse(time_t t); | ||||
| void IRAM_ATTR 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[]); | ||||
| 
 | ||||
|  | ||||
| @ -45,13 +45,6 @@ | ||||
|       pdTRUE | ||||
| #define I2C_MUTEX_UNLOCK() xSemaphoreGive(I2Caccess) | ||||
| 
 | ||||
| // 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 { | ||||
|   uint8_t lorasf;      // 7-12, lora spreadfactor
 | ||||
| @ -104,20 +97,19 @@ typedef struct { | ||||
| 
 | ||||
| enum sendprio_t { prio_low, prio_normal, prio_high }; | ||||
| 
 | ||||
| // global variables
 | ||||
| extern std::set<uint16_t, std::less<uint16_t>, Mallocator<uint16_t>> macs; | ||||
| extern std::array<uint64_t, 0xff>::iterator it; | ||||
| extern std::array<uint64_t, 0xff> beacons; | ||||
| 
 | ||||
| extern configData_t cfg;                      // current device configuration
 | ||||
| extern char display_line6[], display_line7[]; // screen buffers
 | ||||
| extern uint8_t volatile channel;              // wifi channel rotation counter
 | ||||
| extern uint16_t volatile macs_total, macs_wifi, macs_ble, | ||||
|     batt_voltage; // display values
 | ||||
| extern bool volatile TimePulseTick; | ||||
| extern hw_timer_t *sendCycle, *displaytimer; | ||||
| extern SemaphoreHandle_t I2Caccess, TimePulse; | ||||
| 
 | ||||
| extern std::set<uint16_t, std::less<uint16_t>, Mallocator<uint16_t>> macs; | ||||
| extern std::array<uint64_t, 0xff>::iterator it; | ||||
| extern std::array<uint64_t, 0xff> beacons; | ||||
| 
 | ||||
| extern TaskHandle_t irqHandlerTask; | ||||
| extern TaskHandle_t irqHandlerTask, ClockTask; | ||||
| extern TimerHandle_t WifiChanTimer; | ||||
| extern Timezone myTZ; | ||||
| 
 | ||||
|  | ||||
| @ -3,10 +3,13 @@ | ||||
| 
 | ||||
| #include "globals.h" | ||||
| 
 | ||||
| #define IF482_FRAME_SIZE (17) | ||||
| #define IF482_PULSE_LENGTH (1000) | ||||
| 
 | ||||
| extern HardwareSerial IF482;  | ||||
| 
 | ||||
| void IF482_Pulse(time_t t); | ||||
| String IF482_Frame(time_t tt); | ||||
| String IRAM_ATTR IF482_Frame(time_t tt); | ||||
| TickType_t tx_Ticks(unsigned long baud, uint32_t config, int8_t rxPin, | ||||
|                    int8_t txPins); | ||||
| 
 | ||||
|  | ||||
| @ -10,16 +10,14 @@ | ||||
| #include <SPI.h> | ||||
| #include <arduino_lmic_hal_boards.h> | ||||
| #include "loraconf.h" | ||||
| #include "rtctime.h" | ||||
| 
 | ||||
| // Needed for 24AA02E64, does not hurt anything if included and not used
 | ||||
| #ifdef MCP_24AA02E64_I2C_ADDRESS | ||||
| #include <Wire.h> | ||||
| #endif | ||||
| 
 | ||||
| // Needed for RTC time sync if RTC present on board
 | ||||
| #ifdef HAS_RTC | ||||
| #include "rtctime.h" | ||||
| #endif | ||||
| 
 | ||||
| 
 | ||||
| extern QueueHandle_t LoraSendQueue; | ||||
| 
 | ||||
|  | ||||
| @ -17,5 +17,6 @@ | ||||
| #include "led.h" | ||||
| #include "spislave.h" | ||||
| #include "lorawan.h" | ||||
| #include "rtctime.h" | ||||
| #include "clockcontroller.h" | ||||
| #endif | ||||
| @ -11,20 +11,17 @@ | ||||
| 
 | ||||
| extern RtcDS3231<TwoWire> Rtc; // make RTC instance globally available
 | ||||
| 
 | ||||
| extern TaskHandle_t ClockTask; | ||||
| extern hw_timer_t *clockCycle; | ||||
| extern bool volatile TimePulseTick; | ||||
| 
 | ||||
| int rtc_init(void); | ||||
| int set_rtctime(uint32_t t); | ||||
| int set_rtctime(time_t t); | ||||
| void sync_rtctime(void); | ||||
| time_t get_rtctime(void); | ||||
| float get_rtctemp(void); | ||||
| void IRAM_ATTR CLOCKIRQ(); | ||||
| void IRAM_ATTR CLOCKIRQ(void); | ||||
| int timepulse_init(void); | ||||
| void timepulse_start(); | ||||
| void timepulse_start(void); | ||||
| int sync_TimePulse(void); | ||||
| int sync_SysTime(time_t); | ||||
| int sync_SysTime(uint32_t t); | ||||
| 
 | ||||
| #endif // _RTCTIME_H
 | ||||
| @ -30,7 +30,7 @@ description = Paxcounter is a proof-of-concept ESP32 device for metering passeng | ||||
| 
 | ||||
| [common] | ||||
| ; for release_version use max. 10 chars total, use any decimal format like "a.b.c" | ||||
| release_version = 1.7.24 | ||||
| release_version = 1.7.31 | ||||
| ; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running! | ||||
| ; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose | ||||
| debug_level = 0 | ||||
| @ -67,7 +67,7 @@ lib_deps_all = | ||||
| build_flags_basic = | ||||
|     -include "src/hal/${PIOENV}.h" | ||||
|     -include "src/paxcounter.conf" | ||||
|     -w | ||||
|  ;   -w | ||||
|     '-DARDUINO_LMIC_PROJECT_CONFIG_H=../../../src/lmic_config.h' | ||||
|     '-DCORE_DEBUG_LEVEL=${common.debug_level}' | ||||
|     '-DLOG_LOCAL_LEVEL=${common.debug_level}' | ||||
|  | ||||
| @ -6,25 +6,16 @@ | ||||
| #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
 | ||||
| void clock_init(void) { | ||||
| 
 | ||||
|   timepulse_init(); // setup timepulse
 | ||||
| 
 | ||||
| // setup output interface
 | ||||
| // setup clock 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
 | ||||
| @ -36,37 +27,68 @@ void clock_init(void) { // ClockTask | ||||
|                           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
 | ||||
|   TickType_t wakeTime; | ||||
|   time_t t; | ||||
| 
 | ||||
| #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; | ||||
| #define t1(t) (t + DCF77_FRAME_SIZE + 1) // future time for next frame
 | ||||
| 
 | ||||
| // preload first DCF frame before start
 | ||||
| #ifdef HAS_DCF77 | ||||
|   DCF77_Frame(t1(telegram_time())); | ||||
| #endif | ||||
| 
 | ||||
|   // output time telegram triggered by timepulse
 | ||||
|   // output time telegram for second following sec beginning with 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); | ||||
|     } | ||||
|     xTaskNotifyWait(0x00, ULONG_MAX, &wakeTime, | ||||
|                     portMAX_DELAY); // wait for timepulse
 | ||||
| 
 | ||||
|     if (timeStatus() == timeNotSet) // do we have valid time?
 | ||||
|       continue; | ||||
| 
 | ||||
|     t = telegram_time(); // time to send to clock
 | ||||
| 
 | ||||
| #if defined HAS_IF482 | ||||
| 
 | ||||
|     IF482_Pulse(t + 1); // next second
 | ||||
| 
 | ||||
| #elif defined HAS_DCF77 | ||||
| 
 | ||||
|     if (ts == DCF77_FRAME_SIZE - 1) // moment to reload frame?
 | ||||
|       DCF77_Frame(t1(t));           // generate next frame
 | ||||
| 
 | ||||
|     if (DCFpulse[DCF77_FRAME_SIZE] == | ||||
|         minute(t1(t)))  // do he have a recent frame?
 | ||||
|       DCF_Pulse(t + 1); // then output next second of current frame
 | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
|   } // for
 | ||||
| } // clock_loop()
 | ||||
| 
 | ||||
| #endif // HAS_DCF77 || HAS_IF482
 | ||||
| // helper function to fetch current second from most precise time source
 | ||||
| time_t telegram_time(void) { | ||||
|   time_t t; | ||||
| 
 | ||||
| #ifdef HAS_GPS // gps is our primary time source if present
 | ||||
|   t = get_gpstime(); | ||||
|   if (t) // did we get a valid time?
 | ||||
|     return t; | ||||
| #endif | ||||
| 
 | ||||
| #ifdef HAS_RTC // rtc is our secondary time source if present
 | ||||
|   t = get_rtctime(); | ||||
|   if (t) | ||||
|     return t; | ||||
| #endif | ||||
| 
 | ||||
|   // else we use systime as fallback source
 | ||||
|   return now(); | ||||
| } | ||||
| 
 | ||||
| #endif // HAS_IF482 || defined HAS_DCF77
 | ||||
| @ -9,7 +9,7 @@ static const char TAG[] = "main"; | ||||
| 
 | ||||
| time_t userUTCTime; // Seconds since the UTC epoch
 | ||||
| unsigned long nextLoraTimeSync = millis(); | ||||
| unsigned long nextRTCTimeSync = millis() + TIME_WRITE_INTERVAL_RTC * 60000; | ||||
| unsigned long nextGPSTimeSync = millis(); | ||||
| 
 | ||||
| // do all housekeeping
 | ||||
| void doHousekeeping() { | ||||
| @ -24,8 +24,26 @@ void doHousekeeping() { | ||||
|   spi_housekeeping(); | ||||
|   lora_housekeeping(); | ||||
| 
 | ||||
| // do cyclic time sync with LORA network
 | ||||
| #ifdef TIME_SYNC_INTERVAL_LORA | ||||
| // do cyclic sync of systime with GPS timepulse, if present
 | ||||
| #if defined HAS_GPS && defined TIME_SYNC_INTERVAL_GPS | ||||
|   if (millis() >= nextGPSTimeSync) { | ||||
|     nextGPSTimeSync = millis() + TIME_SYNC_INTERVAL_GPS * | ||||
|                                      60000; // set up next time sync period
 | ||||
| 
 | ||||
|     // sync systime on next timepulse
 | ||||
|     if (sync_SysTime(get_gpstime())) { | ||||
|       //setSyncProvider(get_gpstime);
 | ||||
| #ifdef HAS_RTC | ||||
|       set_rtctime(now()); // epoch time
 | ||||
| #endif | ||||
|       ESP_LOGI(TAG, "GPS has set the system time"); | ||||
|     } else | ||||
|       ESP_LOGI(TAG, "Unable to sync system time with GPS"); | ||||
|   } // if
 | ||||
| #endif | ||||
| 
 | ||||
| // do cyclic time sync with LORA network, if present
 | ||||
| #if defined HAS_LORA && defined TIME_SYNC_INTERVAL_LORA | ||||
|   if (millis() >= nextLoraTimeSync) { | ||||
|     nextLoraTimeSync = millis() + TIME_SYNC_INTERVAL_LORA * | ||||
|                                       60000; // set up next time sync period
 | ||||
| @ -35,20 +53,6 @@ void doHousekeeping() { | ||||
|   } | ||||
| #endif | ||||
| 
 | ||||
| // do cyclic write back system time to RTC if we have an external time source
 | ||||
| #if (defined TIME_SYNC_INTERVAL_LORA || defined TIME_SYNC_INTERVAL_GPS) &&     \ | ||||
|     defined HAS_RTC | ||||
|   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 | ||||
|       ESP_LOGI(TAG, "RTC time updated"); | ||||
|   } | ||||
| #endif | ||||
| 
 | ||||
|   // task storage debugging //
 | ||||
|   ESP_LOGD(TAG, "IRQhandler %d bytes left | Taskstate = %d", | ||||
|            uxTaskGetStackHighWaterMark(irqHandlerTask), | ||||
|  | ||||
							
								
								
									
										157
									
								
								src/dcf77.cpp
									
									
									
									
									
								
							
							
						
						
									
										157
									
								
								src/dcf77.cpp
									
									
									
									
									
								
							| @ -1,5 +1,5 @@ | ||||
| /*
 | ||||
| // Emulate a DCF77 radio receiver
 | ||||
| // Emulate a DCF77 radio receiver to control an external clock
 | ||||
| //
 | ||||
| // a nice & free logic test program for DCF77 can be found here:
 | ||||
| https://www-user.tu-chemnitz.de/~heha/viewzip.cgi/hs/Funkuhr.zip/
 | ||||
| @ -13,36 +13,32 @@ https://www-user.tu-chemnitz.de/~heha/viewzip.cgi/hs/Funkuhr.zip/ | ||||
| // Local logging tag
 | ||||
| static const char TAG[] = "main"; | ||||
| 
 | ||||
| // array of dcf pulses for three minutes
 | ||||
| uint8_t DCFpulse[DCF77_FRAME_SIZE]; | ||||
| // array of dcf pulses for one minute
 | ||||
| uint8_t DCFpulse[DCF77_FRAME_SIZE + 1]; | ||||
| 
 | ||||
| // called by timepulse interrupt to ticker out DCF signal
 | ||||
| void DCF_Pulse(time_t startTime) { | ||||
| // triggered by 1 second timepulse to ticker out DCF signal
 | ||||
| void DCF_Pulse(time_t t) { | ||||
| 
 | ||||
|   static uint8_t current_second = second(startTime); | ||||
|   static uint8_t pulse = 0; | ||||
|   static bool SecondsPending = false; | ||||
|   uint8_t sec = second(t); | ||||
| 
 | ||||
|   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; | ||||
|   } | ||||
|   TickType_t startTime = xTaskGetTickCount(); | ||||
| 
 | ||||
|   // ticker out current DCF frame
 | ||||
|   if (SecondsPending) { | ||||
|     switch (pulse++) { | ||||
|   ESP_LOGD(TAG, "DCF77 sec %d", sec); | ||||
| 
 | ||||
|   // induce 10 pulses
 | ||||
|   for (uint8_t pulse = 0; pulse <= 9; pulse++) { | ||||
| 
 | ||||
|     switch (pulse) { | ||||
| 
 | ||||
|     case 0: // start of second -> start of timeframe for logic signal
 | ||||
|       if (DCFpulse[current_second] != dcf_off) | ||||
|       if (DCFpulse[sec] != dcf_off) | ||||
|         set_DCF77_pin(dcf_low); | ||||
|       ESP_LOGD(TAG, "DCF77 bit %d", current_second); | ||||
|       else // 59th second reached, nothing more to do
 | ||||
|         return; | ||||
|       break; | ||||
| 
 | ||||
|     case 1: // 100ms after start of second -> end of timeframe for logic 0
 | ||||
|       if (DCFpulse[current_second] == dcf_zero) | ||||
|       if (DCFpulse[sec] == dcf_zero) | ||||
|         set_DCF77_pin(dcf_high); | ||||
|       break; | ||||
| 
 | ||||
| @ -50,22 +46,69 @@ void DCF_Pulse(time_t startTime) { | ||||
|       set_DCF77_pin(dcf_high); | ||||
|       break; | ||||
| 
 | ||||
|     case 9: // 900ms after start -> last pulse before next second starts
 | ||||
|       pulse = 0; | ||||
|       if (current_second++ >= | ||||
|           (DCF77_FRAME_SIZE - 1)) // end of DCF77 frame (59th second)
 | ||||
|       { | ||||
|         current_second = 0; | ||||
|         SecondsPending = false; | ||||
|       }; | ||||
|       break; | ||||
|     case 9: // 900ms after start -> last pulse
 | ||||
|       return; | ||||
| 
 | ||||
|     }; // switch
 | ||||
|   };   // if
 | ||||
|     } // switch
 | ||||
| 
 | ||||
|     vTaskDelayUntil(&startTime, pdMS_TO_TICKS(DCF77_PULSE_LENGTH)); | ||||
| 
 | ||||
|   } // for
 | ||||
| } // DCF_Pulse()
 | ||||
| 
 | ||||
| void IRAM_ATTR DCF77_Frame(time_t tt) { | ||||
| 
 | ||||
|   uint8_t Parity; | ||||
|   time_t t = myTZ.toLocal(tt); // convert to local time
 | ||||
| 
 | ||||
|   ESP_LOGD(TAG, "DCF77 minute %d", minute(t)); | ||||
| 
 | ||||
|   // ENCODE HEAD
 | ||||
|   // secs 0..19 initialized with zeros
 | ||||
|   for (int n = 0; n <= 19; n++) | ||||
|     DCFpulse[n] = dcf_zero; | ||||
|   // secs 17..18: adjust for DayLightSaving
 | ||||
|   DCFpulse[18 - (myTZ.locIsDST(t) ? 1 : 0)] = dcf_one; | ||||
|   // sec 20: must be 1 to indicate time active
 | ||||
|   DCFpulse[20] = dcf_one; | ||||
| 
 | ||||
|   // ENCODE MINUTE (secs 21..28)
 | ||||
|   Parity = dec2bcd(minute(t), 21, 27, DCFpulse); | ||||
|   DCFpulse[28] = (Parity & 1) ? dcf_one : dcf_zero; | ||||
| 
 | ||||
|   // ENCODE HOUR (secs 29..35)
 | ||||
|   Parity = dec2bcd(hour(t), 29, 34, DCFpulse); | ||||
|   DCFpulse[35] = (Parity & 1) ? dcf_one : dcf_zero; | ||||
| 
 | ||||
|   // ENCODE DATE (secs 36..58)
 | ||||
|   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 (sec 59)
 | ||||
|   DCFpulse[59] = dcf_off; | ||||
|   // !! missing code here for leap second !!
 | ||||
| 
 | ||||
|   // timestamp the frame with minute pointer
 | ||||
|   DCFpulse[60] = minute(t); | ||||
| 
 | ||||
|   /*
 | ||||
|     // for debug: print the DCF77 frame buffer
 | ||||
|     char out[DCF77_FRAME_SIZE + 1]; | ||||
|     uint8_t i; | ||||
|     for (i = 0; i < DCF77_FRAME_SIZE; i++) { | ||||
|       out[i] = DCFpulse[i] + '0'; // convert int digit to printable ascii
 | ||||
|     } | ||||
|     out[DCF77_FRAME_SIZE] = '\0'; // string termination char
 | ||||
|     ESP_LOGD(TAG, "DCF minute %d = %s", DCFpulse[DCF77_FRAME_SIZE], out); | ||||
|   */ | ||||
| } | ||||
| 
 | ||||
| // helper function to convert decimal to bcd digit
 | ||||
| uint8_t dec2bcd(uint8_t dec, uint8_t startpos, uint8_t endpos, | ||||
| uint8_t IRAM_ATTR dec2bcd(uint8_t dec, uint8_t startpos, uint8_t endpos, | ||||
|                 uint8_t pArray[]) { | ||||
| 
 | ||||
|   uint8_t data = (dec < 10) ? dec : ((dec / 10) << 4) + (dec % 10); | ||||
| @ -80,52 +123,6 @@ uint8_t dec2bcd(uint8_t dec, uint8_t startpos, uint8_t endpos, | ||||
|   return parity; | ||||
| } | ||||
| 
 | ||||
| void DCF77_Frame(time_t tt) { | ||||
| 
 | ||||
|   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++) | ||||
|     DCFpulse[n] = dcf_zero; | ||||
|   // bits 17..18: adjust for DayLightSaving
 | ||||
|   DCFpulse[18 - (myTZ.locIsDST(t) ? 1 : 0)] = dcf_one; | ||||
|   // bit 20: must be 1 to indicate time active
 | ||||
|   DCFpulse[20] = dcf_one; | ||||
| 
 | ||||
|   // ENCODE MINUTE (bits 21..28)
 | ||||
|   Parity = dec2bcd(minute(t), 21, 27, DCFpulse); | ||||
|   DCFpulse[28] = (Parity & 1) ? dcf_one : dcf_zero; | ||||
| 
 | ||||
|   // ENCODE HOUR (bits 29..35)
 | ||||
|   Parity = dec2bcd(hour(t), 29, 34, DCFpulse); | ||||
|   DCFpulse[35] = (Parity & 1) ? dcf_one : dcf_zero; | ||||
| 
 | ||||
|   // ENCODE DATE (bits 36..58)
 | ||||
|   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)
 | ||||
|   DCFpulse[59] = dcf_off; | ||||
|   // !! missing code here for leap second !!
 | ||||
| 
 | ||||
|   /*
 | ||||
|     // for debug: print the DCF77 frame buffer
 | ||||
|     char out[DCF77_FRAME_SIZE + 1]; | ||||
|     uint8_t i; | ||||
|     for (i = 0; i < DCF77_FRAME_SIZE; i++) { | ||||
|       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); | ||||
|   */ | ||||
| } | ||||
| 
 | ||||
| // helper function to switch GPIO line with DCF77 signal
 | ||||
| void set_DCF77_pin(dcf_pinstate state) { | ||||
|   switch (state) { | ||||
| @ -146,6 +143,4 @@ void set_DCF77_pin(dcf_pinstate state) { | ||||
|   } // switch
 | ||||
| } // DCF77_pulse
 | ||||
| 
 | ||||
| // helper function calculates next minute
 | ||||
| 
 | ||||
| #endif // HAS_DCF77
 | ||||
| @ -96,8 +96,6 @@ time_t get_gpstime(void) { | ||||
|     time_t t = | ||||
|         tmConvert_t(gps.date.year(), gps.date.month(), gps.date.day(), | ||||
|                     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)); | ||||
|     return t; | ||||
|   } else { | ||||
|     ESP_LOGW(TAG, "GPS has no confident time"); | ||||
|  | ||||
| @ -86,12 +86,16 @@ static const char TAG[] = "main"; | ||||
| 
 | ||||
| HardwareSerial IF482(2); // use UART #2 (note: #1 may be in use for serial GPS)
 | ||||
| 
 | ||||
| // 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
 | ||||
| // triggered by timepulse to ticker out DCF signal
 | ||||
| void IF482_Pulse(time_t t) { | ||||
| 
 | ||||
|   TickType_t startTime = xTaskGetTickCount(); | ||||
|   static const TickType_t txDelay = pdMS_TO_TICKS(IF482_PULSE_LENGTH) - tx_Ticks(HAS_IF482); | ||||
|   vTaskDelayUntil(&startTime, txDelay); | ||||
|   IF482.print(IF482_Frame(t+1)); // note: if482 telegram for *next* second
 | ||||
| } | ||||
| 
 | ||||
| String IF482_Frame(time_t startTime) { | ||||
| String IRAM_ATTR IF482_Frame(time_t startTime) { | ||||
| 
 | ||||
|   time_t t = myTZ.toLocal(startTime); | ||||
|   char mon, buf[14], out[IF482_FRAME_SIZE]; | ||||
| @ -103,19 +107,14 @@ String IF482_Frame(time_t startTime) { | ||||
|   case timeNeedsSync: // time had been set but sync attempt did not succeed
 | ||||
|     mon = 'M'; | ||||
|     break; | ||||
|   default: // time not set, no valid time
 | ||||
|   default: // unknown time status (should never be reached)
 | ||||
|     mon = '?'; | ||||
|     break; | ||||
|   } // switch
 | ||||
| 
 | ||||
|   // do we have confident time/date?
 | ||||
|   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 | ||||
|     snprintf(buf, sizeof(buf), "000000F000000"); // no confident time/date
 | ||||
| 
 | ||||
|   // output IF482 telegram
 | ||||
|   // generate IF482 telegram
 | ||||
|   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)); | ||||
|   snprintf(out, sizeof(out), "O%cL%s\r", mon, buf); | ||||
|   ESP_LOGD(TAG, "IF482 = %s", out); | ||||
|   return out; | ||||
| @ -127,8 +126,11 @@ TickType_t tx_Ticks(unsigned long baud, uint32_t config, int8_t rxPin, | ||||
| 
 | ||||
|   uint32_t datenbits = ((config & 0x0c) >> 2) + 5; | ||||
|   uint32_t stopbits = ((config & 0x20) >> 5) + 1; | ||||
|   return pdMS_TO_TICKS( | ||||
|       round(((1 + datenbits + stopbits) * IF482_FRAME_SIZE * 1000.0 / baud))); | ||||
|   uint32_t tx_delay = | ||||
|       (2 + datenbits + stopbits) * IF482_FRAME_SIZE * 1000.0 / baud; | ||||
|   // +2 ms margin for the startbit and the clock's processing time
 | ||||
| 
 | ||||
|   return pdMS_TO_TICKS(round(tx_delay)); | ||||
| } | ||||
| 
 | ||||
| #endif // HAS_IF482
 | ||||
| @ -399,7 +399,7 @@ esp_err_t lora_stack_init() { | ||||
|   } | ||||
| 
 | ||||
|   return ESP_OK; // continue main program
 | ||||
| #endif | ||||
| #endif // HAS_LORA
 | ||||
| } | ||||
| 
 | ||||
| void lora_enqueuedata(MessageBuffer_t *message, sendprio_t prio) { | ||||
| @ -439,6 +439,7 @@ void lora_housekeeping(void) { | ||||
| 
 | ||||
| void user_request_network_time_callback(void *pVoidUserUTCTime, | ||||
|                                         int flagSuccess) { | ||||
| #ifdef HAS_LORA | ||||
|   // Explicit conversion from void* to uint32_t* to avoid compiler errors
 | ||||
|   uint32_t *pUserUTCTime = (uint32_t *)pVoidUserUTCTime; | ||||
|   lmic_time_reference_t lmicTimeReference; | ||||
| @ -468,11 +469,14 @@ void user_request_network_time_callback(void *pVoidUserUTCTime, | ||||
|   *pUserUTCTime += requestDelaySec; | ||||
| 
 | ||||
|   // Update system time with time read from the network
 | ||||
|   setTime(*pUserUTCTime); | ||||
|   ESP_LOGI(TAG, "LoRaWAN network has set the system time"); | ||||
|   if (sync_TimePulse()) {              // wait for start of next second
 | ||||
|     if (sync_SysTime(*pUserUTCTime)) { // do we have a valid time?
 | ||||
| #ifdef HAS_RTC | ||||
|   sync_TimePulse();                // wait for next start of second
 | ||||
|   if (!set_rtctime(*pUserUTCTime+1)) // epoch time
 | ||||
|     ESP_LOGE(TAG, "RTC set time failure"); | ||||
|       set_rtctime(now()); // epoch time
 | ||||
| #endif | ||||
| } | ||||
|       ESP_LOGI(TAG, "LORA has set the system time"); | ||||
|     } | ||||
|   } else | ||||
|     ESP_LOGI(TAG, "Unable to sync system time with LORA"); | ||||
| #endif // HAS_LORA
 | ||||
| } // user_request_network_time_callback
 | ||||
							
								
								
									
										59
									
								
								src/main.cpp
									
									
									
									
									
								
							
							
						
						
									
										59
									
								
								src/main.cpp
									
									
									
									
									
								
							| @ -27,9 +27,8 @@ Uused tasks and timers: | ||||
| 
 | ||||
| Task          Core  Prio  Purpose | ||||
| ==================================================================================== | ||||
| clockloop     0     4     generates realtime telegrams for external clock | ||||
| ledloop       0     3     blinks LEDs | ||||
| if482loop     0     3     generates serial feed of IF482 time telegrams | ||||
| dcf77loop     0     3     generates DCF77 timeframe pulses | ||||
| spiloop       0     2     reads/writes data on spi interface | ||||
| IDLE          0     0     ESP32 arduino scheduler -> runs wifi sniffer | ||||
| 
 | ||||
| @ -71,8 +70,9 @@ hw_timer_t *sendCycle = NULL, *homeCycle = NULL; | ||||
| hw_timer_t *displaytimer = NULL; | ||||
| #endif | ||||
| 
 | ||||
| TaskHandle_t irqHandlerTask; | ||||
| TaskHandle_t irqHandlerTask, ClockTask; | ||||
| SemaphoreHandle_t I2Caccess, TimePulse; | ||||
| bool volatile TimePulseTick = false; | ||||
| 
 | ||||
| // container holding unique MAC address hashes with Memory Alloctor using PSRAM,
 | ||||
| // if present
 | ||||
| @ -334,24 +334,17 @@ void setup() { | ||||
|   strcat_P(features, " LPPPKD"); | ||||
| #endif | ||||
| 
 | ||||
| // initialize RTC
 | ||||
|   // initialize RTC
 | ||||
| #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"); | ||||
|   else | ||||
|     ESP_LOGI(TAG, "RTC has set the system time"); | ||||
|   setSyncInterval(TIME_SYNC_INTERVAL_RTC * 60); | ||||
| #endif // HAS_RTC
 | ||||
| #endif | ||||
| 
 | ||||
| #if defined HAS_DCF77 | ||||
|   strcat_P(features, " DCF77"); | ||||
| #endif | ||||
| 
 | ||||
| #if defined HAS_IF482 && defined RTC_INT | ||||
| #if defined HAS_IF482 | ||||
|   strcat_P(features, " IF482"); | ||||
| #endif | ||||
| 
 | ||||
| @ -365,6 +358,13 @@ void setup() { | ||||
| #endif | ||||
| #endif | ||||
| 
 | ||||
|   // start pps timepulse
 | ||||
|   ESP_LOGI(TAG, "Starting timepulse..."); | ||||
|   if (timepulse_init()) // setup timepulse
 | ||||
|     timepulse_start();  // start pulse
 | ||||
|   else | ||||
|     ESP_LOGE(TAG, "No timepulse, systime will not be synced!"); | ||||
| 
 | ||||
|   // start wifi in monitor mode and start channel rotation timer
 | ||||
|   ESP_LOGI(TAG, "Starting Wifi..."); | ||||
|   wifi_sniffer_init(); | ||||
| @ -417,19 +417,29 @@ 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"); | ||||
|   else { | ||||
|     ESP_LOGI(TAG, "GPS has set the system time"); | ||||
|   // sync systime on next timepulse
 | ||||
|   ESP_LOGI(TAG, "GPS is setting system time"); | ||||
|   if (sync_SysTime(get_gpstime())) { | ||||
|     //setSyncProvider(get_gpstime); // reset sync cycle on top of second
 | ||||
|     //setSyncInterval(TIME_SYNC_INTERVAL_GPS * 60);
 | ||||
|     // calibrate RTC
 | ||||
| #ifdef HAS_RTC | ||||
|     if (!set_rtctime(now())) // epoch time
 | ||||
|       ESP_LOGE(TAG, "RTC set time failure"); | ||||
| #endif | ||||
|   } | ||||
|   setSyncInterval(TIME_SYNC_INTERVAL_GPS * 60); | ||||
|     set_rtctime(now()); // epoch time
 | ||||
| #endif | ||||
|   } else | ||||
|     ESP_LOGI(TAG, "Unable to sync system time with GPS"); | ||||
| #endif // HAS_GPS
 | ||||
| 
 | ||||
|     // initialize systime from timesource
 | ||||
| #ifdef HAS_RTC | ||||
|   // sync systime on next timepulse
 | ||||
|   ESP_LOGI(TAG, "RTC is setting system time"); | ||||
|   if (sync_SysTime(get_rtctime())) { | ||||
|     //setSyncProvider(get_rtctime); // reset sync cycle on top of second
 | ||||
|     //setSyncInterval(TIME_SYNC_INTERVAL_RTC * 60);
 | ||||
|   } else | ||||
|     ESP_LOGI(TAG, "Unable to sync system time with RTC"); | ||||
| #endif // HAS_RTC
 | ||||
| 
 | ||||
| #if defined HAS_IF482 || defined HAS_DCF77 | ||||
|   ESP_LOGI(TAG, "Starting Clock Controller..."); | ||||
| @ -439,7 +449,6 @@ void setup() { | ||||
| } // setup()
 | ||||
| 
 | ||||
| void loop() { | ||||
| 
 | ||||
|   while (1) { | ||||
| #ifdef HAS_LORA | ||||
|     os_runloop_once(); // execute lmic scheduled jobs and events
 | ||||
|  | ||||
| @ -84,7 +84,6 @@ | ||||
| // settings for syncing time of node and external time sources | ||||
| #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 | ||||
| 
 | ||||
| // time zone, see https://github.com/JChristensen/Timezone/blob/master/examples/WorldClock/WorldClock.ino | ||||
|  | ||||
| @ -3,9 +3,7 @@ | ||||
| // Local logging tag
 | ||||
| static const char TAG[] = "main"; | ||||
| 
 | ||||
| TaskHandle_t ClockTask; | ||||
| hw_timer_t *clockCycle = NULL; | ||||
| bool volatile TimePulseTick = false; | ||||
| 
 | ||||
| // helper function to setup a pulse per second for time synchronisation
 | ||||
| int timepulse_init() { | ||||
| @ -41,7 +39,6 @@ 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);
 | ||||
|   timerAlarmWrite(clockCycle, 10000, true); // 1000ms
 | ||||
|   ESP_LOGI(TAG, "Time base: internal (ESP32 hardware timer)"); | ||||
|   return 1; // success
 | ||||
| @ -60,31 +57,10 @@ void timepulse_start(void) { | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| // 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) { | ||||
|     return 1; | ||||
|   } // success
 | ||||
|   else | ||||
|     ESP_LOGW(TAG, "Missing timepulse, time not synced"); | ||||
|   return 0; // failure
 | ||||
| } | ||||
| 
 | ||||
| // interrupt service routine triggered by either rtc pps or esp32 hardware
 | ||||
| // timer
 | ||||
| void IRAM_ATTR CLOCKIRQ() { | ||||
|   xTaskNotifyFromISR(ClockTask, xTaskGetTickCountFromISR(), eSetBits, NULL); | ||||
| // interrupt service routine triggered by either pps or esp32 hardware timer
 | ||||
| void IRAM_ATTR CLOCKIRQ(void) { | ||||
|   if (ClockTask != NULL) | ||||
|     xTaskNotifyFromISR(ClockTask, xTaskGetTickCountFromISR(), eSetBits, NULL); | ||||
| #if defined GPS_INT || defined RTC_INT | ||||
|   xSemaphoreGiveFromISR(TimePulse, NULL); | ||||
|   TimePulseTick = !TimePulseTick; // flip ticker
 | ||||
| @ -92,6 +68,31 @@ void IRAM_ATTR CLOCKIRQ() { | ||||
|   portYIELD_FROM_ISR(); | ||||
| } | ||||
| 
 | ||||
| // helper function to sync systime on start of next second
 | ||||
| int sync_SysTime(time_t t) { | ||||
|   if (sync_TimePulse() && (t)) { // wait for start of next second by timepulse
 | ||||
|     setTime(t + 1); | ||||
|     ESP_LOGD(TAG, "Systime synced on second"); | ||||
|     return 1; // success
 | ||||
|   } else | ||||
|     return 0; // failure
 | ||||
| } | ||||
| 
 | ||||
| int sync_SysTime(uint32_t t) { // t is epoch seconds starting 1.1.1970
 | ||||
|   return sync_SysTime(static_cast<time_t>(t)); | ||||
| } | ||||
| 
 | ||||
| // 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(1100)) == pdTRUE) { | ||||
|     return 1; | ||||
|   } // success
 | ||||
|   else | ||||
|     ESP_LOGW(TAG, "Missing timepulse, time not synced"); | ||||
|   return 0; // failure
 | ||||
| } | ||||
| 
 | ||||
| #ifdef HAS_RTC // we have hardware RTC
 | ||||
| 
 | ||||
| RtcDS3231<TwoWire> Rtc(Wire); // RTC hardware i2c interface
 | ||||
| @ -153,6 +154,7 @@ int set_rtctime(time_t t) { // t is seconds epoch time starting 1.1.1970 | ||||
|     ESP_LOGI(TAG, "RTC calibrated"); | ||||
|     return 1; // success
 | ||||
|   } | ||||
|   ESP_LOGE(TAG, "RTC set time failure"); | ||||
|   return 0; // failure
 | ||||
| } // set_rtctime()
 | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user