Can LoRa RA-02 Wake Up ESP32 from Sleep?

Hi everyone,

I'm continuing a project where I'm using an ESP32 and LoRa communication (SX1278-based RA-02 module). I’m now trying to achieve ultra-low power operation by putting the ESP32 into sleep modes.

Here's my main question:

:backhand_index_pointing_right: Has anyone successfully used the LoRa RA-02 module to wake up the ESP32 from any sleep mode (light, deep, or ULP) when a LoRa packet is received from another node?

If yes:

  • What sleep mode were you using?
  • How did you wire or configure the RA-02 to trigger a wake-up?
  • Did you use DIO0 or another pin as an interrupt?

If no:

  • Is the RA-02 capable of triggering interrupts on packet receive?
  • Are there better alternatives to RA-02 that support low-power listening or wake-on-radio features?

Any advice, experiences, or even alternative LoRa modules (like Semtech’s SX1262 or others) that are more suited for this type of functionality would be greatly appreciated!

Thanks a lot in advance!

I don't have answers to your question about Lora module.
Anyway, Esp32 can wake up from deep sleep triggered on any RTC pins, so that part shouldn't be a problem.

All of Semtech's LoRa modules (such as the RA-02) have interrupt outputs that can be used for wake-on-packet-reception. Check the SX-1278 data sheet for the details.

Well, you can put the ESP32 to sleep and get a low current sleep if the regulator used on the ESP32 board is a low IQ current type, and then when a packet comes in the ESP32 can be made to wakeup.

But if you want ultra-low power operation then forget it as the RA-02 will use circa 12mA in receive mode.

1 Like

A LoRa module based on the SX1262 will how a lower receive current than the RA-02 (which is SX1278 based) say 6mA versus 12mA.

Yes, you're right — the ESP32 can wake from deep sleep using RTC GPIOs, which is really useful. I'm actually looking into whether the RA-02 module (SX1278) can be configured to trigger such a wake-up.

From what I’ve seen in the SX1278 datasheet, the DIO0 pin can be configured to go high on RxDone (packet received). My question is: has anyone here successfully used DIO0 (or any other DIO pin) to physically wake up the ESP32 from any sleep mode?

If DIO0 can be routed to an RTC GPIO, it should be able to trigger a wake-up — but I haven’t seen a confirmed working example yet.

Anyone tried wiring DIO0 to an RTC-capable pin (like GPIO33 or GPIO25, for instance) and verified that a LoRa packet wakes the ESP32?

Would love to hear if someone got this working in practice!

Thanks for confirming that — yeah, I saw in the SX1278 datasheet that DIO0 can be configured to go high on RxDone (packet received), which definitely sounds like it could be used for wake-on-radio setups.

Just curious — have you ever actually tried this physically? Like, connecting DIO0 to an RTC-capable GPIO on the ESP32 and confirming it wakes from deep sleep when a packet is received?

I’m trying to figure out if this is just theoretically possible or if someone has seen it work reliably in a real setup.

Yes, on Adafruit Feather M0 LoRa and Feather 32U4 LoRa. Interrupt-on-packet-reception works exactly as specified.

I’m trying to figure out if this is just theoretically possible

Manufacturer's data sheets are legal documents and the devices are guaranteed to function according to the specifications in those documents.

Its possible as I indicated in post #4.

On SX126x, SX127x and SX128x.

That’s great to hear — thanks for sharing that it works on those boards!

Would you mind sharing how it's done in terms of programming, specifically for the LoRa side? The SX1278 datasheet I have doesn’t clearly highlight how to enable the interrupt pin (like DIO0) to trigger on packet reception.

Do you have to configure a specific register or use address filtering to enable this behavior? Any code snippet or guidance would be super helpful.

Thanks for the info — that’s a noticeable difference in receive current. I’ll definitely consider switching to the SX1262, especially since I’m aiming for ultra-low power and wake-on-radio setups. Appreciate the tip!

Do yourself a favor and drop the use of bold type. It is useless, ugly and distracting.

The Radiohead library, and presumably the others, use the DIO0 interrupt output to indicate packet reception. That code has been around and working for over a decade.

Thats true, most LoRa libraries do use DIO0 (on SX127x) and DIO1 (on SX126x) to indicate RX and TX done.

An exception is the common LoRa.h by Sandeep Mistry for the SX127x, that by default does not setup DIO0 to indicate RX done, although it can be configured that way.

Got it — and thanks for the heads-up!

I'll take a closer look at the RadioHead library and how it handles DIO0 for packet reception. Sounds like it's been solid and time-tested, which is reassuring. Appreciate the pointer!

I think I found a way to wake up the ESP32 upon receiving a LoRa packet from another node. Here's what I’ve come up with:

Tx

#include <SPI.h>
#include <LoRa.h>

void onTxComplete() {
  Serial.println("Transmission complete!");
}

void setup() {
  Serial.begin(115200);
  while (!Serial);

  LoRa.setPins(27, 14, 26); // SS, RST, DIO0

  if (!LoRa.begin(433E6)) {
    Serial.println("LoRa init failed. Check wiring.");
    while (1);
  }

  LoRa.onTxDone(onTxComplete);

  Serial.println("LoRa Transmitter Ready");
}

void loop() {
  Serial.println("Sending packet...");

  LoRa.beginPacket();
  LoRa.print("Hello from Transmitter");
  LoRa.endPacket(true); // true = async (non-blocking) + trigger onTxDone()

  delay(5000); // Wait 5 seconds before next transmission
}

Rx

#include <SPI.h>
#include <LoRa.h>
#include "esp_sleep.h"

#define LORA_SS   27
#define LORA_RST  14
#define LORA_DIO0 26

volatile bool packetReceived = false;

void IRAM_ATTR onDio0Rise() {
  packetReceived = true;
}

void setup() {
  Serial.begin(115200);
  delay(1000);

  // LoRa module setup
  LoRa.setPins(LORA_SS, LORA_RST, LORA_DIO0);
  if (!LoRa.begin(433E6)) {
    Serial.println("LoRa init failed. Check wiring.");
    while (1);
  }

  Serial.println("LoRa Receiver with Light Sleep Ready");

  // Configure DIO0 (GPIO 26) as wakeup source
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_26, 1); // Wake up on HIGH

  // Attach interrupt to DIO0
  pinMode(LORA_DIO0, INPUT);
  attachInterrupt(digitalPinToInterrupt(LORA_DIO0), onDio0Rise, RISING);

  // Set LoRa to receive mode
  LoRa.receive();
}

void loop() {
  Serial.println("Going to light sleep...");
  delay(100); // Let serial settle

  // Go into light sleep
  esp_light_sleep_start();

  // Woke up by DIO0
  Serial.println("Wakeup from light sleep...");

  if (packetReceived) {
    packetReceived = false;

    int packetSize = LoRa.parsePacket();
    if (packetSize) {
      Serial.print("Received: ");
      while (LoRa.available()) {
        Serial.print((char)LoRa.read());
      }
      Serial.println();
    }

    // Re-enable LoRa receive mode
    LoRa.receive();
  }

  delay(100); // Optional delay before next sleep
}

Could you please review it and let me know if it looks good or if there’s anything I should improve?

Do note that the ESP32 can do weird things to some pins when you put it into deep sleep, some pins can get brief logic level changes, and these pulses can reset the LoRa device that you have put into receive mode.

Thank you for the heads-up! I’ll keep that in mind while testing..

ESP32 connected to a RFM95W LoRa module using interrupt to wake up from light sleep

// ESP32 connected to a RFM95W LoRa module

// on receive packet wakeup from light sleep

#include <SPI.h>
#include <LoRa.h>
#include "driver/rtc_io.h"

// ESP32 VSPI connections
// ESP32 SCK pin GPIO18  to RFM95_pin SCK
// ESP32 MISO pin GPIO19  to RFM95_pin MISO
// ESP32 MOSI pin GPIO23  to RFM95_pin MOSI
// ESP32 pin GPIO 5   to RFM95 SS
// ESP32 pin GPIO4   to RFM95 Reset
// ESP32 pin GPIO2   to RFM95 DIO0

// measure time between rising edge using micros()

#define WAKEUP_GPIO GPIO_NUM_27  // RFM95 DIO0 wakeup pin

void setup() {
  Serial.begin(115200);
  delay(5000);
  Serial.println("\n\nESP32 LoRa RFM95W Receiver");
  // void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN);
  //LoRa.setPins(8, 4, 7);  // for Lora 32u4
  LoRa.setPins(5, 4, 27);  // for ESP32
  if (!LoRa.begin(866E6)) {
    Serial.println("Starting LoRa failed!");
    while (1)
      ;
  }
  Serial.println("Starting LoRa OK!");
}

// enable LoRa receiver, goto light sleep on wakeup read packet
void loop() {
  LoRa.receive();
  // set GPIO pin to wake on high pulse from RFM95 DIO0
  gpio_wakeup_enable(WAKEUP_GPIO, GPIO_INTR_HIGH_LEVEL);
  esp_sleep_enable_gpio_wakeup();
  Serial.println("goto light sleep");
  delay(1000);
  esp_light_sleep_start();  // light sleep!!
  Serial.begin(115200);
  while (!Serial) { delay(500); }
  Serial.println("wakeup from ligh sleep");
  // now read received packet
  int packetSize = LoRa.parsePacket();  // packet available
  if (packetSize) {
    Serial.print("Received packet '");
    while (LoRa.available()) {  // yes, read it
      Serial.print((char)LoRa.read());
    }
    // print RSSI of packet
    Serial.print("' with RSSI ");
    Serial.println(LoRa.packetRssi());
  }
}

result

ESP32 LoRa RFM95W Receiver
Starting LoRa OK!
goto light sleep
wakeup from ligh sleep
Received packet 'hello 1442' with RSSI -69
goto light sleep
wakeup from ligh sleep
Received packet 'hello 1443' with RSSI -70
goto light sleep
wakeup from ligh sleep
Received packet 'hello 1444' with RSSI -70
goto light sleep
wakeup from ligh sleep
Received packet 'hello 1445' with RSSI -68
goto light sleep
wakeup from ligh sleep
Received packet 'hello 1446' with RSSI -69
goto light sleep

waking up from deep sleep

// ESP32 connected to a RFM95W LoRa module

// when in deep sleep on receiving LoRa packet on a RTC_IO receiver wakes up 
// In Deep-sleep mode, the CPUs, most of the RAM, and 
// all digital peripherals that are clocked from APB_CLK are powered off.
// therefore any LoRa packet which caused wakeup from deep sleep will be lost

// one possible technique is
// 1. the receiver program enables LoRa receiver and goes to deep sleep
// 2. initially the transmitter sends a short wakeup packet followed short time later by a data packet 
//      (the short time is to allow time for the RTC_IO reset and initialze the LoRa receiver )
// 3. on receipt of the wakeup RTC_IO packet the receiver will then wait for a short time for the data packet
// 4. receiver will read data packet, process it and then go into deep sleep

// using low level RFM95W function calls it may be possible on wakeup from deep sleep 
//   to read the receiver data???? not attempted it!!

#include <SPI.h>
#include <LoRa.h>
#include "driver/rtc_io.h"

// ESP32 VSPI connections
// ESP32 SCK pin GPIO18  to RFM95_pin SCK
// ESP32 MISO pin GPIO19  to RFM95_pin MISO
// ESP32 MOSI pin GPIO23  to RFM95_pin MOSI
// ESP32 pin GPIO 5   to RFM95 SS
// ESP32 pin GPIO4   to RFM95 Reset
// ESP32 pin GPIO2   to RFM95 DIO0

// measure time between rising edge using micros()

#define WAKEUP_GPIO GPIO_NUM_2  // RFM95 DIO0 wakeup pin

// on reset this will display the reason
int print_wakeup_reason() {
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch (wakeup_reason) {
    case ESP_SLEEP_WAKEUP_EXT0: Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1: Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case ESP_SLEEP_WAKEUP_TIMER: Serial.println("Wakeup caused by timer"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD: Serial.println("Wakeup caused by touchpad"); break;
    case ESP_SLEEP_WAKEUP_ULP: Serial.println("Wakeup caused by ULP program"); break;
    default:
      Serial.printf("Wakeup was not caused by deep sleep: %d\n", wakeup_reason);
      return -1;
      break;
  }
  return wakeup_reason;
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("\n\nESP32 LoRa RFM95W Receiver");
  // void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN);
  //LoRa.setPins(8, 4, 7);  // for Lora 32u4
  LoRa.setPins(5, -1, 2);  // for ESP32
  if (!LoRa.begin(866E6)) {
    Serial.println("Starting LoRa failed!");
    while (1)
      ;
  }
  Serial.println("Starting LoRa OK!");
  // if woken up by external EXT0 from RFM95 DIO0
  if (print_wakeup_reason() == ESP_SLEEP_WAKEUP_EXT0) {
    // wait for a following packet
    unsigned long timer1 = millis();
    while (millis() - timer1 < 10000) {
      int packetSize = LoRa.parsePacket();
      if (packetSize) {
        Serial.print("Received packet '");
        while (LoRa.available()) {
          Serial.print((char)LoRa.read());
        }
        // print RSSI of packet
        Serial.print("' with RSSI ");
        Serial.println(LoRa.packetRssi());
        break;
      }
    }
  }

  // enable LoRa receiver and goto deep sleep waiting for next packet
  LoRa.receive();
  esp_sleep_enable_ext0_wakeup(WAKEUP_GPIO, 1);  //1 = High, 0 = Low
  // Configure pullup/downs via RTCIO to tie wakeup pins to inactive level during deepsleep.
  // EXT0 resides in the same power domain (RTC_PERIPH) as the RTC IO pullup/downs.
  // No need to keep that power domain explicitly, unlike EXT1.
  rtc_gpio_pullup_dis(WAKEUP_GPIO);
  rtc_gpio_pulldown_en(WAKEUP_GPIO);
  //Go to sleep now
  Serial.println("Going to sleep now");
  esp_deep_sleep_start();
  Serial.println("This will never be printed");
}

void loop() {}

results - LoRa transmitter regularly transmits a test packet incrementing a count by one
first packet wakes receiver from deep sleep - second packet is received and is displayed
hence received packet count is 282 284 286 etc

ESP32 LoRa RFM95W Receiver
Starting LoRa OK!
Wakeup was not caused by deep sleep: 0
Going to sleep now
ets Jul 29 2019 12:21:46

rst:0x5 (DEEPSLEEP_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0030,len:4916
load:0x40078000,len:16436
load:0x40080400,len:4
ho 8 tail 4 room 4
load:0x40080404,len:3524
entry 0x400805b8


ESP32 LoRa RFM95W Receiver
Starting LoRa OK!
Wakeup caused by external signal using RTC_IO
Received packet 'hello 282' with RSSI -64
Going to sleep now

ESP32 LoRa RFM95W Receiver
Starting LoRa OK!
Wakeup caused by external signal using RTC_IO
Received packet 'hello 284' with RSSI -65
Going to sleep now

when LoRa.begin(866E6) is called the LoRa packet which caused wakeup from deep sleep is lost
using low level RFM95W function calls it may be possible on wakeup from deep sleep to read the receiver data???? not attempted it!!

Thanks a lot for the detailed explanation and the sample code! This really helps clarify the approach for both light sleep and deep sleep scenarios with the RFM95W. Much appreciated!

Thanks a lot for the detailed explanation and the sample code! This really helps clarify the approach for both light sleep and deep sleep scenarios with the RFM95W. Much appreciated!