(学习日记)2024.04.09:UCOSIII第三十七节:事件函数接口

news2024/11/24 7:48:14

写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。


标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。


点击此处进入学习日记的总目录

2024.04.09:UCOSIII第三十七节:事件函数接口

  • 五十一、UCOSIII:事件函数接口
    • 1、事件创建函数OSFlagCreate()
    • 2、事件删除函数OSFlagDel()
    • 3、事件设置函数OSFlagPost()
      • 1. OSFlagPost()
      • 2. OS_FlagPost()
    • 4、事件等待函数OSFlagPend()

五十一、UCOSIII:事件函数接口

1、事件创建函数OSFlagCreate()

事件创建函数,顾名思义,就是创建一个事件,与其他内核对象一样,都是需要先创建才能使用的资源。
μC/OS给我们提供了一个创建事件的函数OSFlagCreate(), 当创建一个事件时,系统会对我们定义的事件控制块进行基本的初始化。
所以,在使用创建函数之前,我们需要先定义一个事件控制块(句柄), 事件创建函数的源码具体如下:

void  OSFlagCreate (OS_FLAG_GRP  *p_grp,  (1)       //事件指针
                    CPU_CHAR     *p_name, (2)       //命名事件
                    OS_FLAGS      flags,  (3)       //标志初始值
                    OS_ERR       *p_err)  (4)       //返回错误类型
{
    CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和
    //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器
    // SR(临界段关中断只需保存SR),开中断时将该值还原。

#ifdef OS_SAFETY_CRITICAL(5)//如果启用了安全检测
    if (p_err == (OS_ERR *)0)           //如果错误类型实参为空
    {
        OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数
        return;                         //返回,停止执行
    }
#endif

#ifdef OS_SAFETY_CRITICAL_IEC61508(6)//如果启用了安全关键
    if (OSSafetyCriticalStartFlag == DEF_TRUE)   //如果OSSafetyCriticalStart()后创建
    {
        *p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME;  //错误类型为“非法创建内核对象”
        return;                                  //返回,停止执行
    }
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u(7)//如果启用了中断中非法调用检测
    if (OSIntNestingCtr > (OS_NESTING_CTR)0)   //如果该函数是在中断中被调用
    {
        *p_err = OS_ERR_CREATE_ISR;             //错误类型为“在中断中创建对象”
        return;                                //返回,停止执行
    }
#endif

#if OS_CFG_ARG_CHK_EN > 0u(8)//如果启用了参数检测
    if (p_grp == (OS_FLAG_GRP *)0)   //如果 p_grp 为空
    {
        *p_err = OS_ERR_OBJ_PTR_NULL; //错误类型为“创建对象为空”
        return;                      //返回,停止执行
    }
#endif

    OS_CRITICAL_ENTER();         (9)//进入临界段
    p_grp->Type    = OS_OBJ_TYPE_FLAG; //标记创建对象数据结构为事件
    p_grp->NamePtr = p_name;      (10)//标记事件的名称
    p_grp->Flags   = flags;        (11)//设置标志初始值
    p_grp->TS      = (CPU_TS)0;    (12)//清零事件的时间戳
    OS_PendListInit(&p_grp->PendList);(13)//初始化该事件的等待列表

#if OS_CFG_DBG_EN > 0u//如果启用了调试代码和变量
    OS_FlagDbgListAdd(p_grp);          //将该事件添加到事件双向调试链表
#endif
    OSFlagQty++;                 (14)//事件个数加1

    OS_CRITICAL_EXIT_NO_SCHED();       //退出临界段(无调度)
    *p_err = OS_ERR_NONE;         (15)//错误类型为“无错误”
}
  • (1):事件控制块指针,指向我们定义的事件控制块结构体变量,所以在创建之前我们需要先定义一个事件控制块变量。
  • (2):事件名称,字符串形式。
  • (3):事件标志位的初始值,一般为常为0。
  • (4):用于保存返回的错误类型。
  • (5):如果启用了安全检测(默认禁用), 在编译时则会包含安全检测相关的代码,如果错误类型实参为空,系统会执行安全检测异常函数,然后返回,不执行创建互斥量操作。
  • (6):如果启用(默认禁用)了安全关键检测, 在编译时则会包含安全关键检测相关的代码,如果是在调用OSSafetyCriticalStart()后创建该事件,则是非法的,返回错误类型为“非法创建内核对象”错误代码,并且退出,不执行创建事件操作。
  • (7):如果启用了中断中非法调用检测(默认启用), 在编译时则会包含中断非法调用检测相关的代码,如果该函数是在中断中被调用,则是非法的,返回错误类型为“在中断中创建对象”的错误代码,并且退出,不执行创建事件操作。
  • (8):如果启用了参数检测(默认启用), 在编译时则会包含参数检测相关的代码,如果p_grp参数为空,返回错误类型为“创建对象为空”的错误代码,并且退出,不执行创建事件操作。
  • (9):进入临界段,标记创建对象数据结构为事件。
  • (10):初始化事件的名称。
  • (11):设置事件标志的初始值。
  • (12):记录时间戳的变量TS初始化为0。
  • (13):初始化该事件的等待列表。
  • (14):系统事件个数加1。
  • (15):退出临界段(无调度),创建事件成功。

事件结构如下:
在这里插入图片描述

事件创建函数的使用实例具体如下:

OS_FLAG_GRP flag_grp;                   //声明事件

OS_ERR      err;

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

2、事件删除函数OSFlagDel()

在很多场合,某些事件只用一次的,就好比在事件应用场景说的危险机器的启动,假如各项指标都达到了,并且机器启动成功了, 那这个事件之后可能就没用了,那就可以进行销毁了。
想要删除事件怎么办?μC/OS给我们提供了一个删除事件的函数——OSFlagDel(), 使用它就能将事件进行删除了。

注意,想要使用删除事件函数则必须将OS_CFG_FLAG_DEL_EN宏定义配置为1,该宏定义在os_cfg.h文件中。

当系统不再使用事件对象时,可以通过删除事件对象控制块来进行删除,具体如下:

#if OS_CFG_FLAG_DEL_EN > 0u//如果启用了 OSFlagDel() 函数
OS_OBJ_QTY  OSFlagDel (OS_FLAG_GRP  *p_grp, (1)     //事件指针
                    OS_OPT        opt,   (2)        //选项
                    OS_ERR       *p_err) (3)        //返回错误类型
{
    OS_OBJ_QTY        cnt;
    OS_OBJ_QTY        nbr_tasks;
    OS_PEND_DATA     *p_pend_data;
    OS_PEND_LIST     *p_pend_list;
    OS_TCB           *p_tcb;
    CPU_TS            ts;
    CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和
    //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器
    // SR(临界段关中断只需保存SR),开中断时将该值还原。

#ifdef OS_SAFETY_CRITICAL(4)//如果启用(默认禁用)了安全检测
    if (p_err == (OS_ERR *)0)           //如果错误类型实参为空
    {
        OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数
        return ((OS_OBJ_QTY)0);         //返回0(有错误),停止执行
    }
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u(5)//如果启用了中断中非法调用检测
    if (OSIntNestingCtr > (OS_NESTING_CTR)0)   //如果该函数在中断中被调用
    {
        *p_err = OS_ERR_DEL_ISR;                //错误类型为“在中断中删除对象”
        return ((OS_OBJ_QTY)0);                //返回0(有错误),停止执行
    }
#endif

#if OS_CFG_ARG_CHK_EN > 0u(6)//如果启用了参数检测
    if (p_grp == (OS_FLAG_GRP *)0)        //如果 p_grp 为空
    {
        *p_err  = OS_ERR_OBJ_PTR_NULL;     //错误类型为“对象为空”
        return ((OS_OBJ_QTY)0);           //返回0(有错误),停止执行
    }
    switch (opt)                (7)//根据选项分类处理
    {
    case OS_OPT_DEL_NO_PEND:          //如果选项在预期内
    case OS_OPT_DEL_ALWAYS:
    break;                       //直接跳出

    default:                   (8)//如果选项超出预期
        *p_err = OS_ERR_OPT_INVALID;  //错误类型为“选项非法”
        return ((OS_OBJ_QTY)0);      //返回0(有错误),停止执行
    }
#endif

#if OS_CFG_OBJ_TYPE_CHK_EN > 0u(9)//如果启用了对象类型检测
    if (p_grp->Type != OS_OBJ_TYPE_FLAG)  //如果 p_grp 不是事件类型
    {
        *p_err = OS_ERR_OBJ_TYPE;          //错误类型为“对象类型有误”
        return ((OS_OBJ_QTY)0);           //返回0(有错误),停止执行
    }
#endif
    OS_CRITICAL_ENTER();                         //进入临界段
    p_pend_list = &p_grp->PendList;       (10)//获取消息队列的等待列表
    cnt         = p_pend_list->NbrEntries;   (11)//获取等待该队列的任务数
    nbr_tasks   = cnt;                          //按照任务数目逐个处理
    switch (opt)                    (12)//根据选项分类处理
    {
        case OS_OPT_DEL_NO_PEND:        (13)//如果只在没任务等待时进行删除
        if (nbr_tasks == (OS_OBJ_QTY)0)     //如果没有任务在等待该事件
        {
#if OS_CFG_DBG_EN > 0u//如果启用了调试代码和变量
            OS_FlagDbgListRemove(p_grp); (14)//将该事件从事件调试列表移除
#endif
            OSFlagQty--;           (15)//事件数目减1
            OS_FlagClr(p_grp);      (16)//清除该事件的内容

            OS_CRITICAL_EXIT();             //退出临界段
            *p_err = OS_ERR_NONE;     (17)//错误类型为“无错误”
        }
        else
        {
            OS_CRITICAL_EXIT();             //退出临界段
            *p_err = OS_ERR_TASK_WAITING;  (18)//错误类型为“有任务在等待事件”
        }
        break;                              //跳出

        case OS_OPT_DEL_ALWAYS:           (19)//如果必须删除事件
        ts = OS_TS_GET();             (20)//获取时间戳
        while (cnt > 0u)              (21)//逐个移除该事件等待列表中的任务
        {
            p_pend_data = p_pend_list->HeadPtr;
            p_tcb       = p_pend_data->TCBPtr;
            OS_PendObjDel((OS_PEND_OBJ *)((void *)p_grp),
                        p_tcb,
                        ts);                (22)
            cnt--;
        }
#if OS_CFG_DBG_EN > 0u//如果启用了调试代码和变量
        OS_FlagDbgListRemove(p_grp);        //将该事件从事件调试列表移除
#endif
        OSFlagQty--;                   (23)//事件数目减1
        OS_FlagClr(p_grp);             (24)//清除该事件的内容
        OS_CRITICAL_EXIT_NO_SCHED();                //退出临界段(无调度)
        OSSched();                      (25)//调度任务
        *p_err = OS_ERR_NONE;     (26)//错误类型为“无错误”
        break;                              //跳出

        default:                       (27)//如果选项超出预期
        OS_CRITICAL_EXIT();                 //退出临界段
        *p_err = OS_ERR_OPT_INVALID;         //错误类型为“选项非法”
        break;                              //跳出
    }
    return (nbr_tasks);          (28)//返回删除事件前等待其的任务数
}
#endif
  • (1):事件控制块指针,指向我们定义的事件控制块结构体变量, 所以在删除之前我们需要先定义一个事件控制块变量,并且成功创建事件后再进行删除操作。
  • (2):事件删除的选项。
  • (3):用于保存返回的错误类型。
  • (4):如果启用了安全检测(默认),在编译时则会包含安全检测相关的代码, 如果错误类型实参为空,系统会执行安全检测异常函数,然后返回,不执行删除互斥量操作。
  • (5):如果启用了中断中非法调用检测(默认启用), 在编译时则会包含中断非法调用检测相关的代码,如果该函数是在中断中被调用,则是非法的,返回错误类型为“在中断中删除对象”的错误代码,并且退出,不执行删除事件操作。
  • (6):如果启用了参数检测(默认启用), 在编译时则会包含参数检测相关的代码,如果p_grp参数为空,返回错误类型为“内核对象为空”的错误代码,并且退出,不执行删除事件操作。
  • (7):判断opt选项是否合理,该选项有两个, OS_OPT_DEL_ALWAYS与OS_OPT_DEL_NO_PEND,在os.h文件中定义。此处是判断一下选项是否在预期之内,如果在则跳出switch语句。
  • (8):如果选项超出预期,则返回错误类型为“选项非法”的错误代码,退出,不继续执行。
  • (9):如果启用了对象类型检测,在编译时则会包含对象类型检测相关的代码, 如果 p_grp 不是事件类型,返回错误类型为“内核对象类型错误”的错误代码,并且退出,不执行删除事件操作。
  • (10):进入临界段,程序执行到这里,表示可以删除事件了, 系统首先获取互斥量的等待列表保存到p_pend_list变量中,μC/OS在删事件的时候是通过该变量访问事件等待列表的任务的。
  • (11):获取等待该队列的任务数,按照任务个数逐个处理。
  • (12):根据选项分类处理。
  • (13):如果opt是OS_OPT_DEL_NO_PEND,则表示只在没有任务等待的情况下删除事件, 如果当前系统中有任务还在等待该事件的某些位,则不能进行删除操作,反之,则可以删除事件。
  • (14):如果启用了调试代码和变量,将该事件从事件调试列表移除。
  • (15):系统的事件个数减一。
  • (16):清除该事件的内容。
  • (17):删除成功,返回错误类型为“无错误”的错误代码。
  • (18)::如果有任务在等待该事件,则返回错误类型为“有任务在等待该事件”错误代码。
  • (19):如果opt是OS_OPT_DEL_ALWAYS, 则表示无论如何都必须删除事件,那么在删除之前,系统会把所有阻塞在该事件上的任务恢复。
  • (20):获取删除时候的时间戳。
  • (21):根据前面cnt记录阻塞在该事件上的任务个数,逐个移除该事件等待列表中的任务。
  • (22):调用OS_PendObjDel()函数将阻塞在内核对象(如事件)上的任务从阻塞态恢复, 此时系统在删除内核对象,删除之后,这些等待事件的任务需要被恢复。
  • (23):系统事件数目减1
  • (24):清除该事件的内容。
  • (25):进行一次任务调度。
  • (26):删除事件完成,返回错误类型为“无错误”的错误代码。
  • (27):如果选项超出预期则返回错误类型为“任务状态非法”的错误代码。
  • (28):返回删除事件前等待其的任务数

事件删除函数OSFlagDel()的使用也是很简单的,只需要传入要删除的事件的句柄与选项还有保存返回的错误类型即可,调用函数时, 系统将删除这个事件。
需要注意的是在调用删除事件函数前,系统应存在已创建的事件。
如果删除事件时,系统中有任务正在等待该事件, 则不应该进行删除操作,删除事件函数OSFlagDel()的使用实例具体

OS_FLAG_GRPflag_grp;;                             //声明事件句柄

OS_ERR      err;

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

3、事件设置函数OSFlagPost()

1. OSFlagPost()

OSFlagPost()用于设置事件组中指定的位,当位被置位之后,并且满足任务的等待事件,那么等待在事件该标志位上的任务将会被恢复。
使用该函数接口时, 通过参数指定的事件标志来设置事件的标志位,然后遍历等待在事件对象上的事件等待列表,判断是否有任务的事件激活要求与当前事件对象标志值匹配, 如果有,则唤醒该任务。
简单来说,就是设置我们自己定义的事件标志位为1,并且看看有没有任务在等待这个事件,有的话就唤醒它。
OSFlagPost()函数源码具体:

OS_FLAGS  OSFlagPost (OS_FLAG_GRP  *p_grp,          //事件指针
                    OS_FLAGS      flags,            //选定要操作的标志位
                    OS_OPT        opt,              //选项
                    OS_ERR       *p_err)            //返回错误类型
{
    OS_FLAGS  flags_cur;
    CPU_TS    ts;



#ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测
    if (p_err == (OS_ERR *)0)           //如果错误类型实参为空
    {
        OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数
        return ((OS_FLAGS)0);           //返回0,停止执行
    }
#endif

#if OS_CFG_ARG_CHK_EN > 0u//如果启用(默认启用)了参数检测
    if (p_grp == (OS_FLAG_GRP *)0)       //如果参数 p_grp 为空
    {
        *p_err  = OS_ERR_OBJ_PTR_NULL;    //错误类型为“事件对象为空”
        return ((OS_FLAGS)0);            //返回0,停止执行
    }
    switch (opt)                        //根据选项分类处理
    {
    case OS_OPT_POST_FLAG_SET:       //如果选项在预期之内
    case OS_OPT_POST_FLAG_CLR:
    case OS_OPT_POST_FLAG_SET | OS_OPT_POST_NO_SCHED:
    case OS_OPT_POST_FLAG_CLR | OS_OPT_POST_NO_SCHED:
    break;                      //直接跳出

    default:                         //如果选项超出预期
        *p_err = OS_ERR_OPT_INVALID; //错误类型为“选项非法”
        return ((OS_FLAGS)0);       //返回0,停止执行
    }
#endif

#if OS_CFG_OBJ_TYPE_CHK_EN > 0u//如果启用了对象类型检测
    if (p_grp->Type != OS_OBJ_TYPE_FLAG)   //如果 p_grp 不是事件类型
    {
        *p_err = OS_ERR_OBJ_TYPE;           //错误类型“对象类型有误”
        return ((OS_FLAGS)0);              //返回0,停止执行
    }
#endif

    ts = OS_TS_GET();                             //获取时间戳
    #if OS_CFG_ISR_POST_DEFERRED_EN > 0u(1)//如果启用了中断延迟发布
    if (OSIntNestingCtr > (OS_NESTING_CTR)0)      //如果该函数是在中断中被调用
    {
        OS_IntQPost((OS_OBJ_TYPE)OS_OBJ_TYPE_FLAG,//将该事件发布到中断消息队列
                    (void      *)p_grp,
                    (void      *)0,
                    (OS_MSG_SIZE)0,
                    (OS_FLAGS   )flags,
                    (OS_OPT     )opt,
                    (CPU_TS     )ts,
                    (OS_ERR    *)p_err);
        return ((OS_FLAGS)0);                     //返回0,停止执行
    }
#endif
    /* 如果没有启用中断延迟发布 */
    flags_cur = OS_FlagPost(p_grp,               //将事件直接发布
                            flags,
                            opt,
                            ts,
                            p_err); (2)

    return (flags_cur);                         //返回当前标志位的值
}
  • (1):如果启用了中断延迟发布并且该函数在中断中被调用,则将该事件发布到中断消息队列。
  • (2):如果没有启用中断延迟发布, 则直接将该事件对应的标志位置位。

2. OS_FlagPost()

OS_FLAGS  OS_FlagPost (OS_FLAG_GRP  *p_grp,         (1)     //事件指针
                    OS_FLAGS      flags,    (2)     //选定要操作的标志位
                    OS_OPT        opt,      (3)     //选项
                    CPU_TS        ts,       (4)     //时间戳
                    OS_ERR       *p_err)    (5)     //返回错误类型
{
    OS_FLAGS        flags_cur;
    OS_FLAGS        flags_rdy;
    OS_OPT          mode;
    OS_PEND_DATA   *p_pend_data;
    OS_PEND_DATA   *p_pend_data_next;
    OS_PEND_LIST   *p_pend_list;
    OS_TCB         *p_tcb;
    CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和
    //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器
    // SR(临界段关中断只需保存SR),开中断时将该值还原。

    CPU_CRITICAL_ENTER();                                //关中断
    switch (opt)                         (6)//根据选项分类处理
    {
        case OS_OPT_POST_FLAG_SET:          (7)//如果要求将选定位置1
        case OS_OPT_POST_FLAG_SET | OS_OPT_POST_NO_SCHED:
            p_grp->Flags |=  flags;                     //将选定位置1
            break;                                      //跳出

        case OS_OPT_POST_FLAG_CLR:           (8)//如果要求将选定位请0
        case OS_OPT_POST_FLAG_CLR | OS_OPT_POST_NO_SCHED:
                p_grp->Flags &= ~flags;                     //将选定位请0
                break;                                      //跳出

        default:                           (9)//如果选项超出预期
                CPU_CRITICAL_EXIT();                        //开中断
                *p_err = OS_ERR_OPT_INVALID;                 //错误类型为“选项非法”
        return ((OS_FLAGS)0);                       //返回0,停止执行
    }
    p_grp->TS   = ts;                 (10)//将时间戳存入事件
    p_pend_list = &p_grp->PendList;    (11)//获取事件的等待列表
    if (p_pend_list->NbrEntries == 0u) (12)//如果没有任务在等待事件
    {
        CPU_CRITICAL_EXIT();                             //开中断
        *p_err = OS_ERR_NONE;                        //错误类型为“无错误”
        return (p_grp->Flags);                           //返回事件的标志值
    }
    /* 如果有任务在等待事件 */
    OS_CRITICAL_ENTER_CPU_EXIT();       (13)//进入临界段,重开中断
    p_pend_data = p_pend_list->HeadPtr; (14)//获取等待列表头个等待任务
    p_tcb       = p_pend_data->TCBPtr;
    while (p_tcb != (OS_TCB *)0)        (15)
    //从头至尾遍历等待列表的所有任务
    {
        p_pend_data_next = p_pend_data->NextPtr;
        mode = p_tcb->FlagsOpt & OS_OPT_PEND_FLAG_MASK; //获取任务的标志选项
        switch (mode)                (16)//根据任务的标志选项分类处理
        {
        OS_OPT_PEND_FLAG_SET_ALL:  (17)//如果要求任务等待的标志位都得置1
            flags_rdy = (OS_FLAGS)(p_grp->Flags & p_tcb->FlagsPend);
            if (flags_rdy == p_tcb->FlagsPend) //如果任务等待的标志位都置1了
            {
                OS_FlagTaskRdy(p_tcb,            //让该任务准备运行
                            flags_rdy,
                            ts);            (18)
            }
             break;                               //跳出

        case OS_OPT_PEND_FLAG_SET_ANY:     (19)
        //如果要求任务等待的标志位有1位置1即可
            flags_rdy = (OS_FLAGS)(p_grp->Flags & p_tcb->FlagsPend);(20)
            if (flags_rdy != (OS_FLAGS)0)     //如果任务等待的标志位有置1的
            {
                OS_FlagTaskRdy(p_tcb,            //让该任务准备运行
                            flags_rdy,
                            ts);            (21)
            }
            break;                              //跳出

#if OS_CFG_FLAG_MODE_CLR_EN > 0u(22)//如果启用了标志位清零触发模式
        case OS_OPT_PEND_FLAG_CLR_ALL: (23)//如果要求任务等待的标志位都得请0
            flags_rdy = (OS_FLAGS)(~p_grp->Flags & p_tcb->FlagsPend);
            if (flags_rdy == p_tcb->FlagsPend)  //如果任务等待的标志位都请0了
            {
                OS_FlagTaskRdy(p_tcb,           //让该任务准备运行
                            flags_rdy,
                            ts);    (24)
            }
            break;            //跳出

        case OS_OPT_PEND_FLAG_CLR_ANY:     (25)
        //如果要求任务等待的标志位有1位请0即可
            flags_rdy = (OS_FLAGS)(~p_grp->Flags & p_tcb->FlagsPend);
            if (flags_rdy != (OS_FLAGS)0)      //如果任务等待的标志位有请0的
            {
                OS_FlagTaskRdy(p_tcb,          //让该任务准备运行
                            flags_rdy,
                            ts);    (26)
            }
            break;                            //跳出
#endif
        default:                      (27)//如果标志选项超出预期
            OS_CRITICAL_EXIT();               //退出临界段
            *p_err = OS_ERR_FLAG_PEND_OPT;     //错误类型为“标志选项非法”
            return ((OS_FLAGS)0);             //返回0,停止运行
        }
        p_pend_data = p_pend_data_next;   (28)//准备处理下一个等待任务
        if (p_pend_data != (OS_PEND_DATA *)0)      //如果该任务存在
        {
            p_tcb = p_pend_data->TCBPtr;   (29)//获取该任务的任务控制块
        }
        else//如果该任务不存在
        {
            p_tcb = (OS_TCB *)0;     (30)//清空 p_tcb,退出 while 循环
        }
    }
    OS_CRITICAL_EXIT_NO_SCHED();                  //退出临界段(无调度)

    if ((opt & OS_OPT_POST_NO_SCHED) == (OS_OPT)0)    //如果 opt没选择“发布时不调度任务”
    {
        OSSched();                 (31)//任务调度
    }

    CPU_CRITICAL_ENTER();        //关中断
    flags_cur = p_grp->Flags;    //获取事件的标志值
    CPU_CRITICAL_EXIT();         //开中断
    *p_err     = OS_ERR_NONE;     //错误类型为“无错误”
    return (flags_cur);       (32)//返回事件的当前标志值

}
  • (1):事件指针。
  • (2):选定要操作的标志位。
  • (3):设置事件标志位的选项。
  • (4):时间戳。
  • (5):返回错误类型。
  • (6):根据选项分类处理。
  • (7):如果要求将选定位置1,则置1即可,然后跳出switch语句。
  • (8):如果要求将选定位请0,将选定位清零即可,然后跳出switch语句。
  • (9):如果选项超出预期,返回错误类型为“选项非法”的错误代码,退出。
  • (10):将时间戳存入事件的TS成员变量中。
  • (11):获取事件的等待列表。
  • (12):如果当前没有任务在等待事件,置位后直接退出即可,并且返回事件的标志值。
  • (13):如果有任务在等待事件,那么进入临界段,重开中断。
  • (14):获取等待列表头个等待任务,然后获取到对应的任务控制块,保存在p_tcb变量中。
  • (15):当事件等待列表中有任务的时候,就从头至尾遍历等待列表的所有任务。
  • (16):获取任务感兴趣的事件标志选项,根据任务的标志选项分类处理。
  • (17):如果要求任务等待的标志位都得置1,就获取一下任务已经等待到的事件标志,保存在flags_rdy变量中。
  • (18):如果任务等待的标志位都置1了, 就调用OS_FlagTaskRdy()函数让该任务恢复为就绪态,准备运行,然后跳出switch语句。
  • (19):如果要求任务等待的标志位有任意一个位置1即可。
  • (20):那么就获取一下任务已经等待到的事件标志,保存在flags_rdy变量中。
  • (21):如果任务等待的标志位有置1的, 也就是满足了任务唤醒的条件,就调用OS_FlagTaskRdy()函数让该任务恢复为就绪态,准备运行,然后跳出switch语句。
  • (22):如果启用了标志位清零触发模式,在编译的时候就会包含事件标志位清零触发的代码。
  • (23):如果要求任务等待的标志位都得请0,那就看看等待任务对应的标志位是否清零了。
  • (24):如果任务等待的标志位都请0了, 就调用OS_FlagTaskRdy()函数让该任务恢复为就绪态,准备运行,然后跳出switch语句。
  • (25):如果要求任务等待的标志位有1位请0即可。
  • (26):那么如果任务等待的标志位有请0的,就让任务恢复为就绪态。
  • (27):如果标志选项超出预期,返回错误类型为“标志选项非法”的错误代码,并且推出。
  • (28):准备处理下一个等待任务。
  • (29):如果该任务存在,获取该任务的任务控制块。
  • (30):如果该任务不存在,清空 p_tcb,退出 while 循环。
  • (31):进行一次任务调度。
  • (32):事件标志位设置完成,返回事件的当前标志值。

OSFlagPost()的运用很简单,举个例子,比如我们要记录一个事件的发生,这个事件在事件组的位置是bit0,当它还未发生的时候,那么事件组bit0的值也是0, 当它发生的时候,我们往事件标志组的bit0位中写入这个事件,也就是0x01,那这就表示事件已经发生了。
当然,μC/OS也支持事件清零触发。
为了便于理解,一般操作我们都是用宏定义来实现#define EVENT (0x01 << x),“<< x”表示写入事件集合的bit x 。

在使用该函数之前必须先创建事件

应用实例如下:

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

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

static  void  AppTaskPost ( void * p_arg )
{
    OS_ERR      err;


    (void)p_arg;


        while (DEF_TRUE) {                            //任务体
        //如果KEY1被按下
        if ( Key_ReadStatus ( macKEY1_GPIO_PORT, macKEY1_GPIO_PIN, 1 ) == 1 )
        {
            macLED1_ON ();                                    //点亮LED1

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

        }
        else//如果KEY1被释放
        {
            macLED1_OFF ();     //熄灭LED1

            OSFlagPost ((OS_FLAG_GRP  *)&flag_grp,
                        //将标志组的BIT0清零
                        (OS_FLAGS      )KEY1_EVENT,
                        (OS_OPT        )OS_OPT_POST_FLAG_CLR,
                        (OS_ERR       *)&err);

        }
        //如果KEY2被按下
        if ( Key_ReadStatus ( macKEY2_GPIO_PORT, macKEY2_GPIO_PIN, 1 ) == 1 )
        {
            macLED2_ON ();                              //点亮LED2

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

        }
        else//如果KEY2被释放
        {
        macLED2_OFF ();    //熄灭LED2

            OSFlagPost ((OS_FLAG_GRP  *)&flag_grp,
                        //将标志组的BIT1清零
                        (OS_FLAGS      )KEY2_EVENT,
                        (OS_OPT        )OS_OPT_POST_FLAG_CLR,
                        (OS_ERR       *)&err);

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

    }

}

4、事件等待函数OSFlagPend()

既然标记了事件的发生,那么我们怎么知道他到底有没有发生,这也是需要一个函数来获取事件是否已经发生。
μC/OS提供了一个等待指定事件的函数——OSFlagPend(), 通过这个函数,任务可以知道事件标志组中的哪些位,有什么事件发生了,然后通过“逻辑与”、“逻辑或”等操作对感兴趣的事件进行获取,并且这个函数实现了等待超时机制, 当且仅当任务等待的事件发生时,任务才能获取到事件信息。
在这段时间中,如果事件一直没发生,该任务将保持阻塞状态以等待事件发生。
当其他任务或中断服务程序往其等待的事件设置对应的标志位,该任务将自动由阻塞态转为就绪态。
当任务等待的时间超过了指定的阻塞时间,即使事件还未发生, 任务也会自动从阻塞态转移为就绪态。
这样子很有效的体现了操作系统的实时性,如果事件正确获取(等待到)则返回对应的事件标志位,由用户判断再做处理, 因为在事件超时的时候也可能会返回一个不能确定的事件值,所以最好判断一下任务所等待的事件是否真的发生。

OSFlagPend()函数源码具体如下:

OS_FLAGS  OSFlagPend (OS_FLAG_GRP  *p_grp,  (1)     //事件指针
                    OS_FLAGS      flags, (2)        //选定要操作的标志位
                    OS_TICK       timeout,(3)       //等待期限(单位:时钟节拍)
                    OS_OPT        opt,    (4)       //选项
                    CPU_TS       *p_ts,   (5)//返回等到事件标志时的时间戳
                    OS_ERR       *p_err)  (6)       //返回错误类型
{
    CPU_BOOLEAN   consume;
    OS_FLAGS      flags_rdy;
    OS_OPT        mode;
    OS_PEND_DATA  pend_data;
    CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和
    //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器
    // SR(临界段关中断只需保存SR),开中断时将该值还原。

#ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测
    if (p_err == (OS_ERR *)0)           //如果错误类型实参为空
    {
        OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数
        return ((OS_FLAGS)0);           //返回0(有错误),停止执行
    }
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u//如果启用了中断中非法调用检测
    if (OSIntNestingCtr > (OS_NESTING_CTR)0)    //如果该函数在中断中被调用
    {
        *p_err = OS_ERR_PEND_ISR;                //错误类型为“在中断中中止等待”
        return ((OS_FLAGS)0);                   //返回0(有错误),停止执行
    }
#endif

#if OS_CFG_ARG_CHK_EN > 0u//如果启用了参数检测
    if (p_grp == (OS_FLAG_GRP *)0)       //如果 p_grp 为空
    {
        *p_err = OS_ERR_OBJ_PTR_NULL;     //错误类型为“对象为空”
        return ((OS_FLAGS)0);            //返回0(有错误),停止执行
    }
    switch (opt)                 (7)//根据选项分类处理
    {
    case OS_OPT_PEND_FLAG_CLR_ALL:   //如果选项在预期内
    case OS_OPT_PEND_FLAG_CLR_ANY:
    case OS_OPT_PEND_FLAG_SET_ALL:
    case OS_OPT_PEND_FLAG_SET_ANY:
    case OS_OPT_PEND_FLAG_CLR_ALL | OS_OPT_PEND_FLAG_CONSUME:
    case OS_OPT_PEND_FLAG_CLR_ANY | OS_OPT_PEND_FLAG_CONSUME:
    case OS_OPT_PEND_FLAG_SET_ALL | OS_OPT_PEND_FLAG_CONSUME:
    case OS_OPT_PEND_FLAG_SET_ANY | OS_OPT_PEND_FLAG_CONSUME:
    case OS_OPT_PEND_FLAG_CLR_ALL | OS_OPT_PEND_NON_BLOCKING:
    case OS_OPT_PEND_FLAG_CLR_ANY | OS_OPT_PEND_NON_BLOCKING:
    case OS_OPT_PEND_FLAG_SET_ALL | OS_OPT_PEND_NON_BLOCKING:
    case OS_OPT_PEND_FLAG_SET_ANY | OS_OPT_PEND_NON_BLOCKING:
    case OS_OPT_PEND_FLAG_CLR_ALL | OS_OPT_PEND_FLAG_CONSUME | OS_OPT_PEND_NON_BLOCKING:
    case OS_OPT_PEND_FLAG_CLR_ANY | OS_OPT_PEND_FLAG_CONSUME | OS_OPT_PEND_NON_BLOCKING:
    case OS_OPT_PEND_FLAG_SET_ALL | OS_OPT_PEND_FLAG_CONSUME | OS_OPT_PEND_NON_BLOCKING:
    ase OS_OPT_PEND_FLAG_SET_ANY | OS_OPT_PEND_FLAG_CONSUME | OS_OPT_PEND_NON_BLOCKING:
    break;                     //直接跳出

    default:                    (8)//如果选项超出预期
        *p_err = OS_ERR_OPT_INVALID;//错误类型为“选项非法”
        return ((OS_OBJ_QTY)0);    //返回0(有错误),停止执行
    }
#endif

#if OS_CFG_OBJ_TYPE_CHK_EN > 0u//如果启用了对象类型检测
    if (p_grp->Type != OS_OBJ_TYPE_FLAG)   //如果 p_grp 不是事件类型
    {
        *p_err = OS_ERR_OBJ_TYPE;           //错误类型为“对象类型有误”
        return ((OS_FLAGS)0);              //返回0(有错误),停止执行
    }
#endif

    if ((opt & OS_OPT_PEND_FLAG_CONSUME) != (OS_OPT)0)(9)//选择了标志位匹配后自动取反
    {
        consume = DEF_TRUE;
    }
    else(10)//未选择标志位匹配后自动取反
    {
        consume = DEF_FALSE;
    }

    if (p_ts != (CPU_TS *)0)        //如果 p_ts 非空
    {
        *p_ts = (CPU_TS)0;           //初始化(清零)p_ts,待用于返回时间戳
    }

    mode = opt & OS_OPT_PEND_FLAG_MASK; (11)//从选项中提取对标志位的要求
    CPU_CRITICAL_ENTER();                 //关中断
    switch (mode)                (12)//根据事件触发模式分类处理
    {
        case OS_OPT_PEND_FLAG_SET_ALL:   (13)//如果要求所有标志位均要置1
        flags_rdy = (OS_FLAGS)(p_grp->Flags & flags); //提取想要的标志位的值
        if (flags_rdy == flags)        (14)//如果该值与期望值匹配
        {
            if (consume == DEF_TRUE)(15)//如果要求将标志位匹配后取反
            {
                p_grp->Flags &= ~flags_rdy;           //清零事件的相关标志位
            }
            OSTCBCurPtr->FlagsRdy = flags_rdy; (16)//保存让任务脱离等待的标志值
            if (p_ts != (CPU_TS *)0)            //如果 p_ts 非空
            {
                *p_ts  = p_grp->TS;          //获取任务等到事件时的时间戳
            }
            CPU_CRITICAL_EXIT();            //开中断
            *p_err = OS_ERR_NONE;            //错误类型为“无错误”
            return (flags_rdy);     (17)//返回让任务脱离等待的标志值
        }
        else(18)
        //如果想要标志位的值与期望值不匹配
        {
            if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) //如果选择了不阻塞任务
            {
                CPU_CRITICAL_EXIT();                  //关中断
                *p_err = OS_ERR_PEND_WOULD_BLOCK;     //错误类型为“渴求阻塞”
                return ((OS_FLAGS)0);      (19)//返回0(有错误),停止执行
            }
            else(20)//如果选择了阻塞任务
            {
                if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0)   //如果调度器被锁
                {
                    CPU_CRITICAL_EXIT();              //关中断
                    *p_err = OS_ERR_SCHED_LOCKED;    //错误类型为“调度器被锁”
                    return ((OS_FLAGS)0);   (21)//返回0(有错误),停止执行
                }
            }
            /* 如果调度器未被锁 */
            OS_CRITICAL_ENTER_CPU_EXIT();             //进入临界段,重开中断
            OS_FlagBlock(&pend_data,             //阻塞当前运行任务,等待事件
                        p_grp,
                        flags,
                        opt,
                        timeout);           (22)
            OS_CRITICAL_EXIT_NO_SCHED();              //退出临界段(无调度)
        }
break;                                        //跳出

        case OS_OPT_PEND_FLAG_SET_ANY:      (23)//如果要求有标志位被置1即可
        flags_rdy = (OS_FLAGS)(p_grp->Flags & flags); //提取想要的标志位的值
        if (flags_rdy != (OS_FLAGS)0)     (24)//如果有位被置1
        {
            if (consume == DEF_TRUE)           //如果要求将标志位匹配后取反
            {
                p_grp->Flags &= ~flags_rdy;    //清零事件的相关标志位
            }
            OSTCBCurPtr->FlagsRdy = flags_rdy;  //保存让任务脱离等待的标志值
            if (p_ts != (CPU_TS *)0)                  //如果 p_ts 非空
            {
                *p_ts  = p_grp->TS;          //获取任务等到事件时的时间戳
            }
            CPU_CRITICAL_EXIT();                      //开中断
            *p_err = OS_ERR_NONE;                      //错误类型为“无错误”
            return (flags_rdy);      (25)//返回让任务脱离等待的标志值
        }
        else//如果没有位被置1
        {
            if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0)   //如果没设置阻塞任务
            {
                CPU_CRITICAL_EXIT();                  //关中断
                *p_err = OS_ERR_PEND_WOULD_BLOCK;     //错误类型为“渴求阻塞”
                return ((OS_FLAGS)0);     (26)//返回0(有错误),停止执行
            }
            else//如果设置了阻塞任务
            {
                if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0)   //如果调度器被锁
                {
                    CPU_CRITICAL_EXIT();              //关中断
                    *p_err = OS_ERR_SCHED_LOCKED;  //错误类型为“调度器被锁”
                    return ((OS_FLAGS)0);(27)//返回0(有错误),停止执行
                }
            }
            /* 如果调度器没被锁 */
            OS_CRITICAL_ENTER_CPU_EXIT();      //进入临界段,重开中断
            OS_FlagBlock(&pend_data,         //阻塞当前运行任务,等待事件
                        p_grp,
                        flags,
                        opt,
                        timeout);           (28)
            OS_CRITICAL_EXIT_NO_SCHED();       //退出中断(无调度)
        }
        break;                                        //跳出

#if OS_CFG_FLAG_MODE_CLR_EN > 0u          (29)
        //如果启用了标志位清零触发模式
        case OS_OPT_PEND_FLAG_CLR_ALL:               //如果要求所有标志位均要清零
        flags_rdy = (OS_FLAGS)(~p_grp->Flags & flags);//提取想要的标志位的值
        if (flags_rdy == flags)            (30)//如果该值与期望值匹配
        {
            if(consume == DEF_TRUE)          //如果要求将标志位匹配后取反
            {
                p_grp->Flags |= flags_rdy;  (31)//置1事件的相关标志位
            }
            OSTCBCurPtr->FlagsRdy = flags_rdy;  //保存让任务脱离等待的标志值
            if (p_ts != (CPU_TS *)0)                  //如果 p_ts 非空
            {
                *p_ts  = p_grp->TS;           //获取任务等到事件时的时间戳
            }
            CPU_CRITICAL_EXIT();               //开中断
            *p_err = OS_ERR_NONE;              //错误类型为“无错误”
            return (flags_rdy);                //返回0(有错误),停止执行
        }
        else
        //如果想要标志位的值与期望值不匹配
        {
            if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) //如果选择了不阻塞任务
            {
                CPU_CRITICAL_EXIT();                  //关中断
                *p_err = OS_ERR_PEND_WOULD_BLOCK;    //错误类型为“渴求阻塞”
                return ((OS_FLAGS)0);   (32)//返回0(有错误),停止执行
            }
            else//如果选择了阻塞任务
            {
                if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0)   //如果调度器被锁
                {
                    CPU_CRITICAL_EXIT();           //关中断
                    *p_err = OS_ERR_SCHED_LOCKED;  //错误类型为“调度器被锁”
                    return ((OS_FLAGS)0); (33)//返回0(有错误),停止执行
                }
            }
            /* 如果调度器未被锁 */
            OS_CRITICAL_ENTER_CPU_EXIT();        //进入临界段,重开中断
            OS_FlagBlock(&pend_data,             //阻塞当前运行任务,等待事件
                        p_grp,
                        flags,
                        opt,
                        timeout);           (34)
            OS_CRITICAL_EXIT_NO_SCHED();        //退出临界段(无调度)
        }
        break;                                 //跳出

        case OS_OPT_PEND_FLAG_CLR_ANY:     (35)//如果要求有标志位被清零即可
        flags_rdy = (OS_FLAGS)(~p_grp->Flags & flags);//提取想要的标志位的值
        if (flags_rdy != (OS_FLAGS)0)                //如果有位被清零
        {
            if (consume == DEF_TRUE)           //如果要求将标志位匹配后取反
            {
                p_grp->Flags |= flags_rdy; (36)//置1事件的相关标志位
            }
            OSTCBCurPtr->FlagsRdy = flags_rdy;   //保存让任务脱离等待的标志值
            if (p_ts != (CPU_TS *)0)                 //如果 p_ts 非空
            {
                *p_ts  = p_grp->TS;             //获取任务等到事件时的时间戳
            }
            CPU_CRITICAL_EXIT();                     //开中断
            *p_err = OS_ERR_NONE;                     //错误类型为“无错误”
            return (flags_rdy);        (37)//返回0(有错误),停止执行
        }
        else//如果没有位被清零
        {
            if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0)   //如果没设置阻塞任务
            {
                CPU_CRITICAL_EXIT();                 //开中断
                *p_err = OS_ERR_PEND_WOULD_BLOCK;     //错误类型为“渴求阻塞”
                return ((OS_FLAGS)0);      (38)//返回0(有错误),停止执行
            }
            else//如果设置了阻塞任务
            {
                if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0)   //如果调度器被锁
                {
                    CPU_CRITICAL_EXIT();             //开中断
                    *p_err = OS_ERR_SCHED_LOCKED;   //错误类型为“调度器被锁”
                    return ((OS_FLAGS)0);   (39)//返回0(有错误),停止执行
                }
            }
            /* 如果调度器没被锁 */
            OS_CRITICAL_ENTER_CPU_EXIT();            //进入临界段,重开中断
            OS_FlagBlock(&pend_data,           //阻塞当前运行任务,等待事件
                        p_grp,
                        flags,
                        opt,
                        timeout);           (40)
            OS_CRITICAL_EXIT_NO_SCHED();             //退出中断(无调度)
        }
        break;                                       //跳出
#endif

        default:                                 (41)//如果要求超出预期
        CPU_CRITICAL_EXIT();
        *p_err = OS_ERR_OPT_INVALID;                  //错误类型为“选项非法”
        return ((OS_FLAGS)0);                   //返回0(有错误),停止执行
    }

    OSSched();                             (42)//任务调度
    /* 任务等到了事件后得以继续运行 */
    CPU_CRITICAL_ENTER();                                 //关中断
    switch (OSTCBCurPtr->PendStatus)       (43)
    //根据运行任务的等待状态分类处理
    {
        case OS_STATUS_PEND_OK:              (44)//如果等到了事件
        if (p_ts != (CPU_TS *)0)                     //如果 p_ts 非空
        {
            *p_ts  = OSTCBCurPtr->TS;             //返回等到事件时的时间戳
        }
        *p_err = OS_ERR_NONE;                         //错误类型为“无错误”
        break;                                       //跳出

        case OS_STATUS_PEND_ABORT:           (45)//如果等待被中止
        if (p_ts != (CPU_TS *)0)                     //如果 p_ts 非空
        {
            *p_ts  = OSTCBCurPtr->TS;             //返回等待被中止时的时间戳
        }
        CPU_CRITICAL_EXIT();                         //开中断
        *p_err = OS_ERR_PEND_ABORT;                 //错误类型为“等待被中止”
        break;                                       //跳出

        case OS_STATUS_PEND_TIMEOUT:          (46)//如果等待超时
        if (p_ts != (CPU_TS *)0)                     //如果 p_ts 非空
        {
            *p_ts  = (CPU_TS  )0;                     //清零 p_ts
        }
        CPU_CRITICAL_EXIT();                         //开中断
        *p_err = OS_ERR_TIMEOUT;                      //错误类型为“超时”
        break;                                       //跳出

        case OS_STATUS_PEND_DEL:           (47)//如果等待对象被删除
        if (p_ts != (CPU_TS *)0)                     //如果 p_ts 非空
        {
            *p_ts  = OSTCBCurPtr->TS;              //返回对象被删时的时间戳
        }
        CPU_CRITICAL_EXIT();                         //开中断
        *p_err = OS_ERR_OBJ_DEL;                      //错误类型为“对象被删”
        break;                                       //跳出

        default:                             (48)//如果等待状态超出预期
        CPU_CRITICAL_EXIT();                         //开中断
        *p_err = OS_ERR_STATUS_INVALID;               //错误类型为“状态非法”
        break;                                       //跳出
    }
    if (*p_err != OS_ERR_NONE)           (49)//如果有错误存在
    {
        return ((OS_FLAGS)0);               //返回0(有错误),停止执行
    }
    /* 如果没有错误存在 */
    flags_rdy = OSTCBCurPtr->FlagsRdy;    (50)//读取让任务脱离等待的标志值
    if (consume == DEF_TRUE)
    //如果需要取反触发事件的标志位
    {
    switch (mode)                    (51)//根据事件触发模式分类处理
        {
            case OS_OPT_PEND_FLAG_SET_ALL:       //如果是通过置1来标志事件的发生
            case OS_OPT_PEND_FLAG_SET_ANY:
            p_grp->Flags &= ~flags_rdy;  (52)//清零事件里触发事件的标志位
            break;                                   //跳出

#if OS_CFG_FLAG_MODE_CLR_EN > 0u//如果启用了标志位清零触发模式
            case OS_OPT_PEND_FLAG_CLR_ALL:       //如果是通过清零来标志事件的发生
            case OS_OPT_PEND_FLAG_CLR_ANY:
            p_grp->Flags |=  flags_rdy;  (53)//置1事件里触发事件的标志位
            break;                                   //跳出
#endif
            default:                                      //如果触发模式超出预期
            CPU_CRITICAL_EXIT();                     //开中断
            *p_err = OS_ERR_OPT_INVALID;              //错误类型为“选项非法”
            return ((OS_FLAGS)0);         (54)//返回0(有错误),停止执行
        }
    }
    CPU_CRITICAL_EXIT();                      //开中断
    *p_err = OS_ERR_NONE;                     //错误类型为“无错误”
return (flags_rdy);                  (55)//返回让任务脱离等待的标志值
}
  • (1):事件指针。
  • (2):选定要等待的标志位。
  • (3):等待不到事件时指定阻塞时间(单位:时钟节拍)。
  • (4):等待的选项。
  • (5):保存返回等到事件标志时的时间戳。
  • (6):保存返回错误类型。
  • (7):此处是判断一下等待的选项是否在预期内,如果在预期内则继续操作,跳出switch语句。
  • (8):如果选项超出预期,返回错误类型为“选项非法”的错误代码,并且退出,不继续执行等待事件操作。
  • (9):如果用户选择了标志位匹配后自动取反,变量consume就为DEF_TRUE。
  • (10):如果未选择标志位匹配后自动取反,变量consume则为DEF_FALSE。
  • (11):从选项中提取对标志位的要求,利用“&”运算操作符获取选项并且保存在mode变量中。
  • (12):根据事件触发模式分类处理。
  • (13):如果任务要求所有标志位均要置1,那么就提取想要的标志位的值保存在flags_rdy变量中。
  • (14):如果该值与任务的期望值匹配。
  • (15):如果要求将标志位匹配后取反,就将事件的相关标志位清零。
  • (16):保存让任务脱离等待的标志值,此时已经等待到任务要求的事件了,就可以退出了。
  • (17):返回错误类型为“无错误”的错误代码与让任务脱离等待的标志值。
  • (18):如果想要标志位的值与期望值不匹配。
  • (19):并且如果用户选择了不阻塞任务,那么返回错误类型为“渴求阻塞”的错误代码,退出。
  • (20):而如果用户选择了阻塞任务。
  • (21):那就判断一下调度器是否被锁,如果被锁了,返回错误类型为“调度器被锁”的错误代码,并且退出。
  • (22):如果调度器没有被锁,则调用OS_FlagBlock()函数阻塞当前任务,在阻塞中继续等待任务需要的事件。
  • (23):如果要求有标志位被置1即可,那就提取想要的标志位的值保存在flags_rdy变量中。
  • (24):如果有任何一位被置1,就表示等待到了事件。如果要求将标志位匹配后取反,将事件的相关标志位清零。
  • (25):等待成功,就返回让任务脱离等待的标志值。
  • (26):如果没有位被置1,并且用户没有设置阻塞时间,那么就返回错误类型为“渴求阻塞”的错误代码,然后退出。
  • (27):如果设置了阻塞任务,但是调度器被锁了,返回错误类型为“调度器被锁”的错误代码,并且退出。
  • (28):如果调度器没被锁, 则调用OS_FlagBlock()函数阻塞当前任务,在阻塞中继续等待任务需要的事件。
  • (29):如果启用了标志位清零触发模式(宏定义OS_CFG_FLAG_MODE_CLR_EN被配置为1), 则在编译的时候会包含事件清零触发相关代码。
  • (30):如果要求所有标志位均要清零, 首先提取想要的标志位的值保存在flags_rdy变量中,如果该值与任务的期望值匹配,那么就表示等待的事件。
  • (31):如果要求将标志位匹配后取反, 就置1事件的相关标志位,因为现在是清零触发的,事件标志位取反就是将对应标志位置一。
  • (32):如果想要标志位的值与期望值不匹配, 并且如果用户选择了不阻塞任务,那么返回错误类型为“渴求阻塞”的错误代码,退出。
  • (33):如果调度器被锁,返回错误类型为“调度器被锁”的错误代码,并且退出。
  • (34):如果调度器没有被锁,则调用OS_FlagBlock()函数阻塞当前任务,在阻塞中继续等待任务需要的事件。
  • (35):如果要求有标志位被清零即可,提取想要的标志位的值,如果有位被清零则表示等待到事件。
  • (36):如果要求将标志位匹配后取反,将事件的相关标志位置1。
  • (37):等待到事件就返回对应的事件标志位。
  • (38):如果没有位被清零,并且如果用户没设置阻塞任务,那么就返回错误类型为“渴求阻塞”的错误代码,然后退出。
  • (39):如果设置了阻塞任务,但是调度器被锁了,返回错误类型为“调度器被锁”的错误代码,并且退出。
  • (40):如果调度器没有被锁,则调用OS_FlagBlock()函数阻塞当前任务,在阻塞中继续等待任务需要的事件。
  • (41):如果要求超出预期,返回错误类型为“选项非法”的错误代码,退出。
  • (42):执行到这里,说明任务没有等待到事件,并且用户还选择了阻塞任务,那么就进行一次任务调度。
  • (43):当程序能执行到这里,就说明大体上有两种情况, 要么是任务获取到对应的事件了;要么任务还没获取到事件(任务没获取到事件的情况有很多种),无论是哪种情况,都先把中断关掉再说,再根据当前运行任务的等待状态分类处理。
  • (44):如果等到了事件,返回等到事件时的时间戳,然后退出。
  • (45):如果任务在等待事件中被中止,返回等待被中止时的时间戳,记录错误类型为“等待被中止”的错误代码,然后退出。
  • (46):如果等待超时,返回错误类型为“等待超时”的错误代码,退出。
  • (47):如果等待对象被删除,返回对象被删时的时间戳,记录错误类型为“对象被删”的错误代码,退出。
  • (48):如果等待状态超出预期,记录错误类型为“状态非法”的错误代码,退出。
  • (49):如果有错误存在,返回0,表示没有等待到事件。
  • (50):如果没有错误存在,如果需要取反触发事件的标志位。
  • (51):根据事件触发模式分类处理。
  • (52):如果是通过置1来标志事件的发生,将事件里触发事件的标志位清零。
  • (53):如果是通过清零来标志事件的发生,那么就将事件里触发事件的标志位置1。
  • (54):如果触发模式超出预期,返回错误类型为“选项非法”的错误代码。
  • (55):返回让任务脱离等待的标志值。

在这个函数中,根据不同的等待模式,任务会表现出不同的等待行为:

  1. 等待所有指定标志位被置位

    • 当选项为 OS_OPT_PEND_FLAG_SET_ALL 时,任务会等待所有指定的事件标志被置位。
    • 如果所有指定的事件标志被置位,则任务解除等待状态,返回所等待的事件标志,并根据需要进行后续处理,如消费事件标志或保留事件标志。
    • 如果指定的事件标志未被置位:
      • 如果选项包含 OS_OPT_PEND_NON_BLOCKING,则立即返回错误表示任务不会被阻塞。
      • 否则,任务会进入阻塞状态,直到事件标志被置位、超时或者其他任务取消了该等待。
  2. 等待任一指定标志位被置位

    • 当选项为 OS_OPT_PEND_FLAG_SET_ANY 时,任务会等待任一指定的事件标志被置位。
    • 如果任一指定的事件标志被置位,则任务解除等待状态,返回所等待的事件标志,并根据需要进行后续处理,如消费事件标志或保留事件标志。
    • 如果指定的事件标志都未被置位:
      • 如果选项包含 OS_OPT_PEND_NON_BLOCKING,则立即返回错误表示任务不会被阻塞。
      • 否则,任务会进入阻塞状态,直到任一指定的事件标志被置位、超时或者其他任务取消了该等待。
  3. 等待所有指定标志位被清零(如果系统配置支持):

    • 当选项为 OS_OPT_PEND_FLAG_CLR_ALL 时,任务会等待所有指定的事件标志被清零。
    • 如果所有指定的事件标志被清零,则任务解除等待状态,返回所等待的事件标志,并根据需要进行后续处理,如消费事件标志或保留事件标志。
    • 如果指定的事件标志未被清零:
      • 如果选项包含 OS_OPT_PEND_NON_BLOCKING,则立即返回错误表示任务不会被阻塞。
      • 否则,任务会进入阻塞状态,直到事件标志被清零、超时或者其他任务取消了该等待。
  4. 等待任一指定标志位被清零(如果系统配置支持):

    • 当选项为 OS_OPT_PEND_FLAG_CLR_ANY 时,任务会等待任一指定的事件标志被清零。
    • 如果任一指定的事件标志被清零,则任务解除等待状态,返回所等待的事件标志,并根据需要进行后续处理,如消费事件标志或保留事件标志。
    • 如果指定的事件标志都未被清零:
      • 如果选项包含 OS_OPT_PEND_NON_BLOCKING,则立即返回错误表示任务不会被阻塞。
      • 否则,任务会进入阻塞状态,直到任一指定的事件标志被清零、超时或者其他任务取消了该等待。

至此,任务等待事件函数就已经讲解完毕,其实μC/OS这种利用状态机的方法等待事件,根据不一样的情况进行处理,是很好的,省去很多逻辑的代码。

下面简单分析处理过程:
当用户调用这个函数接口时,系统首先根据用户指定参数和接收选项来判断它要等待的事件是否发生。
如果已经发生,则根据等待选项来决定是否清除事件的相应标志位,并且返回事件标志位的值,但是这个值可能不是一个稳定的值, 所以在等待到对应事件的时候,我们最好要判断事件是否与任务需要的一致;
如果事件没有发生,则把任务添加到事件等待列表中, 将当前任务阻塞,直到事件发生或等待时间超时。

事件等待函数OSFlagPend()使用实例具体如下:

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

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

static  void  AppTaskPend ( void * p_arg )
{
    OS_ERR      err;


    (void)p_arg;

    //任务体
    while (DEF_TRUE)
    {
        //等待标志组的的BIT0和BIT1均被置1
        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,
                    (CPU_TS      *)0,
                    (OS_ERR      *)&err);

        LED3_ON ();        //点亮LED3

        //等待标志组的的BIT0和BIT1有一个被清零
        OSFlagPend ((OS_FLAG_GRP *)&flag_grp,
                    (OS_FLAGS     )( KEY1_EVENT | KEY2_EVENT ),
                    (OS_TICK      )0,
                    (OS_OPT       )OS_OPT_PEND_FLAG_CLR_ANY |
                    OS_OPT_PEND_BLOCKING,
                    (CPU_TS      *)0,
                    (OS_ERR      *)&err);

        LED3_OFF ();          //熄灭LED3

    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1579343.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uniapp请求后端接口

新建文件夹utils const request (config) > {// 拼接完整的接口路径config.url http://mm.test.cn config.url;//这里拼接的是访问后端接口的地址&#xff0c;http://mm.test.cn/prod-api/testconsole.log(config.url)//判断是都携带参数if(!config.data){config.data …

比派科技(BananaPI) 和 Qbee 达成战略合作伙伴关系,共同推动物联网技术发展

比派科技&#xff08;Banana PI&#xff09;&#xff08;https://banana-pi.org/&#xff09;和Qbee&#xff08;https://qbee.io/&#xff09;很高兴地宣布建立战略合作伙伴关系&#xff0c;旨在推动物联网&#xff08;IoT&#xff09;技术领域的创新和发展。 作为在物联网领…

[官方推荐]通义灵码做活动,送礼品,快来薅羊毛!!!

你的编辑器装上智能ai编辑了吗&#xff0c;的确挺好用的。 最近阿里云AI编码搞活动&#xff0c;可以免费体验并且还可以抽盲盒。有日历、马克杯、代金券、等等其他数码产品。 大多数都是日历。 点击链接参与「通义灵码 体验 AI 编码&#xff0c;开 AI 盲盒」 https://develope…

代码随想录阅读笔记-二叉树【总结】

二叉树的理论基础 代码随想录 (programmercarl.com)&#xff1a;二叉树的种类、存储方式、遍历方式、定义方式 二叉树的遍历方式 深度优先遍历 代码随想录阅读笔记-二叉树【递归遍历】-CSDN博客&#xff1a;递归三部曲初次亮相代码随想录阅读笔记-二叉树【迭代遍历】-CSDN博…

护眼灯值不值得买?入手不亏护眼灯十大品牌推荐

如今&#xff0c;我们不难发现越来越多的人早早地戴上了眼镜。这背后&#xff0c;或许是频繁接触电子产品&#xff0c;或许是长时间的学习&#xff0c;又或许是长时间处于不健康的光线环境。无论原因如何&#xff0c;我们都深知营造良好的光线环境对于保护视力至关重要。市面上…

Maven POM元素解析

这是对Maven中使用的Maven项目描述符的引用。 <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/…

SQL语法 之 数据库多表查询

对这篇文章的延申 Mysql-全外连接-Union和Union ALL的辨析及练习-CSDN博客 其他SQL系列文章&#xff1a; MySQL《一》-数据库基础_宋红康主要课程-CSDN博客 MySQL《二》-基本查询语句&#xff08;Select)-CSDN博客 Mysql - 定点型(DECIMAL)的使用详解及练习-CSDN博客 MySQ…

RuleEngine规则引擎底层改造AviatorScript 之公式规则

前情提要&#xff0c;看上一个文章&#xff0c;具体要实现的效果就是 当然上来的问题就是前端的问题&#xff0c;这个框首先他们用的是富文本&#xff0c;富文本传到后台的结果是前端脚本&#xff0c;带着h5的标签&#xff0c;后面改成了这个&#xff0c;当时这个东西其实和后…

【Linux】TCP编程{socket/listen/accept/telnet/connect/send}

文章目录 1.TCP接口1.1socket文档 1.2listen拓&#xff1a;端口号8080 1.3accept拓&#xff1a;今天全局函数 1.4读写接口1.5telnet1.一个客户端2.两个客户端 1.6ulimit -a1.7常识回顾1.8connect1.9拓&#xff1a;客户端的ip和地址什么时候被分配&#xff1f;1.10拓&#xff1a…

【接口自动化】参数化替换

在做接口测试时&#xff0c;除了测单个接口&#xff0c;还需要进行业务链路间的接口测试 比如[注册-登陆]需要token鉴权的业务流 当我们用使用postman/jmeter等工具时&#xff0c;将注册接口的一些响应信息提取出来&#xff0c;放到登陆接口的请求中&#xff0c;来完成某个业务…

【Redis】Redis群集的三种模式(主从、哨兵、群集)

redis群集有三种模式&#xff0c;分别是主从同步/复制、哨兵模式、Cluster&#xff0c;下面会讲解一下三种模式的工作方式&#xff0c;以及如何搭建cluster群集 ●主从复制&#xff1a;主从复制是高可用Redis的基础&#xff0c;哨兵和集群都是在主从复制基础上实现高可用的。主…

Qt案例 通过调用Setupapi.h库实现对设备管理器中设备默认驱动的备份

参考腾讯电脑管家-软件市场中的驱动备份专家写的一个驱动备份软件案例&#xff0c;学习Setupapi.h库中的函数使用.通过Setupapi.h库读取设备管理器中安装的设备获取安装的驱动列表&#xff0c;通过bit7z库备份驱动目录下的所有文件. 目录导读 实现效果相关内容示例获取SP_DRVIN…

DevOps已死?2024年的DevOps将如何发展

随着我们进入2024年&#xff0c;DevOps也发生了变化。新兴的技术、变化的需求和发展的方法正在重新定义有效实施DevOps实践。 IDC预测显示&#xff0c;未来五年&#xff0c;支持DevOps实践的产品市场继续保持健康且快速增长&#xff0c;2022年-2027年的复合年增长率&#xff0…

数字人解决方案——Champ单个视频单张图像生成可控且一致的人体视频生成

概述 Champ是阿里巴巴集团、南京大学和复旦大学的研究团队共同提出了一种创新的人体动画生成技术&#xff0c;Champ能够在仅有一段原始视频和一张静态图片的情况下&#xff0c;激活图片中的人物&#xff0c;使其按照视频中的动作进行动态表现&#xff0c;极大地促进了虚拟主播…

Git-LFS 远程命令执行漏洞 CVE-2020-27955 漏洞复现

今天遇到了一个比较有意思的洞&#xff0c;复现一下下.......... 漏洞描述 Git LFS 是 Github 开发的一个 Git 的扩展&#xff0c;用于实现 Git 对大文件的支持 一些受影响的产品包括Git&#xff0c;GitHub CLI&#xff0c;GitHub Desktop&#xff0c;Visual Studio&#xff0…

C#开发常用的库使用-Ioc库Autofac的使用

介绍 Autofac 是一个适用于 .NET 的强大的 IoC&#xff08;控制反转&#xff09;容器。它管理类之间的依赖关系&#xff0c;使得随着应用程序规模和复杂性的增长&#xff0c;应用程序仍然易于修改。它允许您管理 .NET 应用程序中的组件依赖关系&#xff0c;并自动将依赖项解析…

数据库(mysql)-基本查询语句(DQL)

查询语句 这边查询是给予一定表格,这边先做个解释 教师表包括(name(姓名),gender(性别),salary(工资),title(职位),subject_id(课程的编号),comm(奖金)) 学生表包括(姓名(name),gender(性别),job(职位),生日(birth)) 模版 SELECT 字段名 FROM 查询表 WHERE 查询语句 或与非…

Linux|从 STDIN 读取 Awk 输入

简介 在之前关于 Awk 工具的系列文章中&#xff0c;主要探讨了如何从文件中读取数据。但如果你希望从标准输入&#xff08;STDIN&#xff09;中读取数据&#xff0c;又该如何操作呢&#xff1f; 在本文中&#xff0c;将介绍几个示例&#xff0c;展示如何使用 Awk 来过滤其他命令…

Go语言实战:掌握html/template包的高效开发技巧

Go语言实战&#xff1a;掌握html/template包的高效开发技巧 引言Go标准库html包概览html包的基本用途html/template包的核心功能开始使用html/template应用场景 安全的HTML内容处理防范XSS攻击使用示例内容安全策略&#xff08;CSP&#xff09;小结 操作HTML元素创建和解析HTML…

java数据结构与算法刷题-----LeetCode628. 三个数的最大乘积

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 排序选择线性搜索最值 排序 解题思路&#xff1a;时间复杂度O( …