RF24 inconsistencies on linux [needs investigation]

Hello.
I previously posted here but I just found out the solution is wrong, so once again I need your help investigating this issue

Note that the project has moved to using a pair of raspberry pis (3B and 4) but I'm still posting here because:

  • code part is portable and relevant to any arduino mcu
  • previous post needs an update
  • the RF24 community seems most active in this forum

Here are the details:

  • I'm using RF24 from the official repo
  • The library examples seem to work correctly, along with the program I was writing in the previous post
  • Both pi's are running the same code
  • The correct packet is handed over to the radio, wrong bit data is received. It is kind of consistent though
  • Radio model is NRF24L01+PA+LNA with the 5V adapter board. Hardware side should be totally sorted for inconsistencies

Peeking into sent/received data bits
image

Interestingly, if roles are swapped:
image
The first 17 bytes look identical through multiple tries, while the last ones seem random.
But all bits are off

I uploaded my code and will be walking through it
Settings.h (982 Bytes)
radiohandler.h (1.1 KB)
radiohandler.cpp (4.0 KB)
packetshandler.h (1.5 KB)
packetshandler.cpp (3.7 KB)
tuntaphandler.h (654 Bytes)
tuntaphandler.cpp (5.0 KB)
main.cpp (1.3 KB)
CMakeLists.txt (2.5 KB)

Settings holds the radio settings, the issue are present with any combination I've tried

radiohandler is the radio-program interface class.
Holds the RF24 object and makes sure it is properly working.
How?

  • Checks if radio is connected in the loop function, if not, resets it
  • When resetting the radio all settings are used and then printed
    Receiver pretty settings

    Transmitter pretty settings

This class holds the write and read functions and the radiopacket struct:
Note: write and writeFast produces same output

radiopacket* radiohandler::send(radiopacket *rp) {

	std::cout << "Packet OUT: " << radioprintchararray((uint8_t*)rp,32);
	if (radio->writeFast(&rp, 32)) {
        // Not sure what to do with this yet
        }

	if(radio->txStandBy()){
		cout << "Successful\n";
		return read();
	}else{
		cout << "Failed\n";
		return nullptr;
	}
}

// Checks if radio is connected and returns nullptr if nothing is read
radiopacket* radiohandler::read() {
	if (!radio->isChipConnected()) {
		resetRadio();
	}

// Note: this->data is instanced in the class constructor (data = new radiopacket;), is always reused and never deleted
	if (radio->available()) {
		radio->read(this->data, 32);
		std::cout << "Packet IN: " << radioprintchararray((uint8_t*)data,32);

		return data;
	}
	return nullptr;
}

packetshandler is the class handling the packets to be sent and recomposes the ones received.
Handles the radiopacket queue, then in the loop function hands them over in order to the radiohandler class and deletes the radiopackets after they're sent.
Also reads for incoming radiopackets and recomposes them to a larger message, which then is sent to the system through the c->writeback line

Everything is run from a third class tuntaphandler called from main (handles TUN/TAP I/O)

void tuntaphandler::TunnelThread() {

	this->running_ = true;

	std::thread t([&] {
		uint8_t buffer[mtu + (mtu / 2)];
		while (this->running_) {
			/* Note that "buffer" should be at least the MTU size of the interface, eg 1500 bytes */
			int nread = read(tunnel_fd_, buffer, sizeof(buffer));
			if (nread < 0) {
				perror("Reading from interface");
				close(tunnel_fd_);
				exit(1);
			}

			/* Do whatever with the data */
			printf("Read %d bytes from device %s\n", nread, "arocc");
// Commented this out for testing the custom packet shown in the pictures
			//pth.handlepacket(nread, buffer);

			//write(tunnel_fd_, buffer, nread);
		}
	});
	t.detach();

	while (running_) {
// pth refers to the packethandler instance
		pth.loop();

	}

}

void tuntaphandler::writeback(int size, uint8_t *data) {
	int n = write(tunnel_fd_, data, size);
	if (n != size) {
		cout << "Data written is not the same as sent\n";
	}
}

I also attached my CMakeLists.txt for error checking

Thank you

Do you get the exact same issues with Arduinos ?

Yes, I used to have the same issue in the previous post

Some more info

I moved some radio-related code from the class to main, same project, code stolen from radiohandler class but moved into main

radiopacket testpacket { (uint8_t) 0xE2 };

RF24 radio(25, 0);

char radioppppprintchararray(uint8_t *data, int len) {

	cout << "\n";
	for (int i = 0; i < len; i++) {
		bitset<8> x(data[i]);
		cout << x << " ";
		if (((i + 1) % 7 == 0) && (i + 1) != 0)
			cout << "\n";
	}
	cout << "\n";

	return ' ';
}

void justtest(bool primary) {

	while (!radio.begin(Settings::ce_pin, 0)) {
		cout << "Radio can't begin\n";
	}

	radio.setRetries(Settings::radio_delay, Settings::radio_retries);
	radio.setChannel(Settings::channel);
	radio.setCRCLength(Settings::crclen);
	radio.setAddressWidth(3);
	radio.setPALevel(RF24_PA_MAX);
	radio.setDataRate(RF24_2MBPS);
	radio.setAutoAck(Settings::auto_ack);

	// to use ACK payloads, we need to enable dynamic payload lengths (for all nodes)
	if (Settings::dynamic_payloads)
		radio.enableDynamicPayloads();  // ACK payloads are dynamically sized
	else
		radio.disableDynamicPayloads();

	// Acknowledgement packets have no payloads by default. We need to enable
	// this feature for all nodes (TX & RX) to use ACK payloads.
	if (Settings::ack_payloads)
		radio.enableAckPayload();
	else
		radio.disableAckPayload();


	if (primary) {
		radio.openReadingPipe(1, '1');
		radio.openWritingPipe('0');
	} else {
		radio.openReadingPipe(1, '0');
		radio.openWritingPipe('1');
		radio.startListening();
	}

	radio.printPrettyDetails();

	if (primary) {
		radio.write(&testpacket, 32);
		cout << "OUT: " << radioppppprintchararray((uint8_t*) &testpacket, 32);
	}

	radiopacket rp;
	bool running = true;
	while (running) {

		if (radio.available()) {
			radio.read(&rp, 32);
			cout << "IN: " << radioppppprintchararray((uint8_t*) &rp, 32);
		}

	}

}

int main(int argc, char **argv) {

	if (argc < 2) {
		printf(
				"You need to specify primary or secondary role (./thisprogram 1 or 2)");
		return 0;
	}

	for (int i = 0; i < 31; i++) {
		testpacket.data[i] = (uint8_t) (0x0);
	}
	testpacket.data[0] = (uint8_t) (0x55);
	testpacket.data[1] = (uint8_t) (0x55);

	printf("Radio is: %d\n", (int) argv[1][0]);

	bool primary = argv[1][0] == '1';
	cout << "Radio is " << (primary ? "Primary" : "Secondary") << endl;

	justtest(primary);
	return 0;
}

And works as expected. Code and most calls are the same except this is done in a separate class/file.
Where is the issue?

I moved all classes text inside main file along with the functions extracted from the radio class straight into main, the problem arises again.

Issue seems to be bound to calls coming from c++ classes. Not sure how to investigate further

Looks like I was pointing to the pointer memory address and transmitting the neighbours

I came to the same conclusion on the GitHub issue you briefly opened.
Although I used comments in the code block to explain this (instead of markdown syntax within the literal code block).

Also, you never mention in the previous/related thread what Arduino board you were using. Some boards' chips use differently sized allocation of certain data types, namely the ESP8266 or ESP32. This can cause the struct being transmitted to be "misinterpreted" if the bytes aren't aligned (in memory) in a cross-compatible way.

Looks like this was the issue. I've been seriously looking into it for the past 2 days.

In the previous thread I was using an arduino nano, but in that case even the examples didn't seem to do well. --- maybe it could have been different representation, or something else entirely, I was just printing the data in that case
So I'm not sure if that was the same kind of error, maybe rewriting it again after reinstalling the library did the trick.
It is silly yet not too easy to spot.

Happy to have fixed it, thank you for taking your time to read


To avoid opening further threads, I'll test again with the BCM driver as soon as the program is finished.

  • The SPIDEV driver is the fastest and most compatible with any Linux OS. It shouldn't require sudo permission as long as the user is granted access to the dialout group (which is baked into the RPi OS' pi user).
  • The BCM driver is significantly slower and will require sudo permission to execute any program using it. The BCM2835 library also doesn't work well when using SPI and I2C together in the same app.
  • MRAA isn't as well maintained since Intel got out of the maker game.
  • We no longer support nor recommend using the wiringPi driver since the original library's author discontinued development (even though Orange Pi still distributes their own port of the original library).
  • For the time being we're using pigpio for IRQ detection/handling on Linux (despite chosen driver). Thus, the newer driver for the pigpio library; we used to rely on wiring Pi for IRQ stuff.
  • The LittleWire driver is very hardware specific and hasn't compiled successfully for years.

Hi, do you have any idea why the radios would read their own outbound packets (both using writefast or writeackpayload).
With these settings I'm getting failed TX and self-reading the loaded ackpayloads on RX.

Long code post, I've included:
Settings
Loop function
--> read
--> send
--> setAckPayloads

// Note, setting radio_delay=0, radio_retries=2 causes RX to self read packets
// While radio_delay=15, radio_retries=15 causes failure sending and no self read
static uint8_t channel = (uint8_t)(110);
static uint8_t radio_delay = 15; 
static uint8_t radio_retries = 15;

static rf24_crclength_e crclen = RF24_CRC_8;
static bool auto_ack = 1;
static bool dynamic_payloads = 1;
static bool ack_payloads = 1;
static uint8_t payload_size = 32;
static rf24_datarate_e data_rate = RF24_2MBPS;
static rf24_pa_dbm_e power = RF24_PA_LOW;
static const uint8_t addressbytes = 5;

static uint8_t address1[addressbytes+1] = "1Node";
static uint8_t address2[addressbytes+1] = "2Node";

Writing the addresses

// Copying from caller
memcpy(this->read_address, read_address,3);
memcpy(this->write_address, write_address,3);
....
radio->openReadingPipe(1, this->read_address);
radio->openWritingPipe(this->write_address);

Send function

radiopacket* radiohandler::send(radiopacket *triplet) {
	//radio->flush_tx();
	for (int i = 0; i < 3; i++) {
		//radio->startFastWrite(&triplet[i], 32, 0, i == 2);
		radio->writeFast(&triplet[i],32);
	}

	int i = 0;
	static int retrylimit = 1;

	while (!radio->txStandBy() && i<retrylimit) {
		radio->reUseTX();
		i++;
		printf("Failure sending %d\n",i);
	}
	if(i<retrylimit){
		radio->flush_tx();
		return read();
	}else{
		printf("\t\t\t\tDelivery failed\n");
		radio->flush_rx();
		radio->flush_tx();
		return nullptr;
	}

}

Read function

radiopacket* radiohandler::read() {

	static radiopacket triplet[3];

	if (!radio->isChipConnected()) {
		resetRadio();
	}

	uint8_t i = 0;
	if(radio->rxFifoFull()){
		radio->read(triplet,32*3);
		i=3;
	}else{
		while(radio->available()){
			radio->read(&triplet[i++],32);
		}
	}

	if(i==0) return nullptr;

	for(int j = i; j < 3; j++){
		memcpy(&triplet[j],&emptypacket,32);
	}
	return triplet;
}

AckPayloads loader function

void radiohandler::setnextackpacket(radiopacket* triplet) {
	if (Settings::ack_payloads) {
		static radiopacket p1, p2, p3;

		memcpy(&p1, &triplet[0], 32);
		memcpy(&p2, &triplet[1], 32);
		memcpy(&p3, &triplet[2], 32);

		radio->flush_tx();

		if (radio->writeAckPayload(1, &p1, 32)) {

		} else {
			printf("Ack payloads full before finishing, not handled well\n");
		}
		if (radio->writeAckPayload(1, &p2, 32)) {

		} else {
			printf("Ack payloads full before finishing, not handled well\n");
		}
		if (radio->writeAckPayload(1, &p3, 32)) {

		} else {
			printf("Ack payloads full before finishing, not handled well\n");
		}
	}
}

RX pretty settings printout

================ SPI Configuration ================
CSN Pin			= /dev/spidev0.0
CE Pin			= Custom GPIO25
SPI Frequency		= 10 Mhz
================ NRF Configuration ================
Channel			= 110 (~ 2510 MHz)
Model			= nRF24L01+
RF Data Rate		= 2 MBPS
RF Power Amplifier	= PA_LOW
RF Low Noise Amplifier	= Enabled
CRC Length		= 8 bits
Address Length		= 5 bytes
Static Payload Length	= 32 bytes
Auto Retry Delay	= 4000 microseconds
Auto Retry Attempts	= 15 maximum
Packets lost on
    current channel	= 0
Retry attempts made for
    last transmission	= 2
Multicast		= Disabled
Custom ACK Payload	= Enabled
Dynamic Payloads	= Enabled
Auto Acknowledgment	= Enabled
Primary Mode		= RX
TX address		= 0x00056f4e32
pipe 0 (closed) bound	= 0x00056f4e32
pipe 1 ( open ) bound	= 0x00006f4e31
pipe 2 (closed) bound	= 0xc3
pipe 3 (closed) bound	= 0xc4
pipe 4 (closed) bound	= 0xc5
pipe 5 (closed) bound	= 0xc6

TX pretty settings printout

================ SPI Configuration ================
CSN Pin			= /dev/spidev0.0
CE Pin			= Custom GPIO25
SPI Frequency		= 10 Mhz
================ NRF Configuration ================
Channel			= 110 (~ 2510 MHz)
Model			= nRF24L01+
RF Data Rate		= 2 MBPS
RF Power Amplifier	= PA_LOW
RF Low Noise Amplifier	= Enabled
CRC Length		= 8 bits
Address Length		= 5 bytes
Static Payload Length	= 32 bytes
Auto Retry Delay	= 4000 microseconds
Auto Retry Attempts	= 15 maximum
Packets lost on
    current channel	= 0
Retry attempts made for
    last transmission	= 15
Multicast		= Disabled
Custom ACK Payload	= Enabled
Dynamic Payloads	= Enabled
Auto Acknowledgment	= Enabled
Primary Mode		= TX
TX address		= 0x00056f4e31
pipe 0 ( open ) bound	= 0x00056f4e31
pipe 1 ( open ) bound	= 0x00006f4e32
pipe 2 (closed) bound	= 0xc3
pipe 3 (closed) bound	= 0xc4
pipe 4 (closed) bound	= 0xc5
pipe 5 (closed) bound	= 0xc6

My loop function

// rdh is radiohandler class
void packetshandler::loop() {

	if (primary) {
// Prepare radiopacket* triplet, send through the radio, process ack data if any
		processpacketin(rdh.send(preparenextsendpack()));
	} else {
		static radiopacket *trp = nullptr;
// Read data, if any prepare the next radiopacket* triplet and set as ack payload
// Then process the read packets
// The very first payloads are set in setup function
		if ((trp=rdh.read())) {
			if (Settings::ack_payloads) {
				printf("Setting AckPayloads\n");
				rdh.setnextackpacket(preparenextsendpack());
			}
			processpacketin(trp);
		}
	}
}

The radio doesn't read from the TX FIFO unless it is preparing a packet to send; even in that case, there is no way to get the TX FIFO data (by accident or otherwise radio misuse). There is no way for the user to read data in the TX FIFO.

You seem to have a bad habit of presuming what the problem is. This causes you to only disclose details that you think are related, while the actual problem (one you haven't considered) can easily be hidden to troubleshooters/helpers like me.

I have a feeling that your code is just showing you old buffer data without resetting it (like with memset(&triplet, 0, 32)), but you didn't disclose any code that might hint that. I would suggest analyzing the use of your buffers, specifically this triplet pointer. Compiler warnings are not always insignificant enough to ignore (especially if you don't understand what they mean).

2 Likes

Also, radio_delay=0 essentially tells the radio to wait only 250 μS (microseconds) for an auto-ack packet. This is explicitly discouraged in the nRF24L01 datasheet when using ACK packets with custom/user payloads attached:

If ACK packet payload is activated, ACK packets have dynamic payload lengths and the Dynamic Payload Length feature should be enabled for pipe 0 on the PTX and PRX. This is to ensure that they receive the ACK packets with payloads. If the payload in ACK is more than 15 byte in 2Mbps mode the ARD must be 500μS or more, and if the payload is more than 5byte in 1Mbps mode the ARD must be 500μS or more.

Note, ARD stands for "Automatic Retry Delay".

1 Like

You are correct, my troubleshooting tends to be inexact at first
I'm sorry, I actually felt like posting the whole code each time would be disrespectful

Either way, the issue was in this line:

memcpy(&triplet[j],&emptypacket,32);

I was reusing and editing emptypacket for the outward packets.


Perhaps your time is put into much better use by avoiding the XY problem:
this code is part of a TUN interface; I'm trying to stream large volumes of data with as low latency as possible.
I'm interested in both cases: one way and two way highest possible throughput streams

I already set up the TUN interface; split, queue and recomposition of packets to be sent through the radios (with 1 byte overhead) and in my tests could hit 900kbps at the very best (iperf) but what I cannot really do is setting up a proper and reliable radio communication.

To achieve this, I've been reading all the related examples.
Some use writeFast() and reUseTX(), others use startWriteFast().
Then in interruptConfigure.cpp the ack payload tx fifo is filled sequentially. (ps. nice work)

So this is what I came up with: (look for the functions read, handlesend, handleackpackets)
radiohandler.cpp (6.2 KB)
radiohandler.h (1.2 KB)
A couple notes:
handlesend and read are called in TX loop, read and handleackpackets are called in RX loop

packetlinereference is a deque object, containing pointers to the packets to be sent, these are loaded asynchronously using push_back()

use_empty_packets means when no packets are queued, TX will write empty packets to get RX ackPayloads. If possible, RX should too

yes, the check at line 38 shouldn't be there at all

read() return is as following: nullptr nothing was read, or {packet, packet/nullptr, packet/nullptr}

Is there a better way to handle this?

Interrupts are also available, but unused atm

You could do something like what RF24::available(*pipe_num) does... Take a parameter that is a pointer to allocated buffer(s) and only return an integer.

  • If the return integer is 0, then the parameter value passed by reference is unaltered.
  • If the returned integer is greater than 0 (& likely less than 4), then the parameter value passed by reference is altered to have RX data.

I'm not well versed with TAP/TUN interfaces; that's really @TMRh20 area of expertise. I believe the RF24Ethernet and RF24Gateway libraries (for Arduino and Linux platforms respectively) use TAP/TUN interfaces, but they are built on top of RF24Network and RF24Mesh libraries (which do not support the use of auto-ACK payloads).

1 Like

Sure that would save a couple iterations down the line and I'll implement it.

The TUN interface is all sorted, each packet is processed and pushed into the deque to be written through the radio
As result, there will be several radiopacket to be written sequentially.
So I can fill the TX buffers. Depending on data 1, 2 or 3 packets will be loaded

I'm wondering if handling them correctly just means:
on RX - calling writeAckPayload() until it reports false
on TX - calling startWriteFast() 3 times with packets or empty packets, or perhaps writeFast() until it blocks
Reading up to 3 packets at a time, or processing immediately one at a time whenever available() is true

Is there a way to know which acks and which packets have been delivered when writing 3 at a time?
like isFifoFull() will mean 3/3 have been successful and next 3 packets will be loaded for sure

If all ACK payloads are configured to go to 1 TX device, then it should be much simpler to handle. Although, if ACK payloads go to different TX devices, then there is a lot more to consider; ie the TX FIFO limited capacity and it would be best if the order of received packets' origins match the order of loaded ACK payloads' destination.

The only way to know if ACK payloads were delivered is by monitoring the TX FIFO occupancy or by handling the tx_ok (aka "data sent") IRQ flag in RX mode (see whatHappened() and maskIRQ()). A payload that was successfully sent will be popped from the TX FIFO. RF24::reUseTX() does prevent a payload being popped from the TX FIFO, but there are stipulations about that (read the docs link). I doubt that reUseTx() is effective at all in RX mode, but I never tried that myself.

You also might be interested in `write`, `writeFast` and `writeBlocking` methods confusions · Issue #816 · nRF24/RF24 · GitHub for reference.

All in all, handling packets in bulk would be feasible for 1 to 1 communication. But handling packets individually would be preferable for 1 to many communication.

1 Like

Thank you for your patience, almost done with the program; I might publish it soon
Still got a couple questions

This is strictly a 1 to 1 bridge, or remotely, a one way multicast, so as you mentioned it is very feasible to either find out the missing packets and request them
OR just push everything out UDP style (which is the current state of the program, but as 2 way communication)


How is behaviour defined if a packet is delivered while ack is being loaded?

Sometimes always the same packets are spammed
Perhaps this can be handled using failureDetected
documentation mentions:
Reading from radio: Available returns true always
Fixed by adding a timeout to available functions by the user. This is implemented internally in RF24Network.
Not too sure how it will translate here


As far as I can understand, the best radio performance code wise, would be achieved:
Load packets while (get_status() & _BV(TX_FULL)) is 0.
this is private though, so the second best approach I can think of:

TX LOOP
while(writeFast(    /* load the next packet  */   )){
    if(available()){ 
        //handle received packet(s) 
    }
// Delete old packet(s) from memory
}
txStandBy(); // Not required due to auto-ack
while(available()) // Handle rest of the packets
RX method (Caller can be loop or interrupt handler if( tx_ok OR rx_ready )   )
while(available()){
// Handle received packet(s)
    while(writeAckPayload(    /* Load next ack packet reference */    )){
        // Delete old packet(s)
    }
}
// This line might not be needed
while(writeAckPayload(    /* Load next ack packet reference */    )){
    // Do everything again in case TX buffer is not filled
}
// Reconfigure IRQ

And in case packets are found to be missing, one control packet should be sent to request them

If writeAckPayload() does not return before the packet is received, then the ACK payload is not used.


I was never a big fan of keeping the status byte private. It is only that way because that's how maniacbug originally designed it. In my Circuitpython_nRF24L01 library, I exposed the status byte via several read-only class properties. You could expose it in your code by inheriting from RF24 class and adding a public getter:

class MyRF24 : public RF24 {
    uint8_t getStatus() { return get_status(); };
    bool isTxFull() { return get_status() & _BV(TX_FULL); };
}

This typically means the ACK never made its way back to the sender. RF24Network can detect duplicate payloads by reading the RF24NetworkHeader.id (which is incremented on every new payload). As for an implemented timeout to a while available() loop, you can see how that is done in source.

1 Like

Hi, I'm still working on this.
Currently trying different transmission strategies and ruling out logic problems on my code.
Everything is public and viewable in my GitHub repo

In the meantime, I just wanted to wish you happy holidays!

Here's the update (questions at the end)

I completely rewrote the program and was able to set up a two way communication running at about 400Kbps (iperf), with a special selective repeat algorithm, hoping you're happy to see the help finally paid off :blush:
Also soldered a multi-purpose stripboard housing two radios and a 4700uF capacitor in case power was a problem

Done some throughput measurements you might find interesting:
One radio per device, fixed 32 bytes packet size, 1 byte CRC, maximum SPI speed 6.5MHz

Looking online, the SPI speed of 4MHz should allow a maximum datarate of 2Mbps, reality looks different atm, I couldn't reach the maximum expected throughput even if running at 5MHz
(suspecting a bottleneck in my code, or in the raspberry board)

Collected info
Air Data Rate Auto Ack Bytes OUT Bytes IN Notes Expectations accounting overhead
2Mbps yes - no ack payloads as received 520Kbps No errors
2Mbps no 1.2Mbps 1.1Mbps peak, 800Kbps avg Some errors, mostly nonsequential packets rather than invalid Can't say: algorithm / CPU performance / SPI speed might be a bottleneck
1Mbps yes - no ack payloads as received 380Kbps No errors
1Mbps no as received 700Kbps No errors Perfect match with 30% overhead (700Kb data, 300Kb overhead). Radio is 100% used
250Kbps yes - no ack payloads as received 145Kbps No errors
250Kbps no as received 200Kbps No errors 10% better than expected, or 20% under air data rate
1Mbps yes - with ack payloads 230Kbps - bidirectional Some errors, mostly nonsequential packets rather than invalid
250Kbps yes - with ack payloads 10Kbps - bidirectional Some errors, mostly nonsequential packets rather than invalid

This table induces me into thinking it's possible to achieve larger than double speed using two radios per device

I ended up using this sequence (same code used to collect the data minus ack payloads, adapted for the double radio setting)

    while (fill_buffer_tx()) {} // returns false when TX fifo is loaded via writeFast
	send_tx(); // Sends everything once using txStandBy()
	while (read()) {} // Read all packets received

More specifically

Implementation
bool RF24DualRadio::fill_buffer_tx() {

    // Check if radio FIFO TX is full, if yes, skip.
    // Also check if the next packet is available
	if (radio_1->isFifo(true, false)) {
		return (false);
	}

    // Load the next packet, no packet is nullptr
	RadioPacket *rp = nullptr;
	if (!(rp = next_packet()))
		return (false);

	if (radio_1->writeFast(rp->data, rp->size, false)) {
		radio_bytes_out += rp->size;
		return (true);
	} else {
		return (false);
	}

}
bool RF24DualRadio::send_tx() {

	if (radio_1->txStandBy()) {
		// Transmit was successful, everything is okay
		sum_arc += radio_1->getARC();
		count_arc++;
		return (true);
	} else {
        // Either flush tx or keep retrying, not sure yet
		//radio->reUseTX();
		//radio->flush_tx();
		return (false);
	}

}
bool RF24DualRadio::read() {

	bool result = false;
	uint8_t pipe = 0;

	static RadioPacket *rp = new RadioPacket;

	if ((result = (radio_0->available(&pipe)))) {

		rp->size = radio_0->getDynamicPayloadSize();
		radio_0->read(rp, rp->size);

		switch (pipe) {
		// Actions based on pipe
		}
		packets_in++;

	}

	return (result);
}
  • I believe this code is close to the maximum speed of execution (multithreading aside for the 2 radios per device configuration). Let me know if there are any improvements I could make

  • Sometimes both radios in both configurations seem to receive corrupted packets using this code, like reading from pipe 7 or 5 when only pipe 1 and 2 are opened, it's strange though, since CRC is enabled.
    Current behaviour is reset the radio when weird packets show up, seems to happen rather often on higher throughput settings (like 2Mbps no ack) and resetting doesn't prevent these from showing up again

At the moment I'm not checking for duplicate packets (issue mentioned previously), it's possible these might be flying under the radar outside of the throughput test