From e5df1013b303be7ec9eb9db987dcaeae8328a6a1 Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Wed, 3 Oct 2018 00:25:05 +0200 Subject: [PATCH] v1.5.18 (improved tasking, lmic has now core1 exclusive) --- platformio.ini | 6 +- src/cyclic.cpp | 9 +-- src/cyclic.h | 4 ++ src/display.cpp | 18 +++++- src/globals.h | 6 +- src/gps.h | 1 + src/led.cpp | 104 ++++++++++++++++--------------- src/led.h | 2 +- src/lorawan.cpp | 23 +++---- src/lorawan.h | 4 +- src/main.cpp | 143 ++++++++++++++++++------------------------- src/statemachine.cpp | 5 ++ 12 files changed, 164 insertions(+), 161 deletions(-) diff --git a/platformio.ini b/platformio.ini index 1b43cf4f..9683cda7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -26,13 +26,13 @@ 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.5.16 +release_version = 1.5.18 ; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running! ; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose debug_level = 0 ; UPLOAD MODE: select esptool to flash via USB/UART, select custom to upload to cloud for OTA -upload_protocol = esptool -;upload_protocol = custom +;upload_protocol = esptool +upload_protocol = custom extra_scripts = pre:build.py keyfile = ota.conf platform_espressif32 = espressif32@1.4.0 diff --git a/src/cyclic.cpp b/src/cyclic.cpp index 1e864ba5..4dbff221 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -3,8 +3,6 @@ // Basic config #include "globals.h" -#include "senddata.h" -#include "ota.h" // Local logging tag static const char TAG[] = "main"; @@ -26,10 +24,6 @@ void doHousekeeping() { ESP.restart(); // task storage debugging // -#ifdef HAS_LORA - ESP_LOGD(TAG, "Loraloop %d bytes left", - uxTaskGetStackHighWaterMark(LoraTask)); -#endif ESP_LOGD(TAG, "Wifiloop %d bytes left", uxTaskGetStackHighWaterMark(wifiSwitchTask)); ESP_LOGD(TAG, "Statemachine %d bytes left", @@ -37,6 +31,9 @@ void doHousekeeping() { #ifdef HAS_GPS ESP_LOGD(TAG, "Gpsloop %d bytes left", uxTaskGetStackHighWaterMark(GpsTask)); #endif +#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) + ESP_LOGD(TAG, "LEDloop %d bytes left", uxTaskGetStackHighWaterMark(ledLoopTask)); +#endif // read battery voltage into global variable #ifdef HAS_BATTERY_PROBE diff --git a/src/cyclic.h b/src/cyclic.h index a006f137..9aad61d4 100644 --- a/src/cyclic.h +++ b/src/cyclic.h @@ -1,6 +1,10 @@ #ifndef _CYCLIC_H #define _CYCLIC_H +#include "senddata.h" +#include "ota.h" +#include "led.h" + void doHousekeeping(void); void IRAM_ATTR homeCycleIRQ(void); uint64_t uptime(void); diff --git a/src/display.cpp b/src/display.cpp index 773dbf94..1eaef6c5 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -15,6 +15,8 @@ const char lora_datarate[] = {"100908078CNA121110090807"}; uint8_t volatile DisplayState = 0; +hw_timer_t *displaytimer; + portMUX_TYPE mutexDisplay = portMUX_INITIALIZER_UNLOCKED; // helper function, prints a hex key on display @@ -27,8 +29,20 @@ void DisplayKey(const uint8_t *key, uint8_t len, bool lsb) { u8x8.printf("\n"); } -// show startup screen void init_display(const char *Productname, const char *Version) { + + // setup display refresh trigger IRQ using esp32 hardware timer + // https://techtutorialsx.com/2017/10/07/esp32-arduino-timer-interrupts/ + // prescaler 80 -> divides 80 MHz CPU freq to 1 MHz, timer 0, count up + displaytimer = timerBegin(0, 80, true); + // interrupt handler DisplayIRQ, triggered by edge + timerAttachInterrupt(displaytimer, &DisplayIRQ, true); + // reload interrupt after each trigger of display refresh cycle + timerAlarmWrite(displaytimer, DISPLAYREFRESH_MS * 1000, true); + // enable display interrupt + timerAlarmEnable(displaytimer); + + // show startup screen uint8_t buf[32]; u8x8.begin(); u8x8.setFont(u8x8_font_chroma48medium8_r); @@ -107,7 +121,7 @@ void refreshtheDisplay() { if (!DisplayState) return; - uint8_t msgWaiting = 0; + uint8_t msgWaiting; char buff[16]; // 16 chars line buffer // update counter (lines 0-1) diff --git a/src/globals.h b/src/globals.h index 739512f1..eb0adc11 100644 --- a/src/globals.h +++ b/src/globals.h @@ -56,19 +56,17 @@ extern SemaphoreHandle_t xWifiChannelSwitchSemaphore; extern TaskHandle_t stateMachineTask, wifiSwitchTask; #ifdef HAS_GPS -extern TaskHandle_t GpsTask; #include "gps.h" #endif -#ifdef HAS_LED +#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) #include "led.h" +extern TaskHandle_t ledLoopTask; #endif #include "payload.h" #ifdef HAS_LORA -extern QueueHandle_t LoraSendQueue; -extern TaskHandle_t LoraTask; #include "lorawan.h" #endif diff --git a/src/gps.h b/src/gps.h index 5121528e..b5cbff88 100644 --- a/src/gps.h +++ b/src/gps.h @@ -19,6 +19,7 @@ typedef struct { extern TinyGPSPlus gps; // Make TinyGPS++ instance globally availabe extern gpsStatus_t gps_status; // Make struct for storing gps data globally available +extern TaskHandle_t GpsTask; void gps_read(void); void gps_loop(void *pvParameters); diff --git a/src/led.cpp b/src/led.cpp index 0ade583a..9379d9e4 100644 --- a/src/led.cpp +++ b/src/led.cpp @@ -94,69 +94,75 @@ void blink_LED(uint16_t set_color, uint16_t set_blinkduration) { LEDState = LED_ON; // Let main set LED on } -void led_loop() { - // Custom blink running always have priority other LoRaWAN led management - if (LEDBlinkStarted && LEDBlinkDuration) { - // Custom blink is finished, let this order, avoid millis() overflow - if ((millis() - LEDBlinkStarted) >= LEDBlinkDuration) { - // Led becomes off, and stop blink - LEDState = LED_OFF; - LEDBlinkStarted = 0; - LEDBlinkDuration = 0; - LEDColor = COLOR_NONE; +void ledLoop(void *parameter) { + while (1) { + // Custom blink running always have priority other LoRaWAN led management + if (LEDBlinkStarted && LEDBlinkDuration) { + // Custom blink is finished, let this order, avoid millis() overflow + if ((millis() - LEDBlinkStarted) >= LEDBlinkDuration) { + // Led becomes off, and stop blink + LEDState = LED_OFF; + LEDBlinkStarted = 0; + LEDBlinkDuration = 0; + LEDColor = COLOR_NONE; + } else { + // In case of LoRaWAN led management blinked off + LEDState = LED_ON; + } + // No custom blink, check LoRaWAN state } else { - // In case of LoRaWAN led management blinked off - LEDState = LED_ON; - } - // No custom blink, check LoRaWAN state - } else { #ifdef HAS_LORA - // LED indicators for viusalizing LoRaWAN state - if (LMIC.opmode & (OP_JOINING | OP_REJOIN)) { - LEDColor = COLOR_YELLOW; - // quick blink 20ms on each 1/5 second - LEDState = ((millis() % 200) < 20) ? LED_ON : LED_OFF; // TX data pending - } else if (LMIC.opmode & (OP_TXDATA | OP_TXRXPEND)) { - LEDColor = COLOR_BLUE; - // small blink 10ms on each 1/2sec (not when joining) - LEDState = ((millis() % 500) < 10) ? LED_ON : LED_OFF; - // This should not happen so indicate a problem - } else if (LMIC.opmode & - ((OP_TXDATA | OP_TXRXPEND | OP_JOINING | OP_REJOIN) == 0)) { - LEDColor = COLOR_RED; - // heartbeat long blink 200ms on each 2 seconds - LEDState = ((millis() % 2000) < 200) ? LED_ON : LED_OFF; - } else + // LED indicators for viusalizing LoRaWAN state + if (LMIC.opmode & (OP_JOINING | OP_REJOIN)) { + LEDColor = COLOR_YELLOW; + // quick blink 20ms on each 1/5 second + LEDState = + ((millis() % 200) < 20) ? LED_ON : LED_OFF; // TX data pending + } else if (LMIC.opmode & (OP_TXDATA | OP_TXRXPEND)) { + LEDColor = COLOR_BLUE; + // small blink 10ms on each 1/2sec (not when joining) + LEDState = ((millis() % 500) < 10) ? LED_ON : LED_OFF; + // This should not happen so indicate a problem + } else if (LMIC.opmode & + ((OP_TXDATA | OP_TXRXPEND | OP_JOINING | OP_REJOIN) == 0)) { + LEDColor = COLOR_RED; + // heartbeat long blink 200ms on each 2 seconds + LEDState = ((millis() % 2000) < 200) ? LED_ON : LED_OFF; + } else #endif // HAS_LORA - { - // led off - LEDColor = COLOR_NONE; - LEDState = LED_OFF; + { + // led off + LEDColor = COLOR_NONE; + LEDState = LED_OFF; + } } - } - // led need to change state? avoid digitalWrite() for nothing - if (LEDState != previousLEDState) { - if (LEDState == LED_ON) { - rgb_set_color(LEDColor); + // led need to change state? avoid digitalWrite() for nothing + if (LEDState != previousLEDState) { + if (LEDState == LED_ON) { + rgb_set_color(LEDColor); #ifdef LED_ACTIVE_LOW - digitalWrite(HAS_LED, LOW); + digitalWrite(HAS_LED, LOW); #else - digitalWrite(HAS_LED, HIGH); + digitalWrite(HAS_LED, HIGH); #endif - } else { - rgb_set_color(COLOR_NONE); + } else { + rgb_set_color(COLOR_NONE); #ifdef LED_ACTIVE_LOW - digitalWrite(HAS_LED, HIGH); + digitalWrite(HAS_LED, HIGH); #else - digitalWrite(HAS_LED, LOW); + digitalWrite(HAS_LED, LOW); #endif + } + previousLEDState = LEDState; } - previousLEDState = LEDState; - } -}; // led_loop() + // give yield to CPU + vTaskDelay(2 / portTICK_PERIOD_MS); + } // while(1) + vTaskDelete(NULL); // shoud never be reached +}; // ledloop() #endif // #if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) diff --git a/src/led.h b/src/led.h index 8dd4716c..68f5bd88 100644 --- a/src/led.h +++ b/src/led.h @@ -34,6 +34,6 @@ enum led_states { LED_OFF, LED_ON }; // Exported Functions void rgb_set_color(uint16_t hue); void blink_LED(uint16_t set_color, uint16_t set_blinkduration); -void led_loop(); +void ledLoop(void *parameter); #endif \ No newline at end of file diff --git a/src/lorawan.cpp b/src/lorawan.cpp index 7f93c1e6..49a6b861 100644 --- a/src/lorawan.cpp +++ b/src/lorawan.cpp @@ -63,6 +63,18 @@ void RevBytes(unsigned char *b, size_t c) { } } +// initial lmic job +void initlmic(osjob_t *j) { + // reset MAC state + LMIC_reset(); + // This tells LMIC to make the receive windows bigger, in case your clock is + // 1% faster or slower. + LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100); + // start joining + LMIC_startJoining(); + // init done - onEvent() callback will be invoked... +} + // LMIC callback functions void os_getDevKey(u1_t *buf) { memcpy(buf, APPKEY, 16); } @@ -241,17 +253,6 @@ void onEvent(ev_t ev) { } // onEvent() -// LMIC FreeRTos Task -void lorawan_loop(void *pvParameters) { - - configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check - - while (1) { - os_runloop_once(); // execute LMIC jobs - vTaskDelay(2 / portTICK_PERIOD_MS); // yield to CPU - } -} - // helper function to assign LoRa datarates to numeric spreadfactor values void switch_lora(uint8_t sf, uint8_t tx) { if (tx > 20) diff --git a/src/lorawan.h b/src/lorawan.h index 32220496..162caa25 100644 --- a/src/lorawan.h +++ b/src/lorawan.h @@ -14,6 +14,8 @@ #include #endif +extern QueueHandle_t LoraSendQueue; + void onEvent(ev_t ev); void gen_lora_deveui(uint8_t *pdeveui); void RevBytes(unsigned char *b, size_t c); @@ -22,7 +24,7 @@ void os_getDevKey(u1_t *buf); void os_getArtEui(u1_t *buf); void os_getDevEui(u1_t *buf); void showLoraKeys(void); -void lorawan_loop(void *pvParameters); void switch_lora(uint8_t sf, uint8_t tx); +void initlmic(osjob_t *j); #endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index f758a795..0961af1a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,12 +27,14 @@ Uused tasks and timers: Task Core Prio Purpose ==================================================================================== -IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer task -gpsloop 0 2 read data from GPS over serial or i2c -IDLE 1 0 Arduino loop() -> used for LED switching -loraloop 1 2 runs the LMIC stack -statemachine 1 1 switches application process logic wifiloop 0 4 rotates wifi channels +ledloop 0 3 blinks LEDs +gpsloop 0 2 read data from GPS over serial or i2c +statemachine 0 1 switches application process logic +IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer task + +looptask 1 1 arduino loop() -> runs the LMIC stack +IDLE 1 0 ESP32 arduino scheduler ESP32 hardware timers ========================== @@ -53,7 +55,7 @@ uint16_t volatile macs_total = 0, macs_wifi = 0, macs_ble = 0, batt_voltage = 0; // globals for display // hardware timer for cyclic tasks -hw_timer_t *channelSwitch, *displaytimer, *sendCycle, *homeCycle; +hw_timer_t *channelSwitch, *sendCycle, *homeCycle; // this variables will be changed in the ISR, and read in main loop uint8_t volatile ButtonPressedIRQ = 0, ChannelTimerIRQ = 0, @@ -66,7 +68,6 @@ SemaphoreHandle_t xWifiChannelSwitchSemaphore; // RTos send queues for payload transmit #ifdef HAS_LORA QueueHandle_t LoraSendQueue; -TaskHandle_t LoraTask = NULL; #endif #ifdef HAS_SPI @@ -74,7 +75,11 @@ QueueHandle_t SPISendQueue; #endif #ifdef HAS_GPS -TaskHandle_t GpsTask = NULL; +TaskHandle_t GpsTask; +#endif + +#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) +TaskHandle_t ledLoopTask; #endif std::set macs; // container holding unique MAC adress hashes @@ -133,6 +138,7 @@ void setup() { strcat_P(features, " BLE"); #else bool btstop = btStop(); + //esp_bt_controller_mem_release(ESP_BT_MODE_BTDM); #endif // initialize battery status @@ -228,45 +234,25 @@ void setup() { #ifdef HAS_DISPLAY strcat_P(features, " OLED"); DisplayState = cfg.screenon; - init_display(PRODUCTNAME, PROGVERSION); - - // setup display refresh trigger IRQ using esp32 hardware timer - // https://techtutorialsx.com/2017/10/07/esp32-arduino-timer-interrupts/ - - // prescaler 80 -> divides 80 MHz CPU freq to 1 MHz, timer 0, count up - displaytimer = timerBegin(0, 80, true); - // interrupt handler DisplayIRQ, triggered by edge - timerAttachInterrupt(displaytimer, &DisplayIRQ, true); - // reload interrupt after each trigger of display refresh cycle - timerAlarmWrite(displaytimer, DISPLAYREFRESH_MS * 1000, true); - // enable display interrupt - yield(); - timerAlarmEnable(displaytimer); #endif // setup send cycle trigger IRQ using esp32 hardware timer 2 sendCycle = timerBegin(2, 8000, true); timerAttachInterrupt(sendCycle, &SendCycleIRQ, true); timerAlarmWrite(sendCycle, cfg.sendcycle * 2 * 10000, true); + timerAlarmEnable(sendCycle); // setup house keeping cycle trigger IRQ using esp32 hardware timer 3 homeCycle = timerBegin(3, 8000, true); timerAttachInterrupt(homeCycle, &homeCycleIRQ, true); timerAlarmWrite(homeCycle, HOMECYCLE * 10000, true); + timerAlarmEnable(homeCycle); // setup channel rotation trigger IRQ using esp32 hardware timer 1 xWifiChannelSwitchSemaphore = xSemaphoreCreateBinary(); channelSwitch = timerBegin(1, 800, true); timerAttachInterrupt(channelSwitch, &ChannelSwitchIRQ, true); timerAlarmWrite(channelSwitch, cfg.wifichancycle * 1000, true); - - // enable timers - // caution, see: https://github.com/espressif/arduino-esp32/issues/1313 - yield(); - timerAlarmEnable(homeCycle); - yield(); - timerAlarmEnable(sendCycle); - yield(); timerAlarmEnable(channelSwitch); // show payload encoder @@ -288,43 +274,6 @@ void setup() { #ifdef VERBOSE showLoraKeys(); #endif - - // initialize LoRaWAN LMIC run-time environment - os_init(); - // reset LMIC MAC state - LMIC_reset(); - // This tells LMIC to make the receive windows bigger, in case your clock is - // 1% faster or slower. - LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100); - // join network - LMIC_startJoining(); - - // start lmic runloop in rtos task on core 1 - // (note: arduino main loop runs on core 1, too) - // https://techtutorialsx.com/2017/05/09/esp32-get-task-execution-core/ - - ESP_LOGI(TAG, "Starting Lora..."); - xTaskCreatePinnedToCore(lorawan_loop, /* task function */ - "loraloop", /* name of task */ - 3048, /* stack size of task */ - (void *)1, /* parameter of the task */ - 2, /* priority of the task */ - &LoraTask, /* task handle*/ - 1); /* CPU core */ -#endif - -// if device has GPS and it is enabled, start GPS reader task on core 0 with -// higher priority than wifi channel rotation task since we process serial -// streaming NMEA data -#ifdef HAS_GPS - ESP_LOGI(TAG, "Starting GPS..."); - xTaskCreatePinnedToCore(gps_loop, /* task function */ - "gpsloop", /* name of task */ - 1024, /* stack size of task */ - (void *)1, /* parameter of the task */ - 2, /* priority of the task */ - &GpsTask, /* task handle*/ - 0); /* CPU core */ #endif // start BLE scan callback if BLE function is enabled in NVRAM configuration @@ -343,14 +292,16 @@ void setup() { // function gets it's seed from RF noise get_salt(); // get new 16bit for salting hashes - // start wifi channel rotation task - 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 */ +#ifdef HAS_GPS + ESP_LOGI(TAG, "Starting GPS..."); + xTaskCreatePinnedToCore(gps_loop, /* task function */ + "gpsloop", /* name of task */ + 1024, /* stack size of task */ + (void *)1, /* parameter of the task */ + 2, /* priority of the task */ + &GpsTask, /* task handle*/ + 0); /* CPU core */ +#endif // start state machine ESP_LOGI(TAG, "Starting Statemachine..."); @@ -360,17 +311,41 @@ void setup() { (void *)1, /* parameter of the task */ 1, /* priority of the task */ &stateMachineTask, /* task handle */ - 1); /* CPU core */ + 0); /* CPU core */ + +#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) + // start led loop + ESP_LOGI(TAG, "Starting LEDloop..."); + xTaskCreatePinnedToCore(ledLoop, /* task function */ + "ledloop", /* name of task */ + 1024, /* stack size of task */ + (void *)1, /* parameter of the task */ + 3, /* priority of the task */ + &ledLoopTask, /* task handle */ + 0); /* CPU core */ +#endif + + // 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 */ } // setup() void loop() { - -// switch LED state if device has LED(s) -#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) - led_loop(); -#endif - - // give yield to CPU - vTaskDelay(2 / portTICK_PERIOD_MS); + osjob_t initjob; + // initialize run-time env + os_init(); + // setup initial job + os_setCallback(&initjob, initlmic); + // execute scheduled jobs and events + while (1) { + os_runloop_once(); // execute LMIC jobs + vTaskDelay(2 / portTICK_PERIOD_MS); // yield to CPU + } } \ No newline at end of file diff --git a/src/statemachine.cpp b/src/statemachine.cpp index 76d02801..b0476ccb 100644 --- a/src/statemachine.cpp +++ b/src/statemachine.cpp @@ -7,6 +7,11 @@ void stateMachine(void *pvParameters) { configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check + // initialize display - caution: must be done on core 1 in arduino loop! +#ifdef HAS_DISPLAY + init_display(PRODUCTNAME, PROGVERSION); +#endif + while (1) { #ifdef HAS_BUTTON