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

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


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


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

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

    }

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

stable diffusion的从安装到使用

stable-diffusion&#xff0c;一个免费开源的文生图软件&#xff0c;文章主要讲怎么从源码开始安装&#xff0c;以及使用的方式 git地址&#xff1a;https://github.com/AUTOMATIC1111/stable-diffusion-webui 本人电脑环境win10&#xff0c;软件pycharm&#xff0c;需要提前…

基于直方图的图像曝光量分析FPGA实现,包含tb测试文件和MATLAB辅助验证

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 正常图像&#xff1a; checkb位于f192b和f250b之间 多度曝光图像&#xff1a; checkb位于f192b和f250b之外&#xff0c;判决为曝光过度。 2.算法…

k8s_入门_kubelet安装

安装 在大致了解了一些k8s的基本概念之后&#xff0c;我们实际部署一个k8s集群&#xff0c;做进一步的了解 1. 裸机安装 采用三台机器&#xff0c;一台机器为Master&#xff08;控制面板组件&#xff09;两台机器为Node&#xff08;工作节点&#xff09; 机器的准备有两种方式…

【神经网络】卷积神经网络CNN

卷积神经网络 欢迎访问Blog全部目录&#xff01; 文章目录 卷积神经网络1. 神经网络概览2.CNN&#xff08;Convolutional Neunal Network&#xff09;2.1.学习链接2.2.CNN结构2.2.1.基本结构2.2.1.1输入层2.2.1.2.卷积层|Convolution Layers2.2.1.3.池化层|Pooling layers2.3…

设计模式——2_8 策略(Strategy)

文章目录 定义图纸一个例子&#xff1a;如何切换坦克的攻击方式GameElement&#xff08;游戏元素&#xff09;TankFactory&#xff08;坦克工厂&#xff09;Tank&#xff08;坦克&#xff09; 医疗车和飞行车策略模式Behavior(行为)TankTankFactory 碎碎念策略和状态为什么我们…

[数据结构]双向带头循环链表制作

前面我们有提到&#xff0c;单向不带头循环链表的制作 这里我们介绍一个双向带头循环链表的制作方法 双向带头循环链表的示意图如下 带头指针的作用体现在哪呢? 第一、防止头节点为空,既有头结点&#xff0c;头指针始终指向头结点&#xff0c;那么无论链表是否为空&#xf…

游戏公司面试题系列-CocosCreator实现虚拟摇杆控制角色移动中心旋转自转小球割草旋转逻辑

游戏公司面试题系列-CocosCreator实现虚拟摇杆控制角色移动&中心旋转自转小球&割草旋转逻辑<&#xff01;&#xff01;&#xff01;文章末尾有完整代码下载链接地址&#xff01;&#xff01;&#xff01;> Hello大家好&#xff01;今天我们来用最新的CocosCreat…

pringboot2集成swagger2出现guava的FluentIterable方法不存在

错误信息 Description: An attempt was made to call a method that does not exist. The attempt was made from the following location: springfox.documentation.spring.web.scanners.ApiListingScanner.scan(ApiListingScanner.java:117) The following method did not ex…

PHP运算符与流程控制

华子目录 运算符赋值运算符算术运算符比较运算符逻辑运算符连接运算符错误抑制符三目运算符自操作运算符 计算机码位运算符 运算符优先级流程控制控制分类顺序结构分支结构if分支switch分支 循环结构for循环while循环continuebreak 运算符 运算符&#xff1a;operator&#xf…

谷歌留痕霸屏要怎么做?

谷歌留痕霸屏&#xff0c;就是让你的网站或者页面在谷歌搜索结果里尽可能多地出现&#xff0c;就像是在你的潜在客户眼前留下深刻印象一样&#xff0c;你要做的就是在一些高权重平台发布有价值的信息&#xff0c;同时巧妙地留下你的品牌名、产品名或者任何你想要推广的关键词&a…

谷歌不收录怎么办?

谷歌不收录首先你要确认自己网站有没有出问题&#xff0c;比如你的网站是否已经公开&#xff0c;rboot是否允许搜索引擎进来&#xff0c;网站架构有没有问题&#xff0c;面包屑的结构是否有问题&#xff0c;确保你的网站没问题 接下来就是优化这个过程&#xff0c;有内容&#…

python|drop的应用

drop 删除列B 删除索引为1的行 删除列为‘A’&#xff0c;‘C’的列&#xff0c;axis表示方向 删除时保留原始 DataFrame&#xff08;使用 inplaceFalse&#xff09; 删除时直接修改原始 DataFrame&#xff08;使用 inplaceTrue&#xff09;

SEO优化艺术:精细化技巧揭示与搜索引擎推广全面战略解读

SEO&#xff08;搜索引擎优化&#xff0c;Search Engine Optimization&#xff09;是一种网络营销策略&#xff0c;旨在通过改进网站内外的各项元素&#xff0c;提升网站在搜索引擎自然搜索结果中的排名&#xff0c;从而吸引更多目标用户访问网站&#xff0c;增加流量&#xff…

如何快速开启一个项目-ApiHug - API design Copilot

ApiHug101-001开启篇 &#x1f917; ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱&#xff0c;有温度&#xff0c;有质量&#xff0c;有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin |…

少儿编程 2024年3月电子学会图形化编程等级考试Scratch二级真题解析(判断题)

2024年3月scratch编程等级考试二级真题 判断题&#xff08;共10题&#xff0c;每题2分&#xff0c;共20分&#xff09; 26、下列积木块运行结果为false 答案&#xff1a;错 考点分析&#xff1a;考查积木综合使用&#xff0c;重点考查逻辑或积木的使用&#xff0c;或运算是只…

用Echarts词云数据可视化热词表白​​

目录 1、使用前准备 2、准备工作 3、盒子搭建 4、整体展现 1、使用前准备 找到表白对象&#xff08;重中之重&#xff01;&#xff09;&#xff0c;不要一见钟情&#xff08;个人觉得&#xff1a;一见钟情属于见色起意&#xff01;&#xff09;&#xff0c;因为数据可视化需…

中颖51芯片学习3. 定时器

中颖51芯片学习3. 定时器 一、SH79F9476定时器简介1. 简介2. 定时器运行模式 二、定时器21. 说明&#xff08;1&#xff09;时钟&#xff08;2&#xff09;工作模式 2. 寄存器&#xff08;1&#xff09;控制寄存器 T2CON&#xff08;2&#xff09;定时器2模式控制寄存器 T2MOD …

sql注入方式之联合注入

1.1 靶场环境 系统centos7 IP地址192.168.1.24 1.2 联合注入原理 联合查询注入是联合两个表进行注入攻击&#xff0c;使用关键词 union select 对两个表进行联合查询。两个表的字段要数要相同&#xff0c;不然会出现报错。 1.3 找注入点 找注入点&#xff0c;当输入id1 an…

你知道哪几种当前流行的lisp语言的方言?

估计很多人都看过《黑客与画家》这本书&#xff0c;这本书主要介绍黑客即优秀程序员的爱好和动机&#xff0c;讨论黑客成长、黑客对世界的贡献以及编程语言和黑客工作方法等所有对计算机时代感兴趣的人的一些话题。作者保罗格雷厄姆字里行间不经意间向大家推介Lisp是最好的编程…

【linux】set ff=unix、linux设置文件格式

文章目录 一、文件格式二、如何查看文件格式三、设置文件格式、set ffunix四、查看unix与dos的区别 一、文件格式 当我们打开sh脚本时发现有时候格式是unix(LF) ,有时候是windows(CR LF) 。如下图&#xff1a; 文件格式影响了文件中的换行符 linux中sh类型的文件一般要设置为…