-
Notifications
You must be signed in to change notification settings - Fork 1k
/
Copy pathinterruptConfigure.cpp
359 lines (301 loc) · 14.9 KB
/
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
/*
* 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
// 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
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
// 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
// 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
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 on 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, call this fuction print out why
*/
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;
}
// 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");
}
wait_for_event = false; // ready to continue with loop() operations
} // 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
}
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
}