概述
九章 MCP 验证板主控为 STM32F103CBTx(LQFP48,128KB Flash / 20KB RAM)。开发起点是官方仓库自带的 9Mod_MCPBorad 基础工程,其 MX 配置文件 9Mod_MCPBorad.ioc 已把基础外设配置好——你不需要从零创建工程!
本章只做三件事:① 从官方仓库拿到基础工程 → ② 用 STM32CubeMX 打开 .ioc、看懂关键配置(USART2 + RX DMA)→ ③ 学会修改配置并重新生成。后续章节将在这个基础工程上逐步增加功能,最终实现完整效果。
环境先行:先装软件,再动手
本教程的所有命令(git clone、编译、烧录)都在 PowerShell 中执行。如果你还没安装任何开发软件,请先完成 软件安装(Windows) 章节(约 30 分钟),再回来继续本章——否则第①步的 git clone 无处执行。
先看这里,避免走弯路
不要用 CubeMX 的 "New Project" 自己新建工程! 官方仓库的 example/9Mod_MCPBorad 就是九章板的基础工程,自带完整的 .ioc 与基础驱动代码(emMCP、components、Bsp 均已预置)。使用时复制一份为自己的工程(见第①步),不要在官方源码上直接改。自己从零新建的工程既缺外设配置,又没有 components/Bsp/emMCP 等模块,仅作为本章末尾的可选进阶练习。
官方仓库:https://github.com/Ai-Thinker-Open/emMCP | 官方工具下载:STM32CubeMX
⚠️ 必读:USART2 中断配置(所有 AI 控制案例的前提)
USART2 是 MCU 与 AI 模组的通信串口,emMCP 靠它收发 JSON 指令,接收采用 IDLE + DMA 中断——必须勾选 "USART2 global interrupt",MCU 才能在指令到达时立即收到"数据接收完毕"的通知并处理。
⚠️ 不勾的后果(官方基础工程容易漏,实测踩坑):MCU 收不到 IDLE 中断,指令会一直躺在 DMA 缓冲区,直到攒满 256 字节才被处理——AI 等 5 秒超时判定失败,播报"继电器打开失败"等错误结果,且指令"过一会"才执行。
配置步骤(CubeMX 操作):
- 打开
9Mod_MCPBorad.ioc - Pinout & Configuration → Connectivity → USART2
- 右侧 NVIC Settings 面板 → 勾选 "USART2 global interrupt"
- Ctrl+S 保存(选重新生成代码)
生成后 usart.c 会自动出现:
/* USART2 interrupt Init */
HAL_NVIC_SetPriority(USART2_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(USART2_IRQn);stm32f1xx_it.c 会自动生成 USART2_IRQHandler(调用 HAL_UART_IRQHandler(&huart2))。
验证:重新生成后编译烧录,说"打开继电器",继电器应立即动作、AI 播报"已打开"。
💡 手动补代码(不重开 CubeMX)也可以:在
usart.c的HAL_UART_MspInitUSART2 分支加两行 NVIC 使能,在stm32f1xx_it.c加USART2_IRQHandler。但下次 CubeMX 重新生成会被覆盖,推荐直接在 .ioc 里勾选。
先从官方仓库拿到基础工程,然后复制一份作为你自己的工程——不要在官方源码上直接改(方便以后拉取官方更新)。
# 1. 克隆官方仓库(若已克隆可跳过)
cd C:\Users\你的名字
git clone https://github.com/Ai-Thinker-Open/emMCP.git
# 2. 复制基础工程为自己的工程文件夹(名字用英文,如 DOCS_TEST1)
Copy-Item -Recurse emMCP\example\9Mod_MCPBorad DOCS_TEST1
cd DOCS_TEST1
# 3. 确认 .ioc 文件在(基础外设已全部配好)
dir 9Mod_MCPBorad.ioc
以上命令在 PowerShell 中执行(
Win键搜 PowerShell 打开)。工程文件夹名不要用中文(工具链对中文路径不友好),本教程以 DOCS_TEST1 为例,后续所有章节的操作都在C:\Users\你的名字\DOCS_TEST1中进行。
复制出来的工程已预置:emMCP 框架(port + uart-mcp + emMCP_config.h)、外设驱动组件库(components/:relay、SHT3x、ch224、ws2812、SSD1306、log)、板级支持包(Bsp/)以及基础任务代码。后续章节将在此基础上一一增加功能,最终实现完整效果。

-
打开 STM32CubeMX,点击 File → Open Project。
-
选择工程里的
9Mod_MCPBorad.ioc(路径C:\Users\你的名字\DOCS_TEST1\9Mod_MCPBorad.ioc)——工程就在 Windows 磁盘上,直接选即可,不需要任何映射。
-
打开后你会看到基础外设都已配置好:
- Pinout 引脚图:PA2/PA3(USART2)、PA9/PA10(USART1)、PA11(WS2812)等引脚已分配
- Clock Configuration:HSE 8MHz × PLL9 = 72MHz
- 左侧外设列表:USART1、USART2、SPI1、TIM1、DMA、FREERTOS 已启用
先不修改任何东西,熟悉一下界面布局即可。
USART2 是 STM32 与 AI 模组通信的通道,emMCP 靠它收发 JSON 指令。在 CubeMX 里找到它:
-
左侧 Connectivity → USART2:
- Mode:Asynchronous(异步收发,PA2/PA3)
- Baud Rate:115200(AI 模组固定波特率)
- 数据格式:8N1、无校验(默认)
-
切到 DMA Settings 标签页,可以看到已配置好的 USART2_RX:
参数 值 DMA Request USART2_RX Channel DMA1_Channel6 Direction Peripheral to Memory Mode Normal Priority Low 数据宽度 Byte(8 位) -
切到 NVIC Settings:
- DMA1 channel6 global interrupt 已勾选(优先级 5)
- ⚠️ USART2 global interrupt ——官方基础工程容易漏勾!务必确认它已勾选,没有就勾上(它驱动
HAL_UARTEx_RxEventCallback接收回调,详见第⑤步"配置 2")
本项目使用
HAL_UARTEx_ReceiveToIdle_DMA()—— DMA 收数据 + 总线空闲中断通知。中断与 DMA 缺一不可,这两项配置缺一个 AI 指令都收不到(USART2 中断缺失的典型症状:指令延迟执行 + AI 播报"失败",详见继电器案例)。
基础工程的其余外设也已配置完毕,对照下表认识它们(与官方 .ioc 逐项核对):
| 外设 | 配置 | 说明 |
|---|---|---|
| USART1 | Asynchronous,Baud Rate 1500000 | 调试串口(PA9/PA10),日志输出 |
| SPI1 | Full-Duplex Master,Prescaler 128 | OLED 屏幕(PA5/PA6/PA7) |
| TIM1 | PWM Generation Channel 4,Period = 90-1 | WS2812 灯带(PA11,复用开漏+高速),TIM1_CH4 → DMA1_Channel4(半字) |
| DMA | USART2_RX → DMA1_Channel6、TIM1_CH4 → DMA1_Channel4 | 两个 DMA 请求 |
| GPIO PA4 | Output,标签 OLED_CS1,初始 High | OLED 片选 |
| GPIO PA1 | Output,标签 OLED_DC,初始 High | OLED 数据/命令选择 |
| GPIO PB0 | Output,标签 CS2,初始 High | 字库芯片片选 |
| GPIO PB5 | Output | 继电器控制(高电平吸合) |
| GPIO PB6 / PB7 | Output | 软件 I²C:SDA / SCL(SHT3x、CH224 共用) |
| GPIO PB4 / PB8 | Input | 按键(OLED 上翻页 / 下翻页) |
| GPIO PA8 | Input | 雷达 Rd-03L_V2 人体检测 |
| FreeRTOS | Interface: CMSIS_V2;3 个任务 | defaultTask(24) / sht3x_read(25) / ws2812_mode(26),栈 256×4 字,堆 4096(第⑤步改为 6656) |
基础工程未启用 USART3(红外模块通道)与 SPI1_TX DMA——本教程后续案例未使用这些外设,如需启用,可在 CubeMX 中按本章"进阶练习"的同样方式新增。
时基说明:SYS → Timebase Source 为 TIM4(FreeRTOS 占用 SysTick,时基必须换定时器)。如果看到红字警告
SysTick is used for HAL timebase and FreeRTOS,说明时基被改回 SysTick 了,改回 TIM4 即可。
拿到 .ioc 后你可能想改配置(比如改波特率、加引脚)。以两个必改配置为例演示完整闭环——基础工程默认的堆大小不够、且 USART2 接收回调未使能,两个都要改:
配置 1:FreeRTOS 堆改为 6656
-
左侧 Middleware and Software Packs → FREERTOS:
- 参数 configTOTAL_HEAP_SIZE 由
4096改为6656(WS2812 60 灯缓冲需 2882 字节 + emMCP 工具注册;保持 4096 会导致灯带/工具异常)

- 参数 configTOTAL_HEAP_SIZE 由
配置 2:使能 USART2 接收回调(USART2 global interrupt) ⚠️ 必须勾!
-
左侧 Connectivity → USART2 → NVIC Settings:
- 勾选 “USART2 global interrupt”(该中断驱动
HAL_UARTEx_RxEventCallback回调:AI 模组指令到达时,MCU 靠 IDLE 中断立即获知并处理)
⚠️ 不勾的后果(官方基础工程容易漏,实测踩坑):MCU 收不到 IDLE 中断,指令会一直躺在 DMA 缓冲区,直到攒满 256 字节才被处理——AI 等 5 秒超时判定失败,播报"继电器打开失败"等错误结果,且指令"过一会"才执行。详见本章开头"USART2 中断配置"章节。
- 勾选 “USART2 global interrupt”(该中断驱动
-
改完点击右上角 GENERATE CODE,弹窗确认 Overwrite? → Yes。
-
CubeMX 重新生成代码:
USER CODE BEGIN/END之间的代码原样保留- 生成区的配置代码(usart.c、FreeRTOSConfig.h、stm32f1xx_it.c 等)按新配置重写——
USART2_IRQHandler与 NVIC 使能会自动生成

⚠️ 重要:每次改完 .ioc 重新生成后,不要在生成区手改代码(会被覆盖);需要改行为时写进
USER CODE区,或改上层驱动(components/、Bsp/)。
基础工程的结构(比纯 CubeMX 生成的多了 components、Bsp、emMCP 三块,后面章节会逐一讲解):
DOCS_TEST1/
├── Core/ ← 主程序(Inc 头文件 / Src 源文件)
│ └── Src/ ← main.c、usart.c、dma.c、freertos.c(★ 任务所在,后续章节在此加工具)
├── components/ ← ★ 外设驱动组件库(relay、SHT3x、ch224、ws2812、SSD1306、log、emMCP_config.h)
├── Bsp/ ← ★ 板级支持包(软 I²C、SPI、PWM+DMA、延时)
├── Drivers/ ← STM32 HAL 库 + CMSIS
├── Middlewares/ ← FreeRTOS
├── cmake/
│ ├── gcc-arm-none-eabi.cmake ← 交叉编译工具链定义
│ └── stm32cubemx/ ← CubeMX 生成的源码清单 CMakeLists.txt
├── CMakeLists.txt ← 顶层构建脚本
├── 9Mod_MCPBorad.ioc ← ★ MX 工程文件(本章操作对象)
├── CMakePresets.json ← CMake 预设(Debug/Release)
├── STM32F103XX_FLASH.ld ← 链接脚本
├── newlib_lock_glue.c ← newlib 适配(编译必需)
└── openocd.cfg ← OpenOCD 烧录配置
进阶练习:从零新建工程(可选,仅用于理解)
什么时候需要做这个?
平时开发不需要——主线直接使用官方基础工程的 .ioc。做这一节是为了搞懂". .ioc 里的每一项配置是怎么来的",以后自己画板子、换芯片时用得上。练习完的工程不要作为后续章节的开发基础。
- 新建工程:File → New Project → 搜索
STM32F103CBTx→ Start Project(选 Yes 初始化外设)。 - 时钟源:System Core → RCC → High Speed Clock (HSE) 改为 Crystal/Ceramic Resonator(板载 8MHz 晶振)。
- 调试口与时基:System Core → SYS → Debug 选 Serial Wire;Timebase Source 选 TIM4。
- 时钟树:Clock Configuration 页 → HSE 填 8、PLL Source 选 HSE、PLLMUL 选 x9 → SYSCLK = 72MHz。
- USART2:Connectivity → USART2 → Asynchronous → 波特率 115200。
- USART2 RX DMA:DMA Settings → Add → USART2_RX → DMA1_Channel6(外设→内存、Normal、Byte);NVIC 勾选 USART2 与 DMA1 channel6 中断。
- 其余外设:按第④步对照表逐项配置(SPI1、TIM1、USART1/3、GPIO、FreeRTOS)。
- 导出:Project → Settings → Toolchain 选 CMake、Min CMake version 3.22 → GENERATE CODE。
常见问题与踩坑提示
🔧 我自己新建的工程和复制的工程冲突吗?
原因:多个工程并存,不知道该用哪个
解决:只用复制出来的工程(C:\Users\你的名字\DOCS_TEST1,自带 .ioc + components + Bsp + emMCP)。官方源码和练习工程都不要继续写功能
🔧 克隆官方仓库很慢 / 失败
原因:网络波动
解决:重试;或 git clone --depth 1 https://github.com/Ai-Thinker-Open/emMCP.git(只拉最新版本)
🔧 CubeMX 无法下载 / 下载很慢
原因:ST 官网需注册账号,部分网络访问慢
解决:注册 ST 账号(免费)后登录再下载;或让同事代下安装包(如 SetupSTM32CubeMX-6.18.0.exe)
🔧 固件包 Install 按钮灰色 / 安装失败
原因:网络问题或固件包未匹配
解决:多试几次;确认选 F1 系列 V1.8.7;注意磁盘空间
🔧 时钟树页面 HSE 输入框是灰色的
原因:没在 RCC 里打开 HSE
解决:System Core → RCC → High Speed Clock 改为 Crystal/Ceramic Resonator
🔧 红字警告 SysTick is used for HAL timebase and FreeRTOS
原因:时基被设成 SysTick
解决:SYS → Timebase Source 改为 TIM4
🔧 生成后编译报找不到头文件
原因:工具链/工程设置问题
解决:确认 Toolchain 选 CMake、Min CMake version 3.22;按软件安装(Windows)装好工具链
🔧 重新生成后之前写的功能没了
原因:代码写在生成区(USER CODE 之外)
解决:⚠️ CubeMX 只保留 USER CODE BEGIN/END 之间的内容,其余全部重写。自定义代码一律写在 USER CODE 区内
🔧 改了 .ioc 重新生成后 RTOS 堆变小了
原因:.ioc 里的 configTOTAL_HEAP_SIZE 与预期不一致
解决:保持 FreeRTOS 参数里的堆大小 ≥ 6656(WS2812 60 灯缓冲 + emMCP 工具注册需要)
🔧 编译能过,但 %.1f 输出为空(如 {"temperature":})原因:newlib-nano 默认不含浮点格式化 解决:两个 cmake 工具链文件的链接选项加 -u _printf_float(详见下方"链接选项")
🔧 工具执行正常但 AI 播报失败、指令延迟原因:.ioc 里 USART2 的 NVIC 中断没勾 解决:USART2 → NVIC Settings 勾选 "USART2 global interrupt" 并重新生成(详见继电器案例)
🔧 灯带刷屏 Failed to start DMA transmission
原因:WS2812 缓冲用了标准 malloc(标准堆只有 ~2KB,60 灯需 2882B)
解决:bsp_pwm_dma.c 改用 pvPortMalloc(详见灯带案例内存说明)
🔧 工程路径含中文/空格导致工具链报错
原因:部分工具链对路径不友好
解决:工程目录建议全英文无空格
改配置的正确姿势
想改任何配置:CubeMX 打开 .ioc → 改 → GENERATE CODE,然后不要手动改生成区的代码(usart.c、tim.c、gpio.c 等)。要改行为时写在对应文件的 USER CODE 区,或直接改上层驱动/业务代码。
⚠️ 必读:链接选项 -u _printf_float(浮点 printf)
工程使用 newlib-nano(--specs=nano.specs),它默认不含浮点格式化代码——snprintf 里的 %.1f 会输出空字符串,导致温湿度/电压等回执变成非法 JSON({"temperature":}),AI 查询播报失败。
修改两个 CMake 工具链文件(都在 cmake/ 目录):
# cmake/gcc-arm-none-eabi.cmake(默认工具链)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --specs=nano.specs -u _printf_float")
# cmake/starm-clang.cmake(如使用 STARM_HYBRID)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --gcc-specs=nano.specs -u _printf_float")重新编译后 FLASH 增加约 4~5KB(61% → 66%),%.1f 恢复输出。
⚠️ 必读:20KB RAM 的两个堆(内存账)
STM32F103 只有 20KB RAM(本项目已用 92%),标准堆(malloc)与 FreeRTOS 堆(pvPortMalloc)是分开的:
| 堆 | 大小 | 用途 |
|---|---|---|
| 标准堆 | 约 2144 字节(RAM 尾部的剩余空间) | cJSON 消息解析(emMCP 工具调用全靠它) |
| FreeRTOS 堆 | 6656 字节(configTOTAL_HEAP_SIZE) | emMCP 工具注册 + WS2812 灯带缓冲(60 颗 = 2882 字节) |
⚠️ WS2812 缓冲必须用 pvPortMalloc 分配(bsp_pwm_dma.c),用标准 malloc 会失败(2882 > 2144),后果是灯带刷屏 Failed to start DMA transmission,且 cJSON 被挤占后温湿度/调色等工具间歇性失败。详见灯带案例的"WS2812 缓冲内存分配"章节。

