diff --git a/include/cyclic.h b/include/cyclic.h index 13e9419c..c98ed050 100644 --- a/include/cyclic.h +++ b/include/cyclic.h @@ -11,11 +11,14 @@ #include "bme680mems.h" #endif -// Needed for RTC time sync if RTC present on board #ifdef HAS_RTC #include "rtctime.h" #endif +#ifdef HAS_DCF77 +#include "dcf77.h" +#endif + void doHousekeeping(void); uint64_t uptime(void); void reset_counters(void); diff --git a/include/dcf77.h b/include/dcf77.h new file mode 100644 index 00000000..bf2ffcee --- /dev/null +++ b/include/dcf77.h @@ -0,0 +1,16 @@ +#ifndef _DCF77_H +#define _DCF77_H + +#include "globals.h" + +extern TaskHandle_t DCF77Task; +extern hw_timer_t *dcfCycle; + +enum dcf_pulses { dcf_off, dcf_zero, dcf_one }; + +int dcf77_init(void); +void dcf77_loop(void *pvParameters); +void IRAM_ATTR DCF77IRQ(void); +void sendDCF77(void); + +#endif \ No newline at end of file diff --git a/include/if482.h b/include/if482.h index 7ceca1c5..38a6506b 100644 --- a/include/if482.h +++ b/include/if482.h @@ -2,7 +2,6 @@ #define _IF482_H #include "globals.h" -#include "irqhandler.h" extern TaskHandle_t IF482Task; diff --git a/src/cyclic.cpp b/src/cyclic.cpp index 67fcc99c..5b23817d 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -60,6 +60,10 @@ void doHousekeeping() { ESP_LOGD(TAG, "Bmeloop %d bytes left | Taskstate = %d", uxTaskGetStackHighWaterMark(BmeTask), eTaskGetState(BmeTask)); #endif +#ifdef HAS_DCF77 + ESP_LOGD(TAG, "DCF77loop %d bytes left | Taskstate = %d", + uxTaskGetStackHighWaterMark(DCF77Task), eTaskGetState(DCF77Task)); +#endif #if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) ESP_LOGD(TAG, "LEDloop %d bytes left | Taskstate = %d", @@ -79,6 +83,11 @@ void doHousekeeping() { bme_status.temperature, bme_status.iaq, bme_status.iaq_accuracy); #endif +// generate DCF77 timeframes +#ifdef HAS_DCF77 + sendDCF77(); +#endif + // check free heap memory if (ESP.getMinFreeHeap() <= MEM_LOW) { ESP_LOGI(TAG, @@ -86,8 +95,8 @@ void doHousekeeping() { "free heap = %d bytes)", ESP.getMinFreeHeap(), ESP.getFreeHeap()); SendPayload(COUNTERPORT, prio_high); // send data before clearing counters - reset_counters(); // clear macs container and reset all counters - get_salt(); // get new salt for salting hashes + reset_counters(); // clear macs container and reset all counters + get_salt(); // get new salt for salting hashes if (ESP.getMinFreeHeap() <= MEM_LOW) // check again do_reset(); // memory leak, reset device @@ -98,8 +107,8 @@ void doHousekeeping() { if (ESP.getMinFreePsram() <= MEM_LOW) { ESP_LOGI(TAG, "PSRAM full, counter cleared"); SendPayload(COUNTERPORT, prio_high); // send data before clearing counters - reset_counters(); // clear macs container and reset all counters - get_salt(); // get new salt for salting hashes + reset_counters(); // clear macs container and reset all counters + get_salt(); // get new salt for salting hashes if (ESP.getMinFreePsram() <= MEM_LOW) // check again do_reset(); // memory leak, reset device diff --git a/src/dcf77.cpp b/src/dcf77.cpp new file mode 100644 index 00000000..0113e4b7 --- /dev/null +++ b/src/dcf77.cpp @@ -0,0 +1,219 @@ +// +// source: +// https://www.elektormagazine.com/labs/dcf77-emulator-with-esp8266-elektor-labs-version-150713 +// +/* + Simulate a DCF77 radio receiver + Emit a complete three minute pulses train from the GPIO output + the train is preceded by a single pulse and the lacking 59th pulse to allow + some clock model syncronization of the beginning frame. After the three pulses + train one more single pulse is sent to safely close the frame +*/ + +#if defined HAS_DCF77 + +#include "dcf77.h" + +// Local logging tag +static const char TAG[] = "main"; + +TaskHandle_t DCF77Task; +QueueHandle_t DCFSendQueue; +hw_timer_t *dcfCycle = NULL; + +#define DCF77_FRAME_SIZE 60 +#define DCF_FRAME_QUEUE_SIZE (HOMECYCLE / 60 + 1) + +// array of dcf pulses for three minutes +uint8_t DCFtimeframe[DCF77_FRAME_SIZE]; + +// initialize and configure DCF77 output +int dcf77_init(void) { + + DCFSendQueue = xQueueCreate(DCF_FRAME_QUEUE_SIZE, + sizeof(DCFtimeframe) / sizeof(DCFtimeframe[0])); + if (!DCFSendQueue) { + ESP_LOGE(TAG, "Could not create DCF77 send queue. Aborting."); + return 0; // failure + } + ESP_LOGI(TAG, "DCF77 send queue created, size %d Bytes", + DCF_FRAME_QUEUE_SIZE * sizeof(DCFtimeframe) / + sizeof(DCFtimeframe[0])); + + pinMode(HAS_DCF77, OUTPUT); + digitalWrite(HAS_DCF77, LOW); + + return 1; // success + +} // ifdcf77_init + +// called every 100msec for DCF77 output +void DCF_Ticker() { + + static uint8_t DCF_Frame[DCF77_FRAME_SIZE]; + static uint8_t bit = 0; + static uint8_t pulse = 0; + static bool BitsPending = false; + + while (BitsPending) { + switch (pulse++) { + + case 0: // start of second -> start of timeframe for logic signal + if (DCF_Frame[bit] != dcf_off) + digitalWrite(HAS_DCF77, LOW); + return; + + case 1: // 100ms after start of second -> end of timeframe for logic 0 + if (DCF_Frame[bit] == dcf_zero) + digitalWrite(HAS_DCF77, HIGH); + return; + + case 2: // 200ms after start of second -> end of timeframe for logic signal + digitalWrite(HAS_DCF77, HIGH); + return; + + case 9: // last pulse before next second starts + pulse = 0; + if (bit++ != DCF77_FRAME_SIZE) + return; + else { // last pulse of DCF77 frame (59th second) + bit = 0; + BitsPending = false; + }; + break; + + }; // switch + }; // while + + // get next frame to send from queue + if (xQueueReceive(DCFSendQueue, &DCF_Frame, (TickType_t)0) == pdTRUE) + BitsPending = true; + +} // DCF_Ticker() + +void dcf77_loop(void *pvParameters) { + + configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check + + // 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 + NULL, + portMAX_DELAY); // wait forever (missing error handling here...) + + DCF_Ticker(); + } + vTaskDelete(DCF77Task); // shoud never be reached +} // dcf77_loop() + +uint8_t dec2bcd(uint8_t dec, uint8_t startpos, uint8_t endpos, + uint8_t pArray[]) { + + uint8_t data = (dec < 10) ? dec : ((dec / 10) << 4) + (dec % 10); + uint8_t parity = 0; + + for (uint8_t n = startpos; n <= endpos; n++) { + pArray[n] = (data & 1) ? dcf_one : dcf_zero; + parity += (data & 1); + data >>= 1; + } + + return parity; +} + +void enqueueTimeframe(time_t t) { + + uint8_t ParityCount; + + // ENCODE HEAD + // bits 0..19 initialized with zeros + for (int n = 0; n <= 19; n++) + DCFtimeframe[n] = dcf_zero; + // bits 17..18: adjust for DayLightSaving + DCFtimeframe[18 - (myTZ.locIsDST(t) ? 1 : 0)] = dcf_one; + // bit 20: must be 1 to indicate time active + DCFtimeframe[20] = dcf_one; + + // ENCODE MINUTE (bits 21..28) + ParityCount = dec2bcd(minute(t), 21, 27, DCFtimeframe); + DCFtimeframe[28] = (ParityCount & 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; + + // 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; + + // ENCODE TAIL (bit 59) + DCFtimeframe[59] = dcf_off; + // --> missing code here for switching second! + /* + In unregelmäßigen Zeitabständen muss eine Schaltsekunde eingefügt werden. Dies + ist dadurch bedingt, dass sich die Erde nicht genau in 24 Stunden um sich + selbst dreht. Auf die koordinierte Weltzeitskala UTC bezogen, wird diese + Korrektur zum Ende der letzten Stunde des 31. Dezember oder 30. Juni + vorgenommen. In Mitteleuropa muss die Schaltsekunde daher am 1. Januar um 1.00 + Uhr MEZ oder am 1.Juli um 2.00 MESZ eingeschoben werden. Zu den genannten + Zeiten werden daher 61 Sekunden gesendet. + */ + + // post generated DCFtimeframe data to DCF SendQueue + if (xQueueSendToBack(DCFSendQueue, (void *)&DCFtimeframe[0], (TickType_t)0) != + pdPASS) + ESP_LOGE(TAG, "Failed to send DCF data"); + + // 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] = DCFtimeframe[i] + '0'; // convert int digit to printable ascii + } + out[DCF77_FRAME_SIZE] = '\0'; // string termination char + ESP_LOGD(TAG, "DCF=%s", out); +} + +void sendDCF77() { + + time_t t = now(); + + /* + if (second(t) > 56) { + delay(30000); + return; + } + */ + + // enqueue DCF timeframes for each i minute + for (uint8_t i = 0; i < DCF_FRAME_QUEUE_SIZE; i++) + enqueueTimeframe(t + i * 60); + + /* + // how many to the minute end ? + // don't forget that we begin transmission at second 58 + delay((58 - second(t)) * 1000); + + // three minutes are needed to transmit all the packet + // then wait more 30 secs to locate safely at the half of minute + // NB 150+60=210sec, 60secs are lost from main routine + delay(150000); + */ + +} // Ende ReadAndDecodeTime() + +// interrupt service routine triggered each 100ms by ESP32 hardware timer +void IRAM_ATTR DCF77IRQ() { + xTaskNotifyFromISR(DCF77Task, xTaskGetTickCountFromISR(), eSetBits, NULL); + portYIELD_FROM_ISR(); +} + +#endif // HAS_DCF77 \ No newline at end of file diff --git a/src/display.cpp b/src/display.cpp index 280528d1..0c186a80 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -145,12 +145,14 @@ void refreshtheDisplay() { uint8_t msgWaiting; char buff[16]; // 16 chars line buffer +#if defined HAS_RTC || defined HAS_GPS const char timeNosyncSymbol = '?'; -#ifdef HAS_IF482 +#if defined HAS_IF482 || defined HAS_DCF77 const char timesyncSymbol = '+'; #else const char timesyncSymbol = '*'; #endif +#endif // HAS_RTC // update counter (lines 0-1) snprintf( @@ -214,17 +216,16 @@ void refreshtheDisplay() { u8x8.printf("%4dKB", getFreeRAM() / 1024); #ifdef HAS_LORA - u8x8.setCursor(0, 6); -#ifndef HAS_RTC +#if (!defined HAS_RTC) && (!defined HAS_GPS) // update LoRa status display (line 6) u8x8.printf("%-16s", display_line6); -#else +#else // HAS_RTC or HAS_GPS // update time/date display (line 6) time_t t = myTZ.toLocal(now()); char timeState = timeStatus() == timeSet ? timesyncSymbol : timeNosyncSymbol; -#ifdef RTC_INT // make timestatus symbol blinking +#ifdef RTC_INT // make timestatus symbol blinking if pps line if (second(t) % 2) timeState = ' '; #endif // RTC_INT diff --git a/src/gpsread.cpp b/src/gpsread.cpp index d6c58b99..a446118e 100644 --- a/src/gpsread.cpp +++ b/src/gpsread.cpp @@ -73,7 +73,7 @@ time_t get_gpstime(void) { if ((gps.time.age() < 1500) && (gps.time.isValid())) { 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: %d/%d/%d %d:%d:%d", year(t), month(t), day(t), + ESP_LOGD(TAG, "GPS time: %4d/%02d/%02d %02d:%02d:%02d", year(t), month(t), day(t), hour(t), minute(t), second(t)); } else { ESP_LOGW(TAG, "GPS has no confident time"); diff --git a/src/hal/generic.h b/src/hal/generic.h index 2b34967f..2997257a 100644 --- a/src/hal/generic.h +++ b/src/hal/generic.h @@ -55,6 +55,9 @@ // Settings for IF482 interface #define HAS_IF482 9600, SERIAL_7E1, GPIO_NUM_12, GPIO_NUM_14 // IF482 serial port parameters +// Settings for DCF77 interface +#define HAS_DCF77 GPIO_NUM_13 + // Pins for LORA chip SPI interface, reset line and interrupt lines #define LORA_SCK (5) #define LORA_CS (18) diff --git a/src/main.cpp b/src/main.cpp index 2a029a1f..4392c42a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,7 +28,8 @@ Uused tasks and timers: Task Core Prio Purpose ==================================================================================== ledloop 0 3 blinks LEDs -if482loop 1 3 serial feed of IF482 time telegrams +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 @@ -46,13 +47,13 @@ Tasks using i2c bus all must have same priority, because using mutex semaphore ESP32 hardware timers ================================ 0 triggers display refresh - 1 unused (reserved for DCF77) + 1 triggers DCF77 clock signal 2 triggers send payload cycle 3 triggers housekeeping cycle RTC hardware timer (if present) ================================ - triggers IF482 clock generator + triggers IF482 clock signal */ @@ -64,8 +65,12 @@ char display_line6[16], display_line7[16]; // display buffers uint8_t volatile channel = 0; // channel rotation counter uint16_t volatile macs_total = 0, macs_wifi = 0, macs_ble = 0, batt_voltage = 0; // globals for display -hw_timer_t *channelSwitch = NULL, *sendCycle = NULL, *homeCycle = NULL, - *displaytimer = NULL; // irq tasks + +hw_timer_t *sendCycle = NULL, *homeCycle = NULL; +#ifdef HAS_DISPLAY +hw_timer_t *displaytimer = NULL; +#endif + TaskHandle_t irqHandlerTask; SemaphoreHandle_t I2Caccess; @@ -327,6 +332,16 @@ void setup() { setSyncInterval(TIME_SYNC_INTERVAL_RTC * 60); #endif // HAS_RTC +#if defined HAS_DCF77 + strcat_P(features, " DCF77"); + assert(dcf77_init()); +#endif + +#if defined HAS_IF482 && defined RTC_INT + strcat_P(features, " IF482"); + assert(if482_init()); +#endif + // show compiled features ESP_LOGI(TAG, "Features:%s", features); @@ -403,8 +418,6 @@ void setup() { #endif #if defined HAS_IF482 && defined RTC_INT - strcat_P(features, " IF482"); - assert(if482_init()); ESP_LOGI(TAG, "Starting IF482 Generator..."); xTaskCreatePinnedToCore(if482_loop, // task function "if482loop", // name of task @@ -419,6 +432,24 @@ void setup() { attachInterrupt(digitalPinToInterrupt(RTC_INT), IF482IRQ, FALLING); #endif +#if defined HAS_DCF77 + ESP_LOGI(TAG, "Starting DCF77 Generator..."); + 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 + &DCF77Task, // task handle + 0); // CPU core + + // setup 100ms clock signal for DCF77 generator using esp32 hardware timer 1 + assert(DCF77Task != NULL); // has dcf77 task started? + dcfCycle = timerBegin(1, 8000, true); + timerAttachInterrupt(dcfCycle, &DCF77IRQ, true); + timerAlarmWrite(dcfCycle, 1000, true); + timerAlarmEnable(dcfCycle); +#endif + } // setup() void loop() {