第13章
事件标志组
前面的章节我们已经讲解了任务管理和时间管理,从本章节开始讲解任务间的通信和同步机制。首先讲解任务间的通信和同步机制之一,事件标志组。
本章教程配套的例子含Cortex-M3内核的STM32F103和Cortex-M4内核的STM32F407。
13.1 事件标志组
13.1.1 为什么要使用事件标志
事件标志组是实现多任务同步的有效机制之一。也许有不理解的初学者会问采用事件标志组多麻烦,搞个全局变量不是更简单,其实不然。在裸机编程时,使用全局变量的确比较方便,但是在加上RTOS后就是另一种情况了。使用全局变量相比事件标志组主要有如下三个问题:
u
使用事件标志组可以让RTOS内核有效的管理任务,全局变量是无法做到的,任务的超时等机制需要用户自己去实现。
u
使用了全局变量就要防止多任务的访问冲突,使用事件标志组已经处理好了这个问题。用户无需担心。
u
使用事件标志组可以有效的解决中断服务程序和任务之间的同步问题。
13.1.2 RTX任务间事件标志组的实现
任务间事件标志组的实现是指各个任务之间使用事件标志组实现任务的通信或者同步机制。
RTX每个任务创建的时候,会自动创建16个事件标志,事件标志被存储到每个任务的任务控制块中。也就是说每个任务支持16个事件标志。下面我们通过如下的框图来说明一下RTX事件标志的实现,让大家有一个形象的认识。

运行条件:
u
创建2个任务Task1和Task2
运行过程描述如下:
u
任务Task1运行过程中调用函数os_evt_wait_and,等待事件标志位被设置,任务Task1由运行态进入到挂起态
u
任务Task2设置了任务Task1的事件标志,任务Task1由挂起态进入到就绪态,在调度器的作用下由就绪态又进入到运行态。
上面就是一个简单RTX任务间事件标志通信过程。
13.1.3 RTX中断方式事件标志组的实现
RTX中断方式事件标志组的实现是指中断函数和RTX任务之间使用事件标志。下面我们通过如下的框图来说明一下RTX事件标志的实现,让大家有一个形象的认识。

运行条件:
u
创建1个任务和一个串口接收中断
运行过程描述如下:
u
任务Task1运行过程中调用函数os_evt_wait_and,等待事件标志位被设置,任务Task1由运行态进入到挂起态
u
Task1挂起的情况下,串口接收到数据进入到了串口中断服务程序,在串口中断服务程序中设置Task1的事件标志,任务Task1由挂起态进入到就绪态,在调度器的作用下由就绪态又进入到运行态。
上面就是一个简单RTX中断方式事件标志通信过程。实际应用中,中断方式的消息机制切记注意以下四个个问题:
u
中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。
u
实际应用中,建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在任务中实现消息处理,这样可以有效的保证中断服务程序的实时响应。同时此任务也需要设置为高优先级,以便退出中断函数后任务可以得到及时执行。
u
中断服务程序中一定要调用专用于中断的事件标志设置函数isr_evt_set。
u
在RTX操作系统中实现中断函数跟裸机编程是一样的。
l
另外强烈推荐用户将Cortex-M3内核的STM32F103和Cortex-M4内核的STM32F407的NVIC优先级分组设置为4,即:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);这样中断优先级的管理将非常方便。
l
用户要在RTX多任务开启前就设置好优先级分组,一旦设置好切记不可再修改。
13.2事件标志组API函数
使用如下6个函数可以实现RTX的事件标志组:
u
os_evt_clr
u
os_evt_get
u
os_evt_set
u
os_evt_wait_and
u
os_evt_wait_or
u
isr_evt_set
关于这6个函数的讲解及其使用方法可以看教程第3章3.3小节里面说的参考资料rlarm.chm文件

这里我们重点的说一下函数os_evt_set,isr_evt_set和os_evt_wait_and,因为本章节配套的例子使用的是这三个函数。
13.2.1 函数os_evt_set
函数原型:
void
os_evt_set (
U16
event_flags,
OS_TID task
);
函数描述:
函数os_evt_set用于设置指定任务的事件标志。
u
第1个参数表示16个可设置的事件标志位。因为RTX的每个任务创建时有16个可设置的事件标志,这里用U16类型的变量event_flag就可以表示,变量event_flag的某个位设置为1,那么指定RTX任务的事件标志相应位就设置为1。变量event_flag设置为0的位对RTX任务的事件标志相应位没有影响。比如设置变量event_flag
=
0x0003就表示将RTX任务事件标志的位0和位1设置为1,其余位没有变化。
u
第2个参数是任务ID。
使用这个函数要注意以下问题:
1.
此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是isr_evt_set
使用举例:
#include
__task
void task1 (void) {
..
os_evt_set (0x0003, tsk2);
..
}
13.2.2函数isr_evt_set
函数原型:
void
isr_evt_set (
U16
event_flags,
OS_TID task
);
函数描述:
函数isr_evt_set用于设置指定任务的事件标志。
u
第1个参数表示16个可设置的事件标志位。因为RTX的每个任务创建时有16个可设置的事件标志,这里用U16类型的变量event_flag就可以表示,变量event_flag的某个位设置为1,那么指定RTX任务的事件标志相应位就设置为1。变量event_flag设置为0的位对RTX任务的事件标志相应位没有影响。比如设置变量event_flag
=
0x0003就表示将RTX任务事件标志的位0和位1设置为1,其余位没有变化。
u
第2个参数是任务ID。
使用这个函数要注意以下问题:
1.
此函数是用于中断服务程序中调用的,故不可以在任务中调用此函数,任务中使用的是os_evt_set。
2.
事件标志函数isr_evt_set的调用不能太频繁,太频繁的话会大大增加系统内核的开销,会造成事件标志得不到及时处理从而造成丢失事件标志的情况。
使用举例:
#include
void
EXTI0_IRQHandler (void){
..
isr_evt_set (0x0003, tsk2);
..
}
13.2.3函数os_evt_wait_and
函数原型:
OS_RESULT
os_evt_wait_and (
U16
wait_flags,
U16 timeout
);
函数描述:
函数os_evt_wait_and用于等待事件标志被设置。
u
第1个参数表示任务等待的事件标志位。因为RTX的每个任务创建时有16个可以设置的事件标志,这里用U16类型的变量event_flag就可以设置,变量event_flag的那位设置为1,那么RTX任务的事件标志就等待那个位被设置为1。而且要所有要求的位都被设置为1才可以。比如设置变量event_flag
=
0x0003就表示RTX任务在等待事件标志的位0和位1都被设置为1。
u
第2个参数表示设在的等待时间,范围0-0xFFFF,当参数设置为0-0xFFFE时,表示等这么多个时钟节拍,参数设置为0xFFFF时表示无限等待直到事件标志满足要求。
u
函数返回OS_R_EVT表示等待的事件标志位都被设置了,也就是返回成功。返回OS_R_TMO表示超时。
使用这个函数要注意以下问题:
1.
当要求的事件标志位都被设置为1时或者设置的超时时间溢出时,函数os_evt_wait_and才会返回。
2.
如果函数os_evt_wait_and返回前所要求的事件标志位都设置了,那么此函数会在返回前将相应的事件标志位清零,其它位不受此影响。如果初学者不是太理解这个问题,可以看本章节配套的例子,这样会有一个清晰的认识。
使用举例:
#include
#define
BIT_0
(1 << 0)
#define
BIT_1
(1 << 1)
#define
BIT_ALL (BIT_0 | BIT_1)
__task
void AppTaskMsgPro(void)
{
OS_RESULT
xResult;
const
uint16_t usMaxBlockTime = 500;
while(1)
{
xResult = os_evt_wait_and (BIT_ALL, usMaxBlockTime);
switch (xResult)
{
case OS_R_EVT:
printf("接收到bit0和bit1都被设置的消息\r\n");
break;
case OS_R_TMO:
bsp_LedToggle(1);
bsp_LedToggle(4);
break;
default:
break;
}
}
}
13.3实验例程说明(任务间通信)
13.3.1 STM32F103开发板实验
配套例子:
V4-409_RTX实验_事件标志组
实验目的:
1. 学习RTX的事件标志组
实验内容:
1.
K1按键按下,串口打印。
2.
K2键按下,直接发送事件标志给任务AppTaskMsgPro,设置bit0
。
3.
K3键按下,直接发送事件标志给任务AppTaskMsgPro,设置bit1
。
4.
任务AppTaskMsgPro只有接收到bit0和bit1都被设置了才执行串口打印信息。
5.
各个任务实现的功能如下:
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务
:LED闪烁。
AppTaskMsgPro任务
:消息处理,等待任务AppTaskUserIF发来的事件标志。
AppTaskStart任务
:启动任务,也是最高优先级任务,这里实现按键扫描。
RTX配置:
RTX配置向导详情如下:

u
Task
Configuration
l
Number of concurrent
running tasks
允许创建4个任务,实际创建了如下四个任务
AppTaskUserIF任务
:按键消息处理。
AppTaskLED任务
:LED闪烁。
AppTaskMsgPro任务
:消息处理,等待任务AppTaskUserIF发来的事件标志。
AppTaskStart任务
:启动任务,也是最高优先级任务,这里实现按键扫描。
l
Number of tasks with
user-provided stack
创建的4个任务都是采用自定义堆栈方式。
RTX任务调试信息:

u
Event
Value:任务AppTaskMsgPro当前的事件标志数值。
u
Event
Mask:任务AppTaskMsgPro等待的事件标志数值。
程序设计:
u
任务栈大小分配:
static uint64_t
AppTaskUserIFStk[512/8];
static uint64_t
AppTaskLEDStk[256/8];
static uint64_t
AppTaskMsgProStk[512/8];
static uint64_t
AppTaskStartStk[512/8];
将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数,浮点运算和uint64_t类型数据运算会出问题。
u
系统栈大小分配:

u
RTX初始化:
int main
(void)
{
bsp_Init();
os_sys_init_user
(AppTaskStart,
4,
&AppTaskStartStk,
sizeof(AppTaskStartStk));
while(1);
}
u
RTX任务创建:
static
void AppTaskCreate (void)
{
HandleTaskUserIF =
os_tsk_create_user(AppTaskUserIF,
1,
&AppTaskUserIFStk,
sizeof(AppTaskUserIFStk));
HandleTaskLED =
os_tsk_create_user(AppTaskLED,
2,
&AppTaskLEDStk,
sizeof(AppTaskLEDStk));
HandleTaskMsgPro =
os_tsk_create_user(AppTaskMsgPro,
3,
&AppTaskMsgProStk,
sizeof(AppTaskMsgProStk));
}
u
四个RTX任务的实现:
__task
void AppTaskUserIF(void)
{
uint8_t ucKeyCode;
while(1)
{
ucKeyCode = bsp_GetKey();
if (ucKeyCode != KEY_NONE)
{
switch (ucKeyCode)
{
case KEY_DOWN_K1:
printf("K1键按下,使用MDK中自带的RTX调试组件,请务必使用MDK4.74版本进行调试\r\n");
break;
case KEY_DOWN_K2:
printf("K2键按下,直接发送事件标志给任务AppTaskMsgPr,bit0被设置\r\n");
os_evt_set (BIT_0, HandleTaskMsgPro);
break;
case KEY_DOWN_K3:
printf("K3键按下,直接发送事件标志给任务AppTaskMsgPro,bit1被设置\r\n");
os_evt_set (BIT_1, HandleTaskMsgPro);
break;
default:
break;
}
}
os_dly_wait(20);
}
}
__task
void AppTaskLED(void)
{
const uint16_t usFrequency = 200;
os_itv_set(usFrequency);
while(1)
{
bsp_LedToggle(2);
bsp_LedToggle(3);
os_itv_wait();
}
}
__task
void AppTaskMsgPro(void)
{
OS_RESULT xResult;
const uint16_t usMaxBlockTime = 500;
while(1)
{
xResult = os_evt_wait_and (BIT_ALL, usMaxBlockTime);
switch (xResult)
{
case OS_R_EVT:
printf("接收到bit0和bit1都被设置的消息\r\n");
break;
case OS_R_TMO:
bsp_LedToggle(1);
bsp_LedToggle(4);
break;
default:
break;
}
}
}
__task
void AppTaskStart(void)
{
AppTaskCreate();
while(1)
{
bsp_KeyScan();
os_dly_wait(10);
}
}
13.3.2 STM32F407开发板实验
配套例子:
V5-409_RTX实验_事件标志组
实验目的:
1. 学习RTX的事件标志组
实验内容:
1. K1按键按下,串口打印。
2. K2键按下,直接发送事件标志给任务AppTaskMsgPro,设置bit0
。
3. K3键按下,直接发送事件标志给任务AppTaskMsgPro,设置bit1
。
4. 任务AppTaskMsgPro只有接收到bit0和bit1都被设置了才执行串口打印信息。
5. 各个任务实现的功能如下:
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务
:LED闪烁。
AppTaskMsgPro任务
:消息处理,等待任务AppTaskUserIF发来的事件标志。
AppTaskStart任务
:启动任务,也是最高优先级任务,这里实现按键扫描。
RTX配置:
RTX配置向导详情如下:

u
Task
Configuration
l
Number of concurrent
running tasks
允许创建4个任务,实际创建了如下四个任务
AppTaskUserIF任务
:按键消息处理。
AppTaskLED任务
:LED闪烁。
AppTaskMsgPro任务
:消息处理,等待任务AppTaskUserIF发来的事件标志。
AppTaskStart任务
:启动任务,也是最高优先级任务,这里实现按键扫描。
l
Number of tasks with
user-provided stack
创建的4个任务都是采用自定义堆栈方式。
RTX任务调试信息:

u
Event
Value:任务AppTaskMsgPro当前的事件标志数值。
u
Event
Mask:任务AppTaskMsgPro等待的事件标志数值。
程序设计:
u
任务栈大小分配:
static uint64_t
AppTaskUserIFStk[512/8];
static uint64_t
AppTaskLEDStk[256/8];
static uint64_t
AppTaskMsgProStk[512/8];
static uint64_t
AppTaskStartStk[512/8];
将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数,浮点运算和uint64_t类型数据运算会出问题。
u
系统栈大小分配:

u
RTX初始化:
int main
(void)
{
bsp_Init();
os_sys_init_user
(AppTaskStart,
4,
&AppTaskStartStk,
sizeof(AppTaskStartStk));
while(1);
}
u
RTX任务创建:
static
void AppTaskCreate (void)
{
HandleTaskUserIF =
os_tsk_create_user(AppTaskUserIF,
1,
&AppTaskUserIFStk,
sizeof(AppTaskUserIFStk));
HandleTaskLED = os_tsk_create_user(AppTaskLED,
2,
&AppTaskLEDStk,
sizeof(AppTaskLEDStk));
HandleTaskMsgPro =
os_tsk_create_user(AppTaskMsgPro,
3,
&AppTaskMsgProStk,
sizeof(AppTaskMsgProStk));
}
u
四个RTX任务的实现:
__task
void AppTaskUserIF(void)
{
uint8_t ucKeyCode;
while(1)
{
ucKeyCode = bsp_GetKey();
if (ucKeyCode != KEY_NONE)
{
switch (ucKeyCode)
{
case KEY_DOWN_K1:
printf("K1键按下,使用MDK中自带的RTX调试组件,请务必使用MDK4.74版本进行调试\r\n");
break;
case KEY_DOWN_K2:
printf("K2键按下,直接发送事件标志给任务AppTaskMsgPr,bit0被设置\r\n");
os_evt_set (BIT_0, HandleTaskMsgPro);
break;
case KEY_DOWN_K3:
printf("K3键按下,直接发送事件标志给任务AppTaskMsgPro,bit1被设置\r\n");
os_evt_set (BIT_1, HandleTaskMsgPro);
break;
default:
break;
}
}
os_dly_wait(20);
}
}
__task
void AppTaskLED(void)
{
const uint16_t usFrequency = 200;
os_itv_set(usFrequency);
while(1)
{
bsp_LedToggle(2);
bsp_LedToggle(3);
os_itv_wait();
}
}
__task
void AppTaskMsgPro(void)
{
OS_RESULT xResult;
const uint16_t usMaxBlockTime = 500;
while(1)
{
xResult = os_evt_wait_and (BIT_ALL, usMaxBlockTime);
switch (xResult)
{
case OS_R_EVT:
printf("接收到bit0和bit1都被设置的消息\r\n");
break;
case OS_R_TMO:
bsp_LedToggle(1);
bsp_LedToggle(4);
break;
default:
break;
}
}
}
__task
void AppTaskStart(void)
{
AppTaskCreate();
while(1)
{
bsp_KeyScan();
os_dly_wait(10);
}
}
加载中,请稍候......