Bug; Queued packets with nRF24L01+ (SOLVED)

I made these codes to send parameters from a computer to an arduino over wireless, using two nrf24 module. It works as follow:

Parameters are entered in a Python terminal then sent to an Arduino "Bridge" (through serial, then repeated over RF). Finally they are received by the Arduino "Controller", which send a feedback to the Bridge.

So, the parameters are first validated in python, then validated again in the controller, and the controller sends a feedback (either LISTENING, TIMEOUT or SUCCESS)

For exemple; in the Bridge serial I enter these 3 commands

PROFIL;2 (2 indicate that two parameters will follow)
(feedback: LISTENING)
1;2 (parameter 1)
3;4 (parameter 2)
(feedback: SUCCESS)

Overall it works fine, but the feedback are out of sync. Often they are not received immediatly, and seems to be stuck in some kind of buffer. So as soon as a new packet is sent, the queued one is received, as if it would be pushed out of a buffer. I looked at the Radiohead API for nRF24 module but I'm still clueless about why this is happenning. So there is the code;

Bridge code (repeater):

#include <SPI.h>
#include <RH_NRF24.h>
RH_NRF24 nrf24;
const unsigned long timeoutInterval = 30*1000L;
unsigned long previousTimeoutInterval;
uint8_t rf_data[11];
int profil[10][2];
char c;
char serial_data[11];
int n;
int i;

void setup() {
	delay(1000);
	Serial.begin(9600);
	Serial.setTimeout(150);
	Serial.println("BRIDGE");
	if (!nrf24.init()) Serial.println("init failed");
	if (!nrf24.setChannel(1)) Serial.println("setChannel failed");
	if (!nrf24.setRF(RH_NRF24::DataRate2Mbps, RH_NRF24::TransmitPower0dBm)) Serial.println("setRF failed");
}


void loop() {
	serial_listen();
	rf_listen();
} 

void serial_listen() {
	if (Serial.available())
	{
		while (Serial.available() > 0) { //Read string
			c = Serial.read();
			while (c != '\n')
			{
				if (c != (char)-1)
				{
					serial_data[i] = c;
					i++;
				}
			c = Serial.read();
			}
		}
		strcpy((char*)rf_data, serial_data);
		rf_query(); //Repeat string to controller
		memset(serial_data, 0, sizeof serial_data);
		i = 0;
	}
}

void rf_query() {
	delay(200);
	nrf24.send(rf_data, sizeof(rf_data));
	nrf24.waitPacketSent();
	Serial.print("sent: "); Serial.println((char*)rf_data);
}

void rf_listen() {
	if (nrf24.available())
	{
		uint8_t buf[RH_NRF24_MAX_MESSAGE_LEN];
		uint8_t len = sizeof(buf);
		if (nrf24.recv(buf, &len)) 
		{
			Serial.print("got: "); Serial.println((char*)buf);
			if (!strcmp((char*)buf, "LISTENING")) { Serial.println("Controller: LISTENING"); }
			else if (!strcmp((char*)buf, "TIMEOUT")) { Serial.println("Controller: TIMEOUT"); }
			else if (!strcmp((char*)buf, "SUCCESS")) { Serial.println("Controller: SUCCESS"); }
			else 
			{
			//Get temperature from controller
			}
		}
	}
}

Controller code (receiver)

#include <SPI.h>
#include <RH_NRF24.h>
RH_NRF24 nrf24;
uint8_t data[11];
int n; int i; int j; int arguments; int buffer;
byte saveArguments;
int profil[10][2];
char* param;
const unsigned long timeoutInterval = 10*1000L;
unsigned long previousTimeoutInterval;
byte timeout;

void setup()
{
	delay(1000);
	Serial.begin(9600);
	Serial.setTimeout(150);
	Serial.println("CONTROLLER");
	
	if (!nrf24.init()) Serial.println("nrf24: init failed");
	if (!nrf24.setChannel(1)) Serial.println("nrf24: setChannel failed");
	if (!nrf24.setRF(RH_NRF24::DataRate2Mbps, RH_NRF24::TransmitPower0dBm)) Serial.println("nrf24: setRF failed");
}

void loop()
{
	rf_listen();
}

void rf_query() {
	delay(200);
	nrf24.send(data, sizeof(data));
	nrf24.waitPacketSent();
	Serial.print("sent: "); Serial.println((char*)data);
}

void rf_sendString(int s) {
	switch (s) {
		case 0: strcpy((char*)data, "LISTENING"); break;
		case 1: strcpy((char*)data, "TIMEOUT   "); break;
		case 2: strcpy((char*)data, "SUCCESS   "); break;
	}
	rf_query();
}

void rf_listen() {
	if ((timeout == true) && (millis() - previousTimeoutInterval >= timeoutInterval)) {
		memset(profil, 0, sizeof(profil[0][0]) * 10 * 2);
		i = j = arguments = 0;
		rf_sendString(1); Serial.println("TIMEOUT");
		timeout = false;
	}

	if (nrf24.available())
	{
		uint8_t buf[RH_NRF24_MAX_MESSAGE_LEN];
		uint8_t len = sizeof(buf);
		if (nrf24.recv(buf, &len))
		{
			Serial.print("got: "); Serial.println((char*)buf);
			param = strtok((char*)buf, ";");
			while(param != NULL) //explode(param,";")
			{
				if (!strcmp(param, "PROFIL"))
				{
					memset(profil, 0, sizeof(profil[0][0]) * 10 * 2);
					previousTimeoutInterval = millis();
					saveArguments = timeout = true;
					rf_sendString(0); //Serial.println("LISTENING");
				}
				else if (saveArguments == true) {
					arguments = atoi(param);
					saveArguments = false;
				}
				else if ((arguments > 0) && (i < arguments)) //RF parameters -> 2D array
				{
					buffer = atoi(param);
					if (j <= 1) {
						if (j == 0 && buffer < 1) { buffer = 1; } //Parameter[i][0] = Time
						profil[i][j] = buffer;
						Serial.print("profil["); Serial.print(i); Serial.print("]["); Serial.print(j); Serial.print("] = ");  Serial.println(buffer);
					}
					if (j == 1) { i++; j = 0; } else { j++; }
					if (i == arguments) //Done entering array
					{
						i = j = arguments = 0;
						timeout = false;
						//tone(speakerPin,120,150); delay(200); tone(speakerPin,120,150);
						rf_sendString(2); //Serial.println("SUCCESS");
					}
				}
				
				param = strtok(NULL, ";"); //tells strtok to continue on the previous string
			}
		}
	}
}

I am not familiar with the Library you are using - only with TMRh20's RF24 library.

Can you post an example of the data you send and the data that you get back so I can understand the problem better.

What is the purpose of your project?

...R

The code is part of a PID Controller, to which I can send parameters from the computer, and log the average temperature every minute. Eventually I want to send differents command, to change the Kp-Ki-Kd, normal/reverse mode, etc. For now, I just set up the command PROFIL, which save heating instructions in a 2D array.

So for instance, if I send:
PROFIL;3
50;10
60;20
100;75

It tells the controller to go into AUTO mode, and heat to 50C for 10min, then 60C for 20min, and 100C for 75min... The first line sends the command (PROFIL), and the number of arguments that will follow (in that case, 3).

To illustrate the communication between the two arduinos (bridge and controller), here's how they would talk. [] = Bridge, () = Controller

[PROFIL;3]
(LISTENING)
[50;10]
[60;20]
[100;75]
(SUCCESS)

Or

[PROFIL;3]
(LISTENING)
[50;10]
[60;20]
(TIMEOUT) after 10sec

The problem is that the feedback (listening/delay/timeout) are often delayed until a new packet is sent.

So I explained how it should behave, but here's what it actually do:

(Bridge side):
BRIDGE
sent: PROFIL;2
got: LISTENING
sent: 1;2
sent: 3;4

(Controller side):
CONTROLLER
got: PROFIL;2
sent: LISTENING
got: 1;2
got: 3;4
sent: SUCCESS

Then, if I sent a new command;
(Bridge side):
BRIDGE
sent: PROFIL;2
got: LISTENING
sent: 1;2
sent: 3;4
sent: PROFIL;3
got: SUCCESS
got: LISTENING

(Controller side):
CONTROLLER
got: PROFIL;2
sent: LISTENING
got: 1;2
got: 3;4
sent: SUCCESS
got: PROFIL;3
sent: LISTENING
sent: TIMEOUT

It seems to me it would be much simpler to send all the data as a single message

perhaps P,3,50,10,60,20,100,75

...R

I thought about that, but I want to send up to 10 parameters, so in the worst case scenario it would not fit into a single message.

For instance;
P;10;-100;120;-100;120;-100;120;-100;120;-100;120;-100;120;-100;120;-100;120;-100;120;-100;120

would use char[95], or for a more probable scenario:

P;10;100;120;100;120;100;120;100;120;100;120;100;120;100;120;100;120;100;120;100;120

would use char[85]. The max length I can send with nRF24 is char[28], so I thought it would be more versatile and take less memory to send the commands in pair, using only char[11]. Also, I think that the mentioned bug would still be a problem, as initially it works, and then the "lag" occur after some packets has been exchanged.

Bouba:
I thought about that, but I want to send up to 10 parameters, so in the worst case scenario it would not fit into a single message.

Have you considered a more efficient data format.

For example is the times or temperatures are all multiples of 10 there is no need to send the trailing zero.

If all the numbers are in the range -128 to +127 you can send then as chars (i.e. signed bytes).

I will bookmark this Thread to have another look at it in the morning - but don't expect anything :slight_smile:

...R

I have been having another quick look at the code in your Original Post.

My guess is that this (in the Bridge) is the problem

void loop() {
 serial_listen();
 rf_listen();
}

There is nothing in any of that to synchronize the two Arduinos.

If it was my project I would have code (like the 3rd example in Serial Input Basics) to receive the data and note when a complete message has been received from the PC.

Then I would send that message with the nRF24 and using a similar technique listen for a reply.

I would not send another PC message until two conditions are met {a} the reply has been received from the other Arduino and {b} a new message has been received from the PC.

Note that the code in the above link does not block the Arduino and your nRF code should not do so either. That way the nRF reply and the Python data could both be received "at the same time".

...R

At first I thougth that the lag came from the module buffering, but you pointed out the right direction by talking about "blocking" the arduino.

It turned out that the problem was caused by the command delay(200); in the function rf_query(). On both arduinos, I replaced it by a non-blocking timer and now the transmission works fine :slight_smile:

Still, I'll take a look at the document you suggested. Indeed that might help to improve my code.

Thanks a lot for your help :slight_smile:

Bouba:
the problem was caused by the command delay(200);

As a general rule NEVER use delay() in a program - apart from a quick-and-dirty test program.

...R