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

news2025/1/9 16:52:51

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


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


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

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、任务消息队列实验现象

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

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

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

相关文章

Pandas部分应掌握的重要知识点

目录 Pandas部分应掌握的重要知识点一、DataFrame数据框的创建1、直接基于二维数据创建&#xff08;同时使用index和columns参数&#xff09;2、基于excel文件中的数据来创建 二、查看数据框中的数据和联机帮助信息1、查看特殊行的数据2、查看联机帮助的两种常见方法&#xff0…

Harbor镜像仓库报错“Harbor被设置为只读模式,在此模式下,不能删除仓库、标签及推送镜像。”

由于Harbor镜像仓库空间不足&#xff0c;今天扩容的磁盘空间&#xff0c;扩容前做了垃圾清理操作&#xff0c;然后直接停止了服务。估计清理任务没完成&#xff0c;导致服务启动后Harbor被设置为只读模式&#xff0c;无法使用。下面是解决方法。 1、报错信息&#xff1a; “Ha…

【Linux】基础IO----理解缓冲区

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;理解缓冲区 > 毒鸡汤&#xff1a;有些事情&#xff0c;总是不明白&#xff0c;所以我不会坚持。早安! > 专栏选自&#xff1a;Linux初阶 > 望…

开源模型应用落地-chatglm3-6b-zero/one/few-shot-入门篇(五)

一、前言 Zero-Shot、One-Shot和Few-Shot是机器学习领域中重要的概念&#xff0c;特别是在自然语言处理和计算机视觉领域。通过Zero-Shot、One-Shot和Few-Shot学习&#xff0c;模型可以更好地处理未知的情况和新任务&#xff0c;减少对大量标注数据的依赖&#xff0c;提高模型的…

Gradle 实战 - 检查不用包 -ApiHug准备-工具篇-010

&#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 | Marketplace ApiHug …

《架构风清扬-Java面试系列第21讲》什么是线程的优先级?在Java中如何设置线程的优先级?

各位小伙伴早上好&#xff01; 谢谢你的关注&#xff01;也欢迎来加入我主导的知识星球&#xff0c;更多干货&#xff0c;提高你的面试准备效率&#xff01; 敢承诺三天内不满意&#xff0c;可以直接退出&#xff01; 这道题&#xff0c;属于面试热场的题目&#xff0c;我是不…

CentOS如何做端口映射?

在今天的技术发展中&#xff0c;越来越多的应用需要跨越网络进行远程管理和控制。为了实现这一目标&#xff0c;端口映射技术被广泛应用于各个领域。其中&#xff0c;【天联】作为一种性能稳定、安全可靠的端口映射工具&#xff0c;在各种应用场景中得到了广泛的应用和认可。 结…

SAM功能改进VRP-SAM论文解读VRP-SAM: SAM with Visual Reference Prompt

现已总结SAM多方面相关的论文解读&#xff0c;具体请参考该专栏的置顶目录篇 一、总结 1. 简介 发表时间&#xff1a;2024年3月30日 论文&#xff1a; 2402.17726.pdf (arxiv.org)https://arxiv.org/pdf/2402.17726.pdf代码&#xff1a; syp2ysy/VRP-SAM (github.com)htt…

模板进阶 | 非类型模板参数 | 类模板的特化 | 模板的分离编译 | 模板的优缺点

非类型模板参数 我们可以认为非类型模板参数就是一个常量&#xff0c;在我们的类里面我们是不能对它进行改造 为什么会有这样的场景&#xff0c;其次就是C语言那里我们一般使用什么。 场景1 #include<iostream> using namespace std;#define N 10 template<class T…

基于springboot仿雀语的文档管理系统

项目介绍 本项目借鉴了雀语的一些UI设计&#xff0c;实现了文档在线管理的功能&#xff0c;知识库可以对不同分类的文档建立不同的库&#xff0c;知识库里面左边可以维护菜单菜单目录&#xff0c;右边实现在线预览。该项目可以防止用户下载和复制文档&#xff0c;只支持在线预…

RK3568平台 SPI设备驱动

一.SPI简介 SPI是许多不同设备使用的常见通信协议。例如&#xff0c;SD卡模块、RFID读卡器模块和2.4GHz无线发射机/接收器均使用SPI与微控制器进行通信。 SPI是串行外设接口&#xff08;Serial Peripheral Interface)的缩写&#xff0c;是一种高速的&#xff0c;全双工&#x…

短视频转gif怎么做?三十秒在线转换gif

在现在这个快节奏的时代&#xff0c;gif动画相较于长时间的视频更受大众的欢迎。当我们需要将短视频、电影等视频制作成gif动画图片的时候就可以使用gif动画图片&#xff08;https://www.gif.cn/&#xff09;制作网站-GIF中文网&#xff0c;无需下载软件&#xff0c;手机、pc均…

零售行业数字化广告评价标准 - 《IAB/MRC零售(广告)测量指南》

IAB/MRC零售&#xff08;广告&#xff09;测量指南 --- 最新标准&#xff0c;2024年1月发布 目录 1出台此标准的目的是什么&#xff1f;2标准宗旨3本标准的主要关键领域4为什么这对品牌和零售商很重要5能给零售媒体中小型玩家带来什么机会&#xff1f;6评价零售媒体效果的最…

【JS】获取接口返回 EventStream 结构的数据(即接收读取 stream 流)

文章目录 EventStream 是一种服务器推送的数据格式&#xff0c;可以用于实时数据传输。 接口返回的示例图 获取示例&#xff1a; // 这里的 url 为虚拟的&#xff0c;仅供演示用 fetch(https://test.cn.com/api/agent/2, {method: POST,headers: {Content-Type: applicatio…

EaticSearch学习

ES学习目标 1、全文检索 2、ES介绍 2.1 安装&#xff08;docker&#xff09; docker pull elasticsearch:7.14.0 docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.typesingle-node" elasticsearch:7.14.0初步检索 1、_cat GET /_cat/nodes&#xff1a;查看所…

RocketMQ安装部署+简单实战开发

文章目录 1.简介、安装部署2.Springboot集成RocketMQ2.1.添加maven依赖&#xff1a;2.2.RocketMQ配置生产者配置消费者配置 2.3.生产者&#xff08;发送消息&#xff09;2.4.消费者&#xff08;接收消息&#xff09; 3.实战结果3.1.消费者服务3.2.生产者服务3.3.运行日志生产日…

【SpringBoot】-- 使用minio对象存储服务实现上传图片

目录 一、安装minio 拉取镜像 启动 查看 进入登录页面 创建bucket 二、安装miniomc 三、代码 application.yml MinioUtil Controller 四、拓展 以下基于云服务和docker使用minio服务 一、安装minio Minio 是一个开源的对象存储服务器。它允许用户在私有云环境中建…

【vue】watch 侦听器

watch&#xff1a;可监听值的变化&#xff0c;旧值和新值 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><titl…

蓝桥杯嵌入式(G431)备赛——最后一晚查漏补缺

蓝桥杯嵌入式&#xff08;G431&#xff09;备赛笔记——初始化cubeMX 蓝桥杯嵌入式&#xff08;G431&#xff09;备赛笔记——LED 蓝桥杯嵌入式&#xff08;G431&#xff09;备赛笔记——按键模块设计 蓝桥杯嵌入式&#xff08;G431&#xff09;备赛笔记——LCD按键 蓝桥杯…

2023年度编程语言将花落谁家

2023年度编程语言将花落谁家 TIOBE的预测你预测年度最受欢迎的编程语言会是什么&#xff1f;TIOBE 认为 C# 最有可能成为年度编程语言&#xff0c;你同意吗&#xff1f;为什么&#xff1f;AI时代已经到来&#xff0c;你有学习新语言的打算吗&#xff1f; 以下是来自年度编程语言…