RF24 icon indicating copy to clipboard operation
RF24 copied to clipboard

[Question] Wake up from IRQ on RP2040?

Open DatanoiseTV opened this issue 3 years ago • 29 comments

I am currently having trouble with RF24 to get the RP2040 to wake up from dormant mode. It sucessfully received one packet and then stops receiving. Only resetting the MCU makes it receive another packet.

#include "common.h"
#include "pico/sleep.h"
#include "pico/stdlib.h"  // printf(), sleep_ms(), getchar_timeout_us(), to_us_since_boot(), get_absolute_time()
#include "pico/bootrom.h" // reset_usb_boot()
#include <tusb.h>         // tud_cdc_connected()
#include <RF24.h>         // RF24 radio object

RF24 radio(1, 5);

// on every successful transmission
uint8_t payload[4] = {0x23, 0x42, 0x05, 0x00};
uint8_t address[][6] = {"1Node", "2Node", "3Node", "4Node", "5Node", "6Node"};

bool initialized = 0;

#ifdef __cplusplus
extern "C"
{
#endif

    int main(void)
    {

        stdio_init_all();
        // initialize the transceiver on the SPI bus
        if (!radio.begin())
        {
            printf("radio hardware is not responding!!\n");
        }
        else
        {
            printf("radio hardware is responding!!\n");
        }

        radio.setDataRate(RF24_250KBPS);

        radio.setChannel(110);
        radio.maskIRQ(0, 0, 1);

        // 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_MIN); // RF24_PA_MAX is default.

        // save on transmission time by setting the radio to only transmit the
        // number of bytes we need to transmit a float
        radio.setPayloadSize(sizeof(payload) / sizeof(uint8_t)); // float datatype occupies 4 bytes

        radio.enableDynamicPayloads();
        printf("Payload size is %d\n", sizeof(payload) / sizeof(uint8_t));

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

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

        radio.printDetails();       // (smaller) function that prints raw register values
        radio.printPrettyDetails(); // (larger) function that prints human readable data

        radio.startListening();

        // Wake GPIO
        gpio_init(0);
        gpio_set_dir(0, GPIO_IN);
        gpio_set_input_hysteresis_enabled(0, 0);

        // Debug LED
        gpio_init(15);
        gpio_set_dir(15, GPIO_OUT);

        while (1)
        {

            gpio_put(15, 0);

            if (radio.available())
            {
                gpio_put(15, 1);
                uint8_t payloadSize = radio.getDynamicPayloadSize();
                uint8_t payload[payloadSize];

                radio.read(&payload, payloadSize);

                printf("Payload size: %i\n", payloadSize);

                for (int i = 0; i < payloadSize; i++)
                {
                    printf("%02X ", payload[i]);
                }
                printf("\n");

                // radio.startListening();

                sleep_run_from_xosc();
                sleep_goto_dormant_until_pin(0, 0, false); // Wait till pin 0 is low.
            }
        }
    }

#ifdef __cplusplus
}
#endif

DatanoiseTV avatar Jul 21 '22 16:07 DatanoiseTV

I'm not familiar with the pico SDK's sleep functionality. We have a IRQ pico example, but it only uses interrupts for the TX side because there was problems (plural) in the RX side.

https://github.com/nRF24/RF24/blob/92f4a1120f9acbb8db48e729770927621f52115d/examples_pico/interruptConfigure.cpp#L75-L81

I'll have to research some things in the picoSDK first, specifically how to sleep/wake the RP2040 and how to wake the RP2040 via a ISR.

2bndy5 avatar Jul 21 '22 16:07 2bndy5

I've tested with shorting the gpio manually and it seems to work, but not with the NRF (yet)

sleep_run_from_xosc();
sleep_goto_dormant_until_pin(0, 0, false); // Wait till pin 0 is low.

DatanoiseTV avatar Jul 21 '22 16:07 DatanoiseTV

Does the power to radio get cut when the RP2040 goes to sleep? That would be a problem.

2bndy5 avatar Jul 21 '22 16:07 2bndy5

@2bndy5 Nope, the radio stays powered on all the time.

DatanoiseTV avatar Jul 21 '22 17:07 DatanoiseTV

I found one issue. I was using radio.maskIRQ(0,0,1) to get the RX interrupt. But it should be maskIRQ(1,1,0). But still I cannot get it to properly wake up and start receiving packets again.

DatanoiseTV avatar Jul 21 '22 17:07 DatanoiseTV

/*! \brief Send system to sleep until the specified GPIO changes
 *  \ingroup hardware_sleep
 *
 * One of the sleep_run_* functions must be called prior to this call
 *
 * \param gpio_pin The pin to provide the wake up
 * \param edge true for leading edge, false for trailing edge
 * \param high true for active high, false for active low
 */
void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high);

I think edge needs to be true There won't be a trailing edge until the IRQ is cleared (on the radio's side).

2bndy5 avatar Jul 21 '22 17:07 2bndy5

/*! \brief Send system to sleep until the specified GPIO changes
 *  \ingroup hardware_sleep
 *
 * One of the sleep_run_* functions must be called prior to this call
 *
 * \param gpio_pin The pin to provide the wake up
 * \param edge true for leading edge, false for trailing edge
 * \param high true for active high, false for active low
 */
void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high);

I think edge needs to be true There won't be a trailing edge until the IRQ is cleared (on the radio's side).

Unfortunately same issue. I also tried to call radio.whatHappened() after wakeup in order to clean the interrupt flag, but this also didn't help.

DatanoiseTV avatar Jul 21 '22 17:07 DatanoiseTV

I'm starting to think disabling the clock might have a negative impact. My primary concern is with using the USB UART. From the pico-extras sleep example:


    // UART will be reconfigured by sleep_run_from_xosc
    sleep_run_from_xosc();

    printf("Running from XOSC\n");
    uart_default_tx_wait_blocking();

    printf("XOSC going dormant\n");
    uart_default_tx_wait_blocking();

and a note directly from sleep.c:

// The difference between sleep and dormant is that ALL clocks are stopped in dormant mode,
// until the source (either xosc or rosc) is started again by an external event.
// In sleep mode some clocks can be left running controlled by the SLEEP_EN registers in the clocks
// block. For example you could keep clk_rtc running. Some destinations (proc0 and proc1 wakeup logic)
// can't be stopped in sleep mode otherwise there wouldn't be enough logic to wake up again.

I don't think the SPI SCK needs to be running when the radio's CSN is LOW, but this is my secondary concern.

2bndy5 avatar Jul 21 '22 17:07 2bndy5

Found a couple notes calling the pico_sleep lib "WIP" and "not yet stable". IDK if that has anything to do with this issue. In developing the PicoSDK support, we really didn't get into testing with the pico-extras libs, so I'm in new territory here. Originally, my intention was to just use the IRQ functions in the PicoSDK, but your app is taking a different approach.

2bndy5 avatar Jul 21 '22 17:07 2bndy5

Found a couple notes calling the pico_sleep lib "WIP" and "not yet stable". IDK if that has anything to do with this issue. In developing the PicoSDK support, we really didn't get into testing with the pico-extras libs, so I'm in new territory here. Originally, my intention was to just use the IRQ functions in the PicoSDK, but your app is taking a different approach.

I will hook up my logic analyser and try to figure out what could be going wrong.

DatanoiseTV avatar Jul 21 '22 17:07 DatanoiseTV

BTW, you don't need to re-post my entire comment in reply.

2bndy5 avatar Jul 21 '22 17:07 2bndy5

image

This is how SCK, MOSI and CS look after the IRQ has been fired.

DatanoiseTV avatar Jul 21 '22 17:07 DatanoiseTV

Is everything before that when the RP2040 is awake? Am I looking at the interrupt after sleep_goto_dormant_until_pin() is called?

2bndy5 avatar Jul 21 '22 18:07 2bndy5

I've figured out the deepsleep waking actually works when tying that gpio to gnd (and without nrf24). I guess the pulse from the NRF24L01+ is too short to trigger the interrupt?

DatanoiseTV avatar Jul 21 '22 18:07 DatanoiseTV

IRQ going active LOW shouldn't be a pulse. It should remain LOW until what_happened() is called to clear the radio's RX_DR flag that triggered it.

2bndy5 avatar Jul 21 '22 18:07 2bndy5

Alternatively, RF24::read() can also be used to clear the interrupt, if you've set maskIRQ() to only reflect the "data received" event. https://github.com/nRF24/RF24/blob/92f4a1120f9acbb8db48e729770927621f52115d/RF24.cpp#L1542-L1550

2bndy5 avatar Jul 21 '22 18:07 2bndy5

Even if not calling read() or what_happened(), the IRQ is just a short pulse and goes to high. For some reason, before a package is received, IRQ stays low.

DatanoiseTV avatar Jul 21 '22 18:07 DatanoiseTV

No idea why it is only a pulse. From you pic, I think available() is called twice after IRQ goes active, then the call to read() gets the payload and clears the flag.

Turning off hysteresis might be causing the RP2040 to not "see" the pulse. See picoSDK docs about gpio_set_input_hysteresis_enabled()

2bndy5 avatar Jul 21 '22 18:07 2bndy5

For reference, the current code is: https://gist.github.com/DatanoiseTV/5e5059a4e3947e1859e5af303d7464d9

DatanoiseTV avatar Jul 21 '22 18:07 DatanoiseTV

I did some more testing today and weirdly enough, it received always 3 packets after wakeup (no matter in which interval) and then becomes unreachable.

Edit: Sometimes it is 4.

DatanoiseTV avatar Jul 22 '22 13:07 DatanoiseTV

That just means the radio's RX FIFO is full (max capacity is 3 payloads).

2bndy5 avatar Jul 22 '22 13:07 2bndy5

I tried now also flushing the RX/TX before going to sleep. I tried dormant mode with a bare (no RF24) project and it seems to work properly with active low. I am calling whatHappened() after waking up again, but somehow have the feeling that the interrupt doesn't get cleared because the IRQ gpio stays low.

DatanoiseTV avatar Jul 22 '22 13:07 DatanoiseTV

Wait, do mean it is waking up via interrupt now? Or are you still manually pulling the pin low?

Nonetheless, if the radio's RX FIFO is full, then any incoming transmission is ignored. If it received 4 payloads before blocking, then something must have fetched/cleared a single payload from the RX FIFO.

I tried now also flushing the RX/TX before going to sleep

The RX FIFO is an independent stack from the TX FIFO. The TX FIFO is only used in RX mode if you're using custom Ack payloads in the AutoAck'd packets.

I am calling whatHappened() after waking up again

Are you also checking the flags' states? That function was specifically meant to inform the app what triggered the IRQ. If the tx_ok or tx_fail flags are triggered, then there is something else going on (usually with ACK payloads), and the maskIRQ(1, 1, 0) is taking affect.

somehow have the feeling that the interrupt doesn't get cleared because the IRQ gpio stays low

At least it isn't a pulse anymore. we're closer to expected behavior there. I think you can fool the lib into clearing the RX_DR flag by calling read(buf, 0), but I'd double check the mask_irq() isn't configured to reflect anything other than the RX_DR flag.

2bndy5 avatar Jul 22 '22 13:07 2bndy5

I tried to manually wake up the RP2040 by a low gpio to verify the sleep is working properly.

My IRQ mask is set to 1, 1, 0, so it should be only getting the RX_DR.

Current code with your recommended changes:

#include "common.h"
#include "pico/sleep.h"
#include "hardware/clocks.h"
#include "hardware/rosc.h"
#include "hardware/structs/scb.h"
#include "hardware/structs/watchdog.h"
#include "hardware/structs/psm.h"

#include "hardware/regs/psm.h"
#include "hardware/pll.h"
#include "hardware/xosc.h"

#include "pico/stdlib.h"  // printf(), sleep_ms(), getchar_timeout_us(), to_us_since_boot(), get_absolute_time()
#include "pico/bootrom.h" // reset_usb_boot()
//#include <tusb.h>         // tud_cdc_connected()
#include <RF24.h> // RF24 radio object

RF24 radio(1, 5);

// on every successful transmission
uint8_t payload[4] = {0x23, 0x42, 0x05, 0x00};
uint8_t address[][6] = {"1Node", "2Node", "3Node", "4Node", "5Node", "6Node"};

#ifdef __cplusplus
extern "C"
{
#endif

    void recover_from_sleep(uint scb_orig, uint clock0_orig, uint clock1_orig)
    {

        // Re-enable ring Oscillator control
        rosc_write(&rosc_hw->ctrl, ROSC_CTRL_ENABLE_LSB);

        // reset procs back to default
        scb_hw->scr = scb_orig;
        clocks_hw->sleep_en0 = clock0_orig;
        clocks_hw->sleep_en1 = clock1_orig;
        return;
    }

    void setup_radio()
    {
        if (!radio.begin())
        {
            printf("radio hardware is not responding!!\n");
        }
        else
        {
            printf("radio hardware is responding!!\n");
        }

        radio.setDataRate(RF24_250KBPS);

        radio.setChannel(110);
        radio.maskIRQ(1, 1, 0);

        // 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_MIN); // RF24_PA_MAX is default.

        // save on transmission time by setting the radio to only transmit the
        // number of bytes we need to transmit a float
        radio.setPayloadSize(sizeof(payload) / sizeof(uint8_t)); // float datatype occupies 4 bytes

        radio.enableDynamicPayloads();
        printf("Payload size is %d\n", sizeof(payload) / sizeof(uint8_t));

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

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

        radio.printDetails();       // (smaller) function that prints raw register values
        radio.printPrettyDetails(); // (larger) function that prints human readable data

        radio.startListening();
    }

    int main(void)
    {

        /*
        vreg_set_voltage(VREG_VOLTAGE_0_95);
        // set_sys_clock_48mhz();

        // Change clk_sys to be 48MHz. The simplest way is to take this from PLL_USB
        // which has a source frequency of 48MHz
        clock_configure(clk_sys,
                        CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX,
                        CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
                        48 * MHZ,
                        48 * MHZ);

        // Turn off PLL sys for good measure
        pll_deinit(pll_sys);

        // CLK peri is clocked from clk_sys so need to change clk_peri's freq
        clock_configure(clk_peri,
                        0,
                        CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS,
                        48 * MHZ,
                        48 * MHZ);
        */

        stdio_init_all();
        // initialize the transceiver on the SPI bus

        setup_radio();

        // Wake Up GPIO
        gpio_init(14);
        gpio_set_dir(14, GPIO_IN);

        // Debug LED
        gpio_init(15);
        gpio_set_dir(15, GPIO_OUT);
        gpio_put(15, 1);
        sleep_ms(1000);

        // save values for later
        uint scb_orig = scb_hw->scr;
        uint clock0_orig = clocks_hw->sleep_en0;
        uint clock1_orig = clocks_hw->sleep_en1;

        // This works and sees interrupts, but dormant mode doesn't
        // gpio_set_irq_enabled_with_callback(14, GPIO_IRQ_EDGE_FALL, 1, gpio_callback);

        while (1)
        {

            // blink led on pin 15 to check if we are still running
            gpio_put(15, 1);
            sleep_ms(50);
            gpio_put(15, 0);
            sleep_ms(50);

            if (radio.available())
            {
                uint8_t payloadSize = radio.getDynamicPayloadSize();
                uint8_t payload[payloadSize];

                radio.read(&payload, payloadSize);

                printf("Payload size: %i\n", payloadSize);

#ifdef DEBUG_PAYLOAD
                for (int i = 0; i < payloadSize; i++)
                {
                    printf("%02X ", payload[i]);
                }
                printf("\n");
#endif
            }

            sleep_run_from_xosc();

            // 14, true, false doesn't trigger the wakeup
            sleep_goto_dormant_until_pin(14, false, false); // Wait till pin 14 is low.
            recover_from_sleep(scb_orig, clock0_orig, clock1_orig);

            // dummy read to clear interrupt flag in nrf24l01
            uint8_t buf[1];
            radio.read(buf, 0);
        }
    }

#ifdef __cplusplus
}
#endif

DatanoiseTV avatar Jul 22 '22 14:07 DatanoiseTV

  1. setPayloadSize() only applies to static payload lengths. This configuration is obsolete if dynamic payloads are enabled.
  2. While I understand the conception of calling startListening() in the end of setup_radio(), this might be leaving the radio open to triggering the IRQ before going dormant if the radio recieved something between the time that setup_radio() exits and the time that sleep_goto_dormant_until_pin() is called.

2bndy5 avatar Jul 22 '22 14:07 2bndy5

It might be better to use while (radio.available()) to make sure the RX FIFO is indeed cleared before going dormant.

2bndy5 avatar Jul 22 '22 14:07 2bndy5

I think sleep now actually works after removing sleep_run_from_xosc() and changing sleep_goto_dormant_until_pin(14, true, false);

It's a bit hard to debug, but my led flashes and then stays off till another packet is received, so it seems as if the MCU sleeps.

DatanoiseTV avatar Jul 22 '22 14:07 DatanoiseTV

Its possible that sleep_run_from_xosc() is an init only function. The pico-sleep examples are so simple, that they could be considered an Arduino-style setup() function.

2bndy5 avatar Jul 22 '22 14:07 2bndy5

If we get this working, I would be inclined to include this in the examples_pico for this lib. I think this approach would be very desirable for other's projects. And, we'd also have a reason to use the pico-extras libs in our CI as well. BTW, I'm very big on using explanatory comments when specific to the example's application.

2bndy5 avatar Jul 22 '22 14:07 2bndy5