09. µCOS-Ⅲ的事件标志组
一、µC/OS-Ⅲ的事件标志简介
1.1、事件标志简介
事件标志 是一个用于指示事件是否发生的比特位,因为一个事件是否发生只有两种情况,分别为事件发生和事件未发生,因此只需一个比特位就能够表示事件是否发生,µC/OS-Ⅲ 用 1 表示事件发生,用 0 表示事件未发生。
事件标志组是多个事件的集合,事件标志组的结构体定义在文件 os.h 中,具体的代码如下所示:
struct os_flag_grp
{
#if (OS_OBJ_TYPE_REQ > 0u) // 此宏用于使能检查内核对象的类型
// 对象类型,在信号量初始化时,信号量的类型会被初始化为OS_OBJ_TYPE_FLAG
OS_OBJ_TYPE Type;
#endif
#if (OS_CFG_DBG_EN > 0u) // 此宏用于使能代码调试功能
// 如果使能了代码调试功能,那么就会为每一个信号量赋一个名称,方便调试
CPU_CHAR *NamePtr;
#endif
// 挂起等待任务链表,当任务获取的事件还未发生时,当任务获取的事件还未发生时,等待事件发生
OS_PEND_LIST PendList;
#if (OS_CFG_DBG_EN > 0u) // 此宏用于使能代码调试功能
// 一些与代码调试相关的成员变量
OS_FLAG_GRP *DbgPrevPtr;
OS_FLAG_GRP *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
// 事件表示的集合,对于32位MCU,这一成员变量是一个32位的无符号数,一比特位表示一个事件标志,因此一个事件标志组最多容纳32个事件标志
OS_FLAGS Flags;
// 以下的成员变量用于第三方的调试工具
#if (OS_CFG_TS_EN > 0u)
CPU_TS TS;
#endif
#if (defined(OS_CFG_TRACE_EN) && (OS_CFG_TRACE_EN > 0u))
CPU_ADDR FlagID;
#endif
};
从上面的代码中可以看到,事件标志组的结构体中包含了一个数据类型为 OS_FLAGS 的成员变量 Flags,对于 32 位的 MCU 而言,数据类型 OS_FLAGS 实际上就是 32 位的无符号整型,因此成员变量 Flags 就包含了 32 比特,而 Flags 的每一个比特就能够用来存储一个事件标志。当成员变量 Flags 的某一比特位被置一时,就说明这一比特位对应的事件发生了。
它的每一个位表示一个事件(最多可以表示 32 个事件标志)。
每一位事件的含义,由用户自己决定,这些位的值为 1:表示事件发生了;值为 0:表示事件未发生。
任意任务或中断都可以写这些位,但读这些位只能由任务。
可以等待某一位成立,或者等待多位同时成立。
1.2、事件标志操作简介
事件标志组 由多个事件标志组成,因此任务就能够很方便地通过多个事件的发生进行任务间的同步。通过事件标志,任务还能够通过多个事件之间的逻辑关系(与、或关系)进行任务间的同步,如下图所示:
可以等待某一位成立,或者等待多位同时成立。
任意任务或中断都可以写这些位,但读这些位只能由任务。
支持读取阻塞。
二、事件标志相关API函数
2.1、创建一个事件标志组
void OSFlagCreate (OS_FLAG_GRP *p_grp, // 指向事件标志组结构体的指针
CPU_CHAR *p_name, // 指向作为事件标志组名的 ASCII 字符串的指针
OS_FLAGS flags, // 事件标志组的初始值
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSFlagCreate()的错误代码描述,如下所示:
OS_ERR_NONE // 事件标志组创建成功
OS_ERR_CREATE_ISR // 在中断中非法调用该函数
OS_ERR_ILLEGAL_CREATE_RUN_TIME // 在系统运行过程中非法创建内核对象
OS_ERR_OBJ_PTR_NULL // 指向事件标志组结构体的指针为空
2.2、删除一个事件标志组
// 返回值:被终止挂起等待事件标志任务的数量
OS_OBJ_QTY OSFlagDel (OS_FLAG_GRP *p_grp, // 指向事件标志组结构体的指针
OS_OPT opt, // 函数操作选项
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSFlagDel() 的错误代码描述,如下所示:
OS_ERR_NONE // 事件标志组删除成功
OS_ERR_DEL_ISR // 在中断中非法调用该函数
OS_ERR_ILLEGAL_DEL_RUN_TIME // 在系统运行过程中非法删除内核对象
OS_ERR_OBJ_PTR_NULL // 指向事件标志组结构体的指针为空
OS_ERR_OBJ_TYPE // 待被删除内核对象的类型不是事件标志组
OS_ERR_OPT_INVALID // 无效的任务操作选项
OS_ERR_OS_NOT_RUNING // µC/OS-Ⅲ 内核还未运行
OS_ERR_TASK_WAITING // 有任务挂起等待待被删除的事件标志组
2.3、等待事件标志组中的事件
// 返回值:任务实际等待到的事件标志
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); // 指向接收错误代码变量的指针
函数 OSFlagPend() 的函数操作选项描述,如下所示:
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 // 当等待到指定位后,清0对应位
OS_OPT_PEND_BLOCKING // 标志组不满足条件时挂起任务
OS_OPT_PEND_NON_BLOCKING // 标志组不满足条件时不挂起任务
函数 OSFlagPend() 的错误代码描述,如下所示:
OS_ERR_NONE // 没有错误
OS_ERR_OBJ_DEL // 指定的事件标志组已经被删除
OS_ERR_OBJ_PTR_NULL // 指向事件标志组结构体的指针为空
OS_ERR_OBJ_TYPE // 操作的内核对象的类型不是事件标志组
OS_ERR_OPT_INVALID // 无效的函数操作选项
OS_ERR_OS_NOT_RUNING // μC/OS-Ⅲ 内核还未运行
OS_ERR_PEND_ABORT // 任务挂起等待事件标志组时被终止(还未超时)
OS_ERR_PEND_ISR // 在中断中非法调用该函数
OS_ERR_PEND_WOULD_BLOCK // 等待事件失败,并且不挂起任务等待事件
OS_ERR_SCHED_LOCKED // 任务调度器被锁定
OS_ERR_STATUS_INVALID // 无效的任务挂起结果
OS_ERR_TIMEOUT // 任务挂起等待事件标志组超时
2.4、获取任务等待到的事件
// 返回值:任务获取到的事件标志
OS_FLAGS OSFlagPendGetFlagsRdy (OS_ERR *p_err); // p_err 指向接收错误代码变量的指针
函数 OSFlagPendGetFlagsRdy() 的错误代码描述,如下所示:
OS_ERR_NONE // 获取任务获取到的事件标志成功
OS_ERR_OS_NOT_RUNING // µC/OS-Ⅲ 内核还未运行
OS_ERR_PEND_ISR // 在中断中非法调用该函数
2.5、设置事件标志组中的事件
// 返回值:事件标志组更新后的事件标志值
OS_FLAGS OSFlagPost (OS_FLAG_GRP *p_grp, // 指向事件标志组结构体的指针
OS_FLAGS flags, // 等待的事件标志
OS_OPT opt, // 函数操作选项
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSFlagPost() 的函数操作选项描述,如下所示:
OS_OPT_POST_FLAG_SET
OS_OPT_POST_FLAG_CLR
函数 OSFlagPost() 的错误代码描述,如下所示:
OS_ERR_NONE // 设置事件标志成功
OS_ERR_OBJ_PTR_NULL // 指向事件标志组结构体的指针为空
OS_ERR_OBJ_TYPE // 操作的内核对象的类型不是事件标志组
OS_ERR_OPT_INVALID // 无效的函数操作选项
OS_ERR_OS_NOT_RUNING // μC/OS-Ⅲ 内核还未运行
2.6、终止挂起等待事件标志组中的事件
// 返回值:被终止挂起任务的数量
OS_OBJ_QTY OSFlagPendAbort (OS_FLAG_GRP *p_grp, // 指向事件标志组结构体的指针
OS_OPT opt, // 函数操作选项
OS_ERR *p_err); // 指向接收错误代码变量的指针
函数 OSFlagPendAbort() 的错误代码描述,如下所示:
OS_ERR_NONE // 终止任务挂起等待事件标志组成功
OS_ERR_OBJ_PTR_NULL // 指向事件标志组结构体的指针为空
OS_ERR_OBJ_TYPE // 操作的内核对象的类型不是事件标志组
OS_ERR_OBJ_TYPE // 无效的函数操作选项
OS_ERR_OS_NOT_RUNING // μC/OS-Ⅲ 内核还未运行
OS_ERR_PEND_ABORT_ISR // 在中断中非法调用该函数
OS_ERR_PEND_ABORT_NONE // 没有任务挂起等待事件标志组
三、实验例程
main() 函数内容如下:
int main(void)
{
HAL_Init();
System_Clock_Init(8, 336, 2, 7);
Delay_Init(168);
UART_Init(&g_usart1_handle, USART1, 115200);
Key_Init();
UC_OS3_Demo();
return 0;
}
µC/OS-Ⅲ 例程入口函数:
/**
* @brief µC/OS-Ⅲ例程入口函数
*
*/
void UC_OS3_Demo(void)
{
OS_ERR error = {0};
OSInit(&error); // 初始化µC/OS-Ⅲ
// 创建开始任务
OSTaskCreate((OS_TCB * ) &start_task_tcb, // 任务控制块
(CPU_CHAR * ) "start_task", // 任务名
(OS_TASK_PTR ) Start_Task, // 任务函数
(void * ) 0, // 任务参数
(OS_PRIO ) START_TASK_PRIORITY, // 任务优先级
(CPU_STK * ) start_task_stack, // 任务堆栈
(CPU_STK_SIZE) START_TASK_STACK_SIZE / 10, // 任务栈的使用警戒线
(CPU_STK_SIZE) START_TASK_STACK_SIZE, // 任务栈大小
(OS_MSG_QTY ) 0, // 消息队列长度
(OS_TICK ) 0, // 时间片长度
(void * ) 0, // 扩展内存
(OS_OPT ) (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), // 任务选项
(OS_ERR * ) &error); // 错误码
OSStart(&error); // 开始任务调度
}
START_TASK 任务配置:
OS_FLAG_GRP flag; // 事件标志组
/**
* START_TASK 任务配置
* 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define START_TASK_PRIORITY 1
#define START_TASK_STACK_SIZE 256
OS_TCB start_task_tcb;
CPU_STK start_task_stack[START_TASK_STACK_SIZE];
void Start_Task(void *p_arg);
/**
* @brief 开始任务的任务函数
*
* @param p_arg 任务参数
*/
void Start_Task(void *p_arg)
{
OS_ERR error = {0};
CPU_INT32U cnts = 0;
CPU_Init(); // 初始化CPU库
CPU_SR_ALLOC(); // 临界区保护
cnts = HAL_RCC_GetSysClockFreq() / OS_CFG_TICK_RATE_HZ;
OS_CPU_SysTickInit(cnts); // 根据配置的节拍频率配置SysTick中断及优先级
OSFlagCreate(&flag, "flag", 0, &error); // 创建一个事件标志组
CPU_CRITICAL_ENTER(); // 进入临界区
// 创建任务1
OSTaskCreate((OS_TCB * ) &task1_tcb, // 任务控制块
(CPU_CHAR * ) "task1", // 任务名
(OS_TASK_PTR ) Task1, // 任务函数
(void * ) 0, // 任务参数
(OS_PRIO ) TASK1_PRIORITY, // 任务优先级
(CPU_STK * ) task1_stack, // 任务堆栈
(CPU_STK_SIZE) TASK1_STACK_SIZE / 10, // 任务栈的使用警戒线
(CPU_STK_SIZE) TASK1_STACK_SIZE, // 任务栈大小
(OS_MSG_QTY ) 0, // 消息队列长度
(OS_TICK ) 0, // 时间片长度,设置为0,则默认时间片长度
(void * ) 0, // 扩展内存
(OS_OPT ) (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), // 任务选项
(OS_ERR * ) &error); // 错误码
// 创建任务2
OSTaskCreate((OS_TCB * ) &task2_tcb, // 任务控制块
(CPU_CHAR * ) "task2", // 任务名
(OS_TASK_PTR ) Task2, // 任务函数
(void * ) 0, // 任务参数
(OS_PRIO ) TASK2_PRIORITY, // 任务优先级
(CPU_STK * ) task2_stack, // 任务堆栈
(CPU_STK_SIZE) TASK2_STACK_SIZE / 10, // 任务栈的使用警戒线
(CPU_STK_SIZE) TASK2_STACK_SIZE, // 任务栈大小
(OS_MSG_QTY ) 0, // 消息队列长度
(OS_TICK ) 0, // 时间片长度
(void * ) 0, // 扩展内存
(OS_OPT ) (OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), // 任务选项
(OS_ERR * ) &error); // 错误码
CPU_CRITICAL_EXIT(); // 退出临界区
OSTaskDel(NULL, &error); // 删除任务
}
TASK1 任务配置:
#define FLAG_BIT1 (1 << 1)
#define FLAG_BIT2 (1 << 2)
#define FLAG_BIT3 (1 << 3)
/**
* TASK1 任务配置
* 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK1_PRIORITY 2
#define TASK1_STACK_SIZE 256
OS_TCB task1_tcb;
CPU_STK task1_stack[TASK1_STACK_SIZE];
void Task1(void *p_arg);
/**
* @brief 任务1的任务函数
*
* @param p_arg 任务参数
*/
void Task1(void *p_arg)
{
OS_ERR error = {0};
while (1)
{
switch (Key_Scan(0))
{
case KEY1_PRESS:
OSFlagPost(&flag, FLAG_BIT1, OS_OPT_POST_FLAG_SET, &error);
printf("事件1置位\r\n");
break;
case KEY2_PRESS:
OSFlagPost(&flag, FLAG_BIT2, OS_OPT_POST_FLAG_SET, &error);
printf("事件2置位\r\n");
break;
case KEY3_PRESS:
OSFlagPost(&flag, FLAG_BIT3, OS_OPT_POST_FLAG_SET, &error);
printf("事件3置位\r\n");
break;
default:
break;
}
OSTimeDly(10, OS_OPT_TIME_DLY, &error);
}
}
TASK2 任务配置:
/**
* TASK2 任务配置
* 包括:任务优先级 任务栈大小 任务控制块 任务栈 任务函数
*/
#define TASK2_PRIORITY 2
#define TASK2_STACK_SIZE 256
OS_TCB task2_tcb;
CPU_STK task2_stack[TASK2_STACK_SIZE];
void Task2(void *p_arg);
/**
* @brief 任务2的任务函数
*
* @param p_arg 任务参数
*/
void Task2(void *p_arg)
{
OS_ERR error = {0};
while (1)
{
OSFlagPend(&flag, FLAG_BIT1 | FLAG_BIT2 | FLAG_BIT3, 0, OS_OPT_PEND_FLAG_SET_ALL | OS_OPT_PEND_FLAG_CONSUME, 0, &error);
printf("等到到指定事件成立\r\n");
}
}