diff --git a/README.md b/README.md index dbe9d425..e44aafc1 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. Use case of this function is to have paxcounter hardware integrated in clocks, and use it for both counting of pax and controlling the clock. Supported external time sources are GPS time, LORAWAN network time (v1.1) and on board RTC time. Precision of the synthetic DCF77 signal depends on precision of on board available time base. Supported are both external time base (e.g. timepulse pin of GPS chip or oscillator output of RTC chip) and internal ESP32 hardware timer. Selection of time base and clock frequency is done 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/include/cyclic.h b/include/cyclic.h index 13e9419c..007c09d8 100644 --- a/include/cyclic.h +++ b/include/cyclic.h @@ -11,7 +11,6 @@ #include "bme680mems.h" #endif -// Needed for RTC time sync if RTC present on board #ifdef HAS_RTC #include "rtctime.h" #endif diff --git a/include/dcf77.h b/include/dcf77.h new file mode 100644 index 00000000..cd06ea5a --- /dev/null +++ b/include/dcf77.h @@ -0,0 +1,18 @@ +#ifndef _DCF77_H +#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 set_DCF77_pin(dcf_pinstate state); +uint8_t dec2bcd(uint8_t dec, uint8_t startpos, uint8_t endpos, uint8_t pArray[]); + +#endif \ No newline at end of file diff --git a/include/globals.h b/include/globals.h index 6aaad2c7..8755b1f0 100644 --- a/include/globals.h +++ b/include/globals.h @@ -92,21 +92,25 @@ typedef struct { float gas; // raw gas sensor signal } bmeStatus_t; +enum sendprio_t { prio_low, prio_normal, prio_high }; + // global variables 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 hw_timer_t *channelSwitch, *sendCycle, *displaytimer; +extern hw_timer_t *sendCycle, *displaytimer; extern SemaphoreHandle_t I2Caccess; +extern bool volatile BitsPending; extern std::set, Mallocator> macs; extern std::array::iterator it; extern std::array beacons; -extern TaskHandle_t irqHandlerTask, wifiSwitchTask; -extern Timezone myTZ; // make Timezone myTZ globally available +extern TaskHandle_t irqHandlerTask; +extern TimerHandle_t WifiChanTimer; +extern Timezone myTZ; // application includes #include "led.h" @@ -149,4 +153,8 @@ extern Timezone myTZ; // make Timezone myTZ globally available #include "if482.h" #endif +#ifdef HAS_DCF77 +#include "dcf77.h" +#endif + #endif \ No newline at end of file diff --git a/include/gpsread.h b/include/gpsread.h index 3cd4a931..9fccefb4 100644 --- a/include/gpsread.h +++ b/include/gpsread.h @@ -16,5 +16,6 @@ int gps_init(void); void gps_read(void); void gps_loop(void *pvParameters); time_t get_gpstime(void); +int gps_config(); #endif \ No newline at end of file diff --git a/include/if482.h b/include/if482.h index 7ceca1c5..def3a2fe 100644 --- a/include/if482.h +++ b/include/if482.h @@ -2,12 +2,9 @@ #define _IF482_H #include "globals.h" -#include "irqhandler.h" - -extern TaskHandle_t IF482Task; +#include "rtctime.h" int if482_init(void); void if482_loop(void *pvParameters); -void IRAM_ATTR IF482IRQ(void); #endif \ No newline at end of file diff --git a/include/irqhandler.h b/include/irqhandler.h index 45d58fee..cc8d15ac 100644 --- a/include/irqhandler.h +++ b/include/irqhandler.h @@ -11,7 +11,6 @@ #include "senddata.h" void irqHandler(void *pvParameters); -void IRAM_ATTR ChannelSwitchIRQ(); void IRAM_ATTR homeCycleIRQ(); void IRAM_ATTR SendCycleIRQ(); diff --git a/include/lorawan.h b/include/lorawan.h index 05aba7cd..4beee6c4 100644 --- a/include/lorawan.h +++ b/include/lorawan.h @@ -33,7 +33,7 @@ void os_getDevEui(u1_t *buf); void showLoraKeys(void); void switch_lora(uint8_t sf, uint8_t tx); void lora_send(osjob_t *job); -void lora_enqueuedata(MessageBuffer_t *message); +void lora_enqueuedata(MessageBuffer_t *message, sendprio_t prio); void lora_queuereset(void); void lora_housekeeping(void); void user_request_network_time_callback(void *pVoidUserUTCTime, diff --git a/include/payload.h b/include/payload.h index 24b5ece6..686a5b62 100644 --- a/include/payload.h +++ b/include/payload.h @@ -82,7 +82,7 @@ private: uint8_t cursor; #else -#error "No valid payload converter defined" +#error No valid payload converter defined! #endif }; diff --git a/include/rtctime.h b/include/rtctime.h index 80f0ca29..8a98f241 100644 --- a/include/rtctime.h +++ b/include/rtctime.h @@ -11,11 +11,18 @@ extern RtcDS3231 Rtc; // make RTC instance globally available +extern TaskHandle_t ClockTask; +extern hw_timer_t *clockCycle; + 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(); +int timepulse_init(uint32_t pps_freq); +void timepulse_start(); +uint8_t sync_clock(time_t t); #endif // _RTCTIME_H \ No newline at end of file diff --git a/include/senddata.h b/include/senddata.h index da368f71..82cf4581 100644 --- a/include/senddata.h +++ b/include/senddata.h @@ -5,7 +5,7 @@ #include "lorawan.h" #include "cyclic.h" -void SendPayload(uint8_t port); +void SendPayload(uint8_t port, sendprio_t prio); void sendCounter(void); void checkSendQueues(void); void flushQueues(); diff --git a/include/spislave.h b/include/spislave.h index f459b2fd..74b77db0 100644 --- a/include/spislave.h +++ b/include/spislave.h @@ -28,7 +28,7 @@ licenses. Refer to LICENSE.txt file in repository for more details. esp_err_t spi_init(); -void spi_enqueuedata(MessageBuffer_t *message); +void spi_enqueuedata(MessageBuffer_t *message, sendprio_t prio); void spi_queuereset(); void spi_housekeeping(); diff --git a/include/wifiscan.h b/include/wifiscan.h index 0e6962a2..0a6e7b6c 100644 --- a/include/wifiscan.h +++ b/include/wifiscan.h @@ -9,6 +9,6 @@ void wifi_sniffer_init(void); void IRAM_ATTR wifi_sniffer_packet_handler(void *buff, wifi_promiscuous_pkt_type_t type); -void switchWifiChannel(void * parameter); +void switchWifiChannel(TimerHandle_t xTimer); #endif \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 195b221d..233837e9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,10 +30,10 @@ 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.143 +release_version = 1.7.2 ; 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,11 +45,12 @@ 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 +; NeoGPS^4.2.9 lib_deps_rtc = RTC@^2.3.0 lib_deps_basic = @@ -233,7 +234,7 @@ lib_deps = ${common.lib_deps_basic} ${common.lib_deps_lora} ${common.lib_deps_gps} -; ${common.lib_deps_display} + ${common.lib_deps_display} build_flags = ${common.build_flags_all} -mfix-esp32-psram-cache-issue diff --git a/src/button.cpp b/src/button.cpp index 29eff252..d7d5c2a4 100644 --- a/src/button.cpp +++ b/src/button.cpp @@ -10,6 +10,6 @@ void readButton() { ESP_LOGI(TAG, "Button pressed"); payload.reset(); payload.addButton(0x01); - SendPayload(BUTTONPORT); + SendPayload(BUTTONPORT, prio_normal); } #endif \ No newline at end of file diff --git a/src/cyclic.cpp b/src/cyclic.cpp index a26bf6f8..4b921e5a 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -41,7 +41,7 @@ void doHousekeeping() { if ((millis() >= nextRTCTimeSync) && (timeStatus() == timeSet)) { nextRTCTimeSync = millis() + TIME_WRITE_INTERVAL_RTC * 60000; // set up next time sync period - if (!set_rtctime(now())) // epoch time + if (!set_rtctime(now())) // epoch time ESP_LOGE(TAG, "RTC set time failure"); else ESP_LOGI(TAG, "RTC time updated"); @@ -49,9 +49,6 @@ void doHousekeeping() { #endif // task storage debugging // - ESP_LOGD(TAG, "Wifiloop %d bytes left | Taskstate = %d", - uxTaskGetStackHighWaterMark(wifiSwitchTask), - eTaskGetState(wifiSwitchTask)); ESP_LOGD(TAG, "IRQhandler %d bytes left | Taskstate = %d", uxTaskGetStackHighWaterMark(irqHandlerTask), eTaskGetState(irqHandlerTask)); @@ -63,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, "Clockloop %d bytes left | Taskstate = %d", + uxTaskGetStackHighWaterMark(ClockTask), eTaskGetState(ClockTask)); +#endif #if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) ESP_LOGD(TAG, "LEDloop %d bytes left | Taskstate = %d", @@ -88,9 +89,9 @@ void doHousekeeping() { "Memory full, counter cleared (heap low water mark = %d Bytes / " "free heap = %d bytes)", ESP.getMinFreeHeap(), ESP.getFreeHeap()); - SendPayload(COUNTERPORT); // send data before clearing counters - reset_counters(); // clear macs container and reset all counters - get_salt(); // get new salt for salting hashes + 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 if (ESP.getMinFreeHeap() <= MEM_LOW) // check again do_reset(); // memory leak, reset device @@ -100,9 +101,9 @@ void doHousekeeping() { #ifdef BOARD_HAS_PSRAM if (ESP.getMinFreePsram() <= MEM_LOW) { ESP_LOGI(TAG, "PSRAM full, counter cleared"); - SendPayload(COUNTERPORT); // send data before clearing counters - reset_counters(); // clear macs container and reset all counters - get_salt(); // get new salt for salting hashes + 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 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..8be4ed61 --- /dev/null +++ b/src/dcf77.cpp @@ -0,0 +1,233 @@ +/* +// Emulate a DCF77 radio receiver +// +// a nice & free logic test program for DCF77 can be found here: +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"; + +#define DCF77_FRAME_SIZE (60) +#define DCF77_PULSE_DURATION (100) + +// select internal / external clock +#if defined RTC_INT && defined RTC_CLK +#define PPS RTC_CLK +#elif defined GPS_INT && defined GPS_CLK +#define PPS GPS_CLK +#else +#define PPS DCF77_PULSE_DURATION +#endif + +// array of dcf pulses for three minutes +uint8_t DCFtimeframe[DCF77_FRAME_SIZE]; + +// initialize and configure DCF77 output +int dcf77_init(void) { + + BitsPending = false; + + pinMode(HAS_DCF77, OUTPUT); + set_DCF77_pin(dcf_low); + + 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? + + timepulse_init(PPS); // setup pulse + DCF_Out(sync_clock(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) { + + static uint8_t bit = startOffset; + static uint8_t pulse = 0; +#ifdef TIME_SYNC_INTERVAL_DCF + static uint32_t nextDCFsync = millis() + TIME_SYNC_INTERVAL_DCF * 60000; +#endif + + 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); + // start blinking symbol on display and kick off timer + BitsPending = true; + } else + return; + } + + // ticker out current DCF frame + if (BitsPending) { + switch (pulse++) { + + case 0: // start of second -> start of timeframe for logic signal + if (DCFtimeframe[bit] != dcf_off) + set_DCF77_pin(dcf_low); + break; + + case 1: // 100ms after start of second -> end of timeframe for logic 0 + if (DCFtimeframe[bit] == dcf_zero) + set_DCF77_pin(dcf_high); + break; + + case 2: // 200ms after start of second -> end of timeframe for logic 1 + set_DCF77_pin(dcf_high); + break; + + 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) + { + 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(now()); // in second 58,90x -> waiting for second 59 + nextDCFsync = millis() + TIME_SYNC_INTERVAL_DCF * + 60000; // set up next time sync period + } +#endif + }; + 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)); + } + +#elif (PPS < DCF77_PULSE_DURATION) // we need downclocking, not yet implemented +#error Timepulse is too low for DCF77! +#endif + } // for +} // dcf77_loop() + +// helper function to convert decimal to bcd digit +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 generateTimeframe(time_t tt) { + + uint8_t ParityCount; + 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; + // 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 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] = DCFtimeframe[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) { + case dcf_low: +#ifdef DCF77_ACTIVE_LOW + digitalWrite(HAS_DCF77, HIGH); +#else + digitalWrite(HAS_DCF77, LOW); +#endif + break; + case dcf_high: +#ifdef DCF77_ACTIVE_LOW + digitalWrite(HAS_DCF77, LOW); +#else + digitalWrite(HAS_DCF77, HIGH); +#endif + break; + } // switch +} // DCF77_pulse + +#endif // HAS_DCF77 \ No newline at end of file diff --git a/src/display.cpp b/src/display.cpp index 280528d1..75816b37 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -42,8 +42,8 @@ const char lora_datarate[] = {"121110090807FSNA"}; #endif // helper arry for converting month values to text -char *printmonth[] = {"xxx", "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; +const char *printmonth[] = {"xxx", "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; uint8_t volatile DisplayState = 0; @@ -145,11 +145,13 @@ void refreshtheDisplay() { uint8_t msgWaiting; char buff[16]; // 16 chars line buffer +#if (defined HAS_DCF77) || (defined HAS_IF482) const char timeNosyncSymbol = '?'; -#ifdef HAS_IF482 +#if (defined HAS_IF482) const char timesyncSymbol = '+'; #else const char timesyncSymbol = '*'; +#endif #endif // update counter (lines 0-1) @@ -214,23 +216,21 @@ void refreshtheDisplay() { u8x8.printf("%4dKB", getFreeRAM() / 1024); #ifdef HAS_LORA - u8x8.setCursor(0, 6); -#ifndef HAS_RTC +#if (!defined HAS_DCF77) && (!defined HAS_IF482) // update LoRa status display (line 6) u8x8.printf("%-16s", display_line6); -#else +#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 (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/gpsread.cpp b/src/gpsread.cpp index 27d12ca2..6e094faf 100644 --- a/src/gpsread.cpp +++ b/src/gpsread.cpp @@ -18,6 +18,11 @@ int gps_init(void) { int ret = 1; + if (!gps_config()) { + ESP_LOGE(TAG, "GPS chip initializiation error"); + return 0; + } + #if defined GPS_SERIAL GPS_Serial.begin(GPS_SERIAL); ESP_LOGI(TAG, "Using serial GPS"); @@ -41,6 +46,21 @@ int gps_init(void) { return ret; } // gps_init() +// detect gps chipset type and configure it with device specific settings +int gps_config() { + int rslt = 1; // success +#if defined GPS_SERIAL + + /* to come */ + +#elif defined GPS_I2C + + /* to come */ + +#endif + return rslt; +} + // read GPS data and cast to global struct void gps_read() { gps_status.latitude = (int32_t)(gps.location.lat() * 1e6); @@ -73,9 +93,8 @@ 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", 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)); } else { ESP_LOGW(TAG, "GPS has no confident time"); } diff --git a/src/hal/generic.h b/src/hal/generic.h index 2b34967f..6a787fff 100644 --- a/src/hal/generic.h +++ b/src/hal/generic.h @@ -40,20 +40,28 @@ #define BOARD_HAS_PSRAM // use extra 4MB extern RAM -#define HAS_GPS 1 // use if board has GPS -#define GPS_SERIAL 9600, SERIAL_8N1, GPIO_NUM_17, GPIO_NUM_16 // UBlox NEO 6M or 7M with default configuration +// GPS settings +#define HAS_GPS 1 // use on board GPS +#define GPS_SERIAL 9600, SERIAL_8N1, GPIO_NUM_12, GPIO_NUM_15 // UBlox NEO 6M +#define GPS_INT GPIO_NUM_34 // 30ns accurary timepulse, to be external wired on pcb: NEO 6M Pin#3 -> GPIO34 +#define GPS_CLK (1000) // pulse length 100ms, accuracy +/- 3 *e-8 [nanoseconds] = 0,95sec / year // Pins for I2C interface of OLED Display #define MY_OLED_SDA (4) #define MY_OLED_SCL (15) #define MY_OLED_RST (16) -// Pins for on board DS3231 RTC chip +// Settings 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_INT GPIO_NUM_34 // timepulse with accuracy +/- 2*e-6 [microseconds] = 0,1728sec / day +#define RTC_CLK (1000) // pulse length 1000ms // Settings for IF482 interface -#define HAS_IF482 9600, SERIAL_7E1, GPIO_NUM_12, GPIO_NUM_14 // IF482 serial port parameters +//#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_1 +#define DCF77_ACTIVE_LOW 1 // Pins for LORA chip SPI interface, reset line and interrupt lines #define LORA_SCK (5) diff --git a/src/hal/ttgobeam.h b/src/hal/ttgobeam.h index 8dd12bd4..f812e3c9 100644 --- a/src/hal/ttgobeam.h +++ b/src/hal/ttgobeam.h @@ -19,8 +19,12 @@ #define HAS_BUTTON GPIO_NUM_39 // on board button (next to reset) #define HAS_BATTERY_PROBE ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7 #define BATT_FACTOR 2 // voltage divider 100k/100k on board + +// GPS settings #define HAS_GPS 1 // use on board GPS -#define GPS_SERIAL 9600, SERIAL_8N1, GPIO_NUM_12, GPIO_NUM_15 // UBlox NEO 6M or 7M with default configuration +#define GPS_SERIAL 9600, SERIAL_8N1, GPIO_NUM_12, GPIO_NUM_15 // UBlox NEO 6M +//#define GPS_INT GPIO_NUM_34 // 30ns accurary timepulse, to be external wired on pcb: NEO 6M Pin#3 -> GPIO34 +//#define GPS_CLK (1000) // pulse length 100ms, accuracy +/- 3 *e-8 [nanoseconds] = 0,95sec / year // enable only if device has these sensors, otherwise comment these lines // BME680 sensor on I2C bus @@ -34,6 +38,12 @@ //#define MY_OLED_RST U8X8_PIN_NONE //#define DISPLAY_FLIP 1 // use if display is rotated +// Settings for DCF77 interface +//#define HAS_DCF77 GPIO_NUM_13 + +// Settings for IF482 interface +//#define HAS_IF482 9600, SERIAL_7E1, GPIO_NUM_12, GPIO_NUM_14 // IF482 serial port parameters + // user defined sensors (if connected) //#define HAS_SENSORS 1 // comment out if device has user defined sensors diff --git a/src/hal/ttgofox.h b/src/hal/ttgofox.h index f7cd3033..3d29d5f3 100644 --- a/src/hal/ttgofox.h +++ b/src/hal/ttgofox.h @@ -20,16 +20,21 @@ #define MY_OLED_SCL (22) #define MY_OLED_RST U8X8_PIN_NONE -// Pins for on board DS3231 RTC chip +// Settings 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_INT GPIO_NUM_34 // timepulse with accuracy +/- 2*e-6 [microseconds] = 0,1728sec / day +//#define RTC_CLK (1000) // pulse length 1000ms // Settings for IF482 interface -#define HAS_IF482 9600, SERIAL_7E1, GPIO_NUM_12, GPIO_NUM_14 // IF482 serial port parameters +//#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_14 +//#define DCF77_ACTIVE_LOW 1 // Settings for external GPS chip -#define HAS_GPS 1 // use on board GPS -#define GPS_SERIAL 9600, SERIAL_8N1, GPIO_NUM_17, GPIO_NUM_16 // UBlox NEO 6M or 7M with default configuration +//#define HAS_GPS 1 // use on board GPS +//#define GPS_SERIAL 9600, SERIAL_8N1, GPIO_NUM_17, GPIO_NUM_16 // UBlox NEO 6M or 7M with default configuration // Pins for LORA chip SPI interface, reset line and interrupt lines #define LORA_SCK (5) diff --git a/src/if482.cpp b/src/if482.cpp index b575b9b7..97aff8c4 100644 --- a/src/if482.cpp +++ b/src/if482.cpp @@ -1,5 +1,3 @@ -#if defined HAS_IF482 && defined RTC_INT - /* NOTE: The IF482 Generator needs an high precise 1 Hz clock signal which cannot be acquired in suitable precision on the ESP32 SoC itself. Additional clocking @@ -79,42 +77,58 @@ 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"; -TaskHandle_t IF482Task; +#define IF482_FRAME_SIZE (17) +#define IF482_PULSE_DURATION (1000) + +// select internal / external clock +#if defined RTC_INT && defined RTC_CLK +#define PPS RTC_CLK +#elif defined GPS_INT && defined GPS_CLK +#define PPS GPS_CLK +#else +#define PPS IF482_PULSE_DURATION +#endif HardwareSerial IF482(2); // use UART #2 (note: #1 may be in use for serial GPS) -// initialize and configure GPS +// initialize and configure IF482 Generator int if482_init(void) { // 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; - } - pinMode(RTC_INT, INPUT_PULLUP); - return 1; + // 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 + 3, // priority of the task + &ClockTask, // task handle + 0); // CPU core + assert(ClockTask); // has clock task started? + + timepulse_init(PPS); // setup pulse + timepulse_start(); // start pulse + + return 1; // success } // if482_init -String if482Telegram(time_t tt) { +String IF482_Out(time_t tt) { time_t t = myTZ.toLocal(tt); - - char mon; - char buf[14] = "000000F000000"; - char out[17]; + char mon, buf[14], out[IF482_FRAME_SIZE]; switch (timeStatus()) { // indicates if time has been set and recently synced case timeSet: // time is set and is synced @@ -128,12 +142,16 @@ String if482Telegram(time_t tt) { break; } // switch - if ((timeStatus() == timeSet) || - (timeStatus() == timeNeedsSync)) // do we have valid time? - snprintf(buf, sizeof buf, "%02u%02u%02u%1u%02u%02u%02u", year(t) - 2000, + // do we have confident time/date? + if ((timeStatus() == timeSet) || (timeStatus() == timeNeedsSync)) + 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 - snprintf(out, sizeof out, "O%cL%s\r", mon, buf); + // output IF482 telegram + snprintf(out, sizeof(out), "O%cL%s\r", mon, buf); + ESP_LOGD(TAG, "IF482 = %s", out); return out; } @@ -142,16 +160,12 @@ void if482_loop(void *pvParameters) { configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check TickType_t wakeTime; - time_t t, tt; const TickType_t timeOffset = pdMS_TO_TICKS(IF482_OFFSET); // duration of telegram transmit const TickType_t startTime = xTaskGetTickCount(); // now - // wait until begin of a new second - t = tt = now(); - do { - tt = now(); - } while (t == tt); + sync_clock(now()); // wait until begin of a new second + BitsPending = true; // start blink in display // take timestamp at moment of start of new second const TickType_t shotTime = xTaskGetTickCount() - startTime - timeOffset; @@ -164,18 +178,22 @@ void if482_loop(void *pvParameters) { &wakeTime, // receives moment of call from isr portMAX_DELAY); // wait forever (missing error handling here...) - // now we're synced to start of second tt and wait - // until it's time to start transmit telegram for tt+1 +// 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, shotTime); // sets waketime to moment of shot - IF482.print(if482Telegram(now() + 1)); + 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, shotTime); // sets waketime to moment of shot + IF482.print(IF482_Out(now() + 1)); + } + +#elif (PPS < IF482_PULSE_DURATION) // we need downclocking, not yet implemented +#error Timepulse is too low for IF482! +#endif } - vTaskDelete(IF482Task); // shoud never be reached } // if482_loop() -// interrupt service routine triggered by RTC 1Hz precise clock -void IRAM_ATTR IF482IRQ() { - xTaskNotifyFromISR(IF482Task, xTaskGetTickCountFromISR(), eSetBits, NULL); - portYIELD_FROM_ISR(); -} - #endif // HAS_IF482 \ No newline at end of file diff --git a/src/irqhandler.cpp b/src/irqhandler.cpp index f79e035e..fbbf06fa 100644 --- a/src/irqhandler.cpp +++ b/src/irqhandler.cpp @@ -44,11 +44,6 @@ void irqHandler(void *pvParameters) { // esp32 hardware timer triggered interrupt service routines // they notify the irq handler task -void IRAM_ATTR ChannelSwitchIRQ() { - xTaskNotifyGive(wifiSwitchTask); - portYIELD_FROM_ISR(); -} - void IRAM_ATTR homeCycleIRQ() { xTaskNotifyFromISR(irqHandlerTask, CYCLIC_IRQ, eSetBits, NULL); portYIELD_FROM_ISR(); 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/lorawan.cpp b/src/lorawan.cpp index 77c08c76..bc8565de 100644 --- a/src/lorawan.cpp +++ b/src/lorawan.cpp @@ -358,6 +358,7 @@ esp_err_t lora_stack_init() { #ifndef HAS_LORA return ESP_OK; // continue main program #else + assert(SEND_QUEUE_SIZE); LoraSendQueue = xQueueCreate(SEND_QUEUE_SIZE, sizeof(MessageBuffer_t)); if (LoraSendQueue == 0) { ESP_LOGE(TAG, "Could not create LORA send queue. Aborting."); @@ -397,11 +398,20 @@ esp_err_t lora_stack_init() { #endif } -void lora_enqueuedata(MessageBuffer_t *message) { +void lora_enqueuedata(MessageBuffer_t *message, sendprio_t prio) { // enqueue message in LORA send queue #ifdef HAS_LORA - BaseType_t ret = - xQueueSendToBack(LoraSendQueue, (void *)message, (TickType_t)0); + BaseType_t ret; + switch (prio) { + case prio_high: + ret = xQueueSendToFront(LoraSendQueue, (void *)message, (TickType_t)0); + break; + case prio_low: + case prio_normal: + default: + ret = xQueueSendToBack(LoraSendQueue, (void *)message, (TickType_t)0); + break; + } if (ret == pdTRUE) { ESP_LOGI(TAG, "%d bytes enqueued for LORA interface", message->MessageSize); } else { diff --git a/src/macsniff.cpp b/src/macsniff.cpp index b1e5ffcc..5cc02c64 100644 --- a/src/macsniff.cpp +++ b/src/macsniff.cpp @@ -107,7 +107,7 @@ bool mac_add(uint8_t *paddr, int8_t rssi, bool sniff_type) { #endif payload.reset(); payload.addAlarm(rssi, beaconID); - SendPayload(BEACONPORT); + SendPayload(BEACONPORT, prio_high); } }; diff --git a/src/main.cpp b/src/main.cpp index 5734f97f..b2ce3336 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,33 +27,33 @@ Uused tasks and timers: Task Core Prio Purpose ==================================================================================== -wifiloop 0 4 rotates wifi channels 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 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 +IDLE 1 0 ESP32 arduino scheduler -> runs wifi channel rotator 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 Wifi channel switch + 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 */ @@ -65,9 +65,14 @@ 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 -TaskHandle_t irqHandlerTask, wifiSwitchTask; +bool volatile BitsPending = false; // DCF77 or IF482 ticker indicator + +hw_timer_t *sendCycle = NULL, *homeCycle = NULL; +#ifdef HAS_DISPLAY +hw_timer_t *displaytimer = NULL; +#endif + +TaskHandle_t irqHandlerTask; SemaphoreHandle_t I2Caccess; // container holding unique MAC address hashes with Memory Alloctor using PSRAM, @@ -93,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 @@ -305,11 +310,6 @@ void setup() { timerAttachInterrupt(homeCycle, &homeCycleIRQ, true); timerAlarmWrite(homeCycle, HOMECYCLE * 10000, true); - // setup channel rotation trigger IRQ using esp32 hardware timer 1 - channelSwitch = timerBegin(1, 800, true); - timerAttachInterrupt(channelSwitch, &ChannelSwitchIRQ, true); - timerAlarmWrite(channelSwitch, cfg.wifichancycle * 1000, true); - // show payload encoder #if PAYLOAD_ENCODER == 1 strcat_P(features, " PLAIN"); @@ -333,6 +333,14 @@ void setup() { setSyncInterval(TIME_SYNC_INTERVAL_RTC * 60); #endif // HAS_RTC +#if defined HAS_DCF77 + strcat_P(features, " DCF77"); +#endif + +#if (defined HAS_IF482) && (defined RTC_INT) + strcat_P(features, " IF482"); +#endif + // show compiled features ESP_LOGI(TAG, "Features:%s", features); @@ -343,7 +351,7 @@ void setup() { #endif #endif - // start wifi in monitor mode and start channel rotation task on core 0 + // start wifi in monitor mode and start channel rotation timer ESP_LOGI(TAG, "Starting Wifi..."); wifi_sniffer_init(); // initialize salt value using esp_random() called by random() in @@ -361,16 +369,6 @@ void setup() { &irqHandlerTask, // task handle 1); // CPU core - // start wifi channel rotation task - ESP_LOGI(TAG, "Starting Wifi Channel rotation..."); - xTaskCreatePinnedToCore(switchWifiChannel, // task function - "wifiloop", // name of task - 2048, // stack size of task - NULL, // parameter of the task - 4, // priority of the task - &wifiSwitchTask, // task handle - 0); // CPU core - // initialize bme #ifdef HAS_BME strcat_P(features, " BME"); @@ -394,7 +392,6 @@ void setup() { #endif timerAlarmEnable(sendCycle); timerAlarmEnable(homeCycle); - timerAlarmEnable(channelSwitch); // start button interrupt #ifdef HAS_BUTTON @@ -419,21 +416,12 @@ void setup() { setSyncInterval(TIME_SYNC_INTERVAL_GPS * 60); #endif -#if defined HAS_IF482 && defined RTC_INT - strcat_P(features, " IF482"); - assert(if482_init()); +#ifdef HAS_IF482 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); + assert(if482_init()); +#elif defined HAS_DCF77 + ESP_LOGI(TAG, "Starting DCF77 Generator..."); + assert(dcf77_init()); #endif } // setup() diff --git a/src/paxcounter.conf b/src/paxcounter.conf index 1c3c5618..1b6d6c3a 100644 --- a/src/paxcounter.conf +++ b/src/paxcounter.conf @@ -47,7 +47,7 @@ #define PAYLOAD_BUFFER_SIZE 51 // maximum size of payload block per transmit #define LORASFDEFAULT 9 // 7 ... 12 SF, according to LoRaWAN specs #define MAXLORARETRY 500 // maximum count of TX retries if LoRa busy -#define SEND_QUEUE_SIZE 10 // maximum number of messages in payload send queue +#define SEND_QUEUE_SIZE 10 // maximum number of messages in payload send queue [1 = no queue] // Ports on which the device sends and listenes on LoRaWAN and SPI #define COUNTERPORT 1 // Port on which device sends counts @@ -85,8 +85,9 @@ #define TIME_SYNC_INTERVAL_GPS 60 // sync time each .. minutes from source GPS [default = 60], 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 IF482_OFFSET 984 // 1sec minus IF482 serial transmit time [ms]: e.g. 9 bits * 17 bytes * 1/9600 bps = 16ms +//#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 +#define IF482_OFFSET 16 // IF482 serial transmit time [ms]: e.g. 9 bits * 17 bytes * 1/9600 bps = 16ms // 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 diff --git a/src/payload.cpp b/src/payload.cpp index 71534a24..eebe88d7 100644 --- a/src/payload.cpp +++ b/src/payload.cpp @@ -437,5 +437,5 @@ void PayloadConvert::addButton(uint8_t value) { } #else -#error "No valid payload converter defined" +#error No valid payload converter defined! #endif \ No newline at end of file diff --git a/src/rcommand.cpp b/src/rcommand.cpp index 3b6bccc6..038db7db 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -67,8 +67,8 @@ void set_sendcycle(uint8_t val[]) { void set_wifichancycle(uint8_t val[]) { cfg.wifichancycle = val[0]; - // update channel rotation interrupt - timerAlarmWrite(channelSwitch, cfg.wifichancycle * 10000, true); + // update Wifi channel rotation timer period + xTimerChangePeriod(WifiChanTimer, pdMS_TO_TICKS(cfg.wifichancycle * 10), 100 ); ESP_LOGI(TAG, "Remote command: set Wifi channel switch interval to %.1f seconds", @@ -233,7 +233,7 @@ void get_config(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: get device configuration"); payload.reset(); payload.addConfig(cfg); - SendPayload(CONFIGPORT); + SendPayload(CONFIGPORT, prio_high); }; void get_status(uint8_t val[]) { @@ -247,7 +247,7 @@ void get_status(uint8_t val[]) { payload.addStatus(voltage, uptime() / 1000, temperatureRead(), getFreeRAM(), rtc_get_reset_reason(0), rtc_get_reset_reason(1)); - SendPayload(STATUSPORT); + SendPayload(STATUSPORT, prio_high); }; void get_gps(uint8_t val[]) { @@ -256,7 +256,7 @@ void get_gps(uint8_t val[]) { gps_read(); payload.reset(); payload.addGPS(gps_status); - SendPayload(GPSPORT); + SendPayload(GPSPORT, prio_high); #else ESP_LOGW(TAG, "GPS function not supported"); #endif @@ -267,7 +267,7 @@ void get_bme(uint8_t val[]) { #ifdef HAS_BME payload.reset(); payload.addBME(bme_status); - SendPayload(BMEPORT); + SendPayload(BMEPORT, prio_high); #else ESP_LOGW(TAG, "BME680 sensor not supported"); #endif diff --git a/src/rtctime.cpp b/src/rtctime.cpp index 4f299c8e..d59f574b 100644 --- a/src/rtctime.cpp +++ b/src/rtctime.cpp @@ -1,10 +1,13 @@ -#ifdef HAS_RTC - #include "rtctime.h" // Local logging tag static const char TAG[] = "main"; +TaskHandle_t ClockTask; +hw_timer_t *clockCycle = NULL; + +#ifdef HAS_RTC // we have hardware RTC + RtcDS3231 Rtc(Wire); // RTC hardware i2c interface // initialize RTC @@ -97,4 +100,95 @@ float get_rtctemp(void) { return 0; } // get_rtctemp() -#endif // HAS_RTC \ No newline at end of file +#endif // HAS_RTC + +// helper function to setup a pulse for time synchronisation +int timepulse_init(uint32_t pulse_period_ms) { + +// use time pulse from GPS as time base with fixed 1Hz frequency +#if defined GPS_INT && defined GPS_CLK + + // 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"); + switch (GPS_CLK) { + case 1000: + break; // default GPS timepulse 1000ms + default: + goto pulse_period_error; + } + return 1; // success + +// use pulse from on board RTC chip as time base with fixed frequency +#elif defined RTC_INT && defined RTC_CLK + + // setup external interupt for active low RTC INT pin + pinMode(RTC_INT, INPUT_PULLUP); + // setup external rtc 1Hz clock as pulse per second clock + ESP_LOGI(TAG, "Time base: external RTC timepulse"); + if (I2C_MUTEX_LOCK()) { + switch (RTC_CLK) { + case 1000: // 1000ms + Rtc.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz); + break; + case 1: // 1ms + Rtc.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1kHz); + break; + default: + goto pulse_period_error; + } + Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeClock); + I2C_MUTEX_UNLOCK(); + } else { + ESP_LOGE(TAG, "I2c bus busy - RTC initialization error"); + return 0; // failure + } + return 1; // success + +#else + // use ESP32 hardware timer as time base with adjustable frequency + if (pulse_period_ms) { + ESP_LOGI(TAG, "Time base: ESP32 hardware timer"); + clockCycle = + timerBegin(1, 8000, true); // set 80 MHz prescaler to 1/10000 sec + timerAttachInterrupt(clockCycle, &CLOCKIRQ, true); + timerAlarmWrite(clockCycle, 10 * pulse_period_ms, true); // ms + } else + goto pulse_period_error; + return 1; // success + +#endif + +pulse_period_error: + ESP_LOGE(TAG, "Unknown timepulse period value"); + return 0; // failure +} + +void timepulse_start() { +#ifdef GPS_INT // start external clock + attachInterrupt(digitalPinToInterrupt(GPS_INT), CLOCKIRQ, RISING); +#elif defined RTC_INT // start external clock + attachInterrupt(digitalPinToInterrupt(RTC_INT), CLOCKIRQ, FALLING); +#else // start internal clock + timerAlarmEnable(clockCycle); +#endif +} + +// helper function to sync phase of DCF output signal to start of second t +uint8_t sync_clock(time_t t) { + time_t tt = t; + // delay until start of next second + do { + tt = now(); + } while (t == tt); + ESP_LOGI(TAG, "Sync on Sec %d", second(tt)); + return second(tt); +} + +// interrupt service routine triggered by either rtc pps or esp32 hardware +// timer +void IRAM_ATTR CLOCKIRQ() { + xTaskNotifyFromISR(ClockTask, xTaskGetTickCountFromISR(), eSetBits, NULL); + portYIELD_FROM_ISR(); +} \ No newline at end of file diff --git a/src/senddata.cpp b/src/senddata.cpp index fe03be25..c13d907f 100644 --- a/src/senddata.cpp +++ b/src/senddata.cpp @@ -2,7 +2,7 @@ #include "senddata.h" // put data to send in RTos Queues used for transmit over channels Lora and SPI -void SendPayload(uint8_t port) { +void SendPayload(uint8_t port, sendprio_t prio) { MessageBuffer_t SendBuffer; // contains MessageSize, MessagePort, Message[] @@ -24,8 +24,8 @@ void SendPayload(uint8_t port) { memcpy(SendBuffer.Message, payload.getBuffer(), payload.getSize()); // enqueue message in device's send queues - lora_enqueuedata(&SendBuffer); - spi_enqueuedata(&SendBuffer); + lora_enqueuedata(&SendBuffer, prio); + spi_enqueuedata(&SendBuffer, prio); } // SendPayload @@ -55,7 +55,7 @@ void sendCounter() { } #endif - SendPayload(COUNTERPORT); + SendPayload(COUNTERPORT, prio_normal); // clear counter if not in cumulative counter mode if (cfg.countermode != 1) { reset_counters(); // clear macs container and reset all counters @@ -68,7 +68,7 @@ void sendCounter() { case MEMS_DATA: payload.reset(); payload.addBME(bme_status); - SendPayload(BMEPORT); + SendPayload(BMEPORT, prio_normal); break; #endif @@ -79,7 +79,7 @@ void sendCounter() { gps_read(); payload.reset(); payload.addGPS(gps_status); - SendPayload(GPSPORT); + SendPayload(GPSPORT, prio_high); } else ESP_LOGD(TAG, "No valid GPS position"); break; @@ -89,17 +89,17 @@ void sendCounter() { case SENSOR1_DATA: payload.reset(); payload.addSensor(sensor_read(1)); - SendPayload(SENSOR1PORT); + SendPayload(SENSOR1PORT, prio_normal); break; case SENSOR2_DATA: payload.reset(); payload.addSensor(sensor_read(2)); - SendPayload(SENSOR2PORT); + SendPayload(SENSOR2PORT, prio_normal); break; case SENSOR3_DATA: payload.reset(); payload.addSensor(sensor_read(3)); - SendPayload(SENSOR3PORT); + SendPayload(SENSOR3PORT, prio_normal); break; #endif @@ -107,7 +107,7 @@ void sendCounter() { case BATT_DATA: payload.reset(); payload.addVoltage(read_voltage()); - SendPayload(BATTPORT); + SendPayload(BATTPORT, prio_normal); break; #endif diff --git a/src/spislave.cpp b/src/spislave.cpp index 5e9ebb1c..5f6a1b60 100644 --- a/src/spislave.cpp +++ b/src/spislave.cpp @@ -66,7 +66,8 @@ void spi_slave_task(void *param) { uint8_t *messageSize = txbuf + 3; *messageSize = msg.MessageSize; memcpy(txbuf + HEADER_SIZE, &msg.Message, msg.MessageSize); - // calculate crc16 checksum over txbuf and insert checksum at pos 0+1 of txbuf + // calculate crc16 checksum over txbuf and insert checksum at pos 0+1 of + // txbuf uint16_t *crc = (uint16_t *)txbuf; *crc = crc16_be(0, messageType, msg.MessageSize + HEADER_SIZE - 2); @@ -87,7 +88,8 @@ void spi_slave_task(void *param) { // wait until spi master clocks out the data, and read results in rx buffer ESP_LOGI(TAG, "Prepared SPI transaction for %zu byte(s)", transaction_size); ESP_LOG_BUFFER_HEXDUMP(TAG, txbuf, transaction_size, ESP_LOG_DEBUG); - ESP_ERROR_CHECK_WITHOUT_ABORT(spi_slave_transmit(HSPI_HOST, &spi_transaction, portMAX_DELAY)); + ESP_ERROR_CHECK_WITHOUT_ABORT( + spi_slave_transmit(HSPI_HOST, &spi_transaction, portMAX_DELAY)); ESP_LOG_BUFFER_HEXDUMP(TAG, rxbuf, transaction_size, ESP_LOG_DEBUG); ESP_LOGI(TAG, "Transaction finished with size %zu bits", spi_transaction.trans_len); @@ -103,7 +105,7 @@ esp_err_t spi_init() { #ifndef HAS_SPI return ESP_OK; #else - + assert(SEND_QUEUE_SIZE); SPISendQueue = xQueueCreate(SEND_QUEUE_SIZE, sizeof(MessageBuffer_t)); if (SPISendQueue == 0) { ESP_LOGE(TAG, "Could not create SPI send queue. Aborting."); @@ -148,11 +150,20 @@ esp_err_t spi_init() { #endif } -void spi_enqueuedata(MessageBuffer_t *message) { +void spi_enqueuedata(MessageBuffer_t *message, sendprio_t prio) { // enqueue message in SPI send queue #ifdef HAS_SPI - BaseType_t ret = - xQueueSendToBack(SPISendQueue, (void *)message, (TickType_t)0); + BaseType_t ret; + switch (prio) { + case prio_high: + ret = xQueueSendToFront(SPISendQueue, (void *)message, (TickType_t)0); + break; + case prio_low: + case prio_normal: + default: + ret = xQueueSendToBack(SPISendQueue, (void *)message, (TickType_t)0); + break; + } if (ret == pdTRUE) { ESP_LOGI(TAG, "%d byte(s) enqueued for SPI interface", message->MessageSize); diff --git a/src/wifiscan.cpp b/src/wifiscan.cpp index dcd8ebc9..a6130ad9 100644 --- a/src/wifiscan.cpp +++ b/src/wifiscan.cpp @@ -7,6 +7,8 @@ // Local logging tag static const char TAG[] = "wifi"; +TimerHandle_t WifiChanTimer; + static wifi_country_t wifi_country = {WIFI_MY_COUNTRY, WIFI_CHANNEL_MIN, WIFI_CHANNEL_MAX, 100, WIFI_COUNTRY_POLICY_MANUAL}; @@ -43,10 +45,17 @@ IRAM_ATTR void wifi_sniffer_packet_handler(void *buff, mac_add((uint8_t *)hdr->addr2, ppkt->rx_ctrl.rssi, MAC_SNIFF_WIFI); } +// Software-timer driven Wifi channel rotation callback function +void switchWifiChannel(TimerHandle_t xTimer) { + channel = + (channel % WIFI_CHANNEL_MAX) + 1; // rotate channel 1..WIFI_CHANNEL_MAX + esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); + } + void wifi_sniffer_init(void) { - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - cfg.nvs_enable = 0; // we don't need any wifi settings from NVRAM - cfg.wifi_task_core_id = 0; // we want wifi task running on core 0 + wifi_init_config_t wificfg = WIFI_INIT_CONFIG_DEFAULT(); + wificfg.nvs_enable = 0; // we don't need any wifi settings from NVRAM + wificfg.wifi_task_core_id = 0; // we want wifi task running on core 0 wifi_promiscuous_filter_t filter = { // .filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT}; // only MGMT frames .filter_mask = WIFI_PROMIS_FILTER_MASK_ALL}; // we use all frames @@ -54,7 +63,7 @@ void wifi_sniffer_init(void) { ESP_ERROR_CHECK(esp_coex_preference_set( ESP_COEX_PREFER_BALANCE)); // configure Wifi/BT coexist lib - ESP_ERROR_CHECK(esp_wifi_init(&cfg)); // configure Wifi with cfg + ESP_ERROR_CHECK(esp_wifi_init(&wificfg)); // configure Wifi with cfg ESP_ERROR_CHECK( esp_wifi_set_country(&wifi_country)); // set locales for RF and channels ESP_ERROR_CHECK( @@ -65,16 +74,11 @@ void wifi_sniffer_init(void) { esp_wifi_set_promiscuous_filter(&filter)); // set MAC frame filter ESP_ERROR_CHECK(esp_wifi_set_promiscuous_rx_cb(&wifi_sniffer_packet_handler)); ESP_ERROR_CHECK(esp_wifi_set_promiscuous(true)); // now switch on monitor mode -} -// Wifi channel rotation task -void switchWifiChannel(void *parameter) { - while (1) { - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // waiting for channel switch timer - channel = - (channel % WIFI_CHANNEL_MAX) + 1; // rotate channel 1..WIFI_CHANNEL_MAX - esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); - //ESP_LOGD(TAG, "Wifi set channel %d", channel); - } - vTaskDelete(NULL); // shoud never be reached -} + // setup wifi channel rotation timer + WifiChanTimer = + xTimerCreate("WifiChannelTimer", pdMS_TO_TICKS(cfg.wifichancycle * 10), + pdTRUE, (void *)0, switchWifiChannel); + assert(WifiChanTimer); + xTimerStart(WifiChanTimer, 0); +} \ No newline at end of file