器人:從硬件接線到網(wǎng)頁(yè)遙控全攻略)
這次我們來(lái)看一個(gè)很多人問(wèn)過(guò)的硬件項(xiàng)目基于 ESP32 自制一臺(tái)迷你 WiFi 視頻機(jī)器人。它不需要昂貴的工業(yè)底盤也不需要單獨(dú)配樹(shù)莓派加 USB 攝像頭一塊 ESP32-CAM 攝像頭開(kāi)發(fā)板加上兩個(gè)減速電機(jī)和一顆鋰電池就能實(shí)現(xiàn)手機(jī)瀏覽器實(shí)時(shí)看畫面、遠(yuǎn)程控制前進(jìn)后退左右轉(zhuǎn)向。對(duì)玩機(jī)器人和智能硬件的朋友來(lái)說(shuō)這個(gè)項(xiàng)目的價(jià)值不只是“做一臺(tái)小車”而是把 WiFi 視頻傳輸、電機(jī)驅(qū)動(dòng)、電源管理和嵌入式 Web 服務(wù)這四件事完整串起來(lái)后面想擴(kuò)展自動(dòng)巡檢、視覺(jué)跟隨、甚至接進(jìn) ROS 2 導(dǎo)航框架都有現(xiàn)成基礎(chǔ)。先看最核心的幾個(gè)特點(diǎn)整機(jī)成本可以控制在百元級(jí)主控采用帶攝像頭的 ESP32-CAM同時(shí)負(fù)責(zé)視頻采集、WiFi 通信、電機(jī)控制通過(guò)瀏覽器訪問(wèn) IP 即可看到 MJPEG 視頻流不需要安裝額外 App支持接入 L298N、DRV8833、L9110S 這類常見(jiàn)電機(jī)驅(qū)動(dòng)模塊使用 Arduino IDE 開(kāi)發(fā)入門門檻低讀者照著接線和燒錄基本能跑通。本文會(huì)從硬件選型、電路連接、開(kāi)發(fā)環(huán)境、視頻流服務(wù)、電機(jī)控制、網(wǎng)頁(yè)控制端、性能調(diào)優(yōu)、常見(jiàn)問(wèn)題排查一路展開(kāi)最后給出幾個(gè)從“會(huì)動(dòng)”到“好玩”的進(jìn)階方向。如果你之前只玩過(guò) ESP32 點(diǎn)燈、傳感器采集還沒(méi)做過(guò)帶攝像頭的運(yùn)動(dòng)小車這篇文章可以直接收藏。文中代碼基于常見(jiàn)的 Arduino 框架給出了能實(shí)際編譯的攝像頭初始化、網(wǎng)絡(luò)服務(wù)、電機(jī)控制示例。硬件接線可能因板子版本略有差異只要對(duì)照引腳表檢查一般不會(huì)踩坑。下面開(kāi)始。1. 核心能力速覽能力項(xiàng)說(shuō)明主控方案ESP32-CAMAI-Thinker 類板型集成攝像頭與 WiFi視頻傳輸MJPEG 視頻流瀏覽器直接訪問(wèn)幀率隨分辨率和 WiFi 環(huán)境變化網(wǎng)絡(luò)接入2.4G WiFi支持 STA 模式接入路由器也可改造為 AP 模式電機(jī)控制通過(guò) GPIO 控制電機(jī)驅(qū)動(dòng)模塊支持前進(jìn)、后退、左轉(zhuǎn)、右轉(zhuǎn)、停止供電方案鋰電池 穩(wěn)壓模塊建議 5V/2A 級(jí)別供電能力開(kāi)發(fā)環(huán)境Arduino IDE esp32 開(kāi)發(fā)板包 esp32-camera 庫(kù)控制端手機(jī)/電腦瀏覽器訪問(wèn) Web 控制頁(yè)無(wú)需安裝 App擴(kuò)展接口預(yù)留 GPIO 可接舵機(jī)云臺(tái)、超聲波傳感器、OLED 屏等成本估算核心零件約 100 元級(jí)具體視電調(diào)和底盤方案而定適合人群有一定 Arduino 基礎(chǔ)、想入門 WiFi 機(jī)器人或視頻巡檢的開(kāi)發(fā)者這里要說(shuō)明一點(diǎn)ESP32-CAM 的攝像頭接口占用了大部分 GPIO所以留給電機(jī)控制的引腳需要避開(kāi)攝像頭數(shù)據(jù)總線具體引腳分配會(huì)在第 5 節(jié)給出。若想保留更多外設(shè)接口可以換用 ESP32-S3 系列帶攝像頭底板的方案代碼邏輯類似但引腳映射需要重新調(diào)整。2. 適用場(chǎng)景與使用邊界這個(gè)項(xiàng)目適合四類場(chǎng)景。第一類是想做低成本家用/實(shí)驗(yàn)室巡檢小車比如在桌面上遠(yuǎn)程觀察設(shè)備狀態(tài)、檢查角落里是否有障礙物。第二類是學(xué)習(xí) WiFi 視頻傳輸和嵌入式 Web 服務(wù)器ESP32-CAM 提供了非常好的實(shí)驗(yàn)載體你可以通過(guò)它理解 MJPEG 流是怎么被瀏覽器解析的。第三類是機(jī)器人入門訓(xùn)練很多高校課程會(huì)把“ESP32 攝像頭小車”作為移動(dòng)機(jī)器人基礎(chǔ)平臺(tái)學(xué)生可以在上面疊加超聲波避障、紅外循跡、視覺(jué)識(shí)別等模塊。第四類是 ROS 2 和 Micro-ROS 學(xué)習(xí)者的硬件底座通過(guò) Micro-ROS 把 ESP32 接入 ROS 2 節(jié)點(diǎn)能快速做上層導(dǎo)航算法驗(yàn)證。同時(shí)也要明確邊界。ESP32-CAM 的視頻流不是高幀率高清方案峰值分辨率雖然能達(dá)到 UXGA 級(jí)別但實(shí)際跑 720p 時(shí)幀率明顯下降適合室內(nèi)近距離監(jiān)控不適合遠(yuǎn)距離高清實(shí)時(shí)畫面。它的 WiFi 通信距離受天線和干擾影響穿墻能力弱如果實(shí)驗(yàn)環(huán)境路由器信號(hào)不好畫面會(huì)卡頓甚至斷連。此外電機(jī)控制如果使用 L298N模塊自身壓降較大發(fā)熱明顯需要留意供電能力和散熱一味加大電池電壓可能導(dǎo)致模塊損壞。另外必須提醒合規(guī)和安全問(wèn)題。攝像頭屬于圖像采集設(shè)備做實(shí)驗(yàn)時(shí)不要對(duì)準(zhǔn)他人隱私區(qū)域不要未經(jīng)同意記錄無(wú)關(guān)人員畫面。涉及 WiFi 接入時(shí)請(qǐng)使用自己管理或有授權(quán)的無(wú)線網(wǎng)絡(luò)不要嘗試?yán)@過(guò)公共網(wǎng)絡(luò)認(rèn)證也不要對(duì)他人網(wǎng)絡(luò)做任何破解或干擾。最終如果要把機(jī)器人用于商業(yè)展示或公開(kāi)環(huán)境巡航還需要評(píng)估當(dāng)?shù)貙?duì)圖像采集和個(gè)人信息保護(hù)的相關(guān)要求做好標(biāo)注和授權(quán)流程。3. 硬件選型與材料準(zhǔn)備3.1 主控板ESP32-CAM 怎么選市場(chǎng)上常見(jiàn)的是 AI-Thinker ESP32-CAM 及其兼容板核心芯片為 ESP32 雙核帶 OV2640 攝像頭、板載 TF 卡槽和 LED 補(bǔ)光燈部分版本帶 4MB PSRAM。挑選時(shí)優(yōu)先選帶 PSRAM 的版本因?yàn)橐曨l幀緩沖需要額外內(nèi)存沒(méi)有 PSRAM 的板子在較高分辨率下更容易初始化失敗。價(jià)格通常在 30 元到 60 元之間注意區(qū)分帶天線座和板載天線的版本板載天線體積小但信號(hào)略弱外置天線座可接 IPEX 天線適合需要增加傳輸距離的場(chǎng)景。如果手頭已經(jīng)有 ESP32 DevKit 開(kāi)發(fā)板也可以買單獨(dú)的 OV2640 攝像頭模塊組成攝像頭系統(tǒng)但接線會(huì)繁瑣一些不推薦新手上來(lái)就用分離方案。ESP32-S3-CAM 也是可選項(xiàng)它的主頻和 GPIO 更充裕但對(duì) Arduino 生態(tài)的兼容性需要額外關(guān)注部分舊示例不能直接編譯因此本文仍以最普及的 ESP32-CAM 為例。3.2 電機(jī)驅(qū)動(dòng)模塊電機(jī)驅(qū)動(dòng)是運(yùn)動(dòng)控制的核心。常見(jiàn)選擇有 L298N、DRV8833、L9110S、MX1508 等。L298N 驅(qū)動(dòng)能力強(qiáng)支持 5V-12V 電機(jī)但模塊體積大、壓降大需要散熱DRV8833 體積小適合 N20 微型減速電機(jī)但供電范圍有限通常不超過(guò) 10VL9110S 和 MX1508 是低成本雙路驅(qū)動(dòng)方案適合 TT 馬達(dá)和小型減速電機(jī)電流能力在 1A 左右對(duì)小車的兩路電機(jī)夠用。從新手穩(wěn)定性來(lái)看我建議先選 DRV8833 或 L9110S因?yàn)榻泳€簡(jiǎn)單、邏輯電平兼容 3.3V不需要額外跳線設(shè)置。3.3 電機(jī)與底盤底盤方面有兩個(gè)方向。一個(gè)是買成品小車底盤一般包含亞克力板、兩個(gè)直流減速電機(jī)、兩個(gè)車輪和一個(gè)萬(wàn)向輪容易固定、開(kāi)孔對(duì)齊是省時(shí)間的選擇。另一個(gè)是 3D 打印底盤可自行設(shè)計(jì)支架安裝 ESP32-CAM 和電池適合以后想自己開(kāi)模做外殼的人。電機(jī)建議使用 N20 微型減速電機(jī)或 TT 減速電機(jī)前者小巧、轉(zhuǎn)速可定制適合室內(nèi)窄空間后者便宜、扭矩稍大但噪音也大適合在桌面上跑。3.4 供電方案供電決定機(jī)器人穩(wěn)不穩(wěn)定。ESP32-CAM 在開(kāi)啟 WiFi 和視頻流時(shí)瞬時(shí)電流波動(dòng)明顯如果供電能力不足會(huì)出現(xiàn)攝像頭初始化失敗、WiFi 反復(fù)重啟、畫面花屏等問(wèn)題。推薦使用兩節(jié) 18650 鋰電池串聯(lián)成 7.4V然后通過(guò) DC-DC 降壓模塊輸出 5V 給 ESP32-CAM 和電機(jī)驅(qū)動(dòng)或者使用單節(jié) 3.7V 鋰電池加 5V 升壓板但電機(jī)同時(shí)工作時(shí)可能出現(xiàn)壓降需要選擇輸出能力在 2A 以上的升壓模塊。不要直接用 Arduino 的 3.3V 引腳同時(shí)給電機(jī)驅(qū)動(dòng)和攝像頭供電那幾乎必然重啟。3.5 零散工具還需要準(zhǔn)備杜邦線、面包板或洞洞板、焊臺(tái)做正式成品建議焊接排針、USB 轉(zhuǎn) TTL 燒錄器ESP32-CAM 一般沒(méi)有板載 USB需要用串口模塊接 GND、TX、RX、3.3V/5V 燒錄部分帶 CH340 的版本例外、螺絲刀、扎帶或魔術(shù)貼用于固定電池和線纜。4. 開(kāi)發(fā)環(huán)境搭建4.1 安裝 Arduino IDE 與 ESP32 開(kāi)發(fā)板包在 Arduino IDE 中安裝 ESP32 支持需要在“首選項(xiàng)”里添加附加開(kāi)發(fā)板管理器地址。以 Arduino IDE 2.x 為例打開(kāi) File Preferences在 Additional boards manager URLs 中加入https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json然后打開(kāi) Tools Board Boards Manager搜索 esp32找到 Espressif Systems 的 esp32 by Espressif Systems點(diǎn)擊安裝。這里要提醒一下如果網(wǎng)絡(luò)環(huán)境不佳安裝過(guò)程中經(jīng)常出現(xiàn)“failed to install platform: esp32:3.3.11. 13 internal: download failed”這類錯(cuò)誤絕大多數(shù)不是配置問(wèn)題而是下載超時(shí)。遇到這種情況可以先重試一次如果仍然失敗關(guān)閉 IDE 后手動(dòng)下載對(duì)應(yīng)版本的 json 和壓縮包放入本地緩存目錄或者更換網(wǎng)絡(luò)節(jié)點(diǎn)重新嘗試。安裝完成后在 Tools Board 菜單里能看到“ESP32 Arduino”分類說(shuō)明板文件安裝成功。4.2 安裝所需庫(kù)攝像頭功能依賴 esp32-camera 庫(kù)。這個(gè)庫(kù)通常已經(jīng)包含在 esp32 開(kāi)發(fā)板包中路徑在 esp32 包目錄的 libraries 下直接引用即可。如果編譯時(shí)找不到“esp_camera.h”就說(shuō)明板包沒(méi)有正確安裝或版本過(guò)舊。另外需要確認(rèn) Arduino 自帶的 WiFi 庫(kù)可用ESP32 的 WiFi 功能不需要額外安裝。對(duì)于網(wǎng)頁(yè)服務(wù)器可以使用 ESP32 內(nèi)置的 WebServer 庫(kù)也可以使用 ESPAsyncWebServer但后者依賴異步 TCP 庫(kù)新手不建議一開(kāi)始引入先用標(biāo)準(zhǔn)庫(kù)跑通主線后面再優(yōu)化。4.3 串口燒錄設(shè)置ESP32-CAM 燒錄前需要將 GPIO0 拉低進(jìn)入下載模式。常見(jiàn)的做法是按住板上 BOOT 按鈕然后按一下 RST或者在燒錄時(shí)用杜邦線把 GPIO0 接地。接線方式如下USB 轉(zhuǎn) TTL 的 RX 接 ESP32-CAM 的 TXTX 接 RXGND 接 GND5V 接 5V 或 3.3V 接 3.3V具體取決于你的串口模塊輸出電壓。在 Arduino IDE 中選擇開(kāi)發(fā)板為 AI Thinker ESP32-CAM燒錄波特率可以先選 115200不穩(wěn)定時(shí)改成 921600 反而更可靠。5. 硬件接線與電路設(shè)計(jì)5.1 引腳分配ESP32-CAM 的攝像頭接口占用引腳較多電機(jī)控制只能使用剩余引腳。這里給出一組避開(kāi)攝像頭總線的分配方式也是常見(jiàn)做法功能GPIO電機(jī) A 正轉(zhuǎn)GPIO12電機(jī) A 反轉(zhuǎn)GPIO14電機(jī) B 正轉(zhuǎn)GPIO15電機(jī) B 反轉(zhuǎn)GPIO13板載 LED 補(bǔ)光燈GPIO4注意 GPIO4 在部分板子上也連接板載閃光燈如果你計(jì)劃外接繼電器或 PWM 設(shè)備建議先用萬(wàn)用表確認(rèn)該引腳的默認(rèn)輸出狀態(tài)。GPIO12、GPIO14、GPIO15、GPIO13 在 ESP32-CAM 板上有引針連接電機(jī)驅(qū)動(dòng)時(shí)要注意共地ESP32-CAM 的 GND 必須和電機(jī)驅(qū)動(dòng)模塊的 GND 接在一起否則邏輯電平無(wú)法形成回路電機(jī)不會(huì)動(dòng)。5.2 與 DRV8833 的接線示例如果使用 DRV8833接線方式如下ESP32-CAM GPIO12 - DRV8833 AIN1 ESP32-CAM GPIO14 - DRV8833 AIN2 ESP32-CAM GPIO15 - DRV8833 BIN1 ESP32-CAM GPIO13 - DRV8833 BIN2 DRV8833 AOUT1/2 - 電機(jī) A 兩個(gè)引腳 DRV8833 BOUT1/2 - 電機(jī) B 兩個(gè)引腳 DRV8833 VM - 電池正極注意電壓范圍 DRV8833 GND - 電池負(fù)極和 ESP32-CAM GND DRV8833 VCC - 板上 3.3V 或 5V 邏輯供電按模塊要求如果使用 L298N還需要額外接 5V 邏輯電源并在使能端 EN A、EN B 上接高電平或直接接 5V 引腳才能讓 PWM 通道生效。這里提醒一句L298N 的 VCC 邏輯供電與電機(jī)供電是分離的一定要分別接好免得電流倒灌損壞 ESP32-CAM。5.3 供電回路設(shè)計(jì)從供電角度看機(jī)器人可以分為兩個(gè)功率回路電機(jī)回路和主控回路。電機(jī)回路從電池取電經(jīng)過(guò)電機(jī)驅(qū)動(dòng)模塊供給電機(jī)主控回路從電池經(jīng)穩(wěn)壓器取 5V/3.3V 供給 ESP32-CAM 和攝像頭。兩個(gè)回路在 GND 處交匯但電源路徑盡量分開(kāi)避免電機(jī)啟動(dòng)瞬間的大電流拉低主控電壓。如果電機(jī)啟動(dòng)瞬間畫面花屏或攝像頭重啟基本可以判定是主控回路供電被干擾此時(shí)要在 ESP32-CAM 的 5V 輸入處并聯(lián) 100uF 電解電容和 0.1uF 陶瓷電容并確保電機(jī)驅(qū)動(dòng)模塊的電源線盡量短、截面積足夠。6. 攝像頭視頻流服務(wù)實(shí)現(xiàn)6.1 攝像頭初始化代碼在 Arduino 環(huán)境中攝像頭初始化的主要工作是填充 camera_config_t 結(jié)構(gòu)體并調(diào)用 esp_camera_init。引腳定義要和板子一致。下面這段代碼可以直接放到工程的頂部#include esp_camera.h #include WiFi.h #include WebServer.h #define PWDN_GPIO_NUM 32 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 0 #define SIOD_GPIO_NUM 26 #define SIOC_GPIO_NUM 27 #define Y9_GPIO_NUM 35 #define Y8_GPIO_NUM 34 #define Y7_GPIO_NUM 39 #define Y6_GPIO_NUM 36 #define Y5_GPIO_NUM 21 #define Y4_GPIO_NUM 19 #define Y3_GPIO_NUM 18 #define Y2_GPIO_NUM 5 #define VSYNC_GPIO_NUM 25 #define HREF_GPIO_NUM 23 #define PCLK_GPIO_NUM 22 #define MOTOR_A_IN1 12 #define MOTOR_A_IN2 14 #define MOTOR_B_IN1 15 #define MOTOR_B_IN2 13 const char* ssid YOUR_WIFI_SSID; const char* password YOUR_WIFI_PASSWORD; void setupCamera() { camera_config_t config; config.ledc_channel LEDC_CHANNEL_0; config.ledc_timer LEDC_TIMER_0; config.pin_d0 Y2_GPIO_NUM; config.pin_d1 Y3_GPIO_NUM; config.pin_d2 Y4_GPIO_NUM; config.pin_d3 Y5_GPIO_NUM; config.pin_d4 Y6_GPIO_NUM; config.pin_d5 Y7_GPIO_NUM; config.pin_d6 Y8_GPIO_NUM; config.pin_d7 Y9_GPIO_NUM; config.pin_xclk XCLK_GPIO_NUM; config.pin_pclk PCLK_GPIO_NUM; config.pin_vsync VSYNC_GPIO_NUM; config.pin_href HREF_GPIO_NUM; config.pin_sccb_sda SIOD_GPIO_NUM; config.pin_sccb_scl SIOC_GPIO_NUM; config.pin_pwdn PWDN_GPIO_NUM; config.pin_reset RESET_GPIO_NUM; config.xclk_freq_hz 20000000; config.pixel_format PIXFORMAT_JPEG; config.frame_size FRAMESIZE_VGA; config.jpeg_quality 12; config.fb_count 2; esp_err_t err esp_camera_init(config); if (err ! ESP_OK) { Serial.printf(攝像頭初始化失敗: 0x%x\n, err); return; } }這段代碼把幀格式設(shè)為 JPEG分辨率設(shè)為 VGA 640x480質(zhì)量系數(shù)設(shè)為 12。對(duì) 720p 有執(zhí)念的話可以把 frame_size 改成 FRAMESIZE_HD但實(shí)際幀率和穩(wěn)定性要靠后續(xù)測(cè)試確認(rèn)因?yàn)槊恳粠瑘D像大小增加后WiFi 傳輸壓力和內(nèi)存壓力都會(huì)變大。fb_count 設(shè)為 2表示使用雙幀緩沖能減少畫面撕裂但內(nèi)存占用也會(huì)增加。6.2 啟動(dòng) HTTP 視頻流服務(wù)視頻流的核心是一個(gè)不斷從攝像頭獲取幀并寫入 HTTP 響應(yīng)的 handler。這里使用標(biāo)準(zhǔn) WebServer 庫(kù)注冊(cè) /video 路徑設(shè)置響應(yīng)頭為 multipart/x-mixed-replace讓瀏覽器識(shí)別為 MJPEG 流。WebServer server(80); bool streamActive false; void handleVideoStream() { WiFiClient client server.client(); String response HTTP/1.1 200 OK\r\n; response Content-Type: multipart/x-mixed-replace; boundaryframe\r\n; response Cache-Control: no-cache\r\n; response Connection: close\r\n\r\n; client.write(response.c_str()); while (client.connected()) { camera_fb_t* fb esp_camera_fb_get(); if (!fb) { Serial.println(獲取幀失敗); continue; } String header --frame\r\nContent-Type: image/jpeg\r\nContent-Length: ; header fb-len; header \r\n\r\n; client.write(header.c_str()); client.write(fb-buf, fb-len); client.write(\r\n, 2); esp_camera_fb_return(fb); delay(30); } }這個(gè)實(shí)現(xiàn)簡(jiǎn)單直接但對(duì)于多客戶端同時(shí)訪問(wèn)會(huì)存在并發(fā)問(wèn)題。如果只需要手機(jī)和控制頁(yè)面同時(shí)觀看可以在客戶端連接前加一個(gè)互斥標(biāo)志或者用 WebSocket 分發(fā)。對(duì)入門項(xiàng)目來(lái)說(shuō)先保證單個(gè)瀏覽器能流暢看到畫面即可。6.3 網(wǎng)絡(luò)連接與完整啟動(dòng)流程在 setup 函數(shù)中先初始化串口、攝像頭、電機(jī) GPIO再連接 WiFi最后啟動(dòng) Web 服務(wù)。WiFi 連接失敗時(shí)不要直接死循環(huán)打印錯(cuò)誤信息后繼續(xù)嘗試重連避免反復(fù)重啟。void setup() { Serial.begin(115200); setupCamera(); setupMotors(); connectWiFi(); server.on(/video, HTTP_GET, handleVideoStream); server.on(/, HTTP_GET, handleRootPage); server.on(/action, HTTP_GET, handleAction); server.begin(); Serial.print(攝像頭服務(wù)地址: http://); Serial.println(WiFi.localIP()); } void loop() { server.handleClient(); checkWiFi(); }整體啟動(dòng)邏輯不復(fù)雜但要注意 connectWiFi 不要設(shè)置過(guò)長(zhǎng)阻塞。攝像頭等待時(shí)間如果太長(zhǎng)可以先初始化 WiFi 再初始化攝像頭兩種順序在不同板子上表現(xiàn)不同建議都試一下以串口日志中“攝像頭初始化成功”和“WiFi 連接成功”都出現(xiàn)為準(zhǔn)。7. 電機(jī)控制與運(yùn)動(dòng)實(shí)現(xiàn)7.1 電機(jī) GPIO 初始化電機(jī)控制使用數(shù)字輸出和 PWM 即可。前進(jìn)、后退用電機(jī)正反轉(zhuǎn)控制轉(zhuǎn)向用左右電機(jī)差速實(shí)現(xiàn)。初始化時(shí)將所有引腳設(shè)為 OUTPUT并置低電平防止上電瞬間電機(jī)誤動(dòng)。void setupMotors() { pinMode(MOTOR_A_IN1, OUTPUT); pinMode(MOTOR_A_IN2, OUTPUT); pinMode(MOTOR_B_IN1, OUTPUT); pinMode(MOTOR_B_IN2, OUTPUT); digitalWrite(MOTOR_A_IN1, LOW); digitalWrite(MOTOR_A_IN2, LOW); digitalWrite(MOTOR_B_IN1, LOW); digitalWrite(MOTOR_B_IN2, LOW); }7.2 運(yùn)動(dòng)控制函數(shù)把前進(jìn)、后退、左轉(zhuǎn)、右轉(zhuǎn)、停止封裝成獨(dú)立函數(shù)后續(xù) Web 控制層只需要根據(jù)指令字符串調(diào)用對(duì)應(yīng)函數(shù)。void setMotor(int in1, int in2, int speed) { if (speed 0) { digitalWrite(in1, HIGH); digitalWrite(in2, LOW); } else if (speed 0) { digitalWrite(in1, LOW); digitalWrite(in2, HIGH); } else { digitalWrite(in1, LOW); digitalWrite(in2, LOW); } } void runForward() { setMotor(MOTOR_A_IN1, MOTOR_A_IN2, 1); setMotor(MOTOR_B_IN1, MOTOR_B_IN2, 1); } void runBackward() { setMotor(MOTOR_A_IN1, MOTOR_A_IN2, -1); setMotor(MOTOR_B_IN1, MOTOR_B_IN2, -1); } void turnLeft() { setMotor(MOTOR_A_IN1, MOTOR_A_IN2, -1); setMotor(MOTOR_B_IN1, MOTOR_B_IN2, 1); } void turnRight() { setMotor(MOTOR_A_IN1, MOTOR_A_IN2, 1); setMotor(MOTOR_B_IN1, MOTOR_B_IN2, -1); } void stopMotors() { setMotor(MOTOR_A_IN1, MOTOR_A_IN2, 0); setMotor(MOTOR_B_IN1, MOTOR_B_IN2, 0); }這里沒(méi)有加入 PWM 調(diào)速只是一個(gè)最基礎(chǔ)的前進(jìn)后退控制。如果想實(shí)現(xiàn)平滑加速可以把 digitalWrite 換成 analogWrite并傳入 0-255 的占空比但要注意多數(shù)電機(jī)驅(qū)動(dòng)模塊的 IN1/IN2 組合邏輯是同高同低停止一高一低正反轉(zhuǎn)PWM 需要接在驅(qū)動(dòng)模塊的使能腳上不同模塊接法不同。第一次實(shí)驗(yàn)建議先跑通數(shù)字量控制再考慮調(diào)速。7.3 運(yùn)動(dòng)指令的 HTTP 接口為了讓 Web 頁(yè)面控制機(jī)器人可以注冊(cè) /action 路由通過(guò) query 參數(shù)傳遞運(yùn)動(dòng)指令void handleAction() { String cmd server.arg(cmd); if (cmd forward) { runForward(); } else if (cmd backward) { runBackward(); } else if (cmd left) { turnLeft(); } else if (cmd right) { turnRight(); } else if (cmd stop) { stopMotors(); } server.send(200, text/plain, OK); }接口服務(wù)只有這么多但實(shí)際測(cè)試時(shí)要特別注意瀏覽器連續(xù)點(diǎn)擊方向按鈕時(shí)如果接收方在兩次請(qǐng)求之間丟失命令會(huì)把機(jī)器人停在錯(cuò)誤狀態(tài)。建議在控制頁(yè)面設(shè)計(jì)成“按下按鈕發(fā)送運(yùn)動(dòng)指令松開(kāi)按鈕發(fā)送停止指令”并在單片機(jī)端增加超時(shí)保護(hù)函數(shù)比如每 200 毫秒檢查一次是否收到新的運(yùn)動(dòng)指令超時(shí)就自動(dòng)停車。下面給一個(gè)最小超時(shí)保護(hù)的示例unsigned long lastCommandTime 0; const unsigned long STOP_TIMEOUT 500; void loop() { server.handleClient(); if (millis() - lastCommandTime STOP_TIMEOUT) { stopMotors(); } }在 handleAction 中每收到合法指令就更新 lastCommandTime millis()這樣即使手機(jī)端異常斷連機(jī)器人最多空轉(zhuǎn) 0.5 秒就會(huì)停下比較安全。8. 網(wǎng)頁(yè)控制端與調(diào)試8.1 控制頁(yè)面設(shè)計(jì)控制頁(yè)面的目標(biāo)是讓手機(jī)瀏覽器同時(shí)看到視頻流和方向控制按鈕。頁(yè)面可以分為兩部分上半部分放img標(biāo)簽src 指向 /video 地址下半部分放五個(gè)控制按鈕。按鈕事件用 JavaScript 的 touchstart 和 touchend 觸發(fā)移動(dòng)端不容易誤操作。!DOCTYPE html html head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 titleESP32 Robot/title style body { text-align: center; background: #222; color: #fff; } img { width: 100%; max-width: 480px; border-radius: 8px; } .btn { width: 80px; height: 50px; margin: 5px; font-size: 18px; } /style /head body img idstream src/video div button classbtn># 查看視頻流是否返回 multipart 內(nèi)容 curl -N http://192.168.1.123/video | head -c 100 # 發(fā)送運(yùn)動(dòng)指令 curl http://192.168.1.123/action?cmdforward curl http://192.168.1.123/action?cmdstopimport requests def send_command(ip, cmd): url fhttp://{ip}/action?cmd{cmd} try: r requests.get(url, timeout2) print(cmd, r.status_code) except Exception as e: print(請(qǐng)求失敗:, e) send_command(192.168.1.123, forward) send_command(192.168.1.123, left) send_command(192.168.1.123, stop)這里 IP 需要替換成你開(kāi)發(fā)板實(shí)際打印出來(lái)的地址。如果機(jī)器人后續(xù)要接入自動(dòng)化任務(wù)系統(tǒng)只需要把運(yùn)動(dòng)指令封裝成這樣一個(gè) HTTP 客戶端就能在腳本里批量控制小車移動(dòng)。9. 視頻傳輸穩(wěn)定性與性能調(diào)優(yōu)9.1 分辨率、幀率與質(zhì)量權(quán)衡MJPEG 視頻流的幀率主要受限于圖像大小、JPEG 編碼速度、WiFi 帶寬和瀏覽器解碼能力。在 VGA 640x480、質(zhì)量系數(shù) 10-15 的情況下室內(nèi)近距離環(huán)境下常見(jiàn)能跑到 10-20fps這個(gè)值不是固定規(guī)格因?yàn)楣饩€越暗攝像頭自動(dòng)增益會(huì)導(dǎo)致 JPEG 體積變大幀率下降更明顯。如果你的場(chǎng)景需要更流暢可以降到 QQVGA 320x240畫面小但響應(yīng)快如果只需要看清靜止細(xì)節(jié)用 XGA 1280x720 也行但要做好幀率明顯下降的準(zhǔn)備。建議在代碼里把分辨率開(kāi)放為 HTTP 參數(shù)通過(guò) /camera 接口直接切換而不是每次改代碼重新燒錄。9.2 關(guān)鍵調(diào)試參數(shù)在 esp32-camera 中常用調(diào)優(yōu)參數(shù)包括 brightness、contrast、saturation、sharpness、denoise、quality 等。幀率低時(shí)優(yōu)先降低 jpeg_quality 數(shù)值比如從 12 改成 10畫質(zhì)和幀率會(huì)有明顯變化。如果畫面發(fā)灰或過(guò)曝調(diào)整 brightness 和 contrast。如果畫面閃爍優(yōu)先檢查電源穩(wěn)定性其次檢查 XCLK 頻率把 xclk_freq_hz 從 20000000 改成 10000000 也可能改善部分花屏問(wèn)題。9.3 WiFi 與天線優(yōu)化ESP32-CAM 的板載天線有方向性裝機(jī)后天線位置不要直接貼在金屬底盤上。如果機(jī)器人底盤的固定螺絲是金屬材質(zhì)會(huì)讓天線阻抗變化實(shí)測(cè)信號(hào)可能衰減明顯。建議把天線朝上或朝外遠(yuǎn)離電機(jī)線。路由器 5G 頻段與 ESP32-CAM 不兼容必須連接 2.4G 網(wǎng)絡(luò)。如果家里有多頻合一 SSID最好在路由器后臺(tái)單獨(dú)開(kāi)啟一個(gè) 2.4G SSID避免手機(jī)和開(kāi)發(fā)板自動(dòng)跳到不同頻段導(dǎo)致現(xiàn)場(chǎng)掉線。9.4 降低功耗與發(fā)熱視頻流持續(xù)運(yùn)行時(shí)ESP32-CAM 的溫度會(huì)明顯上升這是正常現(xiàn)象但如果燙到觸摸后無(wú)法長(zhǎng)時(shí)間停留說(shuō)明可能是 5V 供電電壓偏高或主板散熱不佳??梢詸z查供電模塊的輸出是否穩(wěn)定在 5V 左右超過(guò) 5.5V 就可能有風(fēng)險(xiǎn)。另外在不需要視頻流時(shí)可以暫時(shí)關(guān)閉攝像頭模塊的幀獲取循環(huán)降低 CPU 負(fù)載但這種方式對(duì)功耗優(yōu)化有限真正的低功耗還需要進(jìn)入 ESP32 的 modem sleep 或 light sleep 模式這屬于進(jìn)階話題建議后續(xù)單獨(dú)研究。10. 常見(jiàn)問(wèn)題與排查方法問(wèn)題現(xiàn)象可能原因排查方式解決方案攝像頭初始化失敗供電不足、PSRAM 缺失、引腳配置錯(cuò)誤查看串口錯(cuò)誤碼檢查板子型號(hào)使用帶 PSRAM 的板子改善供電核對(duì)引腳表WiFi 連接一直失敗SSID 寫錯(cuò)、頻段不匹配、信號(hào)弱打印 WiFi 狀態(tài)碼檢查路由器設(shè)備列表確保連 2.4G拉近路由器距離檢查密碼視頻流卡頓WiFi 帶寬不足、JPEG 過(guò)大、幀緩沖未釋放觀察路由器信號(hào)強(qiáng)度降低分辨率調(diào)低分辨率提高壓縮比檢查 esp_camera_fb_return 是否調(diào)用電機(jī)無(wú)動(dòng)作GPIO 接錯(cuò)、驅(qū)動(dòng)供電缺失、未共地用萬(wàn)用表量 GPIO 電壓檢查驅(qū)動(dòng)指示燈核對(duì)接線連接 GND檢查驅(qū)動(dòng)模塊使能上電后反復(fù)重啟主控供電被拉低觀察重啟周期和串口打印改用獨(dú)立穩(wěn)壓模塊加電容加大電池容量手機(jī)控制頁(yè)面打不開(kāi)IP 無(wú)效、端口被占用、防火墻攔截電腦訪問(wèn)同 IP 測(cè)試確認(rèn) IP重新啟動(dòng) server檢查防火墻規(guī)則燒錄報(bào)錯(cuò) ESP32:3.3.11 下載失敗板包下載超時(shí)查看 Arduino 日志重試或手動(dòng)下載板包到緩存目錄運(yùn)動(dòng)指令發(fā)送后沒(méi)有響應(yīng)URL 路徑拼寫錯(cuò)誤、命令字符串不匹配用瀏覽器直接訪問(wèn) /action?cmdforward對(duì)照代碼檢查 query 參數(shù)名第一類是攝像頭初始化失敗。串口打印錯(cuò)誤碼時(shí)按 esp_camera 定義查找對(duì)應(yīng)錯(cuò)誤常見(jiàn)原因依次是 PSRAM 未啟用、供電不足、引腳沖突。如果你買的板型是 ESP32-CAM MB也就是帶 USB 底板的版本燒錄方式和供電都更簡(jiǎn)單但部分底板會(huì)占用額外引腳接線前要查閱板型資料。第二類是視頻流打不開(kāi)。瀏覽器訪問(wèn) /video 返回 404絕大多數(shù)原因是服務(wù)沒(méi)有注冊(cè)成功返回 200 但一直轉(zhuǎn)圈可能是 JPEG 數(shù)據(jù)沒(méi)有完整發(fā)送。先在一個(gè)能自動(dòng)分幀的播放器或 curl 中驗(yàn)證數(shù)據(jù)再排查瀏覽器端。第三類是電機(jī)控制不穩(wěn)定。機(jī)器人前進(jìn)時(shí)自動(dòng)偏向一側(cè)通常是兩個(gè)電機(jī)轉(zhuǎn)速不一致??梢詼y(cè)量?jī)蓚€(gè)電機(jī)的空載電流和轉(zhuǎn)速如果差異過(guò)大更換一只電機(jī)即可如果只是輕微跑偏可以在軟件里修正左右輪的 PWM 占空比或者讓左右輪使用不同驅(qū)動(dòng)占空比常量。第四類是供電問(wèn)題導(dǎo)致的隨機(jī)重啟。不要只盯著電池電壓還要看電線電阻。杜邦線太細(xì)、接頭松動(dòng)、電池老化都會(huì)造成瞬時(shí)壓降。建議把電機(jī)線和主控線分開(kāi)動(dòng)力線路使用至少 20AWG 硅膠線連接處用焊錫固定。11. 進(jìn)階擴(kuò)展方向11.1 增加傳感器與自動(dòng)避障機(jī)器人基礎(chǔ)功能跑通后可以在前部安裝超聲波測(cè)距模塊或紅外避障傳感器。由于 ESP32-CAM 可用 GPIO 有限建議通過(guò) I2C 擴(kuò)展板把傳感器接到 PCF8574 或直接使用帶別總線的模塊例如 VL53L0X。編寫程序時(shí)可以把超聲波測(cè)距和運(yùn)動(dòng)控制放在同一個(gè) loop 中當(dāng)距離小于閾值時(shí)自動(dòng)停車或自動(dòng)轉(zhuǎn)向這就是最簡(jiǎn)單的避障機(jī)器人。如果引入 PID 控制還可以讓機(jī)器人沿走廊中線行駛這時(shí)候就開(kāi)始接觸移動(dòng)機(jī)器人路徑規(guī)劃了。11.2 接入 ROS 2 與 Micro-ROS很多搜索這個(gè)項(xiàng)目的讀者對(duì)“機(jī)器人導(dǎo)航”和“ROS2 機(jī)器人開(kāi)發(fā)”感興趣。ESP32 可以通過(guò) Micro-ROS 庫(kù)接入 ROS 2把攝像頭圖像或傳感器數(shù)據(jù)發(fā)布到 ROS 2 節(jié)點(diǎn)同時(shí)接收速度控制指令。這樣這個(gè)一百元級(jí)別的小機(jī)器人就能變成一個(gè)用來(lái)調(diào)試導(dǎo)航算法的最小硬件平臺(tái)。Micro-ROS 的 Arduino 支持已經(jīng)比較成熟主要步驟是在開(kāi)發(fā)板上跑 micro_ros_arduino 示例通過(guò)串口或 WiFi 連接到上位機(jī)中的 micro-ROS Agent之后就可以用 RViz 查看 TF、發(fā)布 cmd_vel 控制小車。想從零入門 ROS2 的話建議先準(zhǔn)備一個(gè)運(yùn)行 Ubuntu 的電腦或虛擬機(jī)把基礎(chǔ)通信跑通后再遷移到底盤上。11.3 手機(jī)藍(lán)牙控制和語(yǔ)音控制ESP32 的藍(lán)牙功能也可以利用起來(lái)。通過(guò) Low Energy 藍(lán)牙或經(jīng)典藍(lán)牙手機(jī) App 可以直連機(jī)器人不需要路由器。這樣在戶外沒(méi)有 WiFi 的環(huán)境也能控制小車但視頻傳輸會(huì)受影響因?yàn)橐曨l流依賴 WiFi。比較合理的搭配是WiFi 負(fù)責(zé)視頻傳輸藍(lán)牙負(fù)責(zé)運(yùn)動(dòng)控制指令兩條鏈路互不干擾。如果不想寫 App也可以使用現(xiàn)成的 Bluetooth Serial 類庫(kù)配合 Arduino 控制。11.4 增加舵機(jī)云臺(tái)和遠(yuǎn)程監(jiān)控在 ESP32-CAM 上外接一個(gè) SG90 舵機(jī)云臺(tái)可以控制攝像頭上下左右旋轉(zhuǎn)彌補(bǔ)小車本體轉(zhuǎn)向角度過(guò)大的問(wèn)題。舵機(jī)信號(hào)線可以接在剩余 GPIO 上使用 Servo 庫(kù)驅(qū)動(dòng)。此時(shí)控制頁(yè)面需要增加云臺(tái)控制按鈕通過(guò) /servo?pos90 這類接口設(shè)置角度。加上這塊之后機(jī)器人就具備了一定的“遠(yuǎn)程巡視”能力正好匹配標(biāo)題里“探索未知角落”的應(yīng)用。11.5 接入 4G 模塊實(shí)現(xiàn)跨網(wǎng)段控制如果實(shí)驗(yàn)場(chǎng)景不在同一局域網(wǎng)內(nèi)可以使用 4G 模塊搭配云服務(wù)器中轉(zhuǎn)但這會(huì)讓成本和功耗明顯上升。更簡(jiǎn)單的方案是內(nèi)網(wǎng)穿透工具但依賴外部服務(wù)建議在室內(nèi)局域網(wǎng)調(diào)試好基本功之后再做考慮。對(duì)大多數(shù)場(chǎng)景來(lái)說(shuō)先從局域網(wǎng)視頻傳輸和控制開(kāi)始把穩(wěn)定性、延遲和電池續(xù)航做到心中有數(shù)再?zèng)Q定是否需要跨網(wǎng)段遠(yuǎn)程控制。12. 總結(jié)與下一步這個(gè)項(xiàng)目最值得嘗試的點(diǎn)在于用一塊帶攝像頭的 ESP32 主板同時(shí)完成視頻采集、網(wǎng)絡(luò)服務(wù)和電機(jī)控制沒(méi)有引入額外的高價(jià)控制器也沒(méi)有復(fù)雜的外設(shè)驅(qū)動(dòng)。它是理解 WiFi 智能小車底層原理的一條捷徑。第一次上手時(shí)建議先不要追求高分辨率和高幀率用 VGA 分辨率把視頻流和電機(jī)控制跑通然后逐步提高質(zhì)量參數(shù)觀察畫面延遲和系統(tǒng)穩(wěn)定性。最容易踩的坑是供電和引腳沖突只要嚴(yán)格按照引腳表接線、保證 5V/2A 級(jí)別的供電能力大多數(shù)問(wèn)題都能避免。后續(xù)繼續(xù)擴(kuò)展時(shí)可以按這條路線走先加超聲波避障再嘗試舵機(jī)云臺(tái)然后接入 Micro-ROS 和 ROS 2 導(dǎo)航最后在底盤上做小型巡檢應(yīng)用。設(shè)備和代碼都跑穩(wěn)之后你會(huì)發(fā)現(xiàn)一塊幾十元的開(kāi)發(fā)板能帶來(lái)的空間還很大。