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

[Question] Wake up from IRQ on RP2040? #857

Open
DatanoiseTV opened this issue Jul 21, 2022 · 29 comments
Open

[Question] Wake up from IRQ on RP2040? #857

DatanoiseTV opened this issue Jul 21, 2022 · 29 comments
Labels

Comments

@DatanoiseTV
Copy link

DatanoiseTV commented Jul 21, 2022

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
@2bndy5
Copy link
Member

2bndy5 commented Jul 21, 2022

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.

// setup the IRQ_PIN
gpio_set_irq_enabled_with_callback(IRQ_PIN, GPIO_IRQ_EDGE_FALL, true, &interruptHandler);
// 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.

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.

@DatanoiseTV
Copy link
Author

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.

@2bndy5
Copy link
Member

2bndy5 commented Jul 21, 2022

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

@DatanoiseTV
Copy link
Author

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

@DatanoiseTV
Copy link
Author

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.

@2bndy5
Copy link
Member

2bndy5 commented Jul 21, 2022

/*! \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).

@DatanoiseTV
Copy link
Author

/*! \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.

@2bndy5
Copy link
Member

2bndy5 commented Jul 21, 2022

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
Copy link
Member

2bndy5 commented Jul 21, 2022

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.

@DatanoiseTV
Copy link
Author

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.

@2bndy5
Copy link
Member

2bndy5 commented Jul 21, 2022

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

@DatanoiseTV
Copy link
Author

image

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

@2bndy5
Copy link
Member

2bndy5 commented Jul 21, 2022

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

@DatanoiseTV
Copy link
Author

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?

@2bndy5
Copy link
Member

2bndy5 commented Jul 21, 2022

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
Copy link
Member

2bndy5 commented Jul 21, 2022

Alternatively, RF24::read() can also be used to clear the interrupt, if you've set maskIRQ() to only reflect the "data received" event.

RF24/RF24.cpp

Lines 1542 to 1550 in 92f4a11

void RF24::read(void* buf, uint8_t len)
{
// Fetch the payload
read_payload(buf, len);
//Clear the only applicable interrupt flags
write_register(NRF_STATUS, _BV(RX_DR));
}

@DatanoiseTV
Copy link
Author

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.

@2bndy5
Copy link
Member

2bndy5 commented Jul 21, 2022

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()

@DatanoiseTV
Copy link
Author

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

@DatanoiseTV
Copy link
Author

DatanoiseTV commented Jul 22, 2022

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.

@2bndy5
Copy link
Member

2bndy5 commented Jul 22, 2022

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

@DatanoiseTV
Copy link
Author

DatanoiseTV commented Jul 22, 2022

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.

@2bndy5
Copy link
Member

2bndy5 commented Jul 22, 2022

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.

@DatanoiseTV
Copy link
Author

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

@2bndy5
Copy link
Member

2bndy5 commented Jul 22, 2022

  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
Copy link
Member

2bndy5 commented Jul 22, 2022

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

@DatanoiseTV
Copy link
Author

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.

@2bndy5
Copy link
Member

2bndy5 commented Jul 22, 2022

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
Copy link
Member

2bndy5 commented Jul 22, 2022

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.

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

No branches or pull requests

2 participants