From d7368d5a51226e4614266c3cdf7cbdda08ff8e77 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Fri, 30 Oct 2020 17:05:32 +0800 Subject: [PATCH] begin deep sleep support for nrf52 --- docs/software/TODO.md | 12 +++---- src/PowerFSM.cpp | 6 ++-- src/esp32/main-esp32.cpp | 54 ++++++++++++++++++++++++++++++ src/gps/GPS.cpp | 16 ++++++++- src/gps/GPS.h | 5 +++ src/mesh/RadioInterface.cpp | 6 ++++ src/mesh/RadioInterface.h | 8 ++--- src/mesh/SX1262Interface.cpp | 13 +++++++- src/nrf52/NRF52Bluetooth.cpp | 7 ++++ src/nrf52/NRF52Bluetooth.h | 1 + src/nrf52/main-nrf52.cpp | 23 +++++++++++-- src/sleep.cpp | 64 ++++-------------------------------- src/sleep.h | 3 +- 13 files changed, 137 insertions(+), 81 deletions(-) diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 514fda9cf..4173296e1 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -2,15 +2,11 @@ You probably don't care about this section - skip to the next one. -Threading tasks: +For high speed/lots of devices/short range tasks: -- Use https://github.com/ivanseidel/ArduinoThread? rather than full coroutines -- clean up main loop() -- check that we are mostly asleep, show which thread is causing us to wake -- -- use tickless idle on nrf52, and sleep X msec or until an interrupt occurs or the cooperative scheduling changes. https://devzone.nordicsemi.com/f/nordic-q-a/12363/nrf52-freertos-power-consumption-tickless-idle -- BAD IDEA: use vTaskDelay and https://www.freertos.org/xTaskAbortDelay.html if scheduling changes. (define INCLUDE_xTaskAbortDelay on ESP32 and NRF52 - seems impossible to find?) -- GOOD IDEA: use xSemaphoreTake to take a semaphore using a timeout. Expect semaphore to not be set, but set it to indicate scheduling has changed. +- When guessing numhops for sending: if I've heard from many local (0 hop neighbors) decrease hopcount by 2 rather than 1. +This should nicely help 'router' nodes do the right thing when long range, or if there are many local nodes for short range. +- fix timeouts/delays to be based on packet length at current radio settings Nimble tasks: diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index ccc2f54ac..4b8b96368 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -251,13 +251,11 @@ void PowerFSM_setup() #ifndef NRF52_SERIES // We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally) - lowPowerState = &stateDARK; - powerFSM.add_timed_transition(&stateDARK, &stateNB, getPref_phone_timeout_secs() * 1000, NULL, "Phone timeout"); - powerFSM.add_timed_transition(&stateNB, &stateLS, getPref_min_wake_secs() * 1000, NULL, "Min wake timeout"); - powerFSM.add_timed_transition(&stateDARK, &stateLS, getPref_wait_bluetooth_secs() * 1000, NULL, "Bluetooth timeout"); +#else + lowPowerState = &stateDARK; #endif auto meshSds = getPref_mesh_sds_timeout_secs(); diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index aa85f9524..f5ec62a13 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -86,4 +86,58 @@ void esp32Loop() // for debug printing // radio.radioIf.canSleep(); +} + +void cpuDeepSleep(uint64_t msecToWake) +{ + /* + Some ESP32 IOs have internal pullups or pulldowns, which are enabled by default. + If an external circuit drives this pin in deep sleep mode, current consumption may + increase due to current flowing through these pullups and pulldowns. + + To isolate a pin, preventing extra current draw, call rtc_gpio_isolate() function. + For example, on ESP32-WROVER module, GPIO12 is pulled up externally. + GPIO12 also has an internal pulldown in the ESP32 chip. This means that in deep sleep, + some current will flow through these external and internal resistors, increasing deep + sleep current above the minimal possible value. + + Note: we don't isolate pins that are used for the LORA, LED, i2c, spi or the wake button + */ + static const uint8_t rtcGpios[] = {/* 0, */ 2, + /* 4, */ +#ifndef USE_JTAG + 13, + /* 14, */ /* 15, */ +#endif + /* 25, */ 26, /* 27, */ + 32, 33, 34, 35, + 36, 37 + /* 38, 39 */}; + + for (int i = 0; i < sizeof(rtcGpios); i++) + rtc_gpio_isolate((gpio_num_t)rtcGpios[i]); + + // FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using + // to detect wake and in normal operation the external part drives them hard. + + // We want RTC peripherals to stay on + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + +#ifdef BUTTON_PIN + // Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39. + uint64_t gpioMask = (1ULL << BUTTON_PIN); + +#ifdef BUTTON_NEED_PULLUP + gpio_pullup_en((gpio_num_t)BUTTON_PIN); +#endif + + // Not needed because both of the current boards have external pullups + // FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead of + // just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN); + + esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW); +#endif + + esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs + esp_deep_sleep_start(); // TBD mA sleep current (battery) } \ No newline at end of file diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index d72ff70f5..aca074ccc 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -59,8 +59,10 @@ bool GPS::setup() setAwake(true); // Wake GPS power before doing any init bool ok = setupGPS(); - if (ok) + if (ok) { notifySleepObserver.observe(¬ifySleep); + notifyDeepSleepObserver.observe(¬ifyDeepSleep); + } return ok; } @@ -275,7 +277,19 @@ void GPS::forceWake(bool on) /// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs int GPS::prepareSleep(void *unused) { + DEBUG_MSG("GPS prepare sleep!\n"); forceWake(false); return 0; } + +/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs +int GPS::prepareDeepSleep(void *unused) +{ + DEBUG_MSG("GPS deep sleep!\n"); + + // For deep sleep we also want abandon any lock attempts (because we want minimum power) + setAwake(false); + + return 0; +} diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 98ad185f2..eca2962d2 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -30,6 +30,7 @@ class GPS : private concurrency::OSThread uint8_t numSatellites = 0; CallbackObserver notifySleepObserver = CallbackObserver(this, &GPS::prepareSleep); + CallbackObserver notifyDeepSleepObserver = CallbackObserver(this, &GPS::prepareDeepSleep); public: /** If !NULL we will use this serial port to construct our GPS */ @@ -115,6 +116,10 @@ class GPS : private concurrency::OSThread /// always returns 0 to indicate okay to sleep int prepareSleep(void *unused); + /// Prepare the GPS for the cpu entering deep sleep, expect to be gone for at least 100s of msecs + /// always returns 0 to indicate okay to sleep + int prepareDeepSleep(void *unused); + /** * Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode * diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 83bf5aa37..b8ab4858a 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -125,6 +125,12 @@ bool RadioInterface::init() return true; } +int RadioInterface::notifyDeepSleepCb(void *unused) +{ + sleep(); + return 0; +} + /** hash a string into an integer * * djb2 by Dan Bernstein. diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 1d2e4b750..da717caaf 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -48,7 +48,7 @@ class RadioInterface CallbackObserver(this, &RadioInterface::preflightSleepCb); CallbackObserver notifyDeepSleepObserver = - CallbackObserver(this, &RadioInterface::notifyDeepSleepDb); + CallbackObserver(this, &RadioInterface::notifyDeepSleepCb); protected: MeshPacket *sendingPacket = NULL; // The packet we are currently sending @@ -136,11 +136,7 @@ class RadioInterface /// Return 0 if sleep is okay int preflightSleepCb(void *unused = NULL) { return canSleep() ? 0 : 1; } - int notifyDeepSleepDb(void *unused = NULL) - { - sleep(); - return 0; - } + int notifyDeepSleepCb(void *unused = NULL); int reloadConfig(void *unused) { diff --git a/src/mesh/SX1262Interface.cpp b/src/mesh/SX1262Interface.cpp index 75f60128d..d94951470 100644 --- a/src/mesh/SX1262Interface.cpp +++ b/src/mesh/SX1262Interface.cpp @@ -201,9 +201,20 @@ bool SX1262Interface::isActivelyReceiving() bool SX1262Interface::sleep() { + DEBUG_MSG("sx1262 entering sleep mode\n"); + setStandby(); // Stop any pending operations + + // turn off TCXO if it was powered + // FIXME - this isn't correct + // lora.setTCXO(0); + // put chipset into sleep mode disableInterrupt(); - lora.sleep(); + lora.sleep(false); // Note: we do not keep the config, full reinit will be needed + +#ifdef SX1262_POWER_EN + digitalWrite(SX1262_POWER_EN, LOW); +#endif return true; } \ No newline at end of file diff --git a/src/nrf52/NRF52Bluetooth.cpp b/src/nrf52/NRF52Bluetooth.cpp index 9309e645e..35093ff17 100644 --- a/src/nrf52/NRF52Bluetooth.cpp +++ b/src/nrf52/NRF52Bluetooth.cpp @@ -202,6 +202,13 @@ void setupMeshService(void) // FIXME, turn off soft device access for debugging static bool isSoftDeviceAllowed = true; +void NRF52Bluetooth::shutdown() +{ + // Shutdown bluetooth for minimum power draw + DEBUG_MSG("Disable NRF52 bluetooth\n"); + Bluefruit.Advertising.stop(); +} + void NRF52Bluetooth::setup() { // Initialise the Bluefruit module diff --git a/src/nrf52/NRF52Bluetooth.h b/src/nrf52/NRF52Bluetooth.h index 40f13d8bc..a10e07aba 100644 --- a/src/nrf52/NRF52Bluetooth.h +++ b/src/nrf52/NRF52Bluetooth.h @@ -4,5 +4,6 @@ class NRF52Bluetooth { public: void setup(); + void shutdown(); }; diff --git a/src/nrf52/main-nrf52.cpp b/src/nrf52/main-nrf52.cpp index 6b4b1adc1..eec306e52 100644 --- a/src/nrf52/main-nrf52.cpp +++ b/src/nrf52/main-nrf52.cpp @@ -49,7 +49,7 @@ void getMacAddr(uint8_t *dmac) NRF52Bluetooth *nrf52Bluetooth; static bool bleOn = false; -static const bool enableBle = true; // Set to false for easier debugging +static const bool enableBle = false; // Set to false for easier debugging void setBluetoothEnable(bool on) { @@ -64,7 +64,8 @@ void setBluetoothEnable(bool on) } } } else { - DEBUG_MSG("FIXME: implement BLE disable\n"); + if(nrf52Bluetooth) + nrf52Bluetooth->shutdown(); } bleOn = on; } @@ -109,4 +110,22 @@ void nrf52Setup() // randomSeed(r); DEBUG_MSG("FIXME, call randomSeed\n"); // ::printf("TESTING PRINTF\n"); +} + +void cpuDeepSleep(uint64_t msecToWake) +{ + DEBUG_MSG("FIXME: implement NRF52 deep sleep enter actions\n"); + // FIXME, configure RTC to wake us + // FIXME, power down SPI, I2C, RAMs + + // FIXME, use system off mode with ram retention for key state? + // FIXME, use non-init RAM per https://devzone.nordicsemi.com/f/nordic-q-a/48919/ram-retention-settings-with-softdevice-enabled + + while(1) { + delay(5000); + DEBUG_MSG("."); + } + + // FIXME, after wake power up SPI, I2C, RAMs, reinit LORA + DEBUG_MSG("FIXME: implement NRF52 deep sleep wake actions\n"); } \ No newline at end of file diff --git a/src/sleep.cpp b/src/sleep.cpp index ee90c149c..e2cafe574 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -144,17 +144,20 @@ void doDeepSleep(uint64_t msecToWake) { DEBUG_MSG("Entering deep sleep for %llu seconds\n", msecToWake / 1000); -#ifndef NO_ESP32 // not using wifi yet, but once we are this is needed to shutoff the radio hw // esp_wifi_stop(); waitEnterSleep(); - notifySleep.notifyObservers(NULL); // also tell the regular sleep handlers notifyDeepSleep.notifyObservers(NULL); screen->setOn(false); // datasheet says this will draw only 10ua nodeDB.saveToDisk(); + // Kill GPS power completely (even if previously we just had it in sleep mode) + setGPSPower(false); + + setLed(false); + #ifdef RESET_OLED digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power #endif @@ -163,11 +166,6 @@ void doDeepSleep(uint64_t msecToWake) digitalWrite(VEXT_ENABLE, 1); // turn off the display power #endif - // Kill GPS power completely (even if previously we just had it in sleep mode) - setGPSPower(false); - - setLed(false); - #ifdef TBEAM_V10 if (axp192_found) { // Obsolete comment: from back when we we used to receive lora packets while CPU was in deep sleep. @@ -185,57 +183,7 @@ void doDeepSleep(uint64_t msecToWake) } #endif - /* - Some ESP32 IOs have internal pullups or pulldowns, which are enabled by default. - If an external circuit drives this pin in deep sleep mode, current consumption may - increase due to current flowing through these pullups and pulldowns. - - To isolate a pin, preventing extra current draw, call rtc_gpio_isolate() function. - For example, on ESP32-WROVER module, GPIO12 is pulled up externally. - GPIO12 also has an internal pulldown in the ESP32 chip. This means that in deep sleep, - some current will flow through these external and internal resistors, increasing deep - sleep current above the minimal possible value. - - Note: we don't isolate pins that are used for the LORA, LED, i2c, spi or the wake button - */ - static const uint8_t rtcGpios[] = {/* 0, */ 2, - /* 4, */ -#ifndef USE_JTAG - 13, - /* 14, */ /* 15, */ -#endif - /* 25, */ 26, /* 27, */ - 32, 33, 34, 35, - 36, 37 - /* 38, 39 */}; - - for (int i = 0; i < sizeof(rtcGpios); i++) - rtc_gpio_isolate((gpio_num_t)rtcGpios[i]); - - // FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using - // to detect wake and in normal operation the external part drives them hard. - - // We want RTC peripherals to stay on - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - -#ifdef BUTTON_PIN - // Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39. - uint64_t gpioMask = (1ULL << BUTTON_PIN); - -#ifdef BUTTON_NEED_PULLUP - gpio_pullup_en((gpio_num_t)BUTTON_PIN); -#endif - - // Not needed because both of the current boards have external pullups - // FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead of - // just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN); - - esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW); -#endif - - esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs - esp_deep_sleep_start(); // TBD mA sleep current (battery) -#endif + cpuDeepSleep(msecToWake); } #ifndef NO_ESP32 diff --git a/src/sleep.h b/src/sleep.h index 800100119..6bcb23352 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -4,7 +4,8 @@ #include "Observer.h" #include "configuration.h" -void doDeepSleep(uint64_t msecToWake); +void doDeepSleep(uint64_t msecToWake), cpuDeepSleep(uint64_t msecToWake); + #ifndef NO_ESP32 #include "esp_sleep.h" esp_sleep_wakeup_cause_t doLightSleep(uint64_t msecToWake);