《FreeRTOS 内核实现与应用开发实战—基于STM32》
系统
模型 | 事件响应 | 事件处理 | 特点 |
---|---|---|---|
轮询 | 主程序 | 主程序(无限大循环) | 轮询响应事件,轮询处理事件 |
前后台 | 中断 | 主程序(无限大循环) | 实时响应事件,轮询处理事件 |
多任务 | 中断 | 任务 | 实时响应事件,实时处理事件 |
轮询是没有突发事件,或者是靠轮询速度来响应突发事件,但是可能会忽略掉突发事件
前后台是实时响应了事件,所有事件都会记录,但是处理事件的时候就需要等待时间
多任务是实时响应到事件,立马就解决(任务是并行的,达到同时处理多个任务)
链表
链表虽然看过,但是没用过,然后自己也…,都忘了
链表主要用于多任务的管理,任务延时的管理
链表结构体
/* 链表结构体定义 */
typedef struct xLIST
{
UBaseType_t uxNumberOfItems; /* 链表节点计数器 */
ListItem_t * pxIndex; /* 链表节点索引指针 */
MiniListItem_t xListEnd; /* 链表最后一个节点 */
} List_t;
mini节点结构体
/* mini节点结构体定义,作为双向链表的结尾
因为双向链表是首尾相连的,头即是尾,尾即是头 */
struct xMINI_LIST_ITEM
{
TickType_t xItemValue; /* 辅助值,用于帮助节点做升序排列 */
struct xLIST_ITEM * pxNext; /* 指向链表下一个节点 */
struct xLIST_ITEM * pxPrevious; /* 指向链表前一个节点 */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t; /* 最小节点数据类型重定义 */
节点结构体
/* 节点结构体定义 */
struct xLIST_ITEM
{
TickType_t xItemValue; /* 辅助值,用于帮助节点做顺序排列 */
struct xLIST_ITEM * pxNext; /* 指向链表下一个节点 */
struct xLIST_ITEM * pxPrevious; /* 指向链表前一个节点 */
void * pvOwner; /* 指向拥有该节点的内核对象,通常是TCB */
void * pvContainer; /* 指向该节点所在的链表 */
};
typedef struct xLIST_ITEM ListItem_t; /* 节点数据类型重定义 */
函数
void vListInitialise( List_t * const pxList ); //初始化链表根节点
void vListInitialiseItem( ListItem_t * const pxItem ); //初始化节点
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem ); //添加到最后
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem ); //顺序添加
任务的定义和切换
定义
TaskHandle_t Task1_Handle;
#define TASK1_STACK_SIZE 128 //128个字=128*4字节=512 byte
StackType_t Task1Stack[TASK1_STACK_SIZE]; //任务运行的空间
TCB_t Task1TCB;
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; /* 栈顶 */
ListItem_t xStateListItem; /* 任务节点 */
StackType_t *pxStack; /* 任务栈起始地址 */
/* 任务名称,字符串形式 */
char pcTaskName[ configMAX_TASK_NAME_LEN ];
TickType_t xTicksToDelay; //延时tick
UBaseType_t uxPriority; //优先级
} tskTCB;
typedef tskTCB TCB_t;
就绪列表
就绪列表,用于任务切换使用
实现调度器
任务调度
负责将任务进行调度;什么时候进行任务切换很关键!
配置 PendSV 和 SysTick 的中断优先级为最低。SysTick 和PendSV 都会涉及到系统调度,系统调度的优先级要低于系统的其它硬件中断优先级,即优先相应系统中的外部硬件中断,所以 SysTick 和 PendSV 的中断优先级配置为最低。
SysTick
用于延时计数,判断是否进入任务切换,即触发PendSV
PendSV
用于任务切换
任务切换
需要对上一个任务寄存器进行保存,提取下一个任务的寄存器写入
临界段保护
一旦这部分代码开始执行,则不允许任何中断打断;
FreeRTOS对中断的开和关是通过操作 BASEPRI 寄存器来实现的,即大于等于 BASEPRI 的值的中断会被屏蔽,小于 BASEPRI 的值的中断则不会被屏蔽。这样子的好处就是用户可以设置 BASEPRI 的值来选择性的给一些非常紧急的中断留一条后路。
RTOS | 特点 |
---|---|
FreeRTOS | 通过操作 BASEPRI 寄存器来实现,可以有选择的给紧急中断留一条后路。 |
RT-Thread或μC/OS | 全关,一般临界段的处理时间是比较短的,关了再开其实并没有太大的影响。 |
空闲任务和阻塞延时
#define configCPU_CLOCK_HZ (( unsigned long ) 25000000)
#define configTICK_RATE_HZ (( TickTypet ) 100)
- 如果有具体的硬件,则配置成与硬件的系统时钟一样。
- SysTick 每秒中断多少次,目前配置为 100,即每 10ms 中断一次
阻塞延时
任务运行到一定是会,需要延时等待,这时候就可以放弃CPU的使用权,启动任务调度器,也可以说是任务切换的一个控制入口;实际还涉及到优先级、任务延时、时间片
延时通过 【SysTick 中断周期】(可以设置)进行计数,vTaskDelay( 2 )=2*SysTick
空闲任务(启动调度器时就会一并创建)
如果进入到阻塞,并且没有任务就绪,那么CPU就会跑到【空闲任务】,优先级最低的任务;主要用于系统内存的清理工作
优先级
RTOS | 特点 |
---|---|
FreeRTOS | 数字优先级越小,逻辑优先级也越小 |
RT-Thread或μC/OS | 数字优先级越小,逻辑优先级越大 |
就绪列表
- uxReadyPriorities 的每个位号对应的是任务的优先级,任务就绪时,则将对应的位置 1,反之则清零。
- 就绪列表 pxReadyTasksLists[ configMAX_PRIORITIES ]是一个数组,数组里面存的是就绪任务的 TCB(准确来说是 TCB 里面的 xStateListItem 节点),数组的下标对应任务的优先级,优先级越低对应的数组下标越小。空闲任务的优先级最低,对应的是下标为 0 的链表。
任务调度
当任务正常运行时,没有其他中断,则进入PendSV,查询就绪列表,进行任务切换
任务延时列表
void vTaskDelay( const TickTypet xTicksToDelay )
-
将任务插入延时列表中,更新最近的 xNextTaskUnblockTime
-
任务延时到期,将任务插入到就绪列表中,并从延时列表中移除
时间片
RTOS | 特点 |
---|---|
FreeRTOS | 最小的时间单位为一个 tick,即 SysTick 的中断周期 |
RT-Thread或μC/OS | 可以指定时间片的大小为多个 tick, |
相同优先级任务之间的切换
当相同优先级任务都就绪时,每一个Tick切换一次
总结
- 每个时间片(SysTick 的中断周期)判断是否有任务就绪
- 优先级大于或等于当前优先级(就绪情况下),无需等待当前任务遇到阻塞API函数,进行任务切换
- 低于当前任务优先级的就绪任务,需要等待任务遇到阻塞API函数