FreeRTOS 手动移植教程(一):STM32F103 标准库工程搭建与第一个任务
2026/6/24 10:13:53 网站建设 项目流程

本系列文章旨在从零开始,使用 STM32 标准外设库,手动移植 FreeRTOS,并逐步深入理解其工作原理。
第一篇,我们不依赖任何代码生成工具,完全手动搭建 Keil 工程,移植 FreeRTOS 内核,并运行第一个 LED 闪烁任务。


一、为什么要手动移植?

通过 CubeMX 一键生成 FreeRTOS 工程固然高效,但隐藏了许多关键细节:

  • FreeRTOS 源码中哪些文件是必需的?
  • 任务栈和系统堆内存是如何分配的?
  • Cortex-M 中断优先级为何要严格分组?

亲手移植一次,才能对 FreeRTOS 的启动流程、内核配置以及底层交互有透彻的理解,日后排查问题也更有底气。


二、准备工作

2.1 硬件

  • STM32F103C8T6 最小系统板(板载 PC13 LED,外部 8MHz 晶振)
  • ST-Link 调试器

2.2 软件与源码

  • Keil MDK-ARM(V5 或更高版本)
  • 标准外设库:STM32F10x_StdPeriph_Lib_V3.5.0
  • FreeRTOS 源码:FreeRTOSv10.4.6(可从 FreeRTOS 官网获取)

2.3 工程目录结构

Project/ ├── App/ # 应用层代码 │ └── main.c ├── BSP/ # 板级支持包(LED 驱动等) │ ├── bsp_led.c │ └── bsp_led.h ├── FreeRTOS/ # FreeRTOS 源码 │ ├── inc/ # 内核头文件 │ └── src/ # 内核源文件(含移植层) ├── StdPeriph/ # 标准外设库 │ ├── inc/ # 外设头文件 │ └── src/ # 外设源文件 ├── CMSIS/ # 系统启动与内核相关文件 │ ├── startup_stm32f10x_md.s │ ├── system_stm32f10x.c / .h │ └── core_cm3.c / .h └── User/ # 中断服务函数与 FreeRTOS 配置 ├── stm32f10x_it.c / .h └── FreeRTOSConfig.h

三、Keil 工程搭建与文件添加

3.1 复制必需文件

根据上述目录,将下载的库和源码按如下规则复制:

标准外设库
STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver中,将src下所有.c文件放入StdPeriph/src/inc下所有.h文件放入StdPeriph/inc/

CMSIS 文件

  • 启动文件:startup_stm32f10x_md.s(中容量设备) →CMSIS/
  • 系统文件:system_stm32f10x.csystem_stm32f10x.hCMSIS/
  • 内核文件:core_cm3.ccore_cm3.h(位于 CMSIS 核心目录) →CMSIS/

FreeRTOS 源码
FreeRTOSv10.4.6\FreeRTOS\Source中选取以下必选文件

  • tasks.cqueue.clist.cFreeRTOS/src/
  • 移植层:portable\RVDS\ARM_CM3\port.cFreeRTOS/src/
  • 内存管理:portable\MemMang\heap_4.cFreeRTOS/src/
  • 头文件:整个include文件夹内容 →FreeRTOS/inc/
  • 并将portable\RVDS\ARM_CM3\portmacro.h也复制到FreeRTOS/inc/

注:本基础工程暂不使用软件定时器,故timers.c可暂时不加。

3.2 创建 Keil 工程

  1. 启动 Keil,新建工程,选择芯片STM32F103C8
  2. 添加工程分组:AppBSPStdPeriph/srcFreeRTOS/srcCMSIS
  3. 将对应.c.s文件加入各组(注意启动文件.s放入 CMSIS 组)。
  4. 打开工程选项(魔法棒图标),进行关键设置:
    • C/C++选项卡:
      • Define 框填入:USE_STDPERIPH_DRIVER, STM32F10X_MD
      • Include Paths 添加所有头文件路径(App、BSP、StdPeriph/inc、FreeRTOS/inc、CMSIS、User)
    • Debug选项卡:选择 ST-Link Debugger
    • Utilities选项卡:Flash Download 添加STM32F10x Med-density Flash编程算法

四、编写 FreeRTOSConfig.h

User目录下创建FreeRTOSConfig.h,这是移植的核心配置文件,内容如下(可直接复制使用):

#ifndefFREERTOS_CONFIG_H#defineFREERTOS_CONFIG_H#include"stm32f10x.h"#defineconfigUSE_PREEMPTION1#defineconfigUSE_IDLE_HOOK0#defineconfigUSE_TICK_HOOK0#defineconfigCPU_CLOCK_HZ(72000000UL)#defineconfigTICK_RATE_HZ(1000)#defineconfigMAX_PRIORITIES(32)#defineconfigMINIMAL_STACK_SIZE(128)#defineconfigTOTAL_HEAP_SIZE((size_t)(10*1024))#defineconfigMAX_TASK_NAME_LEN(16)#defineconfigUSE_TRACE_FACILITY1#defineconfigUSE_16_BIT_TICKS0#defineconfigIDLE_SHOULD_YIELD1#defineconfigUSE_MUTEXES1#defineconfigQUEUE_REGISTRY_SIZE8#defineconfigCHECK_FOR_STACK_OVERFLOW0/* 初次运行建议关闭 */#defineconfigUSE_RECURSIVE_MUTEXES0#defineconfigUSE_MALLOC_FAILED_HOOK0#defineconfigUSE_APPLICATION_TASK_TAG0#defineconfigUSE_COUNTING_SEMAPHORES1#defineconfigGENERATE_RUN_TIME_STATS0/* 暂不启用软件定时器 */#defineconfigUSE_TIMERS0/* Cortex-M3 中断优先级配置 */#ifdef__NVIC_PRIO_BITS#defineconfigPRIO_BITS__NVIC_PRIO_BITS#else#defineconfigPRIO_BITS4#endif#defineconfigLIBRARY_LOWEST_INTERRUPT_PRIORITY0xf#defineconfigLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY5#defineconfigKERNEL_INTERRUPT_PRIORITY(configLIBRARY_LOWEST_INTERRUPT_PRIORITY<<(8-configPRIO_BITS))#defineconfigMAX_SYSCALL_INTERRUPT_PRIORITY(configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY<<(8-configPRIO_BITS))/* 断言:条件为假时停止系统 */#defineconfigASSERT(x)if((x)==0){taskDISABLE_INTERRUPTS();for(;;);}/* 将 FreeRTOS 内部中断函数映射到标准启动文件中的向量名 */#definevPortSVCHandlerSVC_Handler#definexPortPendSVHandlerPendSV_Handler#definexPortSysTickHandlerSysTick_Handler#endif/* FREERTOS_CONFIG_H */

配置要点说明:

  • configCPU_CLOCK_HZ必须与实际系统时钟一致。STM32F103 使用外部 8MHz 晶振并 PLL 倍频后为72MHz,因此填72000000UL
  • configTICK_RATE_HZ设置为 1000,即系统节拍周期为 1ms,这是vTaskDelay等延时函数的时钟基准。
  • 中断优先级:configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY设为 5,表示优先级 5~15 的中断可以安全调用 FreeRTOS 的 FromISR API;优先级 0~4 的中断不受内核管理,但绝不能在它们内部调用任何 RTOS 函数。
  • 最后三个映射宏至关重要,它使 FreeRTOS 提供的xPortSysTickHandler等函数直接取代标准库中同名的中断服务函数。

五、处理中断服务函数冲突

标准外设库模板的stm32f10x_it.c中通常会预定义SysTick_HandlerSVC_HandlerPendSV_Handler。FreeRTOS 的port.c已经实现了这三个函数,重复定义会造成链接错误。

打开User/stm32f10x_it.c删除或注释掉以下三个空函数(如果存在):

// void SVC_Handler(void) {}// void PendSV_Handler(void) {}// void SysTick_Handler(void){}

六、实现板级支持包:LED 驱动

BSP/目录下创建 LED 驱动代码,使用标准库控制 PC13。

// bsp_led.h#ifndefBSP_LED_H#defineBSP_LED_H#include"stm32f10x.h"voidLED_Init(void);voidLED_Toggle(void);#endif
// bsp_led.c#include"bsp_led.h"voidLED_Init(void){GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOC,&GPIO_InitStructure);GPIOC->ODR|=GPIO_Pin_13;// 初始熄灭}voidLED_Toggle(void){GPIOC->ODR^=GPIO_Pin_13;}

七、编写应用层:创建任务并启动调度器

main.c中实现一个 LED 闪烁任务,这是 RTOS 的“Hello World”。

// main.c#include"stm32f10x.h"#include"FreeRTOS.h"#include"task.h"#include"bsp_led.h"TaskHandle_t LedTask_Handle=NULL;/* LED 闪烁任务函数 */voidvLedTask(void*pvParameters){while(1){LED_Toggle();vTaskDelay(pdMS_TO_TICKS(500));// 阻塞 500 毫秒}}intmain(void){LED_Init();// 初始化硬件xTaskCreate(vLedTask,// 任务函数入口"LedTask",// 任务名称128,// 任务栈大小(单位:字,即 512 字节)NULL,// 任务参数1,// 优先级(0 为最低)&LedTask_Handle);// 任务句柄(可为 NULL)vTaskStartScheduler();// 启动调度器,此函数不会返回while(1);// 永远不会执行到这里}

代码关键点:

  • xTaskCreatevLedTask函数登记为一个任务,并自动加入就绪列表。栈大小参数的单位是“字”(4 字节),128 表示 512 字节,对于简单的点灯任务完全足够。
  • vTaskStartScheduler()执行后,内核会立即触发 PendSV 异常,切换到当前就绪的最高优先级任务。由于LedTask优先级为 1(高于空闲任务的 0),因此它立即开始运行。
  • vTaskDelay将任务自身挂起 500ms,在此期间 CPU 运行空闲任务;延时结束后任务重新就绪,再次执行。这正是 RTOS 多任务并发的基本机制。

八、编译、下载与验证

  1. 点击编译(Build),确认输出窗口显示0 Error(s), 0 Warning(s)
  2. 通过 ST-Link 连接开发板,选择Flash -> Download下载程序。
  3. 按下复位键,观察 PC13 连接的 LED 是否以 1 秒的周期(亮 0.5s、灭 0.5s)闪烁。若闪烁正常,说明 FreeRTOS 的时钟中断与任务调度均已工作。

若 LED 不闪烁,请检查:

  • 板子是否有外部 8MHz 晶振?若使用的是仅内部 RC 振荡器的特殊板卡,则需修改system_stm32f10x.c的时钟初始化,并将configCPU_CLOCK_HZ改为8000000UL
  • FreeRTOSConfig.h中中断优先级映射是否正确,尤其是configKERNEL_INTERRUPT_PRIORITYconfigMAX_SYSCALL_INTERRUPT_PRIORITY
  • 链接是否有未解决的符号?可将所有 Hook 相关宏暂时设为 0。

九、总结

通过本篇,我们完成了:

  1. 手动组织工程目录,并添加了标准外设库与 FreeRTOS 的必备源文件;
  2. 编写了正确的FreeRTOSConfig.h,理解了系统时钟、节拍周期以及中断优先级分组的意义;
  3. 用标准库驱动 LED,创建了第一个 FreeRTOS 任务,并成功运行了调度器。

FreeRTOS 的本质是一套实时任务调度框架,它让多个任务看起来在同时运行。下一篇,我们将创建多个不同优先级的任务,通过实验现象直观感受抢占式调度,并进一步分析任务状态切换的过程。


下一篇:任务管理 —— 多任务并行、优先级抢占与时间片轮转。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询