Go Down

Topic: nRF24L01+ demo using ackPayload (Read 17638 times) previous topic - next topic

Robin2

Feb 25, 2016, 08:49 pm Last Edit: Feb 28, 2016, 11:24 pm by Robin2
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
Code: [Select]
// 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
Code: [Select]
// 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
Two or three hours spent thinking and reading documentation solves most programming problems.

Arctic_Eddie

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

Whandall

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

I would move the change before the upload.

Code: [Select]
        ackData[0] += valChange; // this just increments so you can see that new data is being sent
        radio.writeAckPayload(1, ackData, ackLen);
Ah, this is obviously some strange usage of the word 'safe' that I wasn't previously aware of. (D.Adams)

Robin2

The slave returns two identical ack-payloads, the first and the second.
Not in my tests.

Quote
I would move the change before the upload.

Code: [Select]
        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
Two or three hours spent thinking and reading documentation solves most programming problems.

Whandall

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).
Ah, this is obviously some strange usage of the word 'safe' that I wasn't previously aware of. (D.Adams)

Robin2

#5
Feb 26, 2016, 12:20 pm Last Edit: Feb 26, 2016, 01:07 pm by Robin2
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
Two or three hours spent thinking and reading documentation solves most programming problems.

Whandall

#6
Feb 26, 2016, 04:25 pm Last Edit: Feb 26, 2016, 04:33 pm by 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.
Ah, this is obviously some strange usage of the word 'safe' that I wasn't previously aware of. (D.Adams)

Robin2

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
Two or three hours spent thinking and reading documentation solves most programming problems.

Whandall

#8
Feb 26, 2016, 05:04 pm Last Edit: Feb 26, 2016, 05:05 pm by 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).
Ah, this is obviously some strange usage of the word 'safe' that I wasn't previously aware of. (D.Adams)

Robin2

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
Two or three hours spent thinking and reading documentation solves most programming problems.

Whandall

It's just a little flaw in a good and short example.  ;)
Ah, this is obviously some strange usage of the word 'safe' that I wasn't previously aware of. (D.Adams)

Robin2

It's just a little flaw in a good and short example.  ;)
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
Two or three hours spent thinking and reading documentation solves most programming problems.

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)?

Whandall

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.
Ah, this is obviously some strange usage of the word 'safe' that I wasn't previously aware of. (D.Adams)

Robin2

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

---------

Your slave is sending only 2 bytes, the master reads always 4.
You should use
Code: [Select]
radio.getDynamicPayloadSize()

This is what I get when I let the slave answer to one packet (pressing reset after the first send result 1)
Code: [Select]
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.
---------------

Probably I got confused by the strange array types and sizes and both sides read and write two bytes.
Code: [Select]
int ackMessg[4];
byte ackMessgLen = 2;

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


----------------

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.
Code: [Select]
#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):
Code: [Select]
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.

Two or three hours spent thinking and reading documentation solves most programming problems.

Go Up