本帖最后由 DKENNY 于 2024-7-6 16:24 编辑
#申请原创# @21小跑堂
前言 上篇文章介绍了 uc/os 中消息队列机制的原理以及工作模式,接下来我们分析一下其相关函数的实现细节。
5、常用函数 5.1 创建消息队列函数-OSQCreate 要在μCOS中使用消息队列,首先需要声明和创建一个消息队列。通过调用OSQCreate()函数可以创建一个新的队列。队列是一种数据结构,用于在任务之间传递数据。每次创建一个新队列都需要分配一定的RAM空间,在创建队列时,需要自行定义一个消息队列的结构体,而结构体的内存分配由编译器自动完成。 如下是 OSQCreate 源码及其相关注释说明。 void OSQCreate (OS_Q *p_q, // 函数原型:创建一个消息队列,接受指向消息队列结构体的指针 p_q
CPU_CHAR *p_name, // 消息队列的名称,用于调试目的
OS_MSG_QTY max_qty, // 消息队列中消息的最大数量
OS_ERR *p_err) // 指向错误变量的指针,用于存放函数执行过程中的错误码
{
CPU_SR_ALLOC(); // 分配一个中断保护区变量
#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif
#ifdef OS_SAFETY_CRITICAL_IEC61508
if (OSSafetyCriticalStartFlag == OS_TRUE) {
*p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME;
return;
}
#endif
#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
if (OSIntNestingCtr > 0u) { // 检查是否正在处理中断中,不允许在中断中调用此函数
*p_err = OS_ERR_CREATE_ISR;
return;
}
#endif
#if (OS_CFG_ARG_CHK_EN > 0u)
if (p_q == (OS_Q *)0) { // 检查消息队列指针是否为空
*p_err = OS_ERR_OBJ_PTR_NULL;
return;
}
if (max_qty == 0u) { // 检查消息队列的最大数量是否为零
*p_err = OS_ERR_Q_SIZE;
return;
}
#endif
CPU_CRITICAL_ENTER(); // 进入临界区
#if (OS_OBJ_TYPE_REQ > 0u)
#if (OS_CFG_OBJ_CREATED_CHK_EN > 0u)
if (p_q->Type == OS_OBJ_TYPE_Q) { // 检查消息队列是否已经被创建
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_OBJ_CREATED;
return;
}
#endif
p_q->Type = OS_OBJ_TYPE_Q; // 标记数据结构为消息队列
#endif
#if (OS_CFG_DBG_EN > 0u)
p_q->NamePtr = p_name; // 设置消息队列的名称指针
#else
(void)p_name;
#endif
OS_MsgQInit(&p_q->MsgQ, // 初始化消息队列
max_qty);
OS_PendListInit(&p_q->PendList); // 初始化等待队列
#if (OS_CFG_DBG_EN > 0u)
OS_QDbgListAdd(p_q);
OSQQty++; // 消息队列数量加一
#endif
OS_TRACE_Q_CREATE(p_q, p_name); // 跟踪消息队列的创建过程
CPU_CRITICAL_EXIT(); // 离开临界区
*p_err = OS_ERR_NONE; // 没有错误发生
}
消息队列创建完成的示意图如下:
在μCOS操作系统中,虽然用户需要自定义一个消息队列的句柄,但这并不意味着消息队列已经被创建。实际上,要创建一个消息队列,必须通过调用专门的创建函数来完成。如果没有正确地调用这个函数,仅仅有句柄是不够的,后续使用句柄来调用其他消息队列相关的函数时就可能出错。一旦消息队列被成功创建,用户就可以利用这个句柄来发送和接收消息。如果在创建过程中有任何问题,错误代码会返回,用户可以通过这些代码来确认消息队列是否成功创建。
打个比方,这就像给面包烤箱取名字。你可以给它取个名字,但这并不代表烤箱已经放在厨房里并且工作了。你还需要真的去购买并安装这个烤箱,才能用它来烤面包。如果安装过程中出了问题,说明书会有错误提示,告诉你哪里出了问题。通过仔细检查,你可以确保烤箱安装成功,并开始使用它来烹饪。
使用实例: OS_Q queue; //声明消息队列
OS_ERR err; // 声明uC/OS-III错误变量err
OSQCreate((OS_Q*)&queue, // 创建消息队列
(CPU_CHAR*)"Queue For Test", //队列名
(OS_MSG_QTY)20, //最多可存放消息的数目
(OS_ERR*)&err); //返回错误类型
5.2 消息队列删除函数-OSQDel 队列删除函数是根据消息队列的结构(也就是消息队列的句柄)直接进行删除操作的。一旦消息队列被删除,系统会清空该消息队列的所有信息,并且不能再次使用这个消息队列。需要注意的是,如果某个消息队列并没有被定义,那么它也无法被删除。要使用消息队列删除函数,必须将宏定义 OS_CFG_Q_DEL_EN 设置为 1。 如下是 OSQDel 源码及其相关注释说明。 #if (OS_CFG_Q_DEL_EN > 0u) // 如果允许删除消息队列的功能被启用
OS_OBJ_QTY OSQDel (OS_Q *p_q, // 函数定义,用于删除消息队列
OS_OPT opt, // 删除选项,决定删除行为
OS_ERR *p_err) // 错误指针,用于返回错误代码
{
OS_OBJ_QTY nbr_tasks; // 用于记录被删除操作影响的任务数量
OS_PEND_LIST *p_pend_list; // 指向挂起列表的指针
OS_TCB *p_tcb; // 任务控制块指针
CPU_TS ts; // 时间戳
CPU_SR_ALLOC(); // 分配保存CPU状态寄存器的空间
#ifdef OS_SAFETY_CRITICAL // 如果定义了安全关键代码
if (p_err == (OS_ERR *)0) { // 如果错误指针为空
OS_SAFETY_CRITICAL_EXCEPTION(); // 抛出安全关键异常
return (0u); // 返回0,表示没有任务被影响
}
#endif
OS_TRACE_Q_DEL_ENTER(p_q, opt); // 跟踪消息队列删除操作的开始
#ifdef OS_SAFETY_CRITICAL_IEC61508 // 如果定义了IEC61508安全关键标准
if (OSSafetyCriticalStartFlag == OS_TRUE) { // 如果安全关键启动标志为真
OS_TRACE_Q_DEL_EXIT(OS_ERR_ILLEGAL_DEL_RUN_TIME); // 跟踪非法的运行时删除操作
*p_err = OS_ERR_ILLEGAL_DEL_RUN_TIME; // 设置错误代码为非法的运行时删除
return (0u); // 返回0,表示没有任务被影响
}
#endif
#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u) // 如果启用了从中断服务例程调用的检查
if (OSIntNestingCtr > 0u) { // 如果中断嵌套计数器大于0
OS_TRACE_Q_DEL_EXIT(OS_ERR_DEL_ISR); // 跟踪从ISR中删除的错误
*p_err = OS_ERR_DEL_ISR; // 设置错误代码为从ISR中删除
return (0u); // 返回0,表示没有任务被影响
}
#endif
#if (OS_CFG_INVALID_OS_CALLS_CHK_EN > 0u) // 如果启用了无效操作系统调用检查
if (OSRunning != OS_STATE_OS_RUNNING) { // 如果操作系统没有运行
OS_TRACE_Q_DEL_EXIT(OS_ERR_OS_NOT_RUNNING); // 跟踪操作系统未运行的错误
*p_err = OS_ERR_OS_NOT_RUNNING; // 设置错误代码为操作系统未运行
return (0u); // 返回0,表示没有任务被影响
}
#endif
#if (OS_CFG_ARG_CHK_EN > 0u) // 如果启用了参数检查
if (p_q == (OS_Q *)0) { // 如果消息队列指针为空
OS_TRACE_Q_DEL_EXIT(OS_ERR_OBJ_PTR_NULL); // 跟踪对象指针为空的错误
*p_err = OS_ERR_OBJ_PTR_NULL; // 设置错误代码为对象指针为空
return (0u); // 返回0,表示没有任务被影响
}
#endif
#if (OS_CFG_OBJ_TYPE_CHK_EN > 0u) // 如果启用了对象类型检查
if (p_q->Type != OS_OBJ_TYPE_Q) { // 如果消息队列类型不正确
OS_TRACE_Q_DEL_EXIT(OS_ERR_OBJ_TYPE); // 跟踪对象类型错误
*p_err = OS_ERR_OBJ_TYPE; // 设置错误代码为对象类型错误
return (0u); // 返回0,表示没有任务被影响
}
#endif
CPU_CRITICAL_ENTER(); // 进入临界区
p_pend_list = &p_q->PendList; // 获取挂起列表的指针
nbr_tasks = 0u; // 初始化被影响的任务数量为0
switch (opt) { // 根据删除选项进行不同的操作
case OS_OPT_DEL_NO_PEND: // 如果选项是仅在没有任务等待时删除
if (p_pend_list->HeadPtr == (OS_TCB *)0) { // 如果没有任务在等待
#if (OS_CFG_DBG_EN > 0u) // 如果启用了调试
OS_QDbgListRemove(p_q); // 从调试列表中移除消息队列
OSQQty--; // 减少消息队列的数量
#endif
OS_TRACE_Q_DEL(p_q); // 跟踪消息队列的删除
OS_QClr(p_q); // 清除消息队列
CPU_CRITICAL_EXIT(); // 退出临界区
*p_err = OS_ERR_NONE; // 设置错误代码为无错误
} else {
CPU_CRITICAL_EXIT(); // 退出临界区
*p_err = OS_ERR_TASK_WAITING; // 设置错误代码为有任务在等待
}
break;
case OS_OPT_DEL_ALWAYS: // 如果选项是总是删除消息队列
#if (OS_CFG_TS_EN > 0u) // 如果启用了时间戳
ts = OS_TS_GET(); // 获取当前的时间戳
#else
ts = 0u; // 如果没有启用时间戳,设置为0
#endif
while (p_pend_list->HeadPtr != (OS_TCB *)0) { // 循环,直到挂起列表为空
p_tcb = p_pend_list->HeadPtr; // 获取挂起列表的头部任务
OS_PendAbort(p_tcb, // 中止挂起的任务
ts, // 使用获取的时间戳
OS_STATUS_PEND_DEL); // 设置状态为因删除而中止 -----[1]-------
nbr_tasks++; // 增加被影响的任务数量
}
#if (OS_CFG_DBG_EN > 0u) // 如果启用了调试
OS_QDbgListRemove(p_q); // 从调试列表中移除消息队列
OSQQty--; // 减少消息队列的数量
#endif
OS_TRACE_Q_DEL(p_q); // 跟踪消息队列的删除
OS_QClr(p_q); // 清除消息队列
CPU_CRITICAL_EXIT(); // 退出临界区
OSSched(); // 调度,寻找优先级最高的任务执行
*p_err = OS_ERR_NONE; // 设置错误代码为无错误
break;
default: // 如果删除选项无效
CPU_CRITICAL_EXIT(); // 退出临界区
*p_err = OS_ERR_OPT_INVALID; // 设置错误代码为无效选项
break;
}
OS_TRACE_Q_DEL_EXIT(*p_err); // 跟踪消息队列删除操作的结束
return (nbr_tasks); // 返回被影响的任务数量
}
#endif
[1]:当系统决定删除一个内核对象,比如信号量,它会首先调用 OS_ PendAbort () 函数。这个函数的作用是将所有因等待该内核对象而处于阻塞状态的任务唤醒。随着每个任务的唤醒,它们不再等待事件的发生,因此消息队列中等待的任务数量会相应减少。一旦确认没有更多的任务在等待这个队列,系统就会安全地执行删除队列的操作。这个过程确保了在删除内核对象时,所有相关的任务都能得到妥善处理。
如下是OS_ PendAbort () 源码及其注释。 void OS_PendAbort (OS_TCB *p_tcb, // 函数用于取消任务的挂起状态
CPU_TS ts, // 时间戳
OS_STATUS reason) // 取消挂起的原因
{
#if (OS_CFG_TS_EN == 0u)
(void)ts; // 防止编译器警告,因为未使用 'ts'
#endif
switch (p_tcb->TaskState) { // 根据任务的状态进行不同的操作
case OS_TASK_STATE_PEND: // 如果任务处于挂起状态
case OS_TASK_STATE_PEND_TIMEOUT: // 或者超时挂起状态
#if (OS_MSG_EN > 0u)
p_tcb->MsgPtr = (void *)0; // 清空任务的消息指针
p_tcb->MsgSize = 0u; // 设置消息大小为0
#endif
#if (OS_CFG_TS_EN > 0u)
p_tcb->TS = ts; // 更新任务的时间戳
#endif
OS_PendListRemove(p_tcb); // 从挂起列表中移除任务
#if (OS_CFG_TICK_EN > 0u)
if (p_tcb->TaskState == OS_TASK_STATE_PEND_TIMEOUT) {
OS_TickListRemove(p_tcb); // 取消超时计时
}
#endif
OS_RdyListInsert(p_tcb); // 将任务插入就绪列表
p_tcb->TaskState = OS_TASK_STATE_RDY; // 设置任务状态为就绪
p_tcb->PendStatus = reason; // 标记任务如何变为就绪
p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; // 表示不再处于挂起状态
break;
case OS_TASK_STATE_PEND_SUSPENDED: // 如果任务处于挂起暂停状态
case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED: // 或者超时挂起暂停状态
#if (OS_MSG_EN > 0u)
p_tcb->MsgPtr = (void *)0; // 清空任务的消息指针
p_tcb->MsgSize = 0u; // 设置消息大小为0
#endif
#if (OS_CFG_TS_EN > 0u)
p_tcb->TS = ts; // 更新任务的时间戳
#endif
OS_PendListRemove(p_tcb); // 从挂起列表中移除任务
#if (OS_CFG_TICK_EN > 0u)
if (p_tcb->TaskState == OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED) {
OS_TickListRemove(p_tcb); // 取消超时计时
}
#endif
p_tcb->TaskState = OS_TASK_STATE_SUSPENDED; // 任务需要保持挂起暂停状态
p_tcb->PendStatus = reason; // 标记任务如何变为就绪
p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; // 表示不再处于挂起状态
break;
case OS_TASK_STATE_RDY: // 任务处于就绪状态,无法取消挂起
case OS_TASK_STATE_DLY: // 任务处于延时状态,无法取消挂起
case OS_TASK_STATE_SUSPENDED: // 任务处于暂停状态,无法取消挂起
case OS_TASK_STATE_DLY_SUSPENDED: // 任务处于延时暂停状态,无法取消挂起
default:
// 默认情况
break;
}
}
使用消息队列删除函数 OSQDel() 是一个直接的过程。我们只需提供要删除的消息队列的引用(句柄)、相关的选项以及一个变量来存储可能出现的错误类型。一旦调用了这个函数,系统就会执行删除操作,移除指定的消息队列。但在执行删除之前,必须确保该消息队列已经被创建。此外,如果有任务在等待从该消息队列接收消息,那么就不应该删除它,因为一旦删除,任何正在等待的任务都将无法继续等待消息,而且该消息队列也将变得不再可用。
举个例子,这就像我们要丢掉一把钥匙。我们需要知道是哪把钥匙,确认它是否真的存在,再决定是否要丢掉它。如果有人在等着用这把钥匙开门,那么我们就不能丢掉它,因为一旦扔了,那些人就没办法开门了。这把钥匙也就失去了作用。
使用实例: OS_Q queue; //声明消息队列
OS_ERR err;
/* 删除消息队列 queue */
OSQDel ((OS_Q *)&queue, //指向消息队列的指针
OS_OPT_DEL_NO_PEND,
(OS_ERR *)&err); //返回错误类型
5.3 消息队列发送函数 在uCOS操作系统中,任务和中断服务程序都有能力向消息队列发送消息。发送操作的基本流程是这样的:如果消息队列尚未满,发送的消息就会被加入到队列中。系统会从消息池里获取一个消息容器,并根据发送方式(先进先出FIFO或后进先出LIFO)将其添加到队列的相应位置。对于FIFO,消息会被添加到队列的尾部;而对于LIFO,则会被放置在队列的头部。消息容器的 MsgPtr 成员随后会指向实际要发送的数据内容。如果有任务因为等待消息而被阻塞,在消息被成功发送到队列后,这些任务会被解除阻塞状态。 void OSQPost (OS_Q *p_q, // 函数名和参数列表
void *p_void, // 指向要发送的消息的指针
OS_MSG_SIZE msg_size, // 消息的大小
OS_OPT opt, // 发送选项,决定发送方式是FIFO还是LIFO
OS_ERR *p_err) // 指向错误码的指针
{
OS_OPT post_type; // 本地变量,用于确定消息的发送类型
OS_PEND_LIST *p_pend_list; // 指向等待列表的指针
OS_TCB *p_tcb; // 用于遍历等待列表的任务控制块指针
OS_TCB *p_tcb_next; // 指向下一个任务控制块的指针
CPU_TS ts; // 时间戳变量
CPU_SR_ALLOC(); // 分配用于保存CPU状态寄存器的变量
#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) { // 安全检查:确保错误指针非空
OS_SAFETY_CRITICAL_EXCEPTION(); // 如果为空,则抛出安全关键异常
return; // 并返回
}
#endif
OS_TRACE_Q_POST_ENTER(p_q, p_void, msg_size, opt); // 跟踪消息队列发送操作
#if (OS_CFG_INVALID_OS_CALLS_CHK_EN > 0u)
if (OSRunning != OS_STATE_OS_RUNNING) { // 检查内核是否正在运行
OS_TRACE_Q_POST_EXIT(OS_ERR_OS_NOT_RUNNING);
*p_err = OS_ERR_OS_NOT_RUNNING;
return;
}
#endif
#if (OS_CFG_ARG_CHK_EN > 0u)
if (p_q == (OS_Q *)0) { // 验证消息队列指针是否为空
OS_TRACE_Q_POST_FAILED(p_q);
OS_TRACE_Q_POST_EXIT(OS_ERR_OBJ_PTR_NULL);
*p_err = OS_ERR_OBJ_PTR_NULL;
return;
}
switch (opt) { // 验证发送选项的有效性
case OS_OPT_POST_FIFO:
case OS_OPT_POST_LIFO:
case OS_OPT_POST_FIFO | OS_OPT_POST_ALL:
case OS_OPT_POST_LIFO | OS_OPT_POST_ALL:
case OS_OPT_POST_FIFO | OS_OPT_POST_NO_SCHED:
case OS_OPT_POST_LIFO | OS_OPT_POST_NO_SCHED:
case OS_OPT_POST_FIFO | (OS_OPT)(OS_OPT_POST_ALL | OS_OPT_POST_NO_SCHED):
case OS_OPT_POST_LIFO | (OS_OPT)(OS_OPT_POST_ALL | OS_OPT_POST_NO_SCHED):
break;
default:
OS_TRACE_Q_POST_FAILED(p_q);
OS_TRACE_Q_POST_EXIT(OS_ERR_OPT_INVALID);
*p_err = OS_ERR_OPT_INVALID;
return;
}
#endif
#if (OS_CFG_OBJ_TYPE_CHK_EN > 0u)
if (p_q->Type != OS_OBJ_TYPE_Q) { // 确保消息队列已创建
OS_TRACE_Q_POST_FAILED(p_q);
OS_TRACE_Q_POST_EXIT(OS_ERR_OBJ_TYPE);
*p_err = OS_ERR_OBJ_TYPE;
return;
}
#endif
#if (OS_CFG_TS_EN > 0u)
ts = OS_TS_GET(); // 获取时间戳
#else
ts = 0u;
#endif
OS_TRACE_Q_POST(p_q); // 跟踪消息队列发送操作
CPU_CRITICAL_ENTER(); // 进入临界区
p_pend_list = &p_q->PendList;
if (p_pend_list->HeadPtr == (OS_TCB *)0) { // 是否有任务在等待消息队列?
if ((opt & OS_OPT_POST_LIFO) == 0u) { // 确定是以FIFO还是LIFO方式发送消息
post_type = OS_OPT_POST_FIFO;
} else {
post_type = OS_OPT_POST_LIFO;
}
OS_MsgQPut(&p_q->MsgQ, // 将消息放入消息队列
p_void,
msg_size,
post_type,
ts,
p_err);
CPU_CRITICAL_EXIT(); // 退出临界区
OS_TRACE_Q_POST_EXIT(*p_err);
return;
}
p_tcb = p_pend_list->HeadPtr;
while (p_tcb != (OS_TCB *)0) {
p_tcb_next = p_tcb->PendNextPtr;
OS_Post((OS_PEND_OBJ *)((void *)p_q),
p_tcb,
p_void,
msg_size,
ts);
if ((opt & OS_OPT_POST_ALL) == 0u) { // 是否向所有等待任务发送消息?
break; // 否
}
p_tcb = p_tcb_next;
}
CPU_CRITICAL_EXIT(); // 退出临界区
if ((opt & OS_OPT_POST_NO_SCHED) == 0u) {
OSSched(); // 运行调度器
}
*p_err = OS_ERR_NONE;
OS_TRACE_Q_POST_EXIT(*p_err);
}
在uC/OS中,发送消息到消息队列的操作提供了多种灵活的方式。首先,你可以选择将消息发送给所有任务,这样可以实现系统范围内的信息广播或通知。其次,你也可以只将消息发送给特定的一个任务,确保消息被正确地发送到目标任务,实现任务间的单向通信。此外,uC/OS还支持中断延迟发布,这意味着我们可以在中断服务程序中延迟发送消息到任务中处理,避免直接在中断中发送消息可能引起的优先级反转等问题。
打个比方,这就像你有一个组信息系统,你可以选择发一条消息给整个团队,全员都会收到。或者你也可以选择只发一条私信给某个特定的人,这样只有他会看到。另外,在紧急情况下,你可以先记下要通知的内容,等到局面稍微稳住了再发出去,这样就不会因为当时消息太多而乱套了。
使用实例: /* 发送消息到消息队列 queue */
OSQPost ((OS_Q *)&queue, //消息变量指针
(void *)"Hello uC/OS-III", //要发送的数据的指针,将内存块首地址通过队列“发送出去”
(OS_MSG_SIZE )sizeof ( "Hello uC/OS-III" ), //数据字节大小
(OS_OPT )OS_OPT_POST_FIFO | OS_OPT_POST_ALL, //先进先出和发布给全部任务的形式
(OS_ERR *)&err); //返回错误类型
5.4 消息队列获取函数-OSQPend 当任务尝试从消息队列中获取消息时,用户可以设定一个阻塞超时时间。只有当消息队列中有消息时,任务才能成功获取消息。在等待过程中,如果队列为空,任务将一直处于阻塞状态,直到队列中有有效消息为止。当其他任务或中断服务程序向任务等待的队列中写入数据时,被阻塞的任务会自动从阻塞状态切换为就绪状态,准备处理消息。如果任务等待的时间超过用户设定的阻塞时间,即使队列中没有有效消息,任务也会自动从阻塞状态切换为就绪状态。这种机制确保了任务不会被阻塞,即使消息暂时不可用。 如下是OSQPend () 源码及其注释。 void *OSQPend (OS_Q *p_q, // 函数名和参数列表
OS_TICK timeout, // 等待超时时间
OS_OPT opt, // 等待选项
OS_MSG_SIZE *p_msg_size, // 指向消息大小的指针
CPU_TS *p_ts, // 时间戳指针
OS_ERR *p_err) // 错误码指针
{
void *p_void; // 用于存储消息的指针
CPU_SR_ALLOC(); // 分配用于保存CPU状态寄存器的变量
#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) { // 安全检查:确保错误指针非空
OS_SAFETY_CRITICAL_EXCEPTION(); // 如果为空,则抛出安全关键异常
return ((void *)0);
}
#endif
OS_TRACE_Q_PEND_ENTER(p_q, timeout, opt, p_msg_size, p_ts); // 跟踪消息队列等待操作
#if (OS_CFG_TICK_EN == 0u)
if (timeout != 0u) { // 如果定时器未启用且超时时间不为0
*p_err = OS_ERR_TICK_DISABLED; // 返回错误码:定时器未启用
OS_TRACE_Q_PEND_FAILED(p_q);
OS_TRACE_Q_PEND_EXIT(OS_ERR_TICK_DISABLED);
return ((void *)0);
}
#endif
#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
if (OSIntNestingCtr > 0u) { // 不允许在中断服务程序中调用该函数
if ((opt & OS_OPT_PEND_NON_BLOCKING) != OS_OPT_PEND_NON_BLOCKING) {
OS_TRACE_Q_PEND_FAILED(p_q);
OS_TRACE_Q_PEND_EXIT(OS_ERR_PEND_ISR);
*p_err = OS_ERR_PEND_ISR;
return ((void *)0);
}
}
#endif
#if (OS_CFG_INVALID_OS_CALLS_CHK_EN > 0u)
if (OSRunning != OS_STATE_OS_RUNNING) { // 检查内核是否正在运行
OS_TRACE_Q_PEND_EXIT(OS_ERR_OS_NOT_RUNNING);
*p_err = OS_ERR_OS_NOT_RUNNING;
return ((void *)0);
}
#endif
#if (OS_CFG_ARG_CHK_EN > 0u)
if (p_q == (OS_Q *)0) { // 验证参数是否有效
OS_TRACE_Q_PEND_FAILED(p_q);
OS_TRACE_Q_PEND_EXIT(OS_ERR_OBJ_PTR_NULL);
*p_err = OS_ERR_OBJ_PTR_NULL;
return ((void *)0);
}
if (p_msg_size == (OS_MSG_SIZE *)0) {
OS_TRACE_Q_PEND_FAILED(p_q);
OS_TRACE_Q_PEND_EXIT(OS_ERR_PTR_INVALID);
*p_err = OS_ERR_PTR_INVALID;
return ((void *)0);
}
switch (opt) {
case OS_OPT_PEND_BLOCKING:
case OS_OPT_PEND_NON_BLOCKING:
break;
default:
OS_TRACE_Q_PEND_FAILED(p_q);
OS_TRACE_Q_PEND_EXIT(OS_ERR_OPT_INVALID);
*p_err = OS_ERR_OPT_INVALID;
return ((void *)0);
}
#endif
#if (OS_CFG_OBJ_TYPE_CHK_EN > 0u)
if (p_q->Type != OS_OBJ_TYPE_Q) { // 确保消息队列已创建
OS_TRACE_Q_PEND_FAILED(p_q);
OS_TRACE_Q_PEND_EXIT(OS_ERR_OBJ_TYPE);
*p_err = OS_ERR_OBJ_TYPE;
return ((void *)0);
}
#endif
if (p_ts != (CPU_TS *)0) {
*p_ts = 0u; // 初始化时间戳为0
}
CPU_CRITICAL_ENTER(); // 进入临界区
p_void = OS_MsgQGet(&p_q->MsgQ, // 从消息队列获取消息
p_msg_size,
p_ts,
p_err);
if (*p_err == OS_ERR_NONE) { // 如果成功获取消息
OS_TRACE_Q_PEND(p_q);
CPU_CRITICAL_EXIT();
OS_TRACE_Q_PEND_EXIT(OS_ERR_NONE);
return (p_void); // 返回接收到的消息
}
if ((opt & OS_OPT_PEND_NON_BLOCKING) != 0u) { // 如果是非阻塞等待
CPU_CRITICAL_EXIT();
OS_TRACE_Q_PEND_FAILED(p_q);
OS_TRACE_Q_PEND_EXIT(OS_ERR_PEND_WOULD_BLOCK);
*p_err = OS_ERR_PEND_WOULD_BLOCK; // 返回错误码:将会阻塞
return ((void *)0);
} else {
if (OSSchedLockNestingCtr > 0u) { // 如果调度器被锁定
CPU_CRITICAL_EXIT();
OS_TRACE_Q_PEND_FAILED(p_q);
OS_TRACE_Q_PEND_EXIT(OS_ERR_SCHED_LOCKED);
*p_err = OS_ERR_SCHED_LOCKED; // 返回错误码:调度器被锁定
return ((void *)0);
}
}
OS_Pend((OS_PEND_OBJ *)((void *)p_q), // 阻塞任务等待消息队列
OSTCBCurPtr,
OS_TASK_PEND_ON_Q,
timeout);
CPU_CRITICAL_EXIT();
OS_TRACE_Q_PEND_BLOCK(p_q);
OSSched(); // 运行调度器,找到下一个最高优先级任务准备运行
CPU_CRITICAL_ENTER();
使用实例: OS_Q queue; //声明消息队列
OS_ERR err;
OS_MSG_SIZE msg_size;
/* 获取消息队列 queue 的消息 */
pMsg = OSQPend ((OS_Q *)&queue, //消息变量指针
(OS_TICK )0, //等待时长为无限
(OS_OPT )OS_OPT_PEND_BLOCKING, //如果没有获取到信号量就等待
(OS_MSG_SIZE *)&msg_size, //获取消息的字节大小
(CPU_TS *)0, //获取任务发送时的时间戳
(OS_ERR *)&err); //返回错误
6、例程实现 准备:APM32F407 开发板,JLINK 或其他下载器。 例程主要涉及了两个任务:AppTaskPost() 和 AppTaskPend()。AppTaskPost() 负责发送消息,而 AppTaskPend() 负责接收消息。 这两个任务是独立运行的。接收到的消息会通过串口调试助手显示出来,这样可以直观地看到消息传递的效果。代码如下。 #define APP_TASK_START_STK_SIZE 128 // 定义开始任务的堆栈大小
#define APP_TASK_POST_STK_SIZE 512 // 定义发布任务的堆栈大小
#define APP_TASK_PEND_STK_SIZE 512 // 定义挂起任务的堆栈大小
#define APP_TASK_START_PRIO 2 // 定义开始任务的优先级
#define APP_TASK_POST_PRIO 3 // 定义发布任务的优先级
#define APP_TASK_PEND_PRIO 3 // 定义挂起任务的优先级
OS_Q queue; // 定义一个消息队列
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); // 声明挂起任务函数
/*!
* [url=home.php?mod=space&uid=247401]@brief[/url] Main program
*
* @param None
*
* @retval None
*/
int main(void)
{
USART_Config_T usartConfig; // 声明USART配置结构体变量usartConfig
/* Configure USART */
usartConfig.baudRate = 115200; // 配置波特率为115200
usartConfig.wordLength = USART_WORD_LEN_8B; // 配置数据位长度为8位
usartConfig.stopBits = USART_STOP_BIT_1; // 配置停止位为1位
usartConfig.parity = USART_PARITY_NONE; // 配置校验位为无校验
usartConfig.mode = USART_MODE_TX_RX; // 配置USART为收发模式
usartConfig.hardwareFlow = USART_HARDWARE_FLOW_NONE; // 配置硬件流控为无流控
SysTick_Config(RCM_ReadSYSCLKFreq() / 1000); // 配置SysTick定时器
APM_MINI_COMInit(COM1, &usartConfig); // 初始化USART通信
APM_MINI_LEDInit(LED2); // 初始化LED2
APM_MINI_LEDInit(LED3); // 初始化LED3
OS_ERR err; // 声明uC/OS-III错误变量err
OSInit(&err); // 初始化uC/OS-III
OSTaskCreate((OS_TCB*)&AppTaskStartTCB, // 创建开始任务
(CPU_CHAR*)"APP Task Start",
(OS_TASK_PTR)AppTaskStart,
(void*)0,
(OS_PRIO)APP_TASK_START_PRIO,
(CPU_STK*)&AppTaskStartStk[0],
(CPU_STK_SIZE)APP_TASK_START_STK_SIZE / 10,
(CPU_STK_SIZE)APP_TASK_START_STK_SIZE,
(OS_MSG_QTY)5,
(OS_TICK)0,
(void*)0,
(OS_OPT)(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR*)&err);
OSStart(&err); // 启动uC/OS-III内核
}
static void AppTaskStart(void *p_arg)
{
OS_ERR err; // 声明uC/OS-III错误变量err
(void)p_arg; // 防止编译器警告
CPU_Init(); // 初始化CPU
Mem_Init(); // 初始化内存管理
#if OS_CFG_STAT_TASK_EN > 0u // 如果统计任务使能
OSStatTaskCPUUsageInit(&err); // 初始化统计任务
#endif
CPU_IntDisMeasMaxCurReset(); // 重置中断最大和当前计数
OSQCreate((OS_Q*)&queue, // 创建消息队列
(CPU_CHAR*)"Queue For Test",
(OS_MSG_QTY)20,
(OS_ERR*)&err);
OSTaskCreate((OS_TCB*)&AppTaskPostTCB, // 创建发布任务
(CPU_CHAR*)"App Task Post",
(OS_TASK_PTR)AppTaskPost,
(void*)0,
(OS_PRIO)APP_TASK_POST_PRIO,
(CPU_STK*)&AppTaskPostStk[0],
(CPU_STK_SIZE)APP_TASK_POST_STK_SIZE / 10,
(CPU_STK_SIZE)APP_TASK_POST_STK_SIZE,
(OS_MSG_QTY)5u,
(OS_TICK)0u,
(void*)0,
(OS_OPT)(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR*)&err);
OSTaskCreate((OS_TCB*)&AppTaskPendTCB, // 创建挂起任务
(CPU_CHAR*)"App Task Pend",
(OS_TASK_PTR)AppTaskPend,
(void*)0,
(OS_PRIO)APP_TASK_PEND_PRIO,
(CPU_STK*)&AppTaskPendStk[0],
(CPU_STK_SIZE)APP_TASK_PEND_STK_SIZE / 10,
(CPU_STK_SIZE)APP_TASK_PEND_STK_SIZE,
(OS_MSG_QTY)5u,
(OS_TICK)0u,
(void*)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; // 声明uC/OS-III错误变量err
(void)p_arg; // 防止编译器警告
while (DEF_TRUE) { // 无限循环
OSQPost((OS_Q*)&queue, // 向消息队列发布消息
(void*)"Hello uC/OS-III!",
(OS_MSG_SIZE)sizeof("Hello uC/OS-III!"),
(OS_OPT)OS_OPT_POST_FIFO | OS_OPT_POST_ALL,
(OS_ERR*)&err);
OSTimeDlyHMSM(0, 0, 0, 500, OS_OPT_TIME_DLY, &err); // 延时500毫秒
}
}
static void AppTaskPend(void *p_arg)
{
OS_ERR err; // 声明uC/OS-III错误变量err
OS_MSG_SIZE msg_size; // 消息大小变量
CPU_SR_ALLOC(); // 申请临界区保护变量
char *pMsg; // 指向消息的指针
(void)p_arg; // 防止编译器警告
while (DEF_TRUE) { // 无限循环
pMsg = OSQPend((OS_Q*)&queue, // 从消息队列接收消息
(OS_TICK)0,
(OS_OPT)OS_OPT_PEND_BLOCKING,
(OS_MSG_SIZE*)&msg_size,
(CPU_TS*)0,
(OS_ERR*)&err);
if (err == OS_ERR_NONE) { // 如果没有错误
CPU_CRITICAL_ENTER(); // 进入临界区
printf("\r\n 接收消息的长度:%d 字节,内容:%s\r\n", msg_size, pMsg); // 打印接收到的消息
CPU_CRITICAL_EXIT(); // 退出临界区
}
}
}
例程实验现象
7、消息队列使用注意事项 在使用 uCOS 的消息队列功能时,有几个关键点需要注意: 1. 创建和操作队列:在使用 OSQPend()、OSQPost() 等函数之前,必须先创建消息队列,并使用队列句柄(队列控制块)来进行操作。 2. 队列读取模式:默认情况下,队列采用先进先出(FIFO)模式,这意味着最先进入队列的数据会被最先读取。不过,uCOS 也支持后进先出(LIFO)模式,这样就会先读取最后进入队列的数据。 3. 消息传递方式:无论是发送还是接收消息,都是通过数据引用的方式进行,即传递数据的指针和大小。 4. 队列的独立性:队列是独立的内核对象,不属于任何特定任务。所有任务都可以向同一个队列写入和读取数据。通常情况下,一个队列可能被多个任务或中断写入,但被多个任务读取的情况相对较少。 5. 消息内容的保护:传递的消息实际上是内容的指针和字节大小。在获取消息之前,不能释放存储在消息中的指针内容。例如,如果中断定义了一个局部变量并将其地址放入消息中,那么在中断退出并释放局部变量之前,其他任务必须已经获取了该消息。否则,任务获取的将是一个无效的地址。为了避免这种情况,可以: - 定义变量为静态变量(在变量前加 static),这样内存单元就不会被释放。 - 定义变量为全局变量。 - 传递内容的值作为“地址”,这样接收消息的任务可以直接处理这个“地址”作为内容。 总结来说,使用 uCOS 的消息队列时,重要的是要正确创建和操作队列,理解队列的读取模式,确保消息传递的正确性,以及保护消息内容不被提前释放。这些措施可以确保消息队列的有效使用,避免数据丢失或错误。
通过这篇文章深入探讨uCOS中的消息队列,我们可以发现,消息队列在任务之间的通信和同步中非常重要。它提供了一种高效的消息传递机制,使得任务之间的数据共享和协调变得更加简单。
附件是消息队列的例程源码,有需要的可自行下载。
uCOS-消息队列.zip
(2.61 MB)
|
通过拆解常用函数和实际应用案例,并通过上文的理论知识,构成完整的消息队列讲解。详略得当,结构完整