หุ่นยนต์ ESP32 บังคับผ่าน WiFi จากมือถือ ไม่ต้องลงแอป
ให้ ESP32 ปล่อย WiFi และเปิดหน้าเว็บปุ่มบังคับ กดค้างเพื่อขับ ปล่อยแล้วหยุด ส่งคำสั่งผ่าน WebSocket ให้ตอบสนองไว และหยุดเองเมื่อสัญญาณหลุด พร้อมโค้ดครบชุด
ESP32 มี WiFi ในตัว จึงทำรถบังคับจากมือถือได้โดยไม่ต้องติดตั้งแอปใดๆ: ให้ ESP32 ปล่อย WiFi ของตัวเอง (Access Point)
มือถือต่อเข้าไปแล้วเปิดเบราว์เซอร์ที่ 192.168.4.1 จะเจอหน้าปุ่มบังคับ กดค้างเพื่อขับ ปล่อยแล้วหยุด
คำสั่งวิ่งผ่าน WebSocket จึงตอบสนองไว และหุ่นหยุดเองเมื่อไม่ได้รับคำสั่งเกิน 0.3 วินาที
ลองขับหุ่น 2 ล้อในตัวจำลองก่อน →
หุ่นตัวนี้ทำงานอย่างไร
- ESP32 เป็น Access Point ปล่อย WiFi ชื่อ
AI-Roverไม่ต้องพึ่งเราเตอร์ เล่นที่ไหนก็ได้ - ESP32 เป็นเว็บเซิร์ฟเวอร์ ส่งหน้า HTML ที่มีปุ่มลูกศรให้มือถือ (พอร์ต 80)
- WebSocket ส่งคำสั่ง หน้าเว็บเปิดการเชื่อมต่อค้างไว้ (พอร์ต 81) แล้วส่งความเร็วล้อซ้ายขวา เช่น
1,1ทุก 0.1 วินาทีขณะกดปุ่ม - ตัวตัดความปลอดภัย ถ้า ESP32 ไม่ได้รับคำสั่งเกิน 300 ms (มือถือหลุดสัญญาณ ล็อกจอ หรือปิดหน้าเว็บ) ล้อจะหยุดทันที
/forward
ทุกการกดต้องเปิดการเชื่อมต่อ HTTP ใหม่ ใช้เวลาหลายสิบถึงหลายร้อยมิลลิวินาที หุ่นจึงตอบช้าและกระตุก
WebSocket เปิดค้างไว้ครั้งเดียว ส่งข้อความสั้นๆ ได้ทันทีชิ้นส่วน
| ชิ้นส่วน | จำนวน | ราคาประมาณ | ร้าน |
|---|---|---|---|
| ESP32 DevKit V1 (30 ขา) | 1 | ฿130–฿250 | Shopee |
| TB6612FNG motor driver | 1 | ฿50–฿120 | Shopee |
| ชุดแชสซี 2WD มีมอเตอร์ TT 2 ตัว ล้อ 2 ล้อ และล้อ caster | 1 | ฿150–฿300 | Shopee |
| ถ่าน Li-ion 18650 | 2 | ฿60–฿150 | Shopee |
| รางถ่าน 18650 แบบ 2 ก้อน (2S) | 1 | ฿20–฿60 | Shopee |
| Buck converter 7.4 V → 5 V 3 A | 1 | ฿30–฿90 | Shopee |
| สวิตช์เปิด-ปิด | 1 | ฿5–฿20 | Shopee |
| สายจัมเปอร์ ผู้-ผู้ / ผู้-เมีย ชุด 40 เส้น | 1 | ฿30–฿60 | Shopee |
| แนะนำเพิ่ม | |||
| ที่ชาร์จถ่าน 18650 | 1 | ฿100–฿250 | Shopee |
| รวมโดยประมาณ | ฿535–฿1,200 | ||
ราคาประมาณการ ณ 2026-10-31 ยังไม่ได้ตรวจกับร้าน · บางลิงก์เป็นลิงก์ affiliate (อ่านเพิ่ม)
วงจร
| TB6612FNG Motors | VM → แบต 7.4 VVCC → 3V3GND → GNDPWMA → GPIO25AIN1 → GPIO27AIN2 → GPIO14PWMB → GPIO26BIN1 → GPIO13BIN2 → GPIO4STBY → 3V3 (ต่อค้างไว้) |
|---|
วงจรเดียวกับ บทความ TB6612FNG ไม่ต้องเพิ่มอะไร WiFi ดึงกระแสเป็นจังหวะสั้นๆ จึงยิ่งต้องแยกไฟมอเตอร์กับไฟบอร์ดให้ดี ถ้าบอร์ดรีเซ็ตตอนเริ่มขับ อ่าน ESP32 รีเซ็ตเองตอนมอเตอร์หมุน
โค้ด (Arduino IDE)
ต้องติดตั้งไลบรารี WebSockets ของ Markus Sattler จาก Library Manager (ค้นคำว่า WebSockets)
ส่วน WiFi.h และ WebServer.h มากับ ESP32 core อยู่แล้ว
#include <WiFi.h>
#include <WebServer.h>
#include <WebSocketsServer.h> // ไลบรารี "WebSockets" ของ Markus Sattler
const char *SSID = "AI-Rover";
const char *PASS = "rover1234"; // อย่างน้อย 8 ตัวอักษร
const int PWMA = 25, AIN1 = 27, AIN2 = 14; // ล้อซ้าย
const int PWMB = 26, BIN1 = 13, BIN2 = 4; // ล้อขวา
const int MAX_SPEED = 200; // ~78 % ของ 255 ถนอมมอเตอร์ TT
const unsigned long TIMEOUT_MS = 300; // ไม่ได้รับคำสั่งนานเท่านี้ = หยุด
WebServer http(80);
WebSocketsServer ws(81);
unsigned long lastCommand = 0;
const char PAGE[] PROGMEM = R"html(<!doctype html>
<html><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<style>
body { margin: 0; height: 100vh; display: grid; place-items: center; background: #111; color: #eee;
font-family: sans-serif; touch-action: none; user-select: none; -webkit-user-select: none; }
.pad { display: grid; grid-template-columns: repeat(3, 90px); gap: 12px; }
button { height: 90px; border: 0; border-radius: 18px; background: #2a3550; color: #fff; font-size: 34px; }
button:active { background: #4a6cf7; }
p { text-align: center; }
</style></head><body>
<div>
<div class="pad">
<span></span><button data-m="1,1">▲</button><span></span>
<button data-m="-0.6,0.6">◀</button><span></span><button data-m="0.6,-0.6">▶</button>
<span></span><button data-m="-0.8,-0.8">▼</button><span></span>
</div>
<p id="status">กำลังเชื่อมต่อ...</p>
</div>
<script>
const info = document.getElementById('status');
const ws = new WebSocket('ws://' + location.hostname + ':81/');
ws.onopen = () => info.textContent = 'พร้อมขับ: กดปุ่มค้างไว้';
ws.onclose = () => info.textContent = 'หลุดการเชื่อมต่อ: รีเฟรชหน้านี้';
let held = null; // คำสั่งของปุ่มที่กดค้างอยู่
const send = m => ws.readyState === 1 && ws.send(m);
setInterval(() => send(held || '0,0'), 100); // ส่งซ้ำตลอด ESP32 จะรู้ว่ายังเชื่อมต่ออยู่
document.querySelectorAll('button').forEach(b => {
b.onpointerdown = e => { b.setPointerCapture(e.pointerId); held = b.dataset.m; send(held); };
b.onpointerup = b.onpointercancel = () => { held = null; send('0,0'); };
});
document.addEventListener('visibilitychange', () => { held = null; send('0,0'); });
</script>
</body></html>)html";
// speed: -1.0 (ถอยเต็มที่) ถึง 1.0 (เดินหน้าเต็มที่)
void motor(int pwm, int in1, int in2, float speed) {
int s = constrain((int)(speed * MAX_SPEED), -MAX_SPEED, MAX_SPEED);
digitalWrite(in1, s > 0);
digitalWrite(in2, s < 0);
analogWrite(pwm, abs(s));
}
void drive(float left, float right) {
motor(PWMA, AIN1, AIN2, left);
motor(PWMB, BIN1, BIN2, right);
}
// ข้อความจากหน้าเว็บ: "ซ้าย,ขวา" เช่น "1,1" หรือ "-0.6,0.6"
void onWebSocket(uint8_t client, WStype_t type, uint8_t *data, size_t length) {
if (type == WStype_DISCONNECTED) {
drive(0, 0);
return;
}
if (type != WStype_TEXT) return;
float left, right;
if (sscanf((const char *)data, "%f,%f", &left, &right) == 2) {
drive(left, right);
lastCommand = millis();
}
}
void setup() {
Serial.begin(115200);
int pins[] = { PWMA, AIN1, AIN2, PWMB, BIN1, BIN2 };
for (int p : pins) pinMode(p, OUTPUT);
drive(0, 0);
WiFi.softAP(SSID, PASS);
Serial.print("ต่อ WiFi ");
Serial.print(SSID);
Serial.print(" แล้วเปิด http://");
Serial.println(WiFi.softAPIP()); // ปกติคือ 192.168.4.1
http.on("/", [] { http.send(200, "text/html", PAGE); });
http.begin();
ws.begin();
ws.onEvent(onWebSocket);
}
void loop() {
http.handleClient();
ws.loop();
if (millis() - lastCommand > TIMEOUT_MS) drive(0, 0); // สัญญาณหลุด: หยุด
}
ใช้งาน
- อัปโหลดโค้ด เปิด Serial Monitor ที่ 115200 จะเห็นชื่อ WiFi และที่อยู่ของหน้าเว็บ
- ยกหุ่นให้ล้อลอยก่อนทดสอบครั้งแรก
- มือถือต่อ WiFi
AI-Roverรหัสrover1234ถ้ามือถือเตือนว่า WiFi นี้ไม่มีอินเทอร์เน็ต ให้เลือกใช้ต่อ - เปิดเบราว์เซอร์ไปที่
http://192.168.4.1รอจนขึ้น พร้อมขับ แล้วกดปุ่มค้าง - ถ้ากดเดินหน้าแล้วล้อข้างหนึ่งถอย ให้สลับค่า
AIN1/AIN2(หรือBIN1/BIN2) ในโค้ด
เรื่องความปลอดภัยที่ไม่ควรตัดออก
รถบังคับที่ไม่มีตัวตัดจะวิ่งต่อไปเรื่อยๆ เมื่อสัญญาณหลุดในจังหวะที่กำลังกดเดินหน้า จนชนหรือตกโต๊ะ โค้ดนี้หยุดล้อใน 3 กรณี:
- ไม่ได้รับข้อความใดๆ เกิน 300 ms (หน้าเว็บส่ง
0,0ซ้ำทุก 100 ms แม้ไม่ได้กดปุ่ม เพื่อยืนยันว่ายังเชื่อมต่ออยู่) - WebSocket ตัดการเชื่อมต่อ
- หน้าเว็บถูกสลับไปเบื้องหลัง หรือจอมือถือดับ (
visibilitychange)
ถ้าหุ่นหยุดๆ เดินๆ ตอนอยู่ไกล แปลว่าสัญญาณอ่อน ให้เพิ่ม TIMEOUT_MS เป็น 500 ms ได้ แต่ไม่ควรเกิน 1 วินาที
ต่อยอด
- ใช้ WiFi บ้านแทน Access Point เปลี่ยน
WiFi.softAP(...)เป็นWiFi.begin("ชื่อ WiFi", "รหัส")รอให้เชื่อมต่อ แล้วเปิดที่อยู่ IP ที่ Serial Monitor แสดง ข้อดีคือมือถือยังใช้อินเทอร์เน็ตได้ - จอยสติ๊กแทนปุ่ม ส่งค่าซ้ายขวาเป็นทศนิยมตามตำแหน่งนิ้ว เลี้ยวโค้งได้นุ่มกว่า
- ส่งค่าเซนเซอร์กลับมาที่มือถือ ใช้
ws.broadcastTXT()ส่งระยะจาก HC-SR04 กลับไปแสดงบนหน้าเว็บ ถ้าเซนเซอร์เป็นแบบ analog ต้องใช้ขา ADC1 เพราะ ADC2 อ่านไม่ได้ตอนเปิด WiFi - เบรกฉุกเฉินอัตโนมัติ ใส่ HC-SR04 ไว้ด้านหน้า แล้วไม่ยอมเดินหน้าเมื่อระยะต่ำกว่า 15 cm
ลองในตัวจำลอง
ตัวจำลองไม่ได้จำลองหน้าเว็บบนมือถือ แต่ขับหุ่นตัวเดียวกันด้วยปุ่ม W A S D บนคีย์บอร์ดได้ ให้ความรู้สึกเหมือนกดค้างบนมือถือ
ใช้ดูว่าหุ่นเลี้ยวกว้างแค่ไหนที่ความเร็วต่างๆ ก่อนปรับตัวเลข 0.6 ของปุ่มเลี้ยวในหน้าเว็บ