戰(zhàn):用LVGL打造低成本人機(jī)界面)
用 ESP32-S2 做 HMI 界面這塊 Dev Kit 值得好好拆一拆最近我在做一款桌面環(huán)境監(jiān)測(cè)面板的原型手里正好有一塊以 ESP32-S2 為核心、專門面向 HMI人機(jī)界面設(shè)計(jì)的 Dev Kit。這塊板子給我的第一感受是它不是那種功能堆疊的通用開發(fā)板而是把顯示接口、觸摸、USB 和外設(shè) IO 全部朝做出一個(gè)能看、能點(diǎn)、能聯(lián)網(wǎng)的交互界面這個(gè)方向去優(yōu)化。如果你正在為智能家居中控屏、儀器面板、桌面小設(shè)備做界面想用低成本方案把 LVGL 跑起來這篇文章可以幫你省掉不少查手冊(cè)的時(shí)間。我打算從整個(gè)方案的來龍去脈講起再到硬件布局、軟件工具鏈、實(shí)際跑通步驟最后把踩過的坑和排查經(jīng)驗(yàn)整理出來。這不是官方文檔的復(fù)讀是我實(shí)際用下來的真實(shí)記錄照著做能出活的那種。1. 這套 Dev Kit 到底解決了什么問題1.1 工業(yè) HMI 和嵌入式 HMI 的路線差異在講這塊板子之前得先弄清楚嵌入式 HMI 和傳統(tǒng)工業(yè) HMI 的區(qū)別。用過西門子博途TIA Portal的人都知道工業(yè) HMI 的軟件鏈路是組態(tài)軟件 專用屏 PLC 數(shù)據(jù)綁定配置起來繁瑣而且生態(tài)封閉。光是仿真按鈕是灰色、仿真按鈕點(diǎn)了沒反應(yīng)這類問題就能在技術(shù)社區(qū)里翻出幾十頁帖子。西門子 PLC 要把變頻器參數(shù)顯示到 HMI 上還需要做變量映射、通信配置一套流程。這沒錯(cuò)工業(yè)場(chǎng)景要的是穩(wěn)定和可追溯代價(jià)就是成本高、上手慢、改一版界面往往要重新下載組態(tài)。而嵌入式 HMI 的路線完全不同。它把 MCU、顯示屏、觸摸、通信協(xié)議整合在一個(gè)開放的環(huán)境里用 LVGL 這類開源圖形庫(kù)畫界面用 Wi-Fi 或串口對(duì)接數(shù)據(jù)來源。成本可以壓到幾十塊錢迭代也快改一個(gè)按鈕的位置不用等組態(tài)編譯半天重新燒一版固件就完事。ESP32-S2 的 Dev Kit 盯的就是這個(gè)生態(tài)位。1.2 這塊板子的目標(biāo)場(chǎng)景和適用人群這塊 Dev Kit 解決的典型問題是如何在低成本、低功耗的前提下快速做出一個(gè)帶觸摸交互的圖形界面并且讓設(shè)備具備聯(lián)網(wǎng)能力。適用場(chǎng)景很明確智能家居面板燈光控制、空調(diào)控制、窗簾控制。桌面小儀表CPU 溫度監(jiān)控、天氣時(shí)鐘、倒計(jì)時(shí)器。小型工控面板用 Modbus RTU 讀傳感器、顯示狀態(tài)。創(chuàng)客作品的人機(jī)交互入口機(jī)械臂控制臺(tái)、門禁面板。適合的人群也比較好判斷會(huì)一點(diǎn) Arduino 或 ESP-IDF、想用 LVGL 做界面的嵌入式開發(fā)者或者是做產(chǎn)品原型想快速驗(yàn)證 UI 交互的硬件工程師。如果你是完全沒碰過單片機(jī)的小白也能按后面的步驟跑通第一個(gè)界面但最好先把基礎(chǔ)電路知識(shí)補(bǔ)一補(bǔ)。2. 硬件方案拆解為什么 ESP32-S2 適合干這活2.1 ESP32-S2 在顯示上的底子MCU 能不能做 HMI不是看主頻有多高而是看它有沒有干凈利落的顯示通路。ESP32-S2 在這方面有一個(gè)被低估的優(yōu)勢(shì)內(nèi)置 LCD 并行接口LCD_CAM 外設(shè)的一部分支持 8-bit 和 16-bit 的并行 RGB 輸出。這意味著它可以直連 480x272、800x480 這類 RGB 接口屏不需要像老 ESP32 那樣用 GPIO 模擬時(shí)序刷新速度和穩(wěn)定性都上了一個(gè)臺(tái)階。除了并行 RGBESP32-S2 的 SPI 接口也夠用。市面上大量 240x320、320x480 的 SPI TFT 屏ST7789、ILI9341 這類驅(qū)動(dòng)用 SPI 跑 40MHz 到 80MHz 完全沒問題。對(duì)于界面元素較多、刷新頻繁的場(chǎng)景選并行 RGB 屏對(duì)于界面簡(jiǎn)單、看重成本的項(xiàng)目SPI 屏足夠。再一個(gè)不得不提的點(diǎn)是 USB。ESP32-S2 內(nèi)置全速 USB OTG除了燒錄固件還能接 USB 鍵鼠、U 盤這讓 HMI 設(shè)備的輸入方式多了一種可能。比如在面板上插一個(gè) USB 鼠標(biāo)LVGL 里注冊(cè)一下輸入設(shè)備就能直接控制光標(biāo)調(diào)試 UI 非常方便。2.2 板載外設(shè)和典型引腳分配絕大多數(shù) ESP32-S2 HMI Dev Kit 會(huì)遵循一套類似的外設(shè)配置一個(gè) USB-C 口用于供電和燒錄、一塊 TFT 屏接口、一個(gè)觸摸芯片接口、若干個(gè) GPIO 排針可能還帶一兩個(gè)按鍵和一顆 LED。我這里以我手頭這塊的典型接法為例做個(gè)說明。SPI 屏常用的引腳分配功能GPIO 引腳說明TFT_SCKGPIO12SPI 時(shí)鐘TFT_MOSIGPIO11SPI 數(shù)據(jù)TFT_CSGPIO10片選低電平有效TFT_DCGPIO9數(shù)據(jù)/命令選擇TFT_RSTGPIO8復(fù)位低電平有效TFT_BLGPIO7背光可接 PWM觸摸 INTGPIO3XPT2046 或 FT6236 中斷注意ESP32-S2 的上電啟動(dòng)模式依賴 GPIO0 和 GPIO46 的電平狀態(tài)所以一般板子不會(huì)把 GPIO0 引到排針上做普通輸出用。設(shè)計(jì) PCB 時(shí)Boot 按鍵會(huì)單獨(dú)接一個(gè)下拉電阻到地按下就是拉低進(jìn)入下載模式。這是板子布局上很關(guān)鍵的一個(gè)細(xì)節(jié)新手畫板時(shí)容易忽略。2.3 供電和 USB 部分的設(shè)計(jì)考量HMI 設(shè)備有個(gè)實(shí)際痛點(diǎn)屏幕背光一開電流立刻上去。一塊 3.5 英寸 TFT 背光全亮?xí)r功耗能到 150mA 以上加上 ESP32-S2 在 Wi-Fi 發(fā)射時(shí)的峰值電流可達(dá) 300mA 以上供電設(shè)計(jì)就不能馬虎。這塊 Dev Kit 的供電鏈路通常是USB 5V - LDO 轉(zhuǎn) 3.3V有的用 AMS1117有的用 RT9013 這類低壓差 LDO再通過一個(gè)磁珠或小電阻把數(shù)字部分和模擬部分隔離。如果你要自己畫板子我的建議是背光的供電不要走 MCU 的 3.3V直接從 5V 經(jīng)過一個(gè)三極管或 MOS 管做開關(guān)這樣既不影響 MCU 的電壓穩(wěn)定還能用 PWM 調(diào)節(jié)亮度范圍更寬。還有一點(diǎn)容易被忽視USB-C 的 CC 電阻。如果 Dev Kit 只做從設(shè)備device mode兩個(gè) CC 引腳各接一個(gè) 5.1k 下拉電阻就行但要支持 USB OTG 接 U 盤或鍵鼠就得把 ID 腳和 VBUS 檢測(cè)都做對(duì)。很多第三方的 S2 板子在 OTG 上翻車就是 CC 電阻沒接對(duì)導(dǎo)致插了設(shè)備沒反應(yīng)。3. 軟件鏈路LVGL 怎么落到這塊板子上3.1 兩個(gè)主流開發(fā)路徑怎么選HMI 軟件層目前基本是 LVGL 的天下。LVGL 現(xiàn)在主推 v8/v9開源、控件豐富、支持中英文和動(dòng)畫社區(qū)生態(tài)比幾年前成熟太多。往 ESP32-S2 上移植 LVGL常見的有兩條路第一條是 Arduino 生態(tài)用 TFT_eSPI 驅(qū)動(dòng)屏幕再疊加 LVGL。優(yōu)點(diǎn)是環(huán)境簡(jiǎn)單、示例多適合快速驗(yàn)證。缺點(diǎn)是 TFT_eSPI 和 LVGL 的版本匹配偶爾會(huì)讓人頭疼而且底層優(yōu)化空間有限。第二條是 ESP-IDF 原生生態(tài)用 esp_lcd 組件驅(qū)動(dòng)屏幕再用官方推薦的 esp_lvgl_port 組件對(duì)接 LVGL。優(yōu)點(diǎn)是性能更好、和 FreeRTOS 集成度高、能直接使用 esp_lcd 的并行 RGB 接口適合做正式產(chǎn)品。我的建議是純學(xué)習(xí)和原型驗(yàn)證走 Arduino想認(rèn)真做產(chǎn)品、要用 RGB 并行屏或者 USB 外設(shè)直接上 ESP-IDF。兩條路我都會(huì)給出關(guān)鍵步驟。3.2 Arduino 路徑TFT_eSPI 的配置要點(diǎn)用 Arduino 跑 LVGL 時(shí)90% 的坑集中在 TFT_eSPI 的配置文件上。TFT_eSPI 通過一個(gè) User_Setup.h 來定義屏幕型號(hào)和引腳很多人的問題是照搬別人的配置但板子的引腳根本不一樣。關(guān)鍵的配置項(xiàng)包括#define ST7789_DRIVER #define TFT_WIDTH 240 #define TFT_HEIGHT 320 #define TFT_MOSI 11 #define TFT_SCLK 12 #define TFT_CS 10 #define TFT_DC 9 #define TFT_RST 8 #define TFT_BL 7 #define SPI_FREQUENCY 40000000注意 SPI_FREQUENCY 我沒直接頂?shù)?80MHz。對(duì)于長(zhǎng)排線和飛線連接的情況40MHz 更穩(wěn)。等界面跑起來、確認(rèn)無花屏再往上加頻率不遲。TFT_BL 用單獨(dú)的引腳控制后記得在 setup 里設(shè)置 analogWrite 或 ledcSetup 初始化為高電平否則屏幕一亮一暗很影響調(diào)試體驗(yàn)。3.3 ESP-IDF 路徑esp_lcd 和 LVGL 的對(duì)接ESP-IDF 下esp_lcd 組件把 SPI 屏和 RGB 屏都抽象成了統(tǒng)一的 panel 接口。以 ST7789 SPI 屏為例核心代碼如下esp_lcd_panel_io_handle_t io_handle NULL; esp_lcd_panel_io_spi_config_t io_config { .dc_gpio_num 9, .cs_gpio_num 10, .pclk_hz 40 * 1000 * 1000, .lcd_cmd_bits 8, .lcd_param_bits 8, .spi_mode 0, .trans_queue_depth 10, }; esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)spi_bus, io_config, io_handle); esp_lcd_panel_handle_t panel NULL; esp_lcd_panel_dev_config_t panel_config { .reset_gpio_num 8, .color_space ESP_LCD_COLOR_SPACE_RGB, .bits_per_pixel 16, }; esp_lcd_new_panel_st7789(io_handle, panel_config, panel);對(duì)接 LVGL 時(shí)我推薦直接用 esp_lvgl_port 組件。它內(nèi)部做了刷新線程和繪制緩沖的管理你只需要調(diào)用esp_lvgl_port_add_disp把 esp_lcd 的 panel handle 傳進(jìn)去就行。相比自己寫刷新回調(diào)這個(gè)組件的可靠性高很多官方也在持續(xù)維護(hù)。3.4 UI 設(shè)計(jì)工具SquareLine Studio 帶來效率提升有讀者可能已經(jīng)注意到LVGL 手寫界面布局的效率不高尤其當(dāng)按鈕、滑塊、圖表一多代碼量很大。SquareLine Studio 是專門為 LVGL 做可視化 UI 設(shè)計(jì)的工具拖拽控件、配置樣式、設(shè)置事件最后一鍵導(dǎo)出 C 代碼再和 ESP32-S2 工程合在一起編譯。用 SquareLine Studio 有個(gè)實(shí)際經(jīng)驗(yàn)導(dǎo)出的代碼默認(rèn)按默認(rèn)屏幕尺寸生成你必須在創(chuàng)建工程時(shí)就把分辨率設(shè)成顯示器的實(shí)際參數(shù)。我見過很多人在 480x320 的設(shè)計(jì)稿上做界面然后導(dǎo)到 240x320 的屏上結(jié)果控件全部錯(cuò)位。另外事件回調(diào)里只填業(yè)務(wù)邏輯不要放耗時(shí)操作比如網(wǎng)絡(luò)請(qǐng)求否則 LVGL 的刷新會(huì)被卡住界面上按鈕按了沒反應(yīng)觀感和博途仿真按鈕無反應(yīng)一個(gè)問題樣。4. 實(shí)操?gòu)牧惆岩粋€(gè) HMI 界面跑起來4.1 燒錄環(huán)境的準(zhǔn)備和進(jìn)入下載模式拿到 Dev Kit 后第一步是裝好工具鏈。Arduino 路線需要在開發(fā)板管理器里添加 esp32 的包選擇ESP32S2 Dev Module或?qū)?yīng)的板子型號(hào)。ESP-IDF 路線需要 clone esp-idf執(zhí)行 install.sh 和 export.sh然后idf.py set-target esp32s2。燒錄前有個(gè)關(guān)鍵動(dòng)作進(jìn)入下載模式。雖然 ESP32-S2 支持 USB CDC 自動(dòng)復(fù)位但不同板子的自動(dòng)下載電路質(zhì)量參差不齊。最穩(wěn)妥的辦法是按住 BOOT 鍵不放短按一下 RESET 鍵再松開 BOOT。此時(shí)電腦上會(huì)枚舉出一個(gè)帶串口屬性的 USB 設(shè)備。我在實(shí)際使用中遇到過不少插上沒反應(yīng)的情況。排查順序很固定先確認(rèn)線材是不是數(shù)據(jù)線很多 Type-C 線只能充電再確認(rèn)設(shè)備管理器里有沒有新串口最后檢查是不是進(jìn)了下載模式。這三個(gè)環(huán)節(jié)覆蓋了九成以上的連接問題。4.2 跑通第一個(gè) LVGL 示例我最推薦的起點(diǎn)是用 SquareLine Studio 導(dǎo)出一個(gè)最簡(jiǎn)單的Hello World界面或者直接用 LVGL 自帶的 demo 工程。以 ESP-IDF 為例工程結(jié)構(gòu)大致是這樣的my_hmi_project/ ├── main/ │ ├── CMakeLists.txt │ ├── idf_component.yml │ └── main.c │ └── ui/ │ ├── ui.c │ └── ui.h └── sdkconfigidf_component.yml里聲明依賴dependencies: lvgl/lvgl: ^9.0.0 espressif/esp_lvgl_port: ^2.0.0主程序里做四件事初始化 SPI 總線、初始化面板、初始化 LVGL 并注冊(cè)顯示設(shè)備、加載 UI 界面。跑通之后屏幕會(huì)顯示第一個(gè)可交互的頁面。我用這塊板子實(shí)測(cè)從編譯到燒錄完成大概 5 分鐘能完成一次迭代比工業(yè) HMI 的組態(tài)下載周期快一個(gè)數(shù)量級(jí)。4.3 按鈕事件的處理和頁面切換HMI 的核心是交互LVGL 里按鈕事件并不難。用 SquareLine Studio 創(chuàng)建按鈕后會(huì)自動(dòng)生成一個(gè)回調(diào)函數(shù)void ui_btn_next_clicked(lv_event_t * e) { lv_scr_load_anim(ui_screen2, LV_SCR_LOAD_ANIM_FADE_IN, 200, 0, false); }這段代碼的意圖是把當(dāng)前屏幕切換到一個(gè)新屏幕帶 200ms 淡入動(dòng)畫。這里有個(gè)性能上的講究lv_scr_load_anim的動(dòng)畫過程會(huì)持續(xù)占用 GPU 時(shí)間片實(shí)際上就是 CPU如果動(dòng)畫時(shí)長(zhǎng)設(shè)置過長(zhǎng)配合復(fù)雜的背景圖會(huì)明顯掉幀。我一般控制在 150ms 到 250ms 之間既順滑又不拖慢刷新。按鈕無反應(yīng)的原因我排查下來最常見的是事件回調(diào)確實(shí)被調(diào)用了但界面代碼里用的控件名字和 SquareLine Studio 里不一致或者是在 LVGL 任務(wù)還沒初始化完就去加載了界面。寫代碼時(shí)記住一個(gè)原則所有 LVGL 控件的操作都要在lv_init()之后、并且最好在主任務(wù)或lv_timer_handler的上下文里執(zhí)行不要隨便在中斷里調(diào)用 LVGL API。4.4 數(shù)據(jù)接入把傳感器數(shù)值顯示到界面上做 HMI 不可能只顯示死界面總得接點(diǎn)數(shù)據(jù)。在我的環(huán)境監(jiān)測(cè)面板項(xiàng)目里數(shù)據(jù)來源是一個(gè)溫濕度傳感器通過 I2C 讀取再通過 Wi-Fi 上報(bào)。LVGL 里更新 label 的文本很簡(jiǎn)單char buf[32]; snprintf(buf, sizeof(buf), %.1f C, temperature); lv_label_set_text(ui_temp_label, buf);但這里藏著一個(gè)新手容易踩的坑不要在一個(gè)高頻循環(huán)里直接調(diào)lv_label_set_text。LVGL 的文本更新涉及重繪區(qū)域的重新計(jì)算頻率太高會(huì)刷屏閃爍CPU 也被拖累。更合理的做法是每 500ms 或 1s 更新一次并且只在數(shù)值變化超過閾值時(shí)才更新。5. 常見問題與排查實(shí)錄5.1 白屏、黑屏、花屏的定位方法屏幕顯示異常是 HMI 項(xiàng)目里最消磨耐心的環(huán)節(jié)。我把現(xiàn)象、原因、解決辦法整理成一個(gè)速查表供你對(duì)照現(xiàn)象常見原因解決辦法屏幕完全不亮背光引腳沒拉高或排線接觸不良檢查 BL 引腳電平用萬用表量背光兩端電壓白屏但背光亮復(fù)位時(shí)序不對(duì)或初始化參數(shù)錯(cuò)誤確認(rèn) RST 引腳有沒有接到面板驅(qū)動(dòng)延長(zhǎng)復(fù)位后的延時(shí)花屏、色塊亂跳SPI 速率過高或接線過長(zhǎng)降低 SPI_FREQUENCY 到 20MHz 測(cè)試檢查飛線長(zhǎng)度顯示內(nèi)容偏移屏幕驅(qū)動(dòng) IC 的設(shè)置參數(shù)是 8 位色深而實(shí)際用了 16 位核對(duì) data_format 設(shè)置和color_space刷新有殘影刷新率偏低或灰度極性配置不對(duì)調(diào)整 SPI 速率、開啟雙緩沖白屏的排查有個(gè)高效思路先手動(dòng)點(diǎn)亮背光試試。如果背光都不亮問題大概率在硬件層背光亮但無內(nèi)容才需要看初始化代碼。這個(gè)思路能幫你快速壓縮問題范圍。5.2 觸摸無反應(yīng)的排查思路觸摸問題在社區(qū)里的提問頻率很高我有一次在論壇上看人問ESP32-S2 HMI 屏觸摸沒反應(yīng)下面跟帖了十幾條建議最后發(fā)現(xiàn)是觸摸芯片的 I2C 地址錯(cuò)了。觸摸屏的驅(qū)動(dòng)芯片常見有兩種老一點(diǎn)的是 XPT2046SPI 接口電阻式新一點(diǎn)的是 FT6236/GT911I2C 接口電容式。FT6236 的默認(rèn)地址是 0x38GT911 的地址由引腳的 I2C 地址選擇位決定常見是 0x5D 或 0x14。如果你在 I2C scan 里掃不到設(shè)備先檢查地址再檢查中斷引腳。很多電容觸摸芯片需要主機(jī)先拉高 INT 引腳一段時(shí)間才能正常工作這個(gè)細(xì)節(jié)在數(shù)據(jù)手冊(cè)里很隱蔽。軟件層面LVGL 接觸摸的方法是注冊(cè)一個(gè)輸入設(shè)備lv_indev_drv_t indev_drv; lv_indev_drv_init(indev_drv); indev_drv.type LV_INDEV_TYPE_POINTER; indev_drv.read_cb touchpad_read; lv_indev_drv_register(indev_drv);touchpad_read回調(diào)里要做的就是把觸摸坐標(biāo)寫入 LVGL 的 data 結(jié)構(gòu)并設(shè)置>#define MY_DISP_HOR_RES 240 #define MY_DISP_VER_RES 320 static lv_disp_draw_buf_t draw_buf; static lv_color_t buf[MY_DISP_HOR_RES * 40];40 行高的部分緩沖區(qū)大小是 240x40x2 19.2KB完全在安全范圍內(nèi)。如果你的板子帶 PSRAM比如 ESP32-S2-WROVER 模塊可以考慮把緩沖區(qū)放在 PSRAM 里進(jìn)一步減少內(nèi)部 SRAM 占用。界面卡頓的另一個(gè)來源是圖片資源。LVGL 顯示 PNG/JPG 需要解碼解碼過程很吃 CPU。對(duì)嵌入式 HMI 來說最穩(wěn)妥的做法是圖片資源提前轉(zhuǎn)成 C 數(shù)組格式C array 或 SJPG用lv_img_set_src直接加載避免運(yùn)行時(shí)解碼的消耗。6. 個(gè)人體會(huì)和后續(xù)擴(kuò)展方向用這塊 Dev Kit 做了幾個(gè) HMI 原型之后我的體會(huì)是ESP32-S2 在顯示 交互 聯(lián)網(wǎng)這條鏈路上確實(shí)是一把好手成本低、開放度好尤其適合產(chǎn)品化的早期驗(yàn)證。和傳統(tǒng)工控 HMI 那種封閉、昂貴、依賴組態(tài)軟件的路線相比它打開了一個(gè)完全不同的思路尤其適合快速迭代和個(gè)性化定制。最后再說一個(gè)實(shí)用小技巧調(diào)試 HMI 界面時(shí)別急著接屏幕。先用 LVGL 模擬器PC 上的 SDL 模擬器把界面布局和交互邏輯調(diào)好再移植到 ESP32-S2 上。這樣一次編譯就能看到界面效果跟工業(yè) HMI 里先仿真后下載的流程很像但速度要快得多。把問題前置到 PC 端解決能省下大量反復(fù)燒錄的時(shí)間。等界面邏輯穩(wěn)定了再跟硬件聯(lián)調(diào)整個(gè)過程會(huì)順暢很多。后續(xù)我打算在這塊板子上再接一個(gè) MQTT 客戶端把云端的設(shè)備狀態(tài)實(shí)時(shí)推到界面再把 Modbus RTU 從機(jī)協(xié)議加上讓它能跟簡(jiǎn)單的 PLC 或傳感器模塊通信。有進(jìn)展了再來更新。