diff --git a/include/cyclic.h b/include/cyclic.h index c98ed050..007c09d8 100644 --- a/include/cyclic.h +++ b/include/cyclic.h @@ -15,10 +15,6 @@ #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/globals.h b/include/globals.h index 73c11287..8755b1f0 100644 --- a/include/globals.h +++ b/include/globals.h @@ -102,6 +102,7 @@ extern uint16_t volatile macs_total, macs_wifi, macs_ble, batt_voltage; // display values extern hw_timer_t *sendCycle, *displaytimer; extern SemaphoreHandle_t I2Caccess; +extern bool volatile BitsPending; extern std::set, Mallocator> macs; extern std::array::iterator it; diff --git a/platformio.ini b/platformio.ini index d64d1be7..462ed686 100644 --- a/platformio.ini +++ b/platformio.ini @@ -6,7 +6,7 @@ ; ---> SELECT TARGET PLATFORM HERE! <--- [platformio] -;env_default = generic +env_default = generic ;env_default = ebox ;env_default = eboxtube ;env_default = heltec @@ -24,16 +24,16 @@ ;env_default = lolin32lora ;env_default = lolin32lite ;env_default = octopus32 -env_default = ebox, eboxtube, heltec, ttgobeam, lopy4, lopy, ttgov21old, ttgov21new, ttgofox +;env_default = ebox, eboxtube, heltec, ttgobeam, lopy4, lopy, ttgov21old, ttgov21new, ttgofox ; description = Paxcounter is a proof-of-concept ESP32 device for metering passenger flows in realtime. It counts how many mobile devices are around. [common] ; for release_version use max. 10 chars total, use any decimal format like "a.b.c" -release_version = 1.7.143 +release_version = 1.7.152 ; 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 = 3 +debug_level = 0 ; UPLOAD MODE: select esptool to flash via USB/UART, select custom to upload to cloud for OTA upload_protocol = esptool ;upload_protocol = custom @@ -45,9 +45,9 @@ monitor_speed = 115200 lib_deps_lora = MCCI LoRaWAN LMIC library@^2.3.1 lib_deps_display = - U8g2@>=2.25.5 + U8g2@>=2.25.7 lib_deps_rgbled = - SmartLeds@>=1.1.3 + SmartLeds@>=1.1.5 lib_deps_gps = TinyGPSPlus@>=1.0.2 lib_deps_rtc = diff --git a/src/cyclic.cpp b/src/cyclic.cpp index 5b23817d..b4e6e9ff 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -83,11 +83,6 @@ 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, diff --git a/src/dcf77.cpp b/src/dcf77.cpp index 0113e4b7..b0a97c2d 100644 --- a/src/dcf77.cpp +++ b/src/dcf77.cpp @@ -1,13 +1,10 @@ -// -// 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 +// Emulate a DCF77 radio receiver +// +// parts of this code werde adapted from source: +// +https://www.elektormagazine.com/labs/dcf77-emulator-with-esp8266-elektor-labs-version-150713 +// */ #if defined HAS_DCF77 @@ -18,11 +15,9 @@ 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]; @@ -30,84 +25,31 @@ 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); + digitalWrite(HAS_DCF77, HIGH); + + 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 + + assert(DCF77Task); // has dcf77 task started? + + // setup 100ms clock signal for DCF77 generator using esp32 hardware timer 1 + ESP_LOGD(TAG, "Starting DCF pulse..."); + dcfCycle = timerBegin(1, 8000, true); // set 80 MHz prescaler to 1/10000 sec + timerAttachInterrupt(dcfCycle, &DCF77IRQ, true); + timerAlarmWrite(dcfCycle, 2000, true); // 100ms cycle + timerAlarmEnable(dcfCycle); + xTaskNotify(DCF77Task, 0, eNoAction); 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[]) { @@ -123,7 +65,7 @@ uint8_t dec2bcd(uint8_t dec, uint8_t startpos, uint8_t endpos, return parity; } -void enqueueTimeframe(time_t t) { +void generateTimeframe(time_t t) { uint8_t ParityCount; @@ -156,21 +98,7 @@ void enqueueTimeframe(time_t t) { // 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"); + // !! missing code here for leap second !! // for debug: print the DCF77 frame buffer char out[DCF77_FRAME_SIZE + 1]; @@ -179,41 +107,80 @@ void enqueueTimeframe(time_t t) { 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); + ESP_LOGD(TAG, "DCF Timeframe = %s", out); } -void sendDCF77() { +// called every 100msec by hardware time +void DCF_Out() { - time_t t = now(); + static uint8_t bit = 0; + static uint8_t pulse = 0; - /* - if (second(t) > 56) { - delay(30000); + if (!BitsPending) { + // prepare next frame to send + generateTimeframe(now()); + BitsPending = true; + // wait until next minute, then kick off hardware timer and first DCF pulse + do { + delay(2); + } while (second()); + } + + // ticker out current frame + while (BitsPending) { + switch (pulse++) { + + case 0: // start of second -> start of timeframe for logic signal + if (DCFtimeframe[bit] != dcf_off) + digitalWrite(HAS_DCF77, LOW); return; - } - */ - // enqueue DCF timeframes for each i minute - for (uint8_t i = 0; i < DCF_FRAME_QUEUE_SIZE; i++) - enqueueTimeframe(t + i * 60); + case 1: // 100ms after start of second -> end of timeframe for logic 0 + if (DCFtimeframe[bit] == dcf_zero) + digitalWrite(HAS_DCF77, HIGH); + return; - /* - // how many to the minute end ? - // don't forget that we begin transmission at second 58 - delay((58 - second(t)) * 1000); + case 2: // 200ms after start of second -> end of timeframe for logic 1 + digitalWrite(HAS_DCF77, HIGH); + return; - // 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); - */ + case 9: // last pulse before next second starts + pulse = 0; + if (bit++ != DCF77_FRAME_SIZE) + return; + else { // end of DCF77 frame (59th second) + bit = 0; + BitsPending = false; + }; + break; -} // Ende ReadAndDecodeTime() + }; // switch + }; // while + +} // DCF_Out() // interrupt service routine triggered each 100ms by ESP32 hardware timer void IRAM_ATTR DCF77IRQ() { - xTaskNotifyFromISR(DCF77Task, xTaskGetTickCountFromISR(), eSetBits, NULL); + xTaskNotifyFromISR(DCF77Task, 0, eNoAction, NULL); portYIELD_FROM_ISR(); } +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_Out(); + } + BitsPending = false; // stop blink in display + vTaskDelete(DCF77Task); // shoud never be reached +} // dcf77_loop() + #endif // HAS_DCF77 \ No newline at end of file diff --git a/src/display.cpp b/src/display.cpp index 0c186a80..552e5b25 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -145,14 +145,14 @@ void refreshtheDisplay() { uint8_t msgWaiting; char buff[16]; // 16 chars line buffer -#if defined HAS_RTC || defined HAS_GPS +#if (defined HAS_DCF77) || (defined HAS_IF482) const char timeNosyncSymbol = '?'; -#if defined HAS_IF482 || defined HAS_DCF77 +#if (defined HAS_IF482) const char timesyncSymbol = '+'; #else const char timesyncSymbol = '*'; #endif -#endif // HAS_RTC +#endif // update counter (lines 0-1) snprintf( @@ -217,21 +217,20 @@ void refreshtheDisplay() { #ifdef HAS_LORA u8x8.setCursor(0, 6); -#if (!defined HAS_RTC) && (!defined HAS_GPS) +#if (!defined HAS_DCF77) && (!defined HAS_IF482) // update LoRa status display (line 6) u8x8.printf("%-16s", display_line6); -#else // HAS_RTC or HAS_GPS +#else // we want a time display instead LoRa status // 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 if pps line - if (second(t) % 2) + // make timestatus symbol blinking if pps line + if ((BitsPending) && (second(t) % 2)) timeState = ' '; -#endif // RTC_INT u8x8.printf("%02d:%02d:%02d%c %2d.%3s", hour(t), minute(t), second(t), timeState, day(t), printmonth[month(t)]); -#endif // HAS_RTC +#endif // update LMiC event display (line 7) u8x8.setCursor(0, 7); diff --git a/src/if482.cpp b/src/if482.cpp index b575b9b7..0d3d0bb8 100644 --- a/src/if482.cpp +++ b/src/if482.cpp @@ -103,7 +103,20 @@ int if482_init(void) { ESP_LOGE(TAG, "I2c bus busy - IF482 initialization error"); return 0; } + + xTaskCreatePinnedToCore(if482_loop, // task function + "if482loop", // name of task + 2048, // stack size of task + (void *)1, // parameter of the task + 3, // priority of the task + &IF482Task, // task handle + 0); // CPU core + + assert(IF482Task); // has if482loop task started? + // setup external interupt for active low RTC INT pin pinMode(RTC_INT, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(RTC_INT), IF482IRQ, FALLING); + return 1; } // if482_init @@ -134,6 +147,8 @@ String if482Telegram(time_t tt) { 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; } @@ -152,6 +167,8 @@ void if482_loop(void *pvParameters) { do { tt = now(); } while (t == tt); + + BitsPending = true; // start blink in display // take timestamp at moment of start of new second const TickType_t shotTime = xTaskGetTickCount() - startTime - timeOffset; @@ -169,6 +186,7 @@ void if482_loop(void *pvParameters) { vTaskDelayUntil(&wakeTime, shotTime); // sets waketime to moment of shot IF482.print(if482Telegram(now() + 1)); } + BitsPending = false; // stop blink in display vTaskDelete(IF482Task); // shoud never be reached } // if482_loop() diff --git a/src/lmic_config.h b/src/lmic_config.h index ea9c532b..a28f24a5 100644 --- a/src/lmic_config.h +++ b/src/lmic_config.h @@ -34,7 +34,7 @@ // faster or slower. This causes the transceiver to be earlier switched on, // so consuming more power. You may sharpen (reduce) this value if you are // limited on battery. -#define CLOCK_ERROR_PROCENTAGE 30 +#define CLOCK_ERROR_PROCENTAGE 3 // Set this to 1 to enable some basic debug output (using printf) about // RF settings used during transmission and reception. Set to 2 to diff --git a/src/main.cpp b/src/main.cpp index 4392c42a..ff7a0d19 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -34,7 +34,7 @@ spiloop 0 2 reads/writes data on spi interface IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer looptask 1 1 arduino core -> runs the LMIC LoRa stack -irqhandler 1 1 executes tasks triggered by irq +irqhandler 1 1 executes tasks triggered by hw irq, see table below gpsloop 1 2 reads data from GPS via serial or i2c bmeloop 1 1 reads data from BME sensor via i2c IDLE 1 0 ESP32 arduino scheduler -> runs wifi channel rotator @@ -44,7 +44,7 @@ Low priority numbers denote low priority tasks. Tasks using i2c bus all must have same priority, because using mutex semaphore (irqhandler, bmeloop) -ESP32 hardware timers +ESP32 hardware irq timers ================================ 0 triggers display refresh 1 triggers DCF77 clock signal @@ -65,6 +65,7 @@ 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 +bool volatile BitsPending = false; // DCF77 or IF482 ticker indicator hw_timer_t *sendCycle = NULL, *homeCycle = NULL; #ifdef HAS_DISPLAY @@ -97,7 +98,7 @@ void setup() { char features[100] = ""; I2Caccess = xSemaphoreCreateMutex(); // for access management of i2c bus - if ((I2Caccess) != NULL) + if (I2Caccess) xSemaphoreGive((I2Caccess)); // Flag the i2c bus available for use // disable brownout detection @@ -334,7 +335,6 @@ void setup() { #if defined HAS_DCF77 strcat_P(features, " DCF77"); - assert(dcf77_init()); #endif #if defined HAS_IF482 && defined RTC_INT @@ -417,37 +417,14 @@ void setup() { setSyncInterval(TIME_SYNC_INTERVAL_GPS * 60); #endif -#if defined HAS_IF482 && defined RTC_INT +#if defined HAS_IF482 && defined DCF_77 +#error "You may define at most one of HAS_IF482 or DCF_77" +#elif defined HAS_IF482 && defined RTC_INT ESP_LOGI(TAG, "Starting IF482 Generator..."); - xTaskCreatePinnedToCore(if482_loop, // task function - "if482loop", // name of task - 2048, // stack size of task - (void *)1, // parameter of the task - 3, // priority of the task - &IF482Task, // task handle - 0); // CPU core - - // setup external interupt for active low RTC INT pin - assert(IF482Task != NULL); // has if482loop task started? - attachInterrupt(digitalPinToInterrupt(RTC_INT), IF482IRQ, FALLING); -#endif - -#if defined HAS_DCF77 + assert(if482_init()); +#elif 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); + assert(dcf77_init()); #endif } // setup()