[TOC]

UCOS-Ⅲ:事件

一、事件基本概念

任务需要同步的时候可以用信号量,首先A任务等待B任务发送信号量,B任务在A、B任务需要同步的时刻发送信号量给任务A,这个时候在μC/OS-III内核的管理下,A、B任务“同时”继续执行。如果同步的时刻需要任务A 传递数据给任务B,那么就用消息队列。但是如果遇到复杂的任务同步,比如多个事件都发生后执行其他事件,比如任务D需要任务E和任务F分别采集完两种数据后进行数据处理,用信号量和消息队列就有点困难,但事件标志的出现轻易地解决了这些复杂的同步。

uC/OS间的事件通信只能是事件类型的通信,无数据传输,每一个事件组只需要很少的RAM 空间来保存事件组的状态。事件组存储在一个OS_FLAGS 类型的Flags 变量中,该变量在事件结构体中定义。而变量的宽度由我们自己定义,可以是8 位、16 位、32 位的变量,取决os_type.h 中的OS_FLAGS 的位数。在STM32 中,我们一般将其定义为32 位的变量,有32 个位用来实现事件标志组。每一位代表一个事件,任务通过“逻辑与”或“逻辑或”与一个或多个事件建立关联,形成一个事件组。事件的“逻辑或”也被称作是独立型同步,指的是任务感兴趣的所有事件任一件发生即可被唤醒;事件“逻辑与”则被称为是关联型同步,指的是任务感兴趣的若干事件都发生时才被唤醒,并且事件发生的时间可以不同步

uC/OS内事件其具有以下特点:

  • 事件只与任务相关联,事件相互独立,一个32 位(数据宽度由用户定义)的事件集合用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、1 表示该事件类型已经发生),一共32 种事件类型。

  • 事件仅用于同步,不提供数据传输功能。

  • 事件无排队性,即多次向任务设置同一事件(如果任务还未来得及读走),等效于只设置一次。

  • 允许多个任务对同一事件进行读写操作。

  • 支持事件等待超时机制。

  • 支持显式清除事件。

事件标志组裁剪控制在os_cfg.h中

1
2
3
4
5
                                             /* ----------------------------- EVENT FLAGS --------------------------- */
#define OS_CFG_FLAG_EN 1u //使能/禁用事件标志组
#define OS_CFG_FLAG_DEL_EN 1u //使能/禁用 OSFlagDel() 函数
#define OS_CFG_FLAG_MODE_CLR_EN 1u //使能/禁用标志位清0触发模式
#define OS_CFG_FLAG_PEND_ABORT_EN 1u //使能/禁用 OSFlagPendAbort() 函数

事件标志组是一个内核对象,由数据类型OS_FLAG_GRP 定义,该数据类型由os_flag_grp 定义(见OS.H),与事件标志组相关的代码在OS_FLAG.C 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct  os_flag_grp {                                       /* Event Flag Group                                       */
/* ------------------ GENERIC MEMBERS ------------------ */
OS_OBJ_TYPE Type; /* Should be set to OS_OBJ_TYPE_FLAG */
CPU_CHAR *NamePtr; /* Pointer to Event Flag Name (NUL terminated ASCII) */
OS_PEND_LIST PendList; /* List of tasks waiting on event flag group */
#if OS_CFG_DBG_EN > 0u
OS_FLAG_GRP *DbgPrevPtr;
OS_FLAG_GRP *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
/* ------------------ SPECIFIC MEMBERS ------------------ */
OS_FLAGS Flags; /* 8, 16 or 32 bit flags */
CPU_TS TS; /* Timestamp of when last post occurred */
};

在uC/OS-III 中,所有的结构体都会定义一个数据类型。都是以”OS_”开头并且全部大写。

  • Type:结构体的第一个变量为”Type”。用于辨认该对象为事件标志组。

  • NamePtr:每个内核对象都可以被分配一个名字。

  • PendList:因为可以有多个任务同时等待事件标志组中的事件,所以事件标志组中包含了一个用于控制挂起队列的结构体。

  • Ctr:事件标志组中包含了很多标志位,这个变量中保存了当前这些标志位的状态。这个变量可以为8 位,16 位或32 位。决定于OS_TYPE.H 中的OS_FLAGS 的位数。

  • TS:事件标志组中包含了一个变量,存储了最后一次标志被提交的时间戳。

二、事件运作机制

等待事件时,可以根据感兴趣的参事件类型等待事件的单个或者多个事件类型。事件等待成功后,必须使用OS_OPT_PEND_FLAG_CONSUME 选项来清除已接收到的事件类型,否则不会清除已接收到的事件,这样就需要用户显式清除事件位。用户可以自定义通过传入opt 选项来选择读取模式,是等待所有感兴趣的事件还是等待感兴趣的任意一个事件。

设置事件时,对指定事件写入指定的事件类型,设置事件集合的对应事件位为1,可以一次同时写多个事件类型,设置事件成功可能会触发任务调度。

清除事件时,根据入参数事件句柄和待清除的事件类型,对事件对应位进行清0 操作。事件不与任务相关联,事件相互独立,一个32 位的变量就是事件的集合,用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、1 表示该事件类型已经发生)

三、调用API

UCOS中事件的调用API有以下四个API,分别为创建、删除、等待、发送事件

2.1 创建事件函数OSFlagCreate()

OSFlagCreate()函数进行创建一个事件,函数参数如下,事件标志组的创建也是跟前面其他内核对象差不多,我们看到了第三个参数设置为0,这个是赋给事件标志组变量的元素Flags 的值,典型值是0,表示所有的位都没有被设置过,当然如果要表示创建的时候某个位已经被置1,可以设置将相应的位置1 后作为参数输入。一般在事件标志组中,某事件发生会对相应的位置1,表示已经发生。在上面的例子中表示一开始按键都没有按下。

1
2
3
4
void OSFlagCreate (OS_FLAG_GRP *p_grp, //事件指针
CPU_CHAR *p_name, //命名事件
OS_FLAGS flags, //标志初始值
OS_ERR *p_err) //返回错误类型

介绍:

p_grp 指向事件标志组变量指针
p_name 指向事件标志组名字字符串的指针。
flags 标志初始值
p_err 指向返回错误类型的指针。

使用实例:

1
2
3
4
5
6
7
8
//定义事件主体
OS_FLAG_GRP flag_grp; //声明事件标志组

/* 创建事件标志组 flag_grp */
OSFlagCreate ((OS_FLAG_GRP *)&flag_grp, //指向事件标志组的指针
(CPU_CHAR *)"FLAG For Test", //事件标志组的名字
(OS_FLAGS )0, //事件标志组的初始值
(OS_ERR *)&err); //返回错误类型

2.2 事件删除函数OSFlagDel()

OSFlagDel()用于将事件进行删除

函数入口:

1
2
3
OS_OBJ_QTY OSFlagDel (  OS_FLAG_GRP *p_grp, //事件指针
OS_OPT opt, //选项
OS_ERR *p_err) //返回错误类型
参数名称 参数作用
p_grp 指向事件变量的指针。
opt 删除事件量时候的选项,有以下两个选择。
p_err 指向返回错误类型的指针,有以下几种可能。

参数选项选择

选项 作用
OS_OPT_DEL_NO_PEND 当事件的等待列表上面没有相应的任务的时候才删除事件。
OS_OPT_DEL_ALWAYS 不管事件的等待列表是否有相应的任务都删除事件,删除之前,系统会把所有阻塞在该事件上的任务恢复

错误类型

错误返回值 错误类型
OS_ERR_DEL_ISR 企图在中断中删除事件。
OS_ERR_OBJ_PTR_NULL 参数p_grp是空指针。
OS_ERR_OBJ_TYPE 参数p_grp指向的内核变量类型不是事件
OS_ERR_OPT_INVALID opt 在给出的选项之外
OS_ERR_TASK_WAITING 在选项opt 是OS_OPT_DEL_NO_PEND 的时候,并且事件的等待列表上有等待的任务。

同时该函数有一个返回值,返回值的含义为:

删除事件的时候,会将事件等待列表上的任务脱离该事件的等待列表。返回值表示的就是脱离等待列表的任务个数。

使用实例

1
2
3
4
5
6
7
8
OS_FLAG_GRP flag_grp;; //声明事件句柄

OS_ERR err;

/* 删除事件 */
OSFlagDel((OS_FLAG_GRP *)& flag_grp, //指向事件的指针
OS_OPT_DEL_NO_PEND,
(OS_ERR *)&err); //返回错误类型

2.3 事件设置函数OSFlagPost()

OSFlagPost()用于设置事件组中指定的位,当位被置位之后,并且满足任务的等待事件,那么等待在事件该标志位上的任务将会被恢复。使

函数入口:

1
2
3
4
OS_FLAGS  OSFlagPost (OS_FLAG_GRP  *p_grp, //事件标志组指针
OS_FLAGS flags, //选定要操作的标志位
OS_OPT opt, //选项
OS_ERR *p_err) //返回错误类型
参数名称 参数作用
p_grp 指向要提交的事件的指针
flags 想要设置的标志位。
opt 置位选项,可以是以下两个选项之一
p_err 指向返回错误类型的指针,错误的类型如下。(只列了必要部分)

选项列表:

选项 功能
OS_OPT_POST_FLAG_SET 参数flags 选定的标志位全部置1。
OS_OPT_POST_FLAG_CLR 参数flags 选定的标志位全部置0。

主要错误值:

错误值 错误含义
OS_ERR_OBJ_PTR_NULL 参数p_grp 是空指针。
OS_ERR_OBJ_TYPE 参数p_grp 指向的变量类型不是事件标志组
OS_ERR_OPT_INVALID 参数opt 不是指定的选项之一

返回值:

事件标志位设置完成,返回事件的当前标志值。

使用实例:

1
2
3
4
5
6
7
8
9
OS_FLAG_GRP flag_grp;                   //声明事件标志组

#define KEY1_EVENT (0x01 << 0)//设置事件掩码的位0
#define KEY2_EVENT (0x01 << 1)//设置事件掩码的位1

OSFlagPost ((OS_FLAG_GRP *)&flag_grp, //将标志组的BIT1置1
(OS_FLAGS )KEY2_EVENT,
(OS_OPT )OS_OPT_POST_FLAG_SET,
(OS_ERR *)&err);

2.4 事件等待函数OSFlagPend()

uCOS 提供了一个等待指定事件的函数——OSFlagPend(),通过这个函数,任务可以知道事件标志组中的哪些位,有什么事件发生了,然后通过 “逻辑
与”、“逻辑或”等操作对感兴趣的事件进行获取,并且这个函数实现了等待超时机制,当且仅当任务等待的事件发生时,任务才能获取到事件信息。在这段时间中,如果事件一直没发生,该任务将保持阻塞状态以等待事件发生。当其它任务或中断服务程序往其等待的事件设置对应的标志位,该任务将自动由阻塞态转为就绪态。当任务等待的时间超过了指定的阻塞时间,即使事件还未发生,任务也会自动从阻塞态转移为就绪态

函数入口:

1
2
3
4
5
6
OS_FLAGS  OSFlagPend (OS_FLAG_GRP  *p_grp,   //事件标志组指针
OS_FLAGS flags, //选定要操作的标志位
OS_TICK timeout, //等待期限(单位:时钟节拍)
OS_OPT opt, //选项
CPU_TS *p_ts, //返回等到事件标志时的时间戳
OS_ERR *p_err) //返回错误类型

参数:

参数 作用
p_grp 指向要获取的事件变量的指针。
opt 可能是以下几个选项之一。
timeout 这个参数是设置的是获取不到等待事件时候等待的时间。如果这个值为0,表示一直等待下去,如果这个值不为0,则最多等待timeout 个时钟节拍。
p_ts 指向等待的事件被删除、等待被强制停止、等待超时等情况时的时间戳的指针。
p_err 指向返回错误类型的指针,有以下几种类型。
flags 选定要操作的标志位

功能选项:

功能 作用
OS_OPT_PEND_FLAG_CLR_ALL 等待上面flags 选定的位都被清0。
OS_OPT_PEND_FLAG_CLR_ANY 等待上面flags 选定的位任意一位被清0。
OS_OPT_PEND_FLAG_SET_ALL 等待上面flags 选定的位都被置1。
OS_OPT_PEND_FLAG_SET_ANY 等待上面flags 选定的位任意一位被置1。
OS_OPT_PEND_FLAG_CONSUME 这个选项主要是获取到任务指定的位都被设置好后对这些位进行置反操作,注意不是清零。这个选项可以起到在满足条件后自动复位的作用。上面4 个任意选项之一可以与上这个选项。
OS_OPT_PEND_NON_BLOCKING 如果一开始判断指定位的设置情况不满足不继续等待,直接退出函数继续运行任务。
OS_OPT_PEND_BLOCKING 如果一开始判断指定位的设置情况不满足将任务置于等待状态,这个属于默认选项,参数输入没有选定等待还是不等待默认是等待。最后这两个选项之一可以与上前面的任意选项。

主要错误类型:

错误 类型
OS_ERR_OBJ_DEL 事件已经被删除了。
OS_ERR_OBJ_PTR_NULL 输入的事件变量指针是空类型。
OS_ERR_OBJ_TYPE p_grp指向的变量内核对象类型不是事件。
OS_ERR_OPT_INVALID 参数opt 不符合要求。
OS_ERR_PEND_ABORT 等待过程,其他的任务调用了函数OSFlagPendAbort 强制取消等待。
OS_ERR_PEND_ISR 企图在中断中等待事件。
OS_ERR_PEND_WOULD_BLOCK 开始获取不到事件,且没有要求等待。
OS_ERR_SCHED_LOCKED 调度器被锁住。
OS_ERR_STATUS_INVALID 系统出错,导致任务控制块的元素PendStatus 不在可能的范围内。
OS_ERR_TIMEOUT 等待超时。
OS_ERR_NONE 成功获取

返回值:

OSFlagPend()的返回有4 种可能

  • 所等待的标志位被设置

  • 挂起被其它任务取消

  • 等待超时

  • 事件标志组被删除

使用实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  OS_FLAG_GRP flag_grp;                   //声明事件标志组

#define KEY1_EVENT (0x01 << 0)//设置事件掩码的位0
#define KEY2_EVENT (0x01 << 1)//设置事件掩码的位1

//等待标志组的的BIT0和BIT1均被置1
flags_rdy = OSFlagPend ((OS_FLAG_GRP *)&flag_grp,
(OS_FLAGS )( KEY1_EVENT | KEY2_EVENT ),
(OS_TICK )0,
(OS_OPT )OS_OPT_PEND_FLAG_SET_ALL |
OS_OPT_PEND_BLOCKING |
OS_OPT_PEND_FLAG_CONSUME,
(CPU_TS *)0,
(OS_ERR *)&err);
if((flags_rdy & (KEY1_EVENT|KEY2_EVENT)) == (KEY1_EVENT|KEY2_EVENT))
{
/* 如果接收完成并且正确 */
printf ( "KEY1与KEY2都按下\n");
LED1_TOGGLE; //LED1 反转
}

四、使用实例

创建两个任务:一个事件发送、一个事件接收任务,按键1按下事件1置位,按键2按下事件2,置位,等待事件任务在等待到1和2都按下时才脱离等待列表,开始运行,代码主体如下:

  • 声明变量
1
2
3
4
5
6
7
8
9
10
/*
*********************************************************************************************************
* LOCAL DEFINES
*********************************************************************************************************
*/

OS_FLAG_GRP flag_grp; //声明事件标志组

#define KEY1_EVENT (0x01 << 0)//设置事件掩码的位0
#define KEY2_EVENT (0x01 << 1)//设置事件掩码的位1
  • 创建信号
1
2
3
4
5
/* 创建事件标志组 flag_grp */
OSFlagCreate ((OS_FLAG_GRP *)&flag_grp, //指向事件标志组的指针
(CPU_CHAR *)"FLAG For Test", //事件标志组的名字
(OS_FLAGS )0, //事件标志组的初始值
(OS_ERR *)&err); //返回错误类型
  • 编写任务主体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/*
*********************************************************************************************************
* POST TASK
*********************************************************************************************************
*/
static void AppTaskPost ( void * p_arg )
{
OS_ERR err;


(void)p_arg;


while (DEF_TRUE) { //任务体
if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) //如果KEY1被按下
{ //点亮LED1
printf("KEY1被按下\n");
OSFlagPost ((OS_FLAG_GRP *)&flag_grp, //将标志组的BIT0置1
(OS_FLAGS )KEY1_EVENT,
(OS_OPT )OS_OPT_POST_FLAG_SET,
(OS_ERR *)&err);

}

if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) //如果KEY2被按下
{ //点亮LED2
printf("KEY2被按下\n");
OSFlagPost ((OS_FLAG_GRP *)&flag_grp, //将标志组的BIT1置1
(OS_FLAGS )KEY2_EVENT,
(OS_OPT )OS_OPT_POST_FLAG_SET,
(OS_ERR *)&err);

}

OSTimeDlyHMSM ( 0, 0, 0, 20, OS_OPT_TIME_DLY, & err ); //每20ms扫描一次

}

}


/*
*********************************************************************************************************
* PEND TASK
*********************************************************************************************************
*/
static void AppTaskPend ( void * p_arg )
{
OS_ERR err;
OS_FLAGS flags_rdy;

(void)p_arg;


while (DEF_TRUE) { //任务体
//等待标志组的的BIT0和BIT1均被置1
flags_rdy = OSFlagPend ((OS_FLAG_GRP *)&flag_grp,
(OS_FLAGS )( KEY1_EVENT | KEY2_EVENT ),
(OS_TICK )0,
(OS_OPT )OS_OPT_PEND_FLAG_SET_ALL |
OS_OPT_PEND_BLOCKING |
OS_OPT_PEND_FLAG_CONSUME,
(CPU_TS *)0,
(OS_ERR *)&err);
if((flags_rdy & (KEY1_EVENT|KEY2_EVENT)) == (KEY1_EVENT|KEY2_EVENT))
{
/* 如果接收完成并且正确 */
printf ( "KEY1与KEY2都按下\n");
LED1_TOGGLE; //LED1 反转
}


}

}

wechat