FreeRTOS入门基础(持续更新)

基础知识

1.任务状态

总共有四种:运行态,就绪态,阻塞态,挂起态。只有就绪态才可转变运行态。其他任务想运行,就必须先转变成就绪态。

2.堆和栈

堆是一块用于动态分配内存的区域,用于存储程序运行时动态创建的对象。堆的大小可以在程序运行时动态调整。

堆的分配和释放由程序员手动控制的。堆的分配和释放顺序是可以任意的,不需要遵循先进先出的原则,堆的分配和释放需要调用相应的函数。

概念介绍

栈是一种用于存储局部变量和函数调用信息的数据结构。栈的大小在程序编译时确定,通常较小。

栈的分配和释放是由编译器自动控制的。栈的分配和释放遵循先进先出的原则。栈的分配和释放无需调用函数,由编译器自动完成。

通常用于跳转函数前,保存相关局部变量。

保存现场:就是保存被打断前的寄存器的值。

可简单理解为任务 = 函数 + 它本身的栈。

保存现场的几种情况:

函数嵌套,不用保存全部寄存器的值

硬件中断,F103,M3,M4硬件也会保存一部分。

任务切换:保存所有的寄存器值。

堆和栈的区别

分配方式

堆的分配是动态的,由程序员手动控制,堆的分配可以在任意时刻进行,不需要遵循特定的顺序。

栈的分配是静态的,由编译器自动控制,栈的分配在编译时确定,无法在运行时改变。

栈还用于中断时的保存现场和恢复现场。

分配效率

堆的分配效率较低,需要在运行时动态分配和释放。

栈的分配效率较高,由编译器在编译时确定分配大小。

空间管理

堆的空间管理由程序员手动控制,需要注意内存泄漏和内存碎片的问题。

栈的空间管理由编译器自动控制,无效程序员关注。

FreeRTOS中的任务栈

  • 任务栈的大小由创建任务时指定,通过由编译器自动计算;
  • 任务栈的空间管理由Free RTOS内核自动控制,无需程序员关注;
  • 任务栈的分配由Free RTOS内核自动控制,无效程序员手动分配;
  • 任务栈的分配效率较高,由编译器在编译时确定分配大小。
  • 每个任务都有一个自己的栈

3.FreeRTOS中的调度算法

优先级抢占式调度算法

算法原理:任务优先级越高,被调度的机会就越大,任务优先级相同时,采用时间片轮转调度。

优点:可以满足实时系统的响应时间要求,可以实现任务的优先级管理。

缺点:低优先级任务可能会被长时间阻塞。

在FreeRTOS中,优先级数字越小。优先级越低。

时间片轮转调度算法

算法原理:每个任务被分配一个时间片,时间片用完后,任务被挂起,等待下一次调度。

优点:公平地分配CPU时间片,可以避免低优先级任务长时间阻塞。

缺点:无法满足实时系统的响应时间要求。

时间片调度

时间片调度是指将CPU的执行时间划分为固定长度的时间片,每个任务在一个时间片内执行一定的指令。当多个优先级相同的任务处于就绪状态时,它们将通过时间片调度方式轮流执行

同优先级的情况下,我们一般是后创建的那个任务先执行。(首次执行的情况)

默认情况下采用优先级调度和时间片调度

当由多个优先级相同的任务处于就绪状态时,它们将通过时间片调度方式轮流执行。当由较高优先级的任务处于就绪状态时,高优先级任务会被优先执行而不受时间片调度的限制。

调度机制概述
优先级不同
  • 高优先级的任务,优先执行,可以抢占低优先级的任务。
  • 高优先级的任务不停止,低优先级的任务永远无法执行。
  • 同等优先级的任务,轮流执行,也叫做时间片流转。

如何管理?取出要运行的任务
  • 找到最高优先级的运行态、就绪态任务,运行它
  • 如果大家平级,轮流执行:排队,链表前面的先运行,运行1个tick后乖乖地去链表尾部排队。

谁来调度?
  • Tick中断(一般1ms一次)

优先级高先执行。优先级相同、后来创建的先执行。空闲任务由调度器创建

深入理解调度机制

可抢占:高优先级的任务先运行

时间片轮转:同优先级的任务轮流运行

空闲任务礼让:如果由同是优先级为0的其他就绪任务,空闲任务主动放弃一次运行机会。

4.动态任务和静态任务

如果任务数量固定且资源需求稳定,静态创建任务是一个简单且高效的选择。如果需要在运行时动态创建和撤销任务,或者任务的数量和资源需求不确定,动态创建任务则提供了更大的灵活性和管理性。一般而言,动态创建更为常用。

5.任务状态

就绪态

任务已经创建,但还未被调度执行。处于就绪态的任务可以被调度器选择执行。

就绪态的任务就放入就绪链表

运行态

被调度器选中执行的任务处于运行态。正在使用CPU资源执行任务代码。

运行态的任务放入就运行链表

阻塞态

任务因为某些原因无法继续执行而被阻塞。可能的原因包括等待某个事件的发生或者等待资源的释放

运行状态的任务可以调用特定函数进入阻塞态,如vTaskDelay()时间延迟函数。

也可由进程间的通信时请求信号量,队列等事件进入阻塞态。

处于阻塞态的任务会被放入阻塞链表

事件的分类

时间相关事件:

  • 时间相关事件,就是设置超时时间,在指定时间内阻塞,时间到了就进入就绪状态。
  • 使用时间相关的事件,可以实现周期性的功能、可以实现超时功能。

同步事件:

  • 同步事件就是某个任务在等待某些信息,别的任务或者中断服务程序会给它发送消息。
  • 发送消息的方式有很多:任务通知,队列,事件组,信号量,互斥量等。这些方法可以用来发送同步信息,比如某个外设得到了数据。

挂起态

调用vTaskSuspend()暂停任务。调用vTaskResume()恢复任务

一个任务暂停后如果想要恢复,需要通过别的任务调用vTaskResume()函数才能恢复这个任务。

句柄为NULL(或自身句柄)则操作自己,句柄为其他任务,则操作其他任务。

注意:挂起后无法自己恢复自己。

FreeRTOS代码以及源码

FreeRTOSConfig.h文件FreeRTOS的配置文件,包含了许多的宏定义,与CubeMx中的设置一一对应。

freertos.c是主要用于创建任务。

RTOS源码在Middlewares文件夹中。

数据类型

变量名

函数名

宏定义的规律(不用刻意去记): 

config开头的都是些配置项(本质是一些数字值)

INCLUDE开头的通常为“函数使能开关”是数值0或1

任务创建和删除的API函数

  1. xTaskCreate();动态创建任务
  2. xTaskCreateStatic();静态创建任务
  3. vTaskDelete();删除任务

动态创建任务函数解析

函数指针:该函数的地址。

在任务栈中存储了函数地址和参数值,以便任务切换。

  静态创建任务函数解析

和动态创建相比,调用的API函数不一样。

空闲任务和软件定时器需要手动配置。动态创建是自动配置。

空闲任务:当没有任务执行时,CPU会执行空闲任务。

调用两个接口函数,完成空闲任务和定时器的配置

任务删除函数

细节补充:

启动任务只需要执行一次,用完就删除。

处于临界区的代码不会被中断打断(因为临界区把中断关了)

调用FreeRTOS的时间延迟函数,才能进入阻塞态。 因为有vTaskDelay()函数把高优先级任务阻塞掉,所以其他优先级任务才能够执行。

HAL库和自定义的延迟函数不会阻塞任务。

自己删自己,空闲函数收尸。别人删自己,别人给收尸。


 

任务挂起和恢复

 

from ISR 在中断使用的函数。

任务挂起后,无论该任务的优先级多高,都无法再执行。

中断管理

简单介绍

如果要在中断里使用的函数,后缀必须带有ISR。

屏蔽中断的等级可以自行设置

可以设置一个阈值,低于这个优先级的,都被Free RTOS管理。只看高4位,0x50则表示0-15被RTOS管理,0-4则不受RTOS控制。

一般情况下只设置抢占优先级。

注意:响应优先级通常也称为子优先级。任何一个中断都可以打断任意级别的任务

Free RTOS的开关中断

进入临界区其实就是关中断,所以临界区的代码不会被中断打断。而退出临界区就是开启中断。

中断函数里也可以设置临界区,防止某个中断不被别的中断打断。

时间片流转的切换上下文依靠的是滴答定时器中断,想要完整执行完任务后再切换则需要设置临界区。临界区可以保护任务不被滴答定时器中断导致切换。

默认软件定时器优先级最高,空闲任务优先级最低

Free RTOS的中断机制

ISR函数中不切换任务,只是进行记录(唤醒)。

一般情况下,Systick和Pendsv的中断优先级设为最低。

FreeRTOS任务相关API函数

获取系统中任务的数量时,被删除掉的任务也会包含在内。注意会算上空闲任务和软件定时器任务(如果开启)。

任务删掉后就不会再显示任务统计时间。

Free RTOS时间管理

vTaskDelay():相对延时。从执行到vTaskDelay()函数开始,真到指定延时的时间结束。

xTaskDelayUntil():绝对延时。将整个任务的运行周期视为一个整体,适用于需要以固定频率定期执行的任务。

图片vTaskDelayUntil()少了个l

vTaskDelay()一般日常使用较多。

FreeRTOS的消息队列

概念介绍

FreeRTOS中的队列是一种用于任务之间通信的机制,它允许一个任务发送信息给另一个任务。队列是线程安全的数据结构,任务可以通过队列在彼此之间传递数据。

写队列和读队列都是采用复制的方式将数据复制过去使用。

消息队列读写API函数

创建队列

写消息

往队列发送:往尾部,往头部。写的方式:正常新增,覆盖写。

从队列读取:从头部读;读的方式:receive(剪切)、peek(复制)

读取消息

注意:队列里面的每个数据大小可以不一样;创建队列,要在任务开始前创建好。

读取队列数据时,会让任务进入阻塞。

C语言基础补充

表示字符串的方法:1.字符指针,2.字符数组。

字符指针

char * p = "hello world";

p的值是字符串首字符的地址,&p是指针变量p的地址,

字符数组

char a[]  = "hello world";

a本身自己没有地址,只有首字符的地址。&a,a,&a[0]都是首字符的地址

信号量

 概念介绍

Free RTOS中的信号量是一种用于任务间同步和资源管理的机制。信号量可以是二进制的0,1也可以是计数型。信号量的基本操作包括“获取”和“释放”。

信号量与队列的区别

信号量小于队列,信号量底层其实还是队列信号量大小为1队列

二值信号量相关函数

信号量刚创建完,会默认释放一次(+1),如果不想要,可以先主动获取一次(-1)。

计数信号量

概念

计数型信号量相关API函数

动态方便。静态灵活,但需要手动配置一些参数。

  

往队列发送数据的原理

二值信号量的实现原理

xSemaphoreCreateBinary();X开头的,不会主动释放信号量。还有返回值

vSemaphoreCreateBinary();V开头的,会主动释放一次信号量。

创建的队列:长度为1,队列项大小为0,类型为二值信号量。

释放信号量:往队列发送NULL值。

获取信号量:......获得NULL值

计数型信号量的实现原理

创建的队列:长度为计数最大值,队列项大小为0,类型为计数型信号量。

释放信号量: 发送一个NULL值,计数变量值 + 1。

获取信号量:计数变量值 - 1。

优先级翻转(倒反天罡)

 解决办法:采用互斥信号量的优先级继承机制。只能消除部分影响。释放完信号量后,恢复原来的优先级。

注意:在中断中不能使用互斥信号量。

互斥信号量(Mutex)

互斥信号量就是加了优先级继承机制的二值信号量。

相关API函数

优先级继承:从旧的就绪链表中移除,放入新的就绪链表,提高优先级。释放完Mutex后再恢复回原先的优先级。

队列集

概念介绍

相关API函数

先创建队列,再添加到队列集。

事件标志组

概念介绍

事件标志组与信号量的区别

相关API函数

事件组的内部机制

事件的含义由自己定义。

关闭调度器,但不会关闭中断,所以不可以在中断中使用事件组。

任务同步的方式选择

队列:两个任务之间传递数据。

信号量:简单需求,例如指示功能。

事件标志位:多个任务,多种条件

细节:信号量发送方不用阻塞,接收方需要阻塞。

总结

任务通知

概念介绍

 1.通知状态:

  • 初始化,没有在等待;
  • 正在等:任务调用了接收通知的函数(xxxTake\xxxWait)。
  • 已收到:发送方调用了发送通知的函数,其实是发送方已发送。

2.通知值

从10.4.0版本,是一个组,每一个都是32位的值,可以实现多对一。

关于发送的重要细节

  1. 临界区操作
  2. 设置接收任务的通知值,通知状态
  3.  将 接收任务  放入就绪列表、主动触发任务切换

接收函数 xxxTake

  1. 如果通知值为=0,将自己的任务状态设置为waiting 中
  2. 将  自己 放入阻塞列表、主动触发任务切换
  3. 如果第二个参数 = true ,直接清零(对标二值信号量);如果 = false,-1(对标计数型信号量)

接收函数 xxxWait

  1. 执行 接收前清理(根据配置)
  2. 将 自己 的任务状态 设置为 waiting 中
  3. 将 自己 放入阻塞列表、主动触发任务切换
  4. 保存一下现在的通知值
  5. 如果状态 = 已接收(发送方已发送),执行清理(根据配置)

任务通知是一对一,两个任务之间进行;事件组可以是多对一(多个任务对应一个事件组)

任务通知相关API函数

注意:无法在中断里接收任务通知,因为ISR没有TCB

进行多个任务通知时,任务优先级高的建议放在前面

FreeRTOS软件定时器

概念介绍

 

软件:代码提供的,参考FreeRTOS系统时钟节拍,Systick = 1ms

  • 节省硬件资源
  • Free RTOS源码中,会把systick设为最低优先级15,容易被打断。
  • 适用于对时间精度要求不高的场景

分类:

  • 单次:第一次超时,调用回调函数之后,就结束了
  • 周期:每次超时,调用回调函数之后,会自动重置继续下一次的超时

硬件:基本、通用、高级

软件定时器任务(启动调度器时创建的):负责管理软件定时器

  • 负责判断软件定时器是否超时(超时时间是我们传的)
  • 超时后,调用 超时回调函数(类似硬件定时器中断处理函数,但它不是中断,只是一个函数)
  • 不要在超时回调函数里定义阻塞的代码;为了不影响其他软件定时器调用超时回调函数。
  • 处理定时器命令队列
  • 也是一个Free RTOS的任务,优先级默认最高优先级31
  • 可选的,configUse_TIMERS == 0 默认关闭 ,  1打开。

软件定时器的状态

软件定时器的相关API函数

空闲任务

空闲任务的状态

只有两种状态:运行状态或者就绪状态

自己删自己,空闲函数收尸。别人删自己,别人给收尸。

空闲任务的钩子函数。

低功耗

概念介绍

不影响系统运行又想降低功耗,可以在空闲任务执行的期间,让MCU进入相应的低功耗模式。

低功耗的实现

依赖于睡眠模式

  • 利用__WFI进入睡眠模模式
  • 唤醒:Systick中断

依赖于空闲任务

会调整systick的周期、做补偿

低功耗模式的相关配置

内存管理算法

heap1

heap2

 heap4

heap4主要流程

分配流程

  • 堆栈初始化
  • 插入新空闲块的逻辑

释放流程

  • 释放指定的内存块
  • 释放成功后,执行 插入新空闲块的逻辑(合并相邻的空闲内存块)

都会用到:挂起调度器 操作   恢复调度器,确保操作期间不被干扰。

相关API函数(熟悉即可,一般由系统自动配置)

FreeRTOS的编码(熟悉)

 

RTOS任务与中断服务程序

中断是MCU的硬件特性。优先级数字越低,中断优先级越高。RTOS中的高优先级的一组中断不受FreeRTOS的管理,称为FreeRTOS不可屏蔽中断。低优先级的一组中断是FreeRTOS的可屏蔽中断。

任务与中断的关系(重要)

中断是MCU的硬件特性,由硬件事件或者软件信号引起中断,运行哪个ISR由硬件决定。中断的优先级数字越低,优先级越高,所以中断的最高优先级为0.

FreeRTOS的任务是一个纯软件的概念,与硬件系统无关。任务的优先级数字越低,表示的优先级越低,所以任务的最低优先级是0。由Free RTOS的任务调度算法决定哪个任务处于运行状态。

任务只有在没有ISR运行的时候才能运行,即使优先级最低的中断也可以抢占高优先级任务的执行。而任务不能抢占ISR的运行。

在实际的软件设计中,一般要尽可能地简化ISR函数的功能,使其尽量少占用CPU的时间,否则会导致程序响应变迟钝。

任务通知

不能向ISR发送通知。只能是任务或者ISR函数向任务发送通知。

任务通知不能进行消息广播。

任务通知一次只能发送或者接收一个uint32_t类型的数据。

实操应用

CubeMx配置

基础配置(基于STM32G431开发板)

Debug + Timebase Source设置

补充知识

汇编相关

读取LDR

写STR

加ADD

减SUB

PUSH入栈(可理解为写内存)

POP出栈(可理解为读内存)

LR返回地址

FreeRTOS的链表

 uxNumberOfitems:链表中有多少个item

pxIndex:指向当前正在使用的item,这个pxIndex被用来遍历链表

pvOwner:包含ITEM的结构体

pxContainer:链表

void * 可以代表任何类型的指针

Container的概念引入

person里含有node,person就是node的容器。

dog里含有node,dog就是node的容器。

核心在于如何根据node找到container

方法1:把node放在container的最前面。

方法2:在结构体中算出container的位置

方法3:在node中保存container的地址

不知道栈空间应该给多少时,可以先给个较大的栈空间,再查询高水位,进行适当的调整。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值