
FreeRTOS 移植到 STM32F407VETX 记录基础工程STM32CubeIDE 生成的 STM32F407VETX HAL 库工程FreeRTOS 版本V11.1.0CM4F 移植层编译器arm-none-eabi-gcc-mfpufpv4-sp-d16 -mfloat-abihard一、编译报错SysTick_Handler 重复定义错误信息multiple definition of SysTick_Handler stm32f4xx_it.o: first defined here port.o: defined here根因FreeRTOSConfig.h第 127 行#definexPortSysTickHandlerSysTick_Handler该宏将 FreeRTOSport.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// 修改前voidSysTick_Handler(void){HAL_IncTick();}// 修改后voidSysTick_Handler(void){HAL_IncTick();if(xTaskGetSchedulerState()!taskSCHEDULER_NOT_STARTED){xPortSysTickHandler();// 调度器启动后才调用 FreeRTOS tick}}文件FreeRTOS/FreeRTOSConfig.h// 修改前#definexPortSysTickHandlerSysTick_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 0FreeRTOS 的 tick hookvApplicationTickHook在xTaskIncrementTick()内部被调用。如果 hook 也调用HAL_IncTick()而 SysTick_Handler已经调过一次了会导致 HAL tick双倍计数。因此禁用 tick hook将HAL_IncTick()放在SysTick_Handler 中统一管理保证每个 SysTick 周期只递增一次 uwTick。二、Phase 0FPU 修复背景STM32F407 内部有硬件 FPUCortex-M4F。FreeRTOS CM4F 移植层使用lazy stacking机制任务未使用 FPU 时上下文切换只保存整数寄存器~64 字节任务使用 FPU 后才额外保存 FPU 寄存器 S0-S31 FPSCR额外 ~132 字节这需要 startup.s、编译器标志、运行时 CPACR 三处配置一致问题配置位置移植前是否启用 FPU编译器标志-mfpufpv4-sp-d16 -mfloat-abihard✅ 硬件 FPUstartup.s 第 29 行.fpu softvfp❌ 软件浮点main() 中 CPACR未显式设置❌但 SystemInit 已设置编译器生成硬件 FPU 指令汇编器却被告知用软件浮点 → 栈帧大小不匹配 → HardFault。修复Step 1Core/Startup/startup_stm32f407vetx.s:29; 修改前 .fpu softvfp ; 修改后 .fpu fpv4-sp-d16 ; FPv4-SP 架构16 个双精度寄存器32 个单精度Step 2Core/Src/main.c:111intmain(void){/* 使能 FPU — 必须在任何可能使用 FPU 的代码之前 */SCB-CPACR|((3UL10U*2U)|(3UL11U*2U));/* CP1011 (full access), CP1111 (full access) */HAL_Init();// ...}SystemInit()在 startup.s 中Reset_Handler调用已经做了同样的 CPACR 配置。在main()中再做一次是双重保险也是 FreeRTOS 移植的推荐做法。三、Phase 1中断向量映射FreeRTOS 需要接管三个 Cortex-M 系统异常SVC、PendSV、SysTick。SVCSupervisor Call文件状态stm32f4xx_it.cSVC_Handler已注释掉FreeRTOSConfig.h#define vPortSVCHandler SVC_Handlerport.c定义vPortSVCHandler()—— 用于启动第一个任务vTaskStartScheduler()→prvPortStartFirstTask()执行svc 0指令 → CPU 进入 SVC 异常 →vPortSVCHandler恢复第一个任务的上下文。PendSVPendable Service Call文件状态stm32f4xx_it.cPendSV_Handler已注释掉FreeRTOSConfig.h#define xPortPendSVHandler PendSV_Handlerport.c定义xPortPendSVHandler()—— 执行任务上下文切换PendSV 被配置为最低优先级确保上下文切换不会打断任何 ISR。FreeRTOS 通过portNVIC_INT_CTRL_REG portNVIC_PENDSVSET_BIT触发 PendSV。SysTick由stm32f4xx_it.c保留处理见第一章方案手动分发到 HAL FreeRTOS。四、Phase 3IWDG 喂狗修复现象两个任务延迟表现vTaskDelay(500)都正常闪烁 ✅vTaskDelay(1800)正常闪烁 ✅vTaskDelay(2000)两个 LED常亮❌根因MX_IWDG_Init()配置了独立看门狗 (Core/Src/iwdg.c)hiwdg.Init.PrescalerIWDG_PRESCALER_16;// 16 分频hiwdg.Init.Reload4095;// 重装载值超时计算超时 Prescaler × (Reload 1) / LSI频率 16 × 4096 / LSI_kHz 65536 / LSI_kHzLSI 标称 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 比任务先到。修复文件Core/Src/main.c:250-260voidvApplicationIdleHook(void){/* 系统空闲时喂狗。 * * 关键设计原则 * ✅ 在 idle hook 中喂狗 — 所有任务阻塞时 idle 运行说明系统健康 * ❌ 不要在 ISR 或周期性任务中喂狗 — 会掩盖任务死循环/死锁 bug */HAL_IWDG_Refresh(hiwdg);}同时添加#include iwdg.h并确保extern IWDG_HandleTypeDef hiwdg;可访问五、完整修改文件清单文件修改内容Core/Src/main.cFPU 使能、添加Task1/Task2、FreeRTOS Hook 函数、IWDG 喂狗、#includeFreeRTOS 头文件Core/Src/stm32f4xx_it.cSysTick_Handler改为手动分发 HAL FreeRTOS注释SVC_Handler、PendSV_HandlerFreeRTOS/FreeRTOSConfig.h注释xPortSysTickHandler宏确认configUSE_TICK_HOOK 0Core/Startup/startup_stm32f407vetx.s.fpu softvfp→.fpu fpv4-sp-d16六、关键配置值配置项值说明configCPU_CLOCK_HZ160000000系统时钟 160MHzconfigTICK_RATE_HZ10001ms 一次 tickconfigMINIMAL_STACK_SIZE130 wordsidle/timer 任务栈configTOTAL_HEAP_SIZE75KBFreeRTOS 堆configMAX_PRIORITIES5优先级数量configCHECK_FOR_STACK_OVERFLOW2栈溢出检测任务栈大小256 words (1KB)每个任务的栈八、防止再次踩坑FPU 配置三要素一致startup.s.fpu 编译器-mfpu 运行时 CPACR中断向量只属于一方SVC/PendSV 归 FreeRTOSSysTick 协商处理IWDG 在 idle hook 喂狗不要在 ISR 或周期性任务中喂