一、前置准备:FreeRTOS 源码获取与结构
1. 下载源码
官网:https://www.freertos.org/
GitHub:https://github.com/FreeRTOS/FreeRTOS/releases
FreeRTOS/
Source/ # 内核核心(必须用)
include/ # 通用头文件
7个.c文件 # tasks.c/queue.c等
portable/ # 移植关键文件
RVDS/ARM_CM3/ # M3内核接口(必须)
MemMang/heap_4.c # 内存管理(必须)
Demo/ # 找 FreeRTOSConfig.h 配置文件
移植只需要 3 部分:
- Source 下 7 个通用 .c 文件
- portable/RVDS/ARM_CM3
- portable/MemMang/heap_4.c
工程根目录新建文件夹:
FreeRTOS/ ├─ source/ ├─ portable/ └─ include/拷贝文件:
源码 Source/*.c → 工程 FreeRTOS/source/
源码 Source/include/ → 工程 FreeRTOS/include/
源码 Source/portable/RVDS/ARM_CM3 → 工程 FreeRTOS/portable/
源码 Source/portable/MemMang/heap_4.c → 工程 FreeRTOS/portable/MemMang/
拷贝配置文件:
从源码 Demo/CORTEX_STM32F103_Keil/ 复制 FreeRTOSConfig.h
HAL 项目:放到 Core/Inc
寄存器项目:放到 USER/Inc 或自定义头文件路径
修改 FreeRTOSConfig.h
添加 3 行(直接加在文件末尾即可)
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler
#define INCLUDE_xTaskGetSchedulerState 1
修改 stm32f1xx_it.c
#include "FreeRTOS.h"
#include "task.h"
- 注释掉系统默认的两个中断函数(非常重要):
// void SVC_Handler(void){} // void PendSV_Handler(void){}- 修改
SysTick_Handler:
extern void xPortSysTickHandler(void); void SysTick_Handler(void) { if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) { xPortSysTickHandler(); } }HAL 时钟源修改
CubeMX / 代码中:
- SysTick 给 FreeRTOS 使用
- HAL 库时钟源切换为:
TIM1或TIM2 - 中断优先级设置高一点(如 1),防止系统卡死
寄存器项目移植
MDK 添加文件 + 头文件路径
和 HAL 库完全一致
修改 FreeRTOSConfig.h
和 HAL 库一
#define xPortPendSVHandler PendSV_Handler #define vPortSVCHandler SVC_Handler #define INCLUDE_xTaskGetSchedulerState 13. main.c 实现 SysTick 中断
extern void xPortSysTickHandler(void); void SysTick_Handler(void) { if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) { xPortSysTickHandler(); } }寄存器项目无需注释 SVC/PendSV,因为 FreeRTOS 已经接管了这两个中断。
1.为什么要在 FreeRTOSConfig.h 里加这两句?
#define xPortPendSVHandler PendSV_Handler #define vPortSVCHandler SVC_Handler原因:
FreeRTOS 自己实现了任务切换必须用的两个中断函数:
xPortPendSVHandler
vPortSVCHandler
但是STM32 官方启动文件(startup_stm32f10x.s)里写死的中断名是:
PendSV_Handler
SVC_Handler
2. 为什么要注释掉 stm32f1xx_it.c 里的 SVC_Handler 和 PendSV_Handler?
//void SVC_Handler(void){} //void PendSV_Handler(void){}原因:
这两个函数,HAL 库默认给你生成了空实现!
如果不注释:
- 编译器会发现:
- FreeRTOS 写了一个
PendSV_Handler - 我自己的文件里也写了一个
PendSV_Handler
- FreeRTOS 写了一个
- 结果:重复定义报错 → 编译失败!
总结:
FreeRTOS 要用这两个中断,你就必须把 HAL 生成的空函数删掉 / 注释掉!
3. 为什么要修改 SysTick_Handler?
extern void xPortSysTickHandler(void); void SysTick_Handler(void) { if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) { xPortSysTickHandler(); } }原因:
SysTick 是 FreeRTOS 的系统心跳!
RTOS 要做:
- 任务延时
- 时间片轮转
- 超时等待
- 软件定时器
全都靠 SysTick 提供时基!
所以:
- 每次滴答定时器中断
- 我必须调用 FreeRTOS 提供的
xPortSysTickHandler() - 告诉 RTOS:时间又过去 1ms 啦!
4. 为什么 HAL 项目必须把时钟源从 SysTick 改成 TIM 定时器?
原因:
HAL 库和 FreeRTOS 抢同一个 SysTick!
- HAL 用它做
HAL_Delay() - FreeRTOS 用它做任务调度
两个同时用 →冲突 → 系统直接卡死 / 死机
解决方法:
让一个人用,另一个人换工具:
- FreeRTOS 继续用 SysTick(必须用)
- HAL 改用 TIM1、TIM2 等普通定时器做时基
这样就不打架了。
5. 为什么寄存器项目不用注释 SVC/PendSV?
原因:
寄存器项目里根本没写这两个函数!
- 寄存器工程 = 你自己从零写代码
- 没写
SVC_Handler、PendSV_Handler - 所以 FreeRTOS 直接接管,不会冲突
HAL 工程 = CubeMX 自动生成了空函数不注释就重复定义。
总结
- SVC、PendSV 是 FreeRTOS 任务切换的核心,必须让 FreeRTOS 接管。
- 改名宏定义 = 让单片机认识 FreeRTOS 的中断函数。
- 注释 HAL 空函数 = 防止重复定义。
- SysTick 给 FreeRTOS,HAL 换 TIM = 避免时钟冲突卡死。