nRF24 Rover Remote
ESP32 DevKit (WROOM-32)·Arduino C++·Updated Jul 27, 2026·by FluxBench TeamIncludes Lab + Schematic + PCB
Wireless remote control bench on a single ESP32: one nRF24L01+ radio plays a handheld controller sending a Command struct (sequence, throttle, steer) once a second to pipe address ROVR1, while a second nRF24L01+ is the rover receiver that prints each command. Midway the remote is deliberately misconfigured to write to pipe GHOST so you can watch write() fail and the packet get lost — the Lab simulator explains the pipe-address routing — then the pipe is restored. Bundled Lab bench, schematic and routed PCB included.
sketch.ino
// nRF24 Rover Remote — two nRF24L01+ radios on one bench
// The "remote" radio (CE=GPIO21, CSN=GPIO5) is a handheld controller that
// sends a Command struct (sequence, throttle %, steer angle) once a second
// to pipe address ROVR1. The "rover" radio (CE=GPIO4, CSN=GPIO15) listens
// on that pipe and prints each command. Halfway through, the remote is
// deliberately misconfigured to write to pipe GHOST — nobody listens there,
// so write() returns false and the packet is lost (watch the Lab warning),
// then the pipe is restored. Both modules share VSPI: MISO=GPIO19,
// MOSI=GPIO23, SCK=GPIO18, 3V3, GND. IRQ pins are unused (polling).
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
RF24 remote(21, 5); // handheld remote: CE, CSN
RF24 rover(4, 15); // rover receiver: CE, CSN
const byte PIPE_ROVER[6] = "ROVR1";
struct Command {
int seq;
int throttle;
int steer;
};
Command cmd;
Command rx;
int seq = 0;
void setup() {
Serial.begin(115200);
remote.begin();
remote.setPALevel(RF24_PA_LOW);
remote.openWritingPipe(PIPE_ROVER);
remote.stopListening();
Serial.println("Remote online (CE=21, CSN=5), writing to pipe ROVR1");
rover.begin();
rover.setPALevel(RF24_PA_LOW);
rover.openReadingPipe(1, PIPE_ROVER);
rover.startListening();
Serial.println("Rover online (CE=4, CSN=15), listening on pipe ROVR1");
}
void loop() {
if (seq >= 8) { delay(1000); return; }
seq++;
if (seq == 4) {
remote.openWritingPipe("GHOST"); // misconfigured: nobody listens here
Serial.println("Remote misconfigured: writing pipe changed to GHOST");
}
if (seq == 5) {
remote.openWritingPipe(PIPE_ROVER); // fixed
Serial.println("Remote writing pipe restored to ROVR1");
}
cmd.seq = seq;
cmd.throttle = 20 + seq * 5;
cmd.steer = (seq % 3) * 15 - 15;
bool ok = remote.write(&cmd, sizeof(cmd));
if (!ok) {
Serial.println("REMOTE TX failed - no radio listening on this pipe");
}
delay(20);
if (rover.available()) {
rover.read(&rx, sizeof(rx));
Serial.print("ROVER cmd #");
Serial.print(rx.seq);
Serial.print(" throttle ");
Serial.print(rx.throttle);
Serial.print(" % steer ");
Serial.print(rx.steer);
Serial.println(" deg");
} else {
Serial.println("ROVER no command received (packet lost)");
}
delay(1000);
}Built with FluxBench.AI — the AI workbench for embedded projects.