RF24 library with nrf24l01+. Can you receive messages while transmitting?

Hello,

I'm trying to use nrf24l01+ transceivers to let a arduino nano communicate with 5 other nanos. The first one is connected to a computer and sends a message to the 5 other nanos when triggered through usb serial (each message on it's own pipe). When the other nanos receive this message they read their sensor values and transmit the data back to the base.

After reading all the info i could find i thought that the nrf24l01 can receive messages on pipes 1-5 while transmitting data on pipe 0. This article in particular implies that you can, as it explicitly says that you can't with pipe 0, Maniacal Bits: RF24 - Avoiding RX Pipe 0 For Enhanced Reliability.

But this doesn't seem to be the case. I assumed that calling the stopListening() method of the RF24 library would only stop listening on pipe 0 but it seems to cause the nrf24l01 to completely stop listening on all pipes until startListening() is called again. This means that I miss the messages from the first arduinos while the base finishes transmitting to the other arduinos (especially if the last one times out on send).

Am i doing something wrong or is this the expected behaviour of the RF24 library and nrf24l01? I've attached my code to help clarify.

Any help you can provide will be greatly appreciated :slight_smile:

Thanks

sensor_node.ino (4.72 KB)

base_node.ino (4.14 KB)

Well technically, that is the expected behaviour. The radio modules switch between two primary modes, being primary transmitter and primary receiver.

The radio will not receive any normal payloads while in TX mode, and will not send any normal payloads while in RX mode, however, the radios do support something called ACK payloads.

When a payload is successfully transmitted, the receiving radio responds back with an ACKnowledgement payload. Making use of the ack payload feature allows you to respond back with an ACK payload without havign to manually switch out of receiver mode, and the transmitter can be set to send until an appropriate response is received.

This makes for a much faster call-response scenario, and I believe would suit what you are doing much better than manually switching from TX to RX modes.

Take a look at this example: http://tmrh20.github.io/RF24/GettingStarted_CallResponse_8ino-example.html It may not work very well with the original library though.

Ok, that makes sense because that explains exactly what i'm seeing. I still don't understand what Maniacal Bits' blog post was about then. Why is pipe 0 any different to the others?

Yeah i figured i'd have to change to using ACK payloads. The problem with this is that the sensor has to have the value ready before it receives the request from the base and so it will be returning the previous reading instead of the current reading.

I could set up the sensor nodes to poll the sensor values so that the current reading is not very old when the request is received. Or i could possibly swap the roles around and have the sensors pushing the data to the base at the required interval and base using ACK payloads back.

Either way ACK payloads sound like the best way forward, I'll have to have more of a think about it :slight_smile:

Thanks for the clarification, I needed to make sure it wasn't just something i was doing wrong :smiley:

wormsquiggle:
Ok, that makes sense because that explains exactly what i'm seeing. I still don't understand what Maniacal Bits' blog post was about then. Why is pipe 0 any different to the others?

The nRF24l01 is a simplex device operation on a single frequency so it cannot transmit and receive at the same time.
When operating in auto_ack mode pipe 0 is used to receive the acknowledgement. That is why the rx_addr for that port is the same as the tx_addr as the ack packet is sent with the tx_addr. If you want to use pipe 0 to listen for a 6th transmitter you can but you will need to keep changing the rx_addr for pipe 0 depending on whether you are listening for the ack or the remote transmitter.

The nRF24l01 is really designed to be a N to 1 system not a 1 to N. When you transmit you are not directing the transmission to a particular receiver but are broadcasting a message. This message can be received by any receiver configured to receive that address. If you configure multiple receivers to receive the same address and have auto ack configured it will be a complete mess as all the receivers will return the ack, all with the same address, at the same time and the transmitter won't know what is happening.

I think, if you want a 1 to N system, the base could send out a trigger message but with auto ack turned off. Configure 6 sensors each with a pipe 1 address matching the base and with a tx_addr matching 1 of the pipe addresses of the base. Use a different delay between receiving the trigger and sending the data at each of the sensors. This will give you a lot more control and should only take a few milliseconds longer that using acks.

Thanks for your reply.

I had thought about using trigger messages with no ack like you suggest but i thought the reliability would suffer. My trigger messages also contain LED values for each sensor so this would make sending these values slightly harder as well. This is an option though if what i'm doing now doesn't work.

Currently i'm sending the trigger message to each sensor individually on it's own address with ack turned on. Then i'm waiting for a bit on each sensor before sending back the data to make sure the base is in receive mode again. For the base I've turned the send reties down to 5 at 250us so the longest the sending can take should be 1.5ms (for 5 sends including time to switch write addresses I measured it to be reliably under 10ms).

I'm haven't tried using ack payloads yet as my current method is simpler. But that will be the next step i think.

crofter:
Use a different delay between receiving the trigger and sending the data at each of the sensors.

Are you implying here that you need a different delay at each sensor because the radios can't receive data on multiple pipes simultaneously? If so then my method is less likely to work. I'm trying to save time by receiving from all 5 sensors at once. :~

wormsquiggle:
Are you implying here that you need a different delay at each sensor because the radios can't receive data on multiple pipes simultaneously? If so then my method is less likely to work. I'm trying to save time by receiving from all 5 sensors at once. :~

The "net" of devices are all operating on the same frequency. If 2 devices transmit data at exactly the same time the receiver will not be able to decode the data from either. Multiply that to 5 transmitters at the same time gives the system little/no chance of working. You need to configure your system to ensure that sensor do not try to send data at the same time. The nRF24l01 can detect if the channel is currently being used but I doubt it is good enough to be used as a protection mechanism.

Ok, that's a very useful piece of information! That will save me a lot of time. Thanks! :smiley:

Hi, in case anyone is wondering, I ended up using ack payloads to solve my problem.

Each sensor operates as PRX and writes an ack payload to the radio every 10ms (after flushing the previous payload in case it wasn't sent) so that it always has current values to send. Then the base operates as PTX and sends to each sensor sequentially receiving the ack payloads along the way.

This way you don't have to worry about multiple transmitters as it's only the base that ever transmits.

A nice side effect of this is that you are not limited to 5 receivers, you can have as many as you want because each receiver is only listening on one channel. Also it is very fast, I can send 10 byte packets and receive 14 byte packets from each of the 5 sensors in about 8ms total.

I also switched to use tmrh20's library. It seems to be the best at the moment and seemed to solve a problem i was having at the time
http://tmrh20.github.io/RF24/

Thanks for all the help :smiley:

Hi wormsquiggle,

I just have been thinking about the same problem BEFORE reading this post.

I've written a possible solution in this post:

http://forum.arduino.cc/index.php?topic=178511.msg1872563#msg1872563

which I quote here:

HI, I have found also the same problem as you.

I think about this:

  1. in the receiver node use writeAckPayload "more often" than the server send data (in this example, each 500 o 100 ms)
  2. in the server node, write data in a loop (for example with a 1000 ms delay)
  3. IT IS NECESSARY in the receiver node to call FLUSHTX before writeAckPayload (the TX FIFO only have a 3 level buffer)

I think that with this method you can get your last value (only for sensors, not for you want about writting back the data that you have read).

I don't know if you could "delay" the ackResponse in the RF24 module itself by changing some register.

Do you think that the method that I wrote is usefull to write back, for example, room's temperature ?

Best regards

I think you and me have finally tought about the same solution, haven't we ?

But I need some help, could you post or send me your sketch ? I'm noob and it will be better for me to see your sample.

Best regards

Hi Juan,

Below is code that i've grabbed out of my sketch. I've tried to keep it all mostly relevant to the subject so there are a lot of things that will stop it compiling. But it should give you the idea of how my code works. Sorry if i've missed out any important details.

RadioMessage is a struct i've defined in a header included by both sensor and base. Same with the mpu struct

Hope it helps :slight_smile:

Base code:

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

#include <my_common_types.h>

//#define DEBUG_PRINT
//#define DEBUG_PRINT_

// Set up nRF24L01 radio on SPI bus plus pins 7 (CE) & 8 (CSN)
// Make sure pin 10 is in output mode or SPI will go into slave mode.
RF24 radio(7,8);

RadioMessage radio_messages[max_num_sensors];

int payload_radio_length = sizeof(RadioMessage);
int payload_accel_gyro_length = sizeof(accel_t_gyro_union);

void setup()
{
  Serial.begin(115200);

  radio.begin();

  radio.setAutoAck(true);
  radio.enableAckPayload();
  radio.powerUp();
  //radio.setDataRate(RF24_250KBPS);
  radio.setDataRate(RF24_2MBPS);
  radio.setRetries(0,2); // Retry 2 times with 250us delay between retries.
  if (payload_accel_gyro_length > payload_radio_length)
  {
    radio.setPayloadSize(payload_accel_gyro_length);
  }
  else
  {
    radio.setPayloadSize(payload_radio_length);
  }
  radio.stopListening();

  Serial.println("Base started!");
}

void sendWifi()
{
#ifdef DEBUG_PRINT
  unsigned long started_sending_at = millis();
#endif

  for (int i = 0; i < current_num_sensors; ++i)
  {
    radio.openWritingPipe(sensor_addresses[i]);

    // Send the data.  This will block until complete
    if (radio.write(&radio_messages[i], sizeof(RadioMessage)))
    {
      // Read the ack payload if it exists.
      //if (radio.isAckPayloadAvailable()) // This didn't seem to work for me so i used radio.available() instead
      if (radio.available())
      {
        radio.read(&accel_t_gyro[i], sizeof(accel_t_gyro_union));
        read_failed[i] = false;
      }
      else
      {
        read_failed[i] = true;
#ifdef DEBUG_PRINT_
        Serial.print("No ack payload available from sensor ");
        Serial.print(i, DEC);
        Serial.println(". Read Failed!");
#endif
      }
    }
    else
    {
      read_failed[i] = true;
#ifdef DEBUG_PRINT_
      Serial.print("Send to sensor ");
      Serial.print(i, DEC);
      Serial.println(" Failed!");
#endif
    }
  }

#ifdef DEBUG_PRINT
  Serial.print("Sending time: ");
  Serial.print(millis() - started_sending_at);
  Serial.println("ms");
#endif
}

void loop()
{  
  // check if data has been sent from the computer:
  if (Serial.available()) 
  {    
    readSerial();

    sendWifi();

    sendSerial();
  }
}

Sensor code:

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

#include <my_common_types.h>

// Set up nRF24L01 radio on SPI bus plus pins 7 (CE) & 8 (CSN)
// Make sure pin 10 is in output mode or SPI will go into slave mode.
RF24 radio(7,8);

void setupRadio()
{
  radio.begin();
  
  // Open the pipe for reading, in position #1 (we can have up to 5 pipes open for reading)
  radio.openReadingPipe(1,addresses[sensor_num]);
  
  radio.setAutoAck(true);
  radio.enableAckPayload();
  radio.powerUp();
  //radio.setDataRate(RF24_250KBPS);
  radio.setDataRate(RF24_2MBPS);
  radio.setRetries(15,15); // Retry 15 times with 4000us delay between retries.

  radio.startListening();
}

void setup()
{
  setupRadio();

  // Other MPU related setup stuff...
}

void readRadioData()
{
#ifdef DEBUG_PRINT
  Serial.println("Reading...");
#endif

  // Fetch the payload.
  radio.read(&radio_message, sizeof(RadioMessage));
}

void sendMPUdata(accel_t_gyro_union accel_t_gyro)
{
  radio.flush_tx();
  radio.writeAckPayload(1, &accel_t_gyro, sizeof(accel_t_gyro_union));
}

void loop()
{  
  // check if data has been sent from the computer:
  if (radio.available()) 
  {    
    readRadioData();
  }

  // check if it's time to read the MPU values again
  if (millis() > last_MPU_read_time + MPU_sample_rate_ms)
  {
    accel_t_gyro_union mpu_data;
    bool error = read_MPU_values(mpu_data);
    
    last_MPU_read_time = millis();
    
    sendMPUdata(mpu_data);
  }
}