学习视频为【正点原子】手把手教你学FreeRTOS实时系统
文章目录
- RTOS 入门
- 裸机
- RTOS
- FreeRTOS
- 任务调度方式
- 任务状态
- FreeRTOS 移植
- 系统配置文件
- API函数
- 任务创建和删除
- 动态方式创建任务
- 静态方式创建任务
- 删除任务
- 任务挂起和恢复函数
- 调度器挂起与恢复
- 临界区
- 任务调度器的挂起和恢复
- 中断管理
- 列表和列表项
- 简介
- 列表相关API函数
- 任务调度
- 时间片调度
- 任务状态查询
- 任务时间统计
- 延时函数
- FreeRTOS 消息队列
- 队列
- API 函数
- 信号量
- 二值信号量
- 事件标志组
- 任务通知
- 简介
- 软件定时器
- C语言知识补充
- 堆
- 栈
- 指针函数与函数指针
- define 和 typedef
- 句柄和指针
RTOS 入门
裸机
裸机:前后台系统,前台系统指中断服务函数,后台系统指大循环。
裸机中断:是一瞬间的事情,不能处理太多问题。可以用来设置标志位,把执行函数放在主函数里面。
RTOS
1、高优先任务只有处于阻塞状态,才会让出CPU资源,来运行低优先级任务。
2、中断可以打断任意任务,因为中断是为硬件实现,任务优先级为软件实现。
任务优先级数字越大,优先级越高;而中断优先级数字越小,优先级越高
FreeRTOS
FreeRTOS官网
任务调度方式
抢占式调度(不同优先级)
时间片调度(同优先级)
在时间片调度中,一个时间片就是SysTick中断周期;没有用完的时间片不会再使
用。
协程式调度(逐渐淘汰)
任务状态
运行态(同一时间仅有一个任务处于运行态)
就绪态
阻塞态
挂起态 vTaskSuspend() 挂起, vTaskResume() 进入就绪
就绪列表 pxReadyTaskLists[x],其中x代表任务优先级数值
阻塞列表 pxDelayedTaskList
挂起列表 xSuspendTaskList
- 新创建任务直接挂载到就绪列表中
- 32位变量,当某个位置一时,代表所对应的优先级就绪列表有任务
FreeRTOS 移植
移植步骤细节根据《FreeRTOS开发指南》的第二章操作
Source文件夹
portable 文件夹是沟通起软件和硬件的桥梁
系统配置文件
《FreeRTOS开发指南》的第三章介绍
include 配置RTOS中可选的API函数
config 完成FreeRTOS的功能配置和裁剪
其他配置项 PendSV 宏定义、SVC宏定义
API函数
任务创建和删除
动态方式创建任务
xTaskCreate() 自动分配内存
- uxTaskGetStackHighWaterMark() 用于获取指定任务的任务栈的历史剩余最小值
- 任务切换是在中断中进行。可使用临界区保护 taskENTER_CRITICAL() 那些不想被打断的程序段。
- stm32 栈向下生长,由高地址向低地址;堆向上生长,由低地址向高地址。
任务控制块
任务栈栈顶,与任务切换有关
每个任务都有属于自己的任务控制块,类似身份证
静态方式创建任务
xTaskCreateStatic()
删除任务
vTaskDelete()
- 传入参数NULL,代表删除任务自身
- 空闲任务会负责释放被删除任务中由系统分配的内存(静态任务内存需由用户删除前提前释放)
任务挂起和恢复函数
API函数 | 描述 |
---|---|
vTaskSuspend() | 挂起任务 |
vTaskResume() | 恢复被挂起任务 |
xTaskResumeFromISR() | 在中断中恢复被挂起的任务 |
xTaskResumeFromISR()函数官网例程
宏:INCLUDE_vTaskSuspend 须置为1
带 FromISR 后缀是在 中断函数 中专用的API函数。
使用该函数需要把 INCLUDE_xTaskResumeFromISR 置为1
中断服务程序中要调用freeRTOS的API函数,则中断优先级不能高于Free RTOS所管理的最高优先级
FreeRTOS 能管理的中断优先级为 5-15
官网解释
- 如果您使用 STM32 和 STM32 驱动器库, 请通过 调用 NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 ) 来确保所有优先级位都被指定为抢占优先级位,这一步需要 在启动 RTOS 前完成。
调度器挂起与恢复
临界区
临界段(临界区):必须完整运行,不能被打断的代码段
1、外设:IIC、SPI等需严格按照时序初始化的外设
2、系统或用户自身需求
1、可以嵌套使用 2、尽量保证临界段代码耗时短
临界区是直接屏蔽了中断,系统任务调度靠中断,ISR也靠中断
中断和任务调度可以打断当前程序的运行
任务调度器的挂起和恢复
挂起任务调度器,调用此函数不需要关闭中断。
仅仅是为了防止任务之间的资源争夺,中断照样可以直接响应。
函数 | 描述 |
---|---|
vTaskSuspendAll() | 挂起任务调度器,uxScheduleSuspend加一 |
xTaskResumeAll() | 恢复任务调度器,uxScheduleSuspend减一 |
中断管理
中断:CPU打断正常运行的程序,转而去处理紧急的事件
关闭中断 portDISABLE_INTERRUPTS()
开启中断 portENABLE_INTERRUPTS()
中断优先级数值越小越优先,任务优先级数值越大越优先
调用 任务延时 vTaskDealy()会进入临界区,而在退出临界区代码时会开启中断
PendSV 和 SysTick 设置最低优先级。保证系统任务切换不会阻塞系统其他中断的响应
FreeRTOS的任务中 只能管理中断优先级在5-15的中断服务
列表和列表项
列表是 FreeRTOS 中的一个数据结构,列表被用来跟踪FreeRTOS中的任务。是一个双向环形链表。
列表项就是存放在列表中的项目
简介
列表结构体
uxNumberOflterms 不包含 xListEnd末尾列表项
列表项结构体
xItemValue 多用于按升序对列表中的列表项进行排序
迷你列表项
仅用于标记列表的末尾 和 挂载其他插入列表中的列表项
列表和列表项关系
列表相关API函数
函数 | 描述 |
---|---|
vListInitialise() | 初始化列表 |
vListInitialiseItem() | 初始化列表项 |
vListInsertEnd() | 列表末尾插入列表项 |
vListInsert() | 列表插入列表项 |
uxListRemove() | 列表移除列表项 |
vListInsertEnd() 插入到pxIndex指针所指向的列表项前面
参考文档《FreeRTOS开发指南》第七章
任务调度
提前阅读《CortexM3权威指南(中文版)》与《FreeRTOS开发指南》第八章
函数 | 描述 |
---|---|
vTaskStartScheduler() | 启动任务调度器 |
xPortStartScheduler() | 启动任务调度器中与硬件架构相关的配置部分,以及启动第一个任务 |
prvStartFirstTask() | 初始化启动第一个任务前的环境,主要是重新设置MSP指针,并使能全局中断 |
vPortSVCHandler() | SVC 中断服务函数 |
在启动第一个任务前关闭中断,启动第一个任务时开启中断
任务切换的本质:CPU寄存器的切换
上下文切换:对任务A保存现场,对任务B恢复现场
任务切换的过程在PendSV中断服务函数
里边完成
进程堆栈(psp)中断以外所使用的堆栈,所以保存的就是任务栈;主堆栈指针(msp)在中断内使用
时间片调度
同等优先级任务轮流享有相同的CPU时间(可设置),叫时间片,在FreeRTOS中,一个时间片就等于SysTick中断周期
宏configUSE_TIME_SLICING 和 configUSE_PREEMPTION 须置1
没有用完的时间片不会再使用,下次执行该任务还是按照一个时间片的时钟节拍运行
任务状态查询
参考《FreeRTOS 开发指南》第11章——FreeRTOS其他任务API函数
任务优先级范围为 0~31
task_num = uxTaskGetNumber0Tasks();
TaskStatus_t* status_array = 0;
/* 所需申请空间 = 任务数量*结构体大小
@param1: 指向TaskStatus_t结构体数组首地址
@param2: 接收信息的数组大小
@param3: 系统总运行时间,为NULL则省略总运行时间值
返回获取信息的任务数量
*/
status_array = mymalloc(SRAMIN, (sizeof(TaskStatus_t) * task_num));
task_num2 = uxTaskGetSystemState(status_array, task_num, NULL);
任务时间统计
滴答定时器: 用来为系统提供时钟节拍
时基定时器: 用于统计任务运行时间
使用流程
1、configGENERATE_RUN_TIME_STATES 置 1
2、configUSE_STATES_FORMATTING_FUNCTIONS 置1
2.1、portCONFIGURE_TIMER_FOR_RUNTIME_STATE() :用于初始化配置任务运行时间统计的时基定时器
时基定时器的计时精度需高于系统时钟节拍精度的10至100倍
2.2、 portGET_RUN_TIME_COUNTER_VALUE() :用于获取该功能时基定时器计数的计数值
延时函数
函数 | 描述 |
---|---|
vTaskDelay() | 相对延时 |
xTaskDelayUntil() | 绝对延时 |
相对延时:每次延时都是从执行函数 vTaskDelay() 开始,直到延时指定的时间结束
绝对延时:将整个任务的运行周期看成一个整体,适用于需要按照一定频率运行的任务
绝对延时适用于需要按照一定频率运行的任务
FreeRTOS 消息队列
队列
队列是任务到任务、任务到中断、中断到任务 数据交流的一种机制(消息传递)
队列中的每一个数据叫做“队列项目”,队列能够存储“队列项目”的最大数量称为队列的长度
全局变量的弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损
写/读队列时 关闭中断,做好保护,防止多任务同时访问冲突
API 函数
函数 | 描述 |
---|---|
xQueueCreate() | 动态方式创建队列 |
xQueueCreateStatic() | 静态方式创建 |
发送
接收
信号量
信号量是一种解决同步问题的机制,可以实现对共享资源的有序访问
二值信号量
本质时一个队列长度为1的队列,该队列就只有空和满两种情况。
通常用于互斥访问或任务同步,但有可能会导致优先级翻转的问题
事件标志组
事件标志位:用一个位,表示事件是否发生
事件标志组就是一组事件标志位的集合
值为1,表示事件发生了;值为0,表示事件未发生
可以等待某一位成立,或者等待多位同时成立
任意任务或中断都可以读写这些位
同步函数 xEvenGroupSync()
任务通知
简介
任务通知:用来通知任务,任务控制块中的结构体成员变量 ulNotifiedValue 就是这个通知值。
任务通知的劣势:
1、无法发送数据给ISR
2、无法广播给多个任务,只能被指定的一个任务接收并处理
3、无法缓存多个数据
4、发送受阻不支持阻塞
任务控制块TCB,里边有两个结构体成员变量:
uint32_t ulNotifiedValue 用来表示通知值
uint8_t ucNotifyState 用来表示通知状态
注意:发送通知API函数可以用于任务和中断服务函数中;接收通知API函数只能用于任务中
发送通知相关API函数
软件定时器
硬件定时器:在中断服务函数中处理信息
软件定时器:在回调函数中处理信息
**注意:**软件定时器的超时回调函数是由软件定时器服务任务
调用的,软件定时器的超时回调函数本身不是任务,因此不能在该回调函数中使用可能会导致任务阻塞的API函数。
需将 configUSE_TIMERS 配置项配置成1
软件定时器服务任务的优先级为 configTIMER_TASK_PRIORITY = 31,及最高
定时器的命令队列长度为 configTIMER_QUEUE_LENGTH = 5
两种状态:休眠态,运行态
新创建的软件定时器处于休眠态
xTimerDelete() 软件定时器删除
C语言知识补充
堆
一块空闲的内存
栈
主要用来存储地址和临时变量
指针函数与函数指针
/ | 指针函数 | 函数指针 |
---|---|---|
主体 | 函数 | 指针 |
需求 | 返回类型为指针(地址) | 指向一个函数 |
形式 | void* f(int x, int y) | void (*f)(int x, int y) |
()的优先级大于*
define 和 typedef
define 多用于定义常量
typedef 多用于为已有的数据类型创建一个新的名称
句柄和指针
指针
指针 是 栈上 存储 指向堆上空间地址的 变量
句柄
通俗理解:Handle 门把手,通过门把手来操控门
本质:是对 底层硬件实例的 指针的 引用。
句柄可以有效节约资源,更高效使用空间。