韌體程式碼導覽:目錄結構、狀態機與開發規則

韌體程式碼導覽:目錄結構、狀態機與開發規則

小智韌體是一個 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 -v

scripts/build.py 會改寫本地 sdkconfig 和建置狀態,切換板子後不要假定舊的建置目錄還對應上一個目標。

上游對驗證的要求是:

  • 只改板級程式碼:建置受影響的變體,並在實體硬體上做冒煙測試。
  • 改核心、通用板級、音訊、協議、顯示、依賴、Kconfig 或 CMake:執行主機側測試,並建置有代表性的晶片與網路路徑。
  • 改音訊:驗證採集、播放、喚醒/VAD、打斷、重連以及適用的 AEC 模式。
  • 改 UI 或 assets:驗證無屏/OLED/LVGL 各路徑與分區大小。
  • 建置成功不等於硬體驗證,彙報時要寫明測了什麼、哪些還需要實體硬體。

相關文檔

對照來源: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。