Build Your Own ESP32 RC Car: Wireless Joystick Controller Guide for Beginners

A top-down view of a DIY ESP32 RC car project featuring a clear acrylic chassis with four yellow wheels, an L298N motor driver, and a 18650 battery pack. In the foreground, a second ESP32 microcontroller is mounted on a white breadboard and wired to a joystick module, serving as the remote controller for the build.

The “I Built My Own RC Car” Moment

I still remember holding the joystick for the first time, pushing it forward, and watching the little robot chassis zoom across my room.

Not a toy from a store. Not a kit where everything just snaps together. Something I built. Wires, motors, ESP32s, code—all of it.

That feeling? It never gets old.

In this tutorial, I’m going to show you exactly how to build your own wireless ESP32 RC car using two ESP32 boards and a joystick module. One ESP32 acts as the transmitter (controller) , reading the joystick. The other is the receiver (car) , driving four DC motors. They talk to each other wirelessly using ESP-NOW—fast, simple, and perfect for beginners. That’s how you build your ESP32 RC Car.

What You’ll Build

 
 
Component (Side)What It Does
Controller (TX)Reads joystick X/Y values, sends them wirelessly
Car (RX)Receives commands, controls 4 motors for smooth driving
CommunicationESP-NOW protocol—low latency, direct connection

The Flow:
You move joystick → ESP32 reads analog values → sends data wirelessly → second ESP32 receives → motor driver spins wheels → car moves | ESP32 RC CAR

Why Build This Instead of Buying a ESP32 RC Car?

 
 
ReasonWhy It Matters
Learn Wireless CommsUnderstand how ESP-NOW or WiFi works for real projects
CustomizableAdd FPV camera, sensors, stronger motors—your call
Cheaper~2500-3500 BDT total, vs. 5000+ for a comparable toy
RepairableSomething breaks? You know exactly how to fix it
Portfolio-ReadyA perfect project for engineering students and hobbyists

For a BUET student budget? This is a no-brainer.

What You’ll Need (Complete Parts List)

For the Controller (Transmitter)

 
 
ComponentSpecs / NotesApprox Price (BDT)
ESP32 Development BoardAny version works (I used a standard 30-pin)600-800
Joystick ModuleXY axes + center push button (SW)80-150
BreadboardSmall 170 or 400 points50-100
Jumper WiresMale-to-Female and Male-to-Male50-100
USB CableFor programming and power (optional battery later)100-200

For the ESP32 RC Car (Receiver)

 
 
ComponentSpecs / NotesApprox Price (BDT)
ESP32 Development BoardSecond one, identical to the first600-800
4-Wheel Car ChassisIncludes a frame, 4 DC motors, and wheels800-1200
Motor Driver ModuleL298N (easy to wire) or L9110S (compact)350-550
18650 Batteries2x, for 7.4V power500-700
Battery HolderFor 2x 18650 batteries100-200
Jumper WiresMale-to-Female, for motor driver connections50-100
IMG 20251127 141614675 AE.jpg scaled e1775706692816
 
 
ComponentWhere to Find
ESP32 Development Board ×2AliExpress / Amazon
4-Wheel Car ChassisAliExpress / Amazon
L298N Motor DriverAliExpress / Amazon
Joystick ModuleAliExpress / Amazon
18650 Batteries + HolderAliExpress / Amazon

Step 1: Wiring the Controller (Transmitter)

This part is simple. The joystick module has 5 pins:

  • GND → ESP32 GND

  • 5V (or VCC) → ESP32 VIN (5V)

  • VRx (X-axis, analog) → ESP32 GPIO 34 (ADC)

  • VRy (Y-axis, analog) → ESP32 GPIO 35 (ADC)

  • SW (button, digital) → ESP32 GPIO 15

A split-screen image showing the physical assembly and a hand-drawn wiring diagram for an ESP32 RC car controller. The diagram illustrates the connections between a joystick module and an ESP32, mapping SW to D32, VRy to D35, VRx to D34, GND to GND, and Vin to VN. The left side shows the actual joystick wired on a breadboard.

Step 2: Wiring the ESP32 RC Car (Receiver)

This is where the robot comes to life. The motor driver is the bridge between the ESP32 and the powerful motors.

L298N Motor Driver to ESP32 Connections

 
 
L298N TerminalConnects To (ESP32)Notes
IN1GPIO 26Left Motor Direction
IN2GPIO 27Left Motor Direction
IN3GPIO 14Right Motor Direction
IN4GPIO 12Right Motor Direction
ENAGPIO 25Left Motor Speed (PWM)
ENBGPIO 33Right Motor Speed (PWM)
+5VESP32 VIN (optional)Can power ESP32 if motors not too heavy
GNDESP32 GNDCommon ground—critical!

L298N to Motors and Battery

 
 
L298N TerminalConnects To
OUT1 & OUT2Left-side motors (parallel)
OUT3 & OUT4Right-side motors (parallel)
12V InputBattery + (7.4V from 18650s)
GNDBattery –
A comprehensive hand-drawn circuit diagram for an ESP32 RC car. The schematic shows the connections between an ESP32 microcontroller, a 7.4V battery with a power switch, a voltage regulator, and a motor controller. The controller is wired to four DC motors, with specific GPIO pins D12, D19, D27, and D26 mapped to the motor driver inputs to manage steering and speed.

Step 3: The Code – How the Magic Happens

The code is split into two parts. Let’s break down the logic first.

The Controller Logic:

  1. Read the analog values from VRx and VRy (0-4095).

  2. Map these values to a joystick position (Forward, Backward, Left, Right, Stop).

  3. Package this command into a simple data structure.

  4. Send the data wirelessly using ESP-NOW to the car’s MAC address.

The Car Logic:

  1. Listen for incoming ESP-NOW data packets.

  2. Read the command (e.g., ‘F’ for Forward).

  3. Based on the command, set the 4 motor driver pins (IN1-IN4) HIGH or LOW.

  4. Use PWM on the ENA and ENB pins to control speed smoothly.

Upload This MAC Address Scanner Sketch

Connect the car ESP32 (receiver) to your computer via USB. Copy and paste this code into Arduino IDE and upload it:

getMACaddressCar.ino
#include "WiFi.h"

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_MODE_STA);
  delay(500);
}

void loop() {
    Serial.println(WiFi.macAddress());
    delay(1000);
}

Step 4: The Code – Controller (Transmitter)

This code runs on the ESP32 that has the joystick attached. It reads the joystick position and sends commands wirelessly to the car.

Copy and paste this code into your Arduino IDE. Upload it to the controller ESP32 (the one with the joystick).

Normal_RC_Car_Controller.ino
#include <Arduino.h>

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

#define JOY_X_PIN 34  // ADC input for joystick X
#define JOY_Y_PIN 35  // ADC input for joystick Y

// Receiver MAC Address (Your Car)
uint8_t receiverAddress[] = {0xEC, 0xE3, 0x34, 0x1A, 0xF4, 0x04};

typedef struct struct_message {
  int16_t x;  // Joystick X value
  int16_t y;  // Joystick Y value
} struct_message;

struct_message myData;

void onSent(const uint8_t *macAddr, esp_now_send_status_t status) {
  Serial.print("Send Status: ");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Success" : "Fail");
}

void setup() {
  Serial.begin(115200);
  
  // Initialize joystick pins
  pinMode(JOY_X_PIN, INPUT);
  pinMode(JOY_Y_PIN, INPUT);
  
  // Set WiFi mode
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();

  // Set channel for better reliability
  esp_wifi_set_promiscuous(true);
  esp_wifi_set_channel(1, WIFI_SECOND_CHAN_NONE);
  esp_wifi_set_promiscuous(false);

  // Initialize ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("ESP-NOW init failed");
    while (1);
  }
  
  // Register send callback
  esp_now_register_send_cb(onSent);

  // Add peer
  esp_now_peer_info_t peerInfo = {};
  memcpy(peerInfo.peer_addr, receiverAddress, 6);
  peerInfo.channel = 1;
  peerInfo.encrypt = false;

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

  Serial.println("ESP-NOW Transmitter Ready");
  Serial.print("Target MAC: ");
  char macStr[18];
  snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X",
           receiverAddress[0], receiverAddress[1], receiverAddress[2],
           receiverAddress[3], receiverAddress[4], receiverAddress[5]);
  Serial.println(macStr);
}

void loop() {
  // Read joystick values (0-4095)
  int rawX = analogRead(JOY_X_PIN);
  int rawY = analogRead(JOY_Y_PIN);

  // Send raw values (0-4095) to maintain your existing motor control logic
  myData.x = rawX;
  myData.y = rawY;

  // Send data via ESP-NOW
  esp_err_t result = esp_now_send(receiverAddress, (uint8_t *)&myData, sizeof(myData));

  if (result == ESP_OK) {
    Serial.printf("Sent - X:%4d Y:%4d\n", rawX, rawY);
  } else {
    Serial.println("Send error");
  }

  delay(50);  // Send every 50ms
}

Step 5: The Code – Receiver (Car)

This code runs on the ESP32 mounted on the car. It receives joystick commands and controls the motors via the L298N driver.

Copy and paste this code into your Arduino IDE. Upload it to the receiver ESP32 (the one on the car chassis).

Normal_RC_Car_Vehicle.cpp
#include <esp_now.h>
#include <WiFi.h>

// L298N Motor Control Pins (Your original pins)
#define out1 12    // Left Motor Direction 1
#define out2 14    // Left Motor Direction 2  
#define out3 27    // Right Motor Direction 1
#define out4 26    // Right Motor Direction 2

typedef struct struct_message {
  int16_t x;  // Joystick X value (0-4095)
  int16_t y;  // Joystick Y value (0-4095)
} struct_message;

struct_message incomingData;

// Function declarations
void initializeMotors();
void controlMotors(int xValue, int yValue);
void stopMotors();
void moveForward();
void moveBackward();
void turnLeft();
void turnRight();

void onReceive(const uint8_t * mac, const uint8_t *incomingDataRaw, int len) {
  memcpy(&incomingData, incomingDataRaw, sizeof(incomingData));
  Serial.printf("Received - X:%4d Y:%4d\n", incomingData.x, incomingData.y);
  
  // Use your existing motor control logic
  controlMotors(incomingData.x, incomingData.y);
}

// Your original motor control functions
void initializeMotors() {
  pinMode(out1, OUTPUT);
  pinMode(out2, OUTPUT);
  pinMode(out3, OUTPUT);
  pinMode(out4, OUTPUT);
  
  digitalWrite(out1, LOW);
  digitalWrite(out2, LOW);
  digitalWrite(out3, LOW);
  digitalWrite(out4, LOW);
}

void controlMotors(int xValue, int yValue) {
  // Convert joystick values to motor control
  // Joystick range: 0-4095, Center: ~2048

  const int center = 2700;
  const int deadZone = 500;

  // Check if in dead zone (center position)
  if ((xValue > center - deadZone) && (xValue < center + deadZone) &&
      (yValue > center - deadZone) && (yValue < center + deadZone)) {
    stopMotors();
    Serial.println("Action: STOP");
    return;
  }

  // Determine primary direction based on which axis has stronger input
  int xDiff = abs(xValue - center);
  int yDiff = abs(yValue - center);

  if (yDiff > xDiff) {
    // Y-axis dominant (Forward/Backward)
    if (yValue < center - deadZone) {
      moveForward();
      Serial.println("Action: FORWARD");
    } else if (yValue > center + deadZone) {
      moveBackward();
      Serial.println("Action: BACKWARD");
    }
  } else {
    if (xValue == 4095 && yValue == 4095) {
      // Special case: Both axes maxed - move backward
      moveBackward();
      Serial.println("Action: BACKWARD");
    } else if (xValue == 0 && yValue == 0) {
      moveBackward();
      Serial.println("Action: BACKWARD");
    } else {
      // X-axis dominant (Left/Right)
      if (xValue < center - deadZone) {
        turnLeft();
        Serial.println("Action: LEFT");
      } else if (xValue > center + deadZone) {
        turnRight();
        Serial.println("Action: RIGHT");
      }
    }
  }
}

void moveForward() {
  // Both motors forward
  digitalWrite(out1, HIGH);  // Left motor forward
  digitalWrite(out2, LOW);
  digitalWrite(out3, LOW);   // Right motor forward
  digitalWrite(out4, HIGH);
}

void moveBackward() {
  // Both motors backward
  digitalWrite(out1, LOW);
  digitalWrite(out2, HIGH);  // Left motor backward
  digitalWrite(out3, HIGH);
  digitalWrite(out4, LOW);   // Right motor backward
}

void turnLeft() {
  // Right motor forward, left motor stopped
  digitalWrite(out1, HIGH);   // Left motor stop
  digitalWrite(out2, LOW);
  digitalWrite(out3, HIGH);   // Right motor forward
  digitalWrite(out4, LOW);
}

void turnRight() {
  // Left motor forward, right motor stopped
  digitalWrite(out1, LOW);    // Left motor forward
  digitalWrite(out2, HIGH);
  digitalWrite(out3, LOW);    // Right motor stop
  digitalWrite(out4, HIGH);
}

void stopMotors() {
  digitalWrite(out1, LOW);
  digitalWrite(out2, LOW);
  digitalWrite(out3, LOW);
  digitalWrite(out4, LOW);
}

void setup() {
  Serial.begin(115200);
  
  // Initialize motors
  initializeMotors();
  
  // Set WiFi mode
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();

  // Initialize ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("ESP-NOW init failed");
    while(1);
  }

  // Register receive callback
  esp_now_register_recv_cb(onReceive);

  Serial.println("ESP-NOW Receiver Ready");
  Serial.print("Receiver MAC: ");
  Serial.println(WiFi.macAddress());
  
  // Stop motors initially
  stopMotors();
}

void loop() {
  // Nothing to do here - everything handled in callback
  delay(1000); // Small delay to prevent watchdog timer issues
}

What to Do After Uploading

1. Upload Controller Code:

  • Connect the controller ESP32 (with joystick) via USB

  • Select the correct board and port

  • Click Upload

2. Upload Receiver Code:

  • Disconnect the controller ESP32

  • Connect the car ESP32 via USB

  • Make sure you’ve updated the MAC address in the controller code first (if you haven’t, the car won’t receive anything yet)

  • Click Upload

3. Power and Test:

  • Power the ESP32 RC car with its 18650 batteries

  • Power the controller via USB power bank or its own battery

  • Push the joystick – your car should move!

Step 6: Upload and Test – The Moment of Truth

  1. Program the Controller: Connect the first ESP32 to your computer. Select the correct board and port in Arduino IDE. Upload the ESP32_RC_Car_Controller code.

  2. Program the Car: Disconnect the first ESP32. Connect the second ESP32 to your computer. Important: You need to modify the code and put the MAC address of your controller ESP32 in the car’s code so they can pair. Upload the ESP32_RC_Car_Receiver code.

  3. Power Up: Disconnect both ESP32s from USB. Power the car with its 18650 batteries. Power the controller via USB power bank or its own battery.

  4. Drive! Push the joystick forward. Your ESP32 RC CAR should move!

Troubleshooting – Don’t Panic!

 
 
ProblemLikely CauseFix
ESP32 RC Car doesn’t moveBattery not connected or insufficient voltageCheck 18650 voltage (should be >6V). Recharge batteries.
Car moves erraticallyCommon ground missing between ESP32 and L298NConnect a GND pin from ESP32 to the GND terminal on the L298N.
One side doesn’t turnL298N connection issueCheck IN1-IN4 and OUT1-OUT4 wiring. Swap motor wires to test.
No wireless connectionMAC address incorrectGet the correct MAC address of your controller ESP32 (use a scanner sketch) and put it in the receiver’s code.
Joystick axes swappedWiring mismatchSwap the VRx and VRy pins on the controller’s ESP32, or swap X/Y in the code.

Level Up: What’s Next?

You’ve built the foundation. Now, make it yours.

 
 
UpgradeWhat You’ll Need
FPV CameraESP32-CAM module – stream video to your phone
Obstacle AvoidanceUltrasonic (HC-SR04) sensor – make it drive itself
Speed ControlUse the joystick’s analog values for proportional speed, not just on/off
Better Power2S LiPo battery (7.4V) with higher discharge rate for more punch
Custom ControllerBuild the controller into a 3D-printed box with a proper joystick knob
Follow Me ModeUse a smartphone’s GPS/compass data sent to the ESP32 RC car

Cost Breakdown (Bangladesh)

 

ComponentApprox Price (USD)
ESP32 ×2$10.00 – $13.50
4WD Chassis with Motors$6.70 – $10.00
L298N Motor Driver$2.90 – $4.60
Joystick Module$0.70 – $1.25
18650 Batteries (2x) + Holder$5.00 – $7.50
Breadboard & Jumpers$1.25 – $2.10
Total~$26.50 – $38.75

For a fully functional, custom-built ESP32 RC car? That’s incredible value.

Final Thoughts

This project taught me something important: wireless control is not magic. It’s just two microcontrollers agreeing on a language and a few GPIO pins firing in the right sequence.

Is it perfect? No.

  • The turning could be sharper.

  • The range could be longer.

  • I’d love a nicer case for the controller.

But it’s mine. I built it. I understand how it works. And when something goes wrong, I know how to fix it.

That’s what Roborear is about. Not just copying tutorials – understanding them, improving them, making them your own.

Your Turn

Got the ESP32 RC car working? Modified it with an FPV camera or sensors? Stuck on a step? Drop a comment below – I read all of them.

And if you want to see this project in action, check out the video on YouTube.

FAQs

Affiliate Disclosure

Some links in this post are affiliate links. If you purchase through them, I may earn a small commission at no extra cost to you. This helps support Roborear. Thanks!

Subscribe to The Newsletter

Join robotics enthusiasts getting weekly project ideas:

• 🔧 Under $50 projects

• ⚙️ Under $100 projects

• 🎥 New video tutorials

• 📝 Blog updates

We don’t spam! Read our privacy policy for more info.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *