Kết nối không dây

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

6 phút đọc05/07/2026HocLapTrinhRobot
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

text
[Đ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 motor

Chế độ 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

cpp
#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')">&#8593;</button><span></span>
  <button ontouchstart="go('L')" ontouchend="go('S')"
    onmousedown="go('L')" onmouseup="go('S')">&#8592;</button>
  <button onclick="go('S')">&#9632;</button>
  <button ontouchstart="go('R')" ontouchend="go('S')"
    onmousedown="go('R')" onmouseup="go('S')">&#8594;</button>
  <span></span><button ontouchstart="go('B')" ontouchend="go('S')"
    onmousedown="go('B')" onmouseup="go('S')">&#8595;</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ứngNguyên nhânCách sửa
Không thấy mạng Robot-APMậ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âyloop() có delay hoặc việc nặng chặn handleClientBỏ delay, dùng millis(); nâng cấp Async
Robot khựng giật khi WiFi hoạt độngSụt nguồn do WiFi ăn đỉnh dòngNguồ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ó WiFiChân ADC2 bị WiFi chiếmDời sang ADC1 (GPIO 32-39)
Trang lên nhưng lệnh không ănSai đườ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.

Tải PDF
Chia sẻ bài viết