FreeRTOS 移植到 STM32F407VETX 记录
基础工程:STM32CubeIDE 生成的 STM32F407VETX HAL 库工程
FreeRTOS 版本:V11.1.0(CM4F 移植层)
编译器:arm-none-eabi-gcc,
-mfpu=fpv4-sp-d16 -mfloat-abi=hard
一、编译报错:SysTick_Handler 重复定义
错误信息
multiple definition of `SysTick_Handler'
stm32f4xx_it.o: first defined here
port.o: defined here
根因
FreeRTOSConfig.h 第 127 行:
c
#define xPortSysTickHandler SysTick_Handler
该宏将 FreeRTOS port.c 中的 xPortSysTickHandler() 重命名为 SysTick_Handler。
而 STM32 HAL 模板在 stm32f4xx_it.c 中也定义了 void SysTick_Handler(void)。
两个 .o 文件链接时产生重复定义。
修复
方案 :注释掉 FreeRTOSConfig.h 中的宏,在 stm32f4xx_it.c 的 SysTick_Handler 中手动调用 FreeRTOS 和 HAL 两个 handler。
文件:<Core/Src/stm32f4xx_it.c>
c
// 修改前
void SysTick_Handler(void)
{
HAL_IncTick();
}
// 修改后
void SysTick_Handler(void)
{
HAL_IncTick();
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
xPortSysTickHandler(); // 调度器启动后才调用 FreeRTOS tick
}
}
文件:<FreeRTOS/FreeRTOSConfig.h>
c
// 修改前
#define xPortSysTickHandler SysTick_Handler
// 修改后
//#define xPortSysTickHandler SysTick_Handler
为什么保留 HAL_IncTick() :HAL 库的
HAL_Delay()、HAL_GetTick()都依赖uwTick计数器,该计数器由
HAL_IncTick()递增。如果不调用,HAL 定时功能失效。
为什么加调度器状态判断 :HAL_Init()在vTaskStartScheduler()之前就启动了 SysTick,在调度器就绪前不应调用 FreeRTOS 的 tick 处理函数。
为什么configUSE_TICK_HOOK = 0:FreeRTOS 的 tick hook(vApplicationTickHook)在
xTaskIncrementTick()内部被调用。如果 hook 也调用HAL_IncTick(),而 SysTick_Handler已经调过一次了,会导致 HAL tick 双倍计数 。因此禁用 tick hook,将
HAL_IncTick()放在SysTick_Handler 中统一管理,保证每个 SysTick 周期只递增一次 uwTick。
二、Phase 0:FPU 修复
背景
STM32F407 内部有硬件 FPU(Cortex-M4F)。FreeRTOS CM4F 移植层使用 lazy stacking 机制:
- 任务未使用 FPU 时,上下文切换只保存整数寄存器(~64 字节)
- 任务使用 FPU 后,才额外保存 FPU 寄存器 S0-S31 + FPSCR(额外 ~132 字节)
- 这需要 startup.s、编译器标志、运行时 CPACR 三处配置一致
问题
| 配置位置 | 移植前 | 是否启用 FPU |
|---|---|---|
| 编译器标志 | -mfpu=fpv4-sp-d16 -mfloat-abi=hard |
✅ 硬件 FPU |
| startup.s 第 29 行 | .fpu softvfp |
❌ 软件浮点 |
| main() 中 CPACR | 未显式设置 | ❌(但 SystemInit 已设置) |
编译器生成硬件 FPU 指令,汇编器却被告知用软件浮点 → 栈帧大小不匹配 → HardFault。
修复
Step 1:Core/Startup/startup_stm32f407vetx.s:29
asm
; 修改前
.fpu softvfp
; 修改后
.fpu fpv4-sp-d16 ; FPv4-SP 架构,16 个双精度寄存器(32 个单精度)
Step 2:Core/Src/main.c:111
c
int main(void)
{
/* 使能 FPU --- 必须在任何可能使用 FPU 的代码之前 */
SCB->CPACR |= ((3UL << 10U * 2U) | (3UL << 11U * 2U));
/* CP10=11 (full access), CP11=11 (full access) */
HAL_Init();
// ...
}
SystemInit()(在 startup.s 中Reset_Handler调用)已经做了同样的 CPACR 配置。在
main()中再做一次是双重保险,也是 FreeRTOS 移植的推荐做法。
三、Phase 1:中断向量映射
FreeRTOS 需要接管三个 Cortex-M 系统异常:SVC 、PendSV 、SysTick。
SVC(Supervisor Call)
| 文件 | 状态 |
|---|---|
stm32f4xx_it.c |
SVC_Handler 已注释掉 |
FreeRTOSConfig.h |
#define vPortSVCHandler SVC_Handler |
port.c |
定义 vPortSVCHandler() ------ 用于启动第一个任务 |
vTaskStartScheduler() → prvPortStartFirstTask() 执行 svc 0 指令 → CPU 进入 SVC 异常 → vPortSVCHandler 恢复第一个任务的上下文。
PendSV(Pendable Service Call)
| 文件 | 状态 |
|---|---|
stm32f4xx_it.c |
PendSV_Handler 已注释掉 |
FreeRTOSConfig.h |
#define xPortPendSVHandler PendSV_Handler |
port.c |
定义 xPortPendSVHandler() ------ 执行任务上下文切换 |
PendSV 被配置为最低优先级,确保上下文切换不会打断任何 ISR。
FreeRTOS 通过 portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT 触发 PendSV。
SysTick
由 stm32f4xx_it.c 保留处理(见第一章方案),手动分发到 HAL + FreeRTOS。
四、Phase 3:IWDG 喂狗修复
现象
| 两个任务延迟 | 表现 |
|---|---|
vTaskDelay(500) |
都正常闪烁 ✅ |
vTaskDelay(1800) |
正常闪烁 ✅ |
vTaskDelay(2000) |
两个 LED 常亮 ❌ |
根因
MX_IWDG_Init() 配置了独立看门狗 (<Core/Src/iwdg.c>):
c
hiwdg.Init.Prescaler = IWDG_PRESCALER_16; // 16 分频
hiwdg.Init.Reload = 4095; // 重装载值
超时计算:
超时 = Prescaler × (Reload + 1) / LSI频率
= 16 × 4096 / LSI_kHz
= 65536 / LSI_kHz
LSI 标称 32kHz,实际范围 17--47kHz。假设约 34kHz → 超时 ≈ 65536 / 34 ≈ 1928ms。
vTaskDelay(1800): ├──── 1800ms ────┤ 唤醒翻灯 ├──── 128ms ────┤ IWDG 复位
└ 任务在 IWDG 之前唤醒 ✓ ─┘
vTaskDelay(2000): ├──────── 2000ms ──────────┤ IWDG 先复位(1928ms)!
└ 任务永远等不到唤醒 ✗ ─┘
IWDG 从未被刷新,超时就把系统复位了。vTaskDelay(1800) 时任务刚好在复位前醒来翻转了 LED,
而 vTaskDelay(2000) 时 IWDG 比任务先到。
修复
c
void vApplicationIdleHook(void)
{
/* 系统空闲时喂狗。
*
* 关键设计原则:
* ✅ 在 idle hook 中喂狗 --- 所有任务阻塞时 idle 运行,说明系统健康
* ❌ 不要在 ISR 或周期性任务中喂狗 --- 会掩盖任务死循环/死锁 bug
*/
HAL_IWDG_Refresh(&hiwdg);
}
同时添加 #include "iwdg.h" 并确保 extern IWDG_HandleTypeDef hiwdg; 可访问
五、完整修改文件清单
| 文件 | 修改内容 |
|---|---|
| <Core/Src/main.c> | FPU 使能、添加 Task1/Task2、FreeRTOS Hook 函数、IWDG 喂狗、#include FreeRTOS 头文件 |
| <Core/Src/stm32f4xx_it.c> | SysTick_Handler 改为手动分发 HAL + FreeRTOS;注释 SVC_Handler、PendSV_Handler |
| <FreeRTOS/FreeRTOSConfig.h> | 注释 xPortSysTickHandler 宏;确认 configUSE_TICK_HOOK = 0 |
| <Core/Startup/startup_stm32f407vetx.s> | .fpu softvfp → .fpu fpv4-sp-d16 |
六、关键配置值
| 配置项 | 值 | 说明 |
|---|---|---|
configCPU_CLOCK_HZ |
160000000 | 系统时钟 160MHz |
configTICK_RATE_HZ |
1000 | 1ms 一次 tick |
configMINIMAL_STACK_SIZE |
130 words | idle/timer 任务栈 |
configTOTAL_HEAP_SIZE |
75KB | FreeRTOS 堆 |
configMAX_PRIORITIES |
5 | 优先级数量 |
configCHECK_FOR_STACK_OVERFLOW |
2 | 栈溢出检测 |
| 任务栈大小 | 256 words (1KB) | 每个任务的栈 |
八、防止再次踩坑
- FPU 配置三要素一致 :startup.s
.fpu= 编译器-mfpu= 运行时 CPACR - 中断向量只属于一方:SVC/PendSV 归 FreeRTOS,SysTick 协商处理
- IWDG 在 idle hook 喂狗,不要在 ISR 或周期性任务中喂