interruptConfigure

See Also

defaultPins.h

examples_pico/interruptConfigure.cpp
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
/*
 * 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 Terminal to change each node's behavior.
 */
#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
#include "defaultPins.h"  // board presumptive default pin numbers for IRQ_PIN, CE_PIN, and CSN_PIN

// We will be using the nRF24L01's IRQ pin for this example
volatile bool wait_for_event = false; // used to wait for an IRQ event to trigger
volatile bool got_interrupt = false;  // used to signal when an IRQ event has been triggered

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

// 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(uint gpio, uint32_t events); // prototype to handle IRQ events
void printRxFifo();                                // prototype to print RX FIFO with 1 buffer
void assessInterruptEvent();                       // prototype to assess IRQ flags triggered

bool setup()
{
    // 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

    // wait here until the CDC ACM (serial port emulation) is connected
    while (!tud_cdc_connected()) {
        sleep_ms(10);
    }

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

    // print example's introductory prompt
    printf("RF24/examples_pico/interruptConfigure\n");

    // To set the radioNumber via the Serial terminal on startup
    printf("Which radio is this? Enter '0' or '1'. Defaults to '0'\n");
    char input = getchar();
    radioNumber = input == 49;
    printf("radioNumber = %d\n", (int)radioNumber);

    // 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.

    // 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 not trigger in RX mode
        radio.maskIRQ(1, 1, 1); // 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
    // radio.printDetails();       // (smaller) function that prints raw register values
    // radio.printPrettyDetails(); // (larger) function that prints human readable data

    // role variable is hardcoded to RX behavior, inform the user of this
    printf("*** PRESS 'T' to begin transmitting to the other node\n");

    return true;
}

void loop()
{
    if (role && !wait_for_event) {
        // 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

            printf("\nConfiguring IRQ pin to ignore the 'data sent' event\n");
            radio.maskIRQ(true, false, false); // args = "data_sent", "data_fail", "data_ready"
            printf("   Pinging RX node for 'data ready' event...\n");
        }
        else if (pl_iterator == 1) {
            // Test the "data sent" event with the IRQ pin

            printf("\nConfiguring IRQ pin to ignore the 'data ready' event\n");
            radio.maskIRQ(false, false, true); // args = "data_sent", "data_fail", "data_ready"
            printf("   Pinging RX node for 'data sent' event...\n");
        }
        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

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

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

        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
            printf("\n*** PRESS 'T' to restart the transmissions");
            printf("\n*** 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 && radio.rxFifoFull()) {
        // This device is a RX node
        //
        // The RX role waits until RX FIFO is full then stops listening while
        // the FIFOs get reset and starts listening again.
        radio.stopListening(); // also calls flush_tx() when ACKs are enabled
        printRxFifo();         // also clears 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);

        sleep_ms(200);          // let radio TX role complete
        radio.startListening(); // We're ready to start over. Begin listening.

    } // role

    if (got_interrupt) {
        assessInterruptEvent();
    }

    char input = getchar_timeout_us(0); // get char from buffer for user input
    if (input != PICO_ERROR_TIMEOUT) {
        // change the role via the serial terminal

        if (input == 'T' || input == 't') {
            // Become the TX node
            if (!role)
                printf("*** CHANGING TO TRANSMIT ROLE -- PRESS 'R' TO SWITCH BACK\n");
            else if (role && wait_for_event) // don't interrupt an ongoing test
                return;                      // exit now; start next loop()
            else
                printf("*** RESTARTING IRQ PIN TEST ***\n");

            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 ((input == 'R' || input == 'r') /*  && role */) {
            // Become the RX node
            printf("*** CHANGING TO RECEIVE ROLE -- PRESS 'T' TO SWITCH BACK\n");

            role = false;
            radio.maskIRQ(1, 1, 1); // the IRQ pin should not trigger in this example's RX rode

            // 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();
        }
        else if (input == 'b' || input == 'B') {
            // reset to bootloader
            radio.powerDown();
            reset_usb_boot(0, 0);
        }
    } // user input
} // loop

/**
 * when the IRQ pin goes active LOW.
 * Here we just tell the main loop() to call `assessInterruptEve4nt()`.
 */
void interruptHandler(uint gpio, uint32_t events)
{
    if (gpio != IRQ_PIN && !(events | GPIO_IRQ_EDGE_FALL)) {
        // the gpio pin and event does not match the configuration we specified
        return;
    }
    got_interrupt = true; // forward event handling back to main loop()
}

/**
 * Called when an event has been triggered.
 * Here, we want to verify the expected IRQ flag has been asserted.
 */
void assessInterruptEvent()
{
    // print IRQ status and all masking flags' states
    printf("\tIRQ pin is actively LOW\n");   // show that this function was called
    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)

    // print "data sent", "data fail", "data ready" mask states
    printf("\tdata_sent: %s, data_fail: %s, data_ready: %s\n",
           tx_ds ? "true" : "false",
           tx_df ? "true" : "false",
           rx_dr ? "true" : "false");

    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) {
        printf("   'Data Ready' event test %s\n", rx_dr ? "passed" : "failed");
    }
    else if (pl_iterator == 2) {
        printf("   'Data Sent' event test %s\n", tx_ds ? "passed" : "failed");
    }
    else if (pl_iterator == 4) {
        printf("   'Data Fail' event test %s\n", tx_df ? "passed" : "failed");
    }
    got_interrupt = false;  // reset this flag to prevent calling this function from loop()
    wait_for_event = false; // ready to continue with loop() operations
}

/**
 * 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
        }
        printf("Complete RX FIFO: %s\n", rx_fifo); // print the entire RX FIFO with 1 buffer
    }
}

int main()
{
    stdio_init_all(); // init necessary IO for the RP2040

    while (!setup()) { // if radio.begin() failed
        // hold program in infinite attempts to initialize radio
    }
    while (true) {
        loop();
    }
    return 0; // we will never reach this
}