(学习日记)2024.04.15:UCOSIII第四十三节:任务消息队列

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


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


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

2024.04.15:UCOSIII第四十三节:任务消息队列

  • 五十七、UCOSIII:任务消息队列
    • 1、任务消息队列的基本概念
    • 2、任务消息队列的函数接口讲解
      • 1. 任务消息队列发送函数OSTaskQPost()
      • 2. 任务消息队列获取函数OSTaskQPend()
    • 3、任务消息队列实验
    • 4、任务消息队列实验现象

五十七、UCOSIII:任务消息队列

1、任务消息队列的基本概念

任务消息队列跟任务信号量一样,均隶属于某一个特定任务,不需单独创建,任务在则任务消息队列在,只有该任务才可以获取(接收)这个任务消息队列的消息, 其他任务只能给这个任务消息队列发送消息,却不能获取。
任务消息队列与前面讲解的(普通)消息队列极其相似,只是任务消息队列已隶属于一个特定任务, 所以它不具有等待列表,在操作的过程中省去了等待任务插入和移除列表的动作,所以工作原理相对更简单一点,效率也比较高一些。

注意:
本专栏所提的“消息队列”,若无特别说明,均指前面的(普通)消息队列(属于内核对象),而非任务消息队列。

通过对任务消息队列的合理使用,可以在一定场合下替代μC/OS的消息队列,用户只需向任务内部的消息队列发送一个消息而不用通过外部的消息队列进行发送, 这样子处理就会很方便并且更加高效,当然,凡事都有利弊,任务消息队列虽然处理更快,RAM开销更小,但也有限制:只能指定消息发送的对象, 有且只有一个任务接收消息;而内核对象的消息队列则没有这个限制,用户在发送消息的时候,可以采用广播消息的方式,让所有等待该消息的任务都获取到消息。

在实际任务间的通信中,一个或多个任务发送一个消息给另一个任务是非常常见的,而一个任务给多个任务发送消息的情况相对比较少, 前者就很适合采用任务消息队列进行传递消息,如果任务消息队列可以满足设计需求,那么尽量不要使用普通消息队列,这样子设计的系统会更加高效。

(内核对象)消息队列是用结构体OS_Q来管理的,包含了管理消息的元素 MsgQ 和管理等待列表的元素 PendList等。 而任务消息队列的结构体成员变量就少了PendList,因为等待任务消息队列只有拥有任务消息队列本身的任务才可以进行获取, 故任务消息队列不需要等待列表的相关数据结构,具体如下:

注意:
想要使用任务消息队列,就必须将OS_CFG_TASK_Q_EN宏定义配置为1,该宏定义位于os_cfg.h文件中。

struct  os_msg_q
{
    OS_MSG              *InPtr;             (1)
    OS_MSG              *OutPtr;            (2)
    OS_MSG_QTY           NbrEntriesSize;    (3)
    OS_MSG_QTY           NbrEntries;                (4)
    OS_MSG_QTY           NbrEntriesMax;             (5)
};
  • (1)、(2):任务消息队列中进出消息指针。
  • (3):任务消息队列中最大可用的消息个数,在创建任务的时候由用户指定这个值的大小。
  • (4):记录任务消息队列中当前的消息个数, 每当发送一个消息到任务消息队列的时候,若任务没有在等待该消息,那么新发送的消息被插入任务消息队列后此值加1, NbrEntries 的大小不能超过NbrEntriesSize。
  • (5):记录任务消息队列最多的时候拥有的消息个数。

任务消息队列的运作机制与普通消息队列一样,没什么差别。

2、任务消息队列的函数接口讲解

1. 任务消息队列发送函数OSTaskQPost()

函数 OSTaskQPost()用来发送任务消息队列,参数中有指向消息要发送给的任务控制块的指针, 任何任务都可以发送消息给拥有任务消息队列的任务(任务在被创建的时候,要设置参数 q_size 大于 0), 其源码具体如下:

#if OS_CFG_TASK_Q_EN > 0u   //如果启用了任务消息队列
void  OSTaskQPost (OS_TCB       *p_tcb,     (1)     //目标任务
void         *p_void,       (2)     //消息内容地址
                OS_MSG_SIZE   msg_size,     (3)     //消息长度
                OS_OPT        opt,          (4)     //选项
                OS_ERR       *p_err)        (5)     //返回错误类型
{
    CPU_TS   ts;

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

#if OS_CFG_ARG_CHK_EN > 0u//如果启用了参数检测
    switch (opt)                          //根据选项分类处理
    {
    case OS_OPT_POST_FIFO:            //如果选项在预期内
    case OS_OPT_POST_LIFO:
    case OS_OPT_POST_FIFO | OS_OPT_POST_NO_SCHED:
    case OS_OPT_POST_LIFO | OS_OPT_POST_NO_SCHED:
    break;                       //直接跳出

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

    ts = OS_TS_GET();                                  //获取时间戳

#if OS_CFG_ISR_POST_DEFERRED_EN > 0u//如果启用了中断延迟发布
    if (OSIntNestingCtr > (OS_NESTING_CTR)0)       //如果该函数在中断中被调用
    {
        OS_IntQPost((OS_OBJ_TYPE)OS_OBJ_TYPE_TASK_MSG, //将消息先发布到中断消息队列
                    (void      *)p_tcb,
                    (void      *)p_void,
                    (OS_MSG_SIZE)msg_size,
                    (OS_FLAGS   )0,
                    (OS_OPT     )opt,
                    (CPU_TS     )ts,
                    (OS_ERR    *)p_err);            (6)
        return;                                         //返回
    }
#endif

    OS_TaskQPost(p_tcb,                                 //将消息直接发布
                p_void,
                msg_size,
                opt,
                ts,
                p_err);                             (7)
}
#endif
  • (1):目标任务。
  • (2):任务消息内容指针。
  • (3):任务消息的大小。
  • (4):发送的选项。
  • (5):用于保存返回的错误类型。
  • (6):如果启用了中断延迟发布,并且如果该函数在中断中被调用,就先将消息先发布到中断消息队列。
  • (7):调用OS_TaskQPost()函数将消息直接发送,其源码具体如下
#if OS_CFG_TASK_Q_EN > 0u//如果启用了任务消息队列
void  OS_TaskQPost (OS_TCB       *p_tcb,    //目标任务
                    void         *p_void,   //消息内容地址
                    OS_MSG_SIZE   msg_size, //消息长度
                    OS_OPT        opt,      //选项
                    CPU_TS        ts,       //时间戳
                    OS_ERR       *p_err)    //返回错误类型
{
    CPU_SR_ALLOC();  //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和
    //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器
    // SR(临界段关中断只需保存SR),开中断时将该值还原。

    OS_CRITICAL_ENTER();                                   //进入临界段
    if (p_tcb == (OS_TCB *)0)                (1)//如果 p_tcb 为空
    {
        p_tcb = OSTCBCurPtr;                          //目标任务为自身
    }
    *p_err  = OS_ERR_NONE;                            //错误类型为“无错误”
    switch (p_tcb->TaskState)                (2)//根据任务状态分类处理
    {
        case OS_TASK_STATE_RDY:                          //如果目标任务没等待状态
        case OS_TASK_STATE_DLY:
        case OS_TASK_STATE_SUSPENDED:
        case OS_TASK_STATE_DLY_SUSPENDED:
        OS_MsgQPut(&p_tcb->MsgQ,                    //把消息放入任务消息队列
                p_void,
                msg_size,
                opt,
                ts,
                p_err);                     (3)
        OS_CRITICAL_EXIT();                           //退出临界段
        break;                                        //跳出

        case OS_TASK_STATE_PEND:                        //如果目标任务有等待状态
        case OS_TASK_STATE_PEND_TIMEOUT:
        case OS_TASK_STATE_PEND_SUSPENDED:
        case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
        if (p_tcb->PendOn == OS_TASK_PEND_ON_TASK_Q) //如果等的是任务消息队列
        {
            OS_Post((OS_PEND_OBJ *)0,                 //把消息发布给目标任务
                    p_tcb,
                    p_void,
                    msg_size,
                    ts);                    (4)
            OS_CRITICAL_EXIT_NO_SCHED();              //退出临界段(无调度)
            if ((opt & OS_OPT_POST_NO_SCHED) == (OS_OPT)0u)   //如果要调度任务
            {
                OSSched();                                    //调度任务
            }
        }
        else(5)//如果没在等待任务消息队列
        {
            OS_MsgQPut(&p_tcb->MsgQ,             //把消息放入任务消息队列
                    p_void,
                    msg_size,
                    opt,
                    ts,
                    p_err);
            OS_CRITICAL_EXIT();                      //退出临界段
        }
        break;                                       //跳出

        default:                             (6)//如果状态超出预期
        OS_CRITICAL_EXIT();                          //退出临界段
        *p_err = OS_ERR_STATE_INVALID;                //错误类型为“状态非法”
        break;                                       //跳出
    }
}
#endif
  • (1):如果目标任务为空,则表示将任务消息释放给自己,那么p_tcb就指向当前任务。
  • (2):根据任务状态分类处理。
  • (3):如果目标任务没等待状态,就调用OS_MsgQPut()函数将消息放入队列中,执行完毕就退出。
  • (4):如果目标任务有等待状态, 那就看看是不是在等待任务消息队列,如果是的话,调用OS_Post()函数把任务消息发送给目标任务。
  • (5):如果任务并不是在等待任务消息队列, 那么调用OS_MsgQPut()函数将消息放入任务消息队列中即可。
  • (6):如果状态超出预期,返回错误类型为“状态非法”的错误代码。

任务消息队列的发送过程是跟消息队列发送过程差不多,先检查目标任务的状态,如果该任务刚刚好在等待任务消息队列的消息, 那么直接让任务脱离等待状态即可。
如果任务没有在等待任务消息队列的消息,那么就将消息插入要发送消息的任务消息队列。

任务消息队列发送函数的使用实例具体如下:

OS_ERR      err;

/* 发布消息到任务 AppTaskPend */
OSTaskQPost ((OS_TCB      *)&AppTaskPendTCB,          //目标任务的控制块
            (void        *)"YeHuo μC/OS-III",             //消息内容
            (OS_MSG_SIZE  )sizeof ( "YeHuo μC/OS-III" ),  //消息长度
            (OS_OPT       )OS_OPT_POST_FIFO,
//发布到任务消息队列的入口端
            (OS_ERR      *)&err);        

2. 任务消息队列获取函数OSTaskQPend()

与OSTaskQPost()任务消息队列发送函数相对应,OSTaskQPend()函数用于获取一个任务消息队列,函数的参数中没有指定哪个任务获取任务消息, 实际上就是当前执行的任务,当任务调用了这个函数就表明这个任务需要获取任务消息,OSTaskQPend()源码具体:

#if OS_CFG_TASK_Q_EN > 0u//如果启用了任务消息队列
void  *OSTaskQPend (OS_TICK       timeout,   (1)//等待期限(单位:时钟节拍)
                    OS_OPT        opt,       (2)    //选项
                    OS_MSG_SIZE  *p_msg_size, (3)   //返回消息长度
                    CPU_TS       *p_ts,       (4)   //返回时间戳
                    OS_ERR       *p_err)      (5)   //返回错误类型
{
    OS_MSG_Q     *p_msg_q;
    void         *p_void;
    CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和
    //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器
    // SR(临界段关中断只需保存SR),开中断时将该值还原。

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

#if OS_CFG_ARG_CHK_EN > 0u//如果启用了参数检测
    if (p_msg_size == (OS_MSG_SIZE *)0)      //如果 p_msg_size 为空
    {
        *p_err = OS_ERR_PTR_INVALID;          //错误类型为“指针不可用”
        return ((void *)0);                  //返回0(有错误),停止执行
    }
    switch (opt)                             //根据选项分类处理
    {
    case OS_OPT_PEND_BLOCKING:           //如果选项在预期内
    case OS_OPT_PEND_NON_BLOCKING:
    break;                          //直接跳出

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

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

    CPU_CRITICAL_ENTER();                           //关中断
    p_msg_q = &OSTCBCurPtr->MsgQ;        (6)//获取当前任务的消息队列
    p_void  = OS_MsgQGet(p_msg_q,                   //从队列里获取一个消息
                        p_msg_size,
                        p_ts,
                        p_err);     (7)
    if (*p_err == OS_ERR_NONE)                            //如果获取消息成功
    {
#if OS_CFG_TASK_PROFILE_EN > 0u

        if (p_ts != (CPU_TS *)0)
        {
            OSTCBCurPtr->MsgQPendTime = OS_TS_GET() - *p_ts;
            if (OSTCBCurPtr->MsgQPendTimeMax < OSTCBCurPtr->MsgQPendTime)
            {
                OSTCBCurPtr->MsgQPendTimeMax = OSTCBCurPtr->MsgQPendTime;
            }
        }
#endif
        CPU_CRITICAL_EXIT();                             //开中断
        return (p_void);                                 //返回消息内容
    }
    /* 如果获取消息不成功(队列里没有消息) */        (8)
    if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) //如果选择了不阻塞任务
    {
        *p_err = OS_ERR_PEND_WOULD_BLOCK;            //错误类型为“缺乏阻塞”
        CPU_CRITICAL_EXIT();                             //开中断
        return ((void *)0);                     //返回0(有错误),停止执行
    }
    else(9)//如果选择了阻塞任务
    {
        if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0)   //如果调度器被锁
        {
            CPU_CRITICAL_EXIT();                         //开中断
            *p_err = OS_ERR_SCHED_LOCKED;          //错误类型为“调度器被锁”
            return ((void *)0);                     //返回0(有错误),停止执行
        }
    }
    /* 如果调度器未被锁 */
    OS_CRITICAL_ENTER_CPU_EXIT();          (10)//锁调度器,重开中断
    OS_Pend((OS_PEND_DATA *)0,             (11)//阻塞当前任务,等待消息
            (OS_PEND_OBJ  *)0,
            (OS_STATE      )OS_TASK_PEND_ON_TASK_Q,
            (OS_TICK       )timeout);
    OS_CRITICAL_EXIT_NO_SCHED();                    //解锁调度器(无调度)

    OSSched();                             (12)//调度任务
        /* 当前任务(获得消息队列的消息)得以继续运行 */
    CPU_CRITICAL_ENTER();                (13)//关中断
    switch (OSTCBCurPtr->PendStatus)           //根据任务的等待状态分类处理
    {
        case OS_STATUS_PEND_OK:               (14)//如果任务已成功获得消息
        p_void      = OSTCBCurPtr->MsgPtr;          //提取消息内容地址
        *p_msg_size  = OSTCBCurPtr->MsgSize;         //提取消息长度
        if (p_ts != (CPU_TS *)0)                    //如果 p_ts 非空
        {
            *p_ts  = OSTCBCurPtr->TS;            //获取任务等到消息时的时间戳
#if OS_CFG_TASK_PROFILE_EN > 0u

            OSTCBCurPtr->MsgQPendTime = OS_TS_GET() - OSTCBCurPtr->TS;
            if (OSTCBCurPtr->MsgQPendTimeMax < OSTCBCurPtr->MsgQPendTime)
            {
                OSTCBCurPtr->MsgQPendTimeMax = OSTCBCurPtr->MsgQPendTime;
            }
#endif
        }
        *p_err = OS_ERR_NONE;                        //错误类型为“无错误”
        break;                                      //跳出

        case OS_STATUS_PEND_ABORT:           (15)//如果等待被中止
        p_void     = (void      *)0;                //返回消息内容为空
        *p_msg_size = (OS_MSG_SIZE)0;                //返回消息大小为0
        if (p_ts  != (CPU_TS *)0)                   //如果 p_ts 非空
        {
            *p_ts   = (CPU_TS  )0;                   //清零 p_ts
        }
        *p_err      =  OS_ERR_PEND_ABORT;            //错误类型为“等待被中止”
        break;                                      //跳出

        case OS_STATUS_PEND_TIMEOUT:          (16)//如果等待超时,
        default:                                         //或者任务状态超出预期。
        p_void     = (void      *)0;                //返回消息内容为空
        *p_msg_size = (OS_MSG_SIZE)0;                //返回消息大小为0
        if (p_ts  != (CPU_TS *)0)                   //如果 p_ts 非空
        {
            *p_ts   =  OSTCBCurPtr->TS;
        }
        *p_err      =  OS_ERR_TIMEOUT;               //错误类为“等待超时”
        break;                                      //跳出
    }
    CPU_CRITICAL_EXIT();                                 //开中断
    return (p_void);                    (17)//返回消息内容地址
}
#endif
  • (1):指定超时时间(单位:时钟节拍)。
  • (2):获取任务消息队列的选项。
  • (3):返回消息大小。
  • (4):返回时间戳。
  • (5):返回错误类型。
  • (6):获取当前任务的消息队列保存在p_msg_q变量中。
  • (7):调用OS_MsgQGet()函数从消息队列获取一个消息,如果获取消息成功,则返回指向消息的指针。
  • (8):如果获取消息不成功(任务消息队列里没有消息), 并且如果用户选择了不阻塞任务,那么返回错误类型为“缺乏阻塞”的错误代码,然后退出。
  • (9):如果选择了阻塞任务,先判断一下调度器是否被锁,如果被锁了也就不能继续执行。
  • (10):如果调度器未被锁,系统会锁调度器,重开中断。
  • (11):调用OS_Pend()函数将当前任务脱离就绪列表, 并根据用户指定的阻塞时间插入节拍列表,但是不会插入队列等待列表,然后打开调度器,但不进行调度,OS_Pend()源码具体见代码清单18‑18。
  • (12):进行一次任务调度。
  • (13):程序能执行到这里,就说明大体上有两种情况, 要么是任务获取到消息了;任务还没获取到消息(任务没获取到消息的情况有很多种),无论是哪种情况,都先把中断关掉再说,然后根据当前运行任务的等待状态分类处理。
  • (14):如果任务状态是OS_STATUS_PEND_OK, 则表示任务获取到消息了,那么就从任务控制块中提取消息,这是因为在发送消息给任务的时候,会将消息放入任务控制块的MsgPtr成员变量中, 然后继续提取消息大小,如果p_ts非空,记录获取任务等到消息时的时间戳,返回错误类型为“无错误”的错误代码,跳出switch语句。
  • (15):如果任务在等待(阻塞)重被中止, 则返回消息内容为空,返回消息大小为0,返回错误类型为“等待被中止”的错误代码,跳出switch语句。
  • (16):如果任务等待(阻塞)超时,说明等待的时间过去了, 任务也没获取到消息,则返回消息内容为空,返回消息大小为0,返回错误类型为“等待超时”的错误代码,跳出switch语句。
  • (17):打开中断,返回消息内容。

3、任务消息队列实验

任务通知代替消息队列是在ΜC/OS中创建了两个任务,其中一个任务是用于接收任务消息,另一个任务发送任务消息。
两个任务独立运行,发送消息任务每秒发送一次任务消息,接收任务在就一直在等待消息, 一旦获取到消息通知就把消息打印在串口调试助手里,具体如下:

#include <includes.h>

static  OS_TCB   AppTaskStartTCB;      //任务控制块
static  OS_TCB   AppTaskPostTCB;
static  OS_TCB   AppTaskPendTCB;
static  CPU_STK  AppTaskStartStk[APP_TASK_START_STK_SIZE];       //任务栈
static  CPU_STK  AppTaskPostStk [ APP_TASK_POST_STK_SIZE ];
static  CPU_STK  AppTaskPendStk [ APP_TASK_PEND_STK_SIZE ];
static  void  AppTaskStart  ( void *p_arg);               //任务函数声明
static  void  AppTaskPost   ( void * p_arg );
static  void  AppTaskPend   ( void * p_arg );

int  main (void)
{
    OS_ERR  err;
    OSInit(&err);                  //初始化 μC/OS

    /* 创建起始任务 */
    OSTaskCreate((OS_TCB     *)&AppTaskStartTCB,
                //任务控制块地址
                (CPU_CHAR   *)"App Task Start",          //任务名称
                (OS_TASK_PTR ) AppTaskStart,             //任务函数
                (void       *) 0,
                //传递给任务函数(形参p_arg)的实参
                (OS_PRIO     ) APP_TASK_START_PRIO,        //任务的优先级
                (CPU_STK    *)&AppTaskStartStk[0],
                //任务栈的基地址
                (CPU_STK_SIZE) APP_TASK_START_STK_SIZE / 10,
                //任务栈空间剩下1/10时限制其增长
                (CPU_STK_SIZE) APP_TASK_START_STK_SIZE,
                //任务栈空间(单位:sizeof(CPU_STK))
                (OS_MSG_QTY  ) 5u,
                //任务可接收的最大消息数
                (OS_TICK     ) 0u,
                //任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)
                (void       *) 0,
                //任务扩展(0表不扩展)
                (OS_OPT     )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),//任务选项
                (OS_ERR     *)&err);                     //返回错误类型

    OSStart(&err);
    //启动多任务管理(交由μC/OS-III控制)
}

static  void  AppTaskStart (void *p_arg)
{
    CPU_INT32U  cpu_clk_freq;
    CPU_INT32U  cnts;
    OS_ERR      err;


    (void)p_arg;

    BSP_Init();                                          //板级初始化
    CPU_Init();     //初始化 CPU组件(时间戳、关中断时间测量和主机名)


    cpu_clk_freq = BSP_CPU_ClkFreq();
    //获取 CPU内核时钟频率(SysTick 工作时钟)
    cnts = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz;
    //根据用户设定的时钟节拍频率计算 SysTick 定时器的计数值
    OS_CPU_SysTickInit(cnts);              //调用 SysTick
    初始化函数,设置定时器计数值和启动定时器

    Mem_Init();
    //初始化内存管理组件(堆内存池和内存池表)

#if OS_CFG_STAT_TASK_EN > 0u
    //如果启用(默认启用)了统计任务
    OSStatTaskCPUUsageInit(&err);

#endif


    CPU_IntDisMeasMaxCurReset();
    //复位(清零)当前最大关中断时间


    /* 创建 AppTaskPost 任务 */
    OSTaskCreate((OS_TCB     *)&AppTaskPostTCB,
    //任务控制块地址
                (CPU_CHAR   *)"App Task Post",          //任务名称
                (OS_TASK_PTR ) AppTaskPost,         //任务函数
                (void       *) 0,
                //传递给任务函数(形参p_arg)的实参
                (OS_PRIO     ) APP_TASK_POST_PRIO,    //任务的优先级
                (CPU_STK    *)&AppTaskPostStk[0],
                //任务栈的基地址
                (CPU_STK_SIZE) APP_TASK_POST_STK_SIZE / 10,
                //任务栈空间剩下1/10时限制其增长
                (CPU_STK_SIZE) APP_TASK_POST_STK_SIZE,
                //任务栈空间(单位:sizeof(CPU_STK))
                (OS_MSG_QTY  ) 5u,
                //任务可接收的最大消息数
                (OS_TICK     ) 0u,
                //任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)
                (void       *) 0,
                //任务扩展(0表不扩展)
                (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                (OS_ERR     *)&err);                       //返回错误类型

    /* 创建 AppTaskPend 任务 */
    OSTaskCreate((OS_TCB     *)&AppTaskPendTCB,
    //任务控制块地址
                (CPU_CHAR   *)"App Task Pend",        //任务名称
                (OS_TASK_PTR ) AppTaskPend,                 //任务函数
                (void       *) 0,
                //传递给任务函数(形参p_arg)的实参
                (OS_PRIO     ) APP_TASK_PEND_PRIO,   //任务的优先级
                (CPU_STK    *)&AppTaskPendStk[0],
                //任务栈的基地址
                (CPU_STK_SIZE) APP_TASK_PEND_STK_SIZE / 10,
                //任务栈空间剩下1/10时限制其增长
                (CPU_STK_SIZE) APP_TASK_PEND_STK_SIZE,
                //任务栈空间(单位:sizeof(CPU_STK))
                (OS_MSG_QTY  ) 50u,
                //任务可接收的最大消息数
                (OS_TICK     ) 0u,
                //任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)
                (void       *) 0,
                //任务扩展(0表不扩展)
                (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), //任务选项
                (OS_ERR     *)&err);              //返回错误类型

    OSTaskDel ( & AppTaskStartTCB, & err );
    //删除起始任务本身,该任务不再运行

}

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


    (void)p_arg;


while (DEF_TRUE)                                   //任务体
    {
        /* 发送消息到任务 AppTaskPend */
        OSTaskQPost ((OS_TCB      *)&AppTaskPendTCB, //目标任务的控制块
                    (void        *)"Fire μC/OS-III", //消息内容
                    (OS_MSG_SIZE  )sizeof( "Fire μC/OS-III" ), //消息长度
                    (OS_OPT       )OS_OPT_POST_FIFO,
                    //发送到任务消息队列的入口端
                    (OS_ERR      *)&err);          //返回错误类型

        OSTimeDlyHMSM ( 0, 0, 1, 0, OS_OPT_TIME_DLY, & err );

    }
}

static  void  AppTaskPend ( void * p_arg )
{
    OS_ERR         err;
    OS_MSG_SIZE    msg_size;
    CPU_TS         ts;
    CPU_INT32U     cpu_clk_freq;
    CPU_SR_ALLOC();

    char * pMsg;


    (void)p_arg;


    cpu_clk_freq = BSP_CPU_ClkFreq();
    //获取CPU时钟,时间戳是以该时钟计数


while (DEF_TRUE)                                 //任务体
    {
        /* 阻塞任务,等待任务消息 */
        pMsg = OSTaskQPend ((OS_TICK        )0,        //无期限等待
        (OS_OPT    )OS_OPT_PEND_BLOCKING, //没有消息就阻塞任务
        (OS_MSG_SIZE   *)&msg_size,  //返回消息长度
        (CPU_TS        *)&ts,
        //返回消息被发送的时间戳
        (OS_ERR        *)&err);  //返回错误类型

        ts = OS_TS_GET() - ts;
        //计算消息从发送到被接收的时间差

        macLED1_TOGGLE ();                     //切换LED1的亮灭状态

        OS_CRITICAL_ENTER();
        //进入临界段,避免串口打印被打断

        printf ( "\r\n接收到的消息的内容为:%s,长度是:%d字节。",
                pMsg, msg_size );

        printf ( "\r\n任务消息从被发送到被接收的时间差是%dus\r\n",
                ts / ( cpu_clk_freq / 1000000 ) );

        OS_CRITICAL_EXIT();                               //退出临界段
    }

}

4、任务消息队列实验现象

打开串口调试助手,然后复位开发板就可以在调试助手中看到串口的运行打印信息, 具体见图
在这里插入图片描述

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

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

相关文章

橱窗带货真的能赚到钱吗?橱窗带货素材哪里找?什么是橱窗带货?

什么是橱窗带货&#xff1f; 橱窗带货&#xff0c;一种以货架电商为基础的营销手段&#xff0c;可类比线下实体店的展示柜和窗口。橱窗的曝光量较高&#xff0c;因此通常展示店铺中的重点产品和推荐系列商品。短视频已经积极进军电商领域&#xff0c;虽然一开始主打直播电商&a…

newman下载安装

postman是专为接口测试而生&#xff0c;Newman是专为postman而生。newman可以让我们的postman的脚本通过非GUI(命令行)的方式。 1. 安装node.js 安装步骤 查看已安装版本 node -v 2. 安装 Newman 运行命令:npm install -g newman&#xff0c;即可完成安装操作。 或者 npm i…

(Oracle)SQL优化案例:隐式转换优化

项目场景 项目现场的某个kettle模型执行非常缓慢&#xff0c;原因在于某个SQL执行效率非常的低。甲方得知此事要求公司赶紧优化&#xff0c;负责该模块的同事对SQL优化并不熟悉。所以作为一个立志成为优秀DBA的ETL工程师&#xff0c;我自告奋勇&#xff1a;不是DBA&#xff0c;…

两步解决 Flutter Your project requires a newer version of the Kotlin Gradle plugin

在开发Flutter项目的时候,遇到这个问题Flutter Your project requires a newer version of the Kotlin Gradle plugin 解决方案分两步: 1、在android/build.gradle里配置最新版本的kotlin 根据提示的kotlin官方网站搜到了Kotlin的最新版本是1.9.23,如下图所示: 同时在Ko…

人事|基于SpringBoot+vue的人事管理系统设计与实现(源码+数据库+文档)

人事管理系统目录 基于SpringBootvue的人事管理系统设计与实现 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布道师&#xff0c;…

链表中常见的使用方法逻辑整理

文章目录 1. 链表特点2. 链表创建3. 链表遍历通用方法3.1 在链表的开头添加元素3.2 在链表的结尾添加元素3.3 删除链表的第一个元素3.4 删除链表的最后一个元素3.5 遍历链表3.6 查找链表中的元素3.7 反转链表 4. 常见面试题4.1 相交链表4.2 反转链表4.3 环形链表4.4 环形链表 I…

Nacos-默认token.secret.key-配置不当权限绕过漏洞复现

漏洞描述&#xff1a; Nacos 身份认证绕过漏洞(QVD-2023-6271)&#xff0c;开源服务管理平台 Nacos在默认配置下未对 token.secret.key 进行修改&#xff0c;导致远程攻击者可以绕过密钥认证进入后台&#xff0c;造成系统受控等后果。 漏洞信息 公开时间&#xff1a;2023-03…

Windows安装MongoDB结合内网穿透轻松实现公网访问本地数据库

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Java 基于微信小程序的校园失物招领小程序,附源码

博主介绍&#xff1a;✌程序员徐师兄、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

Java基础第十一课——类与对象(2)

由于类与对象这一部分的知识点很多&#xff0c;而且操作方法也有很多&#xff0c;所以这次将继续深入讨论一下关于类与对象中方法传参、方法重载、构造方法以及this关键字使用方面的知识。 一、方法传参 1.return关键字 return关键字作用 作用场景&#xff1a;方法内 作用…

一站式开源持续测试平台 MerterSphere 之测试跟踪操作详解

一、MeterSphere平台介绍 MeterSphere是一站式的开源持续测试平台&#xff0c;遵循 GPL v3 开源许可协议&#xff0c;涵盖测试跟踪、接口测试、UI 测试和性能测试等功能&#xff0c;全面兼容JMeter、Selenium 等主流开源标准&#xff0c;有效助力开发和测试团队充分利用云弹性…

NCF代码运行

keras 2.1.4 tensorflow 1.12.0 python 3.6.4 numpy 1.14.5 一、准备工作 1、安装虚拟环境 conda create -n tensorflow python3.6.4conda activate tensorflowconda install tensorflow1.12.0conda install keras2.1.4conda info2、安装相应依赖 cd Py3_Recommender-Syste…

4.2 面向对象程序设计-类的继承实验

本文仅供学习交流&#xff0c;严禁用于商业用途&#xff0c;如本文涉及侵权请及时联系将于24小时内删除 目录 1.实验内容 2.实验原理 2.1类的继承 2.2 继承的优点和缺点 2.3 继承的方式 3.实验代码 1.实验内容 创建一个父类CalcTime&#xff0c;在父类中依次定义用于保存…

对装饰器模式的理解

目录 一、场景二、面对场景中的新需求&#xff0c;我们怎么办&#xff1f;1、暴力法&#xff1a;直接修改原有的代码。2、子类继承法&#xff1a;既然要增强行为&#xff0c;那我搞一个子类&#xff0c;覆写不就完事了&#xff1f;3、装饰器模式 三、对装饰器模式的思考1、从代…

C语言-----结构体详解

前面已经向大家介绍过一点结构体的知识了&#xff0c;这次我们再来深度了解一下结构体。结构体是能够方便表示一个物体具有多种属性的一种结构。物体的属性可以转换为结构体中的变量。 1.结构体类型的声明 1.1 结构体的声明 struct tag {member-list;//结构体成员变量 }vari…

Shopee(虾皮)有哪些站点?

虾皮&#xff08;Shopee&#xff09;是东南亚地区的跨境平台&#xff0c;目前拥有多个站点&#xff0c;以下是对虾皮各站点的简要介绍&#xff1a; 中国台湾站&#xff1a;作为大多数新卖家的默认首站&#xff0c;台湾站没有语言障碍&#xff0c;运营起来更为方便&#xff0c;…

Java 中文官方教程 2022 版(一)

原文&#xff1a;docs.oracle.com/javase/tutorial/reallybigindex.html 教程&#xff1a;入门指南 原文&#xff1a;docs.oracle.com/javase/tutorial/getStarted/index.html 这个教程提供了关于开始使用 Java 编程语言的所有必要信息。 提供了 Java 技术作为一个整体的概述。…

android gradle版本无法下载

android gradle版本无法下载问题解决方法 在引入一个新的android项目的时候&#xff0c;通常会因为无法下载gradle版本而一直卡在同步界面&#xff0c;类似于下面的情况。 这是因为gradle运行时首先会检查distributionUrlhttps://services.gradle.org/distributions/gradle-5.6…

Springboot+Vue项目-基于Java+MySQL的社区团购系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

【linux篇】ubuntu安装教程

有道是工欲善其事必先利其器&#xff0c;在学习linux前&#xff0c;先得搭建好环境才能事半功倍。 1.VMware虚拟机安装 打开浏览器&#xff0c;可直接在搜索栏中输入VMware。