Two RF24 Problems

Hello,

I’m working on some radio controlled vehicles using the NRF24L01 radio modules with the RF24 library. I have a problem where my controller code was getting stuck in the radio.write part for around 500ms which in turn caused the vehicle to go into standby because the signal was lost. I know this happens because I set a bit high at the start of the code and low and the end followed by a delay and watched on an oscilloscope as I tried to drive the vehicle. I changed from radio.write to radio.startWrite which has improved the performance.

The first problem is I taught that specifying a payload size would help improve the performance however when I add the code radio.setPayloadSize(10); to both my controller and receiver I no longer have any control. This seems to be how it is done in the pingpair examples but it’s not working for me.

My code is very long so I’ll include the radio parts. Before the setup in the controller is:

#include "RF24.h"
#include "nRF24L01.h"

RF24 radio(49,53);

// Controller Address
const uint64_t pipe = 0xE8E8F0F0E3LL;

uint8_t command[8];

In the setup is:

radio.begin();

  radio.openWritingPipe(pipe); 
 radio.setPayloadSize(10);

and in the main loop I send using:

radio.startWrite(command, sizeof(command));

Before the setup in the vehicle code is:

#include "RF24.h"
#include "nRF24L01.h"

uint8_t buf[8];

const uint64_t Controller_1 = 0xE8E8F0F0E1LL;
const uint64_t Controller_2 = 0xE8E8F0F0E2LL;
const uint64_t Controller_3 = 0xE8E8F0F0E3LL;
const uint64_t Controller_4 = 0xE8E8F0F0E4LL;

In the setup is:

 radio.begin();                            // Initialize the NRF24 Radio Module
  radio.setPayloadSize(10);
  radio.openReadingPipe(1,Controller_1);  // Set Address of Controller 1 
  radio.openReadingPipe(2,Controller_2);  // Set Address of Controller 2
  radio.openReadingPipe(3,Controller_3);  // Set Address of Controller 1 
  radio.openReadingPipe(4,Controller_4);  // Set Address of Controller 2
  radio.startListening();                   // Start listening for commands from the controllers

and to receive I use:

if (radio.available())radio.read(buf, 8);
  {

The second problem is I am now trying to send data back from the vehicle to the controller so before the setup in the controller I added:

const uint64_t Vehicle_1 = 0xE8E8F0F0E4LL;

In the setup I added:

radio.openReadingPipe(1,Vehicle_1);

and in the main loop I added a call to this function which is mostly taken from another pingpair example:

void Tractor_Voltage(){
  command[1]=2;
  radio.write(command, sizeof(command));  // Function to send the data

radio.startListening();
// Wait here until we get a response, or timeout (250ms)
    unsigned long started_waiting_at = millis();
    bool timeout = false;
    while ( ! radio.available() && ! timeout )
      if (millis() - started_waiting_at > 200 )
        timeout = true;

    // Describe the results
    if ( timeout )
    {
      tft.fillRect(147,300,70,20,BLACK);
  tft.setCursor(147, 300);
  tft.setTextColor(RED);  
  tft.setTextSize(2);
  tft.println("Fail");
    }
    else
    {

      long got_time;
      radio.read( &got_time, 8 );

      tft.fillRect(147,280,70,20,BLACK);
  tft.setCursor(147, 300);
  tft.setTextColor(RED);  
  tft.setTextSize(2);
  tft.print(got_time);
  tft.println("V");    }
  
  radio.stopListening();
  delay(50);
}

To the receiver code, before the setup I added:

//const uint64_t Controller_4 = 0xE8E8F0F0E4LL;
const uint64_t pipe = 0xE8E8F0F0E4LL;

In the setup I added:

// radio.openReadingPipe(4,Controller_4);  // Set Address of Controller 2
   radio.openWritingPipe(pipe);

and finally in the main loop I have:

if (buf[1] == 2){
       radio.stopListening();
      radio.openWritingPipe(pipe);

long time = 3300L;//readVcc();
    radio.write( &time, 8 );
  
       
         radio.startListening();

     }

While testing this I have the setpayload commented out and I can still control the vehicle but the TFT displays Fail all the time, I can’t get it to write the value. Any suggestions?

long time = 3300L;//readVcc();
    radio.write( &time, 8 );

The time variable is 4 bytes (on most Arduinos). Why are you saying that it is 8 bytes?

Does the size of command stay under 10 bytes? If not, why would you say that the payload size is 10 bytes?

Post your Tx and Rx programs as complete programs - that will be much easier to understand than snippets.

I found a problem in the RF24 library's write() function. Near the end of that function there is a line powerDown(); Commenting out that line made a big improvement. It seems not to matter if the write() function is called very frequently but when I had intervals greater than about 10 millisecs between calls to the function it did not work properly when that line was active.

On the other hand the startWrite() function did not have that problem - but it does not deal with an acknowledgement.

Another thing is that the 500 millisec timeout is set towards the top of the write() function. I have reduced mine to 100 millisecs and I suspect 50 would be plenty. Now that I have my system working it never times out.

...R

Robin2:
Post your Tx and Rx programs as complete programs - that will be much easier to understand than snippets.

The controller code is exceeding the 9000 character limit, I’ll try shorten it and post it in a little while but here is the receiver code

// This section describes the vehicle
byte ID = 3; // Enter a four byte ID for the vehicle

// Variables used in the program
int Active = 0; // Variable to determine if the tractor is in active
int Last_Data_Timer = 0;

uint8_t buf[8]; // Array to store recieved data

// Include Libraries
#include <SPI.h>
#include "RF24.h"
#include "nRF24L01.h"
#include "RCTractorCab.h"

// Initialise NRF24L01
RF24 radio(8,10);
RCTractorCab MS(9,2,A0,3,7,4);

// Controller Address
const uint64_t pipe = 0xE8E8F0F0E4LL;

// Controller Address
const uint64_t Controller_1 = 0xE8E8F0F0E1LL;
const uint64_t Controller_2 = 0xE8E8F0F0E2LL;
const uint64_t Controller_3 = 0xE8E8F0F0E3LL;
//const uint64_t Controller_4 = 0xE8E8F0F0E4LL;

void setup() 
{
  Serial.begin(9600); // Start Serial Communications

  radio.begin();                            // Initialize the NRF24 Radio Module
  //radio.setPayloadSize(10);
  radio.openReadingPipe(1,Controller_1);  // Set Address of Controller 1 
  radio.openReadingPipe(2,Controller_2);  // Set Address of Controller 2
  radio.openReadingPipe(3,Controller_3);  // Set Address of Controller 1 
 // radio.openReadingPipe(4,Controller_4);  // Set Address of Controller 2
 //   radio.openWritingPipe(pipe); 

  radio.startListening();                   // Start listening for commands from the controllers

pinMode(A1,OUTPUT);
digitalWrite(A1,LOW);
}

void loop()
{
  buf[0] = 0;
  Data_Check();
  Data_Loss_Check();
  if(Active==1){
  }
  MS.Timer();
}

void   Data_Check(){
  if (radio.available())radio.read(buf, 8);
  {

    // We recieved a string of ten bytes, four ID, one command and six data values but they may not be for this tractor
    if (buf[0] == ID){ // Confirm that the correct vehicle ID has been recieved
      if (buf[1] == 0){
      MS.Drive_Motor(buf[3]);        
            MS.Drive_Motor2(buf[5]); 
      }
     if (buf[1] == 1){        
      MS.Rear_Link_Servo(buf[2]); // Store the controller right joystick y value
      MS.Front_Link_Servo(buf[3]); // Store the controller right pot value
      MS.Accessory_1(buf[4]); // Store the head lights state
      MS.Accessory_2(buf[5]); // Store the work lights state
     }
     if (buf[1] == 2){
       radio.stopListening();
      radio.openWritingPipe(pipe);

long Vehicle_Voltage = readVcc();
    radio.write(&Vehicle_Voltage, sizeof(Vehicle_Voltage) );
  
       
         radio.startListening();

     }
      MS.Lights(buf[6]);        
      MS.Accessory_Functions(buf[7]); // Store the new data value

      Last_Data_Timer = 13500;
      Active = 1;
    }
  }
}

long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = (1126400L / result); // Back-calculate AVcc in mV
  return result;
}

void Data_Loss_Check(){
  if(Last_Data_Timer != 0){
    Last_Data_Timer = Last_Data_Timer - 1;
  }

  if(Last_Data_Timer == 0 && Active == 1){
    Active = 0;
    MS.Lights(0x03);
    MS.Drive_Motor(125);        
  }
}

PaulS:
The time variable is 4 bytes (on most Arduinos). Why are you saying that it is 8 bytes?

Does the size of command stay under 10 bytes? If not, why would you say that the payload size is 10 bytes?

I didn’t realise the time variable was a sort of predefined variable, I change it to Vehicle_Voltage on both controller and receiver in case that helps.

To be honest I’m not 100% sure about the sizing of arrays so I was setting it slightly larger to make sure it was at least big enough. When I say uint8_t buf[8]; am I creating an array of 8 bytes from buf[0] to buf[7] or 9 bytes from buf[0] to buf[8]?

I changed the code to radio.write( &Vehicle_Voltage, sizeof(Vehicle_Voltage) ); on the vehicle and radio.read( &Vehicle_Voltage, sizeof(Vehicle_Voltage) ); on the controller but it still doesn’t work. What does the & symbol in that code signify?

Robin2:
Another thing is that the 500 millisec timeout is set towards the top of the write() function. I have reduced mine to 100 millisecs and I suspect 50 would be plenty. Now that I have my system working it never times out.

That is interesting, I will need to play around with this value and the power down line. 100ms would suit my application better, loosing one or two data packets in a second second isn’t a big problem but no signal for 500ms is enough time for a RC vehicle to travel a long way out of control.

Here is the controller code I just cut out some functions that draw symbols on the TFT and the sd card functions to keep it under the 9000 character limit, I don’t think I removed anything relevant to the problem.

#include "RF24.h"
#include "nRF24L01.h"
#include "RCTractorControllerV2.h"
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_TFTLCD.h> // Hardware-specific library
#include <SD.h>
#include <SPI.h>

RCTractorControllerV2 RCTC;
RF24 radio(49,53);

// Controller Address
const uint64_t pipe = 0xE8E8F0F0E3LL;

const uint64_t Vehicle_1 = 0xE8E8F0F0E4LL;

/*-----( Declare Variables )-----*/
uint8_t command[8];  // 2 element array of unsigned 8-bit type, holding Joystick readings
uint8_t old_command[8];  // 2 element array of unsigned 8-bit type, holding Joystick readings

// The control pins for the LCD can be assigned to any digital or
// analog pins...but we'll use the analog pins as this allows us to
// double up the pins with the touch screen (see the TFT paint example).
#define LCD_CS A3 // Chip Select goes to Analog 3
#define LCD_CD A2 // Command/Data goes to Analog 2
#define LCD_WR A1 // LCD Write goes to Analog 1
#define LCD_RD A0 // LCD Read goes to Analog 0

// Assign human-readable names to some common 16-bit color values:
#define  BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF
#define ORANGE  0xFB20

#define SD_CS 48     // Set the chip select line to whatever you use (10 doesnt conflict with the library)

// our TFT wiring
Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, A4);

// Define variables
float Denominator;
  float Voltage;
  float Old_Voltage;
  float Vehicle_Voltage;
int lights;
int Vehicle_No = 255;
int New_Vehicle_No = 255;
int Loop_Counter = 0;
int Flash_Timer = 0;

void setup()
{
  radio.begin();

  radio.openWritingPipe(pipe); 
radio.openReadingPipe(1,Vehicle_1);
 //radio.setPayloadSize(10);

  Denominator = (float)1000/(3300 + 1000);

  tft.reset();

  uint16_t identifier = tft.readID();

  tft.begin(identifier);
  tft.setRotation(0);
  tft.fillScreen(WHITE);

  SD.begin(SD_CS);

  bmpDraw("logo.bmp", 0, 60);

  delay(1000);
  BackgroundText();

  bitSet(lights,0);
  bitSet(lights,1);
  bitSet(lights,3);
  bitSet(lights,4);
  Light_Symbol();

  pinMode(31, OUTPUT);           // set pin to input
digitalWrite(31, LOW);       // turn on pullup resistors
}

void loop()
{ 

  if (Loop_Counter == 0){
    RCTC.Check_Fast_Select(command);
    Set_Vehicle_ID();
    Loop_Counter = Loop_Counter+1; 
  }

  if (Loop_Counter == 1){
    RCTC.Check_Pots(command);

    Loop_Counter = Loop_Counter+1; 

  }

  if (Loop_Counter == 2){
    RCTC.Check_Light_Buttons(command);

    Loop_Counter = Loop_Counter+1; 
  }  

  if (Loop_Counter == 3){
RCTC.Check_Func_Buttons(command);
    Loop_Counter = Loop_Counter+1; 

  }

  if (Loop_Counter == 4){
Light_Symbol();
    Loop_Counter = Loop_Counter+1; 
  }

  if (Loop_Counter == 5){
    Controller_Voltage(); 
    Loop_Counter = Loop_Counter+1;
  }

  if (Loop_Counter == 6){
    Indicator_Symbol(); 
    Loop_Counter = Loop_Counter+1;
  }

    if (Loop_Counter == 7){
    Tractor_Voltage(); 
    Loop_Counter = 0;
  }


  RCTC.Check_Joys(command);

digitalWrite(31, HIGH);       // turn on pullup resistors

  radio.write(command, sizeof(command));  // Function to send the data
digitalWrite(31, LOW);       // turn on pullup resistors


}

void Tractor_Voltage(){
  command[1]=2;
  radio.write(command, sizeof(command));  // Function to send the data

radio.startListening();
// Wait here until we get a response, or timeout (250ms)
    unsigned long started_waiting_at = millis();
    bool timeout = false;
    while ( ! radio.available() && ! timeout )
      if (millis() - started_waiting_at > 200 )
        timeout = true;

    // Describe the results
    if ( timeout )
    {
      tft.fillRect(147,300,70,20,BLACK);
  tft.setCursor(147, 300);
  tft.setTextColor(RED);  
  tft.setTextSize(2);
  tft.println("Fail");
    }
    else
    {
      // Grab the response, compare, and send to debugging spew
      long Vehicle_V;
      radio.read( &Vehicle_V, sizeof(Vehicle_V) );

      tft.fillRect(147,300,70,20,BLACK);
  tft.setCursor(147, 300);
  tft.setTextColor(RED);  
  tft.setTextSize(2);
  Vehicle_Voltage=Vehicle_V;
    Vehicle_Voltage=Vehicle_Voltage/1000;
  tft.print(Vehicle_Voltage);
  tft.println("V");    }
  
  radio.stopListening();
}

void Controller_Voltage()
{
  Voltage = analogRead(A5);

  Voltage = (Voltage / 1024) * (readVcc());

  Voltage = (Voltage / Denominator)/1000;

if(Voltage!=Old_Voltage){
  tft.fillRect(147,280,70,20,BLACK);
  tft.setCursor(147, 280);
  tft.setTextColor(RED);  
  tft.setTextSize(2);
  tft.print(Voltage);
  tft.println("V");
  Old_Voltage=Voltage;
}
}

long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); ADCSRB &= ~_BV(MUX5);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = (1126400L / result); // Back-calculate AVcc in mV
  return result;
}


void Set_Vehicle_ID(){

  New_Vehicle_No = command[0];

  if(Vehicle_No != New_Vehicle_No){
        tft.fillRect(5,(42+(Vehicle_No*20)),7,11,BLACK);
    tft.fillTriangle(11,(47+(New_Vehicle_No*20)),5,(42+(New_Vehicle_No*20)),5,(52+(New_Vehicle_No*20)),GREEN);
    Vehicle_No = New_Vehicle_No;
  }
}

I changed the batteries and started to receive a measured voltage back although it is flicking between a voltage and Fail. I’m wondering does it take more power to transmit than receive maybe that is why I could control the vehicle but not get data back. I’m going to try commenting out the powerdown line and changing the number of retries now, hopefully that will solve that.

I didn't realise the time variable was a sort of predefined variable

The name of the variable doesn't matter. The type does. The type of time was long. On the Arduino, with the exception of the Due, a long is 4 bytes. Changing the name of the variable doesn't change the number of bytes it occupies.

What does the & symbol in that code signify?

Address of. It means that the data to send is at the address defined by the variable whose name follows. Since the function requires a pointer, which is the address where the data is, the address of operator allows you to use a scalar variable to hold the data without the need to define an array.

PaulS: The name of the variable doesn't matter. The type does. The type of time was long. On the Arduino, with the exception of the Due, a long is 4 bytes. Changing the name of the variable doesn't change the number of bytes it occupies.

Address of. It means that the data to send is at the address defined by the variable whose name follows. Since the function requires a pointer, which is the address where the data is, the address of operator allows you to use a scalar variable to hold the data without the need to define an array.

Thanks for the detailed answer, I get why in some examples they use sizeof(long) rather than the name of the variable now, it makes a lot more sense. I changed all of those fixed values I had been using in my code to sizeof() instead.

After I changed the timeout value to 50 and commented out the powerdown line in the RF24 library it all seems to be working perfectly. I'm not loosing control of the vehicle and the battery voltage is being received.

Thanks for all the help guys :)

Why have you radio.write() in two different places? That is going to make managing the code very difficult.

Why is your TX code listening? - I mean, please explain how the program is intended to work.

Are you aware that you can arrange for the RX device to send a message as part of the acknowledgment so that the Tx can collect data without YOU having to switch it into listening mode.

In my model train system the Tx talks in turn to different handheld devices (which basically have a potentiometer for speed control). The message from the Tx is irrelevant - the important part is the data in the acknowledgment.

This is the function in the Tx that uses write() to send a message and receive the data from the slave

void getHandHeldData() {
 currentMillis = millis();
 if (currentMillis - prevMillis >= txIntervalMillis) {
 for (byte n = 0; n < numSlaves; n++) {
 const uint64_t txId = systemID + slaveData[n][0];
 radio.openWritingPipe(txId);
 
 char rslt;
 unsigned long start = micros();
 rslt = radio.write( sendVal, sizeof(sendVal) );
 Serial.print("RSLT ");
 Serial.println(rslt);
 if (rslt != 'K') {
 rxFails[n] += 1;
 }
 else {
 rxFails[n] = 0;
 }
 unsigned long end = micros();
 Serial.print("RadioMicros ");
 Serial.println(end - start);
 if (rxFails[n] >= 8) { // if there have been less than 8 fails continue using the existing data
 for (byte m = 1; m < 3; m++) { // initialize values to 0
 slaveData[n][m] = 0;
 }
 }
 if ( radio.isAckPayloadAvailable() ) {
 radio.read(ackMessg,sizeof(ackMessg));
 potVal[n] = ackMessg[1];
 // the returned ID should be the same as was called
 if (ackMessg[0] == slaveData[n][0]) {
 for (byte m = 1; m < 3; m++) {
 slaveData[n][m] = ackMessg[m];
 }
 }
 }
 }
 newData = true;
 prevMillis = millis(); // deliberately not using += txIntervalMillis to allow for time taken to get data
 }
}

NB, I have changed the RF24 library so it sends ‘K’ for an OK transmission, ‘F’ for a failed transmission and ‘T’ if the transmission times out. A failed transmission is one with an invalid response.

And these two functions are the key part of the slave (handheld) program

void loop() {
 updateValues();
 checkRadio();
 blinkLed();
}

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

void updateValues() {
 if (newRequest == true) {
 int speedVal = analogRead(A0);
 byte dirVal = digitalRead(dirnPin);
 if (dirVal == HIGH) {
 ackData[1] = speedVal;
 }
 else {
 ackData[1] = -speedVal;
 }
 ackData[2] = 77;  // for future expansion
 radio.writeAckPayload(1, ackData, sizeof(ackData));
 newRequest = false;
 }
}

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

void checkRadio() {
 if ( radio.available() ) {
 bool done = false;
 done = radio.read(recVal, sizeof(recVal) );
 newRequest = true;
 Serial.print("X = ");
 Serial.print(recVal[0]);
 Serial.print(" Y = ");      
 Serial.println(recVal[1]);
 //~ radio.writeAckPayload(1, ackData, ackLen);
 }
}

I have deliberately not included the complete programs because I have made other changes that I think will just be confusing.

Hope this helps.

…R
PS I got a lot of help from @whandall when trying to figure things out.
PPS, apologies for the poor formatting - I use tabs and the Forum does not recognize them

I didn’t know about the ability to return data with the ack. I changed my code because I taught data with the ack would make my code a bit tidier but it has left me back with having good control of the model but no data being returned to the controller. I also tried the pingpair_pl example but that didn’t work either. I’m using a pro mini and a mega, could some setting in the library not suit one of those maybe?

Controller code now:

#include "RF24.h"
#include "nRF24L01.h"
#include "RCTractorControllerV2.h"
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_TFTLCD.h> // Hardware-specific library
#include <SD.h>
#include <SPI.h>

RCTractorControllerV2 RCTC;
RF24 radio(49,53);

// Controller Address
const uint64_t pipe = 0xE8E8F0F0E3LL;

//const uint64_t Vechile[9] = { 0xF0F0F0F0D0LL, 0xF0F0F0F0D1LL,0xF0F0F0F0D2LL, 0xF0F0F0F0D3LL,0xF0F0F0F0D4LL, 0xF0F0F0F0D5LL,0xF0F0F0F0D6LL, 0xF0F0F0F0D7LL,0xF0F0F0F0D8LL };

//const uint64_t Vehicle_1 = 0xF0F0F0F0D2LL;

/*-----( Declare Variables )-----*/
uint8_t command[8];  // 2 element array of unsigned 8-bit type, holding Joystick readings
uint8_t old_command[8];  // 2 element array of unsigned 8-bit type, holding Joystick readings

// The control pins for the LCD can be assigned to any digital or
// analog pins...but we'll use the analog pins as this allows us to
// double up the pins with the touch screen (see the TFT paint example).
#define LCD_CS A3 // Chip Select goes to Analog 3
#define LCD_CD A2 // Command/Data goes to Analog 2
#define LCD_WR A1 // LCD Write goes to Analog 1
#define LCD_RD A0 // LCD Read goes to Analog 0

// Assign human-readable names to some common 16-bit color values:
#define  BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF
#define ORANGE  0xFB20

#define SD_CS 48     // Set the chip select line to whatever you use (10 doesnt conflict with the library)

// our TFT wiring
Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, A4);

// Define variables
float Denominator;
  float Voltage;
  float Old_Voltage;
  float Vehicle_Voltage;
int lights;
int Vehicle_No = 255;
int New_Vehicle_No = 255;
int Loop_Counter = 0;
int Flash_Timer = 0;
long Vehicle_V;

void setup()
{
  radio.begin();

  // We will be using the Ack Payload feature, so please enable it
  radio.enableAckPayload();

  radio.openWritingPipe(pipe); 
 //radio.setPayloadSize(10);

  Denominator = (float)1000/(3300 + 1000);

  tft.reset();

  uint16_t identifier = tft.readID();

  tft.begin(identifier);
  tft.setRotation(0);
  tft.fillScreen(WHITE);

  SD.begin(SD_CS);

  bmpDraw("logo.bmp", 0, 60);

  delay(1000);
  BackgroundText();

  bitSet(lights,0);
  bitSet(lights,1);
  bitSet(lights,3);
  bitSet(lights,4);
  Light_Symbol();

  pinMode(31, OUTPUT);           // set pin to input
digitalWrite(31, LOW);       // turn on pullup resistors
}

void loop()
{ 

  if (Loop_Counter == 0){
    RCTC.Check_Fast_Select(command);
    Set_Vehicle_ID();
    Loop_Counter = Loop_Counter+1; 
  }

  if (Loop_Counter == 1){
    RCTC.Check_Pots(command);

    Loop_Counter = Loop_Counter+1; 

  }

  if (Loop_Counter == 2){
    RCTC.Check_Light_Buttons(command);

    Loop_Counter = Loop_Counter+1; 
  }  

  if (Loop_Counter == 3){
RCTC.Check_Func_Buttons(command);
    Loop_Counter = Loop_Counter+1; 

  }

  if (Loop_Counter == 4){
Light_Symbol();
    Loop_Counter = Loop_Counter+1; 
  }

  if (Loop_Counter == 5){
    Controller_Voltage(); 
    Loop_Counter = Loop_Counter+1;
  }

  if (Loop_Counter == 6){
    Indicator_Symbol(); 
    Loop_Counter = Loop_Counter+1;
  }

    if (Loop_Counter == 7){
   // Tractor_Voltage(); 
    Loop_Counter = 0;
  }


  RCTC.Check_Joys(command);

  radio.write(command, sizeof(command));  // Function to send the data

if ( radio.isAckPayloadAvailable() )
    {
      tft.setCursor(120, 300);
  tft.println("A");
      bool done = false;
   while (!done)
   {
     done = radio.read(&Vehicle_V, sizeof(Vehicle_V) );

   }
      Vehicle_Voltage();
    }

}

void Vehicle_Voltage(){

      tft.fillRect(147,300,70,20,BLACK);
  tft.setCursor(147, 300);
  tft.setTextColor(RED);  
  tft.setTextSize(2);
  Vehicle_Voltage=Vehicle_V;
    Vehicle_Voltage=Vehicle_Voltage/1000;
  tft.print(Vehicle_Voltage);
  tft.println("V");  
  
}

Receiver code now:

uint8_t buf[16]; // Array to store recieved data

// Include Libraries
#include <SPI.h>
#include "RF24.h"
#include "RCTruck.h"
#include <Servo.h> // Include the library for controlling servos

// Initialise NRF24L01
RF24 radio(9,10);
RCTruck Scania(5,A1,A0,4,2,3,6,A2,A3,A4,A5);

// Controller Address
const uint64_t Controller_1 = 0xE8E8F0F0E1LL;
const uint64_t Controller_2 = 0xE8E8F0F0E2LL;
const uint64_t Controller_3 = 0xE8E8F0F0E3LL;
const uint64_t Controller_4 = 0xE8E8F0F0E4LL;

void setup() 
{
  Serial.begin(9600); // Start Serial Communications

  // Set servo pins
  Steering_Servo.attach(7); // attaches the servo on pin 10 to the steering servo object
  Rear_Link_Servo.attach(8); // attaches the servo on pin 9 to the link servo object
  //Front_Link_Servo.attach(7); // attaches the servo on pin 9 to the link servo object

  // Initialise the servos
  Steering_Servo.write(Steering_Center);
  Rear_Link_Servo.write(Rear_Link_Center); // sets the initial link servo position
  //Front_Link_Servo.write(Front_Link_Center); // sets the initial link servo position

  radio.begin();                            // Initialize the NRF24 Radio Module
   // We will be using the Ack Payload feature, so please enable it
  radio.enableAckPayload();
 
  radio.openReadingPipe(1,Controller_1);  // Set Address of Controller 1 
  radio.openReadingPipe(2,Controller_2);  // Set Address of Controller 2
  radio.openReadingPipe(3,Controller_3);  // Set Address of Controller 1 
  radio.openReadingPipe(4,Controller_4);  // Set Address of Controller 2
  
  
  radio.startListening();                   // Start listening for commands from the controllers
}

void loop()
{
  buf[0] = 0;
  Data_Check();
  Data_Loss_Check();
  if(Active==1){
    Update_Features();
  }
  Scania.Timer();
}

void   Data_Check(){
if (radio.available())
  {
//radio.read(buf, sizeof(buf));
 long Vehicle_Voltage = readVcc();
    //radio.write(&Vehicle_Voltage, sizeof(Vehicle_Voltage) );
        radio.writeAckPayload( 3, &Vehicle_Voltage, sizeof(Vehicle_Voltage ));

 
      bool done = false;

      while (!done)
      {
        // Fetch the payload, and see if this was the last one.
        done = radio.read(buf, sizeof(buf));
      }
    

    // We recieved a string of ten bytes, four ID, one command and six data values but they may not be for this tractor
if (buf[0] == ID){ // Confirm that the correct vehicle ID has been recieved
      if (buf[1] == 0){
      Scania.Drive_Motor(buf[3]);        
      Steering_Val = buf[4]; 
      }
     if (buf[1] == 1){        
     Rear_Link_Val = buf[2];    // Store the rear servo position value
      Front_Link_Val = buf[3];   // Store the front servo position value
      Accessory_1_Val = buf[4]; // Store the a value for servos or motors on accessories
      Accessory_2_Val = buf[5]; // Store the a value for servos or motors on accessories
     }

//if (buf[1] == 2){
       //radio.stopListening();
//      radio.openWritingPipe(pipe);

//long Vehicle_Voltage = readVcc();
    //radio.write(&Vehicle_Voltage, sizeof(Vehicle_Voltage) );
  //      radio.writeAckPayload( 3, &Vehicle_Voltage, sizeof(Vehicle_Voltage );

        // radio.startListening();
 //    }

     Scania.Lights(buf[6]);        // Update the lights
      Function_Val = buf[7];    // Store the data for functions on accessories

      Last_Data_Timer = 5000;
      Active = 1;
    }
  }
}

long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = (1126400L / result); // Back-calculate AVcc in mV
  return result;
}

Sorry I screwed up the pins on the pingpair_pl example for the Mega, it does work which means it is not a problem in the library. I guess I'm best to start cutting and pasting my code into the pingpair example until I find the problem.

Okay I have found the problem, the ack payload doesn't work when you have multiple open reading pipes. Is there a way to close a reading pipe?

I tried:

radio.stopListening(); 
radio.openReadingPipe(1,Controller_3);  // Set Address of Controller 1 
  radio.openReadingPipe(2,0);  // Set Address of Controller 2
  radio.openReadingPipe(3,0);  // Set Address of Controller 3 
  radio.openReadingPipe(4,0);  // Set Address of Controller 4
  radio.startListening();                   // Start listening for commands from the controllers

But this didn't work. I need the multiple reading pipes at the start to listen for a connection from one of the controllers, once it is connected I want to close the other reading pipes so that I can send the battery voltage back with the ack payload function.

RCTractorGuy: Okay I have found the problem, the ack payload doesn't work when you have multiple open reading pipes. Is there a way to close a reading pipe?

If you are just communicating between two NRF24s why do you need more than one reading pipe?

By the way I found the use of "pipe" as the variable name in this line very confusing const uint64_t pipe = 0xE8E8F0F0E3LL; That number is just a 5-byte ID number for the device you want to talk to

...R

Robin2: If you are just communicating between two NRF24s why do you need more than one reading pipe?

There aren't just two, there are multiple controllers and multiple vehicles but after a controller had selected a vehicle then if it was possible you could close the other reading pipes. I might just have to go back to the two write approach, you are right it would leave the code hard to manage but at least it was working perfectly after the changes to the library you suggested.

I agree pipe is confusing, I guess he was trying for something that could be interpreted as two way flow. Maybe he taught using ID or address would make people seeing it for the first time think you could only send data to that ID.

I had a mistake in my code, I seem to have it working now. At the setup the vehicle opens all the pipes to listen for a connection from one of the controllers as normal:

 radio.begin();                            // Initialize the NRF24 Radio Module
   // We will be using the Ack Payload feature, so please enable it
  radio.enableAckPayload();

  radio.openReadingPipe(1,Controller_1);  // Set Address of Controller 1 
  radio.openReadingPipe(2,Controller_2);  // Set Address of Controller 2
  radio.openReadingPipe(3,Controller_3);  // Set Address of Controller 1 
  radio.openReadingPipe(4,Controller_4);  // Set Address of Controller 2
  
  radio.startListening();                   // Start listening for commands from the controllers

Then when a controller wants to take control the vehicle loop calls this function which closes the other pipes. You need to move the connecting controllers address to pipe 1 and put 0 in the address of the other pipes.

radio.stopListening(); 
radio.openReadingPipe(1,Controller_3);  // Set Address of Controller 1 
  radio.openReadingPipe(2,0);  // Set Address of Controller 2
  radio.openReadingPipe(3,0);  // Set Address of Controller 3 
  radio.openReadingPipe(4,0);  // Set Address of Controller 4
  radio.startListening();                   // Start listening for commands from the controllers

I just have it fixed to Controller_3 value now for testing but I will be storing the addresses in an array so it will be radio.openReadingPipe(1,Controller[ID]); in the future or something like that.

Finally to write the ack payload you need to have it set to 1. That is the mistake I made last night when I first tried this, I left it as 3.

radio.writeAckPayload( 1, &Vehicle_Voltage, sizeof(Vehicle_Voltage ));

This seems to be working perfectly for me so far but I still have to finish off the rest of my code because I had started again from the pingpair_pl example and was just cutting and pasting parts of my code over until is stopped working. Fingers crossed it keeps working :) Thanks for the great suggestion to use the ack payload, it's a much nicer way to send data back.

RCTractorGuy:
There aren’t just two, there are multiple controllers and multiple vehicles

Please explain in more detail?

Do you mean, for example, that there are 4 controllers and 4 vehicles and each controller is in charge of one vehicle?

Or what ?

…R