Ever wanted to build your own remote-controlled car from scratch? In this tutorial, we'll build a wireless car controlled via Bluetooth Low Energy (BLE) using the ESP32 microcontroller. Unlike traditional RC cars that use RF remotes, our car will be controlled directly from a smartphone app — no extra remote hardware needed!
This project is perfect for students and beginners who want to learn about:
Microcontroller programming (ESP32)
Motor control using H-Bridge driver ICs
Bluetooth Low Energy (BLE) communication
Basic robotics and circuit design
By the end of this guide, you'll have a fully functional 2-wheel-drive (or 4-wheel-drive) car that you can drive around using your phone!
Basic Flow:
Smartphone App (BLE Client)
|
| Sends command (F, B, L, R, S)
v
ESP32 (BLE Server)
|
| Controls GPIO pins
v
Motor Driver (L298N / L293D)
|
v
DC Motors → Car Moves
ESP32 to L298N Motor Driver Connections
ESP32 PinL298N PinFunctionGPIO 14IN1Motor A direction 1GPIO 27IN2Motor A direction 2GPIO 26IN3Motor B direction 1GPIO 25IN4Motor B direction 2GPIO 12ENAMotor A speed (PWM)GPIO 13ENBMotor B speed (PWM)GNDGNDCommon ground
L298N to Motors & Battery
L298N PinConnects ToOUT1, OUT2Left Motor terminalsOUT3, OUT4Right Motor terminals12V / VCCBattery positive (7.4V from 2x 18650)GNDBattery negative + ESP32 GND5V (out)Can power ESP32 VIN (if jumper enabled)
Wiring Notes
Important: Never power the ESP32 directly from the L298N's 12V input — use its regulated 5V output, or power ESP32 separately via USB/battery.
Mount the L298N module on the chassis with the motors below it for clean wiring.
Keep motor wires twisted together to reduce electrical noise.
Double-check polarity of the battery before connecting — reversed polarity can damage your ESP32.
(Tip: When publishing, insert a hand-drawn or Fritzing-style circuit diagram image here for visual clarity. Tools like Fritzing, EasyEDA, or Tinkercad Circuits work great for this.)
Step-by-Step Assembly
Build the chassis — Attach the BO motors to the chassis frame using the provided clips/screws. Attach wheels to the motor shafts.
Mount the L298N driver — Fix it in the center of the chassis using double-sided tape or small screws.
Mount the ESP32 — Place it near the motor driver, ensuring GPIO pins are accessible.
Wire the connections — Follow the wiring table above carefully.
Connect the battery pack — Attach the on/off switch in series with the battery before connecting to the L298N's power input.
Secure all wiring — Use zip ties or hot glue to prevent wires from getting caught in the wheels.
/*
Wireless BLE Car using ESP32
Control via BLE app sending single characters:
F - Forward, B - Backward, L - Left, R - Right, S - Stop
*/
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
// Motor A (Left) pins
#define IN1 14
#define IN2 27
#define ENA 12
// Motor B (Right) pins
#define IN3 26
#define IN4 25
#define ENB 13
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
BLECharacteristic *pCharacteristic;
bool deviceConnected = false;
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) { deviceConnected = true; }
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
pServer->getAdvertising()->start(); // restart advertising
}
};
class MyCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
String value = pCharacteristic->getValue().c_str();
if (value.length() > 0) {
char command = value[0];
Serial.print("Command received: ");
Serial.println(command);
switch (command) {
case 'F': moveForward(); break;
case 'B': moveBackward(); break;
case 'L': turnLeft(); break;
case 'R': turnRight(); break;
case 'S': stopCar(); break;
default: stopCar(); break;
}
}
}
};
void setup() {
Serial.begin(115200);
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT);
pinMode(IN4, OUTPUT);
pinMode(ENA, OUTPUT);
pinMode(ENB, OUTPUT);
// Full speed by default
digitalWrite(ENA, HIGH);
digitalWrite(ENB, HIGH);
stopCar();
// BLE Setup
BLEDevice::init("ESP32_BLE_Car");
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
BLEService *pService = pServer->createService(SERVICE_UUID);
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE
);
pCharacteristic->setCallbacks(new MyCallbacks());
pService->start();
BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising->start();
Serial.println("BLE Car Ready. Waiting for connections...");
}
void loop() {
// Nothing needed here — BLE callbacks handle everything
delay(20);
}
// ---- Motor Control Functions ----
void moveForward() {
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
digitalWrite(IN3, HIGH);
digitalWrite(IN4, LOW);
}
void moveBackward() {
digitalWrite(IN1, LOW);
digitalWrite(IN2, HIGH);
digitalWrite(IN3, LOW);
digitalWrite(IN4, HIGH);
}
void turnLeft() {
digitalWrite(IN1, LOW);
digitalWrite(IN2, HIGH);
digitalWrite(IN3, HIGH);
digitalWrite(IN4, LOW);
}
void turnRight() {
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW);
digitalWrite(IN4, HIGH);
}
void stopCar() {
digitalWrite(IN1, LOW);
digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW);
digitalWrite(IN4, LOW);
}
Get an official Project Completion Certificate with a unique ID & QR verification — perfect for internships, resumes, and college submissions.
Get Certificate →