Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ESP32 Core Panic on radio 0; No panic on Radio number 1, same wiring. How to troubleshoot?[Question] #939

Closed
Tech500 opened this issue Jan 28, 2024 · 22 comments · Fixed by #956
Assignees

Comments

@Tech500
Copy link

Tech500 commented Jan 28, 2024

Radio 0 after selecting radio number, then pressing T; getting a core panic.

Board is a Doit ESP32 Devkit V1; using Arduino IDE

`SPI pin defines:

MOSI 23
MISO 19
SCK 18
CE_PIN 17
CSN_PIN 5
IRQ 15 //Unable to upload code with connection to GPIO2`

Gist of Radio 0 Core panic

@Tech500
Copy link
Author

Tech500 commented Jan 28, 2024

Swapped radio; issue is resolved.

Should there be an immediate response from the Transmit ping test?

@2bndy5
Copy link
Member

2bndy5 commented Jan 28, 2024

Should there be an immediate response from the Transmit ping test?

Yes. In testing the configure IRQ example, I always enabled the RX side before engaging the TX side.

But I'm not sure why it is panicking. There must be a buffer operation missing if response is not received.

@Tech500
Copy link
Author

Tech500 commented Jan 29, 2024

I have taken the configure IRQ example and modified it for use in relay project. Even now I still have Core Panic after seeing "IRQ pin is actively LOW".

`nRF24L01+ IRQ_Receiver
Receving...

Boot number: 1
IRQ pin is actively LOW
data_sent: 1, data_fail: 0, data_ready: 0
'Data Ready' eveGuru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1).

Core 1 register dump:
PC : 0x4008ddac PS : 0x00060935 A0 : 0x8008cd1e A1 : 0x3ffbf13c
A2 : 0x3ffb897c A3 : 0x3ffb81a4 A4 : 0x00000004 A5 : 0x00060923
A6 : 0x00060923 A7 : 0x00000001 A8 : 0x3ffb81a4 A9 : 0x00000018
A10 : 0x3ffb81a4 A11 : 0x00000018 A12 : 0x3ffc265c A13 : 0x00060923
A14 : 0x007bf398 A15 : 0x003fffff SAR : 0x0000001b EXCCAUSE: 0x00000006
EXCVADDR: 0x00000000 LBEG : 0x40088ea9 LEND : 0x40088eb9 LCOUNT : 0xfffffff9
Core 1 was running in ISR context:
EPC1 : 0x400dc63b EPC2 : 0x00000000 EPC3 : 0x00000000 EPC4 : 0x00000000

Backtrace: 0x4008dda9:0x3ffbf13c |<-CORRUPTED

Core 0 register dump:
PC : 0x4008df3b PS : 0x00060035 A0 : 0x8008c947 A1 : 0x3ffbeabc
A2 : 0x3ffbf398 A3 ���I� 0x00000000 LBEG : 0x40088ea9 LEND : 0x40088eb9 LCOUNT : 0xfffffff9
Core 1 was running in ISR context:
EPC1 : 0x400dc657 EPC2 : 0x00000000 EPC3 : 0x00000000 EPC4 : 0x00000000

Backtrace: 0x4008dda7:0x3ffbf13c |<-CORRUPTED
`

Have no idea of what to do with the Core register dumps. Closer to goal than previously...

@TMRh20
Copy link
Member

TMRh20 commented Jan 29, 2024

  bool tx_ds, tx_df, rx_dr;                 // declare variables for IRQ masks
  radio.whatHappened(tx_ds, tx_df, rx_dr);
  wait_for_event = false;

I seem to be getting a similar reaction from my ESP32 using the interrupt example. I modified the interrupt to contain just the above, and it still does it on reception of data. If I remove everything except for the wait_for_event=false then it doesn't hang anymore. I suspect there is something going on with SPI and/or SPI transactions as they are supposed to be interrupt safe.
This will take some time to debug, so I would suggest not using interrupts with ESP32 for the time being, unless you can move all the functions that call SPI commands to outside of the interrupt.

@TMRh20 TMRh20 added the bug label Jan 29, 2024
@TMRh20
Copy link
Member

TMRh20 commented Jan 29, 2024

This would be a workaround until we can figure out what is going on here:
Just need to configure the CE,CS and IRQ pins.

/*
 * See documentation at https://nRF24.github.io/RF24
 * See License information at root directory of this library
 * Author: Brendan Doherty (2bndy5)
 */

/**
 * This example uses Acknowledgement (ACK) payloads attached to ACK packets to
 * demonstrate how the nRF24L01's IRQ (Interrupt Request) pin can be
 * configured to detect when data is received, or when data has transmitted
 * successfully, or when data has failed to transmit.
 *
 * This example was written to be used on 2 devices acting as "nodes".
 * Use the Serial Monitor to change each node's behavior.
 */
#include <SPI.h>
#include "printf.h"
#include "RF24.h"

// We will be using the nRF24L01's IRQ pin for this example
#define IRQ_PIN 27                     // this needs to be a digital input capable pin
volatile bool wait_for_event = false;  // used to wait for an IRQ event to trigger
volatile bool gotInterrupt = false;

#define CE_PIN 12
#define CSN_PIN 14
// instantiate an object for the nRF24L01 transceiver
RF24 radio(CE_PIN, CSN_PIN);

// Let these addresses be used for the pair
uint8_t address[][6] = { "1Node", "2Node" };
// It is very helpful to think of an address as a path instead of as
// an identifying device destination

// to use different addresses on a pair of radios, we need a variable to
// uniquely identify which address this radio will use to transmit
bool radioNumber = 1;  // 0 uses address[0] to transmit, 1 uses address[1] to transmit

// Used to control whether this node is sending or receiving
bool role = false;  // true = TX node, false = RX node

// For this example, we'll be using a payload containing
// a string that changes on every transmission. (successful or not)
// Make a couple arrays of payloads & an iterator to traverse them
const uint8_t tx_pl_size = 5;
const uint8_t ack_pl_size = 4;
uint8_t pl_iterator = 0;
// The " + 1" compensates for the c-string's NULL terminating 0
char tx_payloads[][tx_pl_size + 1] = { "Ping ", "Pong ", "Radio", "1FAIL" };
char ack_payloads[][ack_pl_size + 1] = { "Yak ", "Back", " ACK" };

void interruptHandler();  // prototype to handle IRQ events
void printRxFifo();       // prototype to print RX FIFO with 1 buffer


void setup() {
  Serial.begin(115200);
  while (!Serial) {
    // some boards need to wait to ensure access to serial over USB
  }

  // initialize the transceiver on the SPI bus
  if (!radio.begin()) {
    Serial.println(F("radio hardware is not responding!!"));
    while (1) {}  // hold in infinite loop
  }

  // print example's introductory prompt
  Serial.println(F("RF24/examples/InterruptConfigure"));

  // To set the radioNumber via the Serial monitor on startup
  Serial.println(F("Which radio is this? Enter '0' or '1'. Defaults to '0'"));
  while (!Serial.available()) {
    // wait for user input
  }
  char input = Serial.parseInt();
  radioNumber = input == 1;
  Serial.print(F("radioNumber = "));
  Serial.println((int)radioNumber);

  // role variable is hardcoded to RX behavior, inform the user of this
  Serial.println(F("*** PRESS 'T' to begin transmitting to the other node"));

  // setup the IRQ_PIN
  pinMode(IRQ_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(IRQ_PIN), interruptHandler, FALLING);
  // IMPORTANT: do not call radio.available() before calling
  // radio.whatHappened() when the interruptHandler() is triggered by the
  // IRQ pin FALLING event. According to the datasheet, the pipe information
  // is unreliable during the IRQ pin FALLING transition.

  // Set the PA Level low to try preventing power supply related problems
  // because these examples are likely run with nodes in close proximity to
  // each other.
  radio.setPALevel(RF24_PA_LOW);  // RF24_PA_MAX is default.

  // For this example we use acknowledgment (ACK) payloads to trigger the
  // IRQ pin when data is received on the TX node.
  // to use ACK payloads, we need to enable dynamic payload lengths
  radio.enableDynamicPayloads();  // ACK payloads are dynamically sized

  // Acknowledgement packets have no payloads by default. We need to enable
  // this feature for all nodes (TX & RX) to use ACK payloads.
  radio.enableAckPayload();
  // Fot this example, we use the same address to send data back and forth

  // set the TX address of the RX node into the TX pipe
  radio.openWritingPipe(address[radioNumber]);  // always uses pipe 0

  // set the RX address of the TX node into a RX pipe
  radio.openReadingPipe(1, address[!radioNumber]);  // using pipe 1

  // additional setup specific to the node's role
  if (role) {
    // setup for TX mode
    radio.stopListening();  // put radio in TX mode

  } else {
    // setup for RX mode

    // let IRQ pin only trigger on "data ready" event in RX mode
    radio.maskIRQ(1, 1, 0);  // args = "data_sent", "data_fail", "data_ready"

    // Fill the TX FIFO with 3 ACK payloads for the first 3 received
    // transmissions on pipe 1
    radio.writeAckPayload(1, &ack_payloads[0], ack_pl_size);
    radio.writeAckPayload(1, &ack_payloads[1], ack_pl_size);
    radio.writeAckPayload(1, &ack_payloads[2], ack_pl_size);

    radio.startListening();  // put radio in RX mode
  }

  // For debugging info
  // printf_begin();             // needed only once for printing details
  // radio.printDetails();       // (smaller) function that prints raw register values
  // radio.printPrettyDetails(); // (larger) function that prints human readable data
}

void loop() {


  if (gotInterrupt) {
    gotInterrupt = false;
    Serial.println(F("\tIRQ pin is actively LOW"));  // show that this function was called
    delayMicroseconds(250);
    bool tx_ds, tx_df, rx_dr;                 // declare variables for IRQ masks
    radio.whatHappened(tx_ds, tx_df, rx_dr);  // get values for IRQ masks
    // whatHappened() clears the IRQ masks also. This is required for
    // continued TX operations when a transmission fails.
    // clearing the IRQ masks resets the IRQ pin to its inactive state (HIGH)

    Serial.print(F("\tdata_sent: "));
    Serial.print(tx_ds);  // print "data sent" mask state
    Serial.print(F(", data_fail: "));
    Serial.print(tx_df);  // print "data fail" mask state
    Serial.print(F(", data_ready: "));
    Serial.println(rx_dr);  // print "data ready" mask state

    if (tx_df)           // if TX payload failed
      radio.flush_tx();  // clear all payloads from the TX FIFO

    // print if test passed or failed. Unintentional fails mean the RX node was not listening.
    // pl_iterator has already been incremented by now
    if (pl_iterator <= 1) {
      Serial.print(F("   'Data Ready' event test "));
      Serial.println(rx_dr ? F("passed") : F("failed"));
    } else if (pl_iterator == 2) {
      Serial.print(F("   'Data Sent' event test "));
      Serial.println(tx_ds ? F("passed") : F("failed"));
    } else if (pl_iterator == 4) {
      Serial.print(F("   'Data Fail' event test "));
      Serial.println(tx_df ? F("passed") : F("failed"));
    }
    wait_for_event = false;  // ready to continue with loop() operations
  }


  if (role && !wait_for_event) {

    // delay(1); // wait for IRQ pin to fully RISE

    // This device is a TX node. This if block is only triggered when
    // NOT waiting for an IRQ event to happen

    if (pl_iterator == 0) {
      // Test the "data ready" event with the IRQ pin

      Serial.println(F("\nConfiguring IRQ pin to ignore the 'data sent' event"));
      radio.maskIRQ(true, false, false);  // args = "data_sent", "data_fail", "data_ready"
      Serial.println(F("   Pinging RX node for 'data ready' event..."));

    } else if (pl_iterator == 1) {
      // Test the "data sent" event with the IRQ pin

      Serial.println(F("\nConfiguring IRQ pin to ignore the 'data ready' event"));
      radio.maskIRQ(false, false, true);  // args = "data_sent", "data_fail", "data_ready"
      Serial.println(F("   Pinging RX node for 'data sent' event..."));

    } else if (pl_iterator == 2) {
      // Use this iteration to fill the RX node's FIFO which sets us up for the next test.

      // write() uses virtual interrupt flags that work despite the masking of the IRQ pin
      radio.maskIRQ(1, 1, 1);  // disable IRQ masking for this step

      Serial.println(F("\nSending 1 payload to fill RX node's FIFO. IRQ pin is neglected."));
      // write() will call flush_tx() on 'data fail' events
      if (radio.write(&tx_payloads[pl_iterator], tx_pl_size)) {
        if (radio.rxFifoFull()) {
          Serial.println(F("RX node's FIFO is full; it is not listening any more"));
        } else {
          Serial.println("Transmission successful, but the RX node might still be listening.");
        }
      } else {
        Serial.println(F("Transmission failed or timed out. Continuing anyway."));
        radio.flush_tx();  // discard payload(s) that failed to transmit
      }

    } else if (pl_iterator == 3) {
      // test the "data fail" event with the IRQ pin

      Serial.println(F("\nConfiguring IRQ pin to reflect all events"));
      radio.maskIRQ(0, 0, 0);  // args = "data_sent", "data_fail", "data_ready"
      Serial.println(F("   Pinging inactive RX node for 'data fail' event..."));
    }

    if (pl_iterator < 4 && pl_iterator != 2) {

      // IRQ pin is LOW when activated. Otherwise it is always HIGH
      // Wait until IRQ pin is activated.
      wait_for_event = true;

      // use the non-blocking call to write a payload and begin transmission
      // the "false" argument means we are expecting an ACK packet response
      radio.startFastWrite(tx_payloads[pl_iterator++], tx_pl_size, false);

      // In this example, the "data fail" event is always configured to
      // trigger the IRQ pin active. Because the auto-ACK feature is on by
      // default, we don't need a timeout check to prevent an infinite loop.

    } else if (pl_iterator == 4) {
      // all IRQ tests are done; flush_tx() and print the ACK payloads for fun

      // CE pin is still HIGH which consumes more power. Example is now idling so...
      radio.stopListening();  // ensure CE pin is LOW
      // stopListening() also calls flush_tx() when ACK payloads are enabled

      printRxFifo();
      pl_iterator++;


      // inform user what to do next
      Serial.println(F("\n*** PRESS 'T' to restart the transmissions"));
      Serial.println(F("*** PRESS 'R' to change to Receive role\n"));


    } else if (pl_iterator == 2) {
      pl_iterator++;  // proceed from step 3 to last step (stop at step 4 for readability)
    }

  } else if (!role) {
    // This device is a RX node

    if (radio.rxFifoFull()) {
      // wait until RX FIFO is full then stop listening

      delay(100);             // let ACK payload finish transmitting
      radio.stopListening();  // also discards unused ACK payloads
      printRxFifo();          // flush the RX FIFO

      // Fill the TX FIFO with 3 ACK payloads for the first 3 received
      // transmissions on pipe 1.
      radio.writeAckPayload(1, &ack_payloads[0], ack_pl_size);
      radio.writeAckPayload(1, &ack_payloads[1], ack_pl_size);
      radio.writeAckPayload(1, &ack_payloads[2], ack_pl_size);

      delay(100);              // let TX node finish its role
      radio.startListening();  // We're ready to start over. Begin listening.
    }

  }  // role

  if (Serial.available()) {
    // change the role via the serial monitor

    char c = toupper(Serial.read());
    if (c == 'T') {
      // Become the TX node
      if (!role)
        Serial.println(F("*** CHANGING TO TRANSMIT ROLE -- PRESS 'R' TO SWITCH BACK"));
      else
        Serial.println(F("*** RESTARTING IRQ PIN TEST ***"));

      role = true;
      wait_for_event = false;
      pl_iterator = 0;   // reset the iterator
      radio.flush_tx();  // discard any payloads in the TX FIFO

      // startListening() clears the IRQ masks also. This is required for
      // continued TX operations when a transmission fails.
      radio.stopListening();  // this also discards any unused ACK payloads

    } else if (c == 'R' && role) {
      // Become the RX node
      Serial.println(F("*** CHANGING TO RECEIVE ROLE -- PRESS 'T' TO SWITCH BACK"));

      role = false;

      radio.maskIRQ(0, 0, 0);  // the IRQ pin should only trigger on "data ready" event

      // Fill the TX FIFO with 3 ACK payloads for the first 3 received
      // transmissions on pipe 1
      radio.flush_tx();  // make sure there is room for 3 new ACK payloads
      radio.writeAckPayload(1, &ack_payloads[0], ack_pl_size);
      radio.writeAckPayload(1, &ack_payloads[1], ack_pl_size);
      radio.writeAckPayload(1, &ack_payloads[2], ack_pl_size);
      radio.startListening();
    }
  }  // Serial.available()
}  // loop


/**
 * when the IRQ pin goes active LOW, call this fuction print out why
 */

void interruptHandler() {
  gotInterrupt = true;
}  // interruptHandler


/**
 * Print the entire RX FIFO with one buffer. This will also flush the RX FIFO.
 * Remember that the payload sizes are declared as tx_pl_size and ack_pl_size.
 */
void printRxFifo() {
  if (radio.available()) {  // if there is data in the RX FIFO
    // to flush the data from the RX FIFO, we'll fetch it all using 1 buffer

    uint8_t pl_size = !role ? tx_pl_size : ack_pl_size;
    char rx_fifo[pl_size * 3 + 1];  // RX FIFO is full & we know ACK payloads' size
    if (radio.rxFifoFull()) {
      rx_fifo[pl_size * 3] = 0;           // add a NULL terminating char to use as a c-string
      radio.read(&rx_fifo, pl_size * 3);  // this clears the RX FIFO (for this example)
    } else {
      uint8_t i = 0;
      while (radio.available()) {
        radio.read(&rx_fifo + (i * pl_size), pl_size);
        i++;
      }
      rx_fifo[i * pl_size] = 0;  // add a NULL terminating char to use as a c-string
    }
    Serial.print(F("Complete RX FIFO: "));
    Serial.println(rx_fifo);  // print the entire RX FIFO with 1 buffer
  }
}

@2bndy5
Copy link
Member

2bndy5 commented Jan 29, 2024

We should also flush the RX FIFO when switching back to RX role. That example has been in need of some attention.

@Tech500
Copy link
Author

Tech500 commented Jan 29, 2024

Would it be possiable to have a basic IRQ config for using interrupts; for example just minimum needed to implement in a project?

I did my best to sort out what I believed is needed for both transmitter and receiver. Could have missed something...

@2bndy5
Copy link
Member

2bndy5 commented Jan 29, 2024

Would it be possiable to have a basic IRQ config for using interrupts

I would refer to the docs for finer detail:

@2bndy5
Copy link
Member

2bndy5 commented Jan 29, 2024

Interrupt wdt timeout on CPU1

I believe this error is trying to say something. Probably all the stuff that was going on in that ISR was taking too long for a typical ISR. There may also be some kind of mutex lock on the Serial bus.

@Tech500
Copy link
Author

Tech500 commented Jan 29, 2024

Should there be Serial Prints in an isr; think I read they should not be used in isr and the time spent in isr short.

@2bndy5
Copy link
Member

2bndy5 commented Jan 29, 2024

If I remove everything except for the wait_for_event=false then it doesn't hang anymore.

Reminds me of rust programming language's closures. Any members of an ISR may have to be limited to volatile memory on the ESP32. Its easier to just separate everything out.

@Tech500
Copy link
Author

Tech500 commented Jan 29, 2024

Will make changes to project code; thank you appreciate the help!

@TMRh20
Copy link
Member

TMRh20 commented Jan 29, 2024 via email

@Tech500
Copy link
Author

Tech500 commented Jan 29, 2024

Getting ready to test; have moved everything below whatHappen to a conditional flag in loop.

@2bndy5
Copy link
Member

2bndy5 commented Jan 29, 2024

Created a branch named update-irq-example with the changes proposed to both Linux and Arduino examples (not py example)

@Tech500
Copy link
Author

Tech500 commented Jan 29, 2024

This is how I setup the isr; moving the function to loop with a conditional statement of interruptResults:

`portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;

volatile int interruptResults;

void isr() {

portENTER_CRITICAL_ISR(&mux);

interruptResults = 1;

portEXIT_CRITICAL_ISR(&mux);
}
`

Void loop(){ if(interruptResults){ .....your interruptHandler here no more core panic... received a couple of messages with SwitchState value of 1 more responses all following were switchState = 0 and not a value of expected 2. }

SwitchState comes on, in my code; switchState off does not change to off.

Something I missed. Do I need to clear anything before next radio,write()?

portMUX_TYPE mux Link

@Tech500
Copy link
Author

Tech500 commented Jan 30, 2024

Have "Switch Project" turning switch on and off using RF24 IRQ interrupt:

22:16:27.790 -> Boot number: 2
22:16:27.822 ->
22:16:27.822 -> IRQ pin is actively LOW
22:16:27.822 -> data_sent: 1, data_fail: 0, data_ready: 0
22:16:27.887 -> 'Data Ready' event test failed
22:16:27.918 -> switchState: 1
22:16:27.950 ->
22:16:27.950 -> Battery power switched ON
22:16:27.950 -> ESP32 wake from Deep Sleep
22:16:27.993 ->
22:16:47.670 ->
22:16:47.670 -> IRQ pin is actively LOW
22:16:47.702 -> data_sent: 1, data_fail: 0, data_ready: 0
22:16:47.735 -> 'Data Ready' event test failed
22:16:47.767 -> switchState: 2
22:16:47.799 ->
22:16:47.799 -> Battery power switched OFF
22:16:47.831 -> ESP32 going to Deep Sleep
22:17:00.243 -> ��&zy��V���$g���
22:17:00.307 ->
22:17:00.307 ->
22:17:00.307 -> nRF24L01+ IRQ_Receiver
22:17:00.338 -> Receving...
22:17:00.371 ->
22:17:00.371 -> Boot number: 3
22:17:00.371 ->
22:17:00.371 -> IRQ pin is actively LOW
22:17:00.402 -> data_sent: 1, data_fail: 0, data_ready: 0
22:17:00.435 -> 'Data Ready' event test failed
22:17:00.499 -> switchState: 1
22:17:00.499 ->
22:17:00.499 -> Battery power switched ON
22:17:00.531 -> ESP32 wake from Deep Sleep

Is it possiable to send just an IRQ signal; when nRF24L01 is powerDown. Think I read the IRQ is available in powerdown mode.
Maybe have two instances of RF24; one for switchState and one for IRQ signal, or just add another member to structure for switchState.

Would like to use powerDown() and powerUP() in project; investigating more. Appreciate how responsive you have been with the library.

@TMRh20
Copy link
Member

TMRh20 commented Jan 30, 2024

Is it possible to send just an IRQ signal; when nRF24L01 is powerDown.

Nope, the Microcontroller can be powered down, but not the radio if you want to receive data/trigger IRQ.

@Tech500
Copy link
Author

Tech500 commented Jan 30, 2024

Any way to lower current comsumption? Standby I or II?.

Measured 25.5 mA with RF24_PA_LOW. Will be running from battery...

@2bndy5
Copy link
Member

2bndy5 commented Jan 30, 2024

For hardware reasons, listening has to be done in Standby-II. The only configuration that might affect power consumption is the PA level (lower is better for consumption but worse for distance).

Remember, the PA level might be hardwired for some modules, namely ebyte modules.

@TMRh20
Copy link
Member

TMRh20 commented Feb 16, 2024

@Tech500 Thinking we can close this issue now? Hoping you've had success with your project(s)!

@2bndy5 2bndy5 linked a pull request Feb 18, 2024 that will close this issue
@2bndy5 2bndy5 removed a link to a pull request Feb 18, 2024
@TMRh20 TMRh20 closed this as completed Feb 18, 2024
@2bndy5
Copy link
Member

2bndy5 commented Feb 18, 2024

I should PR my changes to the IRQ examples to help satisfy the panic problem

master...update-irq-example

I just haven't got around to do much testing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants