??本笔记基于B站正点原子的视频讲解,和个人的理解应用情况。应该适合用来回忆复习FreeRTOS的基本内容,避免在应用时突然忘了某个知识点要查很久。还有就是B站正点原子的讲解视频主要是对FreeRTOS的移植应用讲解,而我重点只听应用部分,因为一般都是基于STM32CubeMX直接生成FreeRTOS,也用每次都移植了,所以下面笔记内容关于移植部分的是没有的。有时间会在继续在实践中,补充补充笔记,给每个知识点在加上基本案例之类的。
P7快速的跳过了,因为是配置他家的FreeRTOS配置 。
官网下载源码
https://freertos.org/zh-cn-cmn-s/index.html
下载得到包解压得到的目录及意思
名称 | 描述 |
---|---|
FreeRTOS | FreeRTOS内核 |
FreeRTOS-Plus | FreeRTOS组件 |
tools | 工具 |
GitHub-FreeRTOS-Home | FreeRTOS的GitHub仓库链接 |
Quick_Start_Guide | 快速入门指南官方文档链接 |
Upgrading-toFreeRTOS-XXX | 升级到指定FreeRTOS版本官方文档链接 |
History.txt | FreeRTOS历史更新记录 |
其他 | 其他 |
打开FreeRTOS文件夹
名称 | 描述 |
---|---|
Demo | FreeRTOS演示例程 |
License | FreeRTOS相关许可 |
Source | FreeRTOS源码 |
Test | 公用以及移植层测试代码 |
略。。。
创建首任务,在里面中开启临界区创建各分任务。(这里可能讲的是先开启调度器在创建任务的情况,不过一般都是先创建任务才开始调度吧)
FreeRTOS的基本使用
//任务函数
void task0(void *pvParameters)
{
for(;;)
{
//...
}
}
// 任务函数,任务名称,堆栈大小,传递参数,优先级,任务句柄。
xTaskCreate(vTask1,"LED1",128,NULL,1,NULL);
xTaskCreate(vTask2,"LED2",128,NULL,1,NULL);
//启动任务调度器
vTaskStartScheduler();
功能函数
vTaskDelay(500); //500个任务节拍
vTaskDelay(pdMS_TO_TICKS(500)); // 将500毫秒转换为时钟节拍并进行延迟
xTaskCreate(); //动态方式创建任务
xTaskCreateStatic(); //静态方式创建任务
vTaskDelete(); //删除任务
//案例
/*
xTaskCreate(Task0_RunLed,"Task0",128,NULL,1,NULL);
*/
vTaskSuspend(); //挂起任务
vTaskResume(); //恢复被挂起任务
xTaskResumeFromISR(); //在中断中恢复被挂起的任务
//中断恢复要点
//定义结构
BaseType_t xYieldRequired;
xYieldRequired = xTaskResumeFromISR();
if(xYieldRequired = pdTRUE)
{
portYIELD_FROM_ISR(xYieldRequired);
}
临界保护区段的代码内,关闭所有FreeRTOS所控制的中断
taskENTER_CRITICAL(); //任务级进入临界段
taskEXIT_CRITICAL(); //任务级退出临界段
taskENTER_CRITICAL_FROM_ISR(); //中断级进入临界段
taskEXIT_CRITICAL_FROM_ISR(); //中断级退出临界段
//任务级临界格式:
/*
taskENTER_CRITICAL();
内容
taskEXIT_CRITICAL();
*/
//中断级临界格式:
/*
uint32_t save_status;
save_status = taskENTER_CRITICAL_FROM_ISR();
内容
taskEXIT_CRITICAL_FROM_ISR(save_status);
*/
任务调度器的挂起,只是给任务的切换带来停止,中断还会照常。
vTaskSuspendAll(); //挂起任务调度器
xTaskResumeAll(); //恢复任务调度器
//使用格式示例:
/*
vTaskSuspendAll();
内容
xTaskResumeAll();
*/
vListInitialise(); //初始化列表
vListInitialiseltem(); //初始化列表项
vListInsertEnd(); //列表末尾插入列表项
vListInsert(); //列表插入列表项
uxListRemove(); //列表移除列表项
vTaskDelay(1000); //延时1000个节拍
vTaskDelayUntil(1000); //延时1000给节拍
vTaskDelay(pdMS_TO_TICKS(1000)); //延时1000ms
使用事项
注意导入队列库
#include "queue.h"
可以利用队列的等读取待函数,从就绪态变成阻塞态,卡取任务状态,如等待按钮按下之类的。
创建队列
xQueueCreate(); //动态方式创建队列
xQueueCreateStatic(); //静态方式创建队列
队列写入
//往队列的尾部写入消息
xQueueSendToBack(); //同 xQueueSend()
xQueueSendToFront(); //往队列的头部写入消息
xQueueOverwrite(); //覆写队列消息(只用于队列长度为1的情况)
xQueueSendFromISR(); //在中断中往队列的尾部写入消息
xQueueSendToBackFromISR(); //同xQueueSendFromISR()
xQueueSendToFrontFromISR(); //在中断中往队列的头部写入消息
xQueueOverwriteFromISR(); //在中断中覆写队列消息(只用于队列长度为1的情况)
队列读取
xQueueReceive(); //从队列头部读取消息,并删除消息
xQueuePeek(); //从队列头部读取消息
xQueueReceiveFromISR(); //在中断中从队列头部读取消息,并删除消息
xQueuePeekFromISR(); //在中断中从队列头部读取消息
应用案例
// 定义队列变量
QueueHandle_t xQueue;
简介:信号量是一种解决同步问题的机制,可以实现对共享资源的有序访问。
特点:
二值信号量
。如果最大值不为1,那它就是计数型信号量
。使用流程:创建信号量 --> 获取信号量 --> 释放信号量
??二值型信号量通常用于任务同步或互斥访问,与互斥信号量比较类似,但是二值信号量有可能会导致优先级翻转的问题,所以二值信号量更适合用于同步!
??计数型信号量通常用于事件计数或资源管理,事件计数的数值一般从0开始释放获取,资源管理一般从设定的最大值开始释放获取。
??互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中!
使用事项:
注意导入信号量库
#include "semphr.h"
当有两个不同优先级的任务获取二信号量堵塞时,一个中级任务在运行,在一定条件下,就可能引发优先级翻转(高优先级任务,反而是最慢运行的),会有不可控问题。在这种互斥问题中引入互斥信号量,能缓解优化这种问题。
互斥信号量不能用于中断服务中
,因为中断服务函数不能因为互斥信号的堵塞事件进入堵塞态。而且互斥信号量有任务优先级继承的机制,但是中断不是任务。
注意:创建互斥信号量时,会主动释放一次信号量
// 二值型
xSemaphoreCreateBinary(); //使用动态方式创建二值信号量
xSemaphoreCreateBinaryStatic(); //使用静态方式创建二值信号量
// 计数型
xSemaphoreCreateCounting(); //使用动态方法创建计数型信号量
xSemaphoreCreateCountingStatic(); //使用静态方法创建计数型信号量
uxSemaphoreGetCount(); //获取信号量的计数值
// 互斥量
xSemaphoreCreateMutex(); //使用动态方法创建互斥信号量。
xSemaphoreCreateMutexStatic(); //使用静态方法创建互斥信号量。
// 通用(互斥量不能用于中断服务)
xSemaphoreGive(); //释放信号量
xSemaphoreGiveFromISR(); //在中断中释放信号量
xSemaphoreTake(); //获取信号量
xSemaphoreTakeFromISR(); //在中断中获取信号量
简介:事件标志位,用一个位,来表示事件是否发生。事件标志组是一组事件标志位的集合,可以简单理解事件标志组,就是一个整数。
特点:
事件标志组与队列、信号量的区别?
功能 | 唤醒对象 | 事件清除 |
---|---|---|
队列、信号量 | 事件发生时,只会唤醒一个任务 | 是消耗型的资源,队列的数据被读走就没了;信号量被获取后就减少了 |
事件标志组 | 事件发生时,会唤醒所有符合条件的任务,可以理解为“广播”的作用 | 被唤醒的任务有两个选择,可以让事件保留不动,也可以清除事件 |
使用事项:
注意导入信号量库
#include "event_groups.h"
可以根据宏定义configUSE_16_BIT_TICKS来配置,事件标志组的16位或32位无符号的数据类型。(STM32中这个变量类型为32位的)
xEventGroupCreate(); //使用动态方式创建事件标志组
xEventGroupCreateStstic(); //使用静态方式创建事件标志组
xEventGroupClearBits(); //清零事件标志位
xEventGroupClearBitsFromISR(); //在中断中清零事件标志位
xEventGroupWaitBits(); //设置事件标志位
xEventGroupSetBitsFromISR(); //在中断中设置事件标志位
xEventGroupWaitBits(); //等待事件标志位
xEventGroupSync(); //设置事件标志位,并等待事件标志位
简介:任务通知,用来通知任务,任务控制块中的结构体成员变量 ulNottifiedValue就是这个通知值。
特点:
不消耗额外内存,可以模拟队列,信号量, 事件标志组的使用方式。
使用队列,信号量, 事件标志组时都需要另外创建一个结构体,通过中间的结构体进行间接通信。使用任务通知时,任务结构体TCB中就包含了内部对象,可以直接接收别人发过来的“通知”。
发送通知API函数可以用于任务和中断服务函数中;接收通知API函数只能用在任务中。
任务通知值的更新方式:
优势:
劣势:
// 通知方式
xTaskNotify(); //发送通知,带有通知值
xTaskNotifyAndQuery(); //发送通知,带有通知值,并且保留接收任务的原通知值
xTaskNotifyGive(); //发送通知,不带通知值
xTaskNotifyFromISR(); //在中断中发送任务通知
xTaskNotifyAndQueryFromISR(); //在中断中发送任务通知
vTaskNotifyGiveFromISR(); //在中断中发送任务通知
// 接受方式
ulTaskNotifyTake(); //获取任务通知,可以设置在退出此函数的时候将任务通知值清零戴者减一。当任务通知用作二值信号量或者计数信号量的时候,使用此函数来获取信号量。
(); //获取任务通知,比 ulTaskNotifyTak()更为复杂,可获取通知值和清除通知值的指定位。当任务通知用作于事件标志组或队列时,使用此函数来获取。
简介:
软件定时器优缺点?
优点:
硬件定时器数量有限,而软件定时器理论上只需有足够内存,就可以创建多个;
使用简单、成本
缺点:
FreeRTOS软件定时器特点
注意
:软件定时器的超时回调函数是由软件定时器服务任务调用的,软件定时器的超时回调函数本身不是任务,因此不能在该回调函数中使用可能会导致任务阻塞的API函数。软件定时器服务任务
:在调用函数vTaskStartScheduler()开启任务调度器的时候,会创建一个用于管理软件定时器的任务,这个任务就叫做软件定时器服务任务。软件定时器服务任务作用
负责软件定时器超时的逻辑判断
调用超时软件定时器的超时回调函数
处理软件定时器命令队列
软件定时器的命令队列
??FreeRTOS提供了许多软件定时器相关的API函数,这些API函数大多都是往定时器的队列中写入消息(发送命令),这个队列叫做软件定时器命令队列,是提供给FreeRTOS中的软件定时器使用的,用户是不能直接访问的。
软件定时器的相关配置
当FreeRTOS的配置项configUSE_TIMERS设置为1,在启动任务调度器时,会自动创建软件定时器的服务/守护任务prvTimerTask();
软件定时器服务任务的优先级为 configTIMER_TASK_PRIORITY = 31;
注意:软件定时器的超时回调函数是在软件定时器服务任务中被调用的,服务任务不是专为某个定时器服务的,它还要处理其他定时器。
所以,定时器的回调函数不要影响其他"人” :
1、回调函数要尽快实行,不能进入阻塞状态,即不能调用那些会阻塞任务的API函数,如: vTaskDelay();
2、访问队列或者信号量的非零阻塞时间的API函数也不能调用。
软件定时器共有两种状态:
休眠态:软件定时器可以通过其句柄被引用,但因为没有运行,所以其定时超时回调函数不会被执行
运行态:运行态的定时器,当指定时间到达之后,它的超时回调函数会被调用
注意
:新创建的软件定时器处于休眠状态,也就是未运行的!
状态的转化依靠发送命令队列。
FreeRTOS提供了两种软件定时器:
函数大致分成五类:创建,开启,停止,复位,更改超时时间。
xTimerCreate(); //动态方式创建软件定时器
xTimerCreateStatic(); //静态方式创建软件定时器
xTimerStart(); //开启软件定时器定时
xTimerStartFromISR(); //在中断中开启软件定时器定时
xTimerStop(); //停止软件定时器定时
xTimerStopFromISR(); //在中断中停止软件定时器定时
xTimerReset(); //复位软件定时器定时
xTimerResetFromISR(); //在中断中复位软件定时器定时
xTimerChangePeriod(); //更改软件定时器的定时超时时间
xTimerChangePeriodFromISR(); //在中断中更改定时超时时间
STM32cubeMX中需要在软件中先配置,否正编译报错
介绍:很多应用场合对于功耗的要求很严格,比如可穿戴低功耗产品,物联网低功耗产品。且mcu一般也有对应的低功耗模式,而Tickless则是FreeRTOS中,进入低功耗模式的存在。
Tickless低功耗模式的本质是通过调用指令 WFI 实现睡眠模式!
Tickless模式的设计思想, 在一个任务运行时间统计实验中,可以看出,在整个系统的运行过程中,其实大部分时间是在执行空闲任务(是在系统中的所有其它任务都阻塞或被挂起时才运行的),所以为了达到降低功耗(可以降低功耗,又不影响系统运行),就可以在本该空闲任务执行的期间,让MCU进入相应的低功耗模式;当其他任务准备运行的时候,MCU退出低功耗模式。
难点:1、进入低功耗之后,多久唤醒?也就是下一个要运行的任务如何被准确唤醒。2、任何中断均可唤醒MCU,若滴答定时器频繁中断则会影响低功耗的效果?将滴答定时器的中断周期修改为低功耗运行时间。退出低功耗后,需补上系统时钟节拍数。
值得庆幸的是: FreeRTOS的低功耗Tickless模式机制已经处理好了这些难点。
Tickless模式相关配置项(宏定义)
configUSE_TICKLESS_IDLE
此宏用于使能低功耗 Tickless 模式(0:为不打开 1:启动系统配置好的方案 2:用户自定义方案)
configEXPECTED_IDLE_TIME_BEFORE_SLEEP
此宏用于定义系统进入相应低功耗模式的最短时长(一般为2,也就是2ms以上)
configPRE_SLEEP_PROCESSING (x)
此宏用于定义需要在系统进入低功耗模式前执行的事务,如:进入低功耗前关闭外设时钟,以达到降低功耗的目的(宏函数,需要用户编写,可以在FreeRTOSConfig.h中,编写好宏声明)
configPOSR_SLEEP_PROCESSING(x)
此宏用于定义需要在系统退出低功耗模式后执行的事务,如:退出低功耗后开启之前关闭的外设时钟,以使系统能够正常运行(宏函数,需要用户编写,可以在FreeRTOSConfig.h中,编写好宏声明)
STM32cubeMX中需要在软件中先配置,否正编译报错
疑问:为啥不用标准的C库自带的内存管理算法?
因为标准C库的动态内存管理方法有如下几个缺点:
FreeRTOS的5种动态内存管理 算法的优缺点
算法 | 优点 | 缺点 |
---|---|---|
heap_1 | 分配简单,时间确定 | 只允许申请内存,不允许释放内存 |
heap_2 | 允许申请和释放内存 | 不能合并相邻的空闲内存块会产生碎片、时间不定 |
heap_3 | 直接调用C库函数malloc()和free() ,简单 | 速度慢、时间不定 |
heap_4 | 相邻空闲内存可合并,减少内存碎片的产生 | 时间不定 |
heap_5 | 能够管理多个非连续内存区域的heap_4 | 时间不定 |
内存管理相关函数
void*pvPortMalloc( size_t xWantedSize); //申请内存
void vPortFree(void*pv); //释放内存
size_t xPortGetFreeHeapSize(void); //获取当前空闲内存的大小