µC/OS-III 事件标志

       事件标志与信号量一样属于任务间同步的一种机制,但是信号量一般用于任务间的单事件同步,而对于任务间的多事件同步,仅仅使用信号量就显得力不从心了。为此 µC/OS-III 提供了事件标志这一机制,事件标志就能够很好地处理任务间的多任务同步。

1 µC/OS-III 事件标志简介

1.1 事件标志

        事件标志是一个用于指示事件是否发生的比特位,因为一个事件是否发生只有两种情况,分别为事件发生和事件未发生,因此只需一个比特位就能够表示事件是否发生,µC/OS-III 1表示事件发生,用 0 表示事件未发生。

1.2 事件标志组

        事件表示组是多个事件的集合,下面来看一下事件标志组的结构体,事件标志组的结构体定义在文件 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_LISTPendList;
 
 /* 此宏用于使能代码调试功能 */
#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_INT16U FlagID;
#endif
};
        从上面的代码中可以看到,事件标志组的结构体中包含了一个数据类型为 OS_FLAGS 的成员变量 Flags ,对于 32 位的 MCU 而言,数据类型 OS_FLAGS 实际上就是 32 位的无符号整型,因此成员变量 Flags 就包含了 32 比特,而 Flags 的每一个比特就能够用来存储一个事件标志。当成员变量 Flags 的某一比特位被置一时,就说明这一比特位对应的事件发生了。

1.3 事件标志操作简介

        事件标志组由多个事件标志组成,因此任务就能够很方便地通过多个事件的发生进行任务间的同步。通过事件标志,任务还能够通过多个事件之间的逻辑关系(与、或关系)进行任务间的同步,如下图所示:
     

2 µC/OS-III 事件标志相关 API 函数

        µC/OS-III 提供了一些列操作事件标志的 API 函数,这些操作函数如下表所示:
函数
描述
OSFlagCreate()
创建一个事件标志组
OSFlagDel()
删除一个事件标志组
OSFlagPend()
等待事件标志组中的事件
OSFlagPendAbort()
终止挂起等待事件标志组中的事件
OSFlagPendGetFlagRdy()
获取任务等待到的事件
OSFlagPost()
设置事件标志组中的事件

3 µC/OS-III 事件标志实验

        本实验的程序流程图,如下图所示:
              
(1) start_task 任务
        start_task 任务的入口函数代码如下所示:
/**
* @brief start_task
* @param p_arg : 传入参数(未用到)
* @retval 无
*/
void start_task(void *p_arg)
{
 OS_ERR err;
 CPU_INT32U cnts;
 
 /* 初始化 CPU 库 */
 CPU_Init();
 
 /* 根据配置的节拍频率配置 SysTick */
 cnts = (CPU_INT32U)(HAL_RCC_GetSysClockFreq() / OSCfg_TickRate_Hz);
 OS_CPU_SysTickInit(cnts);
 
 /* 开启时间片调度,时间片设为默认值 */
 OSSchedRoundRobinCfg(OS_TRUE, 0, &err);
 
 /* 创建事件标志 */
 OSFlagCreate( (OS_FLAG_GRP* )&flag,
 (CPU_CHAR* )"flag",
 (OS_FLAGS )0,
 (OS_ERR* )&err);
 
 /* 创建 Task1 */
 Task1Task_STK = (CPU_STK *)mymalloc( SRAMIN,
 TASK1_STK_SIZE * sizeof(CPU_STK));
 OSTaskCreate( (OS_TCB* )&Task1Task_TCB,
 (CPU_CHAR* )"task1",
 (OS_TASK_PTR )task1,
 (void* )0,
 (OS_PRIO )TASK1_PRIO,
 (CPU_STK *)Task1Task_STK,
 (CPU_STK_SIZE )TASK1_STK_SIZE / 10,
 (CPU_STK_SIZE )TASK1_STK_SIZE,
 (OS_MSG_QTY )0,
 (OS_TICK )0,
(void* )0,
 (OS_OPT )(OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
 (OS_ERR* )&err);
 
 /* 创建 Task2 */
 Task2Task_STK = (CPU_STK *)mymalloc( SRAMIN,
 TASK2_STK_SIZE * sizeof(CPU_STK));
 OSTaskCreate( (OS_TCB* )&Task2Task_TCB,
 (CPU_CHAR* )"task2",
 (OS_TASK_PTR )task2,
 (void* )0,
 (OS_PRIO )TASK2_PRIO,
 (CPU_STK* )Task2Task_STK,
 (CPU_STK_SIZE )TASK2_STK_SIZE / 10,
 (CPU_STK_SIZE )TASK2_STK_SIZE,
 (OS_MSG_QTY )0,
 (OS_TICK )0,
 (void* )0,
 (OS_OPT )(OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
 (OS_ERR* )&err);
 
 /* 创建 Task3 */
 Task3Task_STK = (CPU_STK *)mymalloc( SRAMIN,
 TASK3_STK_SIZE * sizeof(CPU_STK));
 OSTaskCreate( (OS_TCB* )&Task3Task_TCB,
 (CPU_CHAR* )"task3",
 (OS_TASK_PTR )task3,
 (void* )0,
 (OS_PRIO )TASK3_PRIO,
 (CPU_STK* )Task3Task_STK,
 (CPU_STK_SIZE )TASK3_STK_SIZE / 10,
 (CPU_STK_SIZE )TASK3_STK_SIZE,
 (OS_MSG_QTY )0,
 (OS_TICK )0,
 (void* )0,
 (OS_OPT )(OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR),
 (OS_ERR* )&err);
 
 /* 删除 Start Task */
 OSTaskDel((OS_TCB *)0, &err);
}
        从上面的代码中可以看出,start_task 任务使用函数 OSTaskCreate() 创建了 task1 任务、 task2任务和 task3 任务,并且 task1 任务、task2 任务和 task3 任务的任务栈都是动态分配的,当然, 也可以静态地定任务栈,就像定义一个数组一样,同时还调用函数 OSFlagCreate() 创建了一个用于本次实验的事件标志组。
(2) task1 任务
/**
* @brief task1
* @param p_arg : 传入参数(未用到)
* @retval 无
*/
void task1(void *p_arg)
{
 uint8_t key;
 OS_ERR err;
 
 while (1)
 {
 key = key_scan(0);
 
 switch (key)
 {
 case KEY0_PRES:
 {
 OSFlagPost( (OS_FLAG_GRP* )&flag,
 (OS_FLAGS )FLAGBIT_0,
 (OS_OPT )OS_OPT_POST_FLAG_SET,
 (OS_ERR* )&err);
 break;
 }
 case KEY1_PRES:
 {
 OSFlagPost( (OS_FLAG_GRP* )&flag,
 (OS_FLAGS )FLAGBIT_1,
 (OS_OPT )OS_OPT_POST_FLAG_SET,
 (OS_ERR* )&err);
 break;
 }
 default:
 {
 break;
 }
 }
 
 OSTimeDly(10, OS_OPT_TIME_DLY, &err);
 }
}
        可以看到 task1 任务就是用于扫描按键,当检测到按键 0 被按下时,就设置事件标志组中的事件标志 0 ,当检测到按键 1 被按下时,就设置事件标志组中的事件标志 1 ,每间隔 10ticks就扫描一次按键。
(3) task2 任务
/**
* @brief task2
* @param p_arg : 传入参数(未用到)
* @retval 无
*/
void task2(void *p_arg)
{
 uint32_t task2_num = 0;
 OS_ERR err;
 CPU_SR_ALLOC();
 
 while (1)
 {
 OSFlagPend( (OS_FLAG_GRP* )&flag,
 (OS_FLAGS )FLAGBIT_ALL,
 (OS_TICK )0,
 (OS_OPT )OS_OPT_PEND_FLAG_SET_ALL |
 OS_OPT_PEND_FLAG_CONSUME |
 OS_OPT_PEND_BLOCKING,
 (CPU_TS *)0,
 (OS_ERR *)&err);
 
 CPU_CRITICAL_ENTER();
 lcd_fill(6, 131, 233, 313, lcd_discolor[++task2_num % 11]);
 CPU_CRITICAL_EXIT();
 }
}
        从上面的代码中可以看出,task2 任务会等待事件标志组中的事件标志 0 和事件标志 1 ,只有当事件标志 0 和事件标志 1 同时被置 1 时, task2 任务才能够执行,并且在 task2 任务成功等待到事件标志 0 和事件标志 1 后,会清零事件标志 0 和事件标志 1
(4) task3 任务
/**
* @brief task3
* @param p_arg : 传入参数(未用到)
* @retval 无
*/
void task3(void *p_arg)
{
 OS_FLAGS flags;
 OS_ERR err;
 CPU_SR_ALLOC();
 
 while (1)
 {
 flags = flag.Flags;
 
 CPU_CRITICAL_ENTER();
 lcd_show_xnum(163, 110, flags, 1, 16, 0, BLUE);
 CPU_CRITICAL_EXIT();
 
 OSTimeDly(10, OS_OPT_TIME_DLY, &err);
 }
}
        task3 任务比较简单,就是不断地获取事件标志组中事件标志的值,然后实时地将事件标志组中事件标志的值显示在 LCD 上,但值得一提的是,从上面的代码中可以看出,操作 LCD 的代码是处于临界区的,这是因为在本次实验中,task2 任务和 task3 任务都会操作 LCD ,在临界区中操作 LCD 就是为了避免任务在访问 LCD 这一共享资源的时候,产生访问冲突,因此在task2 任务的任务代码中,也能够看到操作 LCD 的代码被放在了临界区中。
 
        编译并下载代码,复位后可以看到 LCD 屏幕上显示了本次实验的相关信息,接着按下按键 0,使得 task1 任务将事件标志组中的事件标志 0 置 1,随后立马就能看到task3 任务在 LCD 上显示了事件标志组中事件标志的值为 1。但此时 task2 任务还没有刷新 LCD,因为 task2 任务等待的时事件标志 0 和事件标志 1。接下来按下按键 1,使得 task1 任务设置事件标志组中的事件标志 1。
        可以看到,task2 任务刷新了 LCD ,同时因为 task2 任务等待到了事件标志 0 和事件标志 1 ,清 0 了事件标志组中的事件标志 0 和事件标志 1 ,因此 task3 任务在 LCD 显示的事件标志组中事件标志的值变回了 0
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值