韌體程式碼導覽:目錄結構、狀態機與開發規則
小智韌體是一個 ESP-IDF 的 C/C++ 工程,同時支援多種晶片、開發板、螢幕、音訊器件和網路通道。一次建置只會選擇一個開發板實現。本頁按上游 v2.5.2 的 AGENTS.md、main/audio/README.md 和原始碼整理,幫你在動手改程式碼前先找對位置。
一、原始碼地圖
| 位置 | 職責 |
|---|---|
main/main.cc | 初始化 NVS,建立 Application 並進入 Run() 主迴圈 |
main/application.* | 主事件迴圈、協議生命週期、高層行為(喚醒、對話、升級、通知) |
main/device_state_machine.*、device_state.h | 合法的執行狀態及狀態轉換 |
main/boards/common/ | 板級介面與可複用的硬體/網路輔助類別 |
main/boards/<廠商>/<板>/ | 每個板子的引腳、初始化和建置變體(config.h、config.json) |
main/audio/ | 編解碼器、音訊任務、輸入引擎、喚醒詞與佇列 |
main/protocols/ | 與傳輸無關的 Protocol 介面,以及 WebSocket、MQTT/UDP 兩種實現 |
main/display/、main/led/ | LCD、OLED、表情顯示與 LED 的通用實現 |
main/mcp_server.* | 裝置端 MCP 工具及分發 |
main/ota.*、main/assets.*、main/settings.* | OTA 版本檢查與啟用、韌體升級、assets 分區、NVS 設定 |
main/Kconfig.projbuild、main/CMakeLists.txt | 板型與功能配置;原始碼、板卡、語言、字型和資源的選擇 |
scripts/build.py | 官方推薦的板型/變體建置入口 |
二、執行流程
main.cc 初始化 NVS 後呼叫 Application::GetInstance().Initialize() 與 Run(),Run() 是永不回傳的主事件迴圈。執行狀態由 DeviceStateMachine 維護,DeviceState 共有以下取值:
| 狀態 | 含義 |
|---|---|
kDeviceStateStarting | 啟動中 |
kDeviceStateWifiConfiguring | 配網模式,見首次啟動 |
kDeviceStateIdle | 空閒,等待喚醒或按鍵 |
kDeviceStateConnecting | 正在建立伺服器連線 |
kDeviceStateListening | 錄音上行 |
kDeviceStateSpeaking | 播放回覆 |
kDeviceStateNotifying | 通知提示 |
kDeviceStateUpgrading | 韌體或資源升級中 |
kDeviceStateActivating | 啟用流程 |
kDeviceStateAudioTesting | 音訊測試 |
kDeviceStateFatalError | 致命錯誤 |
Application 對外提供 ToggleChatState()、StartListening()、StopListening()、WakeWordInvoke()、Alert()、Schedule()、SendMcpMessage() 等入口,板級程式碼(如按鍵回呼)透過它們觸發行為。
三、板級抽象
Board(main/boards/common/board.h)是核心程式碼依賴的介面,主要純虛方法有 GetBoardType()、GetAudioCodec()、GetNetwork()、StartNetwork()、GetNetworkStateIcon()、SetPowerSaveLevel()、GetBoardJson()、GetDeviceStatusJson();背光、LED、顯示、攝影機和電池則是可選能力。
boards/common/ 裡提供了不同網路形態的基底類別,例如 WifiBoard、Ml307Board、DualNetworkBoard、EthernetBoard、RndisBoard、Nt26Board,以及按鍵、電源管理、休眠定時器等輔助類別。每個具體板子繼承其中一個基底類別,並用 DECLARE_BOARD(類名) 匯出,建置中必須恰好有一個。
新增開發板的完整流程見自訂開發板指南。
四、音訊路徑
音訊由 AudioService 管理:編解碼器 I/O、Opus 編解碼和網路佇列與晶片相關的語音處理分離。
- 上行:麥克風 →
AudioCodec→AudioInputTask→ 一個AudioEngine(S3 / P4 / S31 為AfeAudioEngine,其餘晶片為LiteAudioEngine)→ 16 kHz 單聲道 PCM →OpusCodecTask編碼 → 傳送佇列 → 網路。 - 下行:網路 → 解碼佇列 →
OpusCodecTask解碼 → 播放佇列 →AudioOutputTask→ 編解碼器 → 喇叭。 - Opus 幀長 60 ms;佇列使用編譯期固定容量的儲存。
引擎與晶片的對應關係見晶片支援與儲存規格。
五、協議層
Protocol 抽象了開啟/關閉音訊通道、傳送音訊、傳送喚醒詞、開始/停止監聽、中斷播放和 MCP 訊息等動作,具體傳輸由 WebsocketProtocol、MqttProtocol 實現。協議細節見 WebSocket 通訊協議、MQTT + UDP 混合協議和 MCP 協議。
六、開發規則
以下規則摘自上游 AGENTS.md,改程式碼前請先對照:
- 保持改動聚焦,不要夾帶無關格式化;只對改動過的 C/C++ 檔案按儲存庫
.clang-format格式化,參見程式碼風格指南。 - 一次建置必須恰好匯出一個
DECLARE_BOARD(...)。 - 不要為了適配別的硬體去改已有開發板的引腳;請新增一個名稱唯一的板子或發布變體,因為板型標識會影響 OTA 相容。
- 核心程式碼只依賴
Board介面,不依賴具體板類或某個板的config.h。 - 攝影機、背光、顯示、LED、電池等都按可選能力處理。
- 執行狀態只透過
Application::SetDeviceState()和狀態機修改。 - 回呼可能不在主任務中執行:用
Application::Schedule()或事件位切回主任務再修改應用狀態。 - 不要阻塞主事件迴圈和音訊任務;音訊路徑避免無界佇列與反覆的大塊分配。
- 共享的訊息語義放在
Protocol中;改動協議契約時要同時驗證 WebSocket 和 MQTT/UDP。 - 校驗網路輸入,保持
cJSON的所有權約定;NVS 鍵屬於持久化 API,修改需要遷移。 - 目標相關的功能用 Kconfig 或元件規則約束,不要假定每個晶片都有 PSRAM 或 S3/P4 的資源。
- 不要手工修改生成物或第三方輸出:
build/、releases/、managed_components/、components/、sdkconfig*、main/assets/lang_config.h等。
七、建置與驗證
建置前先載入 ESP-IDF 環境(source /path/to/esp-idf/export.sh,用 idf.py --version 確認),再使用上游推薦的命令:
# Discover exact board and variant names
python3 scripts/build.py --list-boards
# Canonical variant build
python3 scripts/build.py <board-directory> --name <variant-name>
# Host-side build tests
python3 -m unittest discover -s scripts/tests -vscripts/build.py 會改寫本地 sdkconfig 和建置狀態,切換板子後不要假定舊的建置目錄還對應上一個目標。
上游對驗證的要求是:
- 只改板級程式碼:建置受影響的變體,並在實體硬體上做冒煙測試。
- 改核心、通用板級、音訊、協議、顯示、依賴、Kconfig 或 CMake:執行主機側測試,並建置有代表性的晶片與網路路徑。
- 改音訊:驗證採集、播放、喚醒/VAD、打斷、重連以及適用的 AEC 模式。
- 改 UI 或 assets:驗證無屏/OLED/LVGL 各路徑與分區大小。
- 建置成功不等於硬體驗證,彙報時要寫明測了什麼、哪些還需要實體硬體。
相關文檔
- ESP-IDF 開發環境搭建:IDF v6.0.1 及以上
- 自訂開發板指南:新增開發板的完整步驟
- 進階擴充點:網路形態、喚醒詞、MCP、通知與資源定製
- 問題排查:編譯、燒錄與執行時問題
對照來源:78/xiaozhi-esp32 v2.5.2 的 AGENTS.md、main/audio/README.md、main/application.h、main/device_state.h、main/boards/common/board.h、main/protocols/protocol.h。