前段时间没有什么可以干的事 咸鱼而没有追求的日子里居然啃完了一本Datasheet!!!
FREERTOS
FreeRtos任务管理
单任务系统·
查询方式
- 无法定义查询任务的优先级
- 重要的接口消息得不到及时响应
中断方式
必须在中断(ISR)内处理时间关键运算
ISR函数变得复杂 逻辑变得难以理解
ISR嵌套可能产生不可预测的执行时间和堆栈需求
- 软件开销较大
应用程序的数据交换是通过全局变量进行
- 程序员必须确保数据的一致性
多任务系统
使用相关调度算法决定当前需要执行的任务
- 多任务是在某一时刻只能有一个任务运行,只是通过调度器的决策,看起来所有任务同时运行
实时操作系统
- 抢占式调度
- 时间片式调度
- 合作式调度
栈
- 系统栈空间
- 任务栈空间
任务状态
- Running–运行态
- Ready–就绪态
- Blocked–阻塞态
- Suspended–挂起态
任务管理函数
vTaskStartScheduler() –启动 FreeRTOS
- 在创建完任务后进行开始任务调度
xTaskCreate() –实现 FreeRTOS 操作系统的任务创建
vTaskDelete – 实现 FreeRTOS 操作系统的任务删除
vTaskSuspend–实现 FreeRTOS 操作系统的任务挂起。
实现 FreeRTOS 操作系统的任务恢复
- xTaskResume()
- xTaskResumeFromISR() 中断方式
FreeRtos任务栈大小
函数的嵌套调用
函数的局部变量
函数形参
- 嵌套的函数所使用的形参d
函数返回地址
- 对于M3和M4内核的MCU 函数的返回地址是专门保存到寄存器里面的
任务切换
对于M3和M4内核的MCU 任务执行过程中发生中断
- M3 内核的 MCU 有 8 个寄存器是自动入栈的,这个栈是任务栈,进入中断以后其余寄存器入栈
以及发生中断嵌套都是用的系统栈。 - M4 内核的 MCU 有 8 个通用寄存器和 18 个浮点寄存器是自动入栈的,这个栈是任务栈,进入
中断以后其余通用寄存器和浮点寄存器入栈以及发生中断嵌套都是用的系统栈。 - 进入中断以后使用的局部变量以及可能发生的中断嵌套都是用的系统栈
栈溢出检测
在任务切换时检测任务栈指针是否过界了,如果过界了,在任务切换的时候会触发栈溢出钩子函
数- void vApplicationStackOverflowHook( TaskHandle_t xTask,signed char *pcTaskName );
任务创建的时候将任务栈所有数据初始化为 0xa5,任务切换时进行任务栈检测的时候会检测末
尾的 16 个字节是否都是 0xa5,通过这种方式来检测任务栈是否溢出了。
FreeRtos中断优先级
NVIC基础知识—->嵌套向量中断控制器
NVIC相关寄存器操作
优先级分组
- NVIC_PriorityGroup_0
- NVIC_PriorityGroup_1
- NVIC_PriorityGroup_2
- NVIC_PriorityGroup_3
- NVIC_PriorityGroup_4
开关中断实现机制
primask
- 在它被置 1 后,就关掉所有可屏蔽的异常,只剩下
NMI 和硬 fault 可以响应。它的缺省值是 0,表示没有关中断。
- 在它被置 1 后,就关掉所有可屏蔽的异常,只剩下
faultmask
- 当它置 1 时,只有 NMI 才能响应,所有其它的异
常,甚至是硬 fault,也通通闭嘴。它的缺省值也是 0,表示没有关异常
- 当它置 1 时,只有 NMI 才能响应,所有其它的异
basepri
- 当它被设成某个值后,所有优先级号大于等于此值的中断都被关(优先级号越大,优
先级越低)。但若被设成 0,则不关闭任何中断, 0 也是缺省值。
- 当它被设成某个值后,所有优先级号大于等于此值的中断都被关(优先级号越大,优
任务抢占优先级及其分配方案
任务优先级和中断优先级
- 任务优先级的数值越小,任务优先级越低
- 中断优先级的数值越小,优先级越高
任务优先级分配方案
IRQ任务
- 中断服务程序进行触发的任务 所有优先级中最高的
高优先级后台任务
- 按键检测
- 触摸检测
- USB消息处理
低优先级的时间片调度任务
- 不太需要实时执行的任务
空闲任务
相关函数
- 任务优先级修改 vTaskPrioritySet ()
- 任务优先级获取 vTaskPriorityGet ()
任务调度
抢占式调度器
每个任务都有不同的优先级, 任务会一直运行直到被高优先级任务抢占或者遇到阻塞式的 API 函数,
- 每个任务被分配以不同的优先级,抢占式调度器从就绪列表中火得优先级最高的任务并运行
时间片调度器
每个任务都有相同的优先级, 任务会运行固定的时间片个数或者遇到阻塞式的 API 函数
- 时间片调度发生在相同优先级任务之间
- 每个任务分配一个时间片( 也就是需要运行的时间长度,时间片用完了就进行任务切换)
合作式调度器
- 官方文档曾进行了特别的说明,之后不再升级,故已经使用得很少了
FreeRtos临界段和开关中断
临界段,一但该部分代码开始执行,则不允许中断打断
进入临界段代码之前关闭中断
- taskENTER_CRITICAL 进入临界段
- portSET_INTERRUPT_MASK_FROM_ISR();
执行临界段代码
- 嵌套计数
执行完毕 开启中断
- taskEXIT_CRITICAL退出临界段
- portCLEAR_INTERRUPT_MASK_FROM_ISR( )
影响系统的实时性
FreeRtos调度锁 任务锁和中断锁
调度锁
调度锁禁止任务调度,中断还是可以正常执行的
- xTaskResumeAll 用于实现 FreeRTOS 调度锁关闭
- vTaskSuspendAll 用于实现 FreeRTOS 调度锁开启
代码执行期间不会被高优先级任务抢占
中断锁
- FreeRtos没有专门的中断锁函数
- 使用中断服务程序临界段处理函数实现同样效果
任务锁
- 给调度器加锁,关闭任务切换功能
动态内存管理
动态内存管理是FreeRtos非常重要的一项功能 所有需要RAM空间都是通过动态内存管理从FreeRTOSconfig.h文件定义的heap空间中申请的
FreeRTOS支持五种动态内存管理方案
Heap_1
- 项目应用不需要删除任务、 信号量、 消息队列等已经创建的资源。
- 具有时间确定性,即申请动态内存的时间是固定的并且不会产生内存碎片
- 确切的说这是一种静态内存分配,因为申请的内存是不允许被释放掉的
Heap_2
利用最适应算法
支持内存释放
- 不考虑内存碎片的情况下,这种方式支持重复的任务、 信号量、 事件标志组、 软件定时器等内部资源的创建和删除
不支持内存碎片整理
- 用户随机的创建和删除任务、 消息队列、 事件标志组、 信号量等内部资源也容易出现内存碎片
- 直接的调用函数 pvPortMalloc() 和 vPortFree()也容易出现内存碎片
- heap_2 方式实现的动态内存申请不具有时间确定性,但是比 C 库中的 malloc 函数效率要高
Heap_3
对编译器提供的 malloc 和 free 函数进行了封装,保证是线程安全的
- 需要编译器提供 malloc 和 free 函数
- 不具有时间确定性,即申请动态内存的时间不是固定的
- 增加 RTOS 内核的代码量
Heap_4
利用最适应算法
支持内存碎片的回
收并将其整理为一个大的内存块- 可以用于需要重复的创建和删任务、 信号量、 事件标志组、 软件定时器等内部资源的场合
- 随机的调用 pvPortMalloc() 和 vPortFree(),且每次申请的大小都不同,也不会像 heap_2 那样产
生很多的内存碎片 - 不具有时间确定性, 即申请动态内存的时间不是确定的,但是比 C 库中的 malloc 函数要高效
Heap_5
- 在Heap_4的基础上做一些优化
任务消息邮箱
任务通知
TCB(task control block)任务控制块中一个 32 位的变量成员 ulNotifiedValue 是专门
用于任务通知的。
任务通知
计数信号量
- 接收任务控制块中的变量 ulNotifiedValue 数值进行加一或者减一操作可以实现计数信号量和二
值信号量
- 接收任务控制块中的变量 ulNotifiedValue 数值进行加一或者减一操作可以实现计数信号量和二
二值信号量
事件标志组
- 设置接收任务控制块中的变量 ulNotifiedValue 的 bit0-bit31 数值可以实现事件标志组
消息邮箱 (消息队列长度为1)
- 设置接收任务控制块中的变量 ulNotifiedValue 可以实现消息邮箱
- 如果接收任务控制块中的变量 ulNotifiedValue 还没有被其接收到, 也可以用新数据覆盖原有数据
, 这就是覆盖方式的消息邮箱。
任务事件标志组
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, /* 任务句柄 */
uint32_t ulValue, /* 更新任务控制块中的变量 ulNotifiedValue /
eNotifyAction eAction ); / 任务通知模式设置 */
第三个参数支持的参数———-参数描述
- 接收此消息的任务, 其任务控制块中的变量 ulNotifiedValue 没有变化, 即函数xTaskNotify 第 2 个参数 ulValue 没有用上。
- 接收此消息的任务, 其任务控制块中的变量 ulNotifiedValue 与函数 xTaskNotify 第 2 个参数 ulValue 实现或操作,比如ulValue=0x01, 那么变量的 ulNotifiedValue 的 bit0 = 1,
ulValue=0x08, 那么变量的 ulNotifiedValue 的 bit3= 1, 通过这种方式就实现了任务事件标志组 - 接收此消息的任务, 其任务控制块中的变量 ulNotifiedValue 实现加一操作,此时函数 xTaskNotify 实现的功能等效于函数xTaskNotifyGive, 函数 xTaskNotify 的第二个参数 ulValue 没有用上。 这种方式用于信号量
- 接收此消息的任务, 其任务控制块中的变量 ulNotifiedValue 被设置为函数 xTaskNotify 第 2 个参数 ulValue 的数值, 即使等待此消息的任务还没有收到上一次的数值,即数值被覆盖了。 这种方式用于消息邮箱, 相当于消息队列覆盖方式函数xQueueOverwrite()
- 如果接收此消息的任务, 其任务控制块中的变量
ulNotifiedValue 已经被更新,但由于接收此消息的任务还处于阻塞态等待此消息,那么变量 ulNotifiedValue 不可以被更新为函数 xTaskNotify 第 2 个参数 ulValue 的数值, , 此时函数xTaskNotify 会返回 pdFALSE。
如果接收此消息的任务, 其任务控制块中的变量
ulNotifiedValue 还没有被更新, 那么变量 ulNotifiedValue 会
被设置为 xTaskNotify 第 2 个参数 ulValue 的数值。
这种方式用于消息邮箱,相当于消息队列长度为 1 时调用函数xQueueSend().
任务二值信号量
任务计数信号量
基于任务通知的计数信号量
效率更高
- 速度提升45%
需要的RAM空间更小
仅可用在只有一个任务等待信号量
计数信号量API函数
xTaskNotifyGive 用于释放信号量( 含任务二值信号量, 任务计数信号量)
- 任务信号量的初始计数值是 0。 任务信号量不像FreeRtos的信号量,无需单独创建即可使用
ulTaskNotifyTake用于获取信号量
FreeRtos系统时钟节拍和中断管理
FreeRtos 时钟节拍
- 任何操作系统都需要提供一个时钟节拍,以供系统处理诸如延时、 超时等与时间相关的事件
- #define configTICK_RATE_HZ
- 滴答定时器Systick
FreeRtos 时钟管理
时间延迟
- 为周期性执行的任务提供延迟
- 对于抢占式调度器,让高优先级任务可以通过时间延迟函数释放 CPU 使用权, 从而让低优先级任务可以得到执行。
FreeRtos时间相关函数
- vTaskDelay () 相对时间延迟
- vTaskDelayUntil () 绝对时间延迟
- xTaskGetTickCount 获取系统当前运行的时钟节拍数
- xTaskGetTickCountFromISR
FreeRtos事件标志组
事件标志
- 在裸机编程中 全局变量确实方便
- 在操作系统中全局变量就要防止多任务的访问冲突
- 事件标志组可以有效地解决中断服务程序和任务之间的同步问题 减少任务之间的耦合
任务标志组API函数
- xEventGroupCreate()
xEventGroupCreateStatic() - vEventGroupDelete()
- xEventGroupWaitBits()
- xEventGroupSetBits()
xEventGroupSetBitsFromISR() - xEventGroupClearBits()
xEventGroupClearBitsFromISR() - xEventGroupGetBits()
xEventGroupGetBitsFromISR()
任务间事件标志组的实现
FreeRtos定时器组
软件定时器组,不需要使用任何硬件定时器 可以创建多个
与硬件定时器不同,软件定时器使用指定的回调函数 在回调函数中执行相应功能
单次模式
- 用户创建了定时器并启动了
定时器后, 定时时间到将不再重新执行
- 用户创建了定时器并启动了
周期模式
- 按照设置的时间周期重复去执行
定时器组API函数
- xTimerCreate 用于创建软件定时器
- xTimerStart 用于启动软件定时器
- pvTimerGetTimerID 用于返回使用函数 xTimerCreate 创建的软件定时器 ID
FreeRtos消息队列
消息队列概念及其作用
- 避免使用全局数组可能造成的任务访问冲突
- FreeRTOS 的消息传递是数据的复制,而不是传递的数据地址
- 解决中断服务程序与任务之间消息传递的问题
- FIFO 机制更有利于数据的处理
任务间消息队列的实现
消息队列API函数
- xQueueCreate 用于创建消息队列
- xQueueSend 用于任务中消息发送xQueueSendFromISR 用于中断服务程序中消息发送
- xQueueReceive 用于接收消息队列中的数据
FreeRtos计数信号量
多个共享资源管理
各个任务之间使用信号量实现任务的同步或者资源共享功能
计数信号量API函数
- xSemaphoreCreateCounting 创建计数信号量
- xSemaphoreGive 在任务代码中释放信号量
- xSemaphoreGiveFromISR 中断服务程序中释放信号量
- xSemaphoreTake 在任务代码中获取信号量
FreeRtos二值信号量
特殊的计数信号量 即共享资源为1的时候
二值信号量API函数
- xSemaphoreCreateBinary 用于创建二值信号量。
- xSemaphoreGive 用于在任务代码中释放信号量
- xSemaphoreGiveFromISR 用于中断服务程序中释放信号量
- xSemaphoreTake 用于在任务代码中获取信号量
FreeRtos互斥信号量
对资源实现互斥访问
二值信号量也可以实现对资源的互斥访问
- 一个任务申请成功 另一个任务释放
互斥信号量
同一个任务申请,同一个任务释放
同一个任务实现递归申请
API函数
- xSemaphoreCreateMutex 用于创建互斥信号量
- xSemaphoreGive 用于在任务代码中释放信号量
- xSemaphoreTake 用于在任务代码中获取信号量
优先级翻转
嵌入式实时操作系统
调度器
- 调度算法
任务切换
- 在基本相同的硬件内核架构,任务切换也是相似的
单任务系统编程框架即裸机编程的变成框架,采用的是超级循环,即应用程序是无限的循环,循环中调用相应的函数完成相应的操作的后台行为,中断服务程序处理异步事件,可看作为前台行为
系统上电复位后默认使用优先级分组0
将这几个选项简单的累加就可以得到粗略的栈大小 当然 在系统开始任务调度之后我们可以通过实时监测调整栈的分配
这里提供的两种栈溢出检测机制都是在任务切换时进行的,但是都有自身的缺陷而检测不到某些栈溢出
FreeRTOS 内核源码中有多处开关全局中断的地方,这些开关全局中断会加大中断延迟时间。 比如在源码的某个地方关闭了全局中断,但是此时有外部中断触发,这个中断的服务程序就需要等到再次开启全局中断后才可以得到执行。开关中断之间的时间越长,中断延迟时间就越大,这样极其影响系统的实时性。如果这是一个紧急的中断事件, 得不到及时执行的话,后果是可想而知的。
实际应用中切不可在定时器回调函数中调用任何将定时器任务挂起的阻塞函数
XMind: ZEN - Trial Version