尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
STM32F103移植FreeRTOS通用模板实践与原理详解
简介面向正点原子精英板用户的STM32F103 FreeRTOS移植通用模板专为希望快速上手实时操作系统开发的嵌入式工程师与学习者设计覆盖从工程搭建到任务运行的全流程。工程已完成FreeRTOS与STM32标准外设库的整合任务创建、调度器启动、中断同步、内存管理等关键环节均有可直接运行的样板代码使用者只需替换或新增业务任务就能在真实硬件上验证RTOS行为。压缩包约3.7MB共196个文件以C源码.c与头文件.h为主辅以Keil工程配置.uvprojx、编译链接产物.axf/.map/.hex及readme说明文档便于直接打开、编译与烧录验证。已有2838人学习这一模板。资源价值在于省去繁琐的移植配置工作同时通过实例示范信号量、消息队列、任务优先级等FreeRTOS核心机制既是快速起步的工程基座也是深入理解嵌入式实时系统设计的实用参考。 拿到这块板子的时候我第一反应是想直接点个灯就收工但想了想既然要吃透FreeRTOS不如直接动手做一次完整的移植顺便沉淀一份能复用的通用模板。这个项目标题说得已经很明白在STM32F103上移植FreeRTOS并且最终的目标是形成一份“拿到新板子就能直接套用”的工程基础。我做这件事的出发点很简单市面上关于FreeRTOS移植的教程多如牛毛但很多都停留在“照着步骤点鼠标”的层面一旦脱离教程提供的工程换一块板子、换一个编译器版本立刻就抓瞎。所以我这次不打算只做一次“能跑就行”的移植而是把整个过程的原理、配置文件的每一项取舍、启动文件与中断处理的联动关系全部梳理清楚最终沉淀一份可以直接拷贝复用的通用模板。这篇博文就是这次移植的完整记录内容包含源码选型、工程改造、关键配置、验证实验和问题排查适合正在学习RTOS、准备做裸机转系统开发或者想把FreeRTOS快速落到自己项目里的朋友参考。1. 移植前的准备与工程体检1.1 为什么需要一份通用模板很多刚接触RTOS的同学会问直接按照官方demo改不就行了问题在于官方demo往往针对特定开发板板载外设、晶振频率、启动文件都和你的实际工程有差异。如果你手头是一块国产F103最小系统板核心板是一样的但外部晶振可能不是8MHz或者你的工程用的是标准外设库而不是HAL库这些差异都会让“照着demo抄”这件事变得异常痛苦。我这次的目标是做一个基于标准外设库StdPeriph_Lib V3.5的通用模板为什么选标准库而不是HAL库因为F103的标准库生态非常成熟网上大量现成代码都是基于标准库的很多做产品维护的工程师手头也还留着标准库的老工程。另一方面通过手动移植而不是CubeMX自动生成能让你真正理解RTOS的底层机制——系统节拍怎么产生、PendSV怎么触发、任务切换靠什么完成。搞清楚这些以后再去看HAL库或CubeMX生成的代码就是降维打击。1.2 源码版本与文件清单我选择的FreeRTOS内核版本是V10.4.6这个版本在F103上表现很稳定网上资料也多遇到问题容易搜到答案。如果是从官网或者GitHub下载源码你会在根目录看到几个文件夹但真正需要的核心文件其实很少list.c、queue.c、tasks.c、timers.c内核核心源码前三个是必备的timers.c在使能软件定时器时才需要。event_groups.c、stream_buffer.c用到事件标志组和数据流缓冲区时才需要模板工程可以留着不参与编译以后扩展再添加。portable/RVDS/ARM_CM3/这个目录下是Cortex-M3内核的移植层对应Keil编译器RVDS包含port.c、portmacro.h和portASM.s。portable/MemMang/内存管理方案有heap_1.c到heap_5.c五个文件我选了heap_4.c。很多人移植失败就是因为漏了portable目录下的文件或者内核源码和移植层版本不匹配。FreeRTOS的移植层是跟内核版本强相关的你不能拿V9的port.c塞进V10的内核里这点务必注意。1.3 认识我们手头的那份“标准库工程”在动手移植之前我先把裸机工程做了一次完整的“体检”。一个可以正常点灯的标准库工程通常包含这些部分启动文件startup_stm32f10x_md.s这个文件定义了三件事——栈大小、中断向量表、复位后启动代码。系统初始化system_stm32f10x.c它会调用SystemInit()设置时钟到72MHz。标准外设库stm32f10x_rcc.c、stm32f10x_gpio.c等外设驱动文件。主函数main.c中已经有Delay、GPIO_Config等裸机代码。这个工程的时钟配置我确认过是72MHz。做FreeRTOS移植时有一个很重要的前提系统时钟和SysTick的时钟源必须清楚。STM32F103的SysTick挂在Cortex-M3内核上它的时钟源可以从HCLK72MHz或HCLK/89MHz选择。FreeRTOS在port.c中会用configCPU_CLOCK_HZ和configTICK_RATE_HZ来计算SysTick的重装载值如果这两个配置跟实际的系统时钟对不上系统节拍就会不准最典型的症状就是任务切换异常快或异常慢串口打印频率和实际时间对不上。2. 移植步骤拆解与关键配置2.1 内核文件拷贝与工程组织我先把整个工程的文件结构做了一个整理Project/ ├── User/ // main.c, stm32f10x_it.c, FreeRTOSConfig.h ├── Core/ // 启动文件、system_stm32f10x.c ├── FWLib/ // 标准外设库源码 ├── FreeRTOS/ │ ├── include/ // 内核头文件 │ ├── src/ // 内核源码 │ └── port/ // portable/RVDS/ARM_CM3下的文件 MemMang/heap_4.c └── Output/ // Keil编译输出新建一个FreeRTOS文件夹把内核头文件和源码放进去再把portable/RVDS/ARM_CM3下的三个文件port.c、portmacro.h、portASM.s和MemMang/heap_4.c也放进去。之所以把移植层文件单独拎出来是为了让你心里清楚内核源码是通用的但移植层是和CPU架构、编译器强相关的这个隔离在以后换芯片时特别有用。在Keil中需要添加这些源文件到工程同时配置头文件包含路径。这里我踩过一个坑头文件路径如果漏加了FreeRTOS/include编译器报错会提示找不到tasks.h之类但如果你用的是V10版本的内核它内部还会有message.h、mpu_wrappers.h等头文件这些都在include目录里所以直接包含整个目录最省事。2.2 FreeRTOSConfig.h 的逐项配置FreeRTOSConfig.h是整个移植过程中最核心的配置文件它不像普通的头文件只是声明函数而是通过宏定义来裁剪内核功能、适配硬件参数。我给出的是F103通用模板的核心配置#define configUSE_PREEMPTION 1 #define configUSE_IDLE_HOOK 0 #define configUSE_TICK_HOOK 0 #define configCPU_CLOCK_HZ (SystemCoreClock) #define configTICK_RATE_HZ ((TickType_t)1000) #define configMAX_PRIORITIES (5) #define configMINIMAL_STACK_SIZE ((unsigned short)128) #define configTOTAL_HEAP_SIZE ((size_t)(8 * 1024)) #define configMAX_TASK_NAME_LEN (16) #define configUSE_TRACE_FACILITY 1 #define configUSE_16_BIT_TICKS 0 #define configIDLE_SHOULD_YIELD 1 #define configUSE_MUTEXES 1 #define configQUEUE_REGISTRY_SIZE 8 #define configCHECK_FOR_STACK_OVERFLOW 2 #define configUSE_RECURSIVE_MUTEXES 1 #define configUSE_MALLOC_FAILED_HOOK 1 #define configUSE_APPLICATION_TASK_TAG 0 #define configUSE_COUNTING_SEMAPHORES 1 #define configUSE_QUEUE_SETS 0 #define configUSE_TASK_NOTIFICATIONS 1逐个说说这里面的关键项。configCPU_CLOCK_HZ我直接写SystemCoreClock这个变量在system_stm32f10x.c里被SystemInit设置为72000000这样板子换了系统时钟也能自动适配。configTICK_RATE_HZ设为1000也就是系统节拍1ms一次这个值要配合后面讲到的SysTick配置。configTOTAL_HEAP_SIZE我给了8K对于一个简单的多任务应用足够了如果你后续要跑LVGL、文件系统这种吃内存的组件这个值要提到16K甚至更大。configCHECK_FOR_STACK_OVERFLOW设为2这个务必开启它会在任务切换时花一点额外时间检查栈是否溢出对于开发调试阶段非常有用。configUSE_MALLOC_FAILED_HOOK设为1当堆内存不足时系统会调用vApplicationMallocFailedHook()函数你可以在里面点亮一个错误LED或者进入断言尽早发现内存问题。2.3 中断向量表与启动文件的联动修改接下来是移植中最容易出问题、也最需要理解原理的地方中断服务函数的注册。Cortex-M3内核有一个中断向量表放在启动文件startup_stm32f10x_md.s中。裸机工程里PendSV和SysTick这两个向量默认指向空函数PendSV_Handler和SysTick_Handler但FreeRTOS需要接管这两个中断来实现任务切换和系统节拍。所以需要在启动文件中把这两个向量指向FreeRTOS的实现PendSV_Handler DCD xPortPendSVHandler SysTick_Handler DCD xPortSysTickHandler修改启动文件时我建议直接搜索PendSV_Handler把这一行替换成xPortPendSVHandlerSysTick同理。这个修改的本质是当PendSV中断发生时CPU会从向量表中取出xPortPendSVHandler的地址并跳转执行完成任务上下文的保存和恢复。如果跳过这一步任务调度器一启动就会跑飞因为PendSV中断触发时找不到正确的中断服务函数。还有一个相关的点系统中其他中断比如串口中断USART1_IRQHandler、定时器中断TIM2_IRQHandler这些仍然放在stm32f10x_it.c中定义不受影响。但FreeRTOS也提供了一种方式在中断服务函数开头调用portYIELD_FROM_ISR()来触发任务切换这种方式在后面的串口实验中会用到。3. 核心机制与代码改造细节3.1 系统节拍来源重写SysTick_HandlerFreeRTOS在启动调度器时会在xPortStartScheduler()中初始化SysTick并设置好节拍中断。但是问题来了很多标准库的裸机工程里自己的Delay函数也用了SysTick如果不在同一时间轴处理就会和FreeRTOS冲突。在裸机工程中标准外设库的Delay函数通常这样实现自己操作SysTick寄存器等待计数归零。但在FreeRTOS环境下SysTick中断已经被FreeRTOS接管你再直接操作SysTick的寄存器就会破坏RTOS的心跳。所以模板中的SysTick_Handler应该这样写void SysTick_Handler(void) { if (xTaskGetSchedulerState() ! taskSCHEDULER_NOT_STARTED) { xPortSysTickHandler(); } }注意两个要点一是启动文件中已经把SysTick_Handler映射成了xPortSysTickHandler所以这段代码其实不会在普通中断里被调用二是如果你仍然想保留裸机时代的Delay功能可以考虑用DWT计数器或者普通的定时器例如TIM4来实现不要让裸机Delay和FreeRTOS抢占同一个SysTick。实际移植中我的模板里直接删掉了原来的SysTick_Handler裸机实现把系统节拍完全交给FreeRTOS这是最干净的做法。3.2 SVC与PendSV任务切换的两个关键中断Cortex-M3上运行FreeRTOS时任务切换依赖两个特殊的中断SVC系统服务调用和PendSV可挂起的系统调用。SVC在启动调度器的第一时刻被触发vPortSVCHandler会完成第一个任务上下文环境的初始化让系统跳转到优先级最高的任务执行。正常情况下任务运行过程中SVC不会再被触发。PendSV是任务切换的核心。FreeRTOS在这里做了一个非常优雅的设计任务切换并不会立即发生而是先挂起PendSV等当前中断处理完再执行上下文切换。这避免了在中断处理中直接切换任务导致的临界区乱序问题。用一句生活化的话说就像排队办事PendSV是“最后一个离开的人”它会等到所有人都处理完了才把窗口关上。理解这一点对排查问题很有帮助。如果你在某个中断服务函数里调用了可能导致任务阻塞的API而那个中断的优先级高于PendSVPendSV在FreeRTOS中被设置为最低优先级系统就会卡死。因为高优先级的中断会不断延迟PendSV的执行这也就是为什么FreeRTOS强烈建议所有使用FreeRTOS API的中断其优先级不得高于PendSV。3.3 内存堆管理多方案对比与选择FreeRTOS提供了5种内存管理方案放在MemMang目录下。它们在时间确定性、碎片处理、内存回收上有不同的权衡heap_1.c最简单只支持分配不支持释放适合永不删除任务的场景。heap_2.c支持释放但不会合并相邻空闲块容易产生碎片已经不推荐新项目使用。heap_3.c包装了标准库的malloc/free需要编译器提供C库支持线程安全需要自己保证。heap_4.c支持释放且会将相邻空闲内存块合并显著降低碎片化是大多数项目的首选。heap_5.c在heap_4基础上支持跨非连续内存区域分配适合片内SRAM不够、需要外扩SDRAM的场景。我选用heap_4.c理由很简单代码实现紧凑内存利用率高且实现了空闲块合并。在F103这种只有20K~48K SRAM的芯片上内存是稀缺资源heap_4能最大程度避免碎片浪费。需要特别注意的是FreeRTOS的堆大小由configTOTAL_HEAP_SIZE决定而这个堆是编译器在链接时通过一个静态数组分配的。如果堆大小超过芯片的实际SRAM大小链接器会报错或者程序启动就HardFault。F103C8T6有20K SRAM我配置8K堆是安全的如果换成F103RCT648K SRAM堆可以配到16K以上。4. 验证实验跑起第一个多任务4.1 双任务交替闪烁配置好之后我把之前裸机工程的主函数改成了一个最小验证程序创建两个任务分别控制不同的LED闪烁void vTaskLed1(void *pvParameters) { for (;;) { GPIO_SetBits(GPIOC, GPIO_Pin_13); vTaskDelay(pdMS_TO_TICKS(500)); GPIO_ResetBits(GPIOC, GPIO_Pin_13); vTaskDelay(pdMS_TO_TICKS(500)); } } void vTaskLed2(void *pvParameters) { for (;;) { GPIO_SetBits(GPIOC, GPIO_Pin_14); vTaskDelay(pdMS_TO_TICKS(200)); GPIO_ResetBits(GPIOC, GPIO_Pin_14); vTaskDelay(pdMS_TO_TICKS(200)); } } int main(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); SystemInit(); GPIO_Config(); xTaskCreate(vTaskLed1, LED1, 128, NULL, 2, NULL); xTaskCreate(vTaskLed2, LED2, 128, NULL, 1, NULL); vTaskStartScheduler(); for (;;); }NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4)这一行非常重要。FreeRTOS假定所有中断优先级都支持抢占优先级不区分子优先级。如果你不设置优先级分组为4会导致中断优先级计算出错最直接的后果就是任务切换异常。这个坑非常隐蔽不少人在移植完成后任务一多就出问题最后排查半天发现是优先级分组没设对。板子上电后LED1以500ms周期闪烁LED2以200ms周期闪烁说明两个任务已经被正确调度。如果两个LED的闪烁节奏完全正常但主循环卡死或任务不切换十有八九是启动文件里的向量没改对。4.2 再加一个串口打印任务点灯只是第一步。为了进一步验证任务调度的稳定性和中断与FreeRTOS API的交互我加了一个串口任务通过串口周期性打印当前系统运行时间和任务状态信息void vTaskDebug(void *pvParameters) { TaskStatus_t taskInfo; for (;;) { printf(System tick: %lu\r\n, (unsigned long)xTaskGetTickCount()); vTaskList((char *)pcWriteBuffer); printf(%s\r\n, pcWriteBuffer); vTaskDelay(pdMS_TO_TICKS(1000)); } }xTaskGetTickCount()返回系统从启动到现在的tick数这个值可以用于计算运行时间。vTaskList()则能输出当前所有任务的状态、优先级、栈剩余空间等信息是排查任务异常的重要手段。这里要注意如果启用了configUSE_TRACE_FACILITYvTaskList()才能正常工作。另外printf在嵌入式环境中可能需要重定向到串口我在模板中做了fputc的重定向这也是标准库工程里常见的操作。4.3 典型故障现象与排查速查表移植过程中几乎每个人都踩过坑这里整理一份我实测中遇到的典型问题按故障现象、可能原因、解决方案整理成表格方便你对照排查故障现象可能原因排查与解决程序启动后进入HardFault中断向量表未修改、堆内存不足、任务栈太小检查启动文件PendSV/SysTick向量增大configTOTAL_HEAP_SIZE增大MINIMAL_STACK_SIZE任务不切换只有最高优先级任务在跑优先级配置错误或SysTick未产生中断确认configCPU_CLOCK_HZ与实际时钟一致检查SysTick相关向量是否被FreeRTOS接管串口打印乱码串口波特率和时钟配置不匹配确认SystemCoreClock72000000串口初始化的APB时钟分频正确任务创建后立即崩溃栈溢出、堆耗尽调用vApplicationStackOverflowHook()打印出错任务名增大堆或栈调用API后在中断里卡死中断优先级高于PendSV确保使用FreeRTOS API的中断优先级数值大于或等于5对应抢占优先级低于PendSV关于中断优先级多说一句。Cortex-M3使用数值越小优先级越高的规则FreeRTOS要求configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY必须设置正确。当我们在中断里使用xQueueSendFromISR这类带FromISR后缀的函数时如果该中断的优先级高于FreeRTOS可管理的优先级系统会直接断言失败。这是FreeRTOS的保护机制意在提醒你中断优先级设置不合理。5. 通用模板的扩展思路5.1 从裸机思维切换到RTOS思维做完这个移植最难的不是让代码跑起来而是思维方式的转变。裸机开发是一个大循环不停轮询RTOS则是把整个程序拆分成多个独立的任务每个任务都有自己的栈、优先级和时间片。你不能再理所当然地用delay()来等待某个外设就绪而应该考虑是否用任务通知、信号量、队列这些RTOS原语来同步。我给模板预留了几个常见外设的驱动模板框架比如串口采用“中断接收 队列传递 任务处理”的架构按键采用“定时扫描 事件通知”的架构。这种架构在实际项目中非常实用也是面试时经常考到的点。任务优先级怎么分配一个重要的原则是紧急且短小的任务如按键检测、数据接收优先级可以高一些但运行时间要短计算密集型的任务如浮点运算、LCD刷新优先级要低避免长期霸占CPU。如果你有多个紧急任务不建议把优先级分得太细优先级数量够用就好否则会增加系统调试的复杂度。5.2 模板后续可以扩展的方向这份通用模板只是起点后续有很多方向可以延伸。我计划在模板基础上加入消息队列和信号量的使用示例让任务间通信有章可循加入软件定时器来替代一部分周期性的任务轮询加入空闲任务钩子来统计CPU使用率方便性能调优后续再考虑在这里跑LVGL做一个带界面的RTOS应用那也是很多人在做的小项目。模板本身还留了一个非常好的扩展点因为FreeRTOS的移植层和硬件无关如果以后从F103换到STM32F407或者GD32F303只需要更换启动文件、system文件、调整时钟配置再替换portable/ARM_CM4F目录下的移植文件应用层的任务代码几乎可以原样保留。这也是构建通用模板最大的价值所在。本文还有配套的精品资源点击获取
RELATED

相关推荐

51单片机扫地吸尘智能车设计与制作:从硬件选型到代码调试

51单片机扫地吸尘智能车设计与制作:从硬件选型到代码调试

1. 项目整体认识:一台51单片机小车能做什么把“51扫地吸尘智能车机器人”拆开看,它其实是三件事的组合:一辆能动的智能小车、一套能吸尘的清扫机构、一颗用51单片机驱动一切的“大脑”。这是个非常典型又非常完整的单片机课程设计级项目&…

📅 2026/9/9 17:42:41
盘点无人机飞手接单平台有哪些?

盘点无人机飞手接单平台有哪些?

寻找一个强大的专业的无人机飞手接单平台,是众多无人机操作员和项目方共同关注的问题。随着无人机技术的不断普及,越来越多的行业开始依赖无人机进行测绘、巡检、航拍等任务。而在这个过程中,如何快速找到合适的飞手,成为了一个关…

📅 2026/9/9 17:37:40
正则表达式实战指南:从语法到IP校验、grep过滤与避坑

正则表达式实战指南:从语法到IP校验、grep过滤与避坑

做开发、运维或者测试的朋友,早晚都会碰到“正则表达式”这个东西。第一次看见一长串 \d\.\d 的时候,大多数人脑子里只有三个字:这是啥。实际上,正则表达式就是一套用字符描述“你要在文本里找什么形状”的规则,它能…

📅 2026/9/9 17:37:40
MORE NEWS

更多资讯

📰

爬虫数据清洗实战:构建文本去重引擎的完整方案

做爬虫时间久了,你会发现真正麻烦的往往不是“怎么把数据抓下来”,而是“抓到之后怎么处理”。最常见的一个污染源就是重复文本:同一个新闻被几十个网站转载,同一篇商品描述在不同店铺反复出现,同一条公告被改了标题又…

📰

老Mac如何跑上最新内核?3段代码看懂OpenCore-Legacy-Patcher的KDK与内核缓存重建

老Mac如何跑上最新内核?3段代码看懂OpenCore-Legacy-Patcher的KDK与内核缓存重建 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 启动时 Apple 图…

📰

汽车机盖重拓扑P1:主结构线与密度分配的核心思路

机盖重拓扑是汽车硬表面建模里一个很典型的“看着简单、做起来容易翻车”的部位。它不像前后保险杠那样有大量穿孔和分件,整个表面通常是一大块连续曲面,中间偶尔带一条或两条特征线。正因为结构简单,很多人容易一上来就直接铺平面、拉点、挤…

📰

STM32F407与OV2640嵌入式条码识别:工程解析与调试实战

简介:面向嵌入式视觉与条码识别场景的开发者,该工程以 STM32F407VET6 为主控,搭配 OV2640 摄像头,实现 640480 分辨率 RGB 图像的采集,并通过自定义协议将数据上传至上位机完成一维码/二维码解码,适合需要快…

📰

Android投屏Demo实战:MediaProjection与MediaCodec编码传输全解

简介:Android端投屏demo是一份面向Android开发者的示例工程,其核心是演示基于MediaProjection API的手机屏幕镜像到电视、电脑或投影仪等设备的完整流程。压缩包大小33.55MB,共2000个文件,以class、png、xml、json、jar、java等为…

📰

10分钟上手DeepEval:给LLM应用装上一套自动化质量评估流水线

10分钟上手DeepEval:给LLM应用装上一套自动化质量评估流水线 【免费下载链接】deepeval The LLM Evaluation Framework 项目地址: https://gitcode.com/GitHub_Trending/de/deepeval 你刚上线了一个RAG问答系统,老板问了一句:"怎…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬