第二部分 事件组
一、事件组的简介
1、事件
事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。其实事件组的本质就是一个整数(16/32位)。可以是一个事件发生唤醒一个任务,或者多个事件唤醒一个或多个任务。
2、与队列/信号量的区别:
①信号量/队列当事件发生时只去唤醒一个任务,而事件组可唤醒多个任务,起到一个广播的作用。 ②信号量/队列是一个消耗性资源,即数据读走了就减少,而事件组可以选择清除事件也可以选择保留事件。 ③事件组只能是起到一个同步的作用,并不能传递数据。 ④事件组可以实现多个任务之间的同步,队列/信号量则只能是两个任务之间的同步。
3、事件组
1)整数的位数: 如果宏configUSE_16_BIT_TICKs = 0,那么这个整数是32位的,其中⽤低24位来表示事件组,代表24 个事件,剩余8位用于管理事件。 如果宏configUSE_16_BIT_TICKs = 1,那么这个整数是16位的,其中⽤低8位来表示事件组,代表8个事 件。
2)每⼀位事件的含义由我们自己定义。
3)这些位,值为1表示事件发生了,值为0表示事件没发⽣。
4)⼀个或多个任务、ISR都可以写这些位。
5)⼀个或多个任务可以等待某⼀位、某几个位。
特点
①与信号量不同设置,事件组不会阻塞,多个任务设置同一时间等于设置一次。
②支持事件超时等待机制,等待该事物类型的任务会进行阻塞态
③逻辑与:任务所期望的事件全部发生,任务才能被唤醒
逻辑或:任务所期望的事件只要有任意一个事件发生,任务即可被唤醒。
全局变量的区别
1.全局变量使用在操作系统中存在被多个任务同时读写的风险,则事件组它会直接禁止任务调度来规避风险。 2.使用全局变量需要自己去实现阻塞机制(成本太高)。 3.使用事件组能更方便的实现多任务之间的同步。
二、API函数
接口函数 | 函数功能 |
---|---|
xEventGroupCreate() | 动态创建事件组 |
xEventGroupSetBits() | 事件组置位函数 |
xEventGroupWaitBits() | 等待事件函数 |
EventGroupHandle_t() | 事件组结构体 |
EventGroupHandle_t
xEventGroupCreate
分三步:①为事件组结构体分配内存
②初始化事件组
③初始化等待链表
xEventGroupSetBits()
xEventGroupWaitBits
第三部分 软件定时器
一、简介
1、硬、软定时器
硬件定时器:由外部晶振提供时钟,定时精准,精度可以达到微秒级。不仅具有定时功能,还可以输出PWM,输入捕获等高级功能,当时间到达会触发一次中断。
软件定时器:基于一个守护任务,可以被其他中断或优先级比它高的任务打断,且软件定时器的精度是基于系统时钟SysTick的,一般达不到微秒级别。只能用来定时,每当时间到达会执行回调函数。
2、为何使用软件定时器
硬件定时器数量有限,而且使用定时的高级功能一般不会用来定时,但是实际上需要很多定时器来采集数据,上传数据等,所以我们就有了软件定时器,只要内存足够可以创建无数个软件定时器(一个软件定时器只需要一个定时器结构体的内存,还需要两条定时器链表,一个队列)。
软件定时器适合于对定时器精度要求不高的周期性任务
二、API函数
接口函数 | 函数功能 |
---|---|
tmrTimerControl | 定时器结构体 |
xTimerCreate() | 创建定时器 |
xTimerStart() | 启动定时器 |
xTimerStop() | 停止定时器 |
xTimerDelete() | 删除定时器 |
1、tmrTimerControl
(定时器结构体)
(1)定时器周期
单次定时:像闹钟一样只执行一次,时间一到事情做完定时器就停止运行;
周期定时:比如每隔20s触发一次,这就是周期执行。
(2)回调函数
每个定时器都由用户指定一个回调函数(功能由用户自己实现),每当定时器超时,守护任务则会去调用该定时器的回调函数
typedef void (* TimerCallbackFunction_t)( TimerHandle_t xTimer );
(3)定时器的状态
定时器活跃:定时器并不是一被创建就开始定时,与硬件定时器一样需要一个启动的命令(当前任务通过消息队列发送给守护任务),则定时器才会被挂入定时器链表,则处于活跃态,假设一个定时器是单次定时,在一次定时之后,则定时器会被移出定时器链表,所以判断一个定时器是否在活跃态,就是判断定时器是否在定时器链表中(如果在说明该定时器在参与定时)。
(4)软件定时器的守护任务
通过一个prvTimerTask任务来管理软件定时器,这个任务也叫"守护任务"。在启动调度器时自动创建的,以满足用户定时的需要。
需要在config文件中将configUSE_TIMERS设置为1时,在启用调度器时自动创建守护任务。
(5)定时器命令队列
任务和守护任务之间通过消息队列,守护任务创建完成后将会进入阻塞状态,等待用户程序发起定时器启动命令。其阻塞时间为定时器定时时间。
2、xTimerCreate()
第一次创建定时器需要初始化两条定时器链表(一条正常,一条超时),并创建一个消息队列,以上三者是软件定时器运行起来的基本配置。
守护任务:如果将宏configUSE_TIMERS配置为1则默认使用定时器,回创建一个定时器的守护任务。
3、xTimerStart()
定时器命令ID:
ID号-2~-1:直接调用用户指定的函数在守护任务执行前
ID号0~5:定时器的启动、复位、停止、更改周期、删除命令
ID号6~9:中断版的定时器的启动、复位、停止、更改周期命令
作用:将定时器信息打包发送给定时器信息队列等待守护任务读取信息并处理。
4、xTimerStop()
不难发现他的内部函数还是那个,只是执行命令不同。
5、xTimerDelete()
三、运行机制
(1)创建定时器--创建定时器控制块
用户程序调用定时器创建函数,给出定时时间
SysTick定时器常被用来作为软件定时器的时钟源,1s节拍1000次,1次就是1ms
(2)插入列表
根据定时时间长短:以升序方式插入链表中。
有两个链表:一个是创建时 插入表,另一个是溢出时插入表。
xTimeNow:这个是SYSTIC的计数值。
XTicksToWait:xTimeNow+定时时间。
(3)启动定时器
当⽤户任务发送定时器启动命令后,定时器的守护任务函数将会执⾏。在任务函数中定义了⼀个全
局变量并初始化为0,⽤来保存上次定时器时间到的SYSTICK的值。
然后,⽐较全局变量和当前xTimeNow的值,如果xTimeNow<上次SYSTICK,则表明SYSTICK溢
出,那么将会把第⼀张链表上的定时器移到第⼆张链表中。
如果系统节拍计数器没有溢出,⽐较xTimeNow 和 xTickToWait的值,当xTimeNow >=
xTickToWait时,说明定时器时间到,调⽤回调函数。
如果xTimeNow <xTickToWait,定时器任务阻塞,阻塞时间为:xTickToWait - xTimeNow时。
以上是目前我对FreeRTOS的理解,后续如果对这块有更深理解会继续更新,如果有错误的地方可以交流。