FreeRTOS-基础知识
简要介绍了FreeRTOS操作系统和FreeRTOS在stm32工程中如何移植配置
目录
FreeRTOS概述
为什么需要FreeRTOS
- 裸机开发:在没有操作系统或者其他高级软件支持的情况下直接在裸机硬件上进行软件开发需要亲自管理CPU、内存以及I/O资源,而不依赖任何操作系统提供的抽象层或者服务,裸机开发的程序中代码是一行一行进行的,如果某行的函数有延时则其他函数需要等待这个函数的延时时间走完才能运行,这就导致资源的浪费
- 操作系统:操作系统分为通用操作系统喝实时操作系统,通用操作系统包括Linux,Windows,MACOS等,它们有时为了保障系统的流畅运行,就不能保证每个程序都能实时响应,而且单片机片上资源有限;实时操作系统(RTOS-Real Time Operating System)中实时(Real Time)指的是任务(Task)或者说实现一个功能的线程(Thread)必须在给定的时间(Deadline)内完成,还是直接操作于硬件,但是可以帮助实现复杂任务的灵活调度,目前比较流行的实时操作系统包括黑莓QNX,FreeRTOS,uCOS,RT-Thread等
- 内核是操作系统的核心组件,通用操作系统采用的内核允许多个用户看似同时访问计算机的处理器,这些用户可以执行多个程序,使得看起来并发运行,每个执行的程序由操作系统控制下的一个或多个线程实现,如果操作系统能够以这种方式执行多个线程,则称为多任务处理,像Free RTOS这样的小型RTOS通常将线程称为任务,因为它们不支持虚拟内存,因此进程和线程之间没有区别
- 操作系统的多任务处理和任务间通信功能允许将复杂的应用程序划分为一组更小更易于管理的任务,复杂的时序和排序细节由FreeRTOS内核负责,从而减轻了应用程序代码的负担
FreeRTOS介绍
FreeRTOS实时操作系统是一种体积小巧、确定性强的计算机操作系统,通常用于需要在严格时间限制内对外部事件做出反应的嵌入式系统,FreeRTOS通常比通用操作系统体积更小、重量更轻,因此FreeRTOS非常适用于内存、计算和功率受限的设备
FreeRTOS特点:
- 任务调度:FreeRTOS通过任务调度其管理多个任务,支持不同优先级的任务,实现任务的有序进行
- 任务通信和同步:提供了队列、信号量等机制,支持任务之间的通信和同步,确保数据的安全传递
- 内存管理:提供简单的内存管理机制,适用于嵌入式环境,有效利用有限的内存资源
- 定时器和中断处理:支持定时器功能,能够处理中断,可实现在中断中调度任务,提供可靠的实时性能
多任务处理

单核处理器一次只能执行一项任务,到那时多任务操作系统可以通过任务之间的快速切换制造并发执行的假象(并行是指多个线程同时执行,并发是指多个线程执行)
任务调度

任务调度器在内核中负责决定在特定时间应该执行什么任务的部分,内核可以在任务的生命周期多次暂停并恢复该任务, 调度策略是调度器用来决定在任何时间点执行哪个任务的算法
FreeRTOS默认使用固定优先级的抢占式调度策略,对同等优先级的任务执行时间片轮询调度,还有AMP调度策略,SMP调度策略
- 抢占式调度:FreeRTOS采用抢占式调度方式,允许更高优先级的任务在任何时刻抢占正在执行的低优先级任务,这确保了高优先级任务能够即使响应并提高了系统的实时性
- 时间片轮询:在相同的优先级之间,FreeRTOS采用时间片轮转策略,每个任务轮流执行一个时间片(时钟节拍Tick ,由配置的FreeRTOS时钟源来决定),如果有其他同优先级的任务等待执行,则切换到下一个任务,如果程序执行时间不足一个时间片,则程序执行完后会立即执行下一个程序而不会空等直到1个时间片的时间
- 如果高优先级任务里有个循环或者延时等待等,并不会高优先级会一直执行,导致第优先级的任务无法得到执行,如果高优先级等待某个资源(延时或等待信号量等)而无法执行,调度器会选择执行其他就绪的高优先级的任务
- FreeRTOS AMP调度策略:是指多核设备的每个核心都单独运行自己的 FreeRTOS 实例,因此任何给定核心上的调度算法与上面的默认为调度算法一样
- FreeRTOS SMP调度策略:是指一个FreeRTOS实例可以跨多个核运行任务
任务状态
FreeRTOS中任务共存在4种状态:
- 运行态:当任务执行时,它被称为处于运行状态,如果运行RTOS的处理器只有一个内核,那么再任何给定时间内都只能有一个任务处于运行状态
- 就绪态:准备就绪任务指那些能够执行(他们不处于阻塞或挂起状态),但目前没有执行任务,因为同等或更高优先级的不同任务已经处于运行状态
- 阻塞态:如果任务当前正在等待延时或外部事件,则该任务被认为处于阻塞状态
- 挂起态:类似暂停,调用函数vTaskSuspend()进入挂起态,挂起后会被忽略,需要调用解挂函数vTaskResume()才可以进入就绪态

只有就绪态可转变成运行态,其他状态的任务想运行,必须先转变就绪态
运行态转换为就绪态:
-
当多个相同优先级的任务启用时间片调度(
configUSE_TIME_SLICING为 1)时,当前运行的任务会在时间片用完后(通常是一个系统时钟节拍tick),被调度器强制切换到就绪态,让下一个同优先级的就绪任务获得CPU使用权 -
任务可以通过调用特定API函数主动让出CPU,进入就绪态
-
当有更高优先级的任务从阻塞态转变被就绪态时,FreeRTOS的抢占式调度机制会立即暂停当前运行的低优先级任务,使从运行态转为就绪态,同时高优先级任务进入运行态
-
若任务调用某些API(如
xQueueReceive()、xSemaphoreTake()等)并指定了0超时时间,且等待的资源 / 事件未立即满足时,任务不会进入阻塞态,而是直接从运行态转为就绪态(继续等待下一次调度)
这四种状态种,除了运行态,其他三种任务状态的任务都有其对应的任务状态列表:
- 就绪列表:pxReadyTasksLists[x],其中x表示任务优先级数值
- 阻塞列表:pxDelayedTaskList
- 挂起列表:xSuspendedTaskList
列表类似于链表,而且时双向列表,以就绪列表为例,如果在32位的硬件中,会保存一个32位的变量,代表0-31优先级,当某一个位,置于1时,代表所对应的优先级就绪列表会有任务存在
如果有多个任务优先级相同,会连接在同一个就绪列表上,调度器总是在所有处于就绪列表的任务中,选择具有最高优先级的任务来执行,如果优先级级相同就用时间片轮询机制
任务上下文切换
一个任务是一段有顺序的代码——它不知道什么时候会被内核挂起(换出或换入)或恢复(换入或换入), 甚至不知道什么时候自己被挂起或恢复过,一个任务在即将执行将两个处理器寄存器内包含的数值相加之前被挂起, 当该任务被挂起时,其他任务会执行,还可能会修改处理器寄存器的数值,恢复时, 该任务不会知道处理器寄存器已经被修改过了——如果它使用经修改过的数值, 那么求和会得到一个错误的数值(比如有A和B两个程序,一开始A程序执行到第五行代码,还定义了一个变量a=1,然后切换到B程序执行,之后如果在切换回A程序执行,如果再从头开始执行则效率很低,应该是要将这5行代码以及定义的变量存放于某个寄存器来记录切换任务前的数据和状态,等切换回来后再在刚才的基础上继续执行)
- 所以为了防止这种类型的错误,任务在恢复时必须有一个与挂起之前相同的上下文,通过在任务挂起时保存任务的上下文,操作系统内核负责确保上下文保持不变,任务恢复时,其保存的上下文在执行之前由操作系统内核恢复,保存被挂起的任务的上下文和恢复被恢复的任务的上下文的过程被称为上下文切换
- 将 TaskA在相应的处理器寄存器中的上下文保存到其任务堆栈中
- 这个任务堆栈的栈是指的是一种数据结构,这个数据结构主要占用的是内存划分的堆空间,而且FreeRTOS会自动申请和释放空间
- 在FreeRTOS中的任务栈,本质上与我们平时中说的栈是类似的,特点也是先进后出,每个任务各自拥有一块私有的栈空间
- 平时程序运行的时候,函数调用会用到栈,通常存放函数的局部变量、函数参数、返回地址等,在裸机程序或者普通单线程程序里,通常整个程序只有一条执行流,所有共用一个主栈
- FreeRTOS是多任务系统,每个任务类似于独立运行,所以每个任务需要自己的栈,存放当前任务的CPU寄存器和栈指针,然后切换任务后再恢复另一个任务的寄存器和栈指针
- 栈是从堆(内存的分配区域)分配出来的,在创建任务的时候会分配给任务任务控制块TCB和任务栈,这两个都从FreeRTOS堆里分配
- 而FreeRTOS堆大小是在配置文件中根据芯片的SRAM大小分配而来,这块内存通常用于分配任务TCB,任务栈、队列、信号量、互斥锁、软件定时器等内核对象
- SRAM除了FreeRTOS堆,还包括中断栈/主栈,任务切换和启动时用到的系统栈,各种缓冲区、如果有malloc还有C库自己的栈
- 保存任务A的上下文过程是:当任务A在运行时占用CPU中的寄存器编号,这样存放和恢复数据就能一一对应
- 这里会有一个栈顶指针指向任务A最后的位置,这个指针存放于内核,该指针是用于记录任务A的地址,这样从别的任务切换到任务A就只用根据这个地址即可找到任务A的任务栈
- 这个任务堆栈的栈是指的是一种数据结构,这个数据结构主要占用的是内存划分的堆空间,而且FreeRTOS会自动申请和释放空间

- 将TaskB的上下文从其任务堆栈种恢复到相应的处理器寄存器种
- 恢复任务B上下文的过程:要切换到任务B时,就会将B任务栈的数据再重新写入CPU对应寄存器

在需要切换任务的时候进行上下文切换,真正执行上下文切换是在PendSV(系统异常,相当于系统级的中断)的ISR中的处理,使用PendSV是因为其可以手动触发,并且可以在其他更高中断优先级的ISR中来进行设置,比较灵活,具体触发操作控制是将中断制和状态寄存器ICSR的bit28,就是PendSV的挂起位置1来触发PendSV中断,FreeRTOS会将PendSV设置为最低中断优先级,避免任务切换影响到其他正常的ISR,而且不用担心任务中的高优先级中断里面会有延时函数从而导致无法进行任务切换浪费资源,任务中的高优先级中断不能直接调用延时函数
在FreeRTOS中有一下几个情况会触发PendSV异常产生切换:
- RTOS滴答中断:会处理就绪列表,判断是否要切换任务(包括抢占式、时间片轮转)
- 任务切换过程是在Systick里面进行的,先是将时间片tick+1,再判断有无任务结束阻塞状态,然后再判断有无高优先级的就绪状态需要转换为运行状态,最后就挂起PendSV标志位触发PendSV中断
- 任务执行完毕:主动调用任务切换函数进行强制切换
空闲任务
RTOS调度器启动时,自动创建空闲任务,以确保始终存在一个能够运行的任务,空闲以最低优先级创建,以确保如果有更高邮件及应用程序任务处于准备就绪状态,空闲任务则不适用任何CPU时间,空闲任务负责释放被删除的任务的内存
ESP32-FreeRTOS
esp32和FreeRTOS的联系
ESP32的官方开发框架ESP-IDF是搭建在FreeRTOS操作系统的基础上的,以此来实现多任务运行环境的,FreeRTOS是ESP32的“默认操作系统内核”,但是乐鑫官方对它做了深度的定制和扩展
- esp32是双核cpu(Xtensa LX7),每个核都能独立执行代码
- esp-idf中的FreeRTOS是深度集成进框架的内核,默认启用SMP对称多处理:两个核可以同时跑不同的任务
- xTaskCreate由调度器决定放哪个核心
- xTaskCreatePinnedToCore显示指定任务绑定在Core0还是Core1上
esp32对FreeRTOS的扩展
- 多核 SMP 支持
- 标准 FreeRTOS 有 SMP 版本,但乐鑫在 ESP-IDF 中实现了自己的 SMP 调度器,和双核硬件强绑定
- 增加了:
- 核间中断(IPC 中断)用于核心之间的任务通知、同步
- 核绑定 API(
xTaskCreatePinnedToCore、xPortGetCoreID等)
- 内存管理整合
- 标准FreeRTOS中的,任务、队列、信号量、事件组等内核对象,都需要从“堆”里申请内存,标准FreeRTOS内存管理是通过配置内存算法文件,一般是heap4.h,作用就是在MCU的RAM中划出一块连续区域给FreeRTOS专用,由FreeRTOS自己管理这块区域的分配和释放,不依赖挖补的malloc和free
- esp32是通过memory architecture和multi-heap来进行内存管理,因为esp32的RAM结构比传统单核的MCU复杂,有Internal SARM1、SRAM2、SRAM0:不同地段有的可以缓存有的不可以缓存,IRM:存指令,要求代码放在特定区域、DRAM:存数据、PSRAM(外部扩展):容量更大,但速度比内部RAM慢
- esp-idf没有将所有这些内存混在一起交给FreeRTOS的heap4管理,而是做了一个multi-heap管理,把不同的区域各自抽象成一个或多个“堆”,每个堆可以有自己的分配策略、对齐要求、缓存属性
- 在 esp32 上,FreeRTOS 的 heap4 只管“FreeRTOS 内核对象”那一小块内存,其他模块的内存由乐鑫的 multi-heap 系统分配,两者并存
- 中断与低功耗的融合
- 深度睡眠、Light-sleep 等低功耗模式下,FreeRTOS tick 会暂停或变慢,唤醒后由乐鑫的硬件定时器恢复
- 某些外设中断(如 Wi‑Fi 事件、BLE 事件)由乐鑫专用中断处理,再转发到 FreeRTOS 任务,而非直接在 ISR 里做复杂逻辑
- 删除/替换部分原生模块
- ESP-IDF中有时候不使用FreeRTOS自带的
vTaskDelayUntil在某些低功耗场景下的实现,而是用乐鑫自己的定时器方案 - 某些 Hook 函数、统计功能的实现方式和标准 FreeRTOS 不完全一致
- ESP-IDF中有时候不使用FreeRTOS自带的
FreeRTOS的移植
源码结构
源码整体结构

- FreeRTOS:FreeRTOS内核
- FreeRTOS-Plus:FreeRTOS组件,一般会选择使用第三方的组件
- tools:与FreeRTOS开发、配置、可视化、调试相关的辅助工具
- History.txt:FreeRTOS历史更新记录
- CitHub-FreeRTOS-Home:FreeRTOS的GitHub仓库链接
- Quick_start_Guide:快速入门指南官方文档链接
- Upgrading-to-FreeRTOS:升级到指定FreeRTOS版本官方文档链接
FreeRTOS文件结构

- Demo:FreeRTOS演示例程,支持多种芯片架构、多种型号芯片
- License:FreeRTOS相关许可
- Source:FreeRTOS源码,最重要的文件夹
- Test:公用以及移植层测试代码
Source文件夹结构

- include:内包含了FreeRTOS的头文件
- portable:包含了FreeRTOS移植文件:与编译器相关、keil编译环境等
- 各种.c文件:croutine.c协程相关文件、event_groups.c事件相关文件、list.c列表相关文件、queue.c队列相关文件、stream_buffer.c流式缓冲区相关文件、task.c任务相关文件、timer.c软件定时器相关文件
portable文件夹结构

- 因为这里是以keil为编译环境所以只介绍keil相关的
- Keil文件是历史更替原因,指向RVDS文件夹、RVDS是不同ARM内核芯片的移植文件、MemMang是内存管理相关文件
- RVDS 文件夹包含了各种处理器相关的文件夹,FreeRTOS 是一个软件,单片机是一个硬件,FreeRTOS 要想运行在一个单片机上面,它们就必须关联在一起,关联还是得通过写代码来关联,这部分关联的文件叫接口文件,这些接口文件就放在 RVDS 这个文件夹的目录下,FreeRTOS 为我们提供了 cortex-m0、m3、m4 和 m7 等内核的单片机的接口文件,根据mcu的内核选择对应的接口文件即可
- 以 ARM_CM3 这个文件夹为例,里面只有port.c与portmacro.h”两个文件
- port.c文件:里面的内容是由 FreeRTOS 官方的技术人员为 Cortex-M3 内核的处理器写的接口文件,里面核心的上下文切换代码是由汇编语言编写而成,对技术员的要求比较高,我们只是使用的话只需拷贝过来用即可
- portmacro.h文件:port.c文件对应的头文件,主要是一些数据类型和宏定义
MemMang文件夹结构

- MemMang 文件夹下存放的是跟内存管理相关的,总共有五个 heap 文件以及一个 readme 说明文件
- 这五个 heap 文件在移植的时候必须使用一个,因为 FreeRTOS 在创建内核对象的时候使用的是动态分配内存,而这些动态内存分配的函数则在这几个文件里面实现,不同的分配算法会导致不同的效率与结果,一般使用heap4.c即可
FreeRTOS在项目中移植
该移植步骤是基于stm32f103zet6的hal库工程项目
文件目录处理
先是在一个项目文件目录下新建一个FreeRTOS文件夹专门存放移植的文件

再在FreRTOS文件夹下创建3个文件夹,存放包含的头文件include(直接复制源文件的文件夹全部拷贝过来)、portable(存放RVDS文件夹里的内核文件ARM_CM3(这个跟据你所驱动的芯片内核型号来选择)和MemMang文件夹里的heap4.c内存分配算法文件)

然后还需要 一个FreeRTOS工程配置文件FreeRTOSConfig.h(这是一个用户裁剪文件,让FreeRTOS源码功能更加贴合所开发的芯片,省去了很多多余代码),这个文件可以在Demo文件夹里面找到相应的内核编译器历程文件,然后复制一份即可,然后放在HAL文件夹里的Inc专门存放头文件的文件夹里
在文件夹里创建完目录就可以在keil添加相应的路径和add.c文件了
代码处理
首先是要在系统工程配置文件FreeRTOSConfig.h添加3个必须的宏定义
#define xPortPendSVHandler PendSV_Handler//这是将FreeRTOS里的中断名称改为HAL库里的名称
#define vPortSVCHandler SVC_Handler
#define INCLUDE_xTaskGetSchedulerState 1//这个是获取调度器状态,这里手动打开
然后要修改stm32f1xx_it.c文件,先是添加两个头文件
#include "FreeRTOS.h"//这里的FreeRTOS头文件必须要放在task头文件前面不然会报错
#include "task.h"
然后是将HAL库生成的三个中断函数给注释掉,因为这些中断函数已经在FreeRTOSConfig.h里面定义过了,再次定义会重复定义分别是:void SVC_Handler(void)、void PendSV_Handler(void)、void SysTick_Handler(void)
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler
extern void xPortSysTickHandler(void);
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)//这里就是判断哪个调度器有没有打开,默认是关闭的所以我们要在前面宏定义为1
{
xPortSysTickHandler();//这就是FreeRTOS的定时器中断处理函数,而且这个函数是在某一个.c文件里面定义的,所以要引入
}
然后是设置和更换系统时钟源, 设置FreeRTOS的时钟源,一般是在系统滴答定时器中断里面引入调用FreeRTOS的函数,然后因为 HAL库的延迟函数HAL_Delay是要依赖于Systick系统滴答定时器,但是FreeRTOS中的时间片切换也是依赖于系统时钟源定时器,但是FreeRTOS会将滴答定时器的中断调整到最低优先级和PendSV一样很低的优先级,不会影响FreeRTOS代码,但是可能会让其他地方的中断出现卡壳,比如说一个中断优先级为6的Time_Handle调用了HAL_Delay函数,但是系统滴答定时器的优先级低,所以如果不出Time_Handle这个中断是执行不了HAL_IncTick这个函数的,所以就卡壳了,所以应该更换系统时钟源为不是Systick系统定时器,并且 把它的优先级调高一点,这样由于FreeRTOS对系统滴答定时器的优先级降低就不会影响到HAL_Delay的延时了
系统配置文件说明
整体的配置项可以分为三类:
- INCLUDE开头:一般是“INCLUDE_函数名”,函数的使能,1表示可用,0表示禁用
- config开头:FreeRTOS的一些功能配置,比如基本配置、内存配置、钩子配置、中断配置等
- 其他配置:PendSV宏定义、SVC宏定义

- 1: 抢占式调度器, 0: 协程式调度器, 无默认需定义
- 是否启用空闲任务钩子函数(钩子函数(Hook Function)就是:系统在特定时机自动调用的“用户自定义函数”,用来让你在不改内核源码的前提下,插入自己的代码,做日志、监控、调试、资源回收等事情),使用硬件计算下一个要运行的任务, 0: 使用软件算法计算下一个要运行的任务, 默认: 0
- 是否启用Tick钩子函数 默认: 0
- 定义CPU主频, 单位: Hz, 无默认需定义,MCU 内核实际工作时钟频率,决定多久进一次Tick中断,一般用宏定义SystemCoreClock
- 定义系统时钟节拍频率, 单位: Hz, 无默认需定义,FreeRTOS 每秒产生多少次 Tick 中断,依据对时间精度的要求
- 定义最大优先级数, 最大优先级=configMAX_PRIORITIES-1, 无默认需定义
- 定义空闲任务的栈空间大小, 单位: Word, 无默认需定义
- 定义任务名最大字符数, 默认: 16
- 1: 使能可视化跟踪调试, 默认: 0
- 1: 定义系统时钟节拍计数器的数据类型为16位无符号数, 无默认需定义,0表示用32位,支持更长延时
- 1: 使能在抢占式调度下,同优先级的任务能抢占空闲任务, 默认: 1
还有一些比较少用的高级配置这里就不过多介绍了
FreeRTOS数据类型
针对每个移植定义了四种类型:
- TickType_t:
- 如果 configUSE_16_BIT_TICKS 设置为非零 (true) ,则将 TickType_t 定义为无符号的 16 位类型,如果configUSE_16_BIT_TICKS 设置为零(假),则将 TickType_t 定义为无符号的 32 位类型
- 这个类型的变量, 通常用来表示系统节拍计数器的值。FreeRTOS系统中,每隔一段时间会进行一次滴答定时器中断处理,这个时间间隔就是系统的节拍周期。TickType_t类型的变量记录了系统过去的节拍次数
- BaseType_t:
- 架构中最有效、最自然的类型,例如,在 32 位架构上,BaseType_t 会被定义为 32 位类型,在 16 位架构上,BaseType_t 会被定义为 16 位类型,是有符号的
- UBaseType_t:
- 是无符号的BaseType_t
- StackType_t:
- 意指架构用于存储堆栈项目的 类型,通常是 16 位架构上的 16 位类型和 32 位架构上的 32 位类型,但也有例外情况,供FreeRTOS 内部使用
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐




所有评论(0)