自定義開發板指南
本指南介紹如何為小智AI語音聊天機器人專案定製一個新的開發板初始化程式。小智AI支援大量 ESP32 系列開發板,每個開發板的初始化程式碼都放在對應的目錄下。
重要提示
警告: 對於自定義開發板,當IO配置與原有開發板不同時,切勿直接覆蓋原有開發板的配置編譯韌體。必須建立新的開發板型別,或者透過config.json檔案中的builds配置不同的name和sdkconfig宏定義來區分。使用
python scripts/build.py [開發板目錄名字]來編譯韌體。如果直接覆蓋原有配置,將來OTA升級時,您的自定義韌體可能會被原有開發板的標準韌體覆蓋,導致您的裝置無法正常工作。每個開發板有唯一的標識和對應的韌體升級通道,保持開發板標識的唯一性非常重要。
目錄結構
每個開發板的目錄結構通常包含以下檔案:
xxx_board.cc- 主要的板級初始化程式碼,實現了板子相關的初始化和功能config.h- 板級配置檔案,定義了硬體管腳對映和其他配置項config.json- 上報開發板型別及釋出配置,供 CMake 和scripts/build.py使用README.md- 開發板相關的說明文件
開發板目錄可以直接放在 main/boards/ 下,也可以按廠商分組放在 main/boards/<manufacturer>/<board>/ 中(見下文廠商子目錄)。
定製開發板步驟
1. 建立新的開發板目錄
首先在boards/目錄下建立一個新的目錄,命名方式應使用 [品牌名]-[開發板型別] 的形式,例如 m5stack-tab5:
mkdir main/boards/my-custom-board2. 建立配置檔案
config.h
在config.h中定義所有的硬體配置,包括:
- 音訊取樣率和I2S引腳配置
- 音訊編解碼晶片地址和I2C引腳配置
- 按鈕和LED引腳配置
- 顯示屏參數和引腳配置
參考示例(來自lichuang-c3-dev):
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
// 音訊配置
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10
#define AUDIO_I2S_GPIO_WS GPIO_NUM_12
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11
#define AUDIO_CODEC_PA_PIN GPIO_NUM_13
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_0
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
// 按鈕配置
#define BOOT_BUTTON_GPIO GPIO_NUM_9
// 顯示屏配置
#define DISPLAY_SPI_SCK_PIN GPIO_NUM_3
#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_5
#define DISPLAY_DC_PIN GPIO_NUM_6
#define DISPLAY_SPI_CS_PIN GPIO_NUM_4
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY true
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_2
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
#endif // _BOARD_CONFIG_H_
config.json
在config.json中定義相容性相關的上報型別和自動化編譯配置:
{
"type": "my-custom-board", // 韌體上報的開發板型別,釋出後應保持穩定
"target": "esp32s3", // 目標晶片型號: esp32, esp32s3, esp32c3, esp32c6, esp32p4等
"builds": [
{
"name": "my-custom-board", // 開發板名稱,用於生成韌體包
"sdkconfig_append": [
// 特別 Flash 大小配置
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
// 特別分割槽表配置
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
]
}
]
}配置項說明:
manufacturer: 廠商子目錄名稱;使用廠商目錄時必須填寫,並作為board.manufacturer上報。平鋪社群板可以省略,上報為空字串type: 韌體上報的開發板系列型別,釋出後應保持穩定target: 目標晶片型號,必須與硬體匹配name: release 構建上報的韌體變體名稱,通常與type一致sdkconfig_append: 額外的 sdkconfig 配置項陣列,會追加到預設配置中
type 和 name 只能包含小寫字母、數字、點(.)和連字元(-),
不允許使用下劃線、空格或大寫字母。
常用的 sdkconfig_append 配置:
// Flash 大小
"CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y" // 4MB Flash
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y" // 8MB Flash
// 分割槽表
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/4m.csv\"" // 4MB 分割槽表
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"" // 8MB 分割槽表
// 音訊處理
"CONFIG_USE_DEVICE_AEC=y" // 啟用裝置端 AEC對於適用的目標晶片,專案預設使用 16MB Flash 和
partitions/v2/16m.csv。如果板子配置與專案及目標晶片的有效預設值相同,
不要在 sdkconfig_append 中重複填寫;這裡應只保留板子確實需要的覆蓋項。
不要在板子的 config.json 中選擇語言或具體喚醒詞。這些屬於使用者構建選項,
應統一透過 menuconfig 或構建指令碼參數配置,方便 CLI、Agent 和線上編譯介面
共用同一套參數。
3. 編寫板級初始化程式碼
建立一個my_custom_board.cc檔案,實現開發板的所有初始化邏輯。
一個基本的開發板類定義包含以下幾個部分:
- 類定義:繼承自
WifiBoard或Ml307Board - 初始化函式:包括I2C、顯示屏、按鈕、IoT等元件的初始化
- 虛擬函式重寫:如
GetAudioCodec()、GetDisplay()、GetBacklight()等 - 註冊開發板:使用
DECLARE_BOARD宏註冊開發板
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "display/lcd_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <driver/spi_common.h>
#define TAG "MyCustomBoard"
class MyCustomBoard : public WifiBoard {
private:
i2c_master_bus_handle_t codec_i2c_bus_;
Button boot_button_;
LcdDisplay* display_;
// I2C初始化
void InitializeI2c() {
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_0,
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.intr_priority = 0,
.trans_queue_depth = 0,
.flags = {
.enable_internal_pullup = 1,
},
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_));
}
// SPI初始化(用於顯示屏)
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN;
buscfg.quadwp_io_num = GPIO_NUM_NC;
buscfg.quadhd_io_num = GPIO_NUM_NC;
buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
// 按鈕初始化
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting) {
EnterWifiConfigMode();
return;
}
app.ToggleChatState();
});
}
// 顯示屏初始化(以ST7789為例)
void InitializeDisplay() {
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN;
io_config.dc_gpio_num = DISPLAY_DC_PIN;
io_config.spi_mode = 2;
io_config.pclk_hz = 80 * 1000 * 1000;
io_config.trans_queue_depth = 10;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = GPIO_NUM_NC;
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
panel_config.bits_per_pixel = 16;
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, true);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
// 建立顯示屏物件
display_ = new SpiLcdDisplay(panel_io, panel,
DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y,
DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
}
// MCP Tools 初始化
void InitializeTools() {
// 參考 MCP 文件
}
public:
// 建構函式
MyCustomBoard() : boot_button_(BOOT_BUTTON_GPIO) {
InitializeI2c();
InitializeSpi();
InitializeDisplay();
InitializeButtons();
InitializeTools();
GetBacklight()->SetBrightness(100);
}
// 獲取音訊編解碼器
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec audio_codec(
codec_i2c_bus_,
I2C_NUM_0,
AUDIO_INPUT_SAMPLE_RATE,
AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_MCLK,
AUDIO_I2S_GPIO_BCLK,
AUDIO_I2S_GPIO_WS,
AUDIO_I2S_GPIO_DOUT,
AUDIO_I2S_GPIO_DIN,
AUDIO_CODEC_PA_PIN,
AUDIO_CODEC_ES8311_ADDR);
return &audio_codec;
}
// 獲取顯示屏
virtual Display* GetDisplay() override {
return display_;
}
// 獲取背光控制
virtual Backlight* GetBacklight() override {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
};
// 註冊開發板
DECLARE_BOARD(MyCustomBoard);4. 新增構建系統配置
在 Kconfig.projbuild 中新增開發板選項
開啟 main/Kconfig.projbuild 檔案,在 choice BOARD_TYPE 部分新增新的開發板配置項:
choice BOARD_TYPE
prompt "Board Type"
default BOARD_TYPE_BREAD_COMPACT_WIFI
help
Board type. 開發板型別
# ... 其他開發板選項 ...
config BOARD_TYPE_MY_CUSTOM_BOARD
bool "My Custom Board (我的自定義開發板)"
depends on IDF_TARGET_ESP32S3 # 根據你的目標晶片修改
endchoice注意事項:
BOARD_TYPE_MY_CUSTOM_BOARD是配置項名稱,需要全大寫,使用下劃線分隔depends on指定了目標晶片型別(如IDF_TARGET_ESP32S3、IDF_TARGET_ESP32C3等)- 描述文字可以使用中英文
在 CMakeLists.txt 中新增開發板配置
開啟 main/CMakeLists.txt 檔案,在開發板型別判斷部分新增新的配置:
# 在 elseif 鏈中新增你的開發板配置
elseif(CONFIG_BOARD_TYPE_MY_CUSTOM_BOARD)
set(BOARD_DIR "my-custom-board") # 相對於 main/boards 的完整路徑
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) # 根據螢幕大小選擇合適的字型
set(BUILTIN_ICON_FONT font_awesome_20_4)
set(DEFAULT_EMOJI_COLLECTION twemoji_64) # 可選,如果需要表情顯示
endif()字型和表情配置說明:
根據螢幕解析度選擇合適的字型大小:
- 小螢幕(128x64 OLED):
font_puhui_basic_14_1/font_awesome_14_1 - 中小螢幕(240x240):
font_puhui_basic_16_4/font_awesome_16_4 - 中等螢幕(240x320):
font_puhui_basic_20_4/font_awesome_20_4 - 大螢幕(480x320+):
font_puhui_basic_30_4/font_awesome_30_4
表情集合選項:
twemoji_32- 32x32 畫素表情(小螢幕)twemoji_64- 64x64 畫素表情(大螢幕)
5. 配置和編譯
方法一:使用 idf.py 手動配置
設定目標晶片(首次配置或更換晶片時):
# 對於 ESP32-S3 idf.py set-target esp32s3 # 對於 ESP32-C3 idf.py set-target esp32c3 # 對於 ESP32 idf.py set-target esp32清理舊配置:
idf.py fullclean進入配置選單:
idf.py menuconfig在選單中導航到:
Xiaozhi Assistant->Board Type,選擇你的自定義開發板。編譯和燒錄:
idf.py build idf.py flash monitor
方法二:使用 build.py 指令碼(推薦)
如果你的開發板目錄下有 config.json 檔案,可以使用此指令碼自動完成配置和編譯:
python scripts/build.py my-custom-board語言和喚醒詞屬於使用者構建參數:
python scripts/build.py my-custom-board \
--language en-US \
--wake-word wn9_jarvis_tts--language 接受 main/assets/locales/ 下已有的 locale;--wake-word
接受 ESP-SR 模型名、nihaoxiaozhi(自動選擇與目標晶片相容的模型)或
disabled。ESP32-C3/C5/C6 僅支援 WakeNet9s(wn9s_*)模型;
ESP32-S3/P4/S31 構建會自動使用 AFE 喚醒引擎。
可以用文字或 JSON 格式查詢可用值:
python scripts/build.py --list-languages
python scripts/build.py --list-languages --json
python scripts/build.py --list-wake-words
python scripts/build.py --list-wake-words --json喚醒詞列表從當前已解析的 ESP-SR 元件讀取。如果尚未生成
managed_components/,請先執行 idf.py reconfigure。
此指令碼會自動:
- 不傳參數時列印幫助;使用
--list-boards列出所有開發板型別和變體 - 如果開發板有多個變體,互動式提示選擇;非互動環境使用
--name <變體> - 讀取
config.json中的target;僅當已有 build 目錄的目標晶片不同時 執行清理,然後透過一次idf.py reconfigure同時配置目標晶片、開發板 名稱、defaults 和所選變體的sdkconfig_append。隨後的idf.py build會複用這份配置 - 將所選 build 的
name作為韌體上報的變體名稱 - 預設生成
build/merged-binary.bin,不建立 ZIP;指定--zip時會重新生成releases/v<版本>_<名稱>.zip
Windows:命令列過長
如果 Windows 編譯時因編譯器命令超過 32,767 字元上限而出現 Ninja
CreateProcess 錯誤,可啟用 CMake/Ninja 的 response file。在剛才編譯失敗的
同一個工程目錄、同一個 ESP-IDF 終端中執行:
idf.py -D CMAKE_NINJA_FORCE_RESPONSE_FILE=ON reconfigure
idf.py build這樣會透過 @檔名 傳遞編譯參數,而不是在命令列中展開。執行
idf.py fullclean 或清空 CMake 快取後,需要重新執行上述 reconfigure 命令。
將工程移到較短的路徑(例如 E:\xz)也可能緩解問題;Windows 的“啟用長路徑”
設定不會提高整條命令列的長度上限。參見 #2340 中的解決方法。
6. 建立README.md
在README.md中說明開發板的特性、硬體要求、編譯和燒錄步驟:
廠商子目錄
當同一廠商提供多個型號時,推薦把開發板按廠商分組,放在 main/boards/<manufacturer>/<board>/ 下,例如 main/boards/waveshare/esp32-p4-nano/ 或 main/boards/lceda-course-examples/eda-tv-pro/。
位於廠商子目錄中的開發板,需要在 config.json 中增加相同的值,例如 "manufacturer": "waveshare"。韌體會把它與 board.type、board.name 一起上報為 board.manufacturer;沒有該欄位的平鋪社群開發板會上報空字串。
BOARD_DIR 需要設定為相對於 main/boards/ 的完整路徑:
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_NANO)
set(BOARD_DIR "waveshare/esp32-p4-nano")
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
set(BUILTIN_ICON_FONT font_awesome_30_4)
set(DEFAULT_EMOJI_COLLECTION twemoji_64)構建系統會從 main/boards/${BOARD_DIR}/ 載入原始碼,並從該目錄的 config.json 讀取上報的開發板型別。如果 config.json 或其頂層 type 缺失,則使用把 BOARD_DIR 中的 / 替換為 - 後的完整路徑作為兜底型別。
經驗法則:
- 同一廠商有兩款及以上開發板,且共用驅動、資源或文件時,使用廠商目錄。
- 單一開發板或社群示例,使用平鋪目錄。
- 目錄名使用小寫加連字元(例如
waveshare、lceda-course-examples)。
常見開發板元件
main/boards/common/ 中提供了若干可複用元件,可以直接在你的開發板類中使用:
顯示屏驅動
支援的 LCD 型別包括:
- ST7789 (SPI)
- ILI9341 (SPI)
- SH8601 (QSPI)
- 等等。
音訊編解碼器
Es8311AudioCodec(最常用)Es8374AudioCodecEs8388AudioCodecEs8389AudioCodecBoxAudioCodec(ESP-Box 系列開發板使用的 ES7210 麥克風陣列 + 編解碼器組合)NoAudioCodec(不使用外部編解碼器,直接 I2S)DummyAudioCodec(沒有音訊的開發板使用的佔位實現)
電源管理
Axp2101電源管理晶片輔助類。Sy6970電池充電晶片輔助類。AdcBatteryMonitor:基於 ADC 的簡易電池電壓監測。PowerSaveTimer/SleepTimer:輕度睡眠排程輔助類。
網路
WifiBoard:僅 Wi-Fi 的基類。Ml307Board/Nt26Board:4G 模組基類。DualNetworkBoard:可在 Wi-Fi 與 4G 間切換的基類。RndisBoard:基於 USB 的 RNDIS 網路(ESP32-S3 / ESP32-P4)。- 面向 ESP32-S3 / ESP32-P4 的 ESP-Video 輔助類
EspVideo。
輸入輔助
Button:標準按鍵(單擊、長按、多擊)。Knob:旋轉編碼器封裝。PressToTalkMcpTool:透過 MCP 註冊自身的按住說話工具。SystemReset:開機時按住按鍵可安全執行恢復出廠設定的輔助類。
MCP 裝置控制
任何開發板都可以註冊自定義工具,例如揚聲器控制、螢幕亮度調節、電池電量讀取、燈光控制等。參見 MCP 物聯網控制用法。
開發板類繼承關係
Board:基礎板級類WifiBoard:Wi-Fi 連線的開發板Ml307Board/Nt26Board:使用 4G 模組的開發板DualNetworkBoard:支援 Wi-Fi 與 4G 網路切換的開發板RndisBoard:基於 USB RNDIS 的開發板
開發技巧
- 參考相似的開發板:如果您的新開發板與現有開發板有相似之處,可以參考現有實現
- 分步除錯:先實現基礎功能(如顯示),再新增更復雜的功能(如音訊)
- 管腳對映:確保在config.h中正確配置所有管腳對映
- 檢查硬體相容性:確認所有晶片和驅動程式的相容性
可能遇到的問題
- 顯示屏不正常:檢查SPI配置、映象設定和顏色反轉設定
- 音訊無輸出:檢查I2S配置、PA使能引腳和編解碼器地址
- 無法連線網路:檢查Wi-Fi憑據和網路配置
- 無法與伺服器通訊:檢查MQTT或WebSocket配置
參考資料
- ESP-IDF 文件: https://docs.espressif.com/projects/esp-idf/
- LVGL 文件: https://docs.lvgl.io/
- ESP-SR 文件: https://github.com/espressif/esp-sr
來源:78/xiaozhi-esp32 的 docs/custom-board_zh.md,廠商子目錄與元件清單參照同倉庫英文版 docs/custom-board.md。