Back to Tutorial

ESP32 Joystick Controlled Robot Using ESP-NOW Protocol & L298N Motor Driver


  • 2025

ðŸĪ– ESP32 Joystick Controlled Robot Using ESP-NOW Protocol & L298N Motor Driver

Wireless bot control with speed adjustment via joystick tilt


📝 Introduction

In this project, we built a wireless robot controlled via a joystick and two ESP32 boards using the ESP-NOW communication protocol. Unlike Wi-Fi or Bluetooth, ESP-NOW enables fast, peer-to-peer data transfer between ESP32 devices using MAC addresses, making it ideal for real-time robot control.

The robot’s movement and speed are controlled through analog joystick input, and an L298N motor driver handles motor driving on the receiving end.


🔧 Hardware Used

ComponentDescription
2x ESP32One for sending joystick data, one for receiving and controlling motors
Joystick ModuleOutputs analog X/Y values
L298N Motor DriverDual H-Bridge to drive DC motors
Robot ChassisWith 2x DC motors
Jumper Wires + PowerBatteries or USB

🧠 How It Works

âžĪ Communication Setup

  • The sender ESP32 reads the X and Y analog values from a joystick.
  • These values are sent via ESP-NOW protocol to the receiver ESP32.
  • The receiver ESP32 processes the data and controls the L298N motor driver using the L298NX2 library.
  • Speed is adjusted based on how far the joystick is pushed.

âžĪ Direction Mapping

  • Y-axis forward → robot forward
  • Y-axis backward → robot reverse
  • X-axis left/right → turn accordingly
  • Small movements result in low speed; full push gives max speed.

ðŸ“Ą Setting Up ESP-NOW Communication

To communicate using ESP-NOW, both ESP32 boards need to:

  1. Be in WiFi Station mode
  2. Share the receiver’s MAC address
  3. Be added as peers in the ESP-NOW network

Use this code on the receiver ESP32 to get its MAC ad

🛠ïļ Wiring Summary

Sender (Joystick to ESP32)

Joystick PinESP32 Pin
VRxGPIO 34
VRyGPIO 35
GNDGND
VCC3.3V

Receiver (ESP32 to L298N)

L298N PinESP32 Pin
IN118
IN219
IN316
IN417
ENA4 (PWM)
ENB5 (PWM)

ðŸŽĨ Demo Output

  • Forward push: robot moves ahead
  • Full tilt → max speed
  • Side push: turns left or right
  • Release joystick → bot stops

Tested delay: ~50ms for excellent real-time response

Use this code on the receiver ESP32 to get its MAC ad

/*

  Rui Santos & Sara Santos – Random Nerd Tutorials

  Complete project details at https://RandomNerdTutorials.com/get-change-esp32-esp8266-mac-address-arduino/

  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.  

  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

#include <WiFi.h>
#include <esp_wifi.h>

void readMacAddress(){
  uint8_t baseMac[6];
  esp_err_t ret = esp_wifi_get_mac(WIFI_IF_STA, baseMac);
  if (ret == ESP_OK) {
    Serial.printf("%02x:%02x:%02x:%02x:%02x:%02x\n",
                  baseMac[0], baseMac[1], baseMac[2],
                  baseMac[3], baseMac[4], baseMac[5]);
  } else {
    Serial.println("Failed to read MAC address");
  }
}

void setup(){
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  WiFi.STA.begin();

  Serial.print("[DEFAULT] ESP32 Board MAC Address: ");
  readMacAddress();
}
 
void loop(){

}
Sender Code (Joystick to ESP32)
#include <esp_now.h>
#include <WiFi.h>

const int xPin = 34;         // VRx
const int yPin = 35;         // VRy
const int buttonPin = 14;    // SW
#define LED 2

uint8_t broadcastAddress[] = {0x30, 0xC6, 0xF7, 0x22, 0xEB, 0xD8};

typedef struct struct_message {
  char a[32];
  int x, y;
  bool button;
} struct_message;

struct_message myData;
esp_now_peer_info_t peerInfo;

bool isConnected = false;
unsigned long lastSendTime = 0;
unsigned long timeoutDuration = 2000; // 2 seconds timeout

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  if (status == ESP_NOW_SEND_SUCCESS) {
    isConnected = true;
    lastSendTime = millis(); // reset timeout
    Serial.println("✅ Delivery Success");
  } else {
    Serial.println("❌ Delivery Fail");
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(LED, OUTPUT);

  WiFi.mode(WIFI_STA);

  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }

  esp_now_register_send_cb(OnDataSent);

  memcpy(peerInfo.peer_addr, broadcastAddress, 6);
  peerInfo.channel = 0;  
  peerInfo.encrypt = false;

  if (esp_now_add_peer(&peerInfo) != ESP_OK) {
    Serial.println("Failed to add peer");
    return;
  }
}

void loop() {
  int xValue = analogRead(xPin);
  int yValue = analogRead(yPin);
  bool buttonPressed = digitalRead(buttonPin) == LOW;

  int mappedX = map(xValue, 0, 4095, 0, 100);
  int mappedY = map(yValue, 0, 4095, 0, 100);

  myData.x = mappedX;
  myData.y = mappedY;
  myData.button = buttonPressed;
  strcpy(myData.a, "Joystick Data");

  esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));

  if (result == ESP_OK) {
    Serial.printf("ðŸ“Ī Sent: X=%d, Y=%d, Btn=%s\n", mappedX, mappedY, buttonPressed ? "Pressed" : "Released");
  } else {
    Serial.println("ðŸšŦ Error sending the data");
  }

  // ⚠ïļ Simulate connection timeout (if no successful sends recently)
  if (millis() - lastSendTime > timeoutDuration) {
    isConnected = false;
  }

  digitalWrite(LED, isConnected ? HIGH : LOW);

  delay(200);
}
Receiver Code (Joystick to ESP32)
#include <L298NX2.h>
#include <esp_now.h>
#include <WiFi.h>

// Motor pins
#define ENA 4
#define IN_1 16
#define IN_2 17
#define ENB 5
#define IN_3 18
#define IN_4 19

#define LED 2
#define TIMEOUT 5000

L298NX2 mybot(ENA, IN_1, IN_2, ENB, IN_3, IN_4);

typedef struct struct_message {
  char a[32];
  int x, y;
  bool button;
} struct_message;

struct_message myData;
unsigned long lastRecvTime = 0;

void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
  memcpy(&myData, incomingData, sizeof(myData));

  Serial.print("Message: "); Serial.println(myData.a);
  Serial.print("Xaxis: "); Serial.println(myData.x);
  Serial.print("Yaxis: "); Serial.println(myData.y);
  Serial.print("Button: "); Serial.println(myData.button ? "Pressed" : "Released");
  Serial.println();

  digitalWrite(LED, HIGH);
  lastRecvTime = millis();

  // Basic Joystick-based Movement
  if (myData.y == 0 && myData.x!=0) {
    mybot.forward();
    Serial.println("Forward");
  }
  else if (myData.y==100 &&  myData.x!=100) {
    mybot.backward();
    Serial.println("Backward");
  }
  else if (myData.x==0 && myData.y!=0) {
    mybot.forwardA();
    mybot.backwardB();
    Serial.println("Left");
  }
  else if (myData.x==100 && myData.y!=100) {
    mybot.forwardB();
    mybot.backwardA();
    Serial.println("Right");
  }
  else {
    mybot.stop();
    Serial.println("Stop");
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(LED, OUTPUT);
  digitalWrite(LED, LOW);

  WiFi.mode(WIFI_STA);

  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }

  esp_now_register_recv_cb(esp_now_recv_cb_t(OnDataRecv));

  mybot.setSpeed(100);
  mybot.stop(); // Ensure motors are off on start
}

void loop() {
  if (millis() - lastRecvTime > TIMEOUT) {
    digitalWrite(LED, LOW);
    mybot.stop(); // stop bot if no data received
  }
}
ESP32 Joystick Controlled Robot Using ESP-NOW Protocol & L298N Motor Driver

Share this post

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Back to Tutorial