Skip to content

Commit 3e79842

Browse files
committed
Implement Power Saving for ESP32 and NRF52 repeaters
- Added PowerSaving support for all ESP32 and NRF52-based repeaters. - Implemented light sleep for ESP32 and System-Idle for NRF52. - Added hardware support for SX127x (DIO0/DIO1 wakeup logic). - Moved LoRa pin configurations to platformio.ini for better portability. - Optimized wakeup pin polling and added housekeeping tasks. - Refactored sleep functions and cleaned up unused NRF52 libraries.
1 parent e60fb14 commit 3e79842

24 files changed

Lines changed: 237 additions & 73 deletions

File tree

src/MeshCore.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class MainBoard {
5151
virtual void onAfterTransmit() { }
5252
virtual void reboot() = 0;
5353
virtual void powerOff() { /* no op */ }
54+
virtual uint32_t getIRQGpio() { return P_LORA_DIO_1; } // default for SX1262
55+
virtual bool safeToSleep() { return false; }
5456
virtual void sleep(uint32_t secs) { /* no op */ }
5557
virtual uint32_t getGpio() { return 0; }
5658
virtual void setGpio(uint32_t values) {}

src/helpers/ESP32Board.h

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,29 +56,59 @@ class ESP32Board : public mesh::MainBoard {
5656
return raw / 4;
5757
}
5858

59-
void enterLightSleep(uint32_t secs) {
60-
#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(P_LORA_DIO_1) // Supported ESP32 variants
61-
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
62-
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
63-
esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // To wake up when receiving a LoRa packet
59+
bool safeToSleep() {
60+
// Check for RX status
61+
gpio_num_t wakeupPin = (gpio_num_t)getIRQGpio();
62+
if (digitalRead(wakeupPin) == HIGH) {
63+
return false;
64+
}
6465

65-
if (secs > 0) {
66-
esp_sleep_enable_timer_wakeup(secs * 1000000); // To wake up every hour to do periodically jobs
67-
}
66+
// Check for WiFi status to see if there is active OTA
67+
wifi_mode_t mode;
68+
esp_err_t err = esp_wifi_get_mode(&mode);
6869

69-
esp_light_sleep_start(); // CPU enters light sleep
70+
if (err == ESP_OK) { // WiFi is on
71+
return false;
7072
}
71-
#endif
73+
74+
// Safe to sleep
75+
return true;
7276
}
7377

7478
void sleep(uint32_t secs) override {
75-
// To check for WiFi status to see if there is active OTA
76-
wifi_mode_t mode;
77-
esp_err_t err = esp_wifi_get_mode(&mode);
78-
79-
if (err != ESP_OK) { // WiFi is off ~ No active OTA, safe to go to sleep
80-
enterLightSleep(secs); // To wake up after "secs" seconds or when receiving a LoRa packet
79+
// Skip if not safe to sleep
80+
if (!safeToSleep()) {
81+
return;
82+
}
83+
84+
// Configure GPIO wakeup
85+
gpio_num_t wakeupPin = (gpio_num_t)getIRQGpio();
86+
esp_sleep_enable_gpio_wakeup();
87+
gpio_wakeup_enable((gpio_num_t)wakeupPin, GPIO_INTR_HIGH_LEVEL); // Wake up when receiving a LoRa packet
88+
89+
// Configure timer wakeup
90+
if (secs > 0) {
91+
esp_sleep_enable_timer_wakeup(secs * 1000000ULL); // Wake up periodically to do scheduled jobs
92+
}
93+
94+
// Disable CPU interrupt servicing
95+
noInterrupts();
96+
97+
// Skip sleep if there is a LoRa packet
98+
if (digitalRead(wakeupPin) == HIGH) {
99+
interrupts();
100+
return;
81101
}
102+
103+
// MCU enters light sleep
104+
esp_light_sleep_start();
105+
106+
// Avoid ISR flood during wakeup due to HIGH LEVEL interrupt
107+
gpio_wakeup_disable(wakeupPin);
108+
gpio_set_intr_type(wakeupPin, GPIO_INTR_POSEDGE);
109+
110+
// Enable CPU interrupt servicing
111+
interrupts();
82112
}
83113

84114
uint8_t getStartupReason() const override { return startup_reason; }

src/helpers/NRF52Board.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,4 +318,78 @@ bool NRF52Board::startOTAUpdate(const char *id, char reply[]) {
318318

319319
return true;
320320
}
321+
322+
bool NRF52Board::safeToSleep() {
323+
// Check for RX status
324+
uint32_t wakeupPin = getIRQGpio();
325+
if (digitalRead(wakeupPin) == HIGH) {
326+
return false;
327+
}
328+
329+
// Check if the BLE is powered and looking for/connected to a phone
330+
uint8_t sd_enabled;
331+
sd_softdevice_is_enabled(&sd_enabled); // Set sd_enabled to 1 if the BLE stack is active.
332+
333+
if (sd_enabled) { // BLE is on
334+
return false;
335+
}
336+
337+
// Safe to sleep
338+
return true;
339+
}
340+
341+
void NRF52Board::sleep(uint32_t secs) {
342+
// Skip if not safe to sleep
343+
if (!safeToSleep()) {
344+
return;
345+
}
346+
347+
// Configure GPIO wakeup
348+
uint32_t wakeupPin = getIRQGpio();
349+
350+
// Mark the start of the sleep
351+
uint32_t startTime = millis();
352+
uint32_t timeoutMs = secs * 1000;
353+
354+
// Create event when wakeup pin is high
355+
nrf_gpio_cfg_sense_input(wakeupPin, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_HIGH);
356+
357+
while (true) {
358+
// Do housekeeping tasks for peripherals (UART...) so they do not block sleep
359+
yield();
360+
361+
// Wakeup timer
362+
if ((millis() - startTime) >= timeoutMs) {
363+
break;
364+
}
365+
366+
// Clear event noises from Memory Watch Unit
367+
NVIC_ClearPendingIRQ(MWU_IRQn);
368+
369+
// Clear stale events
370+
__SEV();
371+
__WFE();
372+
373+
// Disable ISR servicing
374+
noInterrupts();
375+
376+
// Skip sleep if there is a LoRa packet
377+
if (digitalRead(wakeupPin) == HIGH) {
378+
interrupts();
379+
break;
380+
}
381+
382+
// Attempt to sleep, wakeup on any events
383+
__WFE();
384+
385+
// Enable ISR servicing
386+
interrupts();
387+
}
388+
389+
// Disable sense on wakeup pin
390+
nrf_gpio_cfg_input(wakeupPin, NRF_GPIO_PIN_NOPULL);
391+
392+
// Clear the latch so the next sleep is fresh and do not remember old events.
393+
NRF_GPIO->LATCH = (1 << wakeupPin);
394+
}
321395
#endif

src/helpers/NRF52Board.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ class NRF52Board : public mesh::MainBoard {
6060
const char* getResetReasonString(uint32_t reason) override;
6161
const char* getShutdownReasonString(uint8_t reason) override;
6262
#endif
63+
64+
virtual bool safeToSleep() override;
65+
virtual void sleep(uint32_t secs) override;
6366
};
6467

6568
/*

src/helpers/esp32/TBeamBoard.h

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,6 @@
5959
// uint32_t P_LORA_BUSY = 0; //shared, so define at run
6060
// uint32_t P_LORA_DIO_2 = 0; //SX1276 only, so define at run
6161

62-
#define P_LORA_DIO_0 26
63-
#define P_LORA_DIO_1 33
64-
#define P_LORA_NSS 18
65-
#define P_LORA_RESET 23
66-
#define P_LORA_SCLK 5
67-
#define P_LORA_MISO 19
68-
#define P_LORA_MOSI 27
69-
7062
// #define PIN_GPS_RX 34
7163
// #define PIN_GPS_TX 12
7264

variants/heltec_mesh_solar/MeshSolarBoard.h

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,6 @@
88
#include "meshSolarApp.h"
99
#endif
1010

11-
// LoRa radio module pins for Heltec T114
12-
#define P_LORA_DIO_1 20
13-
#define P_LORA_NSS 24
14-
#define P_LORA_RESET 25
15-
#define P_LORA_BUSY 17
16-
#define P_LORA_SCLK 19
17-
#define P_LORA_MISO 23
18-
#define P_LORA_MOSI 22
19-
20-
#define SX126X_DIO2_AS_RF_SWITCH true
21-
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
22-
2311
class MeshSolarBoard : public NRF52BoardDCDC {
2412
public:
2513
MeshSolarBoard() : NRF52Board("MESH_SOLAR_OTA") {}

variants/heltec_mesh_solar/platformio.ini

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ build_flags = ${nrf52_base.build_flags}
99
-I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52
1010
-I variants/heltec_mesh_solar
1111
-D HELTEC_MESH_SOLAR
12+
-D P_LORA_DIO_1=20
13+
-D P_LORA_NSS=24
14+
-D P_LORA_RESET=25
15+
-D P_LORA_BUSY=17
16+
-D P_LORA_SCLK=19
17+
-D P_LORA_MISO=23
18+
-D P_LORA_MOSI=22
19+
-D SX126X_DIO2_AS_RF_SWITCH=true
20+
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
1221
-D RADIO_CLASS=CustomSX1262
1322
-D WRAPPER_CLASS=CustomSX1262Wrapper
1423
-D LORA_TX_POWER=22

variants/heltec_v2/HeltecV2Board.h

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,28 @@ class HeltecV2Board : public ESP32Board {
1717
esp_reset_reason_t reason = esp_reset_reason();
1818
if (reason == ESP_RST_DEEPSLEEP) {
1919
long wakeup_source = esp_sleep_get_ext1_wakeup_status();
20-
if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep)
20+
if (wakeup_source & (1 << P_LORA_DIO_0)) { // received a LoRa packet (while in deep sleep)
2121
startup_reason = BD_STARTUP_RX_PACKET;
2222
}
2323

2424
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
25-
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
25+
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_0);
2626
}
2727
}
2828

2929
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
3030
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
3131

3232
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
33-
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
34-
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
33+
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_0, RTC_GPIO_MODE_INPUT_ONLY);
34+
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_0);
3535

3636
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
3737

3838
if (pin_wake_btn < 0) {
39-
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
39+
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_0), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
4040
} else {
41-
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
41+
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
4242
}
4343

4444
if (secs > 0) {
@@ -64,4 +64,8 @@ class HeltecV2Board : public ESP32Board {
6464
const char* getManufacturerName() const override {
6565
return "Heltec V2";
6666
}
67+
68+
uint32_t getIRQGpio() override {
69+
return P_LORA_DIO_0; // default for SX1276
70+
}
6771
};

variants/heltec_v2/platformio.ini

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ build_flags =
77
-D HELTEC_LORA_V2
88
-D RADIO_CLASS=CustomSX1276
99
-D WRAPPER_CLASS=CustomSX1276Wrapper
10-
-D P_LORA_DIO_1=26
10+
-D P_LORA_DIO_0=26
11+
-D P_LORA_DIO_1=35
1112
-D P_LORA_NSS=18
12-
-D P_LORA_RESET=RADIOLIB_NC
13-
-D P_LORA_BUSY=RADIOLIB_NC
13+
-D P_LORA_RESET=14
1414
-D P_LORA_SCLK=5
1515
-D P_LORA_MISO=19
1616
-D P_LORA_MOSI=27

variants/heltec_v2/target.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ HeltecV2Board board;
55

66
#if defined(P_LORA_SCLK)
77
static SPIClass spi;
8-
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
8+
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1, spi);
99
#else
10-
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
10+
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1);
1111
#endif
1212

1313
WRAPPER_CLASS radio_driver(radio, board);

0 commit comments

Comments
 (0)