STM32 固件(CubeMX/HAL + PlatformIO、ST-Link)
同时加载 embedded-c-cpp 技能以获取编码标准、架构、单元测试和通用调试。本技能包含 STM32 特有的部分。
首先,识别项目
在编写代码之前先阅读仓库。下面这些事实会改变答案:
| 查找内容 | 在哪里找到 |
|---|---|
| 确切型号(例如 STM32G431KBT6)和系列(F0/F1/F4/G0/G4/L4/H7/U5...) | .ioc(Mcu.UserName)、platformio.ini 的 board =、启动文件名 |
| 项目类型 | .ioc + .project/.cproject → CubeIDE;.ioc + CMakeLists.txt/CMakePresets.json → CubeMX CMake;platformio.ini → PlatformIO |
| HAL vs LL vs 寄存器 | Drivers/STM32xx_HAL_Driver、#include "stm32xx_ll_*.h"、每个外设的 CubeMX“高级设置” |
| RTOS | Middlewares/Third_Party/FreeRTOS、cmsis_os.h |
| 时钟 | main.c 中的 SystemClock_Config()、.ioc 的 RCC 部分;Nucleo 板通常通过 ST-Link 提供 8 MHz 的 HSE(HSE 旁路) |
| 调试/烧录探针 | ST-Link(Nucleo/Discovery 板载,或独立的 ST-Link V2/V3) |
系列很重要:内核(M0+/M3/M4F/M7/M33)、FPU 有无、Flash 写入粒度(F1 为半字,G4/L4 为双字,H7 为闪存字)、DMA(G0/G4/L4+/H7 有 DMAMUX)、缓存(F7/H7)。当不确定某个寄存器或外设细节时,建议查阅参考手册(RM),而不是猜测。
防止工作丢失的 CubeMX 规则
CubeMX 每次都会重新生成 main.c、stm32xx_it.c、stm32xx_hal_msp.c 和初始化文件。只有位于 /* USER CODE BEGIN x */ 和 /* USER CODE END x */ 之间的文本才会保留。
- 将应用程序逻辑放在您自己的文件中(
App/app.c、App/app.h,或用于 C++ 的app.cpp),并从USER CODE BEGIN 2和while (1)的 USER CODE 块中调用app_init()/app_loop()。 - 更改引脚、时钟、DMA、中断和外设参数应在
.ioc中进行,然后重新生成代码——不要通过编辑MX_xxx_Init()来实现(它会被覆盖,而且.ioc会与代码不匹配)。 - 提交
.ioc文件。它是硬件配置的真理之源。 - 在“项目管理器”→ 代码生成器中启用“将每个外设的初始化生成为一对 .c/.h 文件”:使
main.c更小,差异更清晰。 - 详细信息和 C++ 集成:
references/cubemx-hal.md。
构建、烧录、调试
选择与项目匹配的路径;完整命令在 references/build-flash-debug.md 中。
| 项目 | 构建 | 烧录(ST-Link) | 调试 |
|---|---|---|---|
| CubeIDE | IDE,或命令行 stm32cubeide ... -build | IDE,或 STM32_Programmer_CLI -c port=SWD -w app.elf -v -rst | CubeIDE 调试器(ST-LINK GDB 服务器) |
| CubeMX CMake(VS Code) | cmake --preset Debug && cmake --build --preset Debug | STM32_Programmer_CLI 或 OpenOCD | VS Code STM32 扩展 / OpenOCD + GDB |
| PlatformIO | pio run -e <env> | pio run -e <env> -t upload(upload_protocol = stlink) | pio debug / VS Code(debug_tool = stlink) |
烧录会改变板子:在运行上传之前,请先确认用户连接的是哪块板子/探针,并且未经明确确认,切勿运行全片擦除、选项字节或读保护命令(RDP 级别 2 是永久性的)。
HAL 用法——最耗时的错误
- I2C 地址需要左移后传递:
HAL_I2C_Master_Transmit(&hi2c1, 0x76 << 1, ...)。 - HAL 回调在中断上下文中运行(
HAL_UART_RxCpltCallback、HAL_GPIO_EXTI_Callback、HAL_TIM_PeriodElapsedCallback):保持简短,不要使用HAL_Delay,不要进行阻塞传输。 HAL_Delay/HAL_GetTick需要 SysTick:在更高优先级的中断服务程序或禁用中断时调用它们会导致系统挂起。使用 FreeRTOS 时,在 CubeMX 中将 SYS → Timebase Source 设置为一个基本定时器(TIM6/TIM7)。- UART 接收:使用
HAL_UARTEx_ReceiveToIdle_DMA(变长帧)或中断模式配合环形缓冲区;在回调函数和HAL_UART_ErrorCallback中重新启动接收(溢出错误会静默停止接收)。 - F7/H7 上的 DMA 缓冲区(数据缓存):发送前需 clean,接收后需 invalidate,缓冲区要求 32 字节对齐且大小也是 32 字节的倍数;在 H7 上,DMA1/2 无法访问 DTCM RAM——应将缓冲区放在 D2/AXI SRAM 中。
- 检查每个 HAL 调用的返回值(
HAL_OK、HAL_ERROR、HAL_BUSY、HAL_TIMEOUT);出现HAL_BUSY通常意味着上一次传输未完成。 - 超时:在生产路径中永远不要使用
HAL_MAX_DELAY。 - 更多模式(UART DMA、ADC DMA、定时器/PWM、EXTI、低功耗、Flash 写入、看门狗、复位原因):
references/cubemx-hal.md。
当它崩溃或无法启动时
- HardFault / MemManage / BusFault / UsageFault →
references/faults-and-recovery.md(读取 CFSR/HFSR、堆栈中的 PC、用addr2line解码)。 - ST-Link 无法连接(“No target found”、“DEV_TARGET_HELD_UNDER_RESET”)→ 在复位状态下连接;检查
.ioc中 SWD 引脚是否被重新配置为 GPIO;检查 BOOT0 引脚状态;检查 RDP 级别(另见references/faults-and-recovery.md)。 - 在
SystemClock_Config处挂起 → HSE 不存在或旁路模式不匹配、PLL 设置错误、Flash 等待周期过低。 - 外设无响应 → 时钟已使能?引脚复用功能正确?
MX_xxx_Init是否确实被调用且顺序正确(DMA 初始化应在使用它的外设之前)?
参考文件
| 文件 | 何时阅读 |
|---|---|
references/cubemx-hal.md | 在 CubeMX 项目中编写/审查代码、C++ 集成、外设模式、低功耗、Flash/EEPROM 仿真、看门狗、复位原因 |
references/build-flash-debug.md | 工具链安装、从命令行构建、PlatformIO 的 STM32 设置、烧录、GDB/OpenOCD、printf/SWV 日志记录、大小分析 |
references/faults-and-recovery.md | HardFault 和其他故障、板子无法连接、选项字节和读保护、恢复看起来已变砖的板子 |