Hi everyone,
I'm building a long-range RC system (boat/plane project) and I need help connecting everything properly.
Setup:
TX: PS4 Controller → ESP32 (Bluepad32 library) → nRF24L01
RX: ESP8266 / ESP32 → nRF24L01 → Servos + ESC
What I want to achieve:
Read PS4 joystick values using Bluepad32
Map joystick values (-512 to 512) into 0–255 or PWM-friendly range
Send the data using nRF24L01 (RF24 library) in a structured packet
Receive and convert it into:
writeMicroseconds() for ESC
Servo control signals
Main issue:
I’m not sure about the best way to structure the data packet between ESP32 and ESP8266/ESP32, especially keeping it stable for RC control (latency + reliability).
If anyone has:
Example code
GitHub project
Or tips for Bluepad32 + RF24 integration
I’d really appreciate it ![]()
Thanks in advance!
I'd write
RX: nRF24L01 → ESP8266 / ESP32 → Servos + ESC
The basic packet is simple, just a number and a pointer to a bunch of bytes. No interpretation or meaning beyond N * 8 1s and 0s.
Typically a struct holding all the values you want to send is defined.
Then when you load it up with the readings or the mapped readings, you can nRF24L01 transmit the entire structure as a number of bytes.
If you wanna get fancy, an extra few bytes can be used to pass a CRC code, then the receiver can test the packet for integrity.
But that's overkill here, and maybe for any nRF24L01 link, as it may do more or less the same thing behind the scenes so to speak.
a7
Thanks alto777 for the insight! You're right, I'm already using a struct to package the axes (ch1 to ch7). My main concern now is the 'timing' between the Bluepad32 task and the RF24 transmission on the ESP32. Should I run the RF24 transmission inside the Bluepad32 callback or in the main loop to minimize latency? Also, if you have a known-stable 'Struct' example for 6-7 channels, that would be awesome."
I don't know enough to say!
There's probably not much to be gained by doing the nRF24L01 inside the callback to do that.
I would set a flag and check it in the free-running loop (no blocking, checks very frequently), send a packet.
But either way is not that much different or that much code, so getting empirical may be necessary.
You may find it to be unsatisfactory end-to-end no matter what you do.
There's some magic here you've glossed over
nRF24L01 → Servos
where I assume you will be using the transmitted values to synthesize the servo signals, which for simple servos run at 50 Hz pulses in the range of 1 .. 2 milliseconds.
Modern r/c protocols make that look positively 20th Century level. :-|
Transmit the full -512 to 512 range: I would just add to make it all positive 0 .. 1023.
Those 14 bytes (7 16 bit integers) don't need to be in a struct, just contiguous in memory as they woukd be in a simple array. You will pass the address of the array (or struct) and the byte count.
I understand the need for and roll of the bluepad32, but I am curious what informed your decision to use the nRF24L01. Nothing wrong with it, I've used it for this kind of thing, they work well. But later radio sets meant for r/c may do better.
Post any code, preferably a working version of something and let's see what it looks like.
a7
Welcome!
That sounds like a verry interesting project that should be a lot of fun! However, Please keep in mind that we are not a free design or code-writing service. We’re more than happy to help with your design or code, but we need you to make an initial attempt. Please design and write your code, then post it along with an annotated schematic and an explanation of what’s not working properly. There is also a for hire section if you want to pay for it.
Many will suggest you do it in sections and that is the correct way of doing it. We have no idea of your skill set or what resources you have available. If you are new to Arduino I highly recommend getting a copy of the Arduino Cookbook, A lot of what you will want to do is in there.
- Show Your Work First: Before asking for assistance, make an attempt to design or write the code yourself. Share your work along with details about what isn’t working. That includes a preliminary schematic that you drew.
- Provide Clear Documentation: Since we can’t see your project, share an annotated schematic (best) or a clear drawing of your setup. Clear Pictures are welcome, but avoid using Fritzing diagrams as they are wiring diagrams, not schematics, and are not ideal for troubleshooting.
- Include Technical Details: If there is specific hardware involved, include links to technical information. There are often many versions of similar components, so precise details are essential. I will assume the processor is a UNO. Many times the processor is stated as ESP32, please state which one and post a link to it.
- Reference Resources: For additional help, check out useful links and tutorials: Useful Links on Arduino Forum. Of course one of the better sources of information (in my opinion) is the Arduino Cookbook. Skim it cover to cover and stop on any project that interests you.
ESP32 > NRF24L01 tests transmitting a structure
// ESP32 > NRF24L01 transmitter test using a structure
// https://nrf24.github.io/RF24/
// UNO connections
// arduino SCK pin 11 goes to NRF24L10_pin SCK
// arduino MISO pin 12 goes to NRF24L10_pin MI
// arduino MOSI pin 13 goes to NRF24L10_pin MO
// NRF24L10 CE to arduino pin 9
// NRF24L10 CSN to arduino pin10
// ESP32 connections
// ESP32 SCK pin GPIO18 goes to NRF24L10_pin SCK
// ESP32 MISO pin GPIO19 goes to NRF24L10_pin MI
// ESP32 MOSI pin GPIO23 goes to NRF24L10_pin MO
// NRF24L10 CE to ESP32 pin GPIO4
// NRF24L10 CSN to ESP32 pin GPIO 5
// RP2040 connections
// RP2040 SPIO_SCK pin GP18 goes to NRF24L10_pin SCK
// RP2040 SPIO_RX pin GP16 goes to NRF24L10_pin MISO
// RP2040 SPIO_TX pin GP19 goes to NRF24L10_pin MOSI
// RP2040 pin SPIO_CSn GP17 to NRF24L10 CSN
// RP2040 pin GP20 to NRF24L10 CE
// RP2040 GND and 3.3V to NRF24L10 GND and VCC
#include <SPI.h>
#include "RF24.h"
struct __attribute__((packed)) Struct1 { // test data structure
uint8_t seq; // sequence number
uint8_t id; // node ID if multiple transmitters
uint16_t temperature;
float voltage;
uint8_t crc; // CRC check
} data = { 0, 1, 10, 3.14159, 0 }; // initial test data
// for this test using a simple checksum - replace with CRC check
unsigned char checksum(unsigned char *data, uint8_t *crc) {
unsigned char sum = 0;
while (data != crc) {
sum += *data;
data++;
}
return sum;
}
bool radioNumber = 0;
RF24 radio(4, 5); //CE and CSN
byte addresses[][6] = { "1Node", "2Node" };
void setup() {
Serial.begin(115200);
Serial.println("\n\nESP32 > NRF24L01 transmit structure");
radio.begin();
if (radio.isChipConnected())
Serial.println("\n\nTransmitter NF24 connected to SPI");
else Serial.println("\n\nNF24 is NOT connected to SPI");
radio.setChannel(125);
radio.setPALevel(RF24_PA_MIN);
radio.powerUp();
radio.setDataRate(RF24_1MBPS);
//radio.setDataRate(RF24_250KBPS);
if (radioNumber) {
radio.openWritingPipe(addresses[1]);
radio.openReadingPipe(1, addresses[0]);
} else {
radio.openWritingPipe(addresses[0]);
radio.openReadingPipe(1, addresses[1]);
}
radio.stopListening();
Serial.print("data sizeof (bytes): "); // print size of data structure in bytes
Serial.println(sizeof(Struct1));
radio.setPayloadSize(sizeof(Struct1));
}
// loop transmiting data packet
void loop() {
static int errors = 0, frames = 0;
data.crc = checksum((unsigned char *)&data, &data.crc);
Serial.print("Tx Frame: ");
Serial.print(++frames);
Serial.print(" seq: ");
Serial.print(data.seq);
Serial.print(" temperature: ");
Serial.print(data.temperature);
Serial.print(" voltage: ");
Serial.print(data.voltage);
Serial.print(" crc: 0x");
Serial.print(data.crc, HEX);
if (!radio.write(&data, sizeof(data))) { // transmit packet
Serial.print(F(" Tx failed "));
Serial.println(++errors);
} else {
Serial.print(" Tx OK! - errorrs ");
Serial.println(errors);
}
delay(1000);
data.seq++; // update test data
data.temperature += 5;
data.voltage += 5.0f;
}
receiver
// NRF24L01 receiver test using a structure
// UNO connections
// arduino SCK pin 11 goes to NRF24L10_pin SCK
// arduino MISO pin 12 goes to NRF24L10_pin MI
// arduino MOSI pin 13 goes to NRF24L10_pin MO
// NRF24L10 CE to arduino pin 9
// NRF24L10 CSN to arduino pin10
// ESP32 connections
// ESP32 SCK pin GPIO18 goes to NRF24L10_pin SCK
// ESP32 MISO pin GPIO19 goes to NRF24L10_pin MI
// ESP32 MOSI pin GPIO23 goes to NRF24L10_pin MO
// NRF24L10 CE to ESP32 pin GPIO4
// NRF24L10 CSN to ESP32 pin GPIO 5
#include <SPI.h>
#include <RF24.h>
struct __attribute__((packed)) Struct1 { // test data structure
uint8_t seq; // sequence number
uint8_t id; // node ID if multiple transmitters
uint16_t temperature;
float voltage;
uint8_t crc; // CRC check
} data;
// for this test using a simple checksum - replace with CRC check
unsigned char checksum(unsigned char *data, uint8_t *crc) {
unsigned char sum = 0;
while (data != crc) {
sum += *data;
data++;
}
return sum;
}
#define CE_PIN 4
#define CSN_PIN 5
bool radioNumber = 1;
const uint8_t pipes[][6] = { "1Node", "2Node" };
RF24 radio(CE_PIN, CSN_PIN);
char dataReceived[10]; // this must match dataToSend in the TX
bool newData = false;
//===========
void setup() {
Serial.begin(115200);
radio.begin();
if (radio.isChipConnected())
Serial.println("\n\nReceiver NF24 connected to SPI");
else {
Serial.println("\n\nNF24 is NOT connected to SPI");
while (1)
;
}
radio.setChannel(125);
// radio.setDataRate(RF24_1MBPS);
radio.setDataRate(RF24_250KBPS);
radio.printDetails();
if (!radioNumber) {
radio.openWritingPipe(pipes[0]);
radio.openReadingPipe(1, pipes[1]);
} else {
radio.openWritingPipe(pipes[1]);
radio.openReadingPipe(1, pipes[0]);
}
radio.startListening();
Serial.print("data sizeof (bytes): "); // print size of data structure in bytes
Serial.println(sizeof(Struct1));
radio.setPayloadSize(sizeof(Struct1));
}
//=============
void loop() {
static uint8_t seq_check = 0;
static int crcErrors = 0, seqErrors = 0, frames = 0;
if (radio.available()) {
while (radio.available()) {
int length = radio.getPayloadSize();
Serial.print("received ");
Serial.print(length);
if (length != sizeof(data)) {
Serial.println(" bytes - invalid packet size!");
return;
}
Serial.print(" bytes Frame No: ");
Serial.print(++frames);
radio.read(&data, sizeof(data));
Serial.print(" temperature: ");
Serial.print(data.temperature);
Serial.print(" voltage: ");
Serial.print(data.voltage);
Serial.print(" crc: 0x");
Serial.print(data.crc, HEX);
if (checksum((unsigned char *)&data, &data.crc) == data.crc)
Serial.print(" CRC OK ");
else {
Serial.print(" CRC ERROR! ");
crcErrors++;
}
Serial.print(" errors ");
Serial.print(crcErrors);
Serial.print(" seq: ");
Serial.print(data.seq);
if (seq_check == data.seq) Serial.print(" seq OK - ");
else {
Serial.print(" seq error! expected ");
Serial.print(seq_check);
seqErrors++;
}
Serial.print(" - seq errors ");
Serial.println(seqErrors);
seq_check = ++data.seq;
}
}
}
transmitter serial monitor output
Transmitter NF24 connected to SPI
data sizeof (bytes): 9
Tx Frame: 1 seq: 0 temperature: 10 voltage: 3.14 crc: 0x73 Tx OK! - errorrs 0
Tx Frame: 2 seq: 1 temperature: 15 voltage: 8.14 crc: 0x8B Tx OK! - errorrs 0
Tx Frame: 3 seq: 2 temperature: 20 voltage: 13.14 crc: 0xE1 Tx OK! - errorrs 0
Tx Frame: 4 seq: 3 temperature: 25 voltage: 18.14 crc: 0xA Tx OK! - errorrs 0
Tx Frame: 5 seq: 4 temperature: 30 voltage: 23.14 crc: 0x38 Tx OK! - errorrs 0
Tx Frame: 6 seq: 5 temperature: 35 voltage: 28.14 crc: 0x66 Tx OK! - errorrs 0
Tx Frame: 7 seq: 6 temperature: 40 voltage: 33.14 crc: 0x2 Tx OK! - errorrs 0
Tx Frame: 8 seq: 7 temperature: 45 voltage: 38.14 crc: 0x1C Tx OK! - errorrs 0
Tx Frame: 9 seq: 8 temperature: 50 voltage: 43.14 crc: 0x36 Tx OK! - errorrs 0
Tx Frame: 10 seq: 9 temperature: 55 voltage: 48.14 crc: 0x50 Tx OK! - errorrs 0
Tx Frame: 11 seq: 10 temperature: 60 voltage: 53.14 crc: 0x6A Tx OK! - errorrs 0
Tx Frame: 12 seq: 11 temperature: 65 voltage: 58.14 crc: 0x84 Tx OK! - errorrs 0
Tx Frame: 13 seq: 12 temperature: 70 voltage: 63.14 crc: 0x9E Tx OK! - errorrs 0
Tx Frame: 14 seq: 13 temperature: 75 voltage: 68.14 crc: 0xE9 Tx OK! - errorrs 0
Tx Frame: 15 seq: 14 temperature: 80 voltage: 73.14 crc: 0xF9 Tx OK! - errorrs 0
Tx Frame: 16 seq: 15 temperature: 85 voltage: 78.14 crc: 0x9 Tx OK! - errorrs 0
Tx Frame: 17 seq: 16 temperature: 90 voltage: 83.14 crc: 0x19 Tx OK! - errorrs 0
Tx Frame: 18 seq: 17 temperature: 95 voltage: 88.14 crc: 0x29 Tx OK! - errorrs 0
......
Tx Frame: 1345 seq: 64 temperature: 6730 voltage: 6723.14 crc: 0xF7 Tx OK! - errorrs 0
Tx Frame: 1346 seq: 65 temperature: 6735 voltage: 6728.14 crc: 0x25 Tx OK! - errorrs 0
Tx Frame: 1347 seq: 66 temperature: 6740 voltage: 6733.14 crc: 0x53 Tx OK! - errorrs 0
Tx Frame: 1348 seq: 67 temperature: 6745 voltage: 6738.14 crc: 0x81 Tx OK! - errorrs 0
Tx Frame: 1349 seq: 68 temperature: 6750 voltage: 6743.14 crc: 0xAF Tx OK! - errorrs 0
Tx Frame: 1350 seq: 69 temperature: 6755 voltage: 6748.14 crc: 0xDD Tx OK! - errorrs 0
Tx Frame: 1351 seq: 70 temperature: 6760 voltage: 6753.14 crc: 0xC Tx OK! - errorrs 0
Tx Frame: 1352 seq: 71 temperature: 6765 voltage: 6758.14 crc: 0x3A Tx OK! - errorrs 0
Tx Frame: 1353 seq: 72 temperature: 6770 voltage: 6763.14 crc: 0x68 Tx OK! - errorrs 0
....
Tx Frame: 22779 seq: 250 temperature: 48364 voltage: 113893.14 crc: 0xCC Tx OK! - errorrs 0
Tx Frame: 22780 seq: 251 temperature: 48369 voltage: 113898.14 crc: 0x55 Tx OK! - errorrs 0
Tx Frame: 22781 seq: 252 temperature: 48374 voltage: 113903.14 crc: 0xDD Tx OK! - errorrs 0
Tx Frame: 22782 seq: 253 temperature: 48379 voltage: 113908.14 crc: 0x66 Tx OK! - errorrs 0
Tx Frame: 22783 seq: 254 temperature: 48384 voltage: 113913.14 crc: 0xEF Tx OK! - errorrs 0
Tx Frame: 22784 seq: 255 temperature: 48389 voltage: 113918.14 crc: 0x78 Tx OK! - errorrs 0
Tx Frame: 22785 seq: 0 temperature: 48394 voltage: 113923.14 crc: 0x0 Tx OK! - errorrs 0
Tx Frame: 22786 seq: 1 temperature: 48399 voltage: 113928.14 crc: 0x89 Tx OK! - errorrs 0
Tx Frame: 22787 seq: 2 temperature: 48404 voltage: 113933.14 crc: 0x11 Tx OK! - errorrs 0
receiver serial monitor output
ESP32 RECEIVER
Receiver NF24 connected to SPI
SPI Speedz = 10 Mhz
STATUS = 0x0e RX_DR=0 TX_DS=0 MAX_RT=0 RX_P_NO=7 TX_FULL=0
RX_ADDR_P0-1 = 0x65646f4e32 0x65646f4e31
RX_ADDR_P2-5 = 0xc3 0xc4 0xc5 0xc6
TX_ADDR = 0x65646f4e32
RX_PW_P0-6 = 0x20 0x20 0x20 0x20 0x20 0x20
EN_AA = 0x3f
EN_RXADDR = 0x03
RF_CH = 0x7d
RF_SETUP = 0x27
CONFIG = 0x0e
DYNPD/FEATURE = 0x00 0x00
Data Rate = 250 KBPS
Model = nRF24L01+
CRC Length = 16 bits
PA Power = PA_MAX
ARC = 0
data sizeof (bytes): 9
received 9 bytes Frame No: 1 temperature: 10 voltage: 3.14 crc: 0x73 CRC OK errors 0 seq: 0 seq OK - - seq errors 0
received 9 bytes Frame No: 2 temperature: 15 voltage: 8.14 crc: 0x8B CRC OK errors 0 seq: 1 seq OK - - seq errors 0
received 9 bytes Frame No: 3 temperature: 20 voltage: 13.14 crc: 0xE1 CRC OK errors 0 seq: 2 seq OK - - seq errors 0
received 9 bytes Frame No: 4 temperature: 25 voltage: 18.14 crc: 0xA CRC OK errors 0 seq: 3 seq OK - - seq errors 0
received 9 bytes Frame No: 5 temperature: 30 voltage: 23.14 crc: 0x38 CRC OK errors 0 seq: 4 seq OK - - seq errors 0
received 9 bytes Frame No: 6 temperature: 35 voltage: 28.14 crc: 0x66 CRC OK errors 0 seq: 5 seq OK - - seq errors 0
received 9 bytes Frame No: 7 temperature: 40 voltage: 33.14 crc: 0x2 CRC OK errors 0 seq: 6 seq OK - - seq errors 0
received 9 bytes Frame No: 8 temperature: 45 voltage: 38.14 crc: 0x1C CRC OK errors 0 seq: 7 seq OK - - seq errors 0
.....
received 9 bytes Frame No: 1349 temperature: 6750 voltage: 6743.14 crc: 0xAF CRC OK errors 0 seq: 68 seq OK - - seq errors 0
received 9 bytes Frame No: 1350 temperature: 6755 voltage: 6748.14 crc: 0xDD CRC OK errors 0 seq: 69 seq OK - - seq errors 0
received 9 bytes Frame No: 1351 temperature: 6760 voltage: 6753.14 crc: 0xC CRC OK errors 0 seq: 70 seq OK - - seq errors 0
received 9 bytes Frame No: 1352 temperature: 6765 voltage: 6758.14 crc: 0x3A CRC OK errors 0 seq: 71 seq OK - - seq errors 0
received 9 bytes Frame No: 1353 temperature: 6770 voltage: 6763.14 crc: 0x68 CRC OK errors 0 seq: 72 seq OK - - seq errors 0
received 9 bytes Frame No: 1354 temperature: 6775 voltage: 6768.14 crc: 0x96 CRC OK errors 0 seq: 73 seq OK - - seq errors 0
received 9 bytes Frame No: 1355 temperature: 6780 voltage: 6773.14 crc: 0xC4 CRC OK errors 0 seq: 74 seq OK - - seq errors 0
received 9 bytes Frame No: 1356 temperature: 6785 voltage: 6778.14 crc: 0xF2 CRC OK errors 0 seq: 75 seq OK - - seq errors 0
received 9 bytes Frame No: 1357 temperature: 6790 voltage: 6783.14 crc: 0x20 CRC OK errors 0 seq: 76 seq OK - - seq errors 0
received 9 bytes Frame No: 1358 temperature: 6795 voltage: 6788.14 crc: 0x4F CRC OK errors 0 seq: 77 seq OK - - seq errors 0
.....
received 9 bytes Frame No: 22692 temperature: 47929 voltage: 113458.14 crc: 0x67 CRC OK errors 0 seq: 163 seq OK - - seq errors 0
received 9 bytes Frame No: 22693 temperature: 47934 voltage: 113463.14 crc: 0xEF CRC OK errors 0 seq: 164 seq OK - - seq errors 0
received 9 bytes Frame No: 22694 temperature: 47939 voltage: 113468.14 crc: 0x78 CRC OK errors 0 seq: 165 seq OK - - seq errors 0
received 9 bytes Frame No: 22695 temperature: 47944 voltage: 113473.14 crc: 0x0 CRC OK errors 0 seq: 166 seq OK - - seq errors 0
received 9 bytes Frame No: 22696 temperature: 47949 voltage: 113478.14 crc: 0x89 CRC OK errors 0 seq: 167 seq OK - - seq errors 0
received 9 bytes Frame No: 22697 temperature: 47954 voltage: 113483.14 crc: 0x11 CRC OK errors 0 seq: 168 seq OK - - seq errors 0
received 9 bytes Frame No: 22698 temperature: 47959 voltage: 113488.14 crc: 0x9A CRC OK errors 0 seq: 169 seq OK - - seq errors 0
received 9 bytes Frame No: 22699 temperature: 47964 voltage: 113493.14 crc: 0x22 CRC OK errors 0 seq: 170 seq OK - - seq errors 0
NOTE: if transmitter and receiver are the same processor (same type lengths, padding, etc) the structure
__attribute__((packed))
is not requred
Yeth. But it is overkill and would not be needed at the low level,of the radio packets.
These are small packets with trivial parsing, and the radio already uses CRC for link-level integrity.
In its standard configuration with auto-ack enabled, it also handles retries and rejection of bad packets, so additional error checking is unnecessary.
A CRC might be useful in the higher level logic for some other reasons. I don't see it, TBH, if you are writing the code on both sides of the link.
a7