本系列文章旨在从零开始使用 STM32 标准外设库手动移植 FreeRTOS并逐步深入理解其工作原理。第一篇我们不依赖任何代码生成工具完全手动搭建 Keil 工程移植 FreeRTOS 内核并运行第一个 LED 闪烁任务。一、为什么要手动移植通过 CubeMX 一键生成 FreeRTOS 工程固然高效但隐藏了许多关键细节FreeRTOS 源码中哪些文件是必需的任务栈和系统堆内存是如何分配的Cortex-M 中断优先级为何要严格分组亲手移植一次才能对 FreeRTOS 的启动流程、内核配置以及底层交互有透彻的理解日后排查问题也更有底气。二、准备工作2.1 硬件STM32F103C8T6 最小系统板板载 PC13 LED外部 8MHz 晶振ST-Link 调试器2.2 软件与源码Keil MDK-ARMV5 或更高版本标准外设库STM32F10x_StdPeriph_Lib_V3.5.0FreeRTOS 源码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.c和system_stm32f10x.h→CMSIS/内核文件core_cm3.c、core_cm3.h位于 CMSIS 核心目录 →CMSIS/FreeRTOS 源码从FreeRTOSv10.4.6\FreeRTOS\Source中选取以下必选文件tasks.c、queue.c、list.c→FreeRTOS/src/移植层portable\RVDS\ARM_CM3\port.c→FreeRTOS/src/内存管理portable\MemMang\heap_4.c→FreeRTOS/src/头文件整个include文件夹内容 →FreeRTOS/inc/并将portable\RVDS\ARM_CM3\portmacro.h也复制到FreeRTOS/inc/注本基础工程暂不使用软件定时器故timers.c可暂时不加。3.2 创建 Keil 工程启动 Keil新建工程选择芯片STM32F103C8。添加工程分组App、BSP、StdPeriph/src、FreeRTOS/src、CMSIS。将对应.c和.s文件加入各组注意启动文件.s放入 CMSIS 组。打开工程选项魔法棒图标进行关键设置C/C选项卡Define 框填入USE_STDPERIPH_DRIVER, STM32F10X_MDInclude Paths 添加所有头文件路径App、BSP、StdPeriph/inc、FreeRTOS/inc、CMSIS、UserDebug选项卡选择 ST-Link DebuggerUtilities选项卡Flash Download 添加STM32F10x Med-density Flash编程算法四、编写 FreeRTOSConfig.h在User目录下创建FreeRTOSConfig.h这是移植的核心配置文件内容如下可直接复制使用#ifndefFREERTOS_CONFIG_H#defineFREERTOS_CONFIG_H#includestm32f10x.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表示优先级 515 的中断可以安全调用 FreeRTOS 的 FromISR API优先级 04 的中断不受内核管理但绝不能在它们内部调用任何 RTOS 函数。最后三个映射宏至关重要它使 FreeRTOS 提供的xPortSysTickHandler等函数直接取代标准库中同名的中断服务函数。五、处理中断服务函数冲突标准外设库模板的stm32f10x_it.c中通常会预定义SysTick_Handler、SVC_Handler、PendSV_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#includestm32f10x.hvoidLED_Init(void);voidLED_Toggle(void);#endif// bsp_led.c#includebsp_led.hvoidLED_Init(void){GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);GPIO_InitStructure.GPIO_PinGPIO_Pin_13;GPIO_InitStructure.GPIO_ModeGPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_SpeedGPIO_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#includestm32f10x.h#includeFreeRTOS.h#includetask.h#includebsp_led.hTaskHandle_t LedTask_HandleNULL;/* 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);// 任务句柄可为 NULLvTaskStartScheduler();// 启动调度器此函数不会返回while(1);// 永远不会执行到这里}代码关键点xTaskCreate将vLedTask函数登记为一个任务并自动加入就绪列表。栈大小参数的单位是“字”4 字节128 表示 512 字节对于简单的点灯任务完全足够。vTaskStartScheduler()执行后内核会立即触发 PendSV 异常切换到当前就绪的最高优先级任务。由于LedTask优先级为 1高于空闲任务的 0因此它立即开始运行。vTaskDelay将任务自身挂起 500ms在此期间 CPU 运行空闲任务延时结束后任务重新就绪再次执行。这正是 RTOS 多任务并发的基本机制。八、编译、下载与验证点击编译Build确认输出窗口显示0 Error(s), 0 Warning(s)。通过 ST-Link 连接开发板选择Flash - Download下载程序。按下复位键观察 PC13 连接的 LED 是否以 1 秒的周期亮 0.5s、灭 0.5s闪烁。若闪烁正常说明 FreeRTOS 的时钟中断与任务调度均已工作。若 LED 不闪烁请检查板子是否有外部 8MHz 晶振若使用的是仅内部 RC 振荡器的特殊板卡则需修改system_stm32f10x.c的时钟初始化并将configCPU_CLOCK_HZ改为8000000UL。FreeRTOSConfig.h中中断优先级映射是否正确尤其是configKERNEL_INTERRUPT_PRIORITY和configMAX_SYSCALL_INTERRUPT_PRIORITY。链接是否有未解决的符号可将所有 Hook 相关宏暂时设为 0。九、总结通过本篇我们完成了手动组织工程目录并添加了标准外设库与 FreeRTOS 的必备源文件编写了正确的FreeRTOSConfig.h理解了系统时钟、节拍周期以及中断优先级分组的意义用标准库驱动 LED创建了第一个 FreeRTOS 任务并成功运行了调度器。FreeRTOS 的本质是一套实时任务调度框架它让多个任务看起来在同时运行。下一篇我们将创建多个不同优先级的任务通过实验现象直观感受抢占式调度并进一步分析任务状态切换的过程。下一篇任务管理 —— 多任务并行、优先级抢占与时间片轮转。
FreeRTOS 手动移植教程(一):STM32F103 标准库工程搭建与第一个任务
发布时间:2026/6/4 3:08:36
本系列文章旨在从零开始使用 STM32 标准外设库手动移植 FreeRTOS并逐步深入理解其工作原理。第一篇我们不依赖任何代码生成工具完全手动搭建 Keil 工程移植 FreeRTOS 内核并运行第一个 LED 闪烁任务。一、为什么要手动移植通过 CubeMX 一键生成 FreeRTOS 工程固然高效但隐藏了许多关键细节FreeRTOS 源码中哪些文件是必需的任务栈和系统堆内存是如何分配的Cortex-M 中断优先级为何要严格分组亲手移植一次才能对 FreeRTOS 的启动流程、内核配置以及底层交互有透彻的理解日后排查问题也更有底气。二、准备工作2.1 硬件STM32F103C8T6 最小系统板板载 PC13 LED外部 8MHz 晶振ST-Link 调试器2.2 软件与源码Keil MDK-ARMV5 或更高版本标准外设库STM32F10x_StdPeriph_Lib_V3.5.0FreeRTOS 源码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.c和system_stm32f10x.h→CMSIS/内核文件core_cm3.c、core_cm3.h位于 CMSIS 核心目录 →CMSIS/FreeRTOS 源码从FreeRTOSv10.4.6\FreeRTOS\Source中选取以下必选文件tasks.c、queue.c、list.c→FreeRTOS/src/移植层portable\RVDS\ARM_CM3\port.c→FreeRTOS/src/内存管理portable\MemMang\heap_4.c→FreeRTOS/src/头文件整个include文件夹内容 →FreeRTOS/inc/并将portable\RVDS\ARM_CM3\portmacro.h也复制到FreeRTOS/inc/注本基础工程暂不使用软件定时器故timers.c可暂时不加。3.2 创建 Keil 工程启动 Keil新建工程选择芯片STM32F103C8。添加工程分组App、BSP、StdPeriph/src、FreeRTOS/src、CMSIS。将对应.c和.s文件加入各组注意启动文件.s放入 CMSIS 组。打开工程选项魔法棒图标进行关键设置C/C选项卡Define 框填入USE_STDPERIPH_DRIVER, STM32F10X_MDInclude Paths 添加所有头文件路径App、BSP、StdPeriph/inc、FreeRTOS/inc、CMSIS、UserDebug选项卡选择 ST-Link DebuggerUtilities选项卡Flash Download 添加STM32F10x Med-density Flash编程算法四、编写 FreeRTOSConfig.h在User目录下创建FreeRTOSConfig.h这是移植的核心配置文件内容如下可直接复制使用#ifndefFREERTOS_CONFIG_H#defineFREERTOS_CONFIG_H#includestm32f10x.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表示优先级 515 的中断可以安全调用 FreeRTOS 的 FromISR API优先级 04 的中断不受内核管理但绝不能在它们内部调用任何 RTOS 函数。最后三个映射宏至关重要它使 FreeRTOS 提供的xPortSysTickHandler等函数直接取代标准库中同名的中断服务函数。五、处理中断服务函数冲突标准外设库模板的stm32f10x_it.c中通常会预定义SysTick_Handler、SVC_Handler、PendSV_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#includestm32f10x.hvoidLED_Init(void);voidLED_Toggle(void);#endif// bsp_led.c#includebsp_led.hvoidLED_Init(void){GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);GPIO_InitStructure.GPIO_PinGPIO_Pin_13;GPIO_InitStructure.GPIO_ModeGPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_SpeedGPIO_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#includestm32f10x.h#includeFreeRTOS.h#includetask.h#includebsp_led.hTaskHandle_t LedTask_HandleNULL;/* 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);// 任务句柄可为 NULLvTaskStartScheduler();// 启动调度器此函数不会返回while(1);// 永远不会执行到这里}代码关键点xTaskCreate将vLedTask函数登记为一个任务并自动加入就绪列表。栈大小参数的单位是“字”4 字节128 表示 512 字节对于简单的点灯任务完全足够。vTaskStartScheduler()执行后内核会立即触发 PendSV 异常切换到当前就绪的最高优先级任务。由于LedTask优先级为 1高于空闲任务的 0因此它立即开始运行。vTaskDelay将任务自身挂起 500ms在此期间 CPU 运行空闲任务延时结束后任务重新就绪再次执行。这正是 RTOS 多任务并发的基本机制。八、编译、下载与验证点击编译Build确认输出窗口显示0 Error(s), 0 Warning(s)。通过 ST-Link 连接开发板选择Flash - Download下载程序。按下复位键观察 PC13 连接的 LED 是否以 1 秒的周期亮 0.5s、灭 0.5s闪烁。若闪烁正常说明 FreeRTOS 的时钟中断与任务调度均已工作。若 LED 不闪烁请检查板子是否有外部 8MHz 晶振若使用的是仅内部 RC 振荡器的特殊板卡则需修改system_stm32f10x.c的时钟初始化并将configCPU_CLOCK_HZ改为8000000UL。FreeRTOSConfig.h中中断优先级映射是否正确尤其是configKERNEL_INTERRUPT_PRIORITY和configMAX_SYSCALL_INTERRUPT_PRIORITY。链接是否有未解决的符号可将所有 Hook 相关宏暂时设为 0。九、总结通过本篇我们完成了手动组织工程目录并添加了标准外设库与 FreeRTOS 的必备源文件编写了正确的FreeRTOSConfig.h理解了系统时钟、节拍周期以及中断优先级分组的意义用标准库驱动 LED创建了第一个 FreeRTOS 任务并成功运行了调度器。FreeRTOS 的本质是一套实时任务调度框架它让多个任务看起来在同时运行。下一篇我们将创建多个不同优先级的任务通过实验现象直观感受抢占式调度并进一步分析任务状态切换的过程。下一篇任务管理 —— 多任务并行、优先级抢占与时间片轮转。