(学习日记)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.rhkb.cn/news/307770.html

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

相关文章

文件上传【2】--靶场通关

1.前端禁用js绕过 上传文件&#xff0c;进行抓包&#xff0c;没有抓到&#xff0c;说明这里的验证是前端js验证跳出的弹窗 禁用js后&#xff0c;php文件上传成功。 2.文件上传.htaccess 上传png木马后连接不上 代码中存在.htaccess&#xff0c;判断此时应该就是需要用到.htac…

1111111111

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…

21 标准错误

标准输出重定向关闭无数据 下面的代码&#xff1a; #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>int main() {close(1);i…

超级详细的JDBC和数据库连接池讲解

文章目录 JDBC简介概念本质好处 JDBC快速入门JDBC中API详解DriverManager驱动管理类作用注册驱动获取连接 Connection数据库连接对象作用获取执行SQL的对象事务管理 Statement作用执行SQL语句 ResultSet原理使用步骤 PreparedStatementSQL注入获取对象操作步骤 原理好处 JDBC工…

力扣刷题 二叉树层序遍历相关题目II

NO.116 填充每个节点的下一个右侧节点指针 给定一个 完美二叉树 &#xff0c;其所有叶子节点都在同一层&#xff0c;每个父节点都有两个子节点。二叉树定义如下&#xff1a; struct Node {int val;Node *left;Node *right;Node *next; } 填充它的每个 next 指针&#xff0c;…

redis的主从复制(docker方式快速入门和实战)

目录 一、主从复制简介 二、配置主从服务器 2.1使用配置文件的形式来主从复制 2.2使用纯代码的方式来进行主从复制&#xff1b; 2.3脱离主服务器 三、一些注意事项 一、主从复制简介 主从复制&#xff0c;是指将一台Redis服务器的数据&#xff0c;复制到其他的Redis服务器…

【论文阅读】MCTformer: 弱监督语义分割的多类令牌转换器

【论文阅读】MCTformer: 弱监督语义分割的多类令牌转换器 文章目录 【论文阅读】MCTformer: 弱监督语义分割的多类令牌转换器一、介绍二、联系工作三、方法四、实验结果 Multi-class Token Transformer for Weakly Supervised Semantic Segmentation 本文提出了一种新的基于变换…

尝试在手机上运行google 最新开源的gpt模型 gemma

Gemma介绍 Gemma简介 Gemma是谷歌于2024年2月21日发布的一系列轻量级、最先进的开放语言模型&#xff0c;使用了与创建Gemini模型相同的研究和技术。由Google DeepMind和Google其他团队共同开发。 Gemma提供两种尺寸的模型权重&#xff1a;2B和7B。每种尺寸都带有经过预训练&a…

【动手学深度学习】15_汉诺塔问题

注&#xff1a; 本系列仅为个人学习笔记&#xff0c;学习内容为《算法小讲堂》&#xff08;视频传送门&#xff09;&#xff0c;通俗易懂适合编程入门小白&#xff0c;需要具备python语言基础&#xff0c;本人小白&#xff0c;如内容有误感谢您的批评指正 汉诺塔&#xff08;To…

人员抽烟AI检测算法原理介绍及实际场景应用

抽烟检测AI算法是一种基于计算机视觉和深度学习技术的先进工具&#xff0c;旨在准确识别并监测个体是否抽烟。该算法通过训练大量图像数据&#xff0c;使模型能够识别出抽烟行为的关键特征&#xff0c;如烟雾、手部动作和口部形态等。 在原理上&#xff0c;抽烟检测AI算法主要…

[lesson22]对象的销毁

对象的销毁 对象的销毁 生活中的对象都是被初始化后才上市的 生活中的对象被销毁前会做一些清理工作 一般而言&#xff0c;需要销毁的对象都应该做清理 解决方案 为每个类都提供一个public的free函数对象不在需要时立即调用free函数进行清理 存在的问题 free只是一个普通…

稀碎从零算法笔记Day44-LeetCode:整数转罗马数字

题型&#xff1a;贪心、模拟 链接&#xff1a; 12. 整数转罗马数字 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 罗马数字包含以下七种字符&#xff1a; I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 …

淘宝批量采集商品详情数据(属性丨详情图丨sku丨价格等)

淘宝批量采集商品详情数据&#xff08;包括属性、详情图、SKU、价格等&#xff09;可以通过以下几种方式实现&#xff1a; 使用淘宝数据抓取工具&#xff1a;这类工具&#xff0c;如某鱼等&#xff0c;能够自动化采集淘宝商品数据&#xff0c;并将其转换成CSV、Excel等格式&am…

【PyQt5】环境配置

PyQt5 环境配置 一、前言1.1 PyQt5介绍1.2 PyCharm集成Pyqt5 二、pyqt5安装三、PyQt5-tools工具包安装四、常用工具环境配置4.1、环境变量配置4。2、验证是否安装成功 五、pycharm中设置Qt工具&#xff08;Qt Designer、PyUIC、PyRcc&#xff09;5.1、配置Qt Designer5.2、配置…

C++11 设计模式4. 抽象工厂(Abstract Factory)模式

问题的提出 从前面我们已经使用了工厂方法模式 解决了一些问题。 现在 策划又提出了新的需求&#xff1a;对于各个怪物&#xff0c;在不同的场景下&#xff0c;怪物的面板数值会发生变化&#xff0c; //怪物分类&#xff1a;亡灵类&#xff0c;元素类&#xff0c;机械类 …

【数据交换格式】网络socket编程温度采集智能存储与上报项目技术------JSON、TLV

作者简介&#xff1a; 一个平凡而乐于分享的小比特&#xff0c;中南民族大学通信工程专业研究生在读&#xff0c;研究方向无线联邦学习 擅长领域&#xff1a;驱动开发&#xff0c;嵌入式软件开发&#xff0c;BSP开发 作者主页&#xff1a;一个平凡而乐于分享的小比特的个人主页…

蓝桥杯物联网竞赛_STM32L071KBU6_全部工程及国赛省赛真题及代码

包含stm32L071kbu6全部实验工程、源码、原理图、官方提供参考代码及国、省赛真题及代码 链接&#xff1a;https://pan.baidu.com/s/1pXnsMHE0t4RLCeluFhFpAg?pwdq497 提取码&#xff1a;q497

【零基础学数据结构】双向链表

1.双向链表的概念 1.1头节点 1.2带头双向循环链表 注意&#xff1a; 哨兵位创建后&#xff0c;首尾连接自己 1.3双链表的初始化 // 双向链表的初始化 void ListInit(ListNode** pphead) {// 给双链表创建一个哨兵位*pphead ListBuyNode(-1); } 2.双向链表的打印 // 双向…

扭蛋机小程序:线上扭蛋机模式发展空间有多大?

潮玩行业近几年的发展非常快&#xff0c;推动了扭蛋机市场的发展&#xff0c;越来越多的人加入到了扭蛋机赛道中&#xff0c;市场迎来了新的发展期。如今&#xff0c;我国的二次元文化的发展不断成熟&#xff0c;扭蛋机主打的二次元商品迎来了更多的商业机会。 一、互联网扭蛋机…

uniapp 上传视频到阿里云之后回显视频获取视频封面

uniapp 上传视频到阿里云之后回显视频获取视频封面 官网的解决方案 1.initial-time Number 指定视频初始播放位置&#xff0c;单位为秒&#xff08;s&#xff09;。 没什么卵用 2.使用 uni.createVideoContext(“myVideo”, this).seek(number)。 没什么卵用 <video :id&quo…