| 1 | /* |
| 2 | * See documentation at https://nRF24.github.io/RF24 |
| 3 | * See License information at root directory of this library |
| 4 | * Author: Brendan Doherty (2bndy5) |
| 5 | */ |
| 6 | |
| 7 | /** |
| 8 | * This example uses Acknowledgement (ACK) payloads attached to ACK packets to |
| 9 | * demonstrate how the nRF24L01's IRQ (Interrupt Request) pin can be |
| 10 | * configured to detect when data is received, or when data has transmitted |
| 11 | * successfully, or when data has failed to transmit. |
| 12 | * |
| 13 | * This example was written to be used on 2 devices acting as "nodes". |
| 14 | * Use the Serial Monitor to change each node's behavior. |
| 15 | */ |
| 16 | #include <SPI.h> |
| 17 | #include "printf.h" |
| 18 | #include "RF24.h" |
| 19 | |
| 20 | // We will be using the nRF24L01's IRQ pin for this example |
| 21 | #define IRQ_PIN 2 // this needs to be a digital input capable pin |
| 22 | volatile bool got_interrupt = false; // used to signal processing of interrupt |
| 23 | volatile bool wait_for_event = false; // used to wait for an IRQ event to trigger |
| 24 | |
| 25 | #define CE_PIN 7 |
| 26 | #define CSN_PIN 8 |
| 27 | // instantiate an object for the nRF24L01 transceiver |
| 28 | RF24 radio(CE_PIN, CSN_PIN); |
| 29 | |
| 30 | // Let these addresses be used for the pair |
| 31 | uint8_t address[][6] = { "1Node", "2Node" }; |
| 32 | // It is very helpful to think of an address as a path instead of as |
| 33 | // an identifying device destination |
| 34 | |
| 35 | // to use different addresses on a pair of radios, we need a variable to |
| 36 | // uniquely identify which address this radio will use to transmit |
| 37 | bool radioNumber = 1; // 0 uses address[0] to transmit, 1 uses address[1] to transmit |
| 38 | |
| 39 | // Used to control whether this node is sending or receiving |
| 40 | bool role = false; // true = TX node, false = RX node |
| 41 | |
| 42 | // For this example, we'll be using a payload containing |
| 43 | // a string that changes on every transmission. (successful or not) |
| 44 | // Make a couple arrays of payloads & an iterator to traverse them |
| 45 | const uint8_t tx_pl_size = 5; |
| 46 | const uint8_t ack_pl_size = 4; |
| 47 | uint8_t pl_iterator = 0; |
| 48 | // The " + 1" compensates for the c-string's NULL terminating 0 |
| 49 | char tx_payloads[][tx_pl_size + 1] = { "Ping ", "Pong ", "Radio", "1FAIL" }; |
| 50 | char ack_payloads[][ack_pl_size + 1] = { "Yak ", "Back", " ACK" }; |
| 51 | |
| 52 | void interruptHandler(); // prototype to handle IRQ events |
| 53 | void printRxFifo(); // prototype to print RX FIFO with 1 buffer |
| 54 | |
| 55 | |
| 56 | void setup() { |
| 57 | Serial.begin(115200); |
| 58 | while (!Serial) { |
| 59 | // some boards need to wait to ensure access to serial over USB |
| 60 | } |
| 61 | |
| 62 | // initialize the transceiver on the SPI bus |
| 63 | if (!radio.begin()) { |
| 64 | Serial.println(F("radio hardware is not responding!!")); |
| 65 | while (1) {} // hold in infinite loop |
| 66 | } |
| 67 | |
| 68 | // print example's introductory prompt |
| 69 | Serial.println(F("RF24/examples/InterruptConfigure")); |
| 70 | |
| 71 | // To set the radioNumber via the Serial monitor on startup |
| 72 | Serial.println(F("Which radio is this? Enter '0' or '1'. Defaults to '0'")); |
| 73 | while (!Serial.available()) { |
| 74 | // wait for user input |
| 75 | } |
| 76 | char input = Serial.parseInt(); |
| 77 | radioNumber = input == 1; |
| 78 | Serial.print(F("radioNumber = ")); |
| 79 | Serial.println((int)radioNumber); |
| 80 | |
| 81 | // role variable is hardcoded to RX behavior, inform the user of this |
| 82 | Serial.println(F("*** PRESS 'T' to begin transmitting to the other node")); |
| 83 | |
| 84 | // setup the IRQ_PIN |
| 85 | pinMode(IRQ_PIN, INPUT); |
| 86 | attachInterrupt(digitalPinToInterrupt(IRQ_PIN), interruptHandler, FALLING); |
| 87 | // IMPORTANT: do not call radio.available() before calling |
| 88 | // radio.whatHappened() when the interruptHandler() is triggered by the |
| 89 | // IRQ pin FALLING event. According to the datasheet, the pipe information |
| 90 | // is unreliable during the IRQ pin FALLING transition. |
| 91 | |
| 92 | // Set the PA Level low to try preventing power supply related problems |
| 93 | // because these examples are likely run with nodes in close proximity to |
| 94 | // each other. |
| 95 | radio.setPALevel(RF24_PA_LOW); // RF24_PA_MAX is default. |
| 96 | |
| 97 | // For this example we use acknowledgment (ACK) payloads to trigger the |
| 98 | // IRQ pin when data is received on the TX node. |
| 99 | // to use ACK payloads, we need to enable dynamic payload lengths |
| 100 | radio.enableDynamicPayloads(); // ACK payloads are dynamically sized |
| 101 | |
| 102 | // Acknowledgement packets have no payloads by default. We need to enable |
| 103 | // this feature for all nodes (TX & RX) to use ACK payloads. |
| 104 | radio.enableAckPayload(); |
| 105 | |
| 106 | // set the TX address of the RX node for use on the TX pipe (pipe 0) |
| 107 | radio.stopListening(address[radioNumber]); // put radio in TX mode |
| 108 | |
| 109 | // set the RX address of the TX node into a RX pipe |
| 110 | radio.openReadingPipe(1, address[!radioNumber]); // using pipe 1 |
| 111 | |
| 112 | // additional setup specific to the node's RX role |
| 113 | if (!role) { |
| 114 | // setup for RX mode |
| 115 | |
| 116 | // let IRQ pin only trigger on "data_ready" event in RX mode |
| 117 | radio.setStatusFlags(RF24_RX_DR); |
| 118 | |
| 119 | // Fill the TX FIFO with 3 ACK payloads for the first 3 received |
| 120 | // transmissions on pipe 1 |
| 121 | radio.writeAckPayload(1, &ack_payloads[0], ack_pl_size); |
| 122 | radio.writeAckPayload(1, &ack_payloads[1], ack_pl_size); |
| 123 | radio.writeAckPayload(1, &ack_payloads[2], ack_pl_size); |
| 124 | |
| 125 | radio.startListening(); // put radio in RX mode |
| 126 | } |
| 127 | |
| 128 | // For debugging info |
| 129 | // printf_begin(); // needed only once for printing details |
| 130 | // radio.printDetails(); // (smaller) function that prints raw register values |
| 131 | // radio.printPrettyDetails(); // (larger) function that prints human readable data |
| 132 | } |
| 133 | |
| 134 | void loop() { |
| 135 | if (got_interrupt) { |
| 136 | assessInterruptEvent(); |
| 137 | } |
| 138 | |
| 139 | if (role && !wait_for_event) { |
| 140 | |
| 141 | // This device is a TX node. This if block is only triggered when |
| 142 | // NOT waiting for an IRQ event to happen |
| 143 | |
| 144 | if (pl_iterator == 0) { |
| 145 | // Test the "data ready" event with the IRQ pin |
| 146 | |
| 147 | Serial.println(F("\nConfiguring IRQ pin to ignore the 'data sent' event")); |
| 148 | radio.setStatusFlags(RF24_RX_DR | RF24_TX_DF); |
| 149 | Serial.println(F(" Pinging RX node for 'data ready' event...")); |
| 150 | |
| 151 | } else if (pl_iterator == 1) { |
| 152 | // Test the "data sent" event with the IRQ pin |
| 153 | |
| 154 | Serial.println(F("\nConfiguring IRQ pin to ignore the 'data ready' event")); |
| 155 | radio.setStatusFlags(RF24_TX_DS | RF24_TX_DF); |
| 156 | Serial.println(F(" Pinging RX node for 'data sent' event...")); |
| 157 | |
| 158 | } else if (pl_iterator == 2) { |
| 159 | // Use this iteration to fill the RX node's FIFO which sets us up for the next test. |
| 160 | |
| 161 | // write() uses virtual interrupt flags that work despite the masking of the IRQ pin |
| 162 | // disable IRQ pin for this step |
| 163 | radio.setStatusFlags(); |
| 164 | |
| 165 | Serial.println(F("\nSending 1 payload to fill RX node's FIFO. IRQ pin is neglected.")); |
| 166 | // write() will call flush_tx() on 'data fail' events |
| 167 | if (radio.write(&tx_payloads[pl_iterator], tx_pl_size)) { |
| 168 | if (radio.rxFifoFull()) { |
| 169 | Serial.println(F("RX node's FIFO is full; it is not listening any more")); |
| 170 | } else { |
| 171 | Serial.println("Transmission successful, but the RX node might still be listening."); |
| 172 | } |
| 173 | } else { |
| 174 | Serial.println(F("Transmission failed or timed out. Continuing anyway.")); |
| 175 | radio.flush_tx(); // discard payload(s) that failed to transmit |
| 176 | } |
| 177 | |
| 178 | } else if (pl_iterator == 3) { |
| 179 | // test the "data fail" event with the IRQ pin |
| 180 | |
| 181 | Serial.println(F("\nConfiguring IRQ pin to reflect all events")); |
| 182 | radio.setStatusFlags(RF24_IRQ_ALL); |
| 183 | Serial.println(F(" Pinging inactive RX node for 'data fail' event...")); |
| 184 | } |
| 185 | |
| 186 | if (pl_iterator < 4 && pl_iterator != 2) { |
| 187 | |
| 188 | // IRQ pin is LOW when activated. Otherwise it is always HIGH |
| 189 | // Wait until IRQ pin is activated. |
| 190 | wait_for_event = true; |
| 191 | |
| 192 | // use the non-blocking call to write a payload and begin transmission |
| 193 | // the "false" argument means we are expecting an ACK packet response |
| 194 | radio.startFastWrite(tx_payloads[pl_iterator++], tx_pl_size, false); |
| 195 | |
| 196 | // In this example, the "data fail" event is always configured to |
| 197 | // trigger the IRQ pin active. Because the auto-ACK feature is on by |
| 198 | // default, we don't need a timeout check to prevent an infinite loop. |
| 199 | |
| 200 | } else if (pl_iterator == 4) { |
| 201 | // all IRQ tests are done; flush_tx() and print the ACK payloads for fun |
| 202 | |
| 203 | // CE pin is still HIGH which consumes more power. Example is now idling so... |
| 204 | radio.stopListening(); // ensure CE pin is LOW |
| 205 | // stopListening() also calls flush_tx() when ACK payloads are enabled |
| 206 | |
| 207 | printRxFifo(); |
| 208 | pl_iterator++; |
| 209 | |
| 210 | |
| 211 | // inform user what to do next |
| 212 | Serial.println(F("\n*** PRESS 'T' to restart the transmissions")); |
| 213 | Serial.println(F("*** PRESS 'R' to change to Receive role\n")); |
| 214 | |
| 215 | |
| 216 | } else if (pl_iterator == 2) { |
| 217 | pl_iterator++; // proceed from step 3 to last step (stop at step 4 for readability) |
| 218 | } |
| 219 | |
| 220 | } else if (!role && radio.rxFifoFull()) { |
| 221 | // This device is a RX node: |
| 222 | // wait until RX FIFO is full then stop listening |
| 223 | |
| 224 | delay(100); // let ACK payload finish transmitting |
| 225 | radio.stopListening(); // also discards unused ACK payloads |
| 226 | printRxFifo(); // flush the RX FIFO |
| 227 | |
| 228 | // Fill the TX FIFO with 3 ACK payloads for the first 3 received |
| 229 | // transmissions on pipe 1. |
| 230 | radio.writeAckPayload(1, &ack_payloads[0], ack_pl_size); |
| 231 | radio.writeAckPayload(1, &ack_payloads[1], ack_pl_size); |
| 232 | radio.writeAckPayload(1, &ack_payloads[2], ack_pl_size); |
| 233 | |
| 234 | delay(100); // let TX node finish its role |
| 235 | radio.startListening(); // We're ready to start over. Begin listening. |
| 236 | |
| 237 | } // role |
| 238 | |
| 239 | if (Serial.available()) { |
| 240 | // change the role via the serial monitor |
| 241 | |
| 242 | char c = toupper(Serial.read()); |
| 243 | if (c == 'T') { |
| 244 | // Become the TX node |
| 245 | if (!role) |
| 246 | Serial.println(F("*** CHANGING TO TRANSMIT ROLE -- PRESS 'R' TO SWITCH BACK")); |
| 247 | else |
| 248 | Serial.println(F("*** RESTARTING IRQ PIN TEST ***")); |
| 249 | |
| 250 | role = true; |
| 251 | wait_for_event = false; |
| 252 | pl_iterator = 0; // reset the iterator |
| 253 | radio.flush_tx(); // discard any payloads in the TX FIFO |
| 254 | |
| 255 | // startListening() clears the IRQ masks also. This is required for |
| 256 | // continued TX operations when a transmission fails. |
| 257 | radio.stopListening(); // this also discards any unused ACK payloads |
| 258 | |
| 259 | } else if (c == 'R' && role) { |
| 260 | // Become the RX node |
| 261 | Serial.println(F("*** CHANGING TO RECEIVE ROLE -- PRESS 'T' TO SWITCH BACK")); |
| 262 | |
| 263 | role = false; |
| 264 | |
| 265 | // let IRQ pin only trigger on "data_ready" event in RX mode |
| 266 | radio.setStatusFlags(RF24_RX_DR); |
| 267 | |
| 268 | // Fill the TX FIFO with 3 ACK payloads for the first 3 received |
| 269 | // transmissions on pipe 1 |
| 270 | radio.flush_tx(); // make sure there is room for 3 new ACK payloads |
| 271 | radio.flush_rx(); // make sure there is room for 3 incoming payloads |
| 272 | radio.writeAckPayload(1, &ack_payloads[0], ack_pl_size); |
| 273 | radio.writeAckPayload(1, &ack_payloads[1], ack_pl_size); |
| 274 | radio.writeAckPayload(1, &ack_payloads[2], ack_pl_size); |
| 275 | radio.startListening(); |
| 276 | } |
| 277 | } // Serial.available() |
| 278 | } // loop |
| 279 | |
| 280 | |
| 281 | /** |
| 282 | * when the IRQ pin goes active LOW. |
| 283 | * Here we just tell the main loop() to call `assessInterruptEve4nt()`. |
| 284 | */ |
| 285 | void interruptHandler() { |
| 286 | got_interrupt = true; // forward event handling back to main loop() |
| 287 | } |
| 288 | |
| 289 | /** |
| 290 | * Called when an event has been triggered. |
| 291 | * Here, we want to verify the expected IRQ flag has been asserted. |
| 292 | */ |
| 293 | void assessInterruptEvent() { |
| 294 | // print IRQ status and all masking flags' states |
| 295 | |
| 296 | Serial.println(F("\tIRQ pin is actively LOW")); // show that this function was called |
| 297 | delayMicroseconds(250); |
| 298 | uint8_t flags = radio.clearStatusFlags(); |
| 299 | // Resetting the tx_df flag is required for |
| 300 | // continued TX operations when a transmission fails. |
| 301 | // clearing the status flags resets the IRQ pin to its inactive state (HIGH) |
| 302 | |
| 303 | Serial.print(F("\tdata_sent: ")); |
| 304 | Serial.print((flags & RF24_TX_DS) > 0); // print "data sent" flag state |
| 305 | Serial.print(F(", data_fail: ")); |
| 306 | Serial.print((flags & RF24_TX_DF) > 0); // print "data fail" flag state |
| 307 | Serial.print(F(", data_ready: ")); |
| 308 | Serial.println((flags & RF24_RX_DR) > 0); // print "data ready" flag state |
| 309 | |
| 310 | if (flags & RF24_TX_DF) // if TX payload failed |
| 311 | radio.flush_tx(); // clear all payloads from the TX FIFO |
| 312 | |
| 313 | // print if test passed or failed. Unintentional fails mean the RX node was not listening. |
| 314 | // pl_iterator has already been incremented by now |
| 315 | if (pl_iterator <= 1) { |
| 316 | Serial.print(F(" 'Data Ready' event test ")); |
| 317 | Serial.println(flags & RF24_RX_DR ? F("passed") : F("failed")); |
| 318 | } else if (pl_iterator == 2) { |
| 319 | Serial.print(F(" 'Data Sent' event test ")); |
| 320 | Serial.println(flags & RF24_TX_DS ? F("passed") : F("failed")); |
| 321 | } else if (pl_iterator == 4) { |
| 322 | Serial.print(F(" 'Data Fail' event test ")); |
| 323 | Serial.println(flags & RF24_TX_DF ? F("passed") : F("failed")); |
| 324 | } |
| 325 | got_interrupt = false; // reset this flag to prevent calling this function from loop() |
| 326 | wait_for_event = false; // ready to continue with loop() operations |
| 327 | } |
| 328 | |
| 329 | /** |
| 330 | * Print the entire RX FIFO with one buffer. This will also flush the RX FIFO. |
| 331 | * Remember that the payload sizes are declared as tx_pl_size and ack_pl_size. |
| 332 | */ |
| 333 | void printRxFifo() { |
| 334 | if (radio.available()) { // if there is data in the RX FIFO |
| 335 | // to flush the data from the RX FIFO, we'll fetch it all using 1 buffer |
| 336 | |
| 337 | uint8_t pl_size = !role ? tx_pl_size : ack_pl_size; |
| 338 | char rx_fifo[pl_size * 3 + 1]; // RX FIFO is full & we know ACK payloads' size |
| 339 | if (radio.rxFifoFull()) { |
| 340 | rx_fifo[pl_size * 3] = 0; // add a NULL terminating char to use as a c-string |
| 341 | radio.read(&rx_fifo, pl_size * 3); // this clears the RX FIFO (for this example) |
| 342 | } else { |
| 343 | uint8_t i = 0; |
| 344 | while (radio.available()) { |
| 345 | radio.read(&rx_fifo + (i * pl_size), pl_size); |
| 346 | i++; |
| 347 | } |
| 348 | rx_fifo[i * pl_size] = 0; // add a NULL terminating char to use as a c-string |
| 349 | } |
| 350 | Serial.print(F("Complete RX FIFO: ")); |
| 351 | Serial.println(rx_fifo); // print the entire RX FIFO with 1 buffer |
| 352 | } |
| 353 | } |