nRF24L01+ FlushRx ?

As far as I can see one of the best ways to screw up communications is by not fully emptying the RX buffers. If they get full communication stops.

I believe this was the cause of the Arduino appearing to freeze after several messages were received when I was sending messages of the wrong size (compared to what the receiver was expecting). I have since realized my mistake and corrected that.

However, as a general safeguard it seems like it may be a good idea to flush the RX buffer from time to time. The TMRh20 library has a function FlushTx but not FlushRx, however that is easily overcome.

What I don't understand is the relationship between the nRF24 Flush_Rx command and the existence of the different pipes, each of which may contain received data. The Flush_Rx command does not specify any particular pipe. Does that mean that it flushes all data from all pipes?

...R

Robin2: What I don't understand is the relationship between the nRF24 Flush_Rx command and the existence of the different pipes, each of which may contain received data. The Flush_Rx command does not specify any particular pipe. Does that mean that it flushes all data from all pipes?

Yes. The pipe the packet was received on is probably part of the internal packet structure.

flush_rx has been a part of startListening and stopListening, but is commented out currently. It's kind of weird that flush_rx is private. I think all the private functions should be protected at least, some - like flush_rx - should be public.

I never encountered a lockup from full rx buffers and I think new packets should overwrite the oldest, but I never verified that.

Whandall: I never encountered a lockup from full rx buffers and I think new packets should overwrite the oldest, but I never verified that.

Thanks. I figured you may have an answer.

To be honest I am only assuming the lockup was due to a full buffer. Now that I am sending the correct size of message it is not locking up. And I do know it is the RX end that was locking up because I could easily reset the TX end.

At other times I have noticed occasional unexplained lockups on other projects. But they never occurred at a convenient time for debugging or often enough to make a solution essential. (Or maybe I am lazy)

...R

I run the chip with enableDynamicPayloads always, so a packet size mismatch will never occur. Maybe that's the reason why I never encountered a full buffer.

I'm using the ackPayload feature and I think that automatically uses dynamic payloads.

However I just read a fixed number of bytes from the receive buffer into a struct. I guess I could allocate extra space in the struct for the full 32 bytes and read whatever might happen to be received.

...R

Any read should flush the read packet, regardless of the number of bytes read.

Whandall: Any read should flush the read packet, regardless of the number of bytes read.

I must re-read the datasheet. I thought it just took out bytes on a FIFO basis.

...R

It it were like a byte fifo, it would fail in the normal non dynamic setting, where most users don't read the full 32 bytes regularly that are sent always.

Now isn’t this interesting. I discovered that I had experienced this problem about 2 years ago but had completely forgotten.

But what’s really interesting is that the problem (of not reading the full message) occurs with dynamicPayloads and does not occur with fixed payloads.

To make things even more interesting, when using dynamicPayloads in the following examples, even though the payload size is 9 everything works fine whether I read 8 or 9 bytes. But if I only read 7 the problem occurs.

And the problem becomes evident on the Tx because it stops getting an acknowledgement after three messages. However the problem is clearly caused by the Rx because it goes away (for a further 3 messages) when the Rx is reset.

Demo programs

TX …

// python-build-start
// action, upload
// board, arduino:avr:uno
// port, /dev/ttyACM0
// ide, 1.8.6
// python-build-end


// SimpleTx - the master or the transmitter

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>


#define CE_PIN   9
#define CSN_PIN 10

const byte slaveAddress[5] = {'R','x','A','A','A'};


RF24 radio(CE_PIN, CSN_PIN); // Create a Radio

char dataToSend[9] = "ABCDwxyz";
char txNum = '0';
char dpldState = 'F'; // F for Fixed, D for dynamic


unsigned long currentMillis;
unsigned long prevMillis;
unsigned long txIntervalMillis = 1000; // send once per second
int count = 0;


void setup() {

    Serial.begin(9600);
    Serial.println("CSimpleTx Starting");

    radio.begin();
    radio.setDataRate( RF24_250KBPS );
    radio.setRetries(3,5); // delay, count
    if (dpldState == 'D') {
        radio.enableDynamicPayloads();
    }
    radio.openWritingPipe(slaveAddress);
}

//====================

void loop() {
    currentMillis = millis();
    if (currentMillis - prevMillis >= txIntervalMillis) {
        send();
        prevMillis = millis();
    }
}

//====================

void send() {

    bool rslt;
    rslt = radio.write( &dataToSend, sizeof(dataToSend) );
        // Always use sizeof() as it gives the size as the number of bytes.
        // For example if dataToSend was an int sizeof() would correctly return 2

    Serial.print("Data Sent ");
    Serial.print(dataToSend);
    if (rslt) {
        Serial.print("  Acknowledge received   MessageCount ");
        Serial.println(count);
        updateMessage();
        count ++;
    }
    else {
        Serial.println("  Tx failed");
    }
}

//================

void updateMessage() {
        // so you can see that new data is being sent
    txNum += 1;
    if (txNum > '9') {
        txNum = '0';
    }

    dataToSend[0] = txNum;
}

RX …

// python-build-start
// action, upload
// board, arduino:avr:mega:cpu=atmega2560
// port, /dev/ttyACM1
// ide, 1.8.6
// python-build-end

// arduino:avr:uno
// arduino:avr:mega:cpu=atmega2560

// SimpleRx - the slave or the receiver

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

#define CE_PIN  49
#define CSN_PIN 53

const byte thisSlaveAddress[5] = {'R','x','A','A','A'};

RF24 radio(CE_PIN, CSN_PIN);

bool newData = false;
byte payWidth;
byte dynPayWidth;
char dpldState = 'F'; // F for Fixed, D for dynamic

//===========

void setup() {

    Serial.begin(9600);
    Serial.println("CSimpleRx Starting");
    radio.begin();
    radio.setDataRate( RF24_250KBPS );
    if (dpldState == 'D') {
        radio.enableDynamicPayloads();
    }
    radio.openReadingPipe(0, thisSlaveAddress);
    radio.startListening();
    Serial.println("Setup complete");
}

//=============

void loop() {
    getData();

}

//==============

void getData() {
        // putting these here so they initialised every time
    char dataReceived[9] = "88888888"; // this must match dataToSend in the TX
    char dataRecB[9] = "BBBBBBBB"; // space for half the message
    char dataRecA[9] = "AAAAAAAA"; // putting them in this order so I can see if  A actually reads 8 bytes
    char dataRecC[9] = "CCCCCCCC";

    if ( radio.available() ) {
        radio.read( &dataReceived, 7 );
        //~ radio.read( &dataRecA, 4);
        //~ radio.read( &dataRecB, 9);

        payWidth = radio.getPayloadSize();
        dynPayWidth = radio.getDynamicPayloadSize();
        newData = true;
    }

    if (newData == true) {
        Serial.print("DynPayload ");
        Serial.print(dpldState);
        Serial.print("  Data received ");
        Serial.print(dataReceived);
        Serial.print("  dataRecA ");
        Serial.print(dataRecA);
        Serial.print("  dataRecB ");
        Serial.print(dataRecB);
        Serial.print("  dataRecC ");
        Serial.print(dataRecC);
        Serial.print("  PayWidth ");
        Serial.print(payWidth);
        Serial.print("  DynPayWidth ");
        Serial.print(dynPayWidth);
        Serial.println();
        newData = false;
    }
}

(I am using a Mega for the RX simply because I had an nRF24 attached to it).

As you can see I tried doing multiple reads to see if I could get the 9 bytes as 4 and 5 - but that doesn’t work. Reading 4 does not seem to remove any from the buffer.

…R

From the datasheet I would have expected the packet to vanish after any read.

R_RX_PAYLOAD Read RX-payload: 1 – 32 bytes. A read operation always starts at byte 0.
Payload is deleted from FIFO after it is read. Used in RX mode.

While reading the datasheet I discovered the rule for reception with full fifos too:

If a valid packet is found (by a matching address and a valid CRC)
the payload of the packet is presented in a vacant slot in the RX FIFOs.
If the RX FIFOs are full, the received packet is discarded.

I would have expected a different behaviour, but the above is simpler so they went with it.

I have not compared the TMRh20 library code with the datasheet and, at the moment I don't plan to as it would only distract me from my model railway project. I am easily distracted.

It is a bit tedious to update the program on the Attiny1634 that is in my model train but when I next do so I will set aside 32 bytes for receiving incoming data; always check the dynamicPayload size and always read that amount of bytes. Just to be on the safe side.

...R

Robin2: so I will set aside 32 bytes for receiving incoming data; always check the dynamicPayload size and always read that amount of bytes. Just to be on the safe side.

Seems to be the best strategy. ;)

I couldn’t resist having a look at the TMRh20 library code - the read_payload() function.

When the dynamicPayload is OFF it reads the bytes you request and then reads and dumps all additional bytes so that all 32 are removed. It does the latter by calculating blank_len

But when dynamicPayload is ON it sets blank_len to 0 so if the number of bytes you request does not empty the buffer you run into a problem. It would probably be better if blank_len was calculated as the difference between the dynamicPayload size and the number of bytes requested.

I also think the datasheet is unclear where it says

Read RX-payload: 1 – 32 bytes. A read operation always starts at byte 0. Payload is deleted from FIFO after it is read.

You could read that as meaning that bytes are removed one by one. However it seems more likely that the full payload must be read. I also have the impression that a NULL character is treated as “empty”.

Thinking further about a “safety net” strategy maybe a better plan would be to check if dynamicPayload size is as expected, and if not, just read and dump the message. I think it would be less confusing by avoiding the need for an “artificial” struct containing space for unwanted (and unexpected) data.

…R

Robin2: But when dynamicPayload is ON it sets blank_len to 0 so if the number of bytes you request does not empty the buffer you run into a problem. It would probably be better if blank_len was calculated as the difference between the dynamicPayload size and the number of bytes requested.

That would be an enhancement of the library.

Robin2: I also have the impression that a NULL character is treated as "empty".

That would really be strange.

Whandall:
That would really be strange.

How else do you explain how my program works when I request 8 or 9 characters but not 7. The 9th character is a NULL.

I think this is how I would modify the start of the read_payload() function

uint8_t RF24::read_payload(void* buf, uint8_t data_len)
{
    uint8_t status;
    uint8_t* current = reinterpret_cast<uint8_t*>(buf);
    
    uint8_t paySize;
    if (dynamic_payloads_enabled) {
        paySize = getDynamicPayloadSize();
    }
    else
         paySize = payload_size;
    }
    if (data_len > paySize) {
        data_len = paySize;
    }
    uint8_t blank_len = paySize - data_len;

…R

PS. I added to Reply #12 while you were writing Reply #13

Robin2: How else do you explain how my program works when I request 8 or 9 characters but not 7. The 9th character is a NULL.

If you make it a not null it works the like, you don't have to read all of the packet, you can skip the last byte. If you request more than available, the last byte gets repeated.

If you read not enough, available will stay true, so one is in trouble anyway.