FluxBench.AIShared project · read-only

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.