Repository: nRF24/RF24
Pull request #1030: refactor: migrate to `stopListening(txAddress)` from `openWritingPipe()`
Author: login: 2bndy5
Labels: name: enhancement
Created: 2025-04-26T22:53:33Z | Merged: 2025-05-03T20:08:00Z

`stopListening()` now writes the TX address to pipe 0 (for reading and writing). This deprecates the need for `openWritingPipe()` by offering an overloaded `stopListening(txAddress)`.

All examples (and the python wrapper) have been updated to use the new public `stopListening(txAddress)` accordingly.

> [!important]
> RF24Network should be updated to use the new `stopListening(txAddress)` also. This approach should retain current performance in networking layers while still satisfying #1028.

The main take away is no duplicate SPI transactions when switching to TX mode and (re)setting the TX address. This aims to fix the performance regression in #1029.


Diff:
diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml
index 1f0457aee..fcc62a91c 100644
--- a/.github/workflows/doxygen.yml
+++ b/.github/workflows/doxygen.yml
@@ -50,5 +50,5 @@ jobs:
     uses: nRF24/.github/.github/workflows/build_docs.yaml@main
     with:
       deploy-gh-pages: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master') }}
-      doxygen-version: '1.12.0'
+      doxygen-version: '1.13.2'
     secrets: inherit
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index e6f4edc3b..db2305248 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -15,7 +15,7 @@ build:
   commands:
     # Install doxygen from source distributions (conda forge does not keep up-to-date doxygen releases)
     - >
-      DOXYGEN_VERSION="1.12.0" &&
+      DOXYGEN_VERSION="1.13.2" &&
       mkdir .doxygen && cd .doxygen &&
       echo $(pwd) &&
       echo "https://sourceforge.net/projects/doxygen/files/rel-$DOXYGEN_VERSION/doxygen-$DOXYGEN_VERSION.linux.bin.tar.gz" &&
diff --git a/RF24.cpp b/RF24.cpp
index bbd7df561..d8d14eb80 100644
--- a/RF24.cpp
+++ b/RF24.cpp
@@ -603,7 +603,6 @@ void RF24::_init_obj()
     _spi = &SPI;
 #endif // defined (RF24_SPI_PTR)
 
-    pipe0_reading_address[0] = 0;
     if (spi_speed <= 35000) { //Handle old BCM2835 speed constants, default to RF24_SPI_SPEED
         spi_speed = RF24_SPI_SPEED;
     }
@@ -1192,6 +1191,24 @@ void RF24::stopListening(void)
 
 /****************************************************************************/
 
+void RF24::stopListening(const uint64_t txAddress)
+{
+    memcpy(pipe0_writing_address, &txAddress, addr_width);
+    stopListening();
+    write_register(TX_ADDR, pipe0_writing_address, addr_width);
+}
+
+/****************************************************************************/
+
+void RF24::stopListening(const uint8_t* txAddress)
+{
+    memcpy(pipe0_writing_address, txAddress, addr_width);
+    stopListening();
+    write_register(TX_ADDR, pipe0_writing_address, addr_width);
+}
+
+/****************************************************************************/
+
 void RF24::powerDown(void)
 {
     ce(LOW); // Guarantee CE is low on powerDown
@@ -1626,12 +1643,14 @@ void RF24::openReadingPipe(uint8_t child, uint64_t address)
 
     if (child <= 5) {
         // For pipes 2-5, only write the LSB
-        if (child < 2) {
-            write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), addr_width);
-        }
-        else {
+        if (child > 1) {
             write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), 1);
         }
+        // avoid overwriting the TX address on pipe 0 while still in TX mode.
+        // NOTE, the cached RX address on pipe 0 is written when startListening() is called.
+        else if (static_cast<bool>(config_reg & _BV(PRIM_RX)) || child != 0) {
+            write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), addr_width);
+        }
 
         // Note it would be more efficient to set all of the bits for all open
         // pipes at once.  However, I thought it would make the calling code
@@ -1668,12 +1687,14 @@ void RF24::openReadingPipe(uint8_t child, const uint8_t* address)
     }
     if (child <= 5) {
         // For pipes 2-5, only write the LSB
-        if (child < 2) {
-            write_register(pgm_read_byte(&child_pipe[child]), address, addr_width);
-        }
-        else {
+        if (child > 1) {
             write_register(pgm_read_byte(&child_pipe[child]), address, 1);
         }
+        // avoid overwriting the TX address on pipe 0 while still in TX mode.
+        // NOTE, the cached RX address on pipe 0 is written when startListening() is called.
+        else if (static_cast<bool>(config_reg & _BV(PRIM_RX)) || child != 0) {
+            write_register(pgm_read_byte(&child_pipe[child]), address, addr_width);
+        }
 
         // Note it would be more efficient to set all of the bits for all open
         // pipes at once.  However, I thought it would make the calling code
@@ -2037,6 +2058,11 @@ void RF24::stopConstCarrier()
     powerDown(); // per datasheet recommendation (just to be safe)
     write_register(RF_SETUP, static_cast<uint8_t>(read_register(RF_SETUP) & ~_BV(CONT_WAVE) & ~_BV(PLL_LOCK)));
     ce(LOW);
+    flush_tx();
+    if (isPVariant()) {
+        // restore the cached TX address
+        write_register(TX_ADDR, pipe0_writing_address, addr_width);
+    }
 }
 
 /****************************************************************************/
diff --git a/RF24.h b/RF24.h
index 1b455470e..1fd4d543e 100644
--- a/RF24.h
+++ b/RF24.h
@@ -379,13 +379,16 @@ class RF24
      * @warning When the ACK payloads feature is enabled, the TX FIFO buffers are
      * flushed when calling this function. This is meant to discard any ACK
      * payloads that were not appended to acknowledgment packets.
-     *
-     * @note For auto-ack purposes, the TX address passed to openWritingPipe() will be restored to
-     * RX pipe 0. This still means that `stopListening()` shall be called before
-     * calling openWritingPipe() because the TX address is cached in openWritingPipe().
      */
     void stopListening(void);
 
+    /**
+     * @brief Similar to startListening(void) but changes the TX address.
+     * @param txAddress The new TX address.
+     * This value will be cached for auto-ack purposes.
+     */
+    void stopListening(const uint8_t* txAddress);
+
     /**
      * Check whether there are bytes available to be read
      * @code
@@ -523,6 +526,8 @@ class RF24
      * New: Open a pipe for writing via byte array. Old addressing format retained
      * for compatibility.
      *
+     * @deprecated Use `RF24::stopListening(uint8_t*)` instead.
+     *
      * Only one writing pipe can be opened at once, but this function changes
      * the address that is used to transmit (ACK payloads/packets do not apply
      * here). Be sure to call stopListening() prior to calling this function.
@@ -2022,6 +2027,17 @@ class RF24
      */
     void whatHappened(bool& tx_ok, bool& tx_fail, bool& rx_ready);
 
+    /**
+     * Similar to startListening(void) but changes the TX address.
+     *
+     * @deprecated Use stopListening(const uint8_t*) instead.
+     * See our [migration guide](migration.md) to understand what you should update in your code.
+     *
+     * @param txAddress The new TX address.
+     * This value will be cached for auto-ack purposes.
+     */
+    void stopListening(const uint64_t txAddress);
+
 private:
     /**@}*/
     /**
diff --git a/docs/migration.md b/docs/migration.md
index f3139a05c..b4d378266 100644
--- a/docs/migration.md
+++ b/docs/migration.md
@@ -30,22 +30,34 @@ if radio.available() { /* .. */ }
 
 </td></tr></table>
 
-## openReadingPipe(uint8_t, uint64_t) and openWritingPipe(uint64_t)
+## 64-bit integer addresses
 
-> **Deprecated since v1.3.11**
+Any function that accept an address in the form of `uint64_t` is discouraged. This includes
 
-These functions' address parameter used a 64-bit unsigned integer (`uint64_t`).
+- `RF24::openReadingPipe(uint8_t, uint64_t)`
+   > **Deprecated since v1.3.11**
+- `RF24::openWritingPipe(uint64_t)`
+   > **Deprecated since v1.3.11**
+- `RF24::stopListening(const uint64_t)`
+   > **Deprecated since v1.5**
+
+These functions' address parameter use a 64-bit unsigned integer (`uint64_t`).
 The nRF24L01 can only use up to 40 bit addresses.
-Thus, there was an unused 24 bits being allocated for addresses using this function.
+Thus, there is an unused 24 bits being allocated for addresses using these functions.
 
 There are overloaded functions that use a buffer instead:
 
 - `RF24::openReadingPipe(uint8_t, const uint8_t*)`
 - `RF24::openWritingPipe(const uint8_t*)`
+- `RF24::stopListening(const uint8_t*)`
 
 These eliminate the unnecessary 24 bits by only using the length of the buffer (`uint8_t*`)
 specified by `RF24::setAddressWidth()`.
 
+@see The `RF24::openWritingPipe(const uint8_t*)` is now deprecated in favor of the
+overloaded `RF24::stopListening(const uint8_t*)` function.
+See the section below for more detail.
+
 > [!CAUTION]
 > The endianness (byte order) of a buffer is reversed compared to a 64-bit integer.
 > ```c
@@ -177,6 +189,7 @@ The aptly named `RF24::clearStatusFlags()` is designed to be a replacement for `
 Like `RF24::clearStatusFlags()`, `RF24::setStatusFlags()` takes 1 parameter whose value is defined by
 the `rf24_irq_flags_e` enumerated constants. These constant values specify individual flags;
 they can also be OR'd together to specify multiple flags.
+
 Additionally, `RF24::clearStatusFlags()` returns the STATUS byte containing the flags that
 caused the IRQ pin to go active LOW.
 This allows the user code to allocate less memory when diagnosing the IRQ pin's meaning.
@@ -211,3 +224,64 @@ radio.clearStatusFlags(RF24_RX_DR);
 ```
 
 </td></tr></table>
+
+## openWritingPipe(const uint8_t*)
+
+> Deprecated since v1.5
+
+Originally, `RF24::openWritingPipe(const uint8_t*)` was just a compliment to
+`RF24::openReadingPipe()`.
+It changes the address on pipe 0 because that is the only pipe that can be
+used for transmitting.
+
+Unfortunately, there was a bug that prevented the given TX address from being
+persistent on pipe 0 if the user code also set an RX address to pipe 0.
+This bug would surface when switching between RX mode and TX mode (via
+`RF24::startListening()` and `RF24::stopListening()` respectively) or after
+`RF24::stopConstCarrier()` (if `RF24::isPVariant()` returns `true`).
+
+The solution is to cache the TX address on the `RF24` instance.
+Consequently, this solution did not fit well with the traditional order of
+functions used to set up the radio's TX or RX mode.
+
+By overloading `RF24::stopListening(const uint8_t*)`, we are able to ensure proper radio
+setup without requiring certain functions are called in a certain order.
+
+- Use `RF24::stopListening(const uint8_t*)` to set the TX address and enter inactive TX mode.
+  `RF24::openReadingPipe()` can now (as of v1.5) be used in TX mode without consequence.
+- Use `RF24::stopListening()` to enter inactive TX mode without changing the TX address.
+
+> [!warning]
+> Avoid using pipe 0 for RX operations to improve performance and reliability.
+>
+> For implementation detail, see the source for `RF24::openReadingPipe()` and
+> `RF24::stopListening()`. Ultimately, the datasheet's Appendix A has a detailed
+> example outlining the order of a proper radio setup.
+
+<table><tr>
+<th>Old</th>
+<th>New (supported)</th>
+</tr><tr><td>
+
+```cpp
+// set TX address (pipe 0)
+radio.openWritingPipe(tx_address);
+
+// set RX address (pipe 1)
+radio.openReadingPipe(1, rx_address);
+
+// idle radio using inactive TX mode
+radio.stopListening();
+```
+
+</td><td>
+
+```cpp
+// set TX address (pipe 0)
+radio.stopListening(tx_address); // enters inactive TX mode
+
+// set RX address (pipe 1)
+radio.openReadingPipe(1, rx_address);
+```
+
+</td></tr></table>
diff --git a/examples/AcknowledgementPayloads/AcknowledgementPayloads.ino b/examples/AcknowledgementPayloads/AcknowledgementPayloads.ino
index f4c7f1785..af0d7db92 100644
--- a/examples/AcknowledgementPayloads/AcknowledgementPayloads.ino
+++ b/examples/AcknowledgementPayloads/AcknowledgementPayloads.ino
@@ -84,8 +84,8 @@ void setup() {
   // this feature for all nodes (TX & RX) to use ACK payloads.
   radio.enableAckPayload();
 
-  // set the TX address of the RX node into the TX pipe
-  radio.openWritingPipe(address[radioNumber]);  // always uses pipe 0
+  // set the TX address of the RX node for use on the TX pipe (pipe 0)
+  radio.stopListening(address[radioNumber]);  // put radio in TX mode
 
   // set the RX address of the TX node into a RX pipe
   radio.openReadingPipe(1, address[!radioNumber]);  // using pipe 1
@@ -93,9 +93,7 @@ void setup() {
   // additional setup specific to the node's role
   if (role) {
     // setup the TX payload
-
     memcpy(payload.message, "Hello ", 6);  // set the payload message
-    radio.stopListening();                 // put radio in TX mode
   } else {
     // setup the ACK payload & load the first response into the FIFO
 
diff --git a/examples/GettingStarted/GettingStarted.ino b/examples/GettingStarted/GettingStarted.ino
index 4132c1035..5da9321bc 100644
--- a/examples/GettingStarted/GettingStarted.ino
+++ b/examples/GettingStarted/GettingStarted.ino
@@ -74,16 +74,14 @@ void setup() {
   // number of bytes we need to transmit a float
   radio.setPayloadSize(sizeof(payload));  // float datatype occupies 4 bytes
 
-  // set the TX address of the RX node into the TX pipe
-  radio.openWritingPipe(address[radioNumber]);  // always uses pipe 0
+  // set the TX address of the RX node for use on the TX pipe (pipe 0)
+  radio.stopListening(address[radioNumber]);  // put radio in TX mode
 
   // 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) {
-    radio.stopListening();  // put radio in TX mode
-  } else {
+  // additional setup specific to the node's RX role
+  if (!role) {
     radio.startListening();  // put radio in RX mode
   }
 
diff --git a/examples/InterruptConfigure/InterruptConfigure.ino b/examples/InterruptConfigure/InterruptConfigure.ino
index 48f6a4c9c..09c2fcf40 100644
--- a/examples/InterruptConfigure/InterruptConfigure.ino
+++ b/examples/InterruptConfigure/InterruptConfigure.ino
@@ -102,20 +102,15 @@ void setup() {
   // 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 TX address of the RX node for use on the TX pipe (pipe 0)
+  radio.stopListening(address[radioNumber]);  // put radio in TX mode
 
   // 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 {
+  // additional setup specific to the node's RX role
+  if (!role) {
     // setup for RX mode
 
     // let IRQ pin only trigger on "data_ready" event in RX mode
diff --git a/examples/ManualAcknowledgements/ManualAcknowledgements.ino b/examples/ManualAcknowledgements/ManualAcknowledgements.ino
index 3e8b8fc91..8cb1c6da2 100644
--- a/examples/ManualAcknowledgements/ManualAcknowledgements.ino
+++ b/examples/ManualAcknowledgements/ManualAcknowledgements.ino
@@ -88,8 +88,8 @@ void setup() {
   // number of bytes we need to transmit a float
   radio.setPayloadSize(sizeof(payload));  // char[7] & uint8_t datatypes occupy 8 bytes
 
-  // set the TX address of the RX node into the TX pipe
-  radio.openWritingPipe(address[radioNumber]);  // always uses pipe 0
+  // set the TX address of the RX node for use on the TX pipe (pipe 0)
+  radio.stopListening(address[radioNumber]);  // put radio in TX mode
 
   // set the RX address of the TX node into a RX pipe
   radio.openReadingPipe(1, address[!radioNumber]);  // using pipe 1
@@ -98,7 +98,6 @@ void setup() {
     // setup the TX node
 
     memcpy(payload.message, "Hello ", 6);  // set the outgoing message
-    radio.stopListening();                 // put radio in TX mode
   } else {
     // setup the RX node
 
diff --git a/examples/MulticeiverDemo/MulticeiverDemo.ino b/examples/MulticeiverDemo/MulticeiverDemo.ino
index ee9bf9411..b5c68bea6 100644
--- a/examples/MulticeiverDemo/MulticeiverDemo.ino
+++ b/examples/MulticeiverDemo/MulticeiverDemo.ino
@@ -27,13 +27,13 @@ RF24 radio(CE_PIN, CSN_PIN);
 // an identifying device destination
 // Notice that the last byte is the only byte that changes in the last 5
 // addresses. This is a limitation of the nRF24L01 transceiver for pipes 2-5
-// because they use the same first 4 bytes from pipe 1.
-uint64_t address[6] = { 0x7878787878LL,
-                        0xB3B4B5B6F1LL,
-                        0xB3B4B5B6CDLL,
-                        0xB3B4B5B6A3LL,
-                        0xB3B4B5B60FLL,
-                        0xB3B4B5B605LL };
+// because they use the same first 4 MSBytes from pipe 1.
+uint8_t address[6][5] = { { 0x78, 0x78, 0x78, 0x78, 0x78 },
+                          { 0xF1, 0xB6, 0xB5, 0xB4, 0xB3 },
+                          { 0xCD, 0xB6, 0xB5, 0xB4, 0xB3 },
+                          { 0xA3, 0xB6, 0xB5, 0xB4, 0xB3 },
+                          { 0x0F, 0xB6, 0xB5, 0xB4, 0xB3 },
+                          { 0x05, 0xB6, 0xB5, 0xB4, 0xB3 } };
 
 // role variable is used to control whether this node is sending or receiving
 char role = 'R';  // integers 0-5 = TX node; character 'R' or integer 82 = RX node
@@ -183,9 +183,8 @@ void setRole() {
     payload.nodeID = role;
     payload.payloadID = 0;
 
-    // Set the address on pipe 0 to the RX node.
-    radio.stopListening();  // put radio in TX mode
-    radio.openWritingPipe(address[role]);
+    // set the TX address of the RX node for use on the TX pipe (pipe 0)
+    radio.stopListening(address[role]);  // put radio in TX mode
 
     // According to the datasheet, the auto-retry features's delay value should
     // be "skewed" to allow the RX node to receive 1 transmission at a time.
diff --git a/examples/StreamingData/StreamingData.ino b/examples/StreamingData/StreamingData.ino
index 0c3d6458f..0ce15b620 100644
--- a/examples/StreamingData/StreamingData.ino
+++ b/examples/StreamingData/StreamingData.ino
@@ -80,16 +80,14 @@ void setup() {
   // number of bytes we need to transmit
   radio.setPayloadSize(SIZE);  // default value is the maximum 32 bytes
 
-  // set the TX address of the RX node into the TX pipe
-  radio.openWritingPipe(address[radioNumber]);  // always uses pipe 0
+  // set the TX address of the RX node for use on the TX pipe (pipe 0)
+  radio.stopListening(address[radioNumber]);  // put radio in TX mode
 
   // 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) {
-    radio.stopListening();  // put radio in TX mode
-  } else {
+  // additional setup specific to the node's RX role
+  if (!role) {
     radio.startListening();  // put radio in RX mode
   }
 
diff --git a/examples_linux/acknowledgementPayloads.cpp b/examples_linux/acknowledgementPayloads.cpp
index 5412160f3..e2dd26464 100644
--- a/examples_linux/acknowledgementPayloads.cpp
+++ b/examples_linux/acknowledgementPayloads.cpp
@@ -96,8 +96,8 @@ int main(int argc, char** argv)
     // each other.
     radio.setPALevel(RF24_PA_LOW); // RF24_PA_MAX is default.
 
-    // set the TX address of the RX node into the TX pipe
-    radio.openWritingPipe(address[radioNumber]); // always uses pipe 0
+    // set the TX address of the RX node for use on the TX pipe (pipe 0)
+    radio.stopListening(address[radioNumber]);
 
     // set the RX address of the TX node into a RX pipe
     radio.openReadingPipe(1, address[!radioNumber]); // using pipe 1
diff --git a/examples_linux/acknowledgement_payloads.py b/examples_linux/acknowledgement_payloads.py
index d0fcb20f8..5bc494679 100644
--- a/examples_linux/acknowledgement_payloads.py
+++ b/examples_linux/acknowledgement_payloads.py
@@ -51,8 +51,8 @@
 # usually run with nRF24L01 transceivers in close proximity of each other
 radio.setPALevel(RF24_PA_LOW)  # RF24_PA_MAX is default
 
-# set the TX address of the RX node into the TX pipe
-radio.openWritingPipe(address[radio_number])  # always uses pipe 0
+# set the TX address of the RX node for use on the TX pipe (pipe 0)
+radio.stopListening(address[radio_number])
 
 # set the RX address of the TX node into a RX pipe
 radio.openReadingPipe(1, address[not radio_number])  # using pipe 1
diff --git a/examples_linux/getting_started.py b/examples_linux/getting_started.py
index 6b91d0a13..e289a0158 100644
--- a/examples_linux/getting_started.py
+++ b/examples_linux/getting_started.py
@@ -48,8 +48,8 @@
 # usually run with nRF24L01 transceivers in close proximity of each other
 radio.setPALevel(RF24_PA_LOW)  # RF24_PA_MAX is default
 
-# set the TX address of the RX node into the TX pipe
-radio.openWritingPipe(address[radio_number])  # always uses pipe 0
+# set the TX address of the RX node for use on the TX pipe (pipe 0)
+radio.stopListening(address[radio_number])
 
 # set the RX address of the TX node into a RX pipe
 radio.openReadingPipe(1, address[not radio_number])  # using pipe 1
diff --git a/examples_linux/gettingstarted.cpp b/examples_linux/gettingstarted.cpp
index 752611f6b..c7f00ecae 100644
--- a/examples_linux/gettingstarted.cpp
+++ b/examples_linux/gettingstarted.cpp
@@ -87,8 +87,8 @@ int main(int argc, char** argv)
     // each other.
     radio.setPALevel(RF24_PA_LOW); // RF24_PA_MAX is default.
 
-    // set the TX address of the RX node into the TX pipe
-    radio.openWritingPipe(address[radioNumber]); // always uses pipe 0
+    // set the TX address of the RX node for use on the TX pipe (pipe 0)
+    radio.stopListening(address[radioNumber]);
 
     // set the RX address of the TX node into a RX pipe
     radio.openReadingPipe(1, address[!radioNumber]); // using pipe 1
diff --git a/examples_linux/interruptConfigure.cpp b/examples_linux/interruptConfigure.cpp
index fc68131a1..9269d8808 100644
--- a/examples_linux/interruptConfigure.cpp
+++ b/examples_linux/interruptConfigure.cpp
@@ -109,8 +109,8 @@ int main(int argc, char** argv)
     // each other.
     radio.setPALevel(RF24_PA_LOW); // RF24_PA_MAX is default.
 
-    // set the TX address of the RX node into the TX pipe
-    radio.openWritingPipe(address[radioNumber]); // always uses pipe 0
+    // set the TX address of the RX node for use on the TX pipe (pipe 0)
+    radio.stopListening(address[radioNumber]);
 
     // set the RX address of the TX node into a RX pipe
     radio.openReadingPipe(1, address[!radioNumber]); // using pipe 1
diff --git a/examples_linux/interrupt_configure.py b/examples_linux/interrupt_configure.py
index 72f945c5a..18c0ee254 100644
--- a/examples_linux/interrupt_configure.py
+++ b/examples_linux/interrupt_configure.py
@@ -82,8 +82,8 @@
 # usually run with nRF24L01 transceivers in close proximity of each other
 radio.setPALevel(RF24_PA_LOW)  # RF24_PA_MAX is default
 
-# set the TX address of the RX node into the TX pipe
-radio.openWritingPipe(address[radio_number])  # always uses pipe 0
+# set the TX address of the RX node for use on the TX pipe (pipe 0)
+radio.stopListening(address[radio_number])
 
 # set the RX address of the TX node into a RX pipe
 radio.openReadingPipe(1, address[not radio_number])  # using pipe 1
diff --git a/examples_linux/manualAcknowledgements.cpp b/examples_linux/manualAcknowledgements.cpp
index 621093250..2a5fa02ef 100644
--- a/examples_linux/manualAcknowledgements.cpp
+++ b/examples_linux/manualAcknowledgements.cpp
@@ -103,8 +103,8 @@ int main(int argc, char** argv)
     // number of bytes we need to transmit a float
     radio.setPayloadSize(sizeof(payload)); // char[7] & uint8_t datatypes occupy 8 bytes
 
-    // set the TX address of the RX node into the TX pipe
-    radio.openWritingPipe(address[radioNumber]); // always uses pipe 0
+    // set the TX address of the RX node for use on the TX pipe (pipe 0)
+    radio.stopListening(address[radioNumber]);
 
     // set the RX address of the TX node into a RX pipe
     radio.openReadingPipe(1, address[!radioNumber]); // using pipe 1
diff --git a/examples_linux/manual_acknowledgements.py b/examples_linux/manual_acknowledgements.py
index c38824103..c03bcdcfd 100644
--- a/examples_linux/manual_acknowledgements.py
+++ b/examples_linux/manual_acknowledgements.py
@@ -54,8 +54,8 @@
 # usually run with nRF24L01 transceivers in close proximity of each other
 radio.setPALevel(RF24_PA_LOW)  # RF24_PA_MAX is default
 
-# set the TX address of the RX node into the TX pipe
-radio.openWritingPipe(address[radio_number])  # always uses pipe 0
+# set the TX address of the RX node for use on the TX pipe (pipe 0)
+radio.stopListening(address[radio_number])
 
 # set the RX address of the TX node into a RX pipe
 radio.openReadingPipe(1, address[not radio_number])  # using pipe 1
diff --git a/examples_linux/multiceiverDemo.cpp b/examples_linux/multiceiverDemo.cpp
index aa8744736..bc8fb2343 100644
--- a/examples_linux/multiceiverDemo.cpp
+++ b/examples_linux/multiceiverDemo.cpp
@@ -47,13 +47,13 @@ RF24 radio(CE_PIN, CSN_PIN);
 // an identifying device destination
 // Notice that the last byte is the only byte that changes in the last 5
 // addresses. This is a limitation of the nRF24L01 transceiver for pipes 2-5
-// because they use the same first 4 bytes from pipe 1.
-uint64_t address[6] = {0x7878787878LL,
-                       0xB3B4B5B6F1LL,
-                       0xB3B4B5B6CDLL,
-                       0xB3B4B5B6A3LL,
-                       0xB3B4B5B60FLL,
-                       0xB3B4B5B605LL};
+// because they use the same first 4 MSBytes from pipe 1.
+uint8_t address[6][5] = {{0x78, 0x78, 0x78, 0x78, 0x78},
+                         {0xF1, 0xB6, 0xB5, 0xB4, 0xB3},
+                         {0xCD, 0xB6, 0xB5, 0xB4, 0xB3},
+                         {0xA3, 0xB6, 0xB5, 0xB4, 0xB3},
+                         {0x0F, 0xB6, 0xB5, 0xB4, 0xB3},
+                         {0x05, 0xB6, 0xB5, 0xB4, 0xB3}};
 
 // For this example, we'll be using a payload containing
 // a node ID number and a single integer number that will be incremented
@@ -181,9 +181,8 @@ void master(unsigned int role)
     payload.nodeID = role;
     payload.payloadID = 0;
 
-    // Set the address on pipe 0 to the RX node.
-    radio.stopListening(); // put radio in TX mode
-    radio.openWritingPipe(address[role]);
+    // set the TX address of the RX node for use on the TX pipe (pipe 0)
+    radio.stopListening(address[role]); // put radio in TX mode
 
     // According to the datasheet, the auto-retry features's delay value should
     // be "skewed" to allow the RX node to receive 1 transmission at a time.
diff --git a/examples_linux/multiceiver_demo.py b/examples_linux/multiceiver_demo.py
index 66fa160e7..1f3fe17fb 100644
--- a/examples_linux/multiceiver_demo.py
+++ b/examples_linux/multiceiver_demo.py
@@ -74,9 +74,9 @@ def master(node_number):
         ((node_number * 3) % 12) + 3, 15
     )  # maximum value is 15 for both args
 
-    radio.stopListening()  # put radio in TX mode
-    # set the TX address to the address of the base station.
-    radio.openWritingPipe(addresses[node_number])
+    # set the TX address of the RX node for use on the TX pipe (pipe 0)
+    radio.stopListening(addresses[node_number])  # put radio in TX mode
+
     counter = 0
     failures = 0
     while failures < 6:
diff --git a/examples_linux/streamingData.cpp b/examples_linux/streamingData.cpp
index 30ca66b92..836250464 100644
--- a/examples_linux/streamingData.cpp
+++ b/examples_linux/streamingData.cpp
@@ -149,8 +149,8 @@ int main(int argc, char** argv)
     // each other.
     radio.setPALevel(RF24_PA_LOW); // RF24_PA_MAX is default.
 
-    // set the TX address of the RX node into the TX pipe
-    radio.openWritingPipe(address[radioNumber]); // always uses pipe 0
+    // set the TX address of the RX node for use on the TX pipe (pipe 0)
+    radio.stopListening(address[radioNumber]);
 
     // set the RX address of the TX node into a RX pipe
     radio.openReadingPipe(1, address[!radioNumber]); // using pipe 1
diff --git a/examples_linux/streaming_data.py b/examples_linux/streaming_data.py
index eb38ab26c..4309118a5 100644
--- a/examples_linux/streaming_data.py
+++ b/examples_linux/streaming_data.py
@@ -47,8 +47,8 @@
 # usually run with nRF24L01 transceivers in close proximity of each other
 radio.setPALevel(RF24_PA_LOW)  # RF24_PA_MAX is default
 
-# set the TX address of the RX node into the TX pipe
-radio.openWritingPipe(address[radio_number])  # always uses pipe 0
+# set the TX address of the RX node for use on the TX pipe (pipe 0)
+radio.stopListening(address[radio_number])
 
 # set the RX address of the TX node into a RX pipe
 radio.openReadingPipe(1, address[not radio_number])  # using pipe 1
diff --git a/examples_pico/acknowledgementPayloads.cpp b/examples_pico/acknowledgementPayloads.cpp
index 599e59ed8..98aa4ccb0 100644
--- a/examples_pico/acknowledgementPayloads.cpp
+++ b/examples_pico/acknowledgementPayloads.cpp
@@ -78,8 +78,8 @@ bool setup()
     // this feature for all nodes (TX & RX) to use ACK payloads.
     radio.enableAckPayload();
 
-    // set the TX address of the RX node into the TX pipe
-    radio.openWritingPipe(address[radioNumber]); // always uses pipe 0
+    // set the TX address of the RX node for use on the TX pipe (pipe 0)
+    radio.stopListening(address[radioNumber]);
 
     // set the RX address of the TX node into a RX pipe
     radio.openReadingPipe(1, address[!radioNumber]); // using pipe 1
diff --git a/examples_pico/gettingStarted.cpp b/examples_pico/gettingStarted.cpp
index f7dfaacb4..68e3ad48a 100644
--- a/examples_pico/gettingStarted.cpp
+++ b/examples_pico/gettingStarted.cpp
@@ -67,8 +67,8 @@ bool setup()
     // number of bytes we need to transmit a float
     radio.setPayloadSize(sizeof(payload)); // float datatype occupies 4 bytes
 
-    // set the TX address of the RX node into the TX pipe
-    radio.openWritingPipe(address[radioNumber]); // always uses pipe 0
+    // set the TX address of the RX node for use on the TX pipe (pipe 0)
+    radio.stopListening(address[radioNumber]);
 
     // set the RX address of the TX node into a RX pipe
     radio.openReadingPipe(1, address[!radioNumber]); // using pipe 1
diff --git a/examples_pico/interruptConfigure.cpp b/examples_pico/interruptConfigure.cpp
index b1414d567..a6159f615 100644
--- a/examples_pico/interruptConfigure.cpp
+++ b/examples_pico/interruptConfigure.cpp
@@ -97,18 +97,14 @@ bool setup()
     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 TX address of the RX node for use on the TX pipe (pipe 0)
+    radio.stopListening(address[radioNumber]);
 
     // 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 {
+    if (!role) {
         // setup for RX mode
 
         // disable IRQ pin in RX mode
diff --git a/examples_pico/manualAcknowledgements.cpp b/examples_pico/manualAcknowledgements.cpp
index c0362a8f4..951878370 100644
--- a/examples_pico/manualAcknowledgements.cpp
+++ b/examples_pico/manualAcknowledgements.cpp
@@ -82,8 +82,8 @@ bool setup()
     // number of bytes we need to transmit a float
     radio.setPayloadSize(sizeof(payload)); // char[7] & uint8_t datatypes occupy 8 bytes
 
-    // set the TX address of the RX node into the TX pipe
-    radio.openWritingPipe(address[radioNumber]); // always uses pipe 0
+    // set the TX address of the RX node for use on the TX pipe (pipe 0)
+    radio.stopListening(address[radioNumber]);
 
     // set the RX address of the TX node into a RX pipe
     radio.openReadingPipe(1, address[!radioNumber]); // using pipe 1
@@ -92,7 +92,6 @@ bool setup()
         // setup the TX node
 
         memcpy(payload.message, "Hello ", 6); // set the outgoing message
-        radio.stopListening();                // put radio in TX mode
     }
     else {
         // setup the RX node
diff --git a/examples_pico/multiceiverDemo.cpp b/examples_pico/multiceiverDemo.cpp
index a187ab2c9..634066545 100644
--- a/examples_pico/multiceiverDemo.cpp
+++ b/examples_pico/multiceiverDemo.cpp
@@ -27,13 +27,13 @@ RF24 radio(CE_PIN, CSN_PIN);
 // an identifying device destination
 // Notice that the last byte is the only byte that changes in the last 5
 // addresses. This is a limitation of the nRF24L01 transceiver for pipes 2-5
-// because they use the same first 4 bytes from pipe 1.
-uint64_t address[6] = {0x7878787878LL,
-                       0xB3B4B5B6F1LL,
-                       0xB3B4B5B6CDLL,
-                       0xB3B4B5B6A3LL,
-                       0xB3B4B5B60FLL,
-                       0xB3B4B5B605LL};
+// because they use the same first 4 MSBytes from pipe 1.
+uint8_t address[6][5] = {{0x78, 0x78, 0x78, 0x78, 0x78},
+                         {0xF1, 0xB6, 0xB5, 0xB4, 0xB3},
+                         {0xCD, 0xB6, 0xB5, 0xB4, 0xB3},
+                         {0xA3, 0xB6, 0xB5, 0xB4, 0xB3},
+                         {0x0F, 0xB6, 0xB5, 0xB4, 0xB3},
+                         {0x05, 0xB6, 0xB5, 0xB4, 0xB3}};
 
 // Because this example allow up to 6 nodes (specified by numbers 0-5) to
 // transmit and only 1 node to receive, we will use a negative value in our
@@ -190,9 +190,8 @@ void setRole()
         payload.nodeID = role;
         payload.payloadID = 0;
 
-        // Set the address on pipe 0 to the RX node.
-        radio.stopListening(); // put radio in TX mode
-        radio.openWritingPipe(address[role]);
+        // set the TX address of the RX node for use on the TX pipe (pipe 0)
+        radio.stopListening(address[role]); // put radio in TX mode
 
         // According to the datasheet, the auto-retry features's delay value should
         // be "skewed" to allow the RX node to receive 1 transmission at a time.
diff --git a/examples_pico/streamingData.cpp b/examples_pico/streamingData.cpp
index 8e8d61138..a3716a563 100644
--- a/examples_pico/streamingData.cpp
+++ b/examples_pico/streamingData.cpp
@@ -73,17 +73,14 @@ bool setup()
     // number of bytes we need to transmit
     radio.setPayloadSize(SIZE); // default value is the maximum 32 bytes
 
-    // set the TX address of the RX node into the TX pipe
-    radio.openWritingPipe(address[radioNumber]); // always uses pipe 0
+    // set the TX address of the RX node for use on the TX pipe (pipe 0)
+    radio.stopListening(address[radioNumber]);
 
     // 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) {
-        radio.stopListening(); // put radio in TX mode
-    }
-    else {
+    if (!role) {
         radio.startListening(); // put radio in RX mode
     }
 
diff --git a/pyRF24/pyRF24.cpp b/pyRF24/pyRF24.cpp
index 92ceeb158..2bb4489d5 100644
--- a/pyRF24/pyRF24.cpp
+++ b/pyRF24/pyRF24.cpp
@@ -98,6 +98,11 @@ void openWritingPipe_wrap(RF24& ref, const bp::object address)
     ref.openWritingPipe((const uint8_t*)(get_bytes_or_bytearray_str(address)));
 }
 
+void stopListening_wrap(RF24& ref, const bp::object address)
+{
+    ref.stopListening((const uint8_t*)(get_bytes_or_bytearray_str(address)));
+}
+
 void openReadingPipe_wrap(RF24& ref, uint8_t number, const bp::object address)
 {
     ref.openReadingPipe(number, (const uint8_t*)(get_bytes_or_bytearray_str(address)));
@@ -351,7 +356,8 @@ BOOST_PYTHON_MODULE(RF24)
         .def("startFastWrite", &startFastWrite_wrap2, (bp::arg("buf"), bp::arg("len"), bp::arg("multicast"), bp::arg("startTx")))
         .def("startListening", &RF24::startListening)
         .def("startWrite", &startWrite_wrap, (bp::arg("buf"), bp::arg("len"), bp::arg("multicast")))
-        .def("stopListening", &RF24::stopListening)
+        .def("stopListening", (void(::RF24::*)(void))(&RF24::stopListening))
+        .def("stopListening", &stopListening_wrap, (bp::arg("txAddress")))
         .def("testCarrier", &RF24::testCarrier)
         .def("testRPD", &RF24::testRPD)
         .def("toggleAllPipes", &RF24::toggleAllPipes)


Reviews:
user: login: 2bndy5
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-26T23:06:18Z
user: login: 2bndy5
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-26T23:13:20Z
user: login: 2bndy5
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-26T23:22:35Z
user: login: 2bndy5
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-26T23:42:47Z
user: login: 2bndy5
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-27T00:03:19Z
user: login: 2bndy5
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-30T23:55:01Z
user: login: 2bndy5
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-01T00:15:36Z
user: login: 2bndy5
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-01T00:29:04Z
user: login: TMRh20
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-03T10:35:17Z
user: login: 2bndy5
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-03T10:42:38Z
user: login: TMRh20
body: Arduino sketches all seem to check out now:
1. gettingStarted (pipe0, pipe1, pipe2, etc working)
2. interruptConfigure - good
3. AckknowledgementPayloads - good
4. Multiceiver - good

RF24Network, Mesh, Ethernet & Gateway examples all functioning well also

state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-03T11:11:35Z
user: login: 2bndy5
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-08-07T08:23:14Z
user: login: 2bndy5
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-08-07T08:33:15Z
user: login: TMRh20
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-08-07T08:33:35Z
user: login: 2bndy5
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-08-07T09:27:59Z
user: login: TMRh20
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-08-07T09:45:35Z
user: login: 2bndy5
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-08-07T10:07:23Z

Review comments:
diff_hunk: @@ -499,6 +516,9 @@ class RF24
      * New: Open a pipe for writing via byte array. Old addressing format retained
      * for compatibility.
      *
+     * @deprecated Use `RF24::txAddress` instead.
+     * As of v1.5, using this function is slower than using `RF24::txAddress`.
path: RF24.h
user: login: 2bndy5
body: This patch implies a minor version bump. Network layers (after adopting `txAddress`) would only get a patch version bump.
created_at: 2025-04-26T23:06:11Z
author_association: MEMBER
diff_hunk: @@ -27,13 +27,13 @@ RF24 radio(CE_PIN, CSN_PIN);
 // an identifying device destination
 // Notice that the last byte is the only byte that changes in the last 5
 // addresses. This is a limitation of the nRF24L01 transceiver for pipes 2-5
-// because they use the same first 4 bytes from pipe 1.
-uint64_t address[6] = { 0x7878787878LL,
-                        0xB3B4B5B6F1LL,
-                        0xB3B4B5B6CDLL,
-                        0xB3B4B5B6A3LL,
-                        0xB3B4B5B60FLL,
-                        0xB3B4B5B605LL };
+// because they use the same first 4 MSBytes from pipe 1.
+uint8_t address[6][6] = { { 0x78, 0x78, 0x78, 0x78, 0x78 },
+                          { 0xF1, 0xB6, 0xB5, 0xB4, 0xB3 },
+                          { 0xCD, 0xB6, 0xB5, 0xB4, 0xB3 },
+                          { 0xA3, 0xB6, 0xB5, 0xB4, 0xB3 },
+                          { 0x0F, 0xB6, 0xB5, 0xB4, 0xB3 },
+                          { 0x05, 0xB6, 0xB5, 0xB4, 0xB3 } };
path: examples/MulticeiverDemo/MulticeiverDemo.ino
user: login: 2bndy5
body: The decrease in compile size is from this change.

I switched to using a proper array of buffers to get away from using the older deprecated `openWritingPipe(uint64_t*)`. This also is connonical with using `memcpy()`.
created_at: 2025-04-26T23:13:15Z
author_association: MEMBER
diff_hunk: @@ -27,13 +27,13 @@ RF24 radio(CE_PIN, CSN_PIN);
 // an identifying device destination
 // Notice that the last byte is the only byte that changes in the last 5
 // addresses. This is a limitation of the nRF24L01 transceiver for pipes 2-5
-// because they use the same first 4 bytes from pipe 1.
-uint64_t address[6] = { 0x7878787878LL,
-                        0xB3B4B5B6F1LL,
-                        0xB3B4B5B6CDLL,
-                        0xB3B4B5B6A3LL,
-                        0xB3B4B5B60FLL,
-                        0xB3B4B5B605LL };
+// because they use the same first 4 MSBytes from pipe 1.
+uint8_t address[6][6] = { { 0x78, 0x78, 0x78, 0x78, 0x78 },
+                          { 0xF1, 0xB6, 0xB5, 0xB4, 0xB3 },
+                          { 0xCD, 0xB6, 0xB5, 0xB4, 0xB3 },
+                          { 0xA3, 0xB6, 0xB5, 0xB4, 0xB3 },
+                          { 0x0F, 0xB6, 0xB5, 0xB4, 0xB3 },
+                          { 0x05, 0xB6, 0xB5, 0xB4, 0xB3 } };
path: examples/MulticeiverDemo/MulticeiverDemo.ino
user: login: 2bndy5
body: I also fixed the mistaken `address[6][6]` to be `address[6][5]`.
created_at: 2025-04-26T23:22:35Z
author_association: MEMBER
diff_hunk: @@ -1188,7 +1189,8 @@ void RF24::stopListening(void)
         powerUp();
     }
 #endif
-    write_register(RX_ADDR_P0, pipe0_writing_address, addr_width);
+    write_register(TX_ADDR, txAddress, addr_width);
+    write_register(RX_ADDR_P0, txAddress, addr_width);
path: RF24.cpp
user: login: 2bndy5
body: This addition to `stopListening()` causes an increase in compile size for the scanner example ~and encodeRadioDetails example~. But, those are edge cases where `openWritingPipe()` is never actually used.
created_at: 2025-04-26T23:42:46Z
author_association: MEMBER
diff_hunk: @@ -607,7 +607,8 @@ void RF24::_init_obj()
     _spi = &SPI;
 #endif // defined (RF24_SPI_PTR)
 
-    pipe0_reading_address[0] = 0;
+    memset(pipe0_reading_address, 0, 5);
+    memset(txAddress, 0xE7, 5);
path: RF24.cpp
user: login: 2bndy5
body: This might also be causing a slight increase in compile size (flash consumption). It is only done to properly initialize the address caches.
created_at: 2025-04-27T00:03:19Z
author_association: MEMBER
diff_hunk: @@ -607,7 +607,8 @@ void RF24::_init_obj()
     _spi = &SPI;
 #endif // defined (RF24_SPI_PTR)
 
-    pipe0_reading_address[0] = 0;
+    memset(pipe0_reading_address, 0, 5);
+    memset(txAddress, 0xE7, 5);
path: RF24.cpp
user: login: 2bndy5
body: moved this to the declarations in RF24.h. see https://github.com/nRF24/RF24/pull/1030/commits/76dfead2b5aaa5ef76999a3562ad4f4919c19ada
created_at: 2025-04-30T23:55:01Z
author_association: MEMBER
diff_hunk: @@ -607,7 +607,8 @@ void RF24::_init_obj()
     _spi = &SPI;
 #endif // defined (RF24_SPI_PTR)
 
-    pipe0_reading_address[0] = 0;
+    memset(pipe0_reading_address, 0, 5);
+    memset(txAddress, 0xE7, 5);
path: RF24.cpp
user: login: 2bndy5
body: Initializing the cached addresses in declaration actually increased the flash size. Instead I'm just removing the initial cached values; users should/would end up setting them anyway.
created_at: 2025-05-01T00:15:36Z
author_association: MEMBER
diff_hunk: @@ -607,7 +607,8 @@ void RF24::_init_obj()
     _spi = &SPI;
 #endif // defined (RF24_SPI_PTR)
 
-    pipe0_reading_address[0] = 0;
+    memset(pipe0_reading_address, 0, 5);
+    memset(txAddress, 0xE7, 5);
path: RF24.cpp
user: login: 2bndy5
body: yep. removing the init instructions decreased flash size across the board. There is still an increase in the scanner example... see https://github.com/nRF24/RF24/pull/1030#discussion_r2061843291
created_at: 2025-05-01T00:29:04Z
author_association: MEMBER
diff_hunk: @@ -1606,7 +1614,6 @@ void RF24::openWritingPipe(const uint8_t* address)
     // expects it LSB first too, so we're good.
     write_register(RX_ADDR_P0, address, addr_width);
     write_register(TX_ADDR, address, addr_width);
-    memcpy(pipe0_writing_address, address, addr_width);
path: RF24.cpp
user: login: TMRh20
body: This line needs to be added back in I believe.

`memcpy(pipe0_writing_address, address, addr_width);`

It exists in the `openWritingPipe(uint64_t value)` function and the gettingStarted example won't work without it.
created_at: 2025-05-03T10:35:17Z
author_association: MEMBER
diff_hunk: @@ -1606,7 +1614,6 @@ void RF24::openWritingPipe(const uint8_t* address)
     // expects it LSB first too, so we're good.
     write_register(RX_ADDR_P0, address, addr_width);
     write_register(TX_ADDR, address, addr_width);
-    memcpy(pipe0_writing_address, address, addr_width);
path: RF24.cpp
user: login: 2bndy5
body: Done. Not sure why I removed it. 🤷🏼‍♂️ 
created_at: 2025-05-03T10:42:38Z
author_association: MEMBER
diff_hunk: @@ -1626,12 +1643,14 @@ void RF24::openReadingPipe(uint8_t child, uint64_t address)
 
     if (child <= 5) {
         // For pipes 2-5, only write the LSB
-        if (child < 2) {
-            write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), addr_width);
-        }
-        else {
+        if (child > 1) {
             write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), 1);
         }
+        // avoid overwriting the TX address on pipe 0 while still in TX mode.
+        // NOTE, the cached RX address on pipe 0 is written when startListening() is called.
+        else if (static_cast<bool>(config_reg & _BV(PRIM_RX)) || child != 0) {
path: RF24.cpp
user: login: 2bndy5
body: I'm second guessing the logic here. This was meant to be a consolidated way of doing:
```cpp
if (child > 1) { /* write 1 byte from address */ }
else if (child) { /* write address to pipe 1 */ }
else if (config_reg & _BV(PRIM_RX)) { /* write address to pipe 0 */ }
else { /* skip changing pipe 0 address in TX mode */ }
```
created_at: 2025-08-07T08:23:14Z
author_association: MEMBER
diff_hunk: @@ -1626,12 +1643,14 @@ void RF24::openReadingPipe(uint8_t child, uint64_t address)
 
     if (child <= 5) {
         // For pipes 2-5, only write the LSB
-        if (child < 2) {
-            write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), addr_width);
-        }
-        else {
+        if (child > 1) {
             write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), 1);
         }
+        // avoid overwriting the TX address on pipe 0 while still in TX mode.
+        // NOTE, the cached RX address on pipe 0 is written when startListening() is called.
+        else if (static_cast<bool>(config_reg & _BV(PRIM_RX)) || child != 0) {
path: RF24.cpp
user: login: 2bndy5
body: maybe we could try
```cpp
        if (child != 0) {
            write_register(
                pgm_read_byte(&child_pipe[child]),
                reinterpret_cast<const uint8_t*>(&address),
                child > 1 ? 1 : addr_width);
        }
        // avoid overwriting the TX address on pipe 0 while still in TX mode.
        // NOTE, the cached RX address on pipe 0 is written when startListening() is called.
        else if (static_cast<bool>(config_reg & _BV(PRIM_RX))) {
            write_register(
                pgm_read_byte(&child_pipe[child]),
                reinterpret_cast<const uint8_t*>(&address),
                addr_width);
        }
```
Although, `child > 1 ? 1 : addr_width` might be a bit costly.
created_at: 2025-08-07T08:33:15Z
author_association: MEMBER
diff_hunk: @@ -1626,12 +1643,14 @@ void RF24::openReadingPipe(uint8_t child, uint64_t address)
 
     if (child <= 5) {
         // For pipes 2-5, only write the LSB
-        if (child < 2) {
-            write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), addr_width);
-        }
-        else {
+        if (child > 1) {
             write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), 1);
         }
+        // avoid overwriting the TX address on pipe 0 while still in TX mode.
+        // NOTE, the cached RX address on pipe 0 is written when startListening() is called.
+        else if (static_cast<bool>(config_reg & _BV(PRIM_RX)) || child != 0) {
path: RF24.cpp
user: login: TMRh20
body: Why are you second guessing it? Is something not working?

On quick review, the logic seems to make sense. 
created_at: 2025-08-07T08:33:35Z
author_association: MEMBER
diff_hunk: @@ -1626,12 +1643,14 @@ void RF24::openReadingPipe(uint8_t child, uint64_t address)
 
     if (child <= 5) {
         // For pipes 2-5, only write the LSB
-        if (child < 2) {
-            write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), addr_width);
-        }
-        else {
+        if (child > 1) {
             write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), 1);
         }
+        // avoid overwriting the TX address on pipe 0 while still in TX mode.
+        // NOTE, the cached RX address on pipe 0 is written when startListening() is called.
+        else if (static_cast<bool>(config_reg & _BV(PRIM_RX)) || child != 0) {
path: RF24.cpp
user: login: 2bndy5
body: > Why are you second guessing it? Is something not working?

The inability to switch roles reported in #1042 

created_at: 2025-08-07T09:27:59Z
author_association: MEMBER
diff_hunk: @@ -1626,12 +1643,14 @@ void RF24::openReadingPipe(uint8_t child, uint64_t address)
 
     if (child <= 5) {
         // For pipes 2-5, only write the LSB
-        if (child < 2) {
-            write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), addr_width);
-        }
-        else {
+        if (child > 1) {
             write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), 1);
         }
+        // avoid overwriting the TX address on pipe 0 while still in TX mode.
+        // NOTE, the cached RX address on pipe 0 is written when startListening() is called.
+        else if (static_cast<bool>(config_reg & _BV(PRIM_RX)) || child != 0) {
path: RF24.cpp
user: login: TMRh20
body: I assume if there were an RF24 issue it would have been reported by more users, on forums etc.
created_at: 2025-08-07T09:45:34Z
author_association: MEMBER
diff_hunk: @@ -1626,12 +1643,14 @@ void RF24::openReadingPipe(uint8_t child, uint64_t address)
 
     if (child <= 5) {
         // For pipes 2-5, only write the LSB
-        if (child < 2) {
-            write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), addr_width);
-        }
-        else {
+        if (child > 1) {
             write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), 1);
         }
+        // avoid overwriting the TX address on pipe 0 while still in TX mode.
+        // NOTE, the cached RX address on pipe 0 is written when startListening() is called.
+        else if (static_cast<bool>(config_reg & _BV(PRIM_RX)) || child != 0) {
path: RF24.cpp
user: login: 2bndy5
body: yeah, I just reviewed the possible outcomes from the current logic, and it should behave as intended.

Wish I could turn off my brain just a little bit to prevent overthinking everything.
created_at: 2025-08-07T10:07:23Z
author_association: MEMBER

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-26T23:03:16Z
body: **Memory usage change @ a04c3244dbb0e1b6ea226d5859269a9add37957e**

Board|flash|%|RAM for global variables|%
-|-|-|-|-
`arduino:avr:nano`|:grey_question: -132 - +44|-0.43 - +0.14|:green_heart: -18 - 0|-0.88 - 0.0
`arduino:samd:mkrzero`|:grey_question: -48 - +28|-0.02 - +0.01|:green_heart: -32 - 0|-0.1 - 0.0

<details>
<summary>Click for full report table</summary>

Board|`examples/GettingStarted`<br>flash|%|`examples/GettingStarted`<br>RAM for global variables|%|`examples/AcknowledgementPayloads`<br>flash|%|`examples/AcknowledgementPayloads`<br>RAM for global variables|%|`examples/ManualAcknowledgements`<br>flash|%|`examples/ManualAcknowledgements`<br>RAM for global variables|%|`examples/StreamingData`<br>flash|%|`examples/StreamingData`<br>RAM for global variables|%|`examples/MulticeiverDemo`<br>flash|%|`examples/MulticeiverDemo`<br>RAM for global variables|%|`examples/InterruptConfigure`<br>flash|%|`examples/InterruptConfigure`<br>RAM for global variables|%|`examples/scanner`<br>flash|%|`examples/scanner`<br>RAM for global variables|%|`examples/encodeRadioDetails`<br>flash|%|`examples/encodeRadioDetails`<br>RAM for global variables|%
-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-
`arduino:avr:nano`|-16|-0.05|0|0.0|-20|-0.07|0|0.0|-18|-0.06|0|0.0|-18|-0.06|0|0.0|-132|-0.43|-18|-0.88|-24|-0.08|0|0.0|44|0.14|0|0.0|-2|-0.01|0|0.0
`arduino:samd:mkrzero`|-4|-0.0|0|0.0|-4|-0.0|0|0.0|-8|-0.0|0|0.0|-8|-0.0|0|0.0|-48|-0.02|-32|-0.1|-8|-0.0|0|0.0|28|0.01|0|0.0|-4|-0.0|0|0.0

</details>

<details>
<summary>Click for full report CSV</summary>

```
Board,examples/GettingStarted<br>flash,%,examples/GettingStarted<br>RAM for global variables,%,examples/AcknowledgementPayloads<br>flash,%,examples/AcknowledgementPayloads<br>RAM for global variables,%,examples/ManualAcknowledgements<br>flash,%,examples/ManualAcknowledgements<br>RAM for global variables,%,examples/StreamingData<br>flash,%,examples/StreamingData<br>RAM for global variables,%,examples/MulticeiverDemo<br>flash,%,examples/MulticeiverDemo<br>RAM for global variables,%,examples/InterruptConfigure<br>flash,%,examples/InterruptConfigure<br>RAM for global variables,%,examples/scanner<br>flash,%,examples/scanner<br>RAM for global variables,%,examples/encodeRadioDetails<br>flash,%,examples/encodeRadioDetails<br>RAM for global variables,%
arduino:avr:nano,-16,-0.05,0,0.0,-20,-0.07,0,0.0,-18,-0.06,0,0.0,-18,-0.06,0,0.0,-132,-0.43,-18,-0.88,-24,-0.08,0,0.0,44,0.14,0,0.0,-2,-0.01,0,0.0
arduino:samd:mkrzero,-4,-0.0,0,0.0,-4,-0.0,0,0.0,-8,-0.0,0,0.0,-8,-0.0,0,0.0,-48,-0.02,-32,-0.1,-8,-0.0,0,0.0,28,0.01,0,0.0,-4,-0.0,0,0.0
```
</details>
author_association: CONTRIBUTOR
user: login: TMRh20
created_at: 2025-04-27T05:05:09Z
body: I have one suggestion about this, since it changes the API so much.

```
uint8_t rxNodeAddress[6] = "1Node";
memcpy(radio.txAddress, rxNodeAddress, 5);
radio.stopListening();
```
Why not change it so the user just calls the following:
```
uint8_t rxNodeAddress[6] = "1Node";
radio.stopListening(rxNodeAddress);
 ```
This way users don't need to know about or use the memcpy function or openWritingPipe.

*EDIT: It could also allow use to keep the old stopListening() function in place, for when users don't want the bother of overwriting the pipe0 rx address.

author_association: MEMBER
user: login: 2bndy5
created_at: 2025-04-27T05:33:12Z
body: That could work. It is a little less intuitive because 
1. the name `stopListening()` (thanks maniacBug 😜).
2. it kind of assumes the TX address will be dynamic.
 
We could even overload `stopListening()` where
- `stopListening(void)` remains the same but deprecated along with `openWritingPipe()`. This would leave existing code unhindered by the performance regression I'm trying to resolve here.
- `stopListening(uint8_t*)` would additionally cache/restore the TX address to satisfy the pipe 0 bug.

### setting the address was separate from activating TX mode for a reason though

1. Is it unusual to set the TX address before actually engaging TX mode?
5. What if the TX address doesn't change every time the radio enters TX mode? In most of our examples, the address doesn't have to be globally scoped. In actual user production code, where the TX address is static, each "prep TX mode" function wouldn't necessarily have access to the buffer that configures the TX address.

   1. If the TX address doesn't change, the user might think that `stopListening(void)` would work the same as `stopListening(uint8_t*)`. Then they'd instinctively invoke the deprecated behavior.

author_association: MEMBER
user: login: 2bndy5
created_at: 2025-04-27T05:39:28Z
body: Would this be a bad time to phase in the newer grammar `asTx()` and `asRx()` that I landed on in the rf24-rs port?
author_association: MEMBER
user: login: TMRh20
created_at: 2025-04-27T05:39:50Z
body: > Is it unusual to set the TX address before actually engaging TX mode?

It kind of is, it makes things more difficult to address in RF24Network. I really have to look at the order of operations to see how this will work out.

> What if the TX address doesn't change every time the radio enters TX mode?

Thats why we leave the old stopListening and openWritingPipe in place, so users can just use the old functionality.

Here is what I am playing around with:

```cpp
void RF24::stopListening()
{

    ce(LOW);

    //delayMicroseconds(100);
    delayMicroseconds(static_cast<int>(txDelay));
    if (ack_payloads_enabled) {
        flush_tx();
    }

    config_reg = static_cast<uint8_t>(config_reg & ~_BV(PRIM_RX));
    write_register(NRF_CONFIG, config_reg);

#if defined(RF24_TINY) || defined(LITTLEWIRE)
    // for 3 pins solution TX mode is only left with additional powerDown/powerUp cycle
    if (ce_pin == csn_pin) {
        powerDown();
        powerUp();
    }
#endif

    write_register(EN_RXADDR, static_cast<uint8_t>(read_register(EN_RXADDR) | _BV(pgm_read_byte(&child_pipe_enable[0])))); // Enable RX on pipe0

}

/****************************************************************************/

void RF24::stopListening(uint8_t address[5])
{
    stopListening();
    write_register(TX_ADDR, address, addr_width);
    write_register(RX_ADDR_P0, address, addr_width);

}

/****************************************************************************/

void RF24::stopListening(uint64_t address)
{
    stopListening();
    write_register(TX_ADDR, reinterpret_cast<uint8_t*>(&address), addr_width);
    write_register(RX_ADDR_P0, reinterpret_cast<uint8_t*>(&address), addr_width);

}
```

I think most users are already avoiding pipe 0 because of this bug.

author_association: MEMBER
user: login: 2bndy5
created_at: 2025-04-27T05:42:15Z
body: > I think most users are already avoiding pipe 0 because of this bug.

Without a doubt, ~we are~ I am totally overthinking this but for good reasons this time.
author_association: MEMBER
user: login: 2bndy5
created_at: 2025-04-27T05:51:05Z
body: > > Is it unusual to set the TX address before actually engaging TX mode?
> 
> It kind of is, it makes things more difficult to address in RF24Network. I really have to look at the order of operations to see how this will work out.

Its a little difficult for me to remember the order of ops. In the examples, I noticed that most of them set the addresses to the pipes first and then call `stopListening()` in `setup()`. I often confuse the mandate about `startListening()` being called _after_ setting the RX addresses as if it applies similarly to `stopListening()`.

It only works out in the examples because they often call `stopListening()` to idle the radio after master or slave roles complete.

EDIT: I think if we publicly expose the `ce()` function, the network layer would be less reliant on `stopListening()` to exit RX mode.
author_association: MEMBER
user: login: TMRh20
created_at: 2025-04-27T05:58:45Z
body: This is what I changed in RF24Network
```cpp
template<class radio_t>
bool ESBNetwork<radio_t>::write_to_pipe(uint16_t node, uint8_t pipe, bool multicast)
{
    bool ok = false;

    // Open the correct pipe for writing.
    // First, stop listening so we can talk
    if (!(networkFlags & FLAG_FAST_FRAG)) {
        if(pipe){
          radio.stopListening();
        }else{
          radio.stopListening(pipe_address(node, pipe));    // << **** CHANGED THIS
        }
            
    }
```
Now I'm getting upwards of 27KB/s with the same tests. Not sure what was going on before, but this is more inline with the capabilities of the radios.

author_association: MEMBER
user: login: 2bndy5
created_at: 2025-04-27T06:09:17Z
body: > Now I'm getting upwards of 27KB/s with the same tests. Not sure what was going on before, but this is more inline with the capabilities of the radios.

That's with only 1 hop or a multitude?

> ```c
> // First, stop listening so we can talk
> ```

Honestly, this could be done quicker with just toggling the CE pin. I find it weird this lib hides the CE pin capability and the STATUS flags. Every time I write a nRF24L driver from scratch, I always expose these as a fail-safe for advanced usage; networking and streaming consecutive payloads definitely qualify as advanced usage.
author_association: MEMBER
user: login: TMRh20
created_at: 2025-04-27T06:15:25Z
body: 1. That's direct, node 1 to node 0 at 27KB/s with RF24Network. Kind of the speed I was expecting before.
2. What about the need to config PRIM_RX and the EN_RXADDR register? Isn't that required to stopListening() ?

author_association: MEMBER
user: login: 2bndy5
created_at: 2025-04-27T06:17:27Z
body: > What about the need to config PRIM_RX and the EN_RXADDR register? Isn't that required to stopListening() ?

Yeah, but lumping that in with CE management is what mandates the confusing order of operations.
author_association: MEMBER
user: login: TMRh20
created_at: 2025-04-27T06:18:34Z
body: Well in that case, lets make CE() public!
author_association: MEMBER
user: login: 2bndy5
created_at: 2025-04-27T06:27:59Z
body: Should I combine this PR changes with your `stopListening()` overload approach? Or start a new branch?

It still doesn't hurt to have the TX address public too. I can't think of a point where this lib (or other network layers) needs exclusive access/control over it.

FYI, rust doesn't have implicit function overloading like in C++. Python 3 had to be patched to support overloaded functions (making it an explicit opt-in feature/syntax in pure python).
author_association: MEMBER
user: login: TMRh20
created_at: 2025-04-27T06:29:54Z
body: Whichever way is easiest I guess.
I agree.
author_association: MEMBER
user: login: TMRh20
created_at: 2025-04-27T06:39:18Z
body: > Would this be a bad time to phase in the newer grammar asTx() and asRx() that I landed on in the rf24-rs port?

Missed this comment, but to me those names are more confusing than startListening/stopListening. Maybe I'm just old and averse to change :p
author_association: MEMBER
user: login: 2bndy5
created_at: 2025-04-27T06:43:09Z
body: Fair enough. After writing examples with `radio.as_tx()` or `radio.as_rx()`, I kind of prefer it now. Not to mention the convenient brevity.
author_association: MEMBER
user: login: 2bndy5
created_at: 2025-04-28T07:52:12Z
body: pending rebase after #1031 is merged.
author_association: MEMBER
user: login: 2bndy5
created_at: 2025-04-30T20:05:48Z
body: I found a problem with the overloaded `stopListening(txAddress)` approach.
```cpp
radio.stopListening(txAddress);      // write the TX address to RX_ADDR_P0
radio.openReadingPipe(0, rxAddress); // write the RX address to RX_ADDR_P0
// !!! still in TX mode but RX pipe 0 address is not the TX address
```

My gut reaction is to use the cached `config_reg` and the given `pipe` number in `openReadingPipe()` to prevent overwriting the TX address while still in TX mode.
So, this
https://github.com/nRF24/RF24/blob/7125fb47016cedc3bb47278ae555a8683f74425f/RF24.cpp#L1651-L1656
gets changed to
```cpp
        if (child > 1) {
            write_register(pgm_read_byte(&child_pipe[child]), address, 1);
        }
        else if (static_cast<bool>(config_reg & _BV(PRIM_RX)) || child != 0) {
            write_register(pgm_read_byte(&child_pipe[child]), address, addr_width);
        }
```
Fortunately, the cached `pipe0_reading_address` will be restored to pipe 0 when `startListening()` is called.
https://github.com/nRF24/RF24/blob/7125fb47016cedc3bb47278ae555a8683f74425f/RF24.cpp#L1158-L1161
Following the state of the `EN_RXADDR` register in this scenario, I think we should be fine because both `openReadingPipe()` and `stopListening()` open RX pipe 0, and `startListening()` won't close it because `_is_p0_rx` is true.
author_association: MEMBER
user: login: 2bndy5
created_at: 2025-05-01T05:34:24Z
body: > It still doesn't hurt to have the TX address public too. I can't think of a point where this lib (or other network layers) needs exclusive access/control over it.

The more I think about it, exposing the `txAddress` may only lead to confusion. The way this patch works now, `memcpy(radio.txAddress, ...)` does nothing because
1. `stopListening(void)` doesn't restore the TX address to the `TX_ADDR` reg. It only writes the cached TX address to RX pipe 0.
2. `stopListening(tx_address)` caches the given value, calls `stopListening(void)`, then writes the new address to the `TX_ADDR` register.

I'm going to revert the public exposure of the cached TX address.
author_association: MEMBER
user: login: 2bndy5
created_at: 2025-05-03T07:35:48Z
body: This one needs diligent review. My brain is sore from ~overthinking~ analyzing the order of function calls that might cause erroneous radio setup.
author_association: MEMBER
user: login: TMRh20
created_at: 2025-05-03T09:43:39Z
body: Well good news so far, this doesn't seem to break any old code. I'm running this on all my RPis & a couple Arduinos to test. Will move on to testing the new API soon.
author_association: MEMBER
user: login: 2bndy5
created_at: 2025-05-03T09:57:56Z
body: I don't think it would even if you use pipe 0 to RX (the real crux of the problem). If pipe 0 is used, then there should just be an extra/duplicate write to `RX_ADDR_P0` register (from `openWritingPipe()`). With the overloaded `stopListening(tx_address)`, the duplicate write to that register is removed.

I must admit, your `stopListening()` overload idea is exactly what this needed.
author_association: MEMBER
user: login: 2bndy5
created_at: 2025-05-03T11:17:46Z
body: I'm too tired to think. Mind if this sits overnight (my time). I'll give this another code review pass tomorrow and we can do the merge crusade. 

I still think the network layers would be more performant with the overloaded `stopListening()` instead of `openWritingPipe()`. But the rationale escapes me right now.
author_association: MEMBER
user: login: TMRh20
created_at: 2025-05-03T11:21:16Z
body: Yeah, I guess the DPL bug has existed forever, one more night won't hurt

> I still think the network layers would be more performant with the overloaded stopListening() instead of openWritingPipe(). But the rationale escapes me right now.

I'll take a look at it today also.
author_association: MEMBER
user: login: 2bndy5
created_at: 2025-05-03T18:53:49Z
body: I 'm going to deprecate that new `stopListening(uint64_t)` because using an integer as  an address has a couple  disadvantages:
- The MSB (unique address prefix for each pipe) oddly goes last in an integer. This only confuses users that never heard of "Endianness". In declaring a buffer, the MSB intuitively goes first.
- Even if a 5-byte buffer is byte-aligned to 6 bytes, there's still less memory allocated when compared to a 64-bit integer.

Both of these disadvantages are briefly explained (with example snippets) in the migration guide, so we shouldn't have to repeat ourselves when people ask.

PS - I get why you added it; it makes migration practically a swapping of function names. But, the address passed needs to be changed into a buffer to complete the migration. There would also be a duplicate `stopListening(void)` call that is no longer required if `stopListening(address)` is already called.
author_association: MEMBER
user: login: TMRh20
created_at: 2025-05-03T21:55:45Z
body: Yeah, I should probably have a look at RF24Network some time and see about converting from using uint64_t for addressing.

Soo, release crusade?
author_association: MEMBER
user: login: 2bndy5
created_at: 2025-05-03T22:00:04Z
body: > Yeah, I should probably have a look at RF24Network some time and see about converting from using uint64_t for addressing. 

Already done in nRF24/RF24Network#251. I'm testing that branch now (against another RPi running just master branches).

> Soo, release crusade?

Sure.
author_association: MEMBER
user: login: 2bndy5
created_at: 2025-05-03T22:01:15Z
body: If you're going to do it, remember RF24 gets a minor bump, not a patch.
author_association: MEMBER