FreeRTOS 移植到 STM32F407VETX 记录

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.cSysTick_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 系统异常:SVCPendSVSysTick

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 比任务先到。

修复

文件:Core/Src/main.c:250-260

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_HandlerPendSV_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) 每个任务的栈

八、防止再次踩坑

  1. FPU 配置三要素一致 :startup.s .fpu = 编译器 -mfpu = 运行时 CPACR
  2. 中断向量只属于一方:SVC/PendSV 归 FreeRTOS,SysTick 协商处理
  3. IWDG 在 idle hook 喂狗,不要在 ISR 或周期性任务中喂
相关推荐
qq_411262421 小时前
硬件是ESP32-P4连接LAN8720A,正常初始化之后,设备DHCP失败
stm32·单片机·fpga开发
SUNNYSPY0012 小时前
BSS138-ASEMI中低压通用MOS管BSS138
单片机
国科安芯2 小时前
国科安芯推出商业航天级抗辐照半双工 RS485 收发器 ASC485S2Y
前端·单片机·嵌入式硬件·架构·安全性测试
嵌入式ZYXC4 小时前
第1篇:《面试题:画一个STM32最小系统电路,每个元件的作用》
stm32·单片机·嵌入式硬件·面试·职场和发展
振南的单片机世界5 小时前
printf重定向:一句fputc,串口打印任意变量
stm32·单片机·嵌入式硬件
eng八戒5 小时前
【RA-Eco-RA2L1开发板评测】基于 FSP 实现串口打印功能
单片机·嵌入式硬件
嵌入式ZYXC6 小时前
第2篇:《面试题:LDO和DC-DC的区别?分别用在什么场景?》
stm32·单片机·嵌入式硬件·面试·职场和发展
HAPPY酷7 小时前
单片机OLED进阶:打造赛博朋克风“碎片化消散”文字特效
单片机·嵌入式硬件·microsoft
czhaii7 小时前
GB2312简体中文编码表
单片机·算法