Xe robot 4 bánh stream camera realtime — drive-by-WiFi
Xe robot 4 bánh ESP32-CAM lái qua browser, vừa stream video MJPEG real-time. WiFi control trong nhà ~30m, có flashlight + servo pan camera.
Xe 4 bánh có 4 motor DC giảm tốc → tank-drive (mỗi bên 2 motor cùng chiều). ESP32-CAM trên đỉnh xe, servo SG90 xoay pan ±90°.
ESP32-CAM ít GPIO available vì camera + flash chiếm nhiều. Chỉ còn:
Đấu nối:
Lưu ý: GPIO 12 = boot mode — khi flash code thì pull-down. Nếu đấu motor tới đó, có thể block flash. Cần ngắt motor connector khi reprogram.
Build trên example CameraWebServer ESP32, thêm endpoint /cmd?dir=F:
// Trong app_httpd.cpp hoặc file mới cmd_handler.cpp
#include "esp_http_server.h"
#include <Arduino.h>
const int IN1 = 12, IN2 = 13;
const int IN3 = 14, IN4 = 15;
void stopAll() {
digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
}
esp_err_t cmd_handler(httpd_req_t *req) {
char buf[100];
if (httpd_req_get_url_query_str(req, buf, sizeof(buf)) == ESP_OK) {
char dir[8];
if (httpd_query_key_value(buf, "dir", dir, sizeof(dir)) == ESP_OK) {
char d = dir[0];
switch (d) {
case 'F': digitalWrite(IN1,HIGH); digitalWrite(IN2,LOW);
digitalWrite(IN3,HIGH); digitalWrite(IN4,LOW); break;
case 'B': digitalWrite(IN1,LOW); digitalWrite(IN2,HIGH);
digitalWrite(IN3,LOW); digitalWrite(IN4,HIGH); break;
case 'L': digitalWrite(IN1,LOW); digitalWrite(IN2,HIGH);
digitalWrite(IN3,HIGH); digitalWrite(IN4,LOW); break;
case 'R': digitalWrite(IN1,HIGH); digitalWrite(IN2,LOW);
digitalWrite(IN3,LOW); digitalWrite(IN4,HIGH); break;
case 'S': stopAll(); break;
}
}
}
httpd_resp_set_type(req, "text/plain");
return httpd_resp_send(req, "OK", 2);
}
static httpd_uri_t cmd_uri = {
.uri = "/cmd",
.method = HTTP_GET,
.handler = cmd_handler,
.user_ctx = NULL
};
void startCmdServer() {
pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT);
stopAll();
// Register cmd_uri vào server chính khi startCameraServer()
}
Trang HTML serve từ ESP32 với 5 button + thẻ <img> stream MJPEG:
<!DOCTYPE html>
<html><head><meta charset='utf-8'>
<style>
body{font-family:sans-serif;text-align:center;background:#222;color:#fff;margin:0}
img{width:100%;max-width:640px}
.pad{display:grid;grid-template-columns:repeat(3,80px);gap:10px;justify-content:center;margin-top:20px}
button{padding:25px;font-size:30px;background:#444;color:#fff;border:none;border-radius:8px}
button:active{background:#0a0}
</style></head>
<body>
<h2>Robot Cam</h2>
<img src="/stream" />
<div class='pad'>
<span></span><button onclick="send('F')">↑</button><span></span>
<button onclick="send('L')">←</button>
<button onclick="send('S')">■</button>
<button onclick="send('R')">→</button>
<span></span><button onclick="send('B')">↓</button><span></span>
</div>
<script>
function send(d) {
fetch('/cmd?dir=' + d);
}
// Hold-to-drive: gửi S khi nhả nút
document.querySelectorAll('button').forEach(b => {
b.addEventListener('mouseup', () => send('S'));
b.addEventListener('touchend', () => send('S'));
});
</script>
</body></html>
http://<esp-cam-ip>/control.html.Latency: ~200–400ms từ bấm đến motion. Đủ cho indoor drive nhưng không phải FPV racing.
frame_size xuống QVGA.