Trying to wake esp32 up with nRF24L01 IRQ

Hi I'm trying to wake my esp32 up from deep sleep with the IRQ pin from nRF24L01.
I'm basically making a wireless door lock that has a keypad on one side, and a servo motor on the other side.
Both have nRF24L01 and are used for communication.

Since I use 4 AA batteries for each, power saving is crucial so I decided to use deep sleep mode when there is no external input to both.

The keypad module wakes up well whenever I press a key, but the problem is the servo module.

I want to wake the servo module whenever it receives a signal from nRF module by monitoring the IRQ pin, but it seems like it's not reading the IRQ pin when the esp32 is in deep sleep.

The following are the codes for each module.

[Keypad Module Code (Tx)]

#include "driver/rtc_io.h"
#include <Keypad.h>
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RTC_DATA_ATTR int bootCount = 0;

RF24 radio(4, 5);
// long long address = 0x1234ABCDEFLL;
const byte address[][6] = {"00001", "00002"};

const byte ROWS = 4;
const byte COLS = 3;

const uint8_t soundpin = 22;

char hexaKeys[ROWS][COLS] = {

  {'1','2','3'},

  {'4','5','6'},

  {'7','8','9'},

  {'*','0','#'}

};

byte rowPins[ROWS] = {32, 33, 25, 26}; //connect to the row pinouts of the keypad

byte colPins[COLS] = {27, 14, 12}; //connect to the column pinouts of the keypad

RTC_DATA_ATTR char inputPassword[100] = {};
RTC_DATA_ATTR int idx = 0;
// char presetPassword[100] = {};
char presetPassword[100] = {'1', '2', '3', '4'};
int passwordLength = 4;

Keypad customKeypad = Keypad( makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);

enum doorLockState{
  DOOR_LOCKED,
  DOOR_UNLOCKING,
  DOOR_UNLOCKED
};

RTC_DATA_ATTR doorLockState lockState = DOOR_LOCKED;

const int lowPowerModeDelay = 10000;
long int prevMillis;

void setup(){

  Serial.begin(115200);

  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));
  print_wakeup_reason();

  if(bootCount == 1){
    resetArray(inputPassword, passwordLength);
    idx = 0;
  }

  ledcAttachChannel(soundpin, NOTE_A, 7, 2);

  radio.begin();
  radio.openWritingPipe(address[1]);
  radio.setDataRate(RF24_2MBPS);
  radio.setPALevel(RF24_PA_MIN);
  radio.setChannel(0x4C);
  radio.stopListening();
  delay(1000);
  
  prevMillis = millis();
}

void loop(){
  char customKey = customKeypad.getKey();
  if(customKey){
    prevMillis = millis();
  }

  if(!customKey && (millis() - prevMillis > lowPowerModeDelay)){
    Serial.println("Going to sleep");
    ledcWriteNote(soundpin,NOTE_G, 7);
    delay(100);
    ledcWrite(soundpin,0);

    rtc_gpio_pullup_en(GPIO_NUM_26);
    esp_sleep_enable_ext0_wakeup(GPIO_NUM_26,0);
    esp_deep_sleep_start();
  }

  switch(lockState){
    case DOOR_LOCKED:
      if(customKey == '#'){
        ledcWriteNote(soundpin,NOTE_G, 7);
        delay(100);
        ledcWrite(soundpin,0);
        
        lockState = DOOR_UNLOCKING;
        bool sendSuccess = radio.write(&lockState, sizeof(lockState));
        while(!sendSuccess){
          Serial.println("Transmition error. Sending again.");
          sendSuccess = radio.write(&lockState, sizeof(lockState));
        }
        prevMillis = millis();
      }
      break;
    case DOOR_UNLOCKING:
      if(customKey){
        ledcWriteNote(soundpin,NOTE_G, 7);
        delay(100);
        ledcWrite(soundpin,0);
        delay(100);

        if(customKey == '*'){
          if(compareArrays(inputPassword, presetPassword, passwordLength)){
            lockState = DOOR_UNLOCKED;
            bool sendSuccess = radio.write(&lockState, sizeof(lockState));
            while(!sendSuccess){
              Serial.println("Transmition error. Sending again.");
              sendSuccess = radio.write(&lockState, sizeof(lockState));
            }
            prevMillis = millis();

            Serial.println("PASSWORD MATCH. DOOR OPENED!");
            ledcWriteNote(soundpin,NOTE_C, 7);
            delay(300);
            ledcWriteNote(soundpin,NOTE_E, 7);
            delay(300);
            ledcWriteNote(soundpin,NOTE_G, 7);
            delay(300);
            ledcWrite(soundpin,0);
          }
          else{
            lockState = DOOR_LOCKED;
            bool sendSuccess = radio.write(&lockState, sizeof(lockState));
            while(!sendSuccess){
              Serial.println("Transmition error. Sending again.");
              sendSuccess = radio.write(&lockState, sizeof(lockState));
            }
            prevMillis = millis();

            Serial.println("PASSWORD NOT MATCHING. DOOR CLOSED!");
            for(int i = 0; i < 3; i++){
              ledcWriteNote(soundpin,NOTE_B, 7);
              delay(100);
              ledcWrite(soundpin,0);
              delay(30);
            }
          }
          resetArray(inputPassword, passwordLength);
          idx = 0;
        }
        else{
          Serial.println(customKey);
          inputPassword[idx++] = customKey;
        }
      }
      break;
    case DOOR_UNLOCKED:
        if(customKey == '#'){
          lockState = DOOR_LOCKED;
          Serial.println("DOOR LOCKING!");
          bool sendSuccess = radio.write(&lockState, sizeof(lockState));
          while(!sendSuccess){
            Serial.println("Transmition error. Sending again.");
            sendSuccess = radio.write(&lockState, sizeof(lockState));
          }
          prevMillis = millis();

          ledcWriteNote(soundpin,NOTE_G, 7);
          delay(300);
          ledcWriteNote(soundpin,NOTE_E, 7);
          delay(300);
          ledcWriteNote(soundpin,NOTE_C, 7);
          delay(300);
          ledcWrite(soundpin,0);
        }
      break;
  }
}

bool compareArrays(char arr1[], char arr2[], int size) {
  for(int i = 0; i < size; i++){
    Serial.print(arr1[i]);
  }
  Serial.println();

  for (int i = 0; i < size; i++) {
    if (arr1[i] != arr2[i]) {
      return false; // If any element is different, return false
    }
  }
  return true; // All elements are identical
}

void resetArray(char arr[], int size) {
  for (int i = 0; i < size; i++) {
    arr[i] = '$';
  }
}

void print_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason)
  {
    case 2  : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case 3  : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case 4  : Serial.println("Wakeup caused by timer"); break;
    case 5  : Serial.println("Wakeup caused by touchpad"); break;
    case 6  : Serial.println("Wakeup caused by ULP program"); break;
    default : Serial.println("Wakeup was not caused by deep sleep"); break;
  }
}

[Servo Module Code(Rx)]

#include "ESP32Servo.h"
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <esp_sleep.h>
#include <EEPROM.h>
#include "driver/rtc_io.h"

RF24 radio(4, 5);
// long long address = 0x1234ABCDEFLL;
const byte address[][6] = {"00001", "00002"};

#define SERVO_PIN 13
#define NRF_IRQ_PIN 14  // Pin connected to nRF24L01+ IRQ pin
Servo myservo;

bool isServoRotated = false;
bool isLocked = true;

enum doorLockState {
  DOOR_LOCKED,
  DOOR_UNLOCKING,
  DOOR_UNLOCKED
};

doorLockState lockState = DOOR_LOCKED;

unsigned long lastRadioSignalTime = 0;
const unsigned long sleepDelay = 5000; // 5 seconds

volatile bool radioSignalReceived = false;

void IRAM_ATTR onRadioSignal() {
  radioSignalReceived = true;
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  myservo.setPeriodHertz(50);
  myservo.attach(SERVO_PIN, 500, 2400);
  myservo.write(10);

  radio.begin();
  radio.openReadingPipe(1, address[1]);
  radio.setDataRate(RF24_2MBPS);
  radio.setPALevel(RF24_PA_MIN);
  radio.setChannel(0x4C);
  radio.startListening();
  delay(1000);

  // Set up the interrupt for the nRF24L01+ IRQ pin
  pinMode(NRF_IRQ_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(NRF_IRQ_PIN), onRadioSignal, FALLING);

  // Initialize EEPROM
  EEPROM.begin(512);

  // Check if EEPROM is initialized
  if (EEPROM.read(0) != 0xAA) {
    // EEPROM is not initialized, set default values
    lockState = DOOR_LOCKED;
    isServoRotated = false;
    isLocked = true;

    // Write default values to EEPROM
    EEPROM.write(0, 0xAA); // Mark EEPROM as initialized
    EEPROM.write(1, lockState);
    EEPROM.write(2, isServoRotated);
    EEPROM.write(3, isLocked);
    EEPROM.commit();
  } else {
    // EEPROM is initialized, read stored values
    lockState = (doorLockState)EEPROM.read(1);
    isServoRotated = EEPROM.read(2);
    isLocked = EEPROM.read(3);
  }
}

void loop() {
  // put your main code here, to run repeatedly:
  if (radioSignalReceived) {
    radioSignalReceived = false;
    if (radio.available()) {
      radio.read(&lockState, sizeof(lockState));
      Serial.println(lockState);
      lastRadioSignalTime = millis();
    }
  }

  if (millis() - lastRadioSignalTime >= sleepDelay) {
    // No signal for 5 seconds, go to sleep
    Serial.println("No signal, going to sleep...");
    Serial.flush();
    enterDeepSleep();
  }

  switch (lockState) {
    case DOOR_LOCKED:
      if (!isServoRotated || !isLocked) {
        Serial.println("Servo rotating for door locking!");
        myservo.write(0);
        delay(1000);
        isServoRotated = true;
        isLocked = true;
      }
      break;
    case DOOR_UNLOCKING:
      isServoRotated = false;
      break;
    case DOOR_UNLOCKED:
      if (!isServoRotated) {
        Serial.println("Servo rotating for door unlocking!");
        myservo.write(100);
        delay(1000);
        isServoRotated = true;
      }
      isLocked = false;
      break;
  }
}

void enterDeepSleep() {
  // Store state in EEPROM
  EEPROM.write(1, lockState);
  EEPROM.write(2, isServoRotated);
  EEPROM.write(3, isLocked);
  EEPROM.commit();

  // Prepare for deep sleep
  esp_sleep_enable_ext0_wakeup((gpio_num_t)NRF_IRQ_PIN, 0); // Wake up on falling edge (LOW signal)
  Serial.println("Going to deep sleep...");
  Serial.flush();
  gpio_pullup_en((gpio_num_t)NRF_IRQ_PIN);
  esp_deep_sleep_start();
}

I'm suffering from this problem for the whole day... Please I need your help guys
Thanks.

You just need to use the interrupt wake up function. It will be associated to one pin, in your case the nRF module. I am quite sure the library examples contain that scenario (pin interrupt, not nRF necessarily)

Yes as you can see in my code, the IRQ pin is on GPIO 14, which i set to external wakeup.

Did I do something wrong?

Without more code and a refresher course, I don't know for sure, but it doesn't look famiiar.
Whenever an esp32 is involved, a trip to the randomnerdstutorial (google that) is a good idea. Here is what I found with a 2 minute google. NOTE the comments in the code re only certain pins can be used.
https://randomnerdtutorials.com/esp32-external-wake-up-deep-sleep/

Have you monitored this signal line while you are testing and did it fall when you expect it to fall?

Hi
I connected the nRF24 module's IRQ pin to another arduino GPIO 2 which allows interrupt.
Of course I grounded all the esp32, arduino and the nRF module together.
The arduino also reacts well on the interrupt when the esp32 is not asleep, but when esp32 goes to deep sleep, the arduino can't detect any signal either.

So it seems like when the esp32 board is in deep sleep, the nRF module also can't make the proper signal from IRQ pin.

I can solve this problem by using light sleep but it can't save a meaningful amount of power.
Some articles say that the nRF module doesn't work in deep sleep mode.
If anyone knows the answer for this... I'll be pleased to hear:)

It's the ESP32 that goes into deep sleep mode, not the nRF. The only way that could be effected is through the interface to the ESP.

Have you monitored the logic state of all interface connections between the ESP and the nRF when the former goes into deep sleep? Do they change?

Have you done as @Paul_KD7HB suggested and monitored the nRF's interrupt line to see if it in fact falls when you expect it to?

Have your replaced the nRF with a simple switch to ground to see if that will cause the interrupt to activate?

1 Like

Hi, gfvalvo

I checked the states of the nRF pins with my multimeter and mainly the VCC, GND was the same and also the IRQ pin was always pulled up to 3.3V before and after the ESP went to bed.

So I tested this with another arduino while maintaining the connection to the ESP and the nRF. You can see more explanation of how I did this in my reply to Paul's question.

Since I programmed the ESP to wake up at LOW signal of GPIO 14, I directly shorted the ground with GPIO 14 with a jumper wire and it just woke up as usual.

After trying all of these things and the ESP32 never seemed to wake up, I concluded that it doesn't react to my nRF when it's asleep. (but I still can't understand why)

Feel free to correct me if I have any mistakes. thanks!

What about the other pins in the interface? Especially CE?

That's not what was suggested. You need to use the configuration you're trying to make work. You must monitor the IRQ pin in real time using an oscilloscope (or at the very least a logic analyzer --- they're very inexpensive). Then, send a message to the nRF and see if the IRQ line changes state. Again, this needs to be done real time, not with a DMM.

That is an unsupported conclusion. It contradicts what you said here:

The nRF has no way to know that the ESP is asleep unless going to sleep changes the state of interface between them. You have not adequately investigated that.

Thanks for the feedback and I'll try checking with my oscilloscope.
However, I still think if the nRF module worked properly, the IRQ pin would make a LOW signal and interrupt the arduino even though I didn't check it with an oscilloscope.
When the ESP32 is not sleeping, the arduino also catches the interrupt signal but when it's sleeping, there's no response on the arduino either.

To clarify, the interrupt signal is detected on both the ESP32 and arduino when ESP32 is not sleeping, but when the ESP32 is sleeping while the arduino still stays the same, the interrupt signal is not detected on either side.

I'll check the CE pin and the IRQ signal with my oscilloscope and make some updates.
Thanks!

Easy to check if the sleeping unit has pulled that pin LOW.

Ah, I checked the pin and it was pulled up to 3.3 volts while sleeping and not sleeping.

Is your circuit supplying the pull up voltage or is the esp internal supplying it?

EDIT: Do I see you are telling the RTC to keep that pin pulled up?

As you can see in my code, I activated the internal pullup resistor before going into deep sleep since the IRQ is active low.

The 'gpio_pullup_en' function does that.

So why is the normal way of pinmode(pin, INPUT_PULLUP) not used?

Actually, I used this too and it had the same effect.

I didn't do a good read of your code, but double check the interrupt pin is in the opposite state when asleep compared to when the NRF fires. It makes sense to me that the esp32 pin should be LOW and the NRF pulls it high. Also, a deep dive into the datasheet may be needed to make sure the pin you have picked is capable of what you want. A peek at an equivalent sample code would confirm that.