FreeRTOS大杂烩
文章目录
- Freertos移植
- 任务切换基础
- 同步互斥与通信
- 队列:
- 邮箱:
- 队列集合:
- 信号量:
- 互斥量:
- 事件组:
- 任务通知:
- 定时器使用:
Freertos移植
- 其核心文件为,tasks.c、timers.c、queue.c、event_groups.c、croutine.c、list.c。源码兼顾了很多平台,但是我们可以删除一些不用的平台以及例程等,只保留我们要的。
- 比如RVDS/ARM_CM3,这表示cotexM3架构再RVDS或Keil工具上的移植文件(比如下图中的port.c、portmacro.h)
- 在编程过程中的大部分宏配置都在FreeRTOSConfig.h这个配置文件(调度、优先级、空闲任务等)
任务切换基础
-
任务间的切换,是利用tick中断实现的(关于这个时间片轮转的时间通过上面提到过的配置文件设置)
-
任务状态有。就绪态、运行态、挂起态、阻塞态(延时、同步机制),其中每种状态下若有多个任务使用链表进行扩充
空闲任务(pvIdleTask):(只能是就绪态或运行态)是为了帮别人收尸的(别人杀你,由别人清理。但是自杀自己是无法清理的).但是空闲任务不会直接供你使用,而是提供了一个钩子函数vApplicationIdleHook(执行的要快)让我在内部进行实现代码。 -
首先空闲任务的目的是可以执行一些低优先级,后台的需要连续执行的函数。
-
任务调度算法: 在FreeRTOSConfig.h文件中有宏可以配置
(1)是否可抢占(高优先级可抢占、或者只有我自动说我不用了,你才能用)
(2)同优先级是否可以交替执行(是否支持时间片轮转)
(3)空闲任务是否礼让(一般空闲任务是最低优先级)
同步互斥与通信
这里其实可以把多任务看成是多线程,可知多线程是在一个进程的且文件资源共享。此时最高的办法就是使用全局变量。但是会出现资源竞争的问题,为此提出了同步机制来解决。
实现同步互斥的方法:队列、事件组、信号量、任务通知、互斥量
队列:
-
队列像是一个传送带,而这传送带的两端可以是任务到任务、任务到中断、中断到任务直接传输信息
-
队列结构体内部含有一个环形buffer以及Ready_Send_List、Ready_Receive_List.
//创建队列 QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,// Nitem UBaseType_t uxItemSize ); //item_size //往队列写 BaseType_t xQueueSend( QueueHandle_t xQueue, const void*pvItemToQueue,//数据的地址 TickType_t xTicksToWait ); //阻塞时间 //关于往队列里面写(往尾部写、往头部写以及扩展特定的为中断服务任务使用的xxxxFrom_ISR) //从队列读 BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, //数据地址 TickType_t xTicksToWait );//阻塞时间
邮箱:
- FreeRTOS的邮箱概念与其它RTOS不一样。它是一个特殊的队列,因为队列的长度固定为1
- 写邮箱:新数据覆盖旧数据,在任务中使用xQueueOverwrite(),
在中断中使用 xQueueOverwriteFromISR() 。
既然是覆盖,那么无论如何,数据总可以写入成功 - 读邮箱:读数据时,数据不会被移除;在任务中使用xQueuePeek(),
在中断中使用xQueuePeekFromISR(),
意味着,第一次调用时会因为无数据而阻塞,一旦写入数据,以后读邮箱总能成功
队列集合:
- 检测多个队列,挑出有数据的队列,进行读队列
- 举例:有三个任务队列,鼠标任务队列queueA、按键任务队列queueB、触摸屏任务队列queueC
*(1)直接创建一个队列集 queue_set=xQueueCreateSet(3) *(2)建立联系,即把单个队列依次放到队列集里面 xQueueAddToSet(queueA)、 xQueueAddToSet(queueB) xQueueAddToSet(queueC) *(3)写完队列A一次,就会写队列集一次(写队列自动会写队列集) touch=>(data)=>touch_queue=>(handle)=>QueueSet *(4)读queue_set一次,返回某一个队列(有数据的队列),并且读这个返回的队列
信号量:
- 背景:关于上面的任务队列,可以用于任务与任务、任务与中断中间的数据传输,但是队列传输涉及到数据的复制等。而信号量只需要传递状态,并不需要传递具体的信息
- 信号量:信号起通知作用、量用来表示资源的数量,
用give给出资源,计数值+1,
用take获得资源,计数值-1; - 二进制信号量:二进制信号量与计数型的唯一差别就是二值型的最大值被限定为1
*申请二进制信号量句柄 SemaphoreHandle_t xBinarySemaphore; *创建二值信号量 SemaphoreHandle_t xSemaphoreCreateBinary( void ); *计数型信号量 SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,//3 UBaseType_t uxInitialCount);//0 *加1/减1: BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore ); BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait); * 二值信号量初始值为1,互斥。 * 二值信号量初始值为0,同步。
互斥量:
- 上面的队列和二值信号量和队列都可以实现互斥的功能。但是有个问题。
关于信号量的互斥,只要是个人都可以give和take。如果现在ab互斥,c来捣乱。就出现了很大问题
为此出现了互斥量 - 互斥量的核心:谁上锁,就只能谁解锁,但是FreeRTOS的互斥锁并没有代码实现(其实linux也是如此),只是靠程序员的自觉了。(这样导致谁上锁谁释放只是一种约定,好在RT-Thead中实现了)
- 互斥量的重点:优先级的反转(优先级继承)、扩展递归锁
- 互斥量函数:互斥量不能在ISR中使用。
创建互斥量 SemaphoreHandle_t xSemaphoreCreateMutex( void ); 释放 BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore ); 获得 BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait ); 删除 void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
事件组:
- 解释:上述bit0用来表示串口是否就绪,bit1用来表示按键是否被按下。
(值为1表示事件发生,值为0表示事件没发生)
一个或多个任务、ISR都可以去写这些位;一个或多个任务、ISR都可以去读这些位
可以等待某一位、某些位中的任意一个,也可以等待多位 - 操作:或"、"与
唤醒谁?
*1队列、信号量:事件发生时,只会唤醒一个任务
*2事件组:事件发生时,会唤醒所有符号条件的任务,简单地说它有“广播”的作用
是否清除事件?
*1队列、信号量:是消耗型的资源,队列的数据被读走就没了;信号量被获取后就减少了
*2事件组:被唤醒的任务有两个选择,可以让事件保留不动,也可以清除事件 - 事件组操作函数:
要先创建,得到一个句柄; 创建 EventGroupHandle_t xEventGroupCreate( void ); 设置事件 EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,//事件组 const EventBits_t uxBitsToSet ); //设置哪些位 等待事件 EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, //等待哪些位 const BaseType_t xClearOnExit,//是否要清除事件 const BaseType_t xWaitForAllBits,//等待1位还是all位 TickType_t xTicksToWait );//等待事件 删除 void vEventGroupDelete( EventGroupHandle_t xEventGroup );
任务通知:
使用队列、信号量、事件组等。并不知道对方是谁,
但是使用任务通知时,可以明确指定:通知哪个任务
- 优势:
(1)可以明确指定通知哪个任务
(2)节省内存,TCB中内置通知结构体成员,无须另外创建
通知成员为:通知值ulNotifiedValue(计数值、位、任意数值)
通知状态ucNotifyState(无等待通知、在等待通知、接受通知待处理) - 缺点:
(1)不能发送给ISR,ISR并没有TCB即内部无通知功能
(2)数据只能该任务独享
(3)无法缓冲数据
(4)无法广播
(5)若发送受阻,发送方无法进入阻塞状态等待 - 任务通知函数操作:
发出通知xTaskNotifyGive 与 vTaskNotifyGiveFromISR
取出通知ulTaskNotifyTake
定时器使用:
- 定时器三要素:超时时间、函数(回调函数)、单独触发还是周期触发
- 操作函数:
//创建 TimerHandle_t xTimerCreate( const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction ); //回调函数 void ATimerCallback( TimerHandle_t xTimer ); typedef void (* TimerCallbackFunction_t)( TimerHandle_t xTimer ); //启动/停止/复位(启动定时器设置为运行态,停止定时器设置为睡眠态,复位即是冬眠转运行态) BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait ); BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait ); BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait ); //修改定时器周期: BaseType_t xTimerChangePeriod( TimerHandle_t xTimer, TickType_t xNewPeriod, TickType_t xTicksToWait ); //删除定时器 BaseType_t xTimerDelete( TimerHandle_t xTimer, TickType_t xTicksToWait );