nrf24l01+ - how does writeAckPayload really work?

Hi,

I use TMRh20 - Optimized Fork of NRF24L01+ Driver. I have a setup with couple of wireless temperature sensors. I am building a new set of sensors and I wanted to rewrite my code to have acknowledgement of the transmitted messages with a possibility of re-sending failed ones.

I decided to transmit the payload on the transceiver, then receive it on receiver + send back an ack with certain value - so I can check again on the transceiver, whether the value is correct and in case it is missing or is incorrect, then repeat the sending.

This is my main loop code on the receiver:

void loop(void)
{
    if ( radio.available() ) //if there is a data available
    {
        radio.read( &payload, sizeof(payload) ); //read the data into a buffer
        sndAckBuf.counter = payload.A; //prepare the 2 values which will be sent back as ack
        sndAckBuf.id = payload.E;
        radio.writeAckPayload(1,&sndAckBuf, sizeof(sndAckBuf) ); //send ack
        dtostrf(payload.B,1,2,temp_buff);
        dtostrf(payload.C,1,2,humid_buff);
        dtostrf(payload.D,1,4,volt_buff);
        sprintf(pageAdd,"/add.php?temp=%s&humid=%s&voltage=%s&counter=%d&auth=password&mode=dht&s=%c",temp_buff,humid_buff,volt_buff,payload.A,payload.E);
        // Delay just a little bit to let the other unit
        // make the transition to receiver
        delay(20);

        //
        // Ethernet send part
        //
        sent = 0;
        retries = 0;
        do
        {
            sent = sendData(pageAdd); //sendData function returns true if sending via ethernet was successful
            if ( retries++ > MAX_RETRIES )
            sent = 1;
        }
        while ( sent == 0 );
        //
        // END - ethernet send part
        //
    }

Basically this does the job. But not well. In case there is an outage on the ethernet line (cable disconnected, etc...) for longer time, it looks there is a buffer filled with an ack payloads and then the transmitter keeps re-send 3 times until it has correct value in read ack payload. This I was able to solve by adding radio.stopListening(); before the eth send part and again start listening after the eth sending part is done. I am not sure whether this is OK, but it looks it worked (it is not in the code above).

But I would like to know how exactly the writeAckPayload() function should be used. I do not understand the note in the class definition:

Ack payloads are handled automatically by the radio chip when a payload is received. Users should generally write an ack payload as soon as startListening() is called, so one is available when a regular payload is received.

So is my design completely wrong? Should I prepare some data in the buffer for ack payload and once the incoming payload is available (transceiver sent data), nrf24l01 chip will reply the transceiver with the ack payload? If so, then there is no possibility for including node id and counter in the payload, so I can't verify on the transceiver side whether that particular payload reached the receiver properly or not (I would like to have this to prevent reading of the ack payload sent from other node).

Can anybody help me understanding this?

I tested couple of cases now and found out:

Swapping the order to have radio.writeAckPayload in advance of radio.read will work, the ack payload is sent back to the receiver:

        sndAckBuf.counter = 1; //counter as int
        sndAckBuf.id = '3'; //id of the node as char
        radio.writeAckPayload(1,&sndAckBuf, sizeof(sndAckBuf) ); //send ack
        radio.read( &payload, sizeof(payload) ); //read the data into a buffer

But even removing radio.read function will make ack payload to be sent back to the transceiver

        sndAckBuf.counter = 1; //counter as int
        sndAckBuf.id = '3'; //id of the node as char
        radio.writeAckPayload(1,&sndAckBuf, sizeof(sndAckBuf) ); //send ack
        //radio.read( &payload, sizeof(payload) ); //read the data into a buffer

To be honest, I am a bit confused now and I really do not know how writeAckPayload function works. The note in the class definition mentioned in my previous post is a bit confusing for me and I do not know how to explain it to myself.

But checking the example from the library itself revealed to me it should be used after read() function as I originally had in my code:

      radio.read( &gotByte, 1 );
      radio.writeAckPayload(pipeNo,&gotByte, 1 );

I realized now I am maybe using wrong approach.
Reading the class definition I can see the write() function should return true if the payload was delivered successfully and returns false if it was failed. So basically in my setup it should be enought to use just write() function on the sender and repeat the sending in case write() returns false.

Taken from the class documentation: write(): This blocks until the message is successfully acknowledged by the receiver or the timeout/retransmit maxima are reached. In the current configuration, the max delay here is 60-70ms.
*Returns: True if the payload was delivered successfully false if not *

I have been using the RF24 library with writeAckPayload. It makes it very easy to get data back from a device. In my system the data that is sent using write() is irrelevant. I am only interested in the data that is returned as the ackPayload.

I have a master that write()s in turn to different slaves. The slave already has been "primed" with writeAckPayload() so when it recognizes a message intended for it it automatically sends the payload.

On the master, if the write is successful it then reads the received payload.

...R

Robin2:
I have been using the RF24 library with writeAckPayload. It makes it very easy to get data back from a device. In my system the data that is sent using write() is irrelevant. I am only interested in the data that is returned as the ackPayload.

I have a master that write()s in turn to different slaves. The slave already has been "primed" with writeAckPayload() so when it recognizes a message intended for it it automatically sends the payload.

On the master, if the write is successful it then reads the received payload.

...R

Do you have more slaves? If so, how do you address the sent payload to particular slave? Let's say if I have 10 slaves and I want to retrieve the "primed" ack payload from the particular 1 of them, how would I do it?

My "slave" nodes sleeps most of the time (radio is powered down due to power consumption) and they wake up just to measure the temp/humidity/voltage and sends it to the master. I am not sure if I can do the same with the logic you use. I think the slave radios would have to be in some mode which is able to listen all the time and this would consume more power. I run my slave nodes over half year still with the same 2xAAA alkaline batteries.

Each slave has its own ID (pipe) so when the master uses write() it makes the call to the relevant slave.

Attached is a simplified version of the code I am using. I have tested the attached with an Uno and Mega. The TrackControl is the master.

...R

HandController.ino (1.69 KB)

TrackControl.ino (1.9 KB)

Thanks for sharing the sketch. OK, so theoretically I can have very large amount of unique pipe IDs and I will open writing pipe of the particular slave and write + read ack.

fanofard:
OK, so theoretically I can have very large amount of unique pipe IDs and I will open writing pipe of the particular slave and write + read ack.

Yep. I have only tried it with 3, but I see no reason why there can't be a large number.

If you are using the RF24 library you need to modify the write() function so it doesn't call powerDown().

...R

I spent several hours of my life with experimenting with these nrf24l01+ modules. I was on a good way of giving up my further projects with nrf24l01+ modules.
The story is I decided not to use writeAckPayload() method as in this project I just want sensors to transmit the values when they measure it. So I just need reliable write() method which will return true if the payload was successfully delivered to the receiver and false, in case it failed.
Guess what - it did not work. Even if I received the payload properly on the receiver without any data corruption, the write() function always returned false.

I do not know why, but I decided to experiment with the settings of the radio. And now here it is - all the time I used this setting (swap of the pipes variable on receiver/transmitter of course):

    radio.begin();
    radio.setRetries(0,15);
    radio.setPayloadSize(16);
    radio.setChannel(110);
    radio.setPALevel(RF24_PA_MAX);
    radio.setDataRate(RF24_250KBPS);
    radio.openWritingPipe(pipes[0]);
    radio.openReadingPipe(1,pipes[1]);

So nothing special. As a last step I changed radio.setDataRate(RF24_250KBPS); to radio.setDataRate(RF24_1MBPS); and guess what... It started to work! write() function returns true if payload is delivered successfully and false in case it is not delivered. This is very annoying as I did not find this in class documentation. I assumed write() function will simply work in all speed modes.

I do not know why it does not work properly with 250KBPS - whether my nrf24l01+ modules are somehow "broken", but I think they are not - as they work properly in 1MB and 2MB speeds... Maybe a bug in the tmrh20's library? Or something else I did not realized...

And this is the line that causes the trouble

radio.setRetries(0,15);
There is a restriction on the length of ARD when using ACK packets with payload. The ARD time must
never be shorter than the sum of the startup time and the time on-air for the ACK packet:
• For 2Mbps data rate and 5 byte address; 15 byte is maximum ACK packet payload length for
ARD=250μs (reset value).
• For 1Mbps data rate and 5 byte address; 5 byte is maximum ACK packet payload length for
ARD=250μs (reset value).
ARD=500μs is long enough for any ACK payload length in 1 or 2Mbps mode.
• For 250kbps data rate and 5byte address the following values apply:
1500μs All ACK payload sizes
1250μs < 24
1000μs < 16
750μs < 8
500μs Empty ACK with no payload

I spent several hours of my life with experimenting with these nrf24l01+ modules. I was on a good way of giving up my further projects with nrf24l01+ modules.

Your time probably would have been better spent reading the NRF24L01+ datasheet, rather than the "class documentation".

Whandall:
And this is the line that causes the trouble

radio.setRetries(0,15);
There is a restriction on the length of ARD when using ACK packets with payload. The ARD time must

never be shorter than the sum of the startup time and the time on-air for the ACK packet:
• For 2Mbps data rate and 5 byte address; 15 byte is maximum ACK packet payload length for
ARD=250μs (reset value).
• For 1Mbps data rate and 5 byte address; 5 byte is maximum ACK packet payload length for
ARD=250μs (reset value).
ARD=500μs is long enough for any ACK payload length in 1 or 2Mbps mode.
• For 250kbps data rate and 5byte address the following values apply:
1500μs All ACK payload sizes
1250μs < 24
1000μs < 16
750μs < 8
500μs Empty ACK with no payload

Thanks much!!! This solved the 250KBPS problem.

I read the nrf24L01+ datasheet and there the Enshanced shockburst is explained as:

  • PTX transmits the packet to the PRX
  • PTX listens and awaiting an ACK packet from PRX
  • PRX receives the packet and sends the ACK to the PTX
  • PTX receives the ACK packet

In case PTX will not receive the ACK packet, then based on the settings the transmission is repeated several time (max 15).

But I think here I have a problem:

  • My PTX transmits the packet to the PRX
  • PRX receives the packet and sends ACK back to the PTX
  • But PTX will not receive the ACK packet (lond distance, interference, etc...)

--> The result is my receiver has proper data and my sender thinks the data was not sent successfully (write function returns false). I have a logic in my code which will repeat the write couple of times in case it was not successful. Then I have several times the same data on the receiver side. This occurs with a longer distances between the nodes.
I think I can't avoid this. Or am I wrong? (my senders are low power nodes which sleeps most of the time, so I can't implement the writeAckPayload which would I initiate the sending from the master node with)

fanofard:
(my senders are low power nodes which sleeps most of the time, so I can't implement the writeAckPayload which would I initiate the sending from the master node with)

I'm certainly not an expert but that seems perverse. Surely using ackPayload would reduce the uptime ?

If that was my project I would get it working without sleeping first.

...R

Robin2:
I'm certainly not an expert but that seems perverse. Surely using ackPayload would reduce the uptime ?

If that was my project I would get it working without sleeping first.

...R

You most likely did not understand me (sorry for my English, I am not an English-speaking man).
I already have the temperature monitoring wireless nodes in place for more than a half year with decent results. What I was recently trying to do was an optimisation and better reliability of my setup. In the past I did not understand the Enhanced Shock Burst of nrf24l01+, so I wanted to rewrite my code more clean and reliable.

And in my previous post I meant that I can't use the mode when I will send back the measured values in the ACK payload. Theoretically I could do it in case my wireless sensors are up all the time. Then I would pre-load the measured values in the ACK payload and then in some interval I would send a payload from the master node to the sensors (one by one) and the sensors would return the values back in the ACK payload. If it fails, then I would re-send the request from the master to the sensor again several times till the transmission is without an error.
The problem is my wireless sensor sleep most of the time, so they do not listen. So the wireless nodes have to send the values and this is what I was describing in my previous post. Wireless sensor will send the values in the payload and there can be a situation when the payload is received successfully, but the ACK packet will not arrive back to the sensor, so the sensor will consider the receiver did not receive it...

I think your nodes could just send their data without any acknowledgements, maybe more than once per wakeup.

fanofard:
The problem is my wireless sensor sleep most of the time, so they do not listen. So the wireless nodes have to send the values and this is what I was describing in my previous post. Wireless sensor will send the values in the payload and there can be a situation when the payload is received successfully, but the ACK packet will not arrive back to the sensor, so the sensor will consider the receiver did not receive it...

I'm having trouble making sense of all that, especially when I take the Title of the Thread into account.

Maybe you can draw a diagram showing how you want things to work and post a photo of the drawing.

I assume one of the devices is not asleep and it is either polling the others, or listening for the others.

If it is just listening all the time I can't understand where the concept of ackPayload comes in.

If it is polling the other devices then it seems to me if one of them is asleep, or not ready to listen (i.e. has not got ackPayload loaded) then it just moves on to the next one.

...R

If the NRF would be kept powered and in PRX mode (bad from a battery viewpoint),
it should even be possible to wake the cpu by a packet reception (with ack-data if wished).

But I did not experiment with sleeping modes of the arduino or power modes of the NRF yet.

My setup is:

1. Master node: arduino+nrf24l01++ethernet module+several temperature sensors ds18b20 on 1-wire
--> This node listens on the radio all the time and once per 15 minutes it also reads the ds18b20 temperatures on the 1-wire bus and calls a script via ETH which saves the temperatures into a database
--> In case there is available data on radio (the data from the wireless sensor nodes), it reads the data and stores them via ETH into a database.
--> This node is powered from the electricity via AC adaptor

2. Wireless sensors (I have 8 such sensors): atmega328p+nrf24l01++DHT22 temp/humi sensors.
--> These nodes are powered from 2xAAA batteries.
--> The sensors and also nrf24l01+ are powered down in the sleep mode. They consumes just a few microampers in this mode. I use atmega's 8-seconds watchdog, so the atmega wakes up every 8s and either falls again into the sleep mode (based on the count of previous wake-ups) or perform a measrurement of the temperature which are then sent via nrf24l01+ to the Master node described above.
--> I set it up the measurements and the transmissions are done every 5 minutes (37th watchdog wakeup)

This setup works quite good from the power consumption point of view. I run the wireless nodes since 2.4.2015 till now, what is ~9 months and the batteries voltage is still in 2.8-2.9V range. That's more than I expected at the beginning.
The only "issue" is I have either duplicated values being sent because sometimes wireless sensor thinks the transmission failed (false returned from write() function even if the Master node received the data correctly -- this is what I tried to describe in my posts above).

Sorry the confusing Topic of this thread. I initially did not know how does the automatic packet handling and retransmission in nrf24l01+ hardware work. Now I know the writeAckPayload() function is has to be used only in case I want to add a custom payload to the ACK packet. So I do not need to use this function as I do not want any custom payload in the ACK packets.

So now the only "issue" is that with longer distances, my wireless sensors think the payload was not transferred into the Master node (write() function returns false) but I can see in the Master node the payload was received.
I think I will just remove the send verification part of the code in my wireless sensors and I will just use write() function once per 5 minutes with setRetries(15,15); setting. I will have some packet loss with larger distances, but this anyway I can't avoid.

Keeping the wireless sensors listening on the radio and let them wake up the atmega in case there is a payload received would most likely drain the batteries much much faster. So I did not consider this way.

fanofard:
I think I will just remove the send verification part of the code in my wireless sensors and I will just use write() function once per 5 minutes with setRetries(15,15); setting. I will have some packet loss with larger distances, but this anyway I can't avoid.

As far as I know if you are not bothering with verification there is no need for multiple retries - they are only necessary if a write failure is detected.

A simple write with a simple acknowledgement (no writeAckPayload) should work quickly and reliably. Personally I think it is worth exploring why that is not working.

You say that the Master is receiving the data even though the slave does not know that. I think there must be a risk that the data is invalid.

...R