(学习日记)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;                      //返回,停止执行}
#endifts = 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;                                         //返回}
#endifOS_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(有错误),停止执行}
#endifif (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 > 0uif (p_ts != (CPU_TS *)0){OSTCBCurPtr->MsgQPendTime = OS_TS_GET() - *p_ts;if (OSTCBCurPtr->MsgQPendTimeMax < OSTCBCurPtr->MsgQPendTime){OSTCBCurPtr->MsgQPendTimeMax = OSTCBCurPtr->MsgQPendTime;}}
#endifCPU_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 > 0uOSTCBCurPtr->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;                //返回消息大小为0if (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;                //返回消息大小为0if (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);#endifCPU_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.mzph.cn/news/815317.shtml

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

相关文章

# 达梦sql查询 Sql 优化

达梦sql查询 Sql 优化 文章目录 达梦sql查询 Sql 优化注意点测试数据单表查询 Sort 语句优化优化过程 多表关联SORT 优化函数索引的使用 注意点 关于优化过程中工具的选用&#xff0c;推荐使用自带的DM Manage&#xff0c;其它工具在查看执行计划等时候不明确在执行计划中命中…

MySQL 主从复制部署(8.0)

什么是主从数据库 主从数据库是一种数据库架构模式&#xff0c;通常用于提高数据库的性能、可用性和可伸缩性。 它包括两种类型的数据库服务器&#xff1a; 1&#xff09;主数据库&#xff08;Master&#xff09;&#xff1a;主数据库是读写数据的主要数据库服务器。所有写操…

前端小技巧之轮播图

文章目录 功能htmlcssjavaScript图片 设置了一点小难度&#xff0c;不理解的话&#xff0c;是不能套用的哦&#xff01;&#xff01;&#xff01; &#xff08;下方的圆圈与图片数量不统一&#xff0c;而且宽度是固定的&#xff09; 下次写一些直接套用的&#xff0c;不整这些麻…

SpringBoot配置优先级

配置优先级排序&#xff08;从高到低&#xff09; 1&#xff09;命令行参数 2&#xff09;java系统属性 3&#xff09;application.properties 4&#xff09;application.yaml 5&#xff09;application.ymlSpringBoot的系统属性配置和命令行参数配置 1、cmd端进行配置 1&am…

边缘计算网关究竟是什么呢?它又有什么作用呢?-天拓四方

在数字化时代&#xff0c;信息的传输与处理变得愈发重要&#xff0c;而其中的关键节点之一便是边缘计算网关。这一先进的网络设备&#xff0c;不仅扩展了云端功能至本地边缘设备&#xff0c;还使得边缘设备能够自主、快速地响应本地事件&#xff0c;提供了低延时、低成本、隐私…

基本的数据类型在16位、32位和64位机上所占的字节大小

1、目前常用的机器都是32位和64位的&#xff0c;但是有时候会考虑16位机。总结一下在三种位数下常用的数据类型所占的字节大小。 数据类型16位(byte)32位(byte)64位(byte)取值范围char111-128 ~ 127unsigned char1110 ~ 255short int / short222-32768~32767unsigned short222…

Go程序设计语言 学习笔记 第十一章 测试

1949年&#xff0c;EDSAC&#xff08;第一台存储程序计算机&#xff09;的开发者莫里斯威尔克斯在他的实验室楼梯上攀登时突然领悟到一件令人震惊的事情。在《一位计算机先驱的回忆录》中&#xff0c;他回忆道&#xff1a;“我突然完全意识到&#xff0c;我余生中的很大一部分时…

SpringCloudalibaba之Nacos的配置管理

Nacos的配置管理 放个妹子能增加访问量&#xff1f; 动态配置服务 动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。 动态配置消除了配置变更时重新部署应用和服务的需要&#xff0c;让配置管理变得更加高效和敏捷。 配置中心化管…

计算机网络----第十二天

交换机端口安全技术和链路聚合技术 1、端口隔离技术&#xff1a; 用于在同vlan内部隔离用户&#xff1b; 同一隔离组端口不能通讯&#xff0c;不同隔离组端口可以通讯; 2、链路聚合技术&#xff1a; 含义&#xff1a;把连接到同一台交换机的多个物理端口捆绑为一个逻辑端口…

武林风云之一个shell同时维护多个设备

仅以此文纪念linux中国 小y最近真的颓废了&#xff0c;马上就三十了&#xff0c;一下班整个人跟个废物一样&#xff0c;躺在住处刷B站&#xff0c;太颓废了。哎&#xff0c;我想这今年就收手博客了&#xff0c;后续不再更新。但是人不能这样&#xff0c;人需要和懒惰做斗争&…

Python学习笔记25 - 一些案例

1. 输出金陵前五钗 2. 向文件输出信息 3. 打印彩色字 4. print函数、进制转换 5. 猜数游戏 6. 输出ASCII码对应的字符 7. 计算100~999之间的水仙花数 8. 千年虫数组的索引及其值 9. 星座zip dict 10. 12306车次信息 11. 字符串的格式化 12. 手动抛出异常 13. 计算圆的面积和周长…

杰发科技AC7840——CAN通信简介(4)_过滤器设置

0. 简介 注意&#xff1a;过滤器最高三位用不到&#xff0c;因此最高位随意设置不影响过滤器。 1. 代码分析 注意设置过滤器数量 解释的有点看不懂 详细解释...也看不大懂 Mask的第0位是0&#xff0c;其他位都是1(就是F?)&#xff0c;那就指定了接收值就是这个数&#xff0c;…

版本控制工具Git的使用

1、Git的基本概念和使用 1、Git是什么? ● Git: 是一个开源的分布式版本控制系统&#xff0c;可以有效、高速的处理从很小到非常大的项目版本管理。 ● GitHub: 全球最大的面向开源及私有软件项目的托管平台,免费注册并且可以免费托管开源代码。 ● GitLab:与GitHub类似&a…

ChatGLM3初体验

mac本地化部署ChatGLM3 写在前面环境准备1. python环境2. 安装第三方依赖torch3.下载模型 代码准备1.clone代码 run效果 写在前面 建议直接去看官方文档 https://github.com/THUDM/ChatGLM3?tabreadme-ov-file 环境准备 1. python环境 python -V ## 3.11.42. 安装第三方依…

标注平台工作流:如何提高训练数据质量与管理效率

世界发展日益依托数据的驱动&#xff0c;企业发现&#xff0c;管理不断增长的数据集却愈发困难。数据标注是诸多行业的一个关键过程&#xff0c;其中包括机器学习、计算机视觉和自然语言处理。对于大型语言模型&#xff08;LLM&#xff09;来说尤是如此&#xff0c;大型语言模型…

[大模型]Yi-6B-chat WebDemo 部署

Yi-6B-chat WebDemo 部署 Yi 介绍 由60亿个参数组成的高级语言模型 Yi LLM。为了促进研究&#xff0c;Yi 已经为研究社区开放了Yi LLM 6B/34B Base 和 Yi LLM 6B/34B Chat。 环境准备 在autodl平台中租一个3090等24G显存的显卡机器&#xff0c;如下图所示镜像选择PyTorch–…

leecode438 | 找到所有字符串中的异位词

题意大致是&#xff0c;给定两个字符串&#xff0c;s 和 p 其中 要在s 中找到由p的元素组成的子字符串&#xff0c;记录子字符串首地址 class Solution { public:vector<int> findAnagrams(string s, string p) {int m s.size(), n p.size();if(m < n)return {};vec…

vue-router 原理【详解】hash模式 vs H5 history 模式

hash 模式 【推荐】 路由效果 在不刷新页面的前提下&#xff0c;根据 URL 中的 hash 值&#xff0c;渲染对应的页面 http://test.com/#/login 登录页http://test.com/#/index 首页 核心API – window.onhashchange 监听 hash 的变化&#xff0c;触发视图更新 window.onhas…

谷歌关键词优化全攻略提高曝光率-华媒舍

现如今&#xff0c;互联网已成为信息获取的主要渠道&#xff0c;而搜索引擎则是人们寻找信息的首选工具之一。其中&#xff0c;谷歌作为全球最大的搜索引擎&#xff0c;其搜索结果的排名直接影响着网站的曝光率和流量。了解并掌握谷歌关键词优化的技巧&#xff0c;成为提升网站…

MySQL-多表查询:多表查询分类、SQL99语法实现多表查询、UNION的使用、7种SQL JOINS的实现、SQL99语法新特性、多表查询SQL练习

多表查询 1. 一个案例引发的多表连接1.1 案例说明1.2 笛卡尔积&#xff08;或交叉连接&#xff09;的理解1.3 案例分析与问题解决 2. 多表查询分类讲解分类1&#xff1a;等值连接 vs 非等值连接等值连接非等值连接 分类2&#xff1a;自连接 vs 非自连接分类3&#xff1a;内连接…