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