Jelajahi Sumber

Merge pull request #1687 from IoTThinks/MCdev-PowerSaving-for-all-esp32-repeaters-202602

Added PowerSaving for all ESP32-based repeaters
ripplebiz 2 bulan lalu
induk
melakukan
999e20a4c2

+ 7 - 15
examples/simple_repeater/main.cpp

@@ -20,8 +20,7 @@ void halt() {
 static char command[160];
 
 // For power saving
-unsigned long lastActive = 0; // mark last active time
-unsigned long nextSleepinSecs = 120; // next sleep in seconds. The first sleep (if enabled) is after 2 minutes from boot
+unsigned long POWERSAVING_FIRSTSLEEP_SECS = 120; // The first sleep (if enabled) from boot
 
 #if defined(PIN_USER_BTN) && defined(_SEEED_SENSECAP_SOLAR_H_)
 static unsigned long userBtnDownAt = 0;
@@ -40,9 +39,6 @@ void setup() {
   delay(5000);
 #endif
 
-  // For power saving
-  lastActive = millis(); // mark last active time since boot
-
 #ifdef DISPLAY_CLASS
   if (display.begin()) {
     display.startFrame();
@@ -157,16 +153,12 @@ void loop() {
   rtc_clock.tick();
 
   if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) {
-    #if defined(NRF52_PLATFORM)
-    board.sleep(1800); // nrf ignores seconds param, sleeps whenever possible
-    #else
-    if (the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { // To check if it is time to sleep
-      board.sleep(1800);             // To sleep. Wake up after 30 minutes or when receiving a LoRa packet
-      lastActive = millis();
-      nextSleepinSecs = 5;  // Default: To work for 5s and sleep again
-    } else {
-      nextSleepinSecs += 5; // When there is pending work, to work another 5s
+#if defined(NRF52_PLATFORM)
+    board.sleep(0); // nrf ignores seconds param, sleeps whenever possible
+#else
+    if (the_mesh.millisHasNowPassed(POWERSAVING_FIRSTSLEEP_SECS * 1000)) { // To check if it is time to sleep
+      board.sleep(30); // Sleep. Wake up after a while or when receiving a LoRa packet
     }
-    #endif
+#endif
   }
 }

+ 1 - 0
src/MeshCore.h

@@ -56,6 +56,7 @@ public:
   // Boards may override to stop a boot-indicator LED sequence or similar.
   // Default no-op: boards that don't care need not implement anything.
   virtual void onBootComplete() { /* no op */ }
+  virtual uint32_t getIRQGpio() { return -1; } // not supported. Returns DIO1 (SX1262) and DIO0 (SX127x)
   virtual void sleep(uint32_t secs)  { /* no op */ }
   virtual uint32_t getGpio() { return 0; }
   virtual void setGpio(uint32_t values) {}

+ 14 - 4
src/helpers/CommonCLI.cpp

@@ -430,18 +430,28 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
       }
 #endif
     } else if (memcmp(command, "powersaving on", 14) == 0) {
+#if defined(NRF52_PLATFORM)
       _prefs->powersaving_enabled = 1;
       savePrefs();
-      strcpy(reply, "ok"); // TODO: to return Not supported if required
+      strcpy(reply, "On - Immediate effect");
+#elif defined(ESP32) && !defined(WITH_BRIDGE)
+      _prefs->powersaving_enabled = 1;
+      savePrefs();
+      strcpy(reply, "On - After 2 minutes");
+#elif defined(WITH_BRIDGE)
+      strcpy(reply, "Bridge not supported");
+#else
+      strcpy(reply, "Board not supported");
+#endif
     } else if (memcmp(command, "powersaving off", 15) == 0) {
       _prefs->powersaving_enabled = 0;
       savePrefs();
-      strcpy(reply, "ok");
+      strcpy(reply, "Off");
     } else if (memcmp(command, "powersaving", 11) == 0) {
       if (_prefs->powersaving_enabled) {
-        strcpy(reply, "on");
+        strcpy(reply, "On");
       } else {
-        strcpy(reply, "off");
+        strcpy(reply, "Off");
       }
     } else if (memcmp(command, "log start", 9) == 0) {
       _callbacks->setLoggingOn(true);

+ 47 - 22
src/helpers/ESP32Board.h

@@ -12,17 +12,19 @@
 #include <rom/rtc.h>
 #include <sys/time.h>
 #include <Wire.h>
-#include "driver/rtc_io.h"
+#include "soc/rtc.h"
+#include "esp_system.h"
 
 class ESP32Board : public mesh::MainBoard {
 protected:
   uint8_t startup_reason;
   bool inhibit_sleep = false;
+  static inline portMUX_TYPE sleepMux = portMUX_INITIALIZER_UNLOCKED;
 
 public:
   void begin() {
     // for future use, sub-classes SHOULD call this from their begin()
-    startup_reason = BD_STARTUP_NORMAL;
+    startup_reason = BD_STARTUP_NORMAL;    
 
   #ifdef ESP32_CPU_FREQ
     setCpuFrequencyMhz(ESP32_CPU_FREQ);
@@ -45,7 +47,7 @@ public:
    #endif
   #else
     Wire.begin();
-  #endif
+  #endif    
   }
 
   // Temperature from ESP32 MCU
@@ -60,25 +62,48 @@ public:
     return raw / 4;
   }
 
-  void enterLightSleep(uint32_t secs) {
-#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(P_LORA_DIO_1) // Supported ESP32 variants
-    if (rtc_gpio_is_valid_gpio((gpio_num_t)P_LORA_DIO_1)) { // Only enter sleep mode if P_LORA_DIO_1 is RTC pin
-      esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
-      esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // To wake up when receiving a LoRa packet
+  uint32_t getIRQGpio() override {
+    return P_LORA_DIO_1; // default for SX1262
+  }
+
+  void sleep(uint32_t secs) override {
+    // Skip if not allow to sleep
+    if (inhibit_sleep) {
+      delay(1); // Give MCU to OTA to run
+      return;
+    }
 
-      if (secs > 0) {
-        esp_sleep_enable_timer_wakeup(secs * 1000000); // To wake up every hour to do periodically jobs
-      }
+    // Set GPIO wakeup
+    gpio_num_t wakeupPin = (gpio_num_t)getIRQGpio();    
 
-      esp_light_sleep_start(); // CPU enters light sleep
+    // Configure timer wakeup
+    if (secs > 0) {
+      esp_sleep_enable_timer_wakeup(secs * 1000000ULL); // Wake up periodically to do scheduled jobs
     }
-#endif
-  }
 
-  void sleep(uint32_t secs) override {
-    if (!inhibit_sleep) {
-      enterLightSleep(secs);      // To wake up after "secs" seconds or when receiving a LoRa packet
+    // Disable CPU interrupt servicing
+    portENTER_CRITICAL(&sleepMux);
+
+    // Skip sleep if there is a LoRa packet
+    if (gpio_get_level(wakeupPin) == HIGH) {
+      portEXIT_CRITICAL(&sleepMux);
+      delay(1);
+      return;
     }
+
+    // Configure GPIO wakeup
+    esp_sleep_enable_gpio_wakeup();
+    gpio_wakeup_enable((gpio_num_t)wakeupPin, GPIO_INTR_HIGH_LEVEL); // Wake up when receiving a LoRa packet
+
+    // MCU enters light sleep
+    esp_light_sleep_start();
+
+    // Avoid ISR flood during wakeup due to HIGH LEVEL interrupt
+    gpio_wakeup_disable(wakeupPin);
+    gpio_set_intr_type(wakeupPin, GPIO_INTR_POSEDGE);
+
+    // Enable CPU interrupt servicing
+    portEXIT_CRITICAL(&sleepMux);
   }
 
   uint8_t getStartupReason() const override { return startup_reason; }
@@ -102,7 +127,7 @@ public:
 #endif
 
   uint16_t getBattMilliVolts() override {
-  #ifdef PIN_VBAT_READ
+    #ifdef PIN_VBAT_READ
     analogReadResolution(12);
 
     uint32_t raw = 0;
@@ -141,16 +166,16 @@ public:
       // start with some date/time in the recent past
       struct timeval tv;
       tv.tv_sec = 1715770351;  // 15 May 2024, 8:50pm
-      tv.tv_usec = 0;
-      settimeofday(&tv, NULL);
-    }
+    tv.tv_usec = 0;
+    settimeofday(&tv, NULL);
+  }
   }
   uint32_t getCurrentTime() override {
     time_t _now;
     time(&_now);
     return _now;
   }
-  void setCurrentTime(uint32_t time) override { 
+  void setCurrentTime(uint32_t time) override {
     struct timeval tv;
     tv.tv_sec = time;
     tv.tv_usec = 0;

+ 7 - 7
src/helpers/esp32/TBeamBoard.h

@@ -59,13 +59,13 @@
   // uint32_t  P_LORA_BUSY  = 0;   //shared, so define at run
   // uint32_t  P_LORA_DIO_2 = 0;   //SX1276 only, so define at run
 
-  #define  P_LORA_DIO_0  26
-  #define  P_LORA_DIO_1  33
-  #define  P_LORA_NSS    18
-  #define  P_LORA_RESET  23
-  #define  P_LORA_SCLK    5
-  #define  P_LORA_MISO   19
-  #define  P_LORA_MOSI   27
+  // #define  P_LORA_DIO_0  26
+  // #define  P_LORA_DIO_1  33
+  // #define  P_LORA_NSS    18
+  // #define  P_LORA_RESET  23
+  // #define  P_LORA_SCLK    5
+  // #define  P_LORA_MISO   19
+  // #define  P_LORA_MOSI   27
 
   // #define PIN_GPS_RX      34
   // #define PIN_GPS_TX      12

+ 10 - 6
variants/heltec_v2/HeltecV2Board.h

@@ -17,12 +17,12 @@ public:
     esp_reset_reason_t reason = esp_reset_reason();
     if (reason == ESP_RST_DEEPSLEEP) {
       long wakeup_source = esp_sleep_get_ext1_wakeup_status();
-      if (wakeup_source & (1 << P_LORA_DIO_1)) {  // received a LoRa packet (while in deep sleep)
+      if (wakeup_source & (1 << P_LORA_DIO_0)) {  // received a LoRa packet (while in deep sleep)
         startup_reason = BD_STARTUP_RX_PACKET;
       }
 
       rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
-      rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
+      rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_0);
     }
   }
 
@@ -30,15 +30,15 @@ public:
     esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
 
     // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
-    rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
-    rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
+    rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_0, RTC_GPIO_MODE_INPUT_ONLY);
+    rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_0);
 
     rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
 
     if (pin_wake_btn < 0) {
-      esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH);  // wake up on: recv LoRa packet
+      esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_0), ESP_EXT1_WAKEUP_ANY_HIGH);  // wake up on: recv LoRa packet
     } else {
-      esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH);  // wake up on: recv LoRa packet OR wake btn
+      esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_0) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH);  // wake up on: recv LoRa packet OR wake btn
     }
 
     if (secs > 0) {
@@ -64,4 +64,8 @@ public:
   const char* getManufacturerName() const override {
     return "Heltec V2";
   }
+
+  uint32_t getIRQGpio() override {
+    return P_LORA_DIO_0; // default for SX1276
+  }
 };

+ 3 - 3
variants/heltec_v2/platformio.ini

@@ -7,10 +7,10 @@ build_flags =
   -D HELTEC_LORA_V2
   -D RADIO_CLASS=CustomSX1276
   -D WRAPPER_CLASS=CustomSX1276Wrapper
-  -D P_LORA_DIO_1=26
+  -D P_LORA_DIO_0=26
+  -D P_LORA_DIO_1=35
   -D P_LORA_NSS=18
-  -D P_LORA_RESET=RADIOLIB_NC
-  -D P_LORA_BUSY=RADIOLIB_NC
+  -D P_LORA_RESET=14
   -D P_LORA_SCLK=5
   -D P_LORA_MISO=19
   -D P_LORA_MOSI=27

+ 2 - 2
variants/heltec_v2/target.cpp

@@ -5,9 +5,9 @@ HeltecV2Board board;
 
 #if defined(P_LORA_SCLK)
   static SPIClass spi;
-  RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
+  RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1, spi);
 #else
-  RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
+  RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1);
 #endif
 
 WRAPPER_CLASS radio_driver(radio, board);

+ 10 - 0
variants/lilygo_t3s3_sx1276/LilygoT3S3SX1276Board.h

@@ -0,0 +1,10 @@
+#pragma once
+
+#include <helpers/ESP32Board.h>
+
+class LilygoT3S3SX1276Board : public ESP32Board {
+public:
+  uint32_t getIRQGpio() override {
+    return P_LORA_DIO_0; // default for SX1276
+  }
+};

+ 1 - 1
variants/lilygo_t3s3_sx1276/target.cpp

@@ -1,7 +1,7 @@
 #include <Arduino.h>
 #include "target.h"
 
-ESP32Board board;
+LilygoT3S3SX1276Board board;
 
 #if defined(P_LORA_SCLK)
   static SPIClass spi;

+ 2 - 2
variants/lilygo_t3s3_sx1276/target.h

@@ -3,7 +3,7 @@
 #define RADIOLIB_STATIC_ONLY 1
 #include <RadioLib.h>
 #include <helpers/radiolib/RadioLibWrappers.h>
-#include <helpers/ESP32Board.h>
+#include <LilygoT3S3SX1276Board.h>
 #include <helpers/radiolib/CustomSX1276Wrapper.h>
 #include <helpers/AutoDiscoverRTCClock.h>
 #include <helpers/SensorManager.h>
@@ -12,7 +12,7 @@
   #include <helpers/ui/MomentaryButton.h>
 #endif
 
-extern ESP32Board board;
+extern LilygoT3S3SX1276Board board;
 extern WRAPPER_CLASS radio_driver;
 extern AutoDiscoverRTCClock rtc_clock;
 extern SensorManager sensors;

+ 7 - 0
variants/lilygo_tbeam_SX1262/platformio.ini

@@ -5,6 +5,13 @@ build_flags =
   ${esp32_base.build_flags}
   -I variants/lilygo_tbeam_SX1262
   -D TBEAM_SX1262
+  -D P_LORA_DIO_0=26
+  -D P_LORA_DIO_1=33
+  -D P_LORA_NSS=18
+  -D P_LORA_RESET=23
+  -D P_LORA_SCLK=5
+  -D P_LORA_MISO=19
+  -D P_LORA_MOSI=27
   -D SX126X_DIO2_AS_RF_SWITCH=true
   -D SX126X_DIO3_TCXO_VOLTAGE=1.8
   -D SX126X_CURRENT_LIMIT=140

+ 10 - 0
variants/lilygo_tbeam_SX1276/LilygoTBeamSX1276Board.h

@@ -0,0 +1,10 @@
+#pragma once
+
+#include <helpers/esp32/TBeamBoard.h>
+
+class LilygoTBeamSX1276Board : public TBeamBoard {
+public:
+  uint32_t getIRQGpio() override {
+    return P_LORA_DIO_0; // default for SX1276
+  }
+};

+ 7 - 0
variants/lilygo_tbeam_SX1276/platformio.ini

@@ -5,6 +5,13 @@ build_flags =
   ${esp32_base.build_flags}
   -I variants/lilygo_tbeam_SX1276
   -D TBEAM_SX1276
+  -D P_LORA_DIO_0=26
+  -D P_LORA_DIO_1=33
+  -D P_LORA_NSS=18
+  -D P_LORA_RESET=23
+  -D P_LORA_SCLK=5
+  -D P_LORA_MISO=19
+  -D P_LORA_MOSI=27
   -D SX127X_CURRENT_LIMIT=120
   -D RADIO_CLASS=CustomSX1276
   -D WRAPPER_CLASS=CustomSX1276Wrapper

+ 1 - 1
variants/lilygo_tbeam_SX1276/target.cpp

@@ -1,7 +1,7 @@
 #include <Arduino.h>
 #include "target.h"
 
-TBeamBoard board;
+LilygoTBeamSX1276Board board;
 
 #if defined(P_LORA_SCLK)
   static SPIClass spi;

+ 2 - 2
variants/lilygo_tbeam_SX1276/target.h

@@ -3,7 +3,7 @@
 #define RADIOLIB_STATIC_ONLY 1
 //#include <RadioLib.h>
 #include <helpers/radiolib/RadioLibWrappers.h>
-#include <helpers/esp32/TBeamBoard.h>
+#include <LilygoTBeamSX1276Board.h>
 #include <helpers/radiolib/CustomSX1276Wrapper.h>
 #include <helpers/AutoDiscoverRTCClock.h>
 #include <helpers/sensors/EnvironmentSensorManager.h>
@@ -12,7 +12,7 @@
   #include <helpers/ui/MomentaryButton.h>
 #endif
 
-extern TBeamBoard board;
+extern LilygoTBeamSX1276Board board;
 extern WRAPPER_CLASS radio_driver;
 extern AutoDiscoverRTCClock rtc_clock;
 extern EnvironmentSensorManager sensors;

+ 7 - 0
variants/lilygo_tbeam_supreme_SX1262/platformio.ini

@@ -5,6 +5,13 @@ build_flags =
   ${esp32_base.build_flags}
   -I variants/lilygo_tbeam_supreme_SX1262
   -D TBEAM_SUPREME_SX1262
+  -D P_LORA_DIO_0=26
+  -D P_LORA_DIO_1=33
+  -D P_LORA_NSS=18
+  -D P_LORA_RESET=23
+  -D P_LORA_SCLK=5
+  -D P_LORA_MISO=19
+  -D P_LORA_MOSI=27
   -D SX126X_CURRENT_LIMIT=140
   -D SX126X_RX_BOOSTED_GAIN=1
   -D USE_SX1262

+ 4 - 0
variants/lilygo_tlora_v2_1/LilyGoTLoraBoard.h

@@ -21,4 +21,8 @@ public:
 
     return (2 * raw);
   }
+
+  uint32_t getIRQGpio() override {
+    return P_LORA_DIO_0; // default for SX1276
+  }
 };