diff --git a/README.md b/README.md index dbe9d425..19085256 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Depending on board hardware following features are supported: - GPS (Generic serial NMEA, or Quectel L76 I2C) - Environmental sensor (Bosch BME680 I2C) - Real Time Clock (Maxim DS3231 I2C) -- IF482 time telegram generator (serial port) +- IF482 (serial) and DCF77 (gpio) time telegram generator Target platform must be selected in [platformio.ini](https://github.com/cyberman54/ESP32-Paxcounter/blob/master/platformio.ini).
Hardware dependent settings (pinout etc.) are stored in board files in /hal directory. If you want to use a ESP32 board which is not yet supported, use hal file generic.h and tailor pin mappings to your needs. Pull requests for new boards welcome.
@@ -138,6 +138,10 @@ Paxcounter generates identifiers for sniffed MAC adresses and collects them temp - Red long blink: LoRaWAN stack error - White long blink: Known Beacon detected +# Clock controller + +Paxcounter can be used to sync a clock which has DCF77 or IF482 time telegram input with an external time source. Supported external time sources are GPS time, LORAWAN network time (v1.1) or on board RTC time. The precision of the generated DCF77 / IF482 signal depends on precision of used on board time base. Supported are both external time base (e.g. pps pin of GPS chip or oscillator output of RTC chip) and ESP32 internal clock. Selection of time base and clock frequency must be given by #defines in the board's hal file, see example in [**generic.h**](src/hal/generic.h). + # Payload format You can select different payload formats in [paxcounter.conf](src/paxcounter.conf#L12): diff --git a/src/dcf77.cpp b/src/dcf77.cpp index 95c14c76..f5bd53ec 100644 --- a/src/dcf77.cpp +++ b/src/dcf77.cpp @@ -19,7 +19,8 @@ static const char TAG[] = "main"; TaskHandle_t DCF77Task; hw_timer_t *dcfCycle = NULL; -#define DCF77_FRAME_SIZE 60 +#define DCF77_FRAME_SIZE (60) +#define DCF77_PULSE_DURATION (100) // array of dcf pulses for three minutes uint8_t DCFtimeframe[DCF77_FRAME_SIZE]; @@ -43,14 +44,38 @@ int dcf77_init(void) { assert(DCF77Task); // has dcf77 task started? - // setup 100ms clock signal for DCF77 generator using esp32 hardware timer 1 - ESP_LOGD(TAG, "Starting DCF pulse..."); + // if we have hardware pps signal we use it as precise time base +#ifdef RTC_INT + +#ifndef RTC_CLK // assure we know external clock freq +#error "External clock cycle not defined in board hal file" +#endif + + // setup external interupt for active low RTC INT pin + pinMode(RTC_INT, INPUT_PULLUP); + + // setup external rtc 1Hz clock for triggering DCF77 telegram + ESP_LOGI(TAG, "Time base external clock"); + if (I2C_MUTEX_LOCK()) { + Rtc.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz); + Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeClock); + I2C_MUTEX_UNLOCK(); + } else { + ESP_LOGE(TAG, "I2c bus busy - RTC initialization error"); + return 0; // failure + } + +// if we don't have pps signal from RTC we emulate it using ESP32 hardware timer +#else +#define RTC_CLK (DCF77_PULSE_DURATION) // setup clock cycle + ESP_LOGI(TAG, "Time base ESP32 clock"); dcfCycle = timerBegin(1, 8000, true); // set 80 MHz prescaler to 1/10000 sec timerAttachInterrupt(dcfCycle, &DCF77IRQ, true); - timerAlarmWrite(dcfCycle, 1000, true); // 100ms cycle + timerAlarmWrite(dcfCycle, 10 * RTC_CLK, true); // RTC_CLK / 1sec = 100ms +#endif // wait until beginning of next second, then kick off first DCF pulse and - // start timer interrupt + // start clock signal t = tt = now(); do { @@ -58,10 +83,14 @@ int dcf77_init(void) { } while (t == tt); DCF_Out(second(tt)); + +#ifdef RTC_INT // start external clock + attachInterrupt(digitalPinToInterrupt(RTC_INT), DCF77IRQ, FALLING); +#else // start internal clock timerAlarmEnable(dcfCycle); +#endif return 1; // success - } // ifdcf77_init void generateTimeframe(time_t tt) { @@ -113,9 +142,9 @@ void generateTimeframe(time_t tt) { } // called every 100msec by hardware timer to pulse out DCF signal -void DCF_Out(uint8_t startsec) { +void DCF_Out(uint8_t startOffset) { - static uint8_t bit = startsec; + static uint8_t bit = startOffset; static uint8_t pulse = 0; if (!BitsPending) { @@ -143,7 +172,7 @@ void DCF_Out(uint8_t startsec) { set_DCF77_pin(dcf_high); break; - case 9: // last pulse before next second starts + 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) { @@ -154,31 +183,33 @@ void DCF_Out(uint8_t startsec) { }; // switch }; // if - } // DCF_Out() -// interrupt service routine triggered each 100ms by ESP32 hardware timer -void IRAM_ATTR DCF77IRQ() { - xTaskNotifyFromISR(DCF77Task, 0, eNoAction, NULL); - portYIELD_FROM_ISR(); -} - 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 - NULL, + 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...) - DCF_Out(0); - } - BitsPending = false; // stop blink in display, should never be reached - vTaskDelete(DCF77Task); +#if (!defined RTC_INT) || (RTC_CLK == DCF77_PULSE_DURATION) + DCF_Out(0); // we don't need clock rescaling + +#else // we need clock rescaling by software timer + for (uint8_t i = 1; i <= RTC_CLK / DCF77_PULSE_DURATION; i++) { + DCF_Out(0); + vTaskDelayUntil(&wakeTime, pdMS_TO_TICKS(DCF77_PULSE_DURATION)); + } // for +#endif + + } // for } // dcf77_loop() // helper function to convert decimal to bcd digit @@ -217,4 +248,10 @@ void set_DCF77_pin(dcf_pinstate state) { } // switch } // DCF77_pulse +// interrupt service routine triggered by external interrupt or internal 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/hal/generic.h b/src/hal/generic.h index 8f2c2da4..5db463a0 100644 --- a/src/hal/generic.h +++ b/src/hal/generic.h @@ -51,6 +51,7 @@ // Pins for on board DS3231 RTC chip #define HAS_RTC MY_OLED_SDA, MY_OLED_SCL // SDA, SCL #define RTC_INT GPIO_NUM_34 // interrupt input from rtc +#define RTC_CLK (1000) // frequency of RTC clock signal in ms // Settings for IF482 interface #define HAS_IF482 9600, SERIAL_7E1, GPIO_NUM_12, GPIO_NUM_14 // IF482 serial port parameters diff --git a/src/if482.cpp b/src/if482.cpp index 19fd75af..307de066 100644 --- a/src/if482.cpp +++ b/src/if482.cpp @@ -94,19 +94,7 @@ int if482_init(void) { // setup external interupt for active low RTC INT pin pinMode(RTC_INT, INPUT_PULLUP); - // open serial interface - IF482.begin(HAS_IF482); - - // use external rtc 1Hz clock for triggering IF482 telegram - if (I2C_MUTEX_LOCK()) { - Rtc.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz); - Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeClock); - I2C_MUTEX_UNLOCK(); - } else { - ESP_LOGE(TAG, "I2c bus busy - IF482 initialization error"); - return 0; - } - + // start if482 serial output feed task xTaskCreatePinnedToCore(if482_loop, // task function "if482loop", // name of task 2048, // stack size of task @@ -116,13 +104,42 @@ int if482_init(void) { 0); // CPU core assert(IF482Task); // has if482loop task started? + + // open serial interface + IF482.begin(HAS_IF482); + + // if we have hardware pps signal we use it as precise time base +#ifdef RTC_INT +// assure we know clock freq +#ifndef RTC_CLK +#error "No RTC clock cycle defined in board hal file" +#endif + // use external rtc 1Hz clock for triggering IF482 telegram + if (I2C_MUTEX_LOCK()) { + Rtc.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz); + Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeClock); + I2C_MUTEX_UNLOCK(); + } else { + ESP_LOGE(TAG, "I2c bus busy - IF482 initialization error"); + return 0; // failure + } attachInterrupt(digitalPinToInterrupt(RTC_INT), IF482IRQ, FALLING); - return 1; +// no RTC, thus we use less precise ESP32 hardware timer +#else + // setup 1000ms clock signal for IF482 generator using esp32 hardware timer 1 + ESP_LOGD(TAG, "Starting IF482 pulse..."); + dcfCycle = timerBegin(1, 8000, true); // set 80 MHz prescaler to 1/10000 sec + timerAttachInterrupt(dcfCycle, &IF482IRQ, true); + timerAlarmWrite(dcfCycle, 10000, true); // 1000ms cycle + timerAlarmEnable(dcfCycle); +#endif + + return 1; // success } // if482_init -String if482Telegram(time_t tt) { +String IF482_Out(time_t tt) { time_t t = myTZ.toLocal(tt); char mon, buf[14], out[17]; @@ -184,10 +201,8 @@ void if482_loop(void *pvParameters) { // now we're synced to start of second tt and wait // until it's time to start transmit telegram for tt+1 vTaskDelayUntil(&wakeTime, shotTime); // sets waketime to moment of shot - IF482.print(if482Telegram(now() + 1)); + IF482.print(IF482_Out(now() + 1)); } - BitsPending = false; // stop blink in display - vTaskDelete(IF482Task); // shoud never be reached } // if482_loop() // interrupt service routine triggered by RTC 1Hz precise clock diff --git a/src/main.cpp b/src/main.cpp index a654c5a5..288c5e61 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -418,7 +418,7 @@ void setup() { #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) +#elif defined HAS_IF482 ESP_LOGI(TAG, "Starting IF482 Generator..."); assert(if482_init()); #elif defined HAS_DCF77