diff --git a/bin/version.sh b/bin/version.sh index 1a7f96efd..8a7fb3ca3 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=1.1.7 \ No newline at end of file +export VERSION=1.1.8 \ No newline at end of file diff --git a/boards/nrf52840_dk_modified.json b/boards/nrf52840_dk_modified.json index a11ea02c5..cbd246480 100644 --- a/boards/nrf52840_dk_modified.json +++ b/boards/nrf52840_dk_modified.json @@ -1,7 +1,7 @@ { "build": { "arduino": { - "ldscript": "nrf52840_s140_v6.ld" + "ldscript": "nrf52840_s113_v7.ld" }, "core": "nRF5", "cpu": "cortex-m4", @@ -16,9 +16,9 @@ "name": "adafruit" }, "softdevice": { - "sd_flags": "-DS140", - "sd_name": "s140", - "sd_version": "6.1.1", + "sd_flags": "-DS113", + "sd_name": "s113", + "sd_version": "7.2.0", "sd_fwid": "0x00B6" }, "bootloader": { 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/docs/software/pinetab.md b/docs/software/pinetab.md index ce5d3e753..21600716a 100644 --- a/docs/software/pinetab.md +++ b/docs/software/pinetab.md @@ -14,6 +14,13 @@ Notes here on using that driver: https://www.linuxquestions.org/questions/linux- Or if **absolutely** necessary could bitbang: https://www.cnx-software.com/2018/02/16/wch-ch341-usb-to-serial-chip-gets-linux-driver-to-control-gpios-over-usb/ +## Portduino tasks + +* How to access spi devices via ioctl (spidev): https://www.raspberrypi.org/documentation/hardware/raspberrypi/spi/README.md#:~:text=Troubleshooting-,Overview,bus)%2C%20UARTs%2C%20etc. +* access gpio via libgpiod? +* Use dkms to distribute driver? +* echo 100 > /sys/module/spi_ch341_usb/parameters/poll_period + ## Task list * Port meshtastic to build (under platformio) for a poxix target. spec: no screen, no gpios, sim network interface, posix threads, posix semaphores & queues, IO to the console only 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..25e7e1a30 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -9,6 +9,7 @@ #include "utils.h" #include #include +#include void getMacAddr(uint8_t *dmac) { @@ -86,4 +87,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..314b4c313 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -25,9 +25,15 @@ uint8_t GPS::i2cAddress = 0; GPS *gps; +/// Multiple GPS instances might use the same serial port (in sequence), but we can +/// only init that port once. +static bool didSerialInit; + bool GPS::setupGPS() { - if (_serial_gps) { + if (_serial_gps && !didSerialInit) { + didSerialInit = true; + #ifdef GPS_RX_PIN _serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN); #else @@ -59,8 +65,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 +283,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/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 41d000d69..63c2469f2 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -236,20 +236,25 @@ void RadioLibInterface::startTransmitTimer(bool withDelay) void RadioLibInterface::handleTransmitInterrupt() { // DEBUG_MSG("handling lora TX interrupt\n"); - assert(sendingPacket); // Were we sending? - FIXME, this was null coming out of light sleep due to RF95 ISR! - - completeSending(); + // This can be null if we forced the device to enter standby mode. In that case + // ignore the transmit interrupt + if(sendingPacket) + completeSending(); } void RadioLibInterface::completeSending() { - if (sendingPacket) { + // We are careful to clear sending packet before calling printPacket because + // that can take a long time + auto p = sendingPacket; + sendingPacket = NULL; + + if (p) { txGood++; - printPacket("Completed sending", sendingPacket); + printPacket("Completed sending", p); // We are done sending that packet, release it - packetPool.release(sendingPacket); - sendingPacket = NULL; + packetPool.release(p); // DEBUG_MSG("Done with send\n"); } } diff --git a/src/mesh/SX1262Interface.cpp b/src/mesh/SX1262Interface.cpp index 75f60128d..0a23261b5 100644 --- a/src/mesh/SX1262Interface.cpp +++ b/src/mesh/SX1262Interface.cpp @@ -201,9 +201,21 @@ bool SX1262Interface::isActivelyReceiving() bool SX1262Interface::sleep() { - // put chipset into sleep mode - disableInterrupt(); - lora.sleep(); + // Not keeping config is busted - next time nrf52 board boots lora sending fails tcxo related? - see datasheet + DEBUG_MSG("sx1262 entering sleep mode (FIXME, don't keep config)\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 (we've already disabled interrupts by now) + bool keepConfig = true; + lora.sleep(keepConfig); // 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/portduino/PortduinoGlue.cpp b/src/portduino/PortduinoGlue.cpp index 690af6071..0862e33f9 100644 --- a/src/portduino/PortduinoGlue.cpp +++ b/src/portduino/PortduinoGlue.cpp @@ -1,6 +1,7 @@ #include "CryptoEngine.h" #include "target_specific.h" #include +#include "sleep.h" // FIXME - move getMacAddr/setBluetoothEnable into a HALPlatform class @@ -26,6 +27,10 @@ void setBluetoothEnable(bool on) notImplemented("setBluetoothEnable"); } +void cpuDeepSleep(uint64_t msecs) { + notImplemented("cpuDeepSleep"); +} + // FIXME - implement real crypto for linux CryptoEngine *crypto = new CryptoEngine(); 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); diff --git a/variants/eink/variant.h b/variants/eink/variant.h index a37c50aba..5ce975fb5 100644 --- a/variants/eink/variant.h +++ b/variants/eink/variant.h @@ -205,6 +205,7 @@ External serial flash WP25R1635FZUIL0 #define PIN_EINK_MOSI (0 + 29) // also called SDI // Controls power for the eink display - Board power is enabled either by VBUS from USB or the CPU asserting PWR_ON +// FIXME - I think this is actually just the board power enable - it enables power to the CPU also #define PIN_EINK_PWR_ON (0 + 12) #define HAS_EINK @@ -242,7 +243,7 @@ External serial flash WP25R1635FZUIL0 #define PIN_SPI_SCK (0 + 19) // To debug via the segger JLINK console rather than the CDC-ACM serial device -// #define USE_SEGGER +#define USE_SEGGER #ifdef __cplusplus }