Làm web server trên ESP32 điều khiển robot qua WiFi - không cần cài app

ESP32 tự phát WiFi và phục vụ trang web điều khiển robot - code hoàn chỉnh chế độ AP, trang HTML nút bấm, xử lý lệnh HTTP và failsafe mất kết nối. Trong bài viết này, chúng ta sẽ cùng tìm hiểu chi tiết và ứng dụng thực tế trong robot.
Ý tưởng: robot tự làm trạm phát WiFi kiêm website
ESP32 làm được đồng thời hai việc mà thế hệ Arduino cũ phải mơ: phát mạng WiFi riêng (chế độ Access Point) và chạy web server phục vụ trang điều khiển lưu ngay trong chip. Người dùng chỉ cần: kết nối WiFi "Robot-AP" → mở trình duyệt → bấm nút trên trang web → robot chạy. Không app, không tài khoản, không Internet — chạy được với mọi thiết bị có trình duyệt, kể cả iPhone và laptop.
Dự án này là bài tổng hợp đẹp nhất của giai đoạn giữa: chạm đủ mạng WiFi, giao thức HTTP, chút HTML/JavaScript và điều khiển phần cứng — mỗi mảng đúng một miếng vừa miệng.
Kiến trúc và luồng dữ liệu
[Điện thoại] [ESP32 trên robot]
trình duyệt --kết nối WiFi--> Access Point "Robot-AP" (192.168.4.1)
mở trang --GET / --> trả về trang HTML điều khiển
bấm TIẾN --GET /cmd?m=F --> handler đọc tham số, gọi drive(...)
thả nút --GET /cmd?m=S --> dừng motorChế độ AP (robot tự phát mạng) hợp lớp học và demo; chế độ Station (robot vào WiFi nhà) hợp khi muốn điều khiển từ máy đang online bình thường — code chỉ khác vài dòng khởi tạo.
Code hoàn chỉnh - AP + trang điều khiển + failsafe
#include <WiFi.h>
#include <WebServer.h>
WebServer server(80);
unsigned long lastCommand = 0;
// trang web nằm ngay trong code - PROGMEM giữ trong flash
const char PAGE[] PROGMEM = R"HTML(
<!DOCTYPE html><html><head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: sans-serif; text-align: center; background: #f0f4f8; }
.pad { display: grid; grid-template-columns: repeat(3, 90px);
gap: 10px; justify-content: center; margin-top: 40px; }
button { height: 90px; font-size: 30px; border-radius: 14px; border: none;
background: #2563eb; color: white; }
button:active { background: #1e40af; }
</style></head><body>
<h2>Robot ESP32</h2>
<div class="pad">
<span></span><button ontouchstart="go('F')" ontouchend="go('S')"
onmousedown="go('F')" onmouseup="go('S')">↑</button><span></span>
<button ontouchstart="go('L')" ontouchend="go('S')"
onmousedown="go('L')" onmouseup="go('S')">←</button>
<button onclick="go('S')">■</button>
<button ontouchstart="go('R')" ontouchend="go('S')"
onmousedown="go('R')" onmouseup="go('S')">→</button>
<span></span><button ontouchstart="go('B')" ontouchend="go('S')"
onmousedown="go('B')" onmouseup="go('S')">↓</button><span></span>
</div>
<script>
function go(m) { fetch('/cmd?m=' + m); }
</script></body></html>
)HTML";
void handleRoot() { server.send_P(200, "text/html", PAGE); }
void handleCmd() {
String m = server.arg("m");
lastCommand = millis();
if (m == "F") drive(180, 180);
else if (m == "B") drive(-160, -160);
else if (m == "L") drive(-140, 140);
else if (m == "R") drive(140, -140);
else drive(0, 0);
server.send(200, "text/plain", "OK");
}
void setup() {
Serial.begin(115200);
setupMotors(); // pinMode + ledcAttach như các bài trước
WiFi.softAP("Robot-AP", "12345678"); // tên mạng + mật khẩu (>=8 ký tự)
Serial.print("Dia chi: ");
Serial.println(WiFi.softAPIP()); // mặc định 192.168.4.1
server.on("/", handleRoot);
server.on("/cmd", handleCmd);
server.begin();
}
void loop() {
server.handleClient(); // tim của web server - gọi thật dày
// FAILSAFE: quá 600ms không có lệnh -> dừng
if (millis() - lastCommand > 600 && lastCommand != 0) {
drive(0, 0);
lastCommand = 0;
}
}Hàm drive(left, right) là hàm điều khiển hai motor quen thuộc — nguyên mẫu trong bài Hàm và thư viện Arduino ở mục Kiến thức.
Vì sao trang web viết kiểu "giữ nút = chạy, thả = dừng"?
Cặp sự kiện ontouchstart/ontouchend (và mousedown/mouseup cho máy tính) mô phỏng tay cầm thật: giữ mũi tên robot chạy, buông tay robot dừng — trực giác và an toàn hơn hẳn kiểu "bấm một lần chạy mãi". Kết hợp failsafe phía ESP32: kể cả điện thoại sập nguồn giữa lúc giữ nút, robot dừng sau 600 ms.
Nâng cấp theo bậc
Bậc 1 - thêm thanh tốc độ
Thêm <input type="range" min="80" max="255" oninput="fetch('/speed?v='+this.value)"> vào trang, handler đọc server.arg("v") đổi biến tốc độ — bài học truyền tham số hoàn chỉnh.
Bậc 2 - hiện số liệu cảm biến lên trang
JavaScript hỏi định kỳ setInterval(() => fetch('/status').then(r=>r.json())..., 500), ESP32 trả JSON khoảng cách/pin — trang web thành dashboard hai chiều.
Bậc 3 - WebSocket cho độ trễ thi đấu
HTTP mỗi lệnh một kết nối — trễ tích tụ 50-200 ms. WebSocket (thư viện ESPAsyncWebServer + AsyncWebSocket) giữ một đường ống mở hai chiều: trễ xuống 5-20 ms, đủ cho lái tốc độ cao, kèm stream dữ liệu về liên tục. Đây là kiến trúc của các trang điều khiển "xịn".
Bậc 4 - camera stream
Đổi bo sang ESP32-CAM: trang web hiện luôn hình ảnh robot nhìn thấy (MJPEG stream) cạnh nút điều khiển — dự án "robot tuần tra FPV" gây ấn tượng mạnh nhất hội diễn STEM, tổng linh kiện dưới 300.000 đ.
Lỗi thường gặp và cách gỡ
| Triệu chứng | Nguyên nhân | Cách sửa |
|---|---|---|
| Không thấy mạng Robot-AP | Mật khẩu AP dưới 8 ký tự (softAP âm thầm thất bại) | Đặt >= 8 ký tự, xem Serial log |
| Vào 192.168.4.1 không lên trang | Điện thoại vẫn dính mạng khác (tự chuyển vì AP không có Internet) | Tắt dữ liệu di động, chọn "giữ kết nối" khi điện thoại cảnh báo |
| Bấm nút trễ 1-2 giây | loop() có delay hoặc việc nặng chặn handleClient | Bỏ delay, dùng millis(); nâng cấp Async |
| Robot khựng giật khi WiFi hoạt động | Sụt nguồn do WiFi ăn đỉnh dòng | Nguồn khỏe hơn + tụ 470uF — xem bài Nguồn pin robot |
| Cảm biến analog đọc loạn từ khi có WiFi | Chân ADC2 bị WiFi chiếm | Dời sang ADC1 (GPIO 32-39) |
| Trang lên nhưng lệnh không ăn | Sai đường dẫn fetch hoặc quên server.on("/cmd") | Xem tab Network của trình duyệt + Serial log |
Câu hỏi thường gặp
Bao nhiêu người kết nối vào robot cùng lúc được?
softAP mặc định cho 4 máy kết nối (chỉnh tối đa ~10). Nhưng điều khiển thì chỉ nên MỘT người — trang web nhiều người cùng bấm là robot nhận lệnh loạn. Demo triển lãm: một máy điều khiển, các máy khác mở trang chỉ-xem (dashboard bậc 2).
Chế độ AP có cần Internet không? Có tốn cước không?
Không và không — AP là mạng cục bộ giữa điện thoại và robot, không đi qua Internet. Đây chính là điểm hay cho lớp học: chạy được ở nơi không có WiFi trường, không lo nội dung ngoài luồng.
Trang HTML dài quá làm chật bộ nhớ thì sao?
Trang trong bài (~2 KB) không đáng kể so với 4 MB flash. Trang lớn (thư viện JS, ảnh): dùng SPIFFS/LittleFS chứa file tĩnh riêng khỏi code, hoặc trỏ CDN nếu chế độ Station có Internet. Ngưỡng thực dụng: trên ~50 KB HTML thì chuyển sang LittleFS.
Muốn điều khiển từ xa hẳn qua Internet thì sao?
Web server cục bộ không ra ngoài Internet được nếu không mở cổng router (không khuyến khích vì bảo mật). Kiến trúc đúng cho điều khiển từ xa thật: MQTT qua broker trung gian — xem bài MQTT nhập môn ở mục Kiến thức.
Tài nguyên tải về
Tải bản PDF của bài viết để in giáo án hoặc gửi trước cho học viên.



