3 buttons resulting in 3 different actions over nRF24L01

Hello,

For my RC boat, I am using an Uno as my transmitter and ESP32 as my receiver. The idea is to have different modes of operation for the boat. On the transmitter side, I have 3 buttons, each connected to an LED to indicate what mode is active. I am using nRF24L01 for RF comms between the two. I'm struggling with the programming part. When the first button is pressed, I want to activate manual mode where joystick commands (x and y) are transmitted over to the receiver to influence a motor and a servo connected to the rudder. For the second mode, I'd like to have something like a heading hold mode where I can send a heading value through the serial monitor. I haven't yet decided what I want to do with the 3rd button but once I have an idea of how to successfully transmit and code this correctly, I'd like to have a 3rd functionality. The overall idea is that each LED will stay on as long as another button for a different mode is not selected. As of now, I can make the LEDs stay on but I am unable to command an output on the receiver's side. For the RF part, I have been going through tutorials and have been reading about sending data wirelessly using a struct format but I am relatively new to this so any guidance on that front would also be much appreciated. Thank you!

// Transmitter

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

#define BUTTON1 3
#define BUTTON2 4
#define BUTTON3 5
#define LED1 10
#define LED2 9
#define LED3 6

RF24 radio(7, 8); // CE, CSN pins for nRF24L01
const byte address[6] = "00001"; // Address for communication

bool button1State = false, button2State = false, button3State = false;
bool lastButton1 = false, lastButton2 = false, lastButton3 = false;
unsigned long debounceDelay = 50;
unsigned long lastDebounceTime1 = 0, lastDebounceTime2 = 0, lastDebounceTime3 = 0;

struct Payload {
  uint16_t mode; // 0: Manual, 1: Heading Hold, 2: Blink Mode
  uint16_t joystickX;
  uint16_t joystickY;
  uint16_t heading;
};

Payload data = {0, 0, 0, 0}; // Initial payload

void setup() {
  pinMode(BUTTON1, INPUT_PULLUP);
  pinMode(BUTTON2, INPUT_PULLUP);
  pinMode(BUTTON3, INPUT_PULLUP);
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  pinMode(LED3, OUTPUT);

  radio.begin();
  radio.openWritingPipe(address);
  radio.setPALevel(RF24_PA_LOW);
  radio.stopListening();
}

void loop() {
  // Button 1
  if (digitalRead(BUTTON1) == LOW) {
    if (millis() - lastDebounceTime1 > debounceDelay) {
      button1State = !button1State;
      button2State = false;
      button3State = false;
      lastDebounceTime1 = millis();
    }
  }
  lastButton1 = digitalRead(BUTTON1);

  // Button 2
  if (digitalRead(BUTTON2) == LOW) {
    if (millis() - lastDebounceTime2 > debounceDelay) {
      button2State = !button2State;
      button1State = false;
      button3State = false;
      lastDebounceTime2 = millis();
    }
  }
  lastButton2 = digitalRead(BUTTON2);

  // Button 3
  if (digitalRead(BUTTON3) == LOW) {
    if (millis() - lastDebounceTime3 > debounceDelay) {
      button3State = !button3State;
      button1State = false;
      button2State = false;
      lastDebounceTime3 = millis();
    }
  }
  lastButton3 = digitalRead(BUTTON3);

  // Set LEDs and mode
  if (button1State) {
    digitalWrite(LED1, HIGH);
    digitalWrite(LED2, LOW);
    digitalWrite(LED3, LOW);
    data.mode = 0; // Manual Mode
    data.joystickX = analogRead(A0);
    data.joystickY = analogRead(A1);
  } else if (button2State) {
    digitalWrite(LED1, LOW);
    digitalWrite(LED2, HIGH);
    digitalWrite(LED3, LOW);
    data.mode = 1; // Heading Hold
    if (Serial.available()) {
      data.heading = Serial.parseInt(); // Read heading from Serial Monitor
    }
  } else if (button3State) {
    digitalWrite(LED1, LOW);
    digitalWrite(LED2, LOW);
    digitalWrite(LED3, HIGH);
    data.mode = 2; // Blink Mode
  }

  // Send data to receiver
  radio.write(&data, sizeof(data));
  delay(50);
}


// Receiver

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <ESP32Servo.h>

#define BUILTIN_LED 2

const int servo1_pin = 14; // Servo pin

Servo servo1;

RF24 radio(4, 5); // CE, CSN pins for nRF24L01
const byte address[6] = "00001";

struct Payload {
  uint16_t mode; // 0: Manual, 1: Heading Hold, 2: Blink Mode
  uint16_t joystickX;
  uint16_t joystickY;
  uint16_t heading;
};

Payload data;

unsigned long lastBlinkTime = 0;
bool ledState = false;

void setup() {
  pinMode(BUILTIN_LED, OUTPUT);
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setPALevel(RF24_PA_LOW);
  radio.startListening();
}

void loop() {
  if (radio.available()) {
    radio.read(&data, sizeof(data));
  }

  if (data.mode == 0) {
    // Manual Control
    servo1.write(map(data.joystickX, 0, 1023, 0, 180)); // Control rudder
    analogWrite(16, map(data.joystickY, 0, 1023, 0, 255)); // Control motor
    digitalWrite(BUILTIN_LED, LOW);
  } else if (data.mode == 1) {
    // Heading Hold Mode
    digitalWrite(BUILTIN_LED, LOW);
    int headingError = data.heading - getCurrentHeading(); // Dummy stuff - replace with PID logic
    int correction = map(headingError, 0, 180, 0, 255);
    analogWrite(15, correction); // Adjust motor/servo
  } else if (data.mode == 2) {
    // Blink Mode
    if (millis() - lastBlinkTime >= 1000) {
      ledState = !ledState;
      digitalWrite(BUILTIN_LED, ledState);
      lastBlinkTime = millis();
    }
  }
}

int getCurrentHeading() {
  // Dummy function for now
  return 0;
}

Update: So I've managed to get the built in LED on the ESP32 to blink when I press the 3rd button on the Uno. Some success!

look over the code below for dealing with buttons,

as for sending data, hoe do you know which byte if the first byte? do you need a preamble to delimit the start of a msg that is synchonized to

// check multiple buttons and toggle LEDs

enum { Off = HIGH, On = LOW };

byte pinsLed [] = { 10, 11, 12 };
byte pinsBut [] = { A1, A2, A3 };
#define N_BUT   sizeof(pinsBut)

byte butState [N_BUT];

// -----------------------------------------------------------------------------
int
chkButtons ()
{
    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        byte but = digitalRead (pinsBut [n]);

        if (butState [n] != but)  {
            butState [n] = but;

            delay (10);     // debounce

            if (On == but)
                return n;
        }
    }
    return -1;
}

// -----------------------------------------------------------------------------
void
loop ()
{
    switch (chkButtons ())  {
    case 2:
        digitalWrite (pinsLed [2], ! digitalRead (pinsLed [2]));
        break;

    case 1:
        digitalWrite (pinsLed [1], ! digitalRead (pinsLed [1]));
        break;

    case 0:
        digitalWrite (pinsLed [0], ! digitalRead (pinsLed [0]));
        break;
    }
}

// -----------------------------------------------------------------------------
void
setup ()
{
    Serial.begin (9600);

    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        pinMode (pinsBut [n], INPUT_PULLUP);
        butState [n] = digitalRead (pinsBut [n]);
    }

    for (unsigned n = 0; n < sizeof(pinsLed); n++)  {
        digitalWrite (pinsLed [n], Off);
        pinMode      (pinsLed [n], OUTPUT);
    }
}
1 Like

Thanks for your response, @gcjr, I really appreciate it.

I haven't had a chance to test out your code yet, hoping to do that tomorrow or day after after which I will provide an update. As for the sending data part, I'm simply wanting to send either joystick info or a number through the serial port.

Best send both in the same message. Much simpler to just send a dummy number or a dummy joystick value if no change is required.

1 Like

Hi @Paul_KD7HB, thanks for that. That's what I'm doing. I was trying out sending data as a structure but I think I'll send it as an array since I've managed to do that in the past.

And the real difference is?

From what I've read online, a structure will take all data types whereas an array will take only one type - would that be correct? And since I'm wanting to send joystick values along with numbers sent through the serial monitor, I'm now thinking a structure might be better suited?

why not ascii string terminated with a newline so that you know when a complete msg is received with the newline is received?

No need. nRF24L01 Transmission is packetized by the hardware with Header, Addressing, Payload, CRC, etc. All the packet overhead is stripped off at the RX end and the payload (32 bytes max) is presented to the host processor via SPI, in proper byte order. Also, with such a small available payload size, you usually can't afford to mess around sending ASCII strings.

You need to be careful. ESP32 (32-bit processor) and UNO (8-bit processor) will build the struct differently in memory because of differences in packing. Since the payload is sent as bytes over nRF24L01, what you have may not allow correct transfer of the struct between TX and RX. You can get around this by using a Packed struct:

struct __attribute__((packed)) Payload  {
  uint16_t mode; // 0: Manual, 1: Heading Hold, 2: Blink Mode
  uint16_t joystickX;
  uint16_t joystickY;
  uint16_t heading;
};
1 Like

Thanks @gfvalvo, that's what I was missing! My transmitter and receiver are now communicating! I still have some issues in my loop but I'll start another thread for that. Appreciate the assistance!

Thanks again for this, @gcjr, appreciate your inputs very much! I understand your code but being a newbie, I'm taking it one step at a time with functions. Tried out your code and it works (I'm trying to modify it so that pressing another button turns off the one that's on) just fine, just writing stuff out for now and gaining experience with C/C++ before moving on to functions. I also need to study the data types a little better. Appreciate your time as always!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.