Drive an ESP32 robot car with a console gamepad using Bluepad32
Pair a console gamepad with an ESP32 via Bluepad32 and drive a two-wheel car through a TB6612FNG. Only the original ESP32 takes every controller; power motors apart.
- Bluepad32 turns an ESP32 into a Bluetooth gamepad host: no phone or app in between.
- The original ESP32 supports both Bluetooth Classic and BLE controllers; the S3/C3/C6 have no Bluetooth Classic, so they only take BLE controllers.
- Analogue sticks read roughly −512 to 512. Mix forward/back and left/right into left and right wheel speeds, then clamp.
- Give the motors their own battery with a shared ground, and stop the motors the moment the controller drops.
The console gamepad sitting at home can drive an ESP32 robot car directly, thanks to Bluepad32, a library that lets an ESP32 accept Bluetooth controllers like a console does. Two conditions matter: only the original ESP32 supports every controller (the S3/C3/C6 take BLE controllers only), and the motors need a power supply separate from the board.
This is a how-to guide, not a write-up of a finished build. Numbers in the examples show the method; always check your own motor and battery specs.
Match the board to the controller
Controllers talk to the host over different protocols: some use Bluetooth Classic (BR/EDR), some use BLE. The Bluepad32 docs say the original ESP32, Pico W and Pico 2 W support BR/EDR, and the ESP32-S3, C3, C5, C6 and H2 do not.
| Original ESP32 | ESP32-S3 / C3 / C6 | |
|---|---|---|
| Bluetooth Classic (BR/EDR) | Yes | No |
| BLE | Yes | Yes |
| PS3 / PS4 / PS5 controllers | Yes | No |
| Switch Pro, Joy-Con, Wii Remote | Yes | No |
| 8BitDo family | Yes | No |
| Xbox Wireless model 1914 (firmware v5.15+) | Yes | Yes |
| Switch 2 Pro, Switch 2 Joy-Con | Yes | Yes |
| Steam Controller, Stadia (Bluetooth firmware) | Yes | Yes |
If you don’t own a board yet, pick the original ESP32 and any controller in the house will work. Other differences between models are in comparing ESP32 boards.
Arduino IDE setup
Bluepad32 ships as its own board package, not a normal library.
- In Preferences, add two board index URLs: the official ESP32 one, and
package_esp32_bluepad32_index.jsonfrom thericardoquesada/esp32-arduino-lib-builderrepo (the full link is on Bluepad32’s Arduino docs page). - In Tools → Board → Boards Manager, install
esp32andesp32_bluepad32. - Select your board under Tools → Board → ESP32 + Bluepad32 Arduino.
- Open File → Examples → Bluepad32_ESP32 → Controller, upload, press the pairing button on the controller and watch the Serial Monitor.
Get step 4 working before wiring motors, so you know whether pushing the stick up makes axisY() positive or negative.
Wiring: separate motor power
A TB6612FNG driver runs two DC motors. From Toshiba’s datasheet:
- VM (motor supply): 2.5–13.5 V operating, 15 V absolute maximum
- VCC (logic): 2.7–5.5 V, so the ESP32’s 3V3 is fine
- Current per channel: 1.2 A average, 3.2 A peak (10 ms pulse); the operating-range table gives 1.0 A at VM ≥ 4.5 V
- PWM up to 100 kHz, with thermal shutdown
- STBY must be pulled HIGH for the motors to run
| TB6612FNG | Connect to |
|---|---|
| VM | Motor battery + |
| VCC | ESP32 3V3 |
| GND | Motor battery ground and ESP32 GND |
| AIN1 / AIN2 / PWMA | GPIO25 / 26 / 27 (left wheel) |
| BIN1 / BIN2 / PWMB | GPIO32 / 33 / 4 (right wheel) |
| STBY | GPIO21 |
| AO1-AO2 / BO1-BO2 | Left / right motor |
Why separate supplies: when a motor starts or stalls, current surges and voltage dips. If the board shares that supply, the ESP32 can reset mid-drive. Run the ESP32 from a USB power bank or its own regulator (powering an ESP32), give the motors their own battery, and join the two grounds. Without a common ground, the ESP32’s signals have no reference and the motors won’t respond or will twitch.
Worked check, battery vs driver. Two lithium cells in series are 2 × 4.2 = 8.4 V when full, inside 2.5–13.5 V. Also check the motors are rated for that voltage, and that each motor’s stall current is no more than about 1 A per channel. If the motor spec is higher, use a driver rated for more current. Before using lithium cells, read LiPo and Li-ion safety.
Mixing the stick into wheel speeds
Bluepad32’s docs say analogue sticks are normalised to roughly −512 to 512, and triggers to 0–1023. A two-wheel car with tank-style (differential) drive needs only the left stick: Y is forward/back, X is turning.
- Left wheel = forward + turn
- Right wheel = forward − turn
Step by step: push the stick up and a little right, and Serial shows axisY = −400, axisX = 150 (on most controllers, up is negative; check yours).
- Forward = −(−400) = 400, turn = 150
- Left = 400 + 150 = 550 → clamp to 512
- Right = 400 − 150 = 250
- Convert to 8-bit PWM (0–255): left 512 × 255 ÷ 512 = 255, right 250 × 255 ÷ 512 ≈ 124
- Result: left wheel at full speed, right at about half, so the car curves right.
Sticks never return exactly to zero; they rest at a few tens. Set a dead zone, say ±40, and treat anything smaller as zero so the car doesn’t creep when you let go.
Code
#include <Bluepad32.h>
const int AIN1 = 25, AIN2 = 26, PWMA = 27; // left wheel
const int BIN1 = 32, BIN2 = 33, PWMB = 4; // right wheel
const int STBY = 21;
const int DEAD = 40;
ControllerPtr pad = nullptr;
void onConnected(ControllerPtr c) { if (!pad) pad = c; }
void onDisconnected(ControllerPtr c) { if (pad == c) pad = nullptr; }
void drive(int in1, int in2, int pwm, int v) { // v: -512..512
v = constrain(v, -512, 512);
digitalWrite(in1, v > 0);
digitalWrite(in2, v < 0);
ledcWrite(pwm, abs(v) * 255 / 512);
}
void stopAll() { drive(AIN1, AIN2, PWMA, 0); drive(BIN1, BIN2, PWMB, 0); }
void setup() {
for (int p : {AIN1, AIN2, BIN1, BIN2, STBY}) pinMode(p, OUTPUT);
ledcAttach(PWMA, 20000, 8); // 20 kHz, above hearing
ledcAttach(PWMB, 20000, 8);
digitalWrite(STBY, HIGH);
BP32.setup(&onConnected, &onDisconnected);
}
void loop() {
BP32.update();
if (!pad || !pad->isConnected()) { stopAll(); delay(10); return; } // controller gone = stop
if (pad->hasData()) {
int fwd = -pad->axisY(), turn = pad->axisX();
if (abs(fwd) < DEAD) fwd = 0;
if (abs(turn) < DEAD) turn = 0;
drive(AIN1, AIN2, PWMA, fwd + turn);
drive(BIN1, BIN2, PWMB, fwd - turn);
}
delay(10);
}
ledcAttach(pin, freq, resolution) is the Arduino-ESP32 3.x API. If the compiler doesn’t know it, your core is older; use ledcSetup and ledcAttachPin instead. If pushing up drives the car backwards, remove the minus sign before axisY(). If one side turns the wrong way, swap that motor’s wires.
Stop the motors whenever the controller drops. A car that keeps running on its last command hits things or drives off the table. Do the first test with the wheels lifted off the ground.
A printed chassis
Print a base plate with motor mounting holes, a removable battery bay for charging, and a board enclosure separate from the battery. Use heat-set inserts where screws go in and out repeatedly (inserts). Keep the battery low and central so the car doesn’t flip on a hard start.
Common mistakes
- S3/C3/C6 board with a PlayStation or Switch Pro controller. It won’t pair: no Bluetooth Classic.
- Motors and board on one supply. The ESP32 resets when the motors start and the controller drops mid-drive.
- No common ground. Motors don’t turn, or twitch.
- STBY left low. Everything is wired right but the wheels don’t move.
- No dead zone. The car creeps when the stick is released.
- Not clamping at 512. Values overflow and speed jumps.
- Motors drawing more than the driver can supply. The chip heats up and cuts out intermittently.
Sources
FAQ
Why won't my PS4 controller pair with an ESP32-S3 board?
Every PlayStation controller Bluepad32 supports uses Bluetooth Classic (BR/EDR), and the ESP32-S3, C3 and C6 have no BR/EDR. Use an original ESP32, or a controller from the BLE list.
Can one battery run both the motors and the board?
Yes, with a separate regulator for the board and enough capacitance. For a first car, separate supplies are simpler and avoid resets when the motors start. Either way, the grounds must be connected.
Can I use Bluepad32 in a product I sell?
Bluepad32 itself is Apache 2.0, but it relies on BTstack, which is free only for open-source projects. A closed-source commercial ESP32 product needs a licence from BTstack.
Circuits and code were tested on sample boards. Boards differ in pin layout, so check your board's pinout before you power anything.