Skip to content

Commit d9417e0

Browse files
2bndy5TMRh20
andauthored
refactor: migrate to stopListening(txAddress) from openWritingPipe() (#1030)
follow up to #1029 `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. 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. --------- Co-authored-by: TMRh20 <tmrh20@gmail.com>
1 parent 0bd92d5 commit d9417e0

30 files changed

+220
-121
lines changed

.github/workflows/doxygen.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,5 @@ jobs:
5050
uses: nRF24/.github/.github/workflows/build_docs.yaml@main
5151
with:
5252
deploy-gh-pages: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/master') }}
53-
doxygen-version: '1.12.0'
53+
doxygen-version: '1.13.2'
5454
secrets: inherit

.readthedocs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ build:
1515
commands:
1616
# Install doxygen from source distributions (conda forge does not keep up-to-date doxygen releases)
1717
- >
18-
DOXYGEN_VERSION="1.12.0" &&
18+
DOXYGEN_VERSION="1.13.2" &&
1919
mkdir .doxygen && cd .doxygen &&
2020
echo $(pwd) &&
2121
echo "https://sourceforge.net/projects/doxygen/files/rel-$DOXYGEN_VERSION/doxygen-$DOXYGEN_VERSION.linux.bin.tar.gz" &&

RF24.cpp

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,6 @@ void RF24::_init_obj()
603603
_spi = &SPI;
604604
#endif // defined (RF24_SPI_PTR)
605605

606-
pipe0_reading_address[0] = 0;
607606
if (spi_speed <= 35000) { //Handle old BCM2835 speed constants, default to RF24_SPI_SPEED
608607
spi_speed = RF24_SPI_SPEED;
609608
}
@@ -1192,6 +1191,24 @@ void RF24::stopListening(void)
11921191

11931192
/****************************************************************************/
11941193

1194+
void RF24::stopListening(const uint64_t txAddress)
1195+
{
1196+
memcpy(pipe0_writing_address, &txAddress, addr_width);
1197+
stopListening();
1198+
write_register(TX_ADDR, pipe0_writing_address, addr_width);
1199+
}
1200+
1201+
/****************************************************************************/
1202+
1203+
void RF24::stopListening(const uint8_t* txAddress)
1204+
{
1205+
memcpy(pipe0_writing_address, txAddress, addr_width);
1206+
stopListening();
1207+
write_register(TX_ADDR, pipe0_writing_address, addr_width);
1208+
}
1209+
1210+
/****************************************************************************/
1211+
11951212
void RF24::powerDown(void)
11961213
{
11971214
ce(LOW); // Guarantee CE is low on powerDown
@@ -1626,12 +1643,14 @@ void RF24::openReadingPipe(uint8_t child, uint64_t address)
16261643

16271644
if (child <= 5) {
16281645
// For pipes 2-5, only write the LSB
1629-
if (child < 2) {
1630-
write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), addr_width);
1631-
}
1632-
else {
1646+
if (child > 1) {
16331647
write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), 1);
16341648
}
1649+
// avoid overwriting the TX address on pipe 0 while still in TX mode.
1650+
// NOTE, the cached RX address on pipe 0 is written when startListening() is called.
1651+
else if (static_cast<bool>(config_reg & _BV(PRIM_RX)) || child != 0) {
1652+
write_register(pgm_read_byte(&child_pipe[child]), reinterpret_cast<const uint8_t*>(&address), addr_width);
1653+
}
16351654

16361655
// Note it would be more efficient to set all of the bits for all open
16371656
// 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)
16681687
}
16691688
if (child <= 5) {
16701689
// For pipes 2-5, only write the LSB
1671-
if (child < 2) {
1672-
write_register(pgm_read_byte(&child_pipe[child]), address, addr_width);
1673-
}
1674-
else {
1690+
if (child > 1) {
16751691
write_register(pgm_read_byte(&child_pipe[child]), address, 1);
16761692
}
1693+
// avoid overwriting the TX address on pipe 0 while still in TX mode.
1694+
// NOTE, the cached RX address on pipe 0 is written when startListening() is called.
1695+
else if (static_cast<bool>(config_reg & _BV(PRIM_RX)) || child != 0) {
1696+
write_register(pgm_read_byte(&child_pipe[child]), address, addr_width);
1697+
}
16771698

16781699
// Note it would be more efficient to set all of the bits for all open
16791700
// pipes at once. However, I thought it would make the calling code
@@ -2037,6 +2058,11 @@ void RF24::stopConstCarrier()
20372058
powerDown(); // per datasheet recommendation (just to be safe)
20382059
write_register(RF_SETUP, static_cast<uint8_t>(read_register(RF_SETUP) & ~_BV(CONT_WAVE) & ~_BV(PLL_LOCK)));
20392060
ce(LOW);
2061+
flush_tx();
2062+
if (isPVariant()) {
2063+
// restore the cached TX address
2064+
write_register(TX_ADDR, pipe0_writing_address, addr_width);
2065+
}
20402066
}
20412067

20422068
/****************************************************************************/

RF24.h

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -379,13 +379,16 @@ class RF24
379379
* @warning When the ACK payloads feature is enabled, the TX FIFO buffers are
380380
* flushed when calling this function. This is meant to discard any ACK
381381
* payloads that were not appended to acknowledgment packets.
382-
*
383-
* @note For auto-ack purposes, the TX address passed to openWritingPipe() will be restored to
384-
* RX pipe 0. This still means that `stopListening()` shall be called before
385-
* calling openWritingPipe() because the TX address is cached in openWritingPipe().
386382
*/
387383
void stopListening(void);
388384

385+
/**
386+
* @brief Similar to startListening(void) but changes the TX address.
387+
* @param txAddress The new TX address.
388+
* This value will be cached for auto-ack purposes.
389+
*/
390+
void stopListening(const uint8_t* txAddress);
391+
389392
/**
390393
* Check whether there are bytes available to be read
391394
* @code
@@ -523,6 +526,8 @@ class RF24
523526
* New: Open a pipe for writing via byte array. Old addressing format retained
524527
* for compatibility.
525528
*
529+
* @deprecated Use `RF24::stopListening(uint8_t*)` instead.
530+
*
526531
* Only one writing pipe can be opened at once, but this function changes
527532
* the address that is used to transmit (ACK payloads/packets do not apply
528533
* here). Be sure to call stopListening() prior to calling this function.
@@ -2022,6 +2027,17 @@ class RF24
20222027
*/
20232028
void whatHappened(bool& tx_ok, bool& tx_fail, bool& rx_ready);
20242029

2030+
/**
2031+
* Similar to startListening(void) but changes the TX address.
2032+
*
2033+
* @deprecated Use stopListening(const uint8_t*) instead.
2034+
* See our [migration guide](migration.md) to understand what you should update in your code.
2035+
*
2036+
* @param txAddress The new TX address.
2037+
* This value will be cached for auto-ack purposes.
2038+
*/
2039+
void stopListening(const uint64_t txAddress);
2040+
20252041
private:
20262042
/**@}*/
20272043
/**

docs/migration.md

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,34 @@ if radio.available() { /* .. */ }
3030

3131
</td></tr></table>
3232

33-
## openReadingPipe(uint8_t, uint64_t) and openWritingPipe(uint64_t)
33+
## 64-bit integer addresses
3434

35-
> **Deprecated since v1.3.11**
35+
Any function that accept an address in the form of `uint64_t` is discouraged. This includes
3636

37-
These functions' address parameter used a 64-bit unsigned integer (`uint64_t`).
37+
- `RF24::openReadingPipe(uint8_t, uint64_t)`
38+
> **Deprecated since v1.3.11**
39+
- `RF24::openWritingPipe(uint64_t)`
40+
> **Deprecated since v1.3.11**
41+
- `RF24::stopListening(const uint64_t)`
42+
> **Deprecated since v1.5**
43+
44+
These functions' address parameter use a 64-bit unsigned integer (`uint64_t`).
3845
The nRF24L01 can only use up to 40 bit addresses.
39-
Thus, there was an unused 24 bits being allocated for addresses using this function.
46+
Thus, there is an unused 24 bits being allocated for addresses using these functions.
4047

4148
There are overloaded functions that use a buffer instead:
4249

4350
- `RF24::openReadingPipe(uint8_t, const uint8_t*)`
4451
- `RF24::openWritingPipe(const uint8_t*)`
52+
- `RF24::stopListening(const uint8_t*)`
4553

4654
These eliminate the unnecessary 24 bits by only using the length of the buffer (`uint8_t*`)
4755
specified by `RF24::setAddressWidth()`.
4856

57+
@see The `RF24::openWritingPipe(const uint8_t*)` is now deprecated in favor of the
58+
overloaded `RF24::stopListening(const uint8_t*)` function.
59+
See the section below for more detail.
60+
4961
> [!CAUTION]
5062
> The endianness (byte order) of a buffer is reversed compared to a 64-bit integer.
5163
> ```c
@@ -177,6 +189,7 @@ The aptly named `RF24::clearStatusFlags()` is designed to be a replacement for `
177189
Like `RF24::clearStatusFlags()`, `RF24::setStatusFlags()` takes 1 parameter whose value is defined by
178190
the `rf24_irq_flags_e` enumerated constants. These constant values specify individual flags;
179191
they can also be OR'd together to specify multiple flags.
192+
180193
Additionally, `RF24::clearStatusFlags()` returns the STATUS byte containing the flags that
181194
caused the IRQ pin to go active LOW.
182195
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);
211224
```
212225
213226
</td></tr></table>
227+
228+
## openWritingPipe(const uint8_t*)
229+
230+
> Deprecated since v1.5
231+
232+
Originally, `RF24::openWritingPipe(const uint8_t*)` was just a compliment to
233+
`RF24::openReadingPipe()`.
234+
It changes the address on pipe 0 because that is the only pipe that can be
235+
used for transmitting.
236+
237+
Unfortunately, there was a bug that prevented the given TX address from being
238+
persistent on pipe 0 if the user code also set an RX address to pipe 0.
239+
This bug would surface when switching between RX mode and TX mode (via
240+
`RF24::startListening()` and `RF24::stopListening()` respectively) or after
241+
`RF24::stopConstCarrier()` (if `RF24::isPVariant()` returns `true`).
242+
243+
The solution is to cache the TX address on the `RF24` instance.
244+
Consequently, this solution did not fit well with the traditional order of
245+
functions used to set up the radio's TX or RX mode.
246+
247+
By overloading `RF24::stopListening(const uint8_t*)`, we are able to ensure proper radio
248+
setup without requiring certain functions are called in a certain order.
249+
250+
- Use `RF24::stopListening(const uint8_t*)` to set the TX address and enter inactive TX mode.
251+
`RF24::openReadingPipe()` can now (as of v1.5) be used in TX mode without consequence.
252+
- Use `RF24::stopListening()` to enter inactive TX mode without changing the TX address.
253+
254+
> [!warning]
255+
> Avoid using pipe 0 for RX operations to improve performance and reliability.
256+
>
257+
> For implementation detail, see the source for `RF24::openReadingPipe()` and
258+
> `RF24::stopListening()`. Ultimately, the datasheet's Appendix A has a detailed
259+
> example outlining the order of a proper radio setup.
260+
261+
<table><tr>
262+
<th>Old</th>
263+
<th>New (supported)</th>
264+
</tr><tr><td>
265+
266+
```cpp
267+
// set TX address (pipe 0)
268+
radio.openWritingPipe(tx_address);
269+
270+
// set RX address (pipe 1)
271+
radio.openReadingPipe(1, rx_address);
272+
273+
// idle radio using inactive TX mode
274+
radio.stopListening();
275+
```
276+
277+
</td><td>
278+
279+
```cpp
280+
// set TX address (pipe 0)
281+
radio.stopListening(tx_address); // enters inactive TX mode
282+
283+
// set RX address (pipe 1)
284+
radio.openReadingPipe(1, rx_address);
285+
```
286+
287+
</td></tr></table>

examples/AcknowledgementPayloads/AcknowledgementPayloads.ino

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,16 @@ void setup() {
8484
// this feature for all nodes (TX & RX) to use ACK payloads.
8585
radio.enableAckPayload();
8686

87-
// set the TX address of the RX node into the TX pipe
88-
radio.openWritingPipe(address[radioNumber]); // always uses pipe 0
87+
// set the TX address of the RX node for use on the TX pipe (pipe 0)
88+
radio.stopListening(address[radioNumber]); // put radio in TX mode
8989

9090
// set the RX address of the TX node into a RX pipe
9191
radio.openReadingPipe(1, address[!radioNumber]); // using pipe 1
9292

9393
// additional setup specific to the node's role
9494
if (role) {
9595
// setup the TX payload
96-
9796
memcpy(payload.message, "Hello ", 6); // set the payload message
98-
radio.stopListening(); // put radio in TX mode
9997
} else {
10098
// setup the ACK payload & load the first response into the FIFO
10199

examples/GettingStarted/GettingStarted.ino

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,14 @@ void setup() {
7474
// number of bytes we need to transmit a float
7575
radio.setPayloadSize(sizeof(payload)); // float datatype occupies 4 bytes
7676

77-
// set the TX address of the RX node into the TX pipe
78-
radio.openWritingPipe(address[radioNumber]); // always uses pipe 0
77+
// set the TX address of the RX node for use on the TX pipe (pipe 0)
78+
radio.stopListening(address[radioNumber]); // put radio in TX mode
7979

8080
// set the RX address of the TX node into a RX pipe
8181
radio.openReadingPipe(1, address[!radioNumber]); // using pipe 1
8282

83-
// additional setup specific to the node's role
84-
if (role) {
85-
radio.stopListening(); // put radio in TX mode
86-
} else {
83+
// additional setup specific to the node's RX role
84+
if (!role) {
8785
radio.startListening(); // put radio in RX mode
8886
}
8987

examples/InterruptConfigure/InterruptConfigure.ino

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,20 +102,15 @@ void setup() {
102102
// Acknowledgement packets have no payloads by default. We need to enable
103103
// this feature for all nodes (TX & RX) to use ACK payloads.
104104
radio.enableAckPayload();
105-
// Fot this example, we use the same address to send data back and forth
106105

107-
// set the TX address of the RX node into the TX pipe
108-
radio.openWritingPipe(address[radioNumber]); // always uses pipe 0
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
109108

110109
// set the RX address of the TX node into a RX pipe
111110
radio.openReadingPipe(1, address[!radioNumber]); // using pipe 1
112111

113-
// additional setup specific to the node's role
114-
if (role) {
115-
// setup for TX mode
116-
radio.stopListening(); // put radio in TX mode
117-
118-
} else {
112+
// additional setup specific to the node's RX role
113+
if (!role) {
119114
// setup for RX mode
120115

121116
// let IRQ pin only trigger on "data_ready" event in RX mode

examples/ManualAcknowledgements/ManualAcknowledgements.ino

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ void setup() {
8888
// number of bytes we need to transmit a float
8989
radio.setPayloadSize(sizeof(payload)); // char[7] & uint8_t datatypes occupy 8 bytes
9090

91-
// set the TX address of the RX node into the TX pipe
92-
radio.openWritingPipe(address[radioNumber]); // always uses pipe 0
91+
// set the TX address of the RX node for use on the TX pipe (pipe 0)
92+
radio.stopListening(address[radioNumber]); // put radio in TX mode
9393

9494
// set the RX address of the TX node into a RX pipe
9595
radio.openReadingPipe(1, address[!radioNumber]); // using pipe 1
@@ -98,7 +98,6 @@ void setup() {
9898
// setup the TX node
9999

100100
memcpy(payload.message, "Hello ", 6); // set the outgoing message
101-
radio.stopListening(); // put radio in TX mode
102101
} else {
103102
// setup the RX node
104103

examples/MulticeiverDemo/MulticeiverDemo.ino

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ RF24 radio(CE_PIN, CSN_PIN);
2727
// an identifying device destination
2828
// Notice that the last byte is the only byte that changes in the last 5
2929
// addresses. This is a limitation of the nRF24L01 transceiver for pipes 2-5
30-
// because they use the same first 4 bytes from pipe 1.
31-
uint64_t address[6] = { 0x7878787878LL,
32-
0xB3B4B5B6F1LL,
33-
0xB3B4B5B6CDLL,
34-
0xB3B4B5B6A3LL,
35-
0xB3B4B5B60FLL,
36-
0xB3B4B5B605LL };
30+
// because they use the same first 4 MSBytes from pipe 1.
31+
uint8_t address[6][5] = { { 0x78, 0x78, 0x78, 0x78, 0x78 },
32+
{ 0xF1, 0xB6, 0xB5, 0xB4, 0xB3 },
33+
{ 0xCD, 0xB6, 0xB5, 0xB4, 0xB3 },
34+
{ 0xA3, 0xB6, 0xB5, 0xB4, 0xB3 },
35+
{ 0x0F, 0xB6, 0xB5, 0xB4, 0xB3 },
36+
{ 0x05, 0xB6, 0xB5, 0xB4, 0xB3 } };
3737

3838
// role variable is used to control whether this node is sending or receiving
3939
char role = 'R'; // integers 0-5 = TX node; character 'R' or integer 82 = RX node
@@ -183,9 +183,8 @@ void setRole() {
183183
payload.nodeID = role;
184184
payload.payloadID = 0;
185185

186-
// Set the address on pipe 0 to the RX node.
187-
radio.stopListening(); // put radio in TX mode
188-
radio.openWritingPipe(address[role]);
186+
// set the TX address of the RX node for use on the TX pipe (pipe 0)
187+
radio.stopListening(address[role]); // put radio in TX mode
189188

190189
// According to the datasheet, the auto-retry features's delay value should
191190
// be "skewed" to allow the RX node to receive 1 transmission at a time.

0 commit comments

Comments
 (0)