1. 开发环境搭建
九章 MCP 验证板使用 STM32F103CBT6 作为主控 MCU,通过 emMCP 库与 Ai-WV01-32S 模组进行 MCP 协议通信。
所需工具
| 工具 | 用途 | 下载 |
|---|---|---|
| VSCode | 代码编辑与编译 | 官网下载 |
| STM32CubeMX | STM32 外设配置与工程生成 | 官网下载 |
| ARM GCC 工具链 | 交叉编译 STM32 固件 | ARM 官方下载 |
| CMake | 构建系统(例程使用) | 官网下载 |
| ST-Link 调试器 | 程序烧录与调试 | - |
| BLDevCube | Ai-WV01-32S 固件烧录 | 点击下载 |
参考视频
1.1 安装 ARM GCC 工具链
ARM GCC(gcc-arm-none-eabi)是编译 STM32 固件的交叉编译器,支持从 Cortex-M0 到 Cortex-M7 全系列内核。
安装方式(任选其一):
| 方式 | 说明 | 命令 / 链接 |
|---|---|---|
| ARM 官方(推荐) | 下载压缩包解压后配置 PATH | 下载地址 |
| xPack(Windows) | 包管理器安装,自动配置环境 | xpm install --global @xpack-dev-tools/arm-none-eabi-gcc@latest |
| MSYS2(Windows) | 通过 pacman 安装 | pacman -S mingw-w64-x86_64-arm-none-eabi-gcc |
| apt(Ubuntu/Debian) | 通过系统包管理器安装 | sudo apt install gcc-arm-none-eabi |
验证安装:
arm-none-eabi-gcc --version预期输出示例:
arm-none-eabi-gcc (GNU Arm Embedded Toolchain 10.3-2021.10) 10.3.1 20210824 (release)
Copyright (C) 2020 Free Software Foundation, Inc.VSCode 插件:
在 VSCode 扩展市场中搜索并安装以下插件:
- Cortex-Debug — 支持 ST-Link 调试,提供变量监视、断点、寄存器查看等功能
- CMake — CMake 语法高亮与集成构建
- C/C++ — 代码补全、跳转、语法检查
1.2 STM32CubeMX 工程配置
九章验证板例程已提供完整的 .ioc 配置文件,无需从头创建工程。直接打开即可查看或调整外设配置。
操作步骤:
启动 STM32CubeMX
点击 File > Open Project,选择例程目录下的
.ioc文件:emMCP/example/9Mod_MCPBoard/9Mod_MCPBoard.ioc查看关键芯片配置(已预设,无需修改):
参数 值 芯片型号 STM32F103CBT6 HSE(外部晶振) 8 MHz SYSCLK(系统时钟) 72 MHz APB1 时钟 36 MHz APB2 时钟 72 MHz 各外设配置总览(例程已预设):
外设 引脚 / 接口 通信参数 用途 USART1 PA9(TX) / PA10(RX) 115200-8N1 调试日志输出 USART2 PA2(RX) / PA3(TX) 115200-8N1 AI 模组 MCP 通信 USART3 PB10(TX) / PB11(RX) 9600-8N1 红外模块控制 I2C1 PB6(SCL) / PB7(SDA) 400kHz OLED、SHT30、PD 诱骗(共享总线) GPIO - PA8 PA8 (输入) - 雷达模块状态检测 GPIO - PB4 PB4 (输入) - 用户按键 1 GPIO - PB8 PB8 (输入) - 用户按键 2 GPIO - PB5 PB5 (输出) - 继电器控制 GPIO - PA11 PA11 (输出) - WS2812 灯条 GPIO - PC13 PC13 (输出) - 板载 LED SWD PA13(SWDIO) / PA14(SWCLK) - 调试与烧录接口 如需调整外设参数(如修改波特率),在 Pinout & Configuration 视图中修改后,点击 Project > Generate Code 重新生成代码。注意保持"保留用户代码段"(Keep User Code)选项开启,避免覆盖已有的业务逻辑。
提示: 如果只是开发应用层逻辑(MCP 工具),无需修改 CubeMX 配置,直接跳转到 §1.3 编译即可。
1.3 CMake 构建系统
例程使用 CMake 作为构建系统(而非传统 Makefile)。CMake 会根据 CMakeLists.txt 自动生成编译脚本,调用 ARM GCC 完成交叉编译。
安装 CMake:
VSCode 用户: CMake 插件已内置 CMake 工具,无需额外安装
独立安装: 从 CMake 官网 下载安装包,或使用包管理器:
bash# Ubuntu / Debian sudo apt install cmake # macOS (Homebrew) brew install cmake # Windows (MSYS2) pacman -S mingw-w64-x86_64-cmake验证安装:
bashcmake --version
方式一:终端命令编译
# 进入例程目录
cd emMCP/example/9Mod_MCPBoard
# 创建构建目录
mkdir -p build && cd build
# 配置 CMake(指定 ARM GCC 工具链)
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/gcc-arm-none-eabi.cmake
# 编译
make -j$(nproc)方式二:VSCode CMake 插件一键编译
- 在 VSCode 中打开例程目录:File > Open Folder >
emMCP/example/9Mod_MCPBoard - 按
F1或Ctrl+Shift+P,输入CMake: Configure并回车 - 选择编译器为 ARM GCC (arm-none-eabi)
- 点击底部状态栏的 Build 按钮(或按
F7)
CMakeLists.txt 关键说明:
| 配置项 | 说明 |
|---|---|
project(9Mod_MCPBoard C ASM) | 工程名称,支持 C 和汇编源文件 |
CMAKE_TOOLCHAIN_FILE | 指定 ARM GCC 交叉编译工具链文件 |
target_compile_definitions | 编译宏定义(如 emMCP 移植宏) |
add_executable | 添加目标可执行文件,链接所有源文件 |
TARGET_LINK_DIRECTORIES | 链接脚本目录(.ld 文件) |
TARGET_LINK_OPTIONS | 链接选项(如 -T STM32F103CBTx_FLASH.ld) |
1.4 编译验证
按照 §1.3 完成编译后,进行以下验证:
检查编译输出:
[100%] Built target 9Mod_MCPBoard.elf确认生成文件:
ls -la build/应包含以下关键文件:
| 文件 | 说明 |
|---|---|
9Mod_MCPBoard.elf | ELF 格式可执行文件(含调试信息) |
9Mod_MCPBoard.hex | Intel HEX 格式(用于烧录) |
9Mod_MCPBoard.bin | 纯二进制格式(用于烧录) |
9Mod_MCPBoard.map | 链接映射文件(查看内存分布) |
常见错误排查:
| 错误 | 原因 | 解决方法 |
|---|---|---|
arm-none-eabi-gcc: command not found | ARM GCC 未安装或未加入 PATH | 检查 §1.1 安装步骤 |
CMake Error: Could not find CMAKE_TOOLCHAIN_FILE | 工具链文件路径错误 | 确认在例程根目录执行 cmake .. |
undefined reference to HAL_xxx | STM32 HAL 库未正确包含 | 确认已用 STM32CubeMX 重新生成代码 |
No such file or directory: STM32F103CBTx_FLASH.ld | 链接脚本路径错误 | 检查 cmake/ 目录下是否存在 .ld 文件 |
预期结果: clean build 应无错误(0 errors, 0 warnings 为最佳),并在
build/目录下生成.hex和.bin固件文件,可用于下一步烧录验证。
2. 获取源码
建议使用 Git 克隆 emMCP 仓库:
git clone https://github.com/Ai-Thinker-Open/emMCP.git目录结构
emMCP
├── example/ # 示例工程
│ ├── 9Mod_MCPBoard/ # 九章验证板专用例程 ⭐
│ └── STM32F40xRTOS_XiaoZhiAI/ # STM32F407 FreeRTOS 小智AI示例
├── port/ # 移植接口层
│ ├── uartPort.h # 移植接口头文件(含配置系统)
│ ├── uartPort.c # 串口发送/接收(双缓冲)
│ ├── emMCP_port_config_example.h # 配置示例(STM32 HAL + FreeRTOS)
│ ├── emMCP_port_config_template.h # 配置模板
│ └── README_PORT.md # 移植配置说明
└── uart-mcp/ # emMCP 核心库
├── cJSON/
│ ├── cJSON.h
│ └── cJSON.c
├── emMCP.h # emMCP 主头文件
├── emMCP.c # emMCP 主源文件
└── emMCPLOG.h # 日志头文件提示
九章验证板专用例程位于 example/9Mod_MCPBoard/,配置了所有板载外设的 MCP 工具示例,推荐直接使用该例程作为开发起点。
3. 硬件连接
AI 模组与 MCU 通信
Ai-WV01-32S 模组通过 UART 与 STM32 通信,引脚连接如下:
| STM32 引脚 | Ai-WV01-32S 引脚 | 功能 |
|---|---|---|
| PA2 (USART2_RX) | TX | MCP 协议数据接收 |
| PA3 (USART2_TX) | RX | MCP 协议数据发送 |
注意
通过短路帽连接,确保 USART2 的 TX/RX 与模组正确对接。波特率默认 115200。
ST-Link 调试连接
| ST-Link 引脚 | STM32 引脚 |
|---|---|
| SWDIO | PA13 |
| SWCLK | PA14 |
| GND | GND |
| 3.3V | VDD |
烧录前确认 BOOT0 用跳线帽拉低(接 GND)。
4. 移植 emMCP
详细步骤参考 移植到MCU。针对九章验证板的关键配置如下:
v1.0.1 配置系统变更
port/port.h 已移除。平台相关宏定义(延时、内存管理、串口发送)迁移到 uartPort.h,采用条件编译 + 用户配置的设计。同时新增双缓冲接收机制,提升串口数据稳定性。
4.1 配置移植宏
emMCP v1.0.1 提供三种方式配置平台相关宏:
方式 1:直接定义宏(简单项目) 在包含 uartPort.h 之前定义:
#define emMCP_printf log_printf // 打印函数
#define emMCP_malloc pvPortMalloc // 内存分配
#define emMCP_free vPortFree // 内存释放
#define emMCP_delay osDelay // 延时函数
#define emMCP_uart_send HAL_UART_Transmit // 串口发送(新!)
#include "uartPort.h"方式 2:创建配置文件(推荐) 在项目目录创建 emMCP_port_config.h,uartPort.h 会自动通过 __has_include 检测并包含。九章验证板可直接参考 port/emMCP_port_config_example.h。
方式 3:CMake 编译定义
target_compile_definitions(9Mod_MCPBoard PRIVATE
emMCP_printf=log_printf
emMCP_malloc=pvPortMalloc
emMCP_free=vPortFree
emMCP_delay=osDelay
emMCP_uart_send=HAL_UART_Transmit
)4.2 实现串口收发
uartPortSendData() 已内置 emMCP_uart_send(data, len) 宏调用。确保宏正确定义即可,不必修改函数体:
// 方式 A:通过宏配置(推荐)
#define emMCP_uart_send HAL_UART_Transmit
// 方式 B:直接写具体实现(可选)
int uartPortSendData(char *data, int len)
{
if (data == NULL || len <= 0) return -1;
return HAL_UART_Transmit(&huart2, (uint8_t *)data, len, 100);
}在 DMA 接收回调中调用 uartPortRecvData()——新版会自动使用双缓冲存储数据,避免在中断中动态分配内存:
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if (huart->Instance == USART2) {
HAL_UARTEx_ReceiveToIdle_DMA(huart, (uint8_t *)rxBuffer, sizeof(rxBuffer));
uartPortRecvData((char *)rxBuffer, Size); // 自动存入双缓冲
__HAL_DMA_ENABLE_IT(&hdma_usart2_rx, DMA_IT_TC);
}
}在主循环或任务中通过以下 API 获取数据(线程安全):
char *data = uartPortGetRxData(); // 获取收到的数据
if (data != NULL) {
// 处理接收到的 JSON/MCP 数据...
uartPortClearRxData(); // 标记已处理,允许中断写入新数据
}4.3 新增辅助 API
| API | 用途 |
|---|---|
uartPortGetRxData() | 从双缓冲获取数据(线程安全,NULL 表示无新数据) |
uartPortClearRxData() | 标记数据已处理,允许中断接收新数据 |
emMCP_UpdateUartRecv(bool isRecv) | 更新串口接收状态 |
emMCP_CheckUartSendStatus() | 查询串口发送是否完成 |
5. 初始化与主循环
static emMCP_t emMCP_dev;
static uint8_t uart_rx_buf[512]; // 接收缓冲区
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init(); // AI 模组通信串口
MX_USART1_UART_Init(); // 调试日志串口
// 初始化外设
OLED_Init(); // 0.96寸 OLED
WS2812_Init(); // WS2812 灯条
Relay_Init(); // 继电器
SHT30_Init(); // 温湿度传感器
// 初始化 emMCP
emMCP_Init(&emMCP_dev);
// 设置串口数据缓冲区
uartPortSetDataBuf((char *)uart_rx_buf);
while (1)
{
emMCP_TickHandle(10); // emMCP 状态机处理
userTaskHandler(); // 用户任务处理
}
}关键
emMCP_TickHandle() 必须在主循环中高频调用,以维持 MCP 协议通信的实时性。
6. 注册 MCP 工具
MCP 工具是 AI 控制硬件的核心。每个工具包含名称、描述、参数定义和回调函数。
6.1 工具结构体
/**
* @brief MCP 服务器工具结构体
*
*/
typedef struct emMCP_tool
{
char *name; // 工具名称(AI 调用时使用)
char *description; // 工具功能描述
void (*setRequestHandler)(void *); // 控制回调函数
void (*checkRequestHandler)(void *); // 查询回调函数
inputSchema_t inputSchema; // 输入参数描述
struct emMCP_tool *next; // 下一个工具
} emMCP_tool_t;其中 inputSchema_t 用于描述工具的属性和控制方法:
typedef struct
{
properties_t properties[MCP_SERVER_TOOL_PROPERTIES_NUM]; // 属性
methods_t methods[MCP_SERVER_TOOL_METHODS_NUM]; // 方法
} inputSchema_t;| 字段 | 说明 |
|---|---|
properties | 可查询的属性(AI 能获取这些属性的当前值) |
methods | 可调用的控制方法(AI 能通过这些方法控制设备) |
properties_t 描述单个属性:
typedef struct
{
char *name; // 属性名称(如 "enable")
char *description; // 属性描述
mcp_server_tool_type_t type; // 属性类型
} properties_t;methods_t 描述单个控制方法:
typedef struct
{
char *name; // 方法名称
char *description; // 方法描述
parameters_t parameters[MCP_SERVER_TOOL_METHODS_PARAMETERS_NUM]; // 方法参数
} methods_t;parameters_t 描述方法的输入参数:
typedef struct
{
char *name; // 参数名称
char *description; // 参数描述
mcp_server_tool_type_t type; // 参数类型
} parameters_t;
### 6.2 工具回调函数
工具的回调函数是工具的核心,当 AI 下发 MCP 指令时,会调用对应的回调函数。回调函数接收一个 `void *arg` 参数,需要将其转换为 `cJSON *` 指针来解析指令内容。
**控制回调函数**:当 AI 发送控制指令(如"打开继电器")时调用:
```c
static void relay_set_handler(void *arg)
{
cJSON *param = (cJSON *)arg; // 接收 cJSON 指针
cJSON *state = cJSON_GetObjectItem(param, "enable"); // 获取指令参数
if (state && cJSON_IsTrue(state)) {
HAL_GPIO_WritePin(RELAY_GPIO_Port, RELAY_Pin, GPIO_PIN_SET);
} else {
HAL_GPIO_WritePin(RELAY_GPIO_Port, RELAY_Pin, GPIO_PIN_RESET);
}
emMCP_ResponseValue(emMCP_CTRL_OK); // 返回控制成功
}查询回调函数:当 AI 发送查询指令(如"继电器是否打开")时调用:
static void relay_get_handler(void *arg)
{
// 构造查询结果
uint8_t state = HAL_GPIO_ReadPin(RELAY_GPIO_Port, RELAY_Pin);
cJSON *result = cJSON_CreateObject();
cJSON_AddBoolToObject(result, "enable", state == GPIO_PIN_SET);
emMCP_ResponseValue(result); // 返回查询结果(自动释放)
}| 要点 | 说明 |
|---|---|
arg | cJSON * 指针,包含 AI 下发的指令参数 |
emMCP_ResponseValue(emMCP_CTRL_OK) | 返回控制成功 |
emMCP_ResponseValue(emMCP_CTRL_ERROR) | 返回控制失败 |
emMCP_ResponseValue(cJSON*) | 返回查询结果(JSON 对象) |
6.3 完整注册示例:继电器控制
// 1. 创建工具变量
emMCP_tool_t relay_tool;
// 2. 控制回调函数
static void relay_set_handler(void *arg)
{
cJSON *param = (cJSON *)arg;
cJSON *state = cJSON_GetObjectItem(param, "enable");
if (state && cJSON_IsTrue(state)) {
HAL_GPIO_WritePin(RELAY_GPIO_Port, RELAY_Pin, GPIO_PIN_SET);
} else {
HAL_GPIO_WritePin(RELAY_GPIO_Port, RELAY_Pin, GPIO_PIN_RESET);
}
emMCP_ResponseValue(emMCP_CTRL_OK);
}
// 3. 查询回调函数
static void relay_get_handler(void *arg)
{
uint8_t state = HAL_GPIO_ReadPin(RELAY_GPIO_Port, RELAY_Pin);
cJSON *result = cJSON_CreateObject();
cJSON_AddBoolToObject(result, "enable", state == GPIO_PIN_SET);
emMCP_ResponseValue(result);
}
// 4. 在 main 中配置并注册
int main(void)
{
// ... 初始化代码 ...
emMCP_Init(&emMCP_dev);
// 配置工具
relay_tool.name = "继电器";
relay_tool.description = "控制继电器的开关";
relay_tool.inputSchema.properties[0].name = "enable";
relay_tool.inputSchema.properties[0].description = "是否打开继电器,true表示打开,false表示关闭,查询时为null";
relay_tool.inputSchema.properties[0].type = MCP_SERVER_TOOL_TYPE_BOOLEAN;
relay_tool.setRequestHandler = relay_set_handler;
relay_tool.checkRequestHandler = relay_get_handler;
// 添加工具到工具列表
emMCP_AddToolToToolList(&relay_tool);
// 注册所有工具到小安 AI(确保所有工具已添加完毕)
emMCP_RegistrationTools();
while (1)
{
emMCP_TickHandle(10);
}
}注册流程
- 创建
emMCP_tool_t变量并配置名称、描述、属性、回调 - 调用
emMCP_AddToolToToolList()将每个工具加入工具列表 - 所有工具添加完毕后,调用
emMCP_RegistrationTools()统一注册到小安 AI
7. 九章验证板外设映射速查
以下是各外设对应的 GPIO 和推荐的 MCP 工具名:
| 外设 | GPIO / 接口 | 通信方式 | 推荐 MCP 工具名 |
|---|---|---|---|
| AI 模组 | PA2(RX) / PA3(TX) | USART2 | - (emMCP 内部使用) |
| 继电器 | PB5 | GPIO 输出 | relay_control |
| WS2812 灯条 | PA11 | 单线串行 | led_control |
| SHT30 温湿度 | PB6(SCL) / PB7(SDA) | I²C | get_temperature, get_humidity |
| OLED 显示屏 | PB6(SCL) / PB7(SDA) | I²C | oled_display |
| 雷达模块 | PA8 | GPIO 输入 | get_radar_status |
| 红外控制 | PB10(TX) / PB11(RX) | USART3 | ir_send |
| 用户按键 | PB4, PB8 | GPIO 输入 | - (事件触发) |
| PD 诱骗 | PB6(SCL) / PB7(SDA) | I²C | pd_set_voltage |
| 调试串口 | PA9(TX) / PA10(RX) | USART1 | - (日志输出) |
I²C 总线共享
OLED、SHT30、PD 诱骗三者共用 PB6(SCL) / PB7(SDA) I²C 总线,通过不同设备地址区分(OLED: 0x3C, SHT30: 0x44, CH224K: 0x48)。
8. 完整示例:温湿度读取工具
// 工具变量
emMCP_tool_t sensor_tool;
// 控制回调(温湿度以查询为主,这里做演示)
static void sensor_set_handler(void *arg)
{
cJSON *param = (cJSON *)arg;
cJSON *type = cJSON_GetObjectItem(param, "type");
if (type != NULL) {
// 根据 type 参数执行对应操作...
emMCP_ResponseValue(emMCP_CTRL_OK);
} else {
emMCP_ResponseValue(emMCP_CTRL_ERROR);
}
}
// 查询回调
static void sensor_get_handler(void *arg)
{
float temp = 0, humi = 0;
SHT30_ReadData(&temp, &humi);
cJSON *result = cJSON_CreateObject();
cJSON_AddNumberToObject(result, "temperature", temp);
cJSON_AddNumberToObject(result, "humidity", humi);
cJSON_AddStringToObject(result, "unit_temp", "°C");
cJSON_AddStringToObject(result, "unit_humi", "%");
emMCP_ResponseValue(result);
}
// 在 main 中注册
int main(void)
{
// ... 初始化代码 ...
emMCP_Init(&emMCP_dev);
sensor_tool.name = "温湿度传感器";
sensor_tool.description = "获取环境温湿度数据";
sensor_tool.inputSchema.properties[0].name = "temperature";
sensor_tool.inputSchema.properties[0].description = "当前温度值";
sensor_tool.inputSchema.properties[0].type = MCP_SERVER_TOOL_TYPE_NUMBLE;
sensor_tool.inputSchema.properties[1].name = "humidity";
sensor_tool.inputSchema.properties[1].description = "当前湿度值";
sensor_tool.inputSchema.properties[1].type = MCP_SERVER_TOOL_TYPE_NUMBLE;
sensor_tool.setRequestHandler = sensor_set_handler;
sensor_tool.checkRequestHandler = sensor_get_handler;
emMCP_AddToolToToolList(&sensor_tool);
emMCP_RegistrationTools();
while (1)
{
emMCP_TickHandle(10);
}
}注册后,用户可通过小安语音说「查一下现在的温湿度」即可触发 AI 调用此工具,AI 会自动语音播报结果。
9. 事件回调处理
emMCP 提供了丰富的事件通知,重新定义 emMCP_EventCallback 即可监听:
void emMCP_EventCallback(emMCP_event_t event, mcp_server_tool_type_t type, void *param)
{
char *str = (char *)param;
switch (event) {
case emMCP_EVENT_AI_WAKE:
// 模组被唤醒,可以做 LED 闪烁等交互
OLED_ShowString("聆听中...");
break;
case emMCP_EVENT_AI_SLEEP:
OLED_ShowString("待机");
break;
case emMCP_EVENT_AI_MCP_CMD:
// 收到 MCP 命令,显示调用日志
printf("[MCP] %s\n", str);
break;
case emMCP_EVENT_AI_MCP_Text:
// AI 回复的字幕文本,可显示在 OLED 上
OLED_ShowString(str);
break;
case emMCP_EVENT_AI_WIFI_CONNECT:
OLED_ShowString("WiFi OK");
break;
case emMCP_EVENT_AI_WIFI_DISCONNECT:
OLED_ShowString("WiFi Lost");
break;
default:
break;
}
}完整事件列表请参考 emMCP 事件列表。
10. 调试方法
10.1 TTL 串口日志
九章验证板通过 CH340C 芯片引出 USART1(PA9/PA10)作为调试串口,波特率 115200。
使用串口工具(如 PuTTY、MobaXterm、或 Ai-Thinker 串口助手)连接 Type-C 接口即可查看日志。
// 重定向 printf 到 USART1
int _write(int fd, char *ptr, int len)
{
HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, 100);
return len;
}10.2 OLED 显示调试
在关键节点调用 OLED 显示函数,可视化程序状态:
OLED_ShowString(0, 0, "MCP Init OK"); // 初始化提示
OLED_ShowString(0, 2, "Tool: 5 reg"); // 已注册工具数
OLED_ShowString(0, 4, "WiFi: connected"); // 网络状态10.3 常见问题排查
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| emMCP 收不到数据 | 串口引脚/波特率不匹配 | 确认 PA2/PA3 短路帽连接正确,波特率 115200 |
| 工具注册后 AI 无法调用 | 工具名含特殊字符或未完成注册 | 检查工具名使用中文/字母,确认已调用 emMCP_AddToolToToolList() 并执行 emMCP_RegistrationTools() |
| MCP 命令超时 | 回调函数执行过久 | 避免在回调中使用阻塞延时,复杂操作用任务/队列异步处理 |
| Ai-WV01-32S 无响应 | 模组未配网或固件版本不对 | 确认已配网成功,使用 V3.4 固件 |
| OLED 不显示 | I²C 地址冲突 | 确认 OLED 地址 0x3C,与 SHT30(0x44) 不冲突 |
| 编译报错 undefined reference | 缺少源文件 | 确认 port/ 和 uart-mcp/ 下所有 .c 文件已加入编译 |

