当前位置: 首页 > news >正文

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 行:

#definexPortSysTickHandlerSysTick_Handler

该宏将 FreeRTOSport.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

// 修改前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 = 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

; 修改前 .fpu softvfp ; 修改后 .fpu fpv4-sp-d16 ; FPv4-SP 架构,16 个双精度寄存器(32 个单精度)

Step 2:Core/Src/main.c:111

intmain(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.cSVC_Handler已注释掉
FreeRTOSConfig.h#define vPortSVCHandler SVC_Handler
port.c定义vPortSVCHandler()—— 用于启动第一个任务

vTaskStartScheduler()prvPortStartFirstTask()执行svc 0指令 → CPU 进入 SVC 异常 →vPortSVCHandler恢复第一个任务的上下文。

PendSV(Pendable Service Call)

文件状态
stm32f4xx_it.cPendSV_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):

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

voidvApplicationIdleHook(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_HandlerPendSV_Handler
FreeRTOS/FreeRTOSConfig.h注释xPortSysTickHandler宏;确认configUSE_TICK_HOOK = 0
Core/Startup/startup_stm32f407vetx.s.fpu softvfp.fpu fpv4-sp-d16

六、关键配置值

配置项说明
configCPU_CLOCK_HZ160000000系统时钟 160MHz
configTICK_RATE_HZ10001ms 一次 tick
configMINIMAL_STACK_SIZE130 wordsidle/timer 任务栈
configTOTAL_HEAP_SIZE75KBFreeRTOS 堆
configMAX_PRIORITIES5优先级数量
configCHECK_FOR_STACK_OVERFLOW2栈溢出检测
任务栈大小256 words (1KB)每个任务的栈

八、防止再次踩坑

  1. FPU 配置三要素一致:startup.s.fpu= 编译器-mfpu= 运行时 CPACR
  2. 中断向量只属于一方:SVC/PendSV 归 FreeRTOS,SysTick 协商处理
  3. IWDG 在 idle hook 喂狗,不要在 ISR 或周期性任务中喂
http://www.zskr.cn/news/1477391.html

相关文章:

  • ZCU106开发板实战:用PetaLinux 2019.2为Vitis AI编译系统镜像,我踩过的那些网络和版本坑
  • VS Code字体配置踩坑记:Operator Mono安装后连字不生效?一份详细的排查与修复指南
  • 告别千篇一律!用Operator Mono和Fira Code给你的VS Code编辑器换个“程序员专属”字体
  • AI 代码助手:从 Copilot 到 Code Review 的工程化实践
  • PyQt5写的本地音乐播放器,带界面资源、完整源码和详细使用说明
  • 手把手教你排查RTL8211F-CG网络不通:从晶振到RGMII时序的硬件调试实战
  • 2026年多协议API网关深度横评:架构演进、生产落地与Claude API中转选型实践
  • 别再用F-Droid了!2024年Termux最新安装与配置保姆级教程(含国内镜像源设置)
  • 告别死记硬背:用Anki记忆库+ChatGPT插件,把‘Two Heroes’这类课文词汇量刷爆的完整攻略
  • 避坑指南:OpenMV与STM32串口通信数据乱码、丢包的5个常见原因及解决方法
  • 别再只当故事看!用‘按钮,按钮’教你搭建一个简易的Python心理实验模拟器
  • 告别点灯!用STM8和TM1628驱动4位数码管制作一个简易计数器(附工程源码)
  • CSDN AI数字营销开通倒计时机制首度揭秘(内部文档节选),新账号必须完成的3项冷启动动作
  • 避开这些坑:Ninapro DB2数据处理与论文用图制作的5个常见误区
  • Delphi 12.3专用EMS数据导入控件源码:支持CSV/DBF/XLS/XML/DOCX等格式解析与字段映射
  • 2026年近期如何选择天津专业的厨房地垫优质厂家? - 2026年企业资讯
  • 2026多协议API网关深度横评:架构演进、生产落地与Claude API中转选型实践
  • 避坑指南:Vivado里把Xilinx下载器速度调到最高,为什么我的JTAG链路还是不稳定?
  • 成都荣晟祥发市政:四川管网非开挖修复技术与服务全解析 - 优质品牌商家
  • AI技术人必看的内容分发决策树(平台选择黄金公式已验证:CSDN重私域沉淀、掘金重即时互动、知乎重SEO长尾)
  • 项目实战:为什么我的小数分频PLL加了预分频器?从IBS杂散说起
  • ARM Cortex-M4上Zephyr RTOS的GPIO驱动调用空指针?一次由reset引发的UsageFault深度调试实录
  • 从零到一:Cobalt Strike钓鱼攻击的实战演练与防御策略
  • 从‘简单计算器’到‘鲁棒程序’:聊聊C++初学者最易忽略的输入验证与错误处理
  • 2026年国内头部洗浴设计机构口碑推荐,洗浴设计/浴场设计,洗浴设计机构选哪家 - 品牌推荐师
  • 手把手教你用QDUTT 2.0.2给QCM6490做DDR眼图测试:从环境配置到结果分析
  • 【分享】迷你钢琴 【纯净无广告】:界面干净无干扰,沉浸式演奏
  • ARM Cortex-M4上Zephyr RTOS的GPIO驱动调用崩溃:一次由空指针引发的HardFault深度调试
  • 2026年更新:探寻安徽优秀的局放检测热门公司及其联系之道 - 2026年企业资讯
  • 避坑指南:S7-1200 Modbus RTU通信中MB_MASTER报错8200、80C8的排查与修复