论坛原始地址(持续更新):http://www.armbbs.cn/forum.php?mod=viewthread&tid=99514
第17章 ThreadX事件标志组
前面的章节我们已经讲解了任务管理和时间管理,从本章节开始讲解任务间的通信和同步机制。首先讲解任务间的通信和同步机制之一,事件标志组。
17.1 事件标志组
17.2 事件标志组任务通知(又称Event chaining事件链)
17.3 事件标志组创建函数tx_event_flags_create
17.4 事件标志组设置函数tx_event_flags_set
17.5 事件标志组获取函数tx_event_flags_get
17.6 事件标志组消息通知函数tx_event_flags_set_notify
17.7 实验例程
17.8 总结
17.1 事件标志组
17.1.1 为什么要使用事件标志
事件标志组是实现多任务同步的有效机制之一。也许有不理解的初学者会问采用事件标志组多麻烦,搞个全局变量不是更简单,其实不然。在裸机编程时,使用全局变量的确比较方便,但是在加上RTOS后就是另一种情况了。使用全局变量相比事件标志组主要有如下三个问题:
- 使用事件标志组可以让RTOS内核有效的管理任务,全局变量是无法做到的,任务的超时等机制需要用户自己去实现。
- 使用了全局变量就要防止多任务的访问冲突,使用事件标志组已经处理好了这个问题。用户无需担心。
- 使用事件标志组可以有效的解决中断服务程序和任务之间的同步问题。
17.1.2 ThreadX事件标志组的实现
任务间事件标志组的实现是指各个任务之间使用事件标志组实现任务的通信或者同步机制。
每创建一个事件标志,会自动创建32个事件标志,事件标志被存储到事件标志组的控制块中。实际上就是一个32bit变量,每个bit代表一个标志。下面我们通过如下的框图来说明一下ThreadX事件标志的实现,让大家有一个形象的认识。
运行条件:
- 创建2个任务Task1和Task2
运行过程描述如下:
- 任务Task1运行过程中调用函数tx_event_flags_get,等待事件标志位被设置,任务Task1由运行态进入到挂起态
- 任务Task2设置了任务Task1的事件标志,任务Task1由挂起态进入到就绪态,在调度器的作用下由就绪态又进入到运行态。
上面就是一个简单的ThreadX事件标志通信过程。
17.1.3 ThreadX中断方式事件标志组的实现
RTX中断方式事件标志组的实现是指中断函数和RTX任务之间使用事件标志。下面我们通过如下的框图来说明一下RTX事件标志的实现,让大家有一个形象的认识。
运行条件:
- 创建1个任务和一个串口接收中断
运行过程描述如下:
- 任务Task1运行过程中调用函数tx_event_flags_get,等待事件标志位被设置,任务Task1由运行态进入到挂起态
- Task1挂起的情况下,串口接收到数据进入到了串口中断服务程序,在串口中断服务程序中设置Task1的事件标志,任务Task1由挂起态进入到就绪态,在调度器的作用下由就绪态又进入到运行态。
上面就是一个简单ThreadX中断方式事件标志通信过程。实际应用中,中断方式的事件标志要注意以下几个问题:
- 中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。
- 实际应用中,建议不要在中断中实现功能处理,用户可以在中断服务程序里面发送事件标志通知任务,在任务中实现功能处理,这样可以有效的保证中断服务程序的实时响应。同时此任务也需要设置为高优先级,以便退出中断函数后任务可以得到及时执行。
- 在ThreadX操作系统中实现中断函数跟裸机编程是一样的。
- 另外强烈推荐用户将NVIC优先级分组设置为4,即:HAL_NVIC_SetPriorityGrouping (NVIC_PRIORITYGROUP_4);这样中断优先级的管理将非常方便。
- 用户要在ThreadX多任务开启前就设置好优先级分组,一旦设置好切记不可再修改。
17.2 事件标志组任务通知(又称Event chaining事件链)
ThreadX 中的通知功能可用于将各种同步事件“连接”在一起。当单个线程必须处理多个同步事件时,这通常很有用。
例如,应用程序可以为每个对象注册一个通知回调函数,而不是为队列消息、事件标志和信号量挂起单独的线程。当被调用时,应用程序通知例程然后可以恢复单个线程,该线程可以询问每个对象以便查找和处理新事件。
通常,事件链导致更少的线程、更少的开销和更小的 RAM 需求。它还提供了一种高度灵活的机制来处理更复杂系统的同步要求。
17.3 事件标志组创建函数tx_event_flags_create
函数原型:
代码语言:javascript复制UINT tx_event_flags_create(
TX_EVENT_FLAGS_GROUP *group_ptr,
CHAR *name_ptr);
函数描述:
此函数用于创建事件标志组,支持32bit变量设置,变量的每个bit代表一个标志位,即支持32个标志位。
- 第1个参数是事件标志组控制块。
- 第2个参数是事件标志组名字。
- 返回值:
TX_SUCCESS(0x00)创建成功。
TX_GROUP_ERROR(0x06)无效事件标志。
TX_CALLER_ERROR(0x13)无效调用。
注意事项:
- 初始状态,32个标志位都是0。
- 只能在初始化和任务中调用。
使用举例:
代码语言:javascript复制TX_EVENT_FLAGS_GROUP EventGroup;
/* 创建事件标志组 */
tx_event_flags_create(&EventGroup, "EventGroupName");
17.4 事件标志组设置函数tx_event_flags_set
函数原型:
代码语言:javascript复制UINT tx_event_flags_set(
TX_EVENT_FLAGS_GROUP *group_ptr,
ULONG flags_to_set,
UINT set_option);
函数描述:
此函数用于设置事件标志。
1、 第1个参数是事件标志组控制块。
2、 第2个参数根据第3个参数的类型,用于设置或者清楚标志位。32bit数值,每个bit代表一个标志位。
3、 第3个参数支持如下两种参数:
- TX_AND,与操作。
表示第2个参数的设置值与事件标志当前32bit数值的与操作。比如第2个参数设置为0x0000 0002,表示事件标志组32bit变量的bit0和bit1保持不变,其它全部清零。
- TX_OR,或操作。
表示第2个参数的设置值与事件标志当前32bit数值的或操作。比如第2个参数设置为0x0000 0002,表示事件标志组32bit变量的bit0和bit1全部置1,其它bit不变。
4、 返回值
TX_SUCCESS(0x00)设置成功。
TX_GROUP_ERROR(0x06)无效的事件标志组。
TX_OPTION_ERROR(0x08)无效设置选项。
使用举例:
代码语言:javascript复制#define BIT_0 (1 << 0)
TX_EVENT_FLAGS_GROUP EventGroup;
tx_event_flags_set(&EventGroup, BIT_0, TX_OR);
17.5 事件标志组获取函数tx_event_flags_get
函数原型:
代码语言:javascript复制UINT tx_event_flags_get(
TX_EVENT_FLAGS_GROUP *group_ptr,
ULONG requested_flags,
UINT get_option,
ULONG *actual_flags_ptr,
ULONG wait_option);
函数描述:
此函数用于获取事件标志。
1、 第1个参数是事件标志组控制块。
2、 第2个参数是请求的事件标志,32bit数据值,每个bit代表一个标志位。
3、 第3个参数支持如下四种参数
- TX_AND
等待第2个参数所有请求置1的bit全部被置1。
- TX_AND_CLEAR
等待第2个参数所有请求置1的bit全部被设置,之后相应的bit将被清零。
- TX_OR
等待第2个参数所有请求置1的bit中任何一个bit被置1。
- TX_OR_CLEAR
等待第2个参数所有请求置1的bit中任何一个bit被置1,之后相应的bit将被清零。
4、 第4个参数是32bit事件标志的实际数值(未被清零前的数值)。
5、 第5个参数是等待选项
- TX_NO_WAIT (0x00000000),表示不管是否满足等待条件,立即返回。如果在定时器组,初始化或中断里面调用,必须要设置成这个参数。
- TX_WAIT_FOREVER (0xFFFFFFFF),表示永久等待,直到满足等待条件。
- 等待时间,范围0x00000001 到 0xFFFFFFFE,单位系统时钟节拍。
6、 返回值:
- TX_SUCCESS (0x00) 事件标志获取成功。
- TX_DELETED (0x01) 事件标志组在线程挂起时被删除。
- TX_NO_EVENTS(0x07)无法在指定时间内获取指定的事件。
- TX_WAIT_ABORTED(0x1A)事件标志组被另一个线程、定时器组或中断服务程序终止。
- TX_GROUP_ERROR(0x06)无效事件标志组控制块。
- TX_PTR_ERROR(0x03)第4个参数无实体。
- TX_WAIT_ERROR(0x04) 定时器组,初始化或中断里面调用此函数时,第5个参数必须是TX_NO_WAIT,其它参数会返回此错误值。
- TX_OPTION_ERROR(0x08) 第3个参数错误。
注意事项:
- 可以在初始化,任务,定时器组和中断服务程序里面调用。
- 这个函数的第3个参数推荐配置为TX_AND_CLEAR或者TX_OR_CLEAR,否则用户满足条件时,函数tx_event_flags_get会一直成立。
使用举例:
代码语言:javascript复制#define BIT_0 (1 << 0)
#define BIT_1 (1 << 1)
#define BIT_ALL (BIT_0 | BIT_1)
UINT status;
TX_EVENT_FLAGS_GROUP EventGroup;
status = tx_event_flags_get(&EventGroup, /* 事件标志控制块 */
BIT_ALL, /* 等待标志 */
TX_OR_CLEAR , /* 等待任意bit满足即可 */
&actual_events, /* 获取实际值 */
TX_WAIT_FOREVER);/* 永久等待 */
17.6 事件标志组消息通知函数tx_event_flags_set_notify
函数原型:
代码语言:javascript复制UINT tx_event_flags_set_notify(
TX_EVENT_FLAGS_GROUP *group_ptr,
VOID (*events_set_notify)(TX_EVENT_FLAGS_GROUP *));
函数描述:
此函数用于回调通知,当事件标志组中一个或多个事件标志被设置时,此函数设置的回调程序将被调用。
1、 第1个参数是事件标志组控制块。
2、 第2个参数是回调函数,如果此值TX_NULL,则不执行回调通知。
3、 返回值
- TX_SUCCESS(0x00)注册成功
- TX_GROUP_ERROR(0x06)无效的事件标志控制块。
- TX_FEATURE_NOT_ENABLED(0xFF)系统没有使能事件标志通知特性。
使用举例:
代码语言:javascript复制TX_EVENT_FLAGS_GROUP my_group;
tx_event_flags_set_notify(&my_group, my_event_flags_set_notify);
void my_event_flags_set_notify(TX_EVENT_FLAGS_GROUP *group_ptr)
{
}
17.7 实验例程
配套例子:
V7-3011_ThreadX Event Flags Groups
实验目的:
- 学习ThreadX事件标志组
实验内容:
1、共创建了如下几个任务,通过按下按键K1可以通过串口或者RTT打印任务堆栈使用情况
========================================================
CPU利用率 = 0.89%
任务执行时间 = 0.586484645s
空闲执行时间 = 85.504470575s
中断执行时间 = 0.173225395s
系统总执行时间 = 86.264180615s
=======================================================
任务优先级 任务栈大小 当前使用栈 最大栈使用 任务名
Prio StackSize CurStack MaxStack Taskname
2 4092 303 459 App Task Start
5 4092 167 167 App Msp Pro
4 4092 167 167 App Task UserIF
5 4092 167 167 App Task COM
0 1020 191 191 System Timer Thread
串口软件可以使用SecureCRT或者H7-TOOL RTT查看打印信息。
App Task Start任务 :启动任务,这里用作BSP驱动包处理。
App Task MspPro任务 :消息处理,这里用于接收事件标志。
App Task UserIF任务 :按键消息处理。
App Task COM任务 :这里用作LED闪烁。
System Timer Thread任务:系统定时器任务
2、K2键按下,直接发送事件标志给任务App Task MspPro,设置bit0。
3、K3键按下,直接发送事件标志给任务App Task MspPro,设置bit1。
4、任务App Task MspPro接收到消息后,串口打印。
5、(1) 凡是用到printf函数的全部通过函数App_Printf实现。
(2)App_Printf函数做了信号量的互斥操作,解决资源共享问题。
6、默认上电是通过串口打印信息,如果使用RTT打印信息
(1) MDK AC5,MDK AC6或IAR通过使能bsp.h文件中的宏定义为1即可
#define Enable_RTTViewer 1
(2) Embedded Studio继续使用此宏定义为0, 因为Embedded Studio仅制作了调试状态RTT方式查看。
串口打印信息方式(AC5,AC6和IAR):
波特率 115200,数据位 8,奇偶校验位无,停止位 1
RTT打印信息方式(AC5,AC6和IAR):
Embedded Studio仅支持调试状态RTT打印:
由于Embedded Studio不支持中文,所以中文部分显示乱码,不用管。
程序执行框图:
17.8 总结
本章节为大家讲解了任务间的通信和同步机制之一,事件标志组,初学者要稍花些时间将其掌握。