freeRTOS知识快过
Free即免费的,RTOS的全称是Real time operating system,中文就是实时操作系统。注意:RTOS不是指某一个确定的系统,而是指一类操作系统。比如:uc/OS,FreeRTOS,RTX,RT-Thread等这些都是RTOS类操作系统。FreeRTOS是一个迷你的实时操作系统内核。作为一个轻量级的操作系统,功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能、
一、FreeRTOs介绍
什么是FreeRTOS?
Free即免费的,RTOS的全称是Real time operating system,中文就是实时操作系统。
注意:RTOS不是指某一个确定的系统,而是指一类操作系统。比如:uc/OS,FreeRTOS,
RTX,RT-Thread等这些都是RTOS类操作系统。
FreeRTOS是一个迷你的实时操作系统内核。作为一个轻量级的操作系统,功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满足较小系统的需要。
由于RTOS需占用一定的系统资源(优其是RAM资源),只有μC/OS-II、embOS、salvo、FreeRTOS等少数实时操作系统能在小RAM单片机上运行。相对μC/OS-11、embOs等商业操作系统,FreeRTOS操作系统是完全免费的操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种单片机上运行,其最新版本为10.4.4版。
(以上来自百度百科)

为什么选择FreeRTOS?
- FreeRTOS是免费的;
- 很多半导体厂商产品的SDK(Software Development Kit)软件开发工具包,就使用FreeRTOS作为其操作系统,尤其是WIFI、蓝牙这些带有协议栈的芯片或模块。
- 简单,因为FreeRTOS的文件数量很少。FreeRTOS资料与源码下载
FreeRTOs资料与源码下载
最好的资料就是官网提供的资料!FreeRTOS™ - FreeRTOS™

裸机开发与FreeRTOS
1、裸机开发(Bare Metal)
特点:
没有操作系统。主循环(
while(1))是核心,一切任务靠轮询或中断驱动。程序结构:初始化 → 主循环(或状态机) → 中断响应。
资源管理:全靠开发者手动管理(CPU时间、内存、外设等)。
适用场景:逻辑简单、实时要求高的小型系统,如LED控制、简单传感器采集、基础通讯。
优点:
运行效率高,没有系统调度开销;
程序占用内存极小;
上电即跑,响应快。
缺点:
难以扩展和维护;
多任务之间容易“抢资源”;
复杂逻辑下容易陷入“中断地狱”或“状态机迷宫”。
2、FreeRTOS(实时操作系统)
特点:
提供多任务(task)调度机制,能同时管理多个逻辑任务;
每个任务都有独立栈空间,系统通过调度器切换任务;
提供任务优先级、消息队列、信号量、互斥锁等;
时间管理统一(Tick中断),能精确控制任务延时与超时。
适用场景:
逻辑复杂的系统;
存在多种独立但需协调的任务(如通信、显示、传感、控制);
有一定资源的MCU(如STM32、GD32、ESP32等)。
优点:
程序结构清晰,可模块化;
各任务独立运行,调试方便;
更接近工业级嵌入式系统架构;
方便以后移植到更复杂的RTOS或Linux。
缺点:存在上下文切换开销;
需要RAM更多;
系统设计复杂度上升(尤其是任务同步与资源共享)。
对比点 裸机开发 FreeRTOS 主体结构 主循环 + 中断 多任务 + 调度器 时间控制 延时函数/定时器中断 vTaskDelay / 系统Tick 任务切换 人工控制流程 系统自动调度 共享资源 全靠程序员小心 信号量、互斥锁 适用复杂度 简单应用 中等以上复杂系统 总结一句话
裸机是直接操控硬件的“手工模式”,FreeRTOS是带“调度与分工”的协作系统。
但CPU是个无情的战斗机器,可以快速在两个乃至多个任务间快速切换,并且不觉得劳累,
实现二者兼顾。
FreeRTOS实现多任务的原理
严格来说FreeRTOS并不是实时操作系统,因为它是分时复用的。
系统将时间分割成很多时间片,然后轮流执行各个任务。
每个任务都是独立运行的,互不影响,由于切换的频率很快,就感觉像是同时运行的一样。

二、移植freeRTOS
手动移植参考这篇文章:【FreeRTOS移植到STM32F103C8T6超详细教程-->>>基于标准库】_freertos stm32f103c8t6-CSDN博客
使用CubeMX快速移植
快速移植流程
1.在SYS选项里,将Debug设为Serial Wire,并且将Timebase Source设为TIM2(其它定时器也行)。为何要如此配置?下文解说。

2.将 RCC里的 HSE设置为Crystal/Ceramic Resonator。

3.时钟按下图配置

4.选择FREERTOS选项,并将Interface改为CMSIS_V1。V1和V2有啥区别?下文解释。

5.配置项目信息,并导出代码。


一些常见问题

意思是当使用RTOS时,强烈建议给HAL库使用一个Systick以外的定时器作为时钟基准,这是因为FreeRTOS会以Systick中断作为时钟基准,因而会将Systick的中断优先级设置得比较低甚至有时会关闭其中断,这可能会导致HAL库的定时发生错乱。
1.Timebase Source为什么不能设置为SysTick?
裸机的时钟源默认是 SysTick,但是开启 FreeRTOs,FreeRTOS会占用 SysTick(用来生成1ms定时,用于任务调度),所以需要需要为其他总线提供另外的时钟源。
2. FreeRTOS版本问题
V2的内核版本更高,功能更多,在大多数情况下V1版本的内核完全够用。
3.FreeRTOS各配置选项卡的解释
- Events:事件相关的创建
- Task and Queues:任务与队列的创建
- Timers and Semaphores:定时器和信号量的创建
- Mutexes:互斥量的创建
- FreeRTOS Heap Usage:用于查看堆使用情况
- config parameters:内核参数设置,用户根据自己的实际应用来裁剪定制FreeRTOS内核
- Include parameters:FreeRTOS部分函数的使能
- User Constants:相关宏的定义,可以自建一些常量在工程中使用
- Advanced settings:高级设置
4.内核配置、函数使能的一些翻译



freeRTOS内核配置的相关说明,可以参考下面这篇文章。
FreeRTOS系列第6篇---FreeRTOS内核配置说明_vassertcalled-CSDN博客
三、任务的创建与删除
1.什么是任务?

任务可以理解为进程/线程,创建一个任务,就会在内存开辟一个空间。
比如:
玩游戏、陪女朋友,都可以视为任务
Windows 系统中的MarkText、谷歌浏览器、记事本,都是任务。
任务通常都含有While(1)死循环。
2.任务创建与删除相关函数
任务创建与删除相关函数有如下三个:
任务动态创建与静态创建的区别:
动态创建任务的堆栈由系统分配,而静态创建任务的堆栈由用户自己传递。
通常情况下使用动态方式创建任务。
xTaskCreate函数原型
1.pvTaskCode:指向任务函数的指针,任务必须实现为永不返回(即连续循环);
2.pcName:任务的名字,主要是用来调试,默认情况下最大长度是16;
3. pvParameters:指定的任务栈的大小;
4.uxPriority:任务优先级,数值越大,优先级越大;
5.pxCreatedTask:用于返回已创建任务的句柄可以被引用。
官方提供的案例:
/* Task to be created. */
void vTaskCode( void * pvParameters )
{
/* The parameter value is expected to be 1 as 1 is passed in the
pvParameters value in the call to xTaskCreate() below.
configASSERT( ( ( uint32_t ) pvParameters ) == 1 );
for( ;; )
{
/* Task code goes here. */
}
}
/* Function that creates a task. */
void vOtherFunction( void )
{
BaseType_t xReturned;
TaskHandle_t xHandle = NULL;
/* Create the task, storing the handle. */
xReturned = xTaskCreate(
vTaskCode, /* Function that implements the task. */
"NAME", /* Text name for the task. */
STACK_SIZE, /* Stack size in words, not bytes. */
( void * ) 1, /* Parameter passed into the task. */
tskIDLE_PRIORITY,/* Priority at which the task is created. */
&xHandle ); /* Used to pass out the created task's handle. */
if( xReturned == pdPASS )
{
/* The task was created. Use the task's handle to delete the task. */
vTaskDelete( xHandle );
}
}
vTaskDelete函数原型
void vTaskDelete(TaskHandle_t xTaskToDelete);
只需将待删除的任务句柄传入该函数,即可将该任务删除。
当传入的参数为NULL,则代表删除任务自身(当前正在运行的任务)。
3.实操
四、任务的状态
什么是任务调度?
调度器就是使用相关的调度算法来决定当前需要执行的哪个任务。
FreeRTOS中开启任务调度的函数是vTaskStartScheduler(),但在CubeMX中被封装为
osKernelStart()。
FreeRTOS的任务调度规则是怎样的?
FreeRTOS是一个实时操作系统,它所奉行的调度规则:
- 高优先级抢占低优先级任务,系统永远执行最高优先级的任务(即抢占式调度)
- 同等优先级的任务轮转调度(即时间片调度)
还有一种调度规则是协程式调度,但官方已明确表示不更新,主要是用在小容量的芯片上,用得也不多。
抢占式调度运行过程
Task 1:玩游戏
Task 2:老妈喊你吃饭
Task 3:女朋友喊你看电视
总结:
1.高优先级任务,优先执行;
2.高优先级任务不停止,低优先级任务无法执行;
3.被抢占的任务将会进入就绪态
时间片调度运行过程
总结:
1.同等优先级任务,轮流执行,时间片流转
2.一个时间片大小,取决为滴答定时器中断周期;
3.注意没有用完的时间片不会再使用,下次任务Task3得到执行,还是按照一个时间片的
时钟节拍运行
五、任务的状态
FreeRTOS中任务共存在4种状态:
- Running 运行态
当任务处于实际运行状态称之为运行态,即CPU的使用权被这个任务占用(同一时间仅一个
任务处于运行态)。
- Ready 就绪态
处于就绪态的任务是指那些能够运行(没有被阻塞和挂起),但是当前没有运行的任务,因
为同优先级或更高优先级的任务正在运行。
- Blocked 阻塞态
如果一个任务因延时,或等待信号量、消息队列、事件标志组等而处于的状态被称之为阻塞
态。
- Suspended 挂起态
类似暂停,通过调用函数vTaskSuspend()对指定任务进行挂起,挂起后这个任务将不被执
行,只有调用函数xTaskResume)才可以将这个任务从挂起态恢复。
总结:
1.仅就绪态可转变成运行态
2.其他状态的任务想运行,必须先转变成就绪态
任务综合小实验:
实验需求
创建4个任务:taskLED1,taskLED2,taskKEY1,taskKEY2,任务要求如下:
taskLED1:间隔500ms闪烁LED1;
taskLED2:间隔1000ms 闪烁LED2;
taskKEY1:如果taskLED1存在,则按下KEY1后删除 taskLED1,否则创建taskLED1;
taskKEY2:如果taskLED2正常运行,则按下KEY2后挂起taskLED2,否则恢复taskLED2
cubeMX配置:
代码实现:
/* USER CODE END Header_StartTaskKEY01 */ void StartTaskKEY01(void const * argument) { /* USER CODE BEGIN StartTaskKEY01 */ /* Infinite loop */ for(;;) { if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0 == GPIO_PIN_RESET)) { osDelay(20); if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0 == GPIO_PIN_RESET)) { printf("KEY1按下!\r\n"); if(taskLED01Handle == NULL)//判断按键1有没有按下 { printf("任务1不存在,准备创建任务1\r\n"); osThreadDef(taskLED01, StartTaskLED01, osPriorityNormal, 0, 128); taskLED01Handle = osThreadCreate(osThread(taskLED01), NULL); if(taskLED01Handle !=NULL ) printf("任务1创建完成!\r\n"); } else { printf("删除任务1\r\n"); osThreadTerminate(taskLED01Handle); taskLED01Handle=NULL; } } while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET ); } osDelay(10); } /* USER CODE END StartTaskKEY01 */ }核心功能
循环检测按键(KEY01)是否被按下,通过按键状态控制
taskLED01任务的创建与删除:
- 当按键按下且
taskLED01任务不存在时,创建该任务;- 当按键按下且
taskLED01任务已存在时,删除该任务。代码细节分析
无限循环
任务函数通过for(;;)实现无限循环,持续检测按键状态,符合 FreeRTOS 任务 “永不返回” 的特性。按键检测与消抖
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET) //这是一个逻辑判断,结果为0或1 { osDelay(20); // 延时20ms消抖 if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0 == GPIO_PIN_RESET)) // 再次检测确认 { // 按键按下后的逻辑... while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET ); // 等待按键释放 } }
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)(先读引脚状态,再与 “低电平” 比较)。消抖逻辑:第一次检测到按键按下后,延时 20ms(跳过机械抖动阶段),再次检测确认按下,避免误触发。
等待释放:
while循环等待按键松开,避免一次按下被多次识别。
任务的创建与删除
- 当
taskLED01Handle == NULL(任务不存在)时:通过osThreadDef定义任务属性(名称、函数、优先级等),再用osThreadCreate创建任务,成功后更新句柄。- 当
taskLED01Handle != NULL(任务已存在)时:用osThreadTerminate删除任务,并将句柄设为NULL(标记任务已删除)。延时调度循环末尾的
osDelay(10)让任务让出 CPU,给其他任务运行机会,避免独占系统资源(FreeRTOS 的任务调度依赖延时函数触发)/* USER CODE END Header_StartTaskKEY02 */ void StartTaskKEY02(void const * argument) { /* USER CODE BEGIN StartTaskKEY02 */ static int flag = 0;//标志位 /* Infinite loop */ for(;;) { if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1 == GPIO_PIN_RESET))//当按键2呗按下 { osDelay(20);//消抖 if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1 == GPIO_PIN_RESET)) { printf("KEY2按下!\r\n"); if(flag == 0) { osThreadSuspend(taskLED02Handle);//挂起 printf("任务2已暂停\r\n"); flag = 1; } else { osThreadResume(taskLED02Handle); printf("任务2已恢复\r\n"); flag = 0; } } while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET ); } osDelay(10); } /* USER CODE END StartTaskKEY02 */ }FreeRTOS 的按键检测任务函数(
StartTaskKEY02),核心功能是通过检测按键(连接在 GPIOA 的 PIN1 引脚)的状态,来切换另一个 LED 任务(taskLED02)的 “挂起” 与 “恢复” 状态。
核心功能
循环检测按键(KEY02)是否被按下,通过一个标志位(
flag)记录taskLED02的当前状态,每次按键按下时切换状态:
- 若
taskLED02正在运行,则将其挂起(暂停执行);- 若
taskLED02已被挂起,则将其恢复(继续执行)。代码细节分析
静态标志位
flag
static int flag = 0; // 标志位static修饰确保变量只在当前任务函数内可见,且任务运行期间不会被重置(保留上次的值)。- 作用:记录
taskLED02的状态 ——0表示任务正在运行,1表示任务已被挂起。- 无限循环与按键检测
for(;;) // 无限循环,持续检测按键 { if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1 )== GPIO_PIN_RESET) // 检测按键是否按下 { osDelay(20); // 延时20ms消抖(跳过机械抖动阶段) if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1 == GPIO_PIN_RESET)) // 再次确认按键按下 { // 按键按下后的逻辑... while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET ); // 等待按键释放 } } osDelay(10); // 让出CPU,给其他任务运行机会 }
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)(先读取引脚状态,再与 “低电平(按下)” 比较)。- 消抖逻辑:两次检测按键状态(间隔 20ms),避免按键机械抖动导致的误触发。
- 等待释放:
while循环等待按键松开,确保一次按下只触发一次状态切换。任务的挂起与恢复当按键确认按下后,根据
flag的值执行操作:if(flag == 0) // 若标志位为0(任务正在运行) { osThreadSuspend(taskLED02Handle); // 挂起taskLED02(暂停执行) printf("任务2已暂停\r\n"); flag = 1; // 更新标志位为“已挂起” } else // 若标志位为1(任务已挂起) { osThreadResume(taskLED02Handle); // 恢复taskLED02(继续执行) printf("任务2已恢复\r\n"); flag = 0; // 更新标志位为“运行中” }
osThreadSuspend:FreeRTOS 函数,暂停指定任务(任务进入 “挂起态”,不再参与调度)。osThreadResume:FreeRTOS 函数,恢复被挂起的任务(任务重新进入 “就绪态”,等待调度执行)。任务调度循环末尾的
osDelay(10)是 FreeRTOS 的延时函数,作用是让当前任务(StartTaskKEY02)暂时让出 CPU,允许系统调度其他任务运行,避免当前任务独占资源。
五、队列
什么是队列?
队列又称消息队列,是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断
和任务间传递信息。
为什么不使用全局变量?
如果使用全局变量,免子(任务1)修改了变量a,等待树獭(任务3)处理,但树獭处理速
度很慢,在处理数据的过程中,狐狸(任务2)有可能又修改了变量a,导致树獭有可能得
到的不是正确的数据。
在这种情况下,就可以使用队列。免子和狐狸产生的数据放在流水线上,树獭可以慢慢一个
个依次处理。
关于队列的几个名词:
队列项目:队列中的每一个数据;
队列长度:队列能够存储队列项目的最大数量;
创建队列时,需要指定队列长度及队列项目大小。
队列特点
1. 数据入队出队方式
通常采用先进先出(FIFO)的数据存储缓冲机制,即先入队的数据会先从队列中被读取。
也可以配置为后进先出(LIFO)方式,但用得比较少。
2.数据传递方式
采用实际值传递,即将数据拷贝到队列中进行传递,也可以传递指针,在传递较大的数据的
时候采用指针传递。
3.多任务访问
队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息
4.出队、入队阻塞
当任务向一个队列发送消息时,可以指定一个阻塞时间,假设此时当队列已满无法入队。
阻塞时间如果设置为:
- 0:直接返回不会等待;
- 0-port_MAX_DELAY:等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;
- port_MAX_DELAY:死等,一直等到可以入队为止。出队阻塞与入队阻塞类似;
队列相关API函数
1. 创建队列
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,
UBaseType_t uxItemSize);
参数:
- uxQueueLength:队列可同时容纳的最大项目数。
- uxltemSize:存储队列中的每个数据项所需的大小(以字节为单位)。
返回值:
如果队列创建成功,则返回所创建队列的句柄。如果创建队列所需的内存无法分配,则返回NULL。
2.写队列
写队列总共有以下几个函数:
BaseType_t xQueueSend(
QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait
);
参数:
- xQueue:队列的句柄,数据项将发送到此队列。
- pvItemToQueue:待写入数据
- xTicksToWait:阻塞超时时间
返回值:
如果成功写入数据,返回pdTRUE,否则返回errQUEUE_FULL。
3.读队列
读队列总共有以下几个函数:
BaseType_t xQueueReceive(
QueueHandle_t xQueue,
void *pvBuffer,
TickType_t xTicksToWait
);
参数:
- xQueue:待读取的队列
- pvItemToQueue:数据读取缓冲区
- xTicksToWait:阻塞超时时间
返回值:
成功返回pdTRUE,否则返回pdFALSE。
实操
实验需求
创建一个队列,按下KEY1向队列发送数据,按下KEY2向队列读取数据。
cube MX配置
六、二值信号量
什么是信号量?
信号量(SeMaphore),是在多任务环境下使用的一种机制,是可以用来保证两个或多个关
键代码段不被并发调用。
信号量这个名字,我们可以把它拆分来看,信号可以起到通知信号的作用,然后我们的量还
可以用来表示资源的数量,当我们的量只有0和1的时候,它就可以被称作二值信号量,只有
两个状态,当我们的那个量没有限制的时候,它就可以被称作为计数型信号量。
信号量也是队列的一种。|
什么是二值信号量?
二值信号量其实就是一个长度为1,大小为零的队列,只有0和1两种状态,通常情况下,我
们用它来进行互斥访问或任务同步。
互斥访问:比如门钥匙,只有获取到钥匙才可以开门
任务同步:比如我录完视频你才可以看视频
二值信号量相关API函数

- 创建二值信号量
SemaphoreHandle_t xSemaphoreCreateBinary( void)参数:
无
返回值:
成功,返回对应二值信号量的句柄;
失败,返回NULL。
- 释放二值信号量
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore)
参数:
xSemaphore:要释放的信号量句柄
返回值:
成功,返回 pdPASS;
失败,返回errQUEUE_FULL。 - 获取二值信号量
BaseType_t xSemaphoreTake(
SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait
);
参数:
xSemaphore:要获取的信号量句柄
xTicksToWait:超时时间,0表示不超时,portMAX_DELAY表示卡死等待;
返回值:
成功,返回 pdPASS;
失败,返回errQUEUE_FULL。
七、计数型信号量
什么是计数型信号量?
计数型信号量相当于队列长度大于1的队列,因此计数型信号量能够容纳多个资源,这在计
数型信号量被创建的时候确定的。

计数型信号量相关API函数

计数型信号量的释放和获取与二值信号量完全相同!
SemaphoreHandle_t xSemaphoreCreateCounting(
UBaseType_tuxMaxCount,
UBaseType_tuxInitialCount
);
参数:
uxMaxCount:可以达到的最大计数值
uxInitialCount:创建信号量时分配给信号量的计数值
返回值:
成功,返回对应计数型信号量的句柄;
失败,返回NULL。
八、互斥量
什么是互斥量?
在多数情况下,互斥型信号量和二值型信号量非常相似,但是从功能上二值型信号量用于同
步,而互斥型信号量用于资源保护。
互斥型信号量和二值型信号量还有一个最大的区别,互斥型信号量可以有效解决优先级反转
现象。
什么是优先级翻转?

以上图为例,系统中有3个不同优先级的任务H/M/L,最高优先级任务H和最低优先级任务L
通过信号量机制,共享资源。目前任务L占有资源,锁定了信号量,TaskH运行后将被阻
塞,直到TaskL释放信号量后,TaskH才能够退出阻塞状态继续运行。但是TaskH在等待
TaskL释放信号量的过程中,中等优先级任务M抢占了任务L,从而延迟了信号量的释放时
间,导致TaskH阻塞了更长时间,这种现象称为优先级倒置或反转。
优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时,如果此时有个高优先级
的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先
级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影
响。
互斥量相关API函数
互斥信号量不能用于中断服务函数中!
SemaphoreHandle_t xSemaphoreCreateMutex(void)
参数:
无
返回值:
成功,返回对应互斥量的句柄;
失败,返回NULL。
九、事件标志组
什么是事件标志组?
事件标志位:表明某个事件是否发生,联想:全局变量flag。通常按位表示,每一个位表示
一个事件(高8位不算)
事件标志组是一组事件标志位的集合,可以简单的理解事件标志组,就是一个整数。
事件标志组本质是一个16位或32位无符号的数据类型EventBits_t,由configUSE_16_BIT_TICKS决定。
虽然使用了32位无符号的数据类型变量来存储事件标志,但其中的高8位用作存储事件标志
组的控制信息,低24位用作存储事件标志,所以说一个事件组最多可以存储24个事件标志!
事件标志组相关API函数

- 创建事件标志位
EventGroupHandle_t xEventGroupCreate(void );参数:
无
返回值:
成功,返回对应事件标志组的句柄;
失败,返回NULL。 - 设置事件标志位
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_tuxBitsToSet );参数:
xEventGroup:对应事件组句柄。
uxBitsToSet:指定要在事件组中设置的一个或多个位的按位值。
返回值:
设置之后事件组中的事件标志位值。 - 清除事件标志位
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear参数:
xEventGroup:对应事件组句柄。
uxBitsToClear:指定要在事件组中清除的一个或多个位的按位值。
返回值:
清零之前事件组中事件标志位的值。 - 等待事件标志位
EventBits_t xEventGroupwaitBits const EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait );参数:
xEventGroup:对应的事件标志组句柄
uxBitsToWaitFor:指定事件组中要等待的一个或多个事件位的按位值
xClearOnExit:pdTRUE——清除对应事件位,pdFALSE——不清除
xWaitForAlIBits:pdTRUE——所有等待事件位全为1(逻辑与),pdFALSE—等待的事件
位有一个为1(逻辑或)
xTicksToWait:超时
返回值:
等待的事件标志位值:等待事件标志位成功,返回等待到的事件标志位
其他值:等待事件标志位失败,返回事件组中的事件标志位
十、任务通知
什么十任务通知?
FreeRTOS从版本V8.2.0开始提供任务通知这个功能,每个任务都有一个32位的通知值。
按照FreeRTOS官方的说法,使用消息通知比通过二进制信号量方式解除阻塞任务快45%,并且更加省内存(无需创建队列)。
在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件标志组,可以替代长度
为1的队列(可以保存一个32位整数或指针值),并且任务通知速度更快、使用的RAM更
少!
任务通知值得更新方式
FreeRTOS提供以下几种方式发送通知给任务:
- ·发送消息给任务,如果有通知未读,不覆盖通知值
- ·发送消息给任务,直接覆盖通知值
- ·发送消息给任务,设置通知值的一个或者多个位
- ·发送消息给任务,递增通知值
通过对以上方式的合理使用,可以在一定场合下替代原本的队列、信号量、事件标志组等。
任务通知的优势和劣势
任务通知的优势
- 使用任务通知向任务发送事件或数据,比使用队列、事件标志组或信号量快得多。
- 使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。
任务通知的劣势
- 只有任务可以等待通知,中断服务函数中不可以,因为中断没有TCB(在任务中开辟一个空间)。
- 通知只能一对一,因为通知必须指定任务。
- 等待通知的任务可以被阻塞,但是发送消息的任务,任何情况下都不会被阻塞等待。
- 任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保持一个数据。
任务通知相关API函数
- 发送通知
BaseType_t xTaskNotify(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction );
参数:
xTaskToNotify:需要接收通知的任务句柄;
ulValue:用于更新接收任务通知值,具体如何更新由形参eAction决定;
eAction:一个枚举,代表如何使用任务通知的值;
返回值:
如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回
pdFALSE,而其他情况均返回pdPASS。BaseType_t xTaskNotifyAndQuery(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotifyValue );参数:
xTaskToNotify:需要接收通知的任务句柄;
ulValue:用于更新接收任务通知值,具体如何更新由形参eAction决定;
eAction:一个枚举,代表如何使用任务通知的值;
pulPreviousNotifyValue:对象任务的上一个任务通知值,如果为NULL,则不需要回传,这个时候就等价于函数xTaskNotify( )。
返回值:
如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回
pdFALSE,而其他情况均返回pdPASS。BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify);参数:
xTaskToNotify:接收通知的任务句柄,并让其首身的任务通知值加1。
返回值:
总是返回pdPASS。 - 等待通知
等待通知API函数只能用在任务,不可应用于中断中!
uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
参数:
xClearCountOnExit:指定在成功接收通知后,将通知值清零或减1,pdTRUE:把通知值清
零(二值信号量);pdFALSE:把通知值减一(计数型信号量);
xTicksToWait:阻塞等待任务通知值的最大时间;
返回值:
0:接收失败
非0:接收成功,返回任务通知的通知值
ulBitsToClearOnEntry:函数执行前清零任务通知值那些位。BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait);
ulBitsToClearOnExit:表示在函数退出前,清零任务通知值那些位,在清0前,接收到的任
务通知值会先被保存到形参*pulNotificationValue中。
pulNotificationValue:用于保存接收到的任务通知值。如果不需要使用,则设置为NULL
即可。
xTicksToWait:等待消息通知的最大等待时间。
十一、延时函数
什么是延时函数?
在 FreeRTOS 中,常用的延时函数主要有两个:vTaskDelay() 和 vTaskDelayUntil(),此外在使用 CMSIS-RTOS 封装层时还会用到 osDelay()(本质是对vTaskDelay()的封装)。这些函数的核心作用是让当前任务进入阻塞态(释放 CPU 资源,允许其他任务运行),等待指定时间后再进入就绪态。
延时函数分类
相对延时:vTaskDelay
绝对延时:vTaskDelayUntil

vTaskDelay与 HAL_Delay 的区别


TaskDelay作用是让任务阻塞,任务阻塞后,RTOS系统调用其它处于就绪状态的优先级最
高的任务来执行。
HAL_Delay一直不停的调用获取系统时间的函数,直到指定的时间流逝然后退出,故其占用
了全部CPU时间。
十二、软件定时器
什么是定时器?
简单可以理解为闹钟,到达指定一段时间后,就会响铃。
TSTM32芯片自带硬件定时器,精度较高,达到定时时间后会触发中断,也可以生成PWM、
输入捕获、输出比较,等等,功能强大,但是由于硬件的限制,个数有限。
软件定时器也可以实现定时功能,达到定时时间后可调用回调函数,可以在回调函数里处理
信息。
软件定时器优缺点
优点:
1.简单、成本低;
2.只要内存足够,可创建多个;
缺点:
精度较低,容易受中断影响。在大多数情况下够用,但对于精度要求比较高的场合不建议使
用。
软件定时器原理
定时器是一个可选的、不属于FreeRTOS内核的功能,它是由定时器服务任务来提供的。
在调用函数vTaskStartScheduler()开启任务调度器的时候,会创建一个用于管理软件定时器
的任务,这个任务就叫做软件定时器服务任务。
1.负责软件定时器超时的逻辑判断
2.调用超时软件定时器的超时回调函数
3.处理软件定时器命令队列
FreeRTOS提供了很多定时器有关的API函数,这些API函数大多都使用FreeRTOS的队列发送
命令给定时器服务任务。这个队列叫做定时器命令队列。定时器命令队列是提供给FreeRTOS的软件定时器使用的,用户不能直接访问!

软件定时器相关配置
软件定时器有一个定时器服务任务和定时器命令队列,这两个东西肯定是要配置的,相关的
配置也是放到文件FreeRTOSConfig.h中的,涉及到的配置如下:
1、configUSE_TIMERS
如果要使用软件定时器的话宏configUSE_TIMERS一定要设置为1,当设置为1的话定时器服
务任务就会在启动FreeRTOS调度器的时候自动创建。
2、configTIMER_TASK_PRIORITY
设置软件定时器服务任务的任务优先级,可以为0~(configMAX_PRIORITIES-1)。优先级一定
要根据实际的应用要求来设置。如果定时器服务任务的优先级设置的高的话,定时器命令队
列中的命令和定时器回调函数就会及时的得到处理。
3、configTIMER_QUEUE_LENGTH
此宏用来设置定时器命令队列的队列长度。
4、configTIMER_TASK_STACK_DEPTH
此宏用来设置定时器服务任务的任务堆栈大小。
单次定时器和周期定时器
单次定时器:只超时一次,调用一次回调函数。可手动再开启定时器;
周期定时器:多次超时,多次调用回调函数。
软件定时器相关API函数

1.创建软件定时器
TimerHandle_t xTimerCreate
(const char * const pcTimerName,
const TickType_t xTimerPeriod,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );
参数:
pcTimerName:软件定时器名称
xTimerPeriodInTicks:定时超时时间,单位:系统时钟节拍。宏pdMS_TO_TICKSO可用于
将以毫秒为单位指定的时间转换为以tick为单位指定的时间。
uxAutoReload:定时器模式,pdTRUE:周期定时器,pdFALSE:单次定时器
pvTimerID:软件定时器ID,用于多个软件定时器公用一个超时回调函数
pxCallbackFunction:软件定时器超时回调函数
返回值:
成功:定时器句柄
失败:NULL
2.开启软件定时器
BaseType_t xTimerStart (TimerHandle_t xTimer,
TickType_t xBlockTime );
参数:
xTimer:待开启的软件定时器的句柄
xTickToWait:发送命令到软件定时器命令队列的最大等待时间
返回值:
pdPASS:开启成功
pdFAIL:开启失败
3.停止软件定时器
BaseType_t xTimerStop(TimerHandle_t xTimer,
TickType_t xBlockTime );
参数与返回值同上。
4.复位软件定时器
BaseType_t xTimerReset(TimerHandle_t xTimer,
TickType_t xBlockTime );
参数与返回值同上。
该功能将使软件定时器的重新开启定时,复位后的软件定时器以复位时的时刻作为开启时刻
重新定时。
5.更改软件定时器定时时间
BaseType_t xTimerChangePeriod(TimerHandle_t xTimer,
TickType_t xNewPeriod,
TickType_t xBlockTime);
xNewPeriod:新的定时超时时间,单位:系统时钟节拍。
其余参数与返回值同上。

十三、中断管理
中断定义
请参考51及STM32中断相关课程。
中断优先级
任何中断的优先级都大于任务!
在我们的操作系统,中断同样是具有优先级的,并且我们也可以设置它的优先级,但是他的
优先级并不是从0~15,默认情况下它是从5~15,0~4这5个中断优先级不是FreeRTOS
控制的(5是取决于configMAX_SYSCALL_INTERRUPT_PRIORITY)。
相关注意
1.在中断中必需使用中断相关的函数;
2.中断服务函数运行时间越短越好。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)