mirror of
https://github.com/meshtastic/firmware.git
synced 2026-11-07 14:54:44 -05:00
doLightSleep() arms PMU_IRQ with GPIO_INTR_LOW_LEVEL and, unlike every other wake pin it arms, never disarms it. The low-level interrupt type and the wake-enable bit are therefore still set in the GPIO registers once the node is awake, and they stay set for as long as the chip keeps its GPIO peripheral. A panic or software reset does keep it: only a chip-level reset clears those registers. Measured on a T-Beam S3 Supreme (ESP32-S3), reading GPIO_PIN40_REG immediately before the first attachInterrupt() of each boot: 0x00000000 after a flash/chip reset, then 0x00002600 (int_type 4 low level, wakeup_enable 1, int_ena set) on each of three successive CPU-only resets. With this change the same recipe reads 0x00002100 (int_type 2 negedge, wakeup_enable 0) on all three. That leftover level arming is the precondition for the interrupt storm #12131 fixed for DIO1: the first attachInterrupt() of a boot installs the GPIO ISR service inside a critical section, and a level condition that is already true re-enters gpio_intr_service with nothing able to clear it, which ends in an interrupt WDT panic that only an EN reset recovers from. On the boards that define PMU_IRQ that first attach is attachPowerInterrupts(), which is where the panic in #12114 landed. The storm itself was not reproduced here: PMU_IRQ is open-drain with an external pull-up, and the pad read 0xff both as found and with no internal pull on every boot, so the low-level condition was never true across the attach. A VBUS insert or remove asserting the line in the window between axpChipInit() clearing the PMU status and attachPowerInterrupts() would make it true. This closes that window instead of relying on it staying narrow. gpio_wakeup_disable() on its own leaves int_type behind - the #12131 lesson - so the pin is also set back to an edge type, matching what the BUTTON_PIN path above it does. Claude-Session: https://claude.ai/code/session_01GTiifXrVc4YdonmLRCeWhw Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
761 lines
27 KiB
C++
761 lines
27 KiB
C++
#include "configuration.h"
|
|
|
|
#if !MESHTASTIC_EXCLUDE_GPS
|
|
#include "GPS.h"
|
|
#endif
|
|
|
|
#include "Default.h"
|
|
#include "MeshRadio.h"
|
|
#include "MeshService.h"
|
|
#include "NodeDB.h"
|
|
#include "PowerMon.h"
|
|
#include "RadioLibInterface.h"
|
|
#include "TransmitHistory.h"
|
|
#include "detect/LoRaRadioType.h"
|
|
#include "error.h"
|
|
#include "main.h"
|
|
#include "modules/StatusLEDModule.h"
|
|
#include "sleep.h"
|
|
#include "target_specific.h"
|
|
|
|
#ifdef ARCH_ESP32
|
|
#include "esp_pm.h"
|
|
#if HAS_WIFI
|
|
#include "mesh/wifi/WiFiAPClient.h"
|
|
#endif
|
|
#include "rom/rtc.h"
|
|
#include <RadioLib.h>
|
|
#include <driver/rtc_io.h>
|
|
#include <driver/uart.h>
|
|
|
|
esp_sleep_source_t wakeCause; // the reason we booted this time
|
|
#endif
|
|
#include "Throttle.h"
|
|
|
|
#ifdef USE_PCA95X5
|
|
#include PCA95X5_INC
|
|
extern PCA95X5_CLS io;
|
|
#endif
|
|
|
|
#ifdef HAS_PPM
|
|
#include <XPowersLib.h>
|
|
extern XPowersPPM *PPM;
|
|
#endif
|
|
|
|
#ifndef INCLUDE_vTaskSuspend
|
|
#define INCLUDE_vTaskSuspend 0
|
|
#endif
|
|
|
|
/// Called to ask any observers if they want to veto sleep. Return 1 to veto or 0 to allow sleep to happen
|
|
Observable<void *> preflightSleep;
|
|
|
|
/// Called to tell observers we are now entering (deep) sleep and you should prepare. Must return 0
|
|
Observable<void *> notifyDeepSleep;
|
|
|
|
/// Called to tell observers we are rebooting ASAP. Must return 0
|
|
Observable<void *> notifyReboot;
|
|
|
|
#ifdef ARCH_ESP32
|
|
/// Called to tell observers that light sleep is about to begin
|
|
Observable<void *> notifyLightSleep;
|
|
|
|
/// Called to tell observers that light sleep has just ended, and why it ended
|
|
Observable<esp_sleep_wakeup_cause_t> notifyLightSleepEnd;
|
|
#endif
|
|
|
|
// deep sleep support
|
|
RTC_DATA_ATTR int bootCount = 0;
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Application
|
|
// -----------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Control CPU core speed (80MHz vs 240MHz)
|
|
*
|
|
* We leave CPU at full speed during init, but once loop is called switch to low speed (for a 50% power savings)
|
|
*
|
|
*/
|
|
void setCPUFast(bool on)
|
|
{
|
|
#if defined(ARCH_ESP32) && HAS_WIFI && !HAS_TFT && !defined(T_LORA_PAGER) && !defined(T_DECK)
|
|
|
|
if (isWifiAvailable()) {
|
|
/*
|
|
*
|
|
* There's a newly introduced bug in the espressif framework where WiFi is
|
|
* unstable when the frequency is less than 240MHz.
|
|
*
|
|
* This mostly impacts WiFi AP mode but we'll bump the frequency for
|
|
* all WiFi use cases.
|
|
* (Added: Dec 23, 2021 by Jm Casler)
|
|
*/
|
|
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
|
LOG_DEBUG("Set CPU to 240MHz because WiFi is in use");
|
|
setCpuFrequencyMhz(240);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// The Heltec LORA32 V1 runs at 26 MHz base frequency and doesn't react well to switching to 80 MHz...
|
|
#if !defined(ARDUINO_HELTEC_WIFI_LORA_32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
|
setCpuFrequencyMhz(on ? 240 : 80);
|
|
#endif
|
|
|
|
#endif
|
|
}
|
|
|
|
// Perform power on init that we do on each wake from deep sleep
|
|
void initDeepSleep()
|
|
{
|
|
#ifdef ARCH_ESP32
|
|
bootCount++;
|
|
const char *reason;
|
|
wakeCause = esp_sleep_get_wakeup_cause();
|
|
|
|
switch (wakeCause) {
|
|
case ESP_SLEEP_WAKEUP_EXT0:
|
|
reason = "ext0 RTC_IO";
|
|
break;
|
|
case ESP_SLEEP_WAKEUP_EXT1:
|
|
reason = "ext1 RTC_CNTL";
|
|
break;
|
|
case ESP_SLEEP_WAKEUP_TIMER:
|
|
reason = "timer";
|
|
break;
|
|
case ESP_SLEEP_WAKEUP_TOUCHPAD:
|
|
reason = "touchpad";
|
|
break;
|
|
case ESP_SLEEP_WAKEUP_ULP:
|
|
reason = "ULP program";
|
|
break;
|
|
default:
|
|
reason = "reset";
|
|
break;
|
|
}
|
|
/*
|
|
Not using yet because we are using wake on all buttons being low
|
|
|
|
wakeButtons = esp_sleep_get_ext1_wakeup_status(); // If one of these buttons is set it was the reason we woke
|
|
if (wakeCause == ESP_SLEEP_WAKEUP_EXT1 && !wakeButtons) // we must have been using the 'all buttons rule for waking' to
|
|
support busted boards, assume button one was pressed wakeButtons = ((uint64_t)1) << buttons.gpios[0];
|
|
*/
|
|
|
|
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
|
// If we booted because our timer ran out or the user pressed reset, send those as fake events
|
|
RESET_REASON hwReason = rtc_get_reset_reason(0);
|
|
|
|
#ifdef CONFIG_IDF_TARGET_ESP32P4
|
|
if (hwReason == BROWN_OUT_RESET)
|
|
reason = "brownout";
|
|
else if (hwReason == HP_CORE_HP_WDT_RESET)
|
|
reason = "taskWatchdog";
|
|
else if (hwReason == HP_CORE_LP_WDT_RESET)
|
|
reason = "intWatchdog";
|
|
else if (hwReason == CHIP_LP_WDT_RESET)
|
|
reason = "chipWatchdog";
|
|
else if (hwReason == SUPER_WDT_RESET)
|
|
reason = "superWatchdog";
|
|
else if (hwReason == HP_SYS_HP_WDT_RESET)
|
|
reason = "systemWatchdog";
|
|
else if (hwReason == HP_SYS_LP_WDT_RESET)
|
|
reason = "systemLowPowerWatchdog";
|
|
#else
|
|
if (hwReason == RTCWDT_BROWN_OUT_RESET)
|
|
reason = "brownout";
|
|
else if (hwReason == RTCWDT_RTC_RESET)
|
|
reason = "rtcWatchdog";
|
|
else if (hwReason == TG0WDT_SYS_RESET)
|
|
reason = "taskWatchdog";
|
|
else if (hwReason == TG1WDT_SYS_RESET)
|
|
reason = "intWatchdog";
|
|
#endif
|
|
LOG_INFO("Booted, wake cause %d (boot count %d), reset_reason=%s", wakeCause, bootCount, reason);
|
|
#endif
|
|
|
|
#if SOC_RTCIO_HOLD_SUPPORTED
|
|
// If waking from sleep, release any and all RTC GPIOs
|
|
if (wakeCause != ESP_SLEEP_WAKEUP_UNDEFINED) {
|
|
LOG_DEBUG("Disable any holds on RTC IO pads");
|
|
for (uint8_t i = 0; i <= GPIO_NUM_MAX; i++) {
|
|
if (rtc_gpio_is_valid_gpio((gpio_num_t)i))
|
|
rtc_gpio_hold_dis((gpio_num_t)i);
|
|
|
|
// ESP32 (original)
|
|
else if (GPIO_IS_VALID_OUTPUT_GPIO((gpio_num_t)i))
|
|
gpio_hold_dis((gpio_num_t)i);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
}
|
|
|
|
bool doPreflightSleep(bool deepSleep)
|
|
{
|
|
// Observers only get a void*: non-NULL means the hardware (radio) is about to be powered
|
|
// down (deep sleep / shutdown), NULL means a light sleep where the radio keeps running
|
|
static const bool deepSleepFlag = true;
|
|
if (preflightSleep.notifyObservers(deepSleep ? (void *)&deepSleepFlag : NULL) != 0)
|
|
return false; // vetoed
|
|
else
|
|
return true;
|
|
}
|
|
|
|
/// Tell devices we are going to sleep and wait for them to handle things
|
|
static void waitEnterSleep(bool skipPreflight, bool deepSleep)
|
|
{
|
|
if (!skipPreflight) {
|
|
uint32_t now = millis();
|
|
while (!doPreflightSleep(deepSleep)) {
|
|
delay(100); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives)
|
|
|
|
if (!Throttle::isWithinTimespanMs(now,
|
|
THIRTY_SECONDS_MS)) { // If we wait too long just report an error and go to sleep
|
|
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_SLEEP_ENTER_WAIT);
|
|
assert(0); // FIXME - for now we just restart, need to fix bug #167
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Code that still needs to be moved into notifyObservers
|
|
console->flush(); // send all our characters before we stop cpu clock
|
|
setBluetoothEnable(false); // has to be off before calling light sleep
|
|
}
|
|
|
|
void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveNodeDb = false)
|
|
{
|
|
if (INCLUDE_vTaskSuspend && (msecToWake == portMAX_DELAY)) {
|
|
LOG_INFO("Enter deep sleep forever");
|
|
} else {
|
|
LOG_INFO("Enter deep sleep for %u seconds", msecToWake / 1000);
|
|
}
|
|
|
|
// not using wifi yet, but once we are this is needed to shutoff the radio hw
|
|
// esp_wifi_stop();
|
|
waitEnterSleep(skipPreflight, true);
|
|
|
|
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH
|
|
// Full shutdown of bluetooth hardware
|
|
if (nimbleBluetooth)
|
|
nimbleBluetooth->deinit();
|
|
#endif
|
|
|
|
#ifdef ARCH_ESP32
|
|
if (!shouldLoraWake(msecToWake))
|
|
notifyDeepSleep.notifyObservers(NULL);
|
|
#else
|
|
notifyDeepSleep.notifyObservers(NULL);
|
|
#endif
|
|
|
|
powerMon->setState(meshtastic_PowerMon_State_CPU_DeepSleep);
|
|
if (screen)
|
|
screen->doDeepSleep(); // datasheet says this will draw only 10ua
|
|
|
|
if (!skipSaveNodeDb) {
|
|
nodeDB->saveToDisk();
|
|
}
|
|
|
|
// Persist broadcast transmit times so throttle survives reboot
|
|
if (transmitHistory)
|
|
transmitHistory->saveToDisk();
|
|
|
|
#ifdef PIN_POWER_EN
|
|
digitalWrite(PIN_POWER_EN, LOW);
|
|
pinMode(PIN_POWER_EN, INPUT); // power off peripherals
|
|
#endif
|
|
|
|
#ifdef RAK_WISMESH_TAP_V2
|
|
digitalWrite(SDCARD_CS, LOW);
|
|
#endif
|
|
|
|
#if defined(TRACKER_T1000_E) || defined(MESH_TRACKER_X1)
|
|
digitalWrite(GPS_VRTC_EN, LOW);
|
|
digitalWrite(PIN_GPS_RESET, LOW);
|
|
digitalWrite(GPS_SLEEP_INT, LOW);
|
|
digitalWrite(GPS_RTC_INT, LOW);
|
|
#ifdef GPS_RESETB_OUT
|
|
pinMode(GPS_RESETB_OUT, OUTPUT);
|
|
digitalWrite(GPS_RESETB_OUT, LOW);
|
|
#endif
|
|
|
|
#ifdef BUZZER_EN_PIN
|
|
digitalWrite(BUZZER_EN_PIN, LOW);
|
|
#endif
|
|
|
|
#ifdef PIN_DRV_EN
|
|
digitalWrite(PIN_DRV_EN, LOW);
|
|
#endif
|
|
|
|
#ifdef PIN_3V3_EN
|
|
digitalWrite(PIN_3V3_EN, LOW);
|
|
#endif
|
|
#ifdef PIN_WD_EN
|
|
digitalWrite(PIN_WD_EN, LOW);
|
|
#endif
|
|
#endif
|
|
statusLEDModule->setPowerLED(false);
|
|
#ifdef RESET_OLED
|
|
digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power
|
|
#endif
|
|
|
|
#if defined(VEXT_ENABLE)
|
|
digitalWrite(VEXT_ENABLE, !VEXT_ON_VALUE); // turn on the display power
|
|
#endif
|
|
|
|
#ifdef ARCH_ESP32
|
|
if (shouldLoraWake(msecToWake)) {
|
|
enableLoraInterrupt();
|
|
}
|
|
#ifdef BUTTON_PIN
|
|
// Avoid leakage through button pin
|
|
if (GPIO_IS_VALID_OUTPUT_GPIO(BUTTON_PIN)) {
|
|
#ifdef BUTTON_NEED_PULLUP
|
|
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
|
#else
|
|
pinMode(BUTTON_PIN, INPUT);
|
|
#endif
|
|
// A held pad ignores ext1_wakeup_prepare()'s re-route to RTC, so never hold the pin we wake on.
|
|
if (config.device.button_gpio && config.device.button_gpio != BUTTON_PIN)
|
|
gpio_hold_en((gpio_num_t)BUTTON_PIN);
|
|
}
|
|
#endif
|
|
#ifdef SENSECAP_INDICATOR
|
|
// Portexpander definition does not pass GPIO_IS_VALID_OUTPUT_GPIO
|
|
pinMode(LORA_CS, OUTPUT);
|
|
digitalWrite(LORA_CS, HIGH);
|
|
gpio_hold_en((gpio_num_t)LORA_CS);
|
|
#elif defined(ELECROW_PANEL)
|
|
// Elecrow panels do not use LORA_CS, do nothing
|
|
#else
|
|
if (GPIO_IS_VALID_OUTPUT_GPIO(LORA_CS)) {
|
|
// LoRa CS (RADIO_NSS) needs to stay HIGH, even during deep sleep
|
|
pinMode(LORA_CS, OUTPUT);
|
|
digitalWrite(LORA_CS, HIGH);
|
|
gpio_hold_en((gpio_num_t)LORA_CS);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef HAS_PPM
|
|
if (PPM) {
|
|
// BQ25896 PMIC shutdown is a hard power-off state.
|
|
// Only use it for "sleep forever" / explicit shutdown, because timed deep sleep
|
|
// must remain wakeable by RTC timer.
|
|
if (msecToWake == portMAX_DELAY) {
|
|
LOG_INFO("PPM shutdown");
|
|
console->flush();
|
|
PPM->shutdown();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAS_PMU
|
|
if (pmu_found && PMU) {
|
|
// Obsolete comment: from back when we we used to receive lora packets while CPU was in deep sleep.
|
|
// We no longer do that, because our light-sleep current draws are low enough and it provides fast start/low cost
|
|
// wake. We currently use deep sleep only for 'we want our device to actually be off - because our battery is
|
|
// critically low'. So in deep sleep we DO shut down power to LORA (and when we boot later we completely reinit it)
|
|
//
|
|
// No need to turn this off if the power draw in sleep mode really is just 0.2uA and turning it off would
|
|
// leave floating input for the IRQ line
|
|
// If we want to leave the radio receiving in would be 11.5mA current draw, but most of the time it is just waiting
|
|
// in its sequencer (true?) so the average power draw should be much lower even if we were listening for packets
|
|
// all the time.
|
|
PMU->setChargingLedMode(XPOWERS_CHG_LED_OFF);
|
|
|
|
uint8_t model = PMU->getChipModel();
|
|
if (model == XPOWERS_AXP2101) {
|
|
if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) {
|
|
// t-beam v1.2 radio power channel
|
|
PMU->disablePowerOutput(XPOWERS_ALDO2); // lora radio power channel
|
|
} else if (HW_VENDOR == meshtastic_HardwareModel_LILYGO_TBEAM_S3_CORE ||
|
|
HW_VENDOR == meshtastic_HardwareModel_T_WATCH_S3 || HW_VENDOR == meshtastic_HardwareModel_T_WATCH_ULTRA) {
|
|
PMU->disablePowerOutput(XPOWERS_ALDO3); // lora radio power channel
|
|
}
|
|
} else if (model == XPOWERS_AXP192) {
|
|
// t-beam v1.1 radio power channel
|
|
PMU->disablePowerOutput(XPOWERS_LDO2); // lora radio power channel
|
|
}
|
|
if (msecToWake == portMAX_DELAY) {
|
|
LOG_INFO("PMU shutdown");
|
|
console->flush();
|
|
PMU->shutdown();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if !MESHTASTIC_EXCLUDE_I2C && defined(ARCH_ESP32) && defined(I2C_SDA)
|
|
// Added by https://github.com/meshtastic/firmware/pull/4418
|
|
// Possibly to support Heltec Capsule Sensor?
|
|
Wire.end();
|
|
pinMode(I2C_SDA, ANALOG);
|
|
pinMode(I2C_SCL, ANALOG);
|
|
#endif
|
|
|
|
console->flush();
|
|
cpuDeepSleep(msecToWake);
|
|
}
|
|
|
|
#ifdef ARCH_ESP32
|
|
/**
|
|
* The GPIO an SX126x/SX128x/LR part raises its IRQ on, or -1 if there is no usable pin.
|
|
*
|
|
* Nearly every variant spells it LORA_DIO1, but an SX1280 board may define SX128X_DIO1 on a different
|
|
* pin (t-eth-elite, tlora_t3s3_v1) or as the only one of the two (tlora_v2_1_18, betafpv_2400_tx_micro),
|
|
* because that is the name RadioInterface hands to the driver. Resolve it per radio rather than per
|
|
* macro, so arming, polling and teardown below cannot pick different pins. RF95 keeps RF95_IRQ.
|
|
*/
|
|
static int loraWakeDio1Pin()
|
|
{
|
|
#if defined(SX128X_DIO1) && (SX128X_DIO1 != RADIOLIB_NC)
|
|
if (radioType == SX1280_RADIO)
|
|
return (int)SX128X_DIO1;
|
|
#endif
|
|
#if defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC)
|
|
return (int)LORA_DIO1;
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Is the LoRa IRQ line already asserted right now?
|
|
*
|
|
* DIO1 (SX126x/SX128x) and RF95_IRQ are active-high, so a HIGH level means the radio has already
|
|
* latched an interrupt (typically an RX_DONE). If that edge lands in the small window between
|
|
* enableLoraInterrupt() and esp_light_sleep_start(), the DIO ISR fires and detachInterrupt() clears
|
|
* the per-pin GPIO wake enable as a side effect - the node would then sleep with the packet latched
|
|
* but the wake source disarmed, stranding it for the rest of the sleep chunk (up to 30s). Reading the
|
|
* raw line just before sleeping lets us skip that sleep and let the main loop service the packet.
|
|
* Boards where DIO1 is a virtual/expander pin never reach here - doLightSleep() returns early on
|
|
* LORA_DIO1_EXTENDED_IO before this is called.
|
|
*/
|
|
static bool loraWakeIrqAsserted()
|
|
{
|
|
#if defined(RF95_IRQ) && (RF95_IRQ != RADIOLIB_NC)
|
|
if (radioType == RF95_RADIO)
|
|
return gpio_get_level((gpio_num_t)RF95_IRQ) == 1;
|
|
#endif
|
|
const int dio1 = loraWakeDio1Pin();
|
|
if (dio1 >= 0 && radioType != RF95_RADIO)
|
|
return gpio_get_level((gpio_num_t)dio1) == 1;
|
|
return false;
|
|
}
|
|
|
|
// The radio ISR is rising-edge and stays attached with the RX readout task. As a level wake source it would fire
|
|
// nonstop, starving the readout task that clears the IRQ, so mask it while armed and restore the edge type after.
|
|
static void armLoraWakePin(gpio_num_t pin)
|
|
{
|
|
gpio_intr_disable(pin);
|
|
gpio_wakeup_enable(pin, GPIO_INTR_HIGH_LEVEL);
|
|
}
|
|
|
|
static void restoreLoraIrqPin(gpio_num_t pin)
|
|
{
|
|
gpio_wakeup_disable(pin); // leaves the level type in place
|
|
gpio_set_intr_type(pin, GPIO_INTR_POSEDGE);
|
|
gpio_intr_enable(pin);
|
|
// The edge that woke us was consumed by the sleep; deliver an IRQ that is already pending.
|
|
if (gpio_get_level(pin) == 1 && RadioLibInterface::instance)
|
|
RadioLibInterface::instance->pollMissedIrqs();
|
|
}
|
|
|
|
// Consecutive sleeps skipped because the LoRa IRQ was already asserted. A line stuck high would
|
|
// otherwise stop the node sleeping for good, so give up on skipping once it stops looking transient.
|
|
static uint8_t loraIrqSkippedSleeps = 0;
|
|
#define LORA_IRQ_SKIP_SLEEP_WARN 3
|
|
#define LORA_IRQ_SKIP_SLEEP_MAX 5
|
|
|
|
/**
|
|
* enter light sleep (preserves ram but stops everything about CPU).
|
|
*
|
|
* Returns (after restoring hw state) when the user presses a button or we get a LoRa interrupt
|
|
*/
|
|
esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more reasonable default
|
|
{
|
|
// LOG_DEBUG("Enter light sleep");
|
|
|
|
// LORA_DIO1 is an extended IO pin (on an I/O expander). Setting it as a wake-up pin will cause problems,
|
|
// such as the device not entering light sleep. Boards opt in with LORA_DIO1_EXTENDED_IO in their variant.
|
|
#if defined(LORA_DIO1_EXTENDED_IO)
|
|
return ESP_SLEEP_WAKEUP_TIMER;
|
|
#endif
|
|
|
|
waitEnterSleep(false, false);
|
|
notifyLightSleep.notifyObservers(NULL); // Button interrupts are detached here
|
|
|
|
uint64_t sleepUsec = sleepMsec * 1000LL;
|
|
|
|
// NOTE! ESP docs say we must disable bluetooth and wifi before light sleep
|
|
|
|
#if SOC_PM_SUPPORT_RTC_PERIPH_PD
|
|
// We want RTC peripherals to stay on
|
|
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
|
#endif
|
|
|
|
#if defined(BUTTON_PIN) && defined(BUTTON_NEED_PULLUP)
|
|
gpio_pullup_en((gpio_num_t)BUTTON_PIN);
|
|
#endif
|
|
|
|
#ifdef SERIAL0_RX_GPIO
|
|
// We treat the serial port as a GPIO for a fast/low power way of waking, if we see a rising edge that means
|
|
// someone started to send something
|
|
|
|
// gpio 3 is RXD for serialport 0 on ESP32
|
|
// Send a few Z characters to wake the port
|
|
|
|
// this doesn't work on TBEAMs when the USB is depowered (causes bogus interrupts)
|
|
// So we disable this "wake on serial" feature - because now when a TBEAM (only) has power connected it
|
|
// never tries to go to sleep if the user is using the API
|
|
// gpio_wakeup_enable((gpio_num_t)SERIAL0_RX_GPIO, GPIO_INTR_LOW_LEVEL);
|
|
|
|
// doesn't help - I think the USB-UART chip losing power is pulling the signal low
|
|
// gpio_pullup_en((gpio_num_t)SERIAL0_RX_GPIO);
|
|
|
|
// alas - can only work if using the refclock, which is limited to about 9600 bps
|
|
// assert(uart_set_wakeup_threshold(UART_NUM_0, 3) == ESP_OK);
|
|
// assert(esp_sleep_enable_uart_wakeup(0) == ESP_OK);
|
|
#endif
|
|
#ifdef ROTARY_PRESS
|
|
// The enableLoraInterrupt() method is using ext0_wakeup, so we are forced to use GPIO wakeup
|
|
gpio_wakeup_enable((gpio_num_t)ROTARY_PRESS, GPIO_INTR_LOW_LEVEL);
|
|
#endif
|
|
#ifdef KB_INT
|
|
#if KB_INT_WAKE_ON_HIGH
|
|
gpio_wakeup_enable((gpio_num_t)KB_INT, GPIO_INTR_HIGH_LEVEL);
|
|
#else
|
|
gpio_wakeup_enable((gpio_num_t)KB_INT, GPIO_INTR_LOW_LEVEL);
|
|
#endif // KB_INT_WAKE_ON_HIGH
|
|
#endif // KB_INT
|
|
#ifdef BOARD_PCA9535_INT
|
|
// Side-key interrupt line from PCA9535 expander (active low).
|
|
gpio_wakeup_enable((gpio_num_t)BOARD_PCA9535_INT, GPIO_INTR_LOW_LEVEL);
|
|
#endif
|
|
#ifdef BUTTON_PIN
|
|
gpio_num_t pin = (gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
|
|
gpio_wakeup_enable(pin, GPIO_INTR_LOW_LEVEL);
|
|
#endif
|
|
#if defined(INPUTDRIVER_TWO_WAY_ROCKER_BTN) || defined(INPUTDRIVER_ENCODER_BTN)
|
|
#if defined(INPUTDRIVER_TWO_WAY_ROCKER_BTN)
|
|
#define INPUTDRIVER_WAKE_BTN_PIN INPUTDRIVER_TWO_WAY_ROCKER_BTN
|
|
#else
|
|
#define INPUTDRIVER_WAKE_BTN_PIN INPUTDRIVER_ENCODER_BTN
|
|
#endif
|
|
// Most of these switches idle high and pull to ground, but not all. Arming the wrong level on a
|
|
// button that idles low means the wake condition is already true, and light sleep ends the
|
|
// instant it begins. Defaults to low, so only a board that says otherwise changes behaviour.
|
|
#if defined(INPUTDRIVER_ENCODER_BTN_ACTIVE_LOW) && !INPUTDRIVER_ENCODER_BTN_ACTIVE_LOW
|
|
gpio_wakeup_enable((gpio_num_t)INPUTDRIVER_WAKE_BTN_PIN, GPIO_INTR_HIGH_LEVEL);
|
|
#else
|
|
gpio_wakeup_enable((gpio_num_t)INPUTDRIVER_WAKE_BTN_PIN, GPIO_INTR_LOW_LEVEL);
|
|
#endif
|
|
#endif
|
|
#if defined(WAKE_ON_TOUCH)
|
|
gpio_wakeup_enable((gpio_num_t)SCREEN_TOUCH_INT, GPIO_INTR_LOW_LEVEL);
|
|
#endif
|
|
#ifdef MOTION_WAKE_INT_PIN
|
|
// Only arm motion wake when the user asked for it, otherwise every tilt costs a wakeup.
|
|
if (config.display.wake_on_tap_or_motion)
|
|
gpio_wakeup_enable((gpio_num_t)MOTION_WAKE_INT_PIN,
|
|
MOTION_WAKE_INT_ACTIVE_HIGH ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL);
|
|
#endif
|
|
enableLoraInterrupt();
|
|
#ifdef PMU_IRQ
|
|
// wake due to PMU can happen repeatedly if there is no battery installed or the battery fills
|
|
if (pmu_found)
|
|
gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_LOW_LEVEL); // pmu irq
|
|
#endif
|
|
|
|
auto res = esp_sleep_enable_gpio_wakeup();
|
|
if (res != ESP_OK) {
|
|
LOG_ERROR("esp_sleep_enable_gpio_wakeup result %d", res);
|
|
}
|
|
assert(res == ESP_OK);
|
|
res = esp_sleep_enable_timer_wakeup(sleepUsec);
|
|
if (res != ESP_OK) {
|
|
LOG_ERROR("esp_sleep_enable_timer_wakeup result %d", res);
|
|
}
|
|
assert(res == ESP_OK);
|
|
|
|
console->flush();
|
|
const bool loraIrqPending = loraWakeIrqAsserted() && loraIrqSkippedSleeps < LORA_IRQ_SKIP_SLEEP_MAX;
|
|
if (loraIrqPending) {
|
|
// A LoRa IRQ is already pending (see loraWakeIrqAsserted). Don't sleep on top of it - return so
|
|
// the main loop services the packet now instead of stranding it until the next wake.
|
|
if (++loraIrqSkippedSleeps >= LORA_IRQ_SKIP_SLEEP_WARN)
|
|
LOG_WARN("LoRa IRQ still pending after %u skipped sleeps", loraIrqSkippedSleeps);
|
|
else
|
|
LOG_INFO("LoRa IRQ pending, skip light sleep to service packet");
|
|
res = ESP_OK;
|
|
} else {
|
|
// Either nothing pending, or the line has stayed high past LORA_IRQ_SKIP_SLEEP_MAX and the main
|
|
// loop is clearly not going to clear it. Never sleeping costs more than one stranded packet.
|
|
loraIrqSkippedSleeps = 0;
|
|
res = esp_light_sleep_start();
|
|
}
|
|
if (res != ESP_OK) {
|
|
LOG_ERROR("esp_light_sleep_start result %d", res);
|
|
}
|
|
// commented out because it's not that crucial;
|
|
// if it sporadically happens the node will go into light sleep during the next round
|
|
// assert(res == ESP_OK);
|
|
#ifdef ROTARY_PRESS
|
|
gpio_wakeup_disable((gpio_num_t)ROTARY_PRESS);
|
|
#endif
|
|
#ifdef KB_INT
|
|
gpio_wakeup_disable((gpio_num_t)KB_INT);
|
|
#endif
|
|
#ifdef BOARD_PCA9535_INT
|
|
gpio_wakeup_disable((gpio_num_t)BOARD_PCA9535_INT);
|
|
#endif
|
|
#ifdef BUTTON_PIN
|
|
// Disable wake-on-button interrupt. Re-attach normal button-interrupts
|
|
gpio_wakeup_disable(pin);
|
|
#endif
|
|
#ifdef PMU_IRQ
|
|
// Undo the arming above. PMU_IRQ was the only light-sleep wake pin left armed after the sleep, so
|
|
// its GPIO_INTR_LOW_LEVEL type and wake-enable bit stayed in the GPIO registers indefinitely. A
|
|
// panic or software reset is CPU-only and leaves those registers intact, so the next boot's first
|
|
// attachInterrupt() - attachPowerInterrupts() on the boards that define PMU_IRQ - installs the ISR
|
|
// service with a level condition already pending and nothing to clear it. gpio_wakeup_disable()
|
|
// alone leaves int_type behind, so set an edge type too, matching what the button path does.
|
|
if (pmu_found) {
|
|
gpio_wakeup_disable((gpio_num_t)PMU_IRQ);
|
|
gpio_set_intr_type((gpio_num_t)PMU_IRQ, GPIO_INTR_NEGEDGE);
|
|
}
|
|
#endif
|
|
#ifdef INPUTDRIVER_WAKE_BTN_PIN
|
|
gpio_wakeup_disable((gpio_num_t)INPUTDRIVER_WAKE_BTN_PIN);
|
|
#undef INPUTDRIVER_WAKE_BTN_PIN
|
|
#endif
|
|
#if defined(WAKE_ON_TOUCH)
|
|
gpio_wakeup_disable((gpio_num_t)SCREEN_TOUCH_INT);
|
|
#endif
|
|
#ifdef MOTION_WAKE_INT_PIN
|
|
// Unconditional: the config can have changed while we were asleep.
|
|
gpio_wakeup_disable((gpio_num_t)MOTION_WAKE_INT_PIN);
|
|
#endif
|
|
#if defined(LORA_DIO1) || defined(SX128X_DIO1)
|
|
const int wokeDio1 = loraWakeDio1Pin(); // must match what enableLoraInterrupt() armed
|
|
#if SOC_PM_SUPPORT_EXT_WAKEUP
|
|
if (wokeDio1 >= 0 && radioType != RF95_RADIO) {
|
|
#else
|
|
if (wokeDio1 >= 0 && radioType != RF95_RADIO) {
|
|
#endif
|
|
restoreLoraIrqPin((gpio_num_t)wokeDio1);
|
|
}
|
|
#endif
|
|
#if defined(RF95_IRQ) && (RF95_IRQ != RADIOLIB_NC)
|
|
if (radioType == RF95_RADIO) {
|
|
restoreLoraIrqPin((gpio_num_t)RF95_IRQ);
|
|
}
|
|
#endif
|
|
|
|
// When we skipped the sleep because a LoRa IRQ was already pending, esp_sleep_get_wakeup_cause()
|
|
// would report the previous real sleep's cause (e.g. TIMER); synthesize a GPIO cause so the
|
|
// handling below (and PowerFSM) takes the packet-service path instead of a stale timer path.
|
|
esp_sleep_wakeup_cause_t cause = loraIrqPending ? ESP_SLEEP_WAKEUP_GPIO : esp_sleep_get_wakeup_cause();
|
|
notifyLightSleepEnd.notifyObservers(cause); // Button interrupts are reattached here
|
|
|
|
if (cause == ESP_SLEEP_WAKEUP_GPIO) {
|
|
LOG_INFO("Exit light sleep gpio");
|
|
// If we woke because of a GPIO, it's possible power needs to run to handle.
|
|
power->setIntervalFromNow(0);
|
|
runASAP = true;
|
|
} else {
|
|
LOG_INFO("Exit light sleep cause: %d", cause);
|
|
}
|
|
|
|
return cause;
|
|
}
|
|
|
|
// not legal on the stock android ESP build
|
|
|
|
/**
|
|
* enable modem sleep mode as needed and available. Should lower our CPU current draw to an average of about 20mA.
|
|
*
|
|
* per https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/power_management.html
|
|
*
|
|
* supposedly according to https://github.com/espressif/arduino-esp32/issues/475 this is already done in arduino
|
|
*/
|
|
void enableModemSleep()
|
|
{
|
|
static esp_pm_config_t esp32_config; // filled with zeros because bss
|
|
#if CONFIG_IDF_TARGET_ESP32S3
|
|
esp32_config.max_freq_mhz = CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ;
|
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
|
esp32_config.max_freq_mhz = CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ;
|
|
#elif CONFIG_IDF_TARGET_ESP32C6
|
|
esp32_config.max_freq_mhz = CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ;
|
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
|
esp32_config.max_freq_mhz = CONFIG_ESP32C3_DEFAULT_CPU_FREQ_MHZ;
|
|
#elif CONFIG_IDF_TARGET_ESP32P4
|
|
#if CONFIG_ESP32P4_REV_MIN_FULL < 300
|
|
esp32_config.max_freq_mhz = 360;
|
|
#else
|
|
esp32_config.max_freq_mhz = 400;
|
|
#endif
|
|
#else
|
|
esp32_config.max_freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ;
|
|
#endif
|
|
esp32_config.min_freq_mhz = 20; // 10Mhz is minimum recommended
|
|
esp32_config.light_sleep_enable = false;
|
|
int rv = esp_pm_configure(&esp32_config);
|
|
LOG_DEBUG("Sleep request result %x", rv);
|
|
}
|
|
|
|
bool shouldLoraWake(uint32_t msecToWake)
|
|
{
|
|
return msecToWake < portMAX_DELAY && (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
|
|
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE);
|
|
}
|
|
|
|
void enableLoraInterrupt()
|
|
{
|
|
#if defined(LORA_DIO1_EXTENDED_IO)
|
|
// DIO1 is a virtual pin on an I/O expander - it cannot be a GPIO wakeup source
|
|
#elif SOC_PM_SUPPORT_EXT_WAKEUP && (defined(LORA_DIO1) || defined(SX128X_DIO1))
|
|
const int loraDio1 = loraWakeDio1Pin();
|
|
esp_err_t res;
|
|
if (loraDio1 >= 0 && radioType != RF95_RADIO) {
|
|
res = gpio_pulldown_en((gpio_num_t)loraDio1);
|
|
if (res != ESP_OK) {
|
|
LOG_ERROR("gpio_pulldown_en(lora dio1) result %d", res);
|
|
}
|
|
}
|
|
#if defined(LORA_RESET) && (LORA_RESET != RADIOLIB_NC)
|
|
res = gpio_pullup_en((gpio_num_t)LORA_RESET);
|
|
if (res != ESP_OK) {
|
|
LOG_ERROR("gpio_pullup_en(LORA_RESET) result %d", res);
|
|
}
|
|
#endif
|
|
#if defined(LORA_CS) && (LORA_CS != RADIOLIB_NC) && !defined(ELECROW_PANEL)
|
|
gpio_pullup_en((gpio_num_t)LORA_CS);
|
|
#endif
|
|
|
|
#if HAS_LORA_FEM
|
|
loraFEMInterface.setRxModeEnableWhenMCUSleep();
|
|
#endif
|
|
|
|
if (loraDio1 >= 0 && radioType != RF95_RADIO) {
|
|
LOG_INFO("Wake on LoRa DIO1 (GPIO%02d) gpio interrupt", loraDio1);
|
|
armLoraWakePin((gpio_num_t)loraDio1);
|
|
}
|
|
|
|
#elif defined(LORA_DIO1) || defined(SX128X_DIO1)
|
|
const int loraDio1 = loraWakeDio1Pin();
|
|
if (loraDio1 >= 0 && radioType != RF95_RADIO) {
|
|
LOG_INFO("Wake on LoRa DIO1 (GPIO%02d) gpio interrupt", loraDio1);
|
|
armLoraWakePin((gpio_num_t)loraDio1); // SX126x/SX128x interrupt, active high
|
|
}
|
|
#endif
|
|
#if defined(RF95_IRQ) && (RF95_IRQ != RADIOLIB_NC)
|
|
if (radioType == RF95_RADIO) {
|
|
LOG_INFO("Wake on RF95_IRQ (GPIO%02d) gpio interrupt", RF95_IRQ);
|
|
armLoraWakePin((gpio_num_t)RF95_IRQ); // RF95 interrupt, active high
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|