nRF24L01+ demo using ackPayload

I posted a pair of demo programs here and following some comments I have changed them a little in the hope of making things a bit clearer.

These two programs are cut-down versions of a system I use for controlling model trains. The idea is that th Track Control program can control a number of tracks based on data sent from different hand controllers. The TrackControl program acts as the master and sends a message to each hand control in turn. When it receives a message the hand-control uses ackPayload to send data back to the master.

By using ackPayload there is no need for the master and slave to switch roles from transmitting (receiving) to receiving (transmtting).

In the example code the master just calls one slave and sends a couple of numbers. The slave sends a different number back. All the numbers are incremented so you can see that change is happening.

EDIT 28 Feb 2016 - there is a slightly revised version of the code in Reply #16

The master code

// TrackControl - the master or the transmitter

 // http://tmrh20.github.io/RF24/

 //~ - CONNECTIONS: nRF24L01 Modules See:
 //~ http://arduino-info.wikispaces.com/Nrf24L01-2.4GHz-HowTo
 //~ 1 - GND
 //~ 2 - VCC 3.3V !!! NOT 5V
 //~ 3 - CE to Arduino pin 9
 //~ 4 - CSN to Arduino pin 10
 //~ 5 - SCK to Arduino pin 13
 //~ 6 - MOSI to Arduino pin 11
 //~ 7 - MISO to Arduino pin 12
 //~ 8 - UNUSED

#include <SPI.h>
//~ #include <TMRh20nRF24L01.h>
//~ #include <TMRh20RF24.h>
#include <RF24L01.h>
#include <RF24.h>


#define CE_PIN   9
#define CSN_PIN 10

// NOTE: the "LL" at the end of the constant is "LongLong" type
// These are the IDs of each of the slaves
const uint64_t slaveID[2] = {0xE8E8F0F0E1LL, 0xE8E8F0F0E2LL} ;

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

int dataToSend[2];

unsigned long currentMillis;
unsigned long prevMillis;
unsigned long txIntervalMillis = 1000;
int txVal = 0;
int ackMessg[4];
byte ackMessgLen = 2;


void setup() {

    Serial.begin(9600);
    Serial.println("Track Control Starting");
    radio.begin();
    radio.setDataRate( RF24_250KBPS );
    radio.enableAckPayload();
    radio.setRetries(3,5); // delay, count
}

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

void loop() {

    currentMillis = millis();
    if (currentMillis - prevMillis >= txIntervalMillis) {

    radio.openWritingPipe(slaveID[0]); // calls the first slave
                                        // there could be a FOR loop to call several slaves in turn
    dataToSend[0] = txVal; // this gets incremented so you can see that new data is being sent
    txVal += 1;
    dataToSend[1] = txVal;
    txVal += 1;
    bool rslt;
    rslt = radio.write( dataToSend, sizeof(dataToSend) );
    Serial.print("\nRSLT (1 = success) ");
    Serial.println(rslt);
    Serial.print("Data Sent ");
    Serial.print(dataToSend[0]);
    Serial.print("  ");
    Serial.println(dataToSend[1]);
    if ( radio.isAckPayloadAvailable() ) {
        radio.read(ackMessg,ackMessgLen);
        Serial.print("Acknowledge received: ");
        Serial.println(ackMessg[0]);
    }
    prevMillis = millis();
 }
}

The slave code

// HandController - the slave or the receiver

    // http://tmrh20.github.io/RF24/

    //~ - CONNECTIONS: nRF24L01 Modules See:
    //~ http://arduino-info.wikispaces.com/Nrf24L01-2.4GHz-HowTo
    //~ 1 - GND
    //~ 2 - VCC 3.3V !!! NOT 5V
    //~ 3 - CE to Arduino pin 9
    //~ 4 - CSN to Arduino pin 10
    //~ 5 - SCK to Arduino pin 13
    //~ 6 - MOSI to Arduino pin 11
    //~ 7 - MISO to Arduino pin 12
    //~ 8 - UNUSED

#include <SPI.h>
//~ #include <TMRh20nRF24L01.h>
//~ #include <TMRh20RF24.h>
#include <RF24L01.h>
#include <RF24.h>

#define CE_PIN   9
#define CSN_PIN 10

// NOTE: the "LL" at the end of the constant is "LongLong" type

const uint64_t   deviceID = 0xE8E8F0F0E1LL; // Define the ID for this slave

int valChange = 1;

RF24 radio(CE_PIN, CSN_PIN);

int dataReceived[2];
int ackData[2] = {12,0};
byte ackLen = 2;

void setup() {

    Serial.begin(9600);
    delay(1000);
    Serial.println("Hand Controller Starting");
    radio.begin();
    radio.setDataRate( RF24_250KBPS );
    radio.openReadingPipe(1,deviceID);
    radio.enableAckPayload();
    radio.writeAckPayload(1, ackData, ackLen);
    radio.startListening();
}

void loop() {

    if ( radio.available() ) {
        radio.read( dataReceived, sizeof(dataReceived) );
        Serial.print("Data received Number0 ");
        Serial.print(dataReceived[0]);
        Serial.print(" Number1 ");
        Serial.println(dataReceived[1]);
        radio.writeAckPayload(1, ackData, ackLen);
        ackData[0] += valChange; // this just increments so you can see that new data is being sent
    }
}

NOTE that these programs use TMRh20's version of the RF24 library

...R

Thanks Robin. This will help considerably in one of my projects.

The slave returns two identical ack-payloads, the first and the second.

I would move the change before the upload.

        ackData[0] += valChange; // this just increments so you can see that new data is being sent
        radio.writeAckPayload(1, ackData, ackLen);

Whandall:
The slave returns two identical ack-payloads, the first and the second.

Not in my tests.

I would move the change before the upload.

        ackData[0] += valChange; // this just increments so you can see that new data is being sent

radio.writeAckPayload(1, ackData, ackLen);

I don't think that makes any difference for the demo.

It is that way in the demo because the project from which it came sends the previous value in the ackPayload and then calculates a new value after the ackPayload has been sent.

The important thing is that there is no time to calculate the value for ackPayload between the receive and the acknowledgment.

...R

Robin2:
I don't think that makes any difference for the demo.

Then your demo does not show all received ack-payloads (at least not the first and the second that are equal).

Whandall:
Then your demo does not show all received ack-payloads (at least not the first and the second that are equal).

I will check again.
I have looked at it again and I don't see the point you are making.

To my mind the Master sends 2 ints to the slave and the slave sends 2 ints back as the ackPayload. However only one of the values in the ackPayload is incremented.

You have been right before so if there is an issue I would like to deal with it. Perhaps you would be kind enough to point it out for me.

...R

You initialize the ack payload in setup.
You reload it after a reception.
You change it after uploading (only after a reception).

So the 1st payload will be equal to the 2nd.

If you do not see that, you loose a payload.

Whandall:
You initialize the ack payload in setup.
You reload it after a reception.
You change it after uploading (only after a reception).

So the 1st payload will be equal to the 2nd.

If you do not see that, you loose a payload.

That reads like there is only a problem with the very first payload and after that everything is fine. Is that what you mean ?

...R

Exactly.
It is only the first and the second that are equal.
If you move the change before the upload that effect vanishes, that was my suggestion (in #2).

Whandall:
Exactly.
It is only the first and the second that are equal.
If you move the change before the upload that effect vanishes, that was my suggestion (in #2).

Thanks. I will review it later (maybe not until tomorrow).

...R

It's just a little flaw in a good and short example. :wink:

Whandall:
It's just a little flaw in a good and short example. :wink:

I can't reproduce the problem you have seen.

I have the two Unos running and displaying their output on two PuTTY terminals.

If I press reset for the hand-control (the slave) the ack messages start over at 12 and increment one at a time. I never see 12 repeated twice.

If I press reset for the master the data that is sent starts off again at 0

...R

@Robin2, I was wondering how I could get this to work with several receivers? I want to broadcast the data to anywhere between 2-30 receivers. Would it be as simple as using the same pipe address on all slaves? (i.e. the slaveID from your sketch)?

JoeK1973:
I want to broadcast the data to anywhere between 2-30 receivers.

Broadcast and acknowledge (even without payload) does not work together.
So this example will be impossible to change to use broadcast.
I believe Robin2 has a better example for this situation.

This is a follow-on from Reply #11. @Whandall has been kind enough to make some more comments in PMs and he has given me his approval to include them here. I think that will help to maintain continuity of the discussion.

...R


Whandall:
Your slave is sending only 2 bytes, the master reads always 4.
You should use

radio.getDynamicPayloadSize()

This is what I get when I let the slave answer to one packet (pressing reset after the first send result 1)

Track Control Starting

Data Sent 0, 1 RSLT (1 = success) 0
Data Sent 2, 3 RSLT (1 = success) 0
Data Sent 4, 5 RSLT (1 = success) 1 <---- SLAVE in reset from here
Data Sent 6, 7 RSLT (1 = success) 1
Acknowledge received: 12
Data Sent 8, 9 RSLT (1 = success) 1
Data Sent 10, 11 RSLT (1 = success) 1
Data Sent 12, 13 RSLT (1 = success) 0
Data Sent 14, 15 RSLT (1 = success) 0



That ist hard to interpret for me..
(I changed the frequency of the master to 4 seconds to make it easy to reset at the right moment.)
Is that effect from the internal usage of the tx fifos in the master?
So true does not signal reception, but only enqueue?
Maybe you could/should use a blocking write instead?

So I could not see what the chip is supposed to do (2 times 12 in ack), probably due a special handling in/from the library.

Whandall:
Probably I got confused by the strange array types and sizes and both sides read and write two bytes.

int ackMessg[4];

byte ackMessgLen = 2;





int ackData[2] = {12,0};
byte ackLen = 2;


Whandall:
I rechecked the behaviour with my library, which I know quite well and in that I see exactly the behaviour that I thought your code should show.
There is a one packet delay in the signaling of an ack-reception.
I use a callback from the ISR to change the ack-payload-data on reception on the acking pipe.
The code is the base for an ack-payload example for my library (no real master and slave but interactive). I added it to only give you an impression of the interface.

#include <SimCommDefs.h>

#include <OUtility.h>
#include <SPI.h>
#include <NRF24.h>

bool blockAck = false;  // to switch between the two send modes

// this driver supports addresses as strings, in MSB, out of PROGMEM

const byte addrs[] PROGMEM = "pipe0" "pipe1" "pipe2" "pipe3" "pipe4" "pipe5";

byte testMsg[] = "****0000 Test Message";

// and there is a solid support for ack payloads

byte ackPayLoad[] = "****0000 AckPayLoad";

// SPI pins CE,CS,IRQ,Receive, State
NRF24 radio(9, 10, 2, onReceive, onEvent);

void setup() {
  Serial.begin(115200);
  radio.addresses(addrs, 6, dcLen5 + dcMSB + dcInPM);
  radio.listenOn(0, aiFirst);            // for pipe#0 use addr#0 with Ack
  radio.listenOn(1, aiFirst + 1, true);  // for pipe#1 use addr#1 without Ack
  radio.listenOn(2, aiFirst + 2, ackPayLoad, sizeof(ackPayLoad), dcInRAM); // for pipe#2 use addr#2 and a small payload
  radio.onAckReload(onAckRld);
  radio.begin(PRX);
  showHelp();
  showSendMode();
}

void loop() {
  radio.processEvents();
  serialHandler();
}
void onAckRld(byte pipe, byte* data) {
  if (pipe == 2) {
    ascInc(data + 4, 4);
  }
}

byte onReceive(RCB& rcb) {
  return rPrint; // packets can be dumped by the driver
}

byte onEvent(Event& e) {
  if (e.txAny()) {
    radio.printSendResult();
  }
  return 0;
}

void ascInc(byte* data, byte len) {
  while (len--) {
    if (++data[len] <= '9') {
      break;
    }
    data[len] = '0';
  }
}

void showHelp() {
  Serial.println(F("NRF24 simple Ack/noAck/ackPayLoad"));
  Serial.println(F("1  - send to Ack pipe"));
  Serial.println(F("2  - send to noAck pipe"));
  Serial.println(F("3  - send to ackPayload pipe"));
  Serial.println(F("a  - toggle Ack/noAck for send"));
  Serial.println(F("d  - show details"));
  Serial.println(F("p  - show pipes"));
  Serial.println(F("s  - show state"));
  Serial.println(F("m# - change mode"));
}

void showSendMode() {
  Serial.print(F("Sending with"));
  if (blockAck) {
    Serial.print(F("out"));
  }
  Serial.println(F(" acknowledge request"));
}

void processCmd(char* buf) {
  if (*buf == '1') {
    ascInc(testMsg + 4, 4);
    radio.send(aiFirst, testMsg, sizeof(testMsg), blockAck, dcInRAM);
  } else if (*buf == '2') {
    ascInc(testMsg + 4, 4);
    radio.send(aiFirst + 1, testMsg, sizeof(testMsg), blockAck, dcInRAM);
  } else if (*buf == '3') {
    ascInc(testMsg + 4, 4);
    radio.send(aiFirst + 2, testMsg, sizeof(testMsg), blockAck, dcInRAM);
  } else if (*buf == 'a') {
    blockAck = !blockAck;
    showSendMode();
  } else if (*buf == 's') {
    radio.printState();
  } else if (*buf == 'p') {
    radio.printPipes();
  } else if (*buf == 'd') {
    radio.printDetails();
  } else if (buf == 'm') {
    char
satis;
    byte result = strtol(++buf, &satis, 0);
    if (result >= PNONE && result < NoMode) {
      radio.mode(result);
    }
    radio.printMode();
  }
}

// Command line stuff

void serialHandler() {
  const byte sCBMax = 30;
  static char sCBuffer[sCBMax];
  static byte buffIndex = 0;

byte inChar;
  bool doCheck = false;

while (Serial.available()) {
    inChar = Serial.read();
    if (inChar == 13) {
      doCheck = true;
    } else if (inChar != 10) {
      if ((buffIndex == 0) && isWhitespace(inChar)) {
        continue;
      }
      sCBuffer[buffIndex++] = inChar;
      doCheck = (buffIndex == (sCBMax - 1));
    }
    if (doCheck) {
      sCBuffer[buffIndex] = 0;
      doCheck = false;
      if (buffIndex != 0) {
        processCmd(sCBuffer);
        buffIndex = 0;
      } else {
        Serial.println();
      }
    }
  }
}



This is what I see when I send some single packets to the acking address (command 3):


NRF24 simple Ack/noAck/ackPayLoad
1  - send to Ack pipe
2  - send to noAck pipe
3  - send to ackPayload pipe
a  - toggle Ack/noAck for send
d  - show details
p  - show pipes
s  - show state
m# - change mode
Sending with acknowledge request
3>4>16 P 484 µs
A0.02.14: '' '0000 AckPayLoad.'
3>4>16 P 548 µs
A0.02.14: '
' '0000 AckPayLoad.'
3>4>16 P 548 µs
A0.02.14: '' '0001 AckPayLoad.'
3>4>16 P 548 µs
A0.02.14: '
' '0002 AckPayLoad.'
3>4>16 P 484 µs
A0.02.14: '****' '0003 AckPayLoad.'



My callback is not executed for the very first packet, I can see no reason for that.

JoeK1973:
@Robin2, I was wondering how I could get this to work with several receivers? I want to broadcast the data to anywhere between 2-30 receivers. Would it be as simple as using the same pipe address on all slaves? (i.e. the slaveID from your sketch)?

If you want several slaves to receive the same message then you will have to disable acknowledgement because the slaves would all be replying at the same time. Assuming you do that you could use the same address for every slave.

The alternative is to send the message in turn to every slave separately. It would not take long for the master to communicate with 30 slaves. How often do you need to send the message?

There is a network mode for the nRF24s but I don't know anything about it - I have never used it and I have never read any of the documentation. It may or may not be relevant to your situation.

...R

I have tidied up (I hope) my use of ackLen and ackMessgLen in the versions in this Reply.
EDIT ... Note to self - I downloaded and tested this code successfully on 14/08/2016

There is a one packet delay in the signaling of an ack-reception.

I'm afraid I don't believe this happens with my code. The data that is sent in the acknowledgment is what I expect it to send.

Revised versions of the programs

The master program

// TrackControl - the master or the transmitter

 // http://tmrh20.github.io/RF24/

 //~ - CONNECTIONS: nRF24L01 Modules See:
 //~ http://arduino-info.wikispaces.com/Nrf24L01-2.4GHz-HowTo
 //~ 1 - GND
 //~ 2 - VCC 3.3V !!! NOT 5V
 //~ 3 - CE to Arduino pin 9
 //~ 4 - CSN to Arduino pin 10
 //~ 5 - SCK to Arduino pin 13
 //~ 6 - MOSI to Arduino pin 11
 //~ 7 - MISO to Arduino pin 12
 //~ 8 - UNUSED

#include <SPI.h>
//~ #include <TMRh20nRF24L01.h>
//~ #include <TMRh20RF24.h>
#include <nRF24L01.h>
#include <RF24.h>


#define CE_PIN   9
#define CSN_PIN 10

// NOTE: the "LL" at the end of the constant is "LongLong" type
// These are the IDs of each of the slaves
const uint64_t slaveID[2] = {0xE8E8F0F0E1LL, 0xE8E8F0F0E2LL} ;

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

int dataToSend[2];

unsigned long currentMillis;
unsigned long prevMillis;
unsigned long txIntervalMillis = 1000;
int txVal = 0;
int ackMessg[6];
byte ackMessgLen = 4; // NB this 4 is the number of bytes in the 2 ints that will be recieved


void setup() {

    Serial.begin(9600);
    Serial.println("Track Control Starting");
    radio.begin();
    radio.setDataRate( RF24_250KBPS );
    radio.enableAckPayload();
    radio.setRetries(3,5); // delay, count
}

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

void loop() {

    currentMillis = millis();
    if (currentMillis - prevMillis >= txIntervalMillis) {

    radio.openWritingPipe(slaveID[0]); // calls the first slave
                                        // there could be a FOR loop to call several slaves in turn
    dataToSend[0] = txVal; // this gets incremented so you can see that new data is being sent
    txVal += 1;
    dataToSend[1] = txVal;
    txVal += 1;
    bool rslt;
    rslt = radio.write( dataToSend, sizeof(dataToSend) );
    Serial.print("\nRSLT (1 = success) ");
    Serial.println(rslt);
    Serial.print("Data Sent ");
    Serial.print(dataToSend[0]);
    Serial.print("  ");
    Serial.println(dataToSend[1]);
    if ( radio.isAckPayloadAvailable() ) {
        radio.read(ackMessg,ackMessgLen);
        Serial.print("Acknowledge received: ");
        Serial.print(ackMessg[0]);
        Serial.print("  ");
        Serial.println(ackMessg[1]);
    }
    prevMillis = millis();
 }
}

and the slave program

// HandController - the slave or the receiver

    // http://tmrh20.github.io/RF24/

    //~ - CONNECTIONS: nRF24L01 Modules See:
    //~ http://arduino-info.wikispaces.com/Nrf24L01-2.4GHz-HowTo
    //~ 1 - GND
    //~ 2 - VCC 3.3V !!! NOT 5V
    //~ 3 - CE to Arduino pin 9
    //~ 4 - CSN to Arduino pin 10
    //~ 5 - SCK to Arduino pin 13
    //~ 6 - MOSI to Arduino pin 11
    //~ 7 - MISO to Arduino pin 12
    //~ 8 - UNUSED

#include <SPI.h>
//~ #include <TMRh20nRF24L01.h>
//~ #include <TMRh20RF24.h>
#include <nRF24L01.h>
#include <RF24.h>

#define CE_PIN   9
#define CSN_PIN 10

// NOTE: the "LL" at the end of the constant is "LongLong" type

const uint64_t   deviceID = 0xE8E8F0F0E1LL; // Define the ID for this slave

int valChange = 1;

RF24 radio(CE_PIN, CSN_PIN);

int dataReceived[2];
int ackData[2] = {12,23};

void setup() {

    Serial.begin(9600);
    delay(1000);
    Serial.println("Hand Controller Starting");
    radio.begin();
    radio.setDataRate( RF24_250KBPS );
    radio.openReadingPipe(1,deviceID);
    radio.enableAckPayload();
    radio.writeAckPayload(1, ackData, sizeof(ackData));
    radio.startListening();
}

void loop() {

    if ( radio.available() ) {
        radio.read( dataReceived, sizeof(dataReceived) );
        Serial.print("Data received Number0 ");
        Serial.print(dataReceived[0]);
        Serial.print(" Number1 ");
        Serial.println(dataReceived[1]);
        radio.writeAckPayload(1, ackData, sizeof(ackData));
        ackData[0] += valChange; // this just increments so you can see that new data is being sent
    }
}

NOTE that these programs use TMRh20's version of the RF24 library

...R

1 Like

The latest version looks as if the master would not get any ack/ack-payload

Track Control Starting

RSLT (1 = success) 0
Data Sent 0  1

RSLT (1 = success) 0
Data Sent 2  3

RSLT (1 = success) 0
Data Sent 4  5

RSLT (1 = success) 0
Data Sent 6  7

RSLT (1 = success) 0
Data Sent 8  9

The slave gets the packets

Hand Controller Starting
Data received Number0 0 Number1 1
Data received Number0 2 Number1 3
Data received Number0 4 Number1 5
Data received Number0 6 Number1 7
Data received Number0 8 Number1 9

I would like to understand this line [] {}

int ackData[2] = {12,23};

[] indicates an array, ackData[2] is a 2 location array.
{} is the array contents
so ackData[0] = 12
and ackData[1] = 23
Each location holds a 2 byte value as int was used for the datatype. byte would have been sufficient as the data used is <= a value of 255.

1 Like