[HELP] nrf24l01p + pro mini serial bridge data loss

Hello, I’m trying to build a serial bridge with a pair of NRF24L01p (Ebyte E01-2G4M27D) and Pro Minis (3V3, 8Mhz + FTDI USB->Serial).

The code works fine but if I push bytes at full speed I get a lot of garbage on the other end (e.g. half of the payload is missing), I’ve tried matching the baud rate with the radio speed (250000) but it only made the matters worse, and I’ve also tried slower baud rates (125000) but nothing helped, the 500k baud rate is the most stable I’ve been able to achieve. (The 8Mhz cpu won’t handle higher baud rates I guess). I suspect that some buffers somewhere get overfilled and data gets lost but I’m unable to find which buffer exactly :frowning:
I’m using this contraption to send TCP/IP via a SLIP interface on Linux machines, with TCP above that I get ~40kbit/s max (L1 speed).

Any help would be appreciated!

Here’s the code that I have, it’s identical for both stations only the pipe addresses flip:

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

RF24 radio(7, 8);  // CE, CSN

const byte tx_address[6] = "00002";
const byte rx_address[6] = "00001";

byte txbuf[32];
byte rxbuf[32];

void setup() {
  radio.begin();
  radio.enableDynamicPayloads();
  radio.openWritingPipe(tx_address);
  radio.openReadingPipe(1, rx_address);
  radio.setChannel(1);
  radio.setPALevel(RF24_PA_MIN);
  radio.setDataRate(RF24_250KBPS);
  radio.setAutoAck(true);
  radio.setCRCLength(RF24_CRC_16);
  Serial.begin(500000);
  radio.startListening();
}

void loop() {
  if(radio.available()) {
    int p_size = radio.getDynamicPayloadSize();
    if(p_size > 0 && p_size <= 32) {
      radio.read(&rxbuf, p_size);
      Serial.write(rxbuf, p_size);
    }
  }
 
 if(Serial.available()) {
  size_t i = 0;
  while(Serial.available()) {
    txbuf[i] = Serial.read();
    if(i==31) {
      // array starts from 0 to 31 == 32 bytes
      radio_tx(32);
      i = 0;
      continue;
    }
    i++;
  }
  // send remaining bytes that are less than 32B chunk
  if (i>0) {
    radio_tx(i);
    i = 0;
  }
 }
}

void radio_tx(size_t len) {
  radio.stopListening();
  radio.write(&txbuf, len);
  radio.startListening();
}

I guess my first approach would be to get a statistic out of it, namely average transmission packet size. If that looks too small, then put more effort into creating full buffer loads (32 bytes) before sending, maybe using two buffers and switching between them.

I think you're probably sending a lot of tiny packets, because it looks like a packet is sent any time there is any interval at all between received characters. You should set a timer to continue assembling a full packet until a time-out occurs.

As said, try accumulating more than a single byte per packet. Although your code looks to be structured to do this, it is unlikely to ever happen because single bytes are processed and transmitted as fast as they are received.

Is this unidirectional, or bidirectional communications?
If bidirectional and asynchronous, you may run into problems.

The NRF24 can only receive or transmit at any one instant, it cannot do both at the same time. So if one end is continuously transmitting, and the other end tries to transmit at the same time, it will result in potentially lost packets (both ends).

If the protocol is is unidirectional or synchronous you shouldn’t have this problem. You can run an Arduino boot loader over NRF24 modules (bidirectional synchronous) quite happily.

First of all, thank you all for your suggestions!

I changed the code so now it only transmits via radio only if the buffer has 32B or more (since I’m constantly pushing bytes over the radio I don’t really need a timeout here, the buffer is always filled) and it actually helped a little bit, now I’m getting like 50Kbit/s. I’ve also soldered pin 10 to the CTS pin of the FTDI chip and trying to control the flow this way, but it gave no speed increase at all.

Is this unidirectional, or bidirectional communications?
Mostly unidirectional

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#define CTS_PIN 10

RF24 radio(7, 8);  // CE, CSN

const byte tx_address[6] = "00002";
const byte rx_address[6] = "00001";

byte txbuf[32];
byte rxbuf[32];

void setup() {
  radio.begin();
  radio.enableDynamicPayloads();
  radio.openWritingPipe(tx_address);
  radio.openReadingPipe(1, rx_address);
  radio.setChannel(1);
  radio.setPALevel(RF24_PA_MIN);
  radio.setDataRate(RF24_250KBPS);
  radio.setAutoAck(true);
  radio.setCRCLength(RF24_CRC_8);
  Serial.begin(500000);
  radio.startListening();
  pinMode(CTS_PIN, OUTPUT);
}

void loop() {
  if(radio.available()) {
    int p_size = radio.getDynamicPayloadSize();
    if(p_size > 0 && p_size <= 32) {
      radio.read(&rxbuf, p_size);
      Serial.write(rxbuf, p_size);
    }
  }
 
 if(Serial.available()>=32) {
  set_cts();
  size_t i = 0;
  while(Serial.available()) {
    txbuf[i] = Serial.read();
    if(i==31) {
      // array starts from 0 to 31 == 32 bytes
      radio_tx(32);
      i = 0;
      continue;
    }
    i++;
  }
  // send remaining bytes that are less than 32B chunk
  if (i>0) {
    radio_tx(i);
    i = 0;
  }
  clear_cts();
 }
}

void radio_tx(size_t len) {
  radio.stopListening();
  radio.writeFast(&txbuf, len);
  radio.txStandBy(1);
  radio.startListening();
}

void set_cts() {
  digitalWrite(CTS_PIN, HIGH);
}

void clear_cts() {
  digitalWrite(CTS_PIN, LOW);
}

pcbbc:
The NRF24 can only receive or transmit at any one instant, it cannot do both at the same time.

While this is true, the NRF can work in a bidirectional way by attaching up to 32 bytes to the ack packet,
without changing between tx and rx mode under program control.

Tx side controls the packet flow (only this station initiates a packet exchange),
so you have to handle empty packets to allow rx -> tx data flow without a tx -> rx data flow.

Robin2's tutoral has an example of the basic technique.