Hướng dẫn làm xe robot ESP32 điều khiển qua Wi-Fi (Arduino IDE)

Trong dự án này, bạn sẽ học cách xây dựng xe robot ESP32 điều khiển qua Wi-Fi từng bước một. Bạn sẽ điều khiển robot thông qua web server để cho robot di chuyển tiến, lùi, rẽ trái, rẽ phải và kiểm soát tốc độ bằng thanh trượt. Trang web điều khiển hoạt động trên bất kỳ thiết bị nào có kết nối Wi-Fi — điện thoại, máy tính bảng hay laptop đều dùng được.
Tổng quan dự án

ESP32 tạo ra một web server lưu trữ trang điều khiển. Khi người dùng nhấn các nút điều hướng hoặc kéo thanh trượt tốc độ, trình duyệt gửi request HTTP đến ESP32, và ESP32 điều khiển driver động cơ L298N để di chuyển robot.
- 📡 Kết nối: Wi-Fi (ESP32 làm Access Point hoặc kết nối mạng nhà)
- 🎮 Điều khiển: Tiến / Lùi / Trái / Phải / Dừng
- ⚡ Tốc độ: Điều chỉnh qua thanh trượt (PWM)
- 📱 Giao diện: Web browser, không cần app
Linh kiện cần chuẩn bị

- ESP32 Development Board
- Bộ khung robot Smart Car Chassis Kit (2 hoặc 4 bánh, gồm motor DC)
- Module driver động cơ L298N
- Pin dự phòng (Powerbank) hoặc pin 18650 + hộp pin
- Dây jumper
Module Driver L298N

L298N là module driver động cơ DC phổ biến, có thể điều khiển 2 động cơ DC độc lập. Các chân quan trọng:
- IN1, IN2: Điều khiển chiều quay Motor A
- IN3, IN4: Điều khiển chiều quay Motor B
- ENA: Bật/tắt và điều chỉnh tốc độ Motor A (PWM)
- ENB: Bật/tắt và điều chỉnh tốc độ Motor B (PWM)
- 12V: Nguồn cấp cho động cơ (6–12V)
- 5V out: Ra 5V để cấp cho ESP32
- GND: Mass chung
Sơ đồ mạch

Kết nối ESP32 với L298N theo bảng sau:
| ESP32 GPIO | L298N Pin | Chức năng |
|---|---|---|
| GPIO 27 | IN1 | Motor A chiều 1 |
| GPIO 26 | IN2 | Motor A chiều 2 |
| GPIO 14 | IN3 | Motor B chiều 1 |
| GPIO 12 | IN4 | Motor B chiều 2 |
| GPIO 33 | ENA | Tốc độ Motor A (PWM) |
| GPIO 32 | ENB | Tốc độ Motor B (PWM) |
Code Arduino – ESP32 Robot Car điều khiển Wi-Fi
Trước khi nạp code, hãy thay đổi ssid và password thành thông tin Wi-Fi của bạn:
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
https://RandomNerdTutorials.com/esp32-wi-fi-car-robot-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 <WebServer.h>
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// Create an instance of the WebServer on port 80
WebServer server(80);
// Motor 1
int motor1Pin1 = 27;
int motor1Pin2 = 26;
int enable1Pin = 14;
// Motor 2
int motor2Pin1 = 33;
int motor2Pin2 = 25;
int enable2Pin = 32;
// Setting PWM properties
const int freq = 30000;
const int resolution = 8;
int dutyCycle = 0;
String valueString = String(0);
void handleRoot() {
const char html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<style>
html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center; }
.button { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background-color: #4CAF50; border: none; color: white; padding: 12px 28px; text-decoration: none; font-size: 26px; margin: 1px; cursor: pointer; }
.button2 {background-color: #555555;}
</style>
<script>
function moveForward() { fetch('/forward'); }
function moveLeft() { fetch('/left'); }
function stopRobot() { fetch('/stop'); }
function moveRight() { fetch('/right'); }
function moveReverse() { fetch('/reverse'); }
function updateMotorSpeed(pos) {
document.getElementById('motorSpeed').innerHTML = pos;
fetch(`/speed?value=${pos}`);
}
</script>
</head>
<body>
<h1>ESP32 Motor Control</h1>
<p><button class="button" onclick="moveForward()">FORWARD</button></p>
<div style="clear: both;">
<p>
<button class="button" onclick="moveLeft()">LEFT</button>
<button class="button button2" onclick="stopRobot()">STOP</button>
<button class="button" onclick="moveRight()">RIGHT</button>
</p>
</div>
<p><button class="button" onclick="moveReverse()">REVERSE</button></p>
<p>Motor Speed: <span id="motorSpeed">0</span></p>
<input type="range" min="0" max="100" step="25" id="motorSlider" oninput="updateMotorSpeed(this.value)" value="0"/>
</body>
</html>)rawliteral";
server.send(200, "text/html", html);
}
void handleForward() {
Serial.println("Forward");
digitalWrite(motor1Pin1, LOW);
digitalWrite(motor1Pin2, HIGH);
digitalWrite(motor2Pin1, LOW);
digitalWrite(motor2Pin2, HIGH);
server.send(200);
}
void handleLeft() {
Serial.println("Left");
digitalWrite(motor1Pin1, LOW);
digitalWrite(motor1Pin2, LOW);
digitalWrite(motor2Pin1, LOW);
digitalWrite(motor2Pin2, HIGH);
server.send(200);
}
void handleStop() {
Serial.println("Stop");
digitalWrite(motor1Pin1, LOW);
digitalWrite(motor1Pin2, LOW);
digitalWrite(motor2Pin1, LOW);
digitalWrite(motor2Pin2, LOW);
server.send(200);
}
void handleRight() {
Serial.println("Right");
digitalWrite(motor1Pin1, LOW);
digitalWrite(motor1Pin2, HIGH);
digitalWrite(motor2Pin1, LOW);
digitalWrite(motor2Pin2, LOW);
server.send(200);
}
void handleReverse() {
Serial.println("Reverse");
digitalWrite(motor1Pin1, HIGH);
digitalWrite(motor1Pin2, LOW);
digitalWrite(motor2Pin1, HIGH);
digitalWrite(motor2Pin2, LOW);
server.send(200);
}
void handleSpeed() {
if (server.hasArg("value")) {
valueString = server.arg("value");
int value = valueString.toInt();
if (value == 0) {
ledcWrite(enable1Pin, 0);
ledcWrite(enable2Pin, 0);
digitalWrite(motor1Pin1, LOW);
digitalWrite(motor1Pin2, LOW);
digitalWrite(motor2Pin1, LOW);
digitalWrite(motor2Pin2, LOW);
} else {
dutyCycle = map(value, 25, 100, 200, 255);
ledcWrite(enable1Pin, dutyCycle);
ledcWrite(enable2Pin, dutyCycle);
Serial.println("Motor speed set to " + String(value));
}
}
server.send(200);
}
void setup() {
Serial.begin(115200);
// Set the Motor pins as outputs
pinMode(motor1Pin1, OUTPUT);
pinMode(motor1Pin2, OUTPUT);
pinMode(motor2Pin1, OUTPUT);
pinMode(motor2Pin2, OUTPUT);
// Configure PWM Pins
ledcAttach(enable1Pin, freq, resolution);
ledcAttach(enable2Pin, freq, resolution);
// Initialize PWM with 0 duty cycle
ledcWrite(enable1Pin, 0);
ledcWrite(enable2Pin, 0);
// Connect to Wi-Fi
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
// Define routes
server.on("/", handleRoot);
server.on("/forward", handleForward);
server.on("/left", handleLeft);
server.on("/stop", handleStop);
server.on("/right", handleRight);
server.on("/reverse", handleReverse);
server.on("/speed", handleSpeed);
// Start the server
server.begin();
}
void loop() {
server.handleClient();
}
Cách code hoạt động
ESP32 khởi động và kết nối Wi-Fi, sau đó tạo web server tại địa chỉ IP được cấp phát. Web server phục vụ trang HTML với các nút điều khiển và thanh trượt tốc độ. Khi người dùng tương tác:
- Nhấn Forward → GPIO IN1=HIGH, IN2=LOW, IN3=HIGH, IN4=LOW → cả 2 motor quay tiến
- Nhấn Backward → đảo chiều cả 2 motor
- Nhấn Left/Right → một motor tiến, một motor lùi → robot xoay
- Kéo Slider tốc độ → thay đổi giá trị PWM cho ENA và ENB (0–255)

Cách nạp code và chạy thử
- Cài đặt board ESP32 trong Arduino IDE
- Cài thư viện ESPAsyncWebServer và AsyncTCP
- Điền thông tin Wi-Fi vào code
- Chọn board: ESP32 Dev Module → nạp code
- Mở Serial Monitor (115200 baud) → xem địa chỉ IP
- Mở trình duyệt → nhập địa chỉ IP → giao diện điều khiển hiện ra
- Nhấn các nút để điều khiển robot!
📖 Nguồn: Random Nerd Tutorials – ESP32 Remote-Controlled Wi-Fi Car Robot