本文主要是介绍GD32F103RCT6/GD32F303RCT6-UCOSIII底层移植(4)消息队列实验,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
本文章基于兆易创新GD32 MCU所提供的2.2.4版本库函数开发
后续项目主要在下面该专栏中发布:
手把手教你嵌入式国产化_不及你的温柔的博客-CSDN博客
感兴趣的点个关注收藏一下吧!
电机驱动开发可以跳转:
手把手教你嵌入式国产化-实战项目-无刷电机驱动(1)-CSDN博客
BMS电源系统开发可以跳转:暂未放链接
向上代码兼容GD32F303RCT6中使用
本项目配套开发板:
基于GD32F103RCT6国产GD32平台,以下教程编写基于该开发板
图片:
原理图以及例程请联系客服获取!
注意:
本教程致力于解决所有在调试中出现的所有问题,如有未包含在的问题,请联系QQ:2049363803,有奖更新文档!
参考资料:
《UCOS-III开发指南_V1.5》
《µC/OS-III Documentation》
《ARM Cortex-M3 与 Cortex-M4 权威指南(第 3 版)》
《uCOS-III内核实现与应用开发实战指南—基于STM32》
介绍
消息队列,又称为消息队列,是实时操作系统中一种重要的内核对象,它用于任务间或中断与任务间的通信,实现数据传递。消息队列的特点和工作方式灵活,支持多种通信模式,以下是其基本概念的详细解析:
数据结构与功能:
消息息队列是一种数据结构,通常由一系列消息单元组成,每个单元存放一条消息。每个消息可能包含指针指向实际数据的指针、消息的大小、时间戳等元数据,以及指向下一个消息的指针,形成链表或队列。
先进先出列(FIFO) 默认情况下,消息队列遵循先进先出原则,即先加入队列的消息会被首先取出。这适用于大多数需要顺序处理消息的场景。
后进先出(LIFO) uCOS还支持后进先出(LIFO),紧急消息会排在队列头部。这种模式下,新来的消息优先被处理,适用于紧急或优先级较高的消息传递。
超时机制 在尝试读取空队列时,任务可以选择等待(阻塞),并指定超时长。超时未等到消息,任务自动恢复就绪,继续执行。
发送模式 任务或中断服务程序可以发送消息到队列。发送时,若队列未满,操作成功;满则返回错误。
资源管理 为提高效率,uCOSOSIII引入了消息池概念,预先初始化时将数组链表为链表,动态分配消息。消息池简化了消息分配与回收,减少了开销耗时开销操作。
删除 当队列不再使用,可通过API删除,资源被清理,释放关联的内存,不可再用,除非重新创建。
消息类型和长度 uCOS支持不同长度的消息,不限类型,通过指针传递,无论数据大小,处理时间复杂度一致,便于内存管理。
任务消息队列 与普通列不同,任务消息队列专属于特定任务,仅该任务可接收,其他任务发送。它不带等待列表,简化了等待处理,提高了效率。
应用场景:
传感器数据处理: 在物联网应用中,传感器任务定期收集数据发送数据到处理任务,使用消息列。处理任务从列中获取数据进行分析,实现异步操作。
任务间协作: 多任务合作场景,如生产者-消费者模型,生产者任务生产消息到列,消费者任务消费。列作为缓冲区,平衡任务速率差异,防止数据丢失。
状态更新: 系统状态更新,如任务状态改变,如任务状态变化,通过消息列告知其他任务。这样,其他任务能即时响应状态变化,实现动态调整。
实现细节:
队列控制块:每个队列在uCOSIII由控制块管理,包含队列的属性如队列的指针、消息数、入队指针、出队针等。
API安全性:uCOSIII提供安全API如OS_QPost、QPend等,检查错误处理,确保安全,如参数验证、中断锁调度、队列状态。
中断中使用:uCOSIII允许中断中使用列操作,如中断服务例程向列发送消息,需注意配置中断安全使用,确保不破坏系统稳定性。
消息队列的阻塞机制
消息队列的阻塞机制是uCOSIII中用于解决任务间通信的同步问题的一种重要机制。该机制确保了任务在尝试访问共享资源时的竞争公平性和同步性,尤其是当资源暂时不可被任务调用时,任务能够被允许等待直至消息队列资源可用。
阻塞:当任务尝试从空列读取消息而列无消息时,可以选择进入阻塞状态。这意味着任务暂停执行,直到满足特定条件(如消息到达)才继续。
超时:任务等待时长:在阻塞时,任务可以指定超时长,即最长等待时间。若超时未收到消息,任务将自动恢复执行,任务不会继续等待,哪怕这个时候任务依旧没有等到消息队列更新。
优先级排序:如果多个任务阻塞在同一个消息队列上,此时优先级更高的任务会被调度器优先唤醒获得消息的处理权。uCOSIII根据任务优先级自动管理等待队列,有效保证了任务与任务之间从消息队列中获取消息的顺序。
模式:uCOSIII提供了不同的任务阻塞模式,比如
OS_OPT_PEND
用于等待,OS_OPT_NO_WAIT
不等待。在实际使用中我们需要根据需求去选择不同的工作模式。唤醒:一旦消息列存在消息更新,调度器会重新唤醒当前因消息队列阻塞而等待的任务。任务由阻塞态重新变为就绪态,此时该任务可以被调度执行。
消息队列常用函数
OSQend()
功能:用于从消息队列中获取(接收)消息。任务会等待直到消息可用(如果设置了等待选项为阻塞等待模式)或超时才继续执行。
参数:
OS_Q *p_q:指向消息队列的指针
OS_TICK timeout:等待时长,单位为0表示无限等待;非0则为等待时长
OS_OPTopt:操作选项,如OS_OPT_PEND_BLOCKING表示阻塞等待,OS_OPT_NO_WAIT`表示不等待
OS_MSG_SIZE *p_msg_size:传出参数,返回消息的大小
CPU_TS *p_ts:传出参数,消息发送时戳
OS_ERR *p_err:传出错误代码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)// 返回错误类型例子:
CPU_TS ts; OS_MSG_SIZE msg_size; char *pMsg; OS_ERR err; pMsg = OSQPend(&queue, 0, OS_OPT_PEND, &msg_size, &ts, &err); if (err == OS_ERR_NONE) {printf("Message received, size=%d: %d, content: %.*s\n", msg_size, msg_size, pMsg); } else if (err == OS_ERR_PEND_TIMEOUT) {printf("Timeout waiting for message.\n");} else {printf("Error %d\n", err);}
OSQPost()
功能:向消息列发送消息。任务或中断服务程序可以将消息放入队列,如果有任务等待则唤醒它们。
参数:
OS_Q *p_q:指向消息队列的指针
void *p_void:指向消息内容的指针- OS_MSG_SIZE msg_size:消息大小- OS_OPT opt:选项,如OS_FIFO先进先出列,OS_LIFO后出列
OS_ERR *p_err:传出错误代码void OSQPost (OS_Q *p_q,// 消息队列指针void *p_void,// 消息指针OS_MSG_SIZE msg_size,// 消息大小(单位:字节)OS_OPT opt,// 选项OS_ERR *p_err)// 返回错误类型例子:
char msg[] = "Hello World!"; OS_ERR err; OSQPost(&queue, (void *)msg, sizeof(msg), OS_FIFO, &err); if (err == OS_ERR_NONE) {printf("Message sent.\n");} else {printf("Error sending: %d\n", err);}
OSQDel()
功能:删除消息列。此操作永久删除队列,删除后不可再使用。确保删除前无任务等待队列。
参数:
OS_Q *p_q:指向队列的指针- OS_OPTopt:删除选项,如OS_OPT_DEL_ALWAYS总是立即删除
OS_ERR *p_err:传出错误OS_OBJ_QTY OSQDel (OS_Q *p_q, // 消息队列指针OS_OPT opt, // 选项OS_ERR *p_err) // 返回错误类型例子:
_OS_ERR err; if (OSQDel(&queue, OS_OPT_DEL_ALWAYS, &err) == OS_ERR_NONE) {printf("Queue deleted.\n");} else {printf("Error deleting: %d\n", err);}
OSQCreate()
功能:创建新队列。为任务间通信分配内存并初始化队列结构。
参数:
OS_Q *p_q:指向队列的指针- CPU_CHAR *name:队列名称- OS_MSG_QTY:队列大小。
OS_ERR *p_err:传出错误。void OSQCreate (OS_Q *p_q,// 消息队列指针CPU_CHAR *p_name,// 消息队列名称OS_MSG_QTY max_qty,// 消息队列大小(不能为 0 )OS_ERR *p_err)// 返回错误类型例子:
_OS_Q queue; _OS_ERR err; OSQCreate(&queue, "ExampleQueue", 10, &err); if (err == OS_ERR_NONE) {printf("Queue created.\n");} else {printf("Error creating: %d\n", err);}
消息队列实验
接下来,让我们来建立一下消息队列的实验,其中AppTaskPost()用于向队列中发送信息;AppTaskPend()用于从队列中接收信息。然后用串口打印接收到的信息。
在本次实验之前,我们还没有在系统中加入我们常用的串口调试打印的代码,那么本次开始我们将逐渐完善。
首先从之前定义的外设库中取出usart的头文件和源文件,具体的也可以参考:GD32F103RCT6/GD32F303RCT6(6.3)USART通讯实验
复制到User中的Bsp文件下:
不要忘记载入和头文件包含了:
然后初始化
这样我们就可以在系统中对其进行调用了!
记得关闭串口中断,我们本次实验使用不到:
app.c
#include <includes.h>/*
*************************************************************************
* 任务栈存放区域
*************************************************************************
*/static CPU_STK AppTaskStartStk[APP_TASK_START_STK_SIZE];static CPU_STK AppTaskPostStk [ APP_TASK_LED1_STK_SIZE ];
static CPU_STK AppTaskPendStk [ APP_TASK_LED2_STK_SIZE ]; /*
*************************************************************************
* 定义任务控制块存放区域
*************************************************************************
*/
static OS_TCB AppTaskStartTCB;static OS_TCB AppTaskPostTCB;
static OS_TCB AppTaskPendTCB;/*
*************************************************************************
* 函数原型存放区域
*************************************************************************
*/static void AppTaskStart (void *p_arg);static void AppTaskPost ( void * p_arg );
static void AppTaskPend ( void * p_arg ); /*
*************************************************************************
* 消息队列存放区域
*************************************************************************
*/OS_Q queue; //声明消息队列/*
*************************************************************************
* main 函数
*************************************************************************
*/
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{OS_ERR err;OSInit(&err);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 ) 5u,(OS_TICK ) 0u,(void *) 0,(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),(OS_ERR *)&err);OSStart(&err);}/*
*************************************************************************
* 空闲任务存放区域
*************************************************************************
*/static void AppTaskStart (void *p_arg)
{CPU_INT32U cpu_clk_freq;CPU_INT32U cnts;OS_ERR err;(void)p_arg;BSP_Init(); /* Initialize BSP functions */CPU_Init();cpu_clk_freq = BSP_CPU_ClkFreq(); /* Determine SysTick reference freq. */cnts = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz; /* Determine nbr SysTick increme nts */OS_CPU_SysTickInit(cnts); /*Init uC/OS periodic time src(SysTick).*/Mem_Init(); /* Initialize Memory Management Module */#if OS_CFG_STAT_TASK_EN > 0uOSStatTaskCPUUsageInit(&err); /* Compute CPU capacity with no task running */#endifCPU_IntDisMeasMaxCurReset();/* 创建消息队列 queue */OSQCreate ((OS_Q *)&queue, //指向消息队列的指针(CPU_CHAR *)"Queue For Test", //队列的名字(OS_MSG_QTY )20, //最多可存放消息的数目(OS_ERR *)&err); //返回错误类型OSTaskCreate((OS_TCB *)&AppTaskPostTCB,/*Create the Led1 task */ (CPU_CHAR *)"App Task Led1", (OS_TASK_PTR ) AppTaskPost, (void *) 0, (OS_PRIO ) APP_TASK_LED1_PRIO, (CPU_STK *)&AppTaskPostStk[0], (CPU_STK_SIZE) APP_TASK_LED1_STK_SIZE / 10, (CPU_STK_SIZE) APP_TASK_LED1_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, /*Create the Led2 task*/ (CPU_CHAR *)"App Task Led2", (OS_TASK_PTR ) AppTaskPend, (void *) 0, (OS_PRIO ) APP_TASK_LED2_PRIO, (CPU_STK *)&AppTaskPendStk[0], (CPU_STK_SIZE) APP_TASK_LED2_STK_SIZE / 10, (CPU_STK_SIZE) APP_TASK_LED2_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 ); }/********************************************************************** LED1 TASK*********************************************************************/static void AppTaskPost ( void * p_arg )
{ OS_ERR err; CPU_SR_ALLOC();(void)p_arg; while (DEF_TRUE) {char Post_test[]=" uC/OS-III test";/* 发布消息到消息队列 queue */OSQPost ((OS_Q *)&queue, //消息变量指针(void *)Post_test, //要发送的数据的指针,将内存块首地址通过队列“发送出去”(OS_MSG_SIZE )sizeof ( Post_test ),//数据字节大小(OS_OPT )OS_OPT_POST_FIFO | OS_OPT_POST_ALL, //先进先出和发布给全部任务的形式(OS_ERR *)&err); //返回错误类型if ( err == OS_ERR_NONE ) //如果接收成功{OS_CRITICAL_ENTER(); //进入临界段printf ( "\r\n 发送消息的长度:%d 字节,内容:%s\r\n", sizeof ( Post_test ), Post_test );OS_CRITICAL_EXIT();}OSTimeDlyHMSM ( 0, 0, 0, 500, OS_OPT_TIME_DLY, & err );}} /************************************************************************* LED2 TASK*************************************************************************/static void AppTaskPend ( void * p_arg )
{ OS_ERR err; OS_MSG_SIZE msg_size;CPU_SR_ALLOC(); char * pMsg;(void)p_arg; while (DEF_TRUE) { /* 请求消息队列 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); //返回错误if ( err == OS_ERR_NONE ) //如果接收成功{OS_CRITICAL_ENTER(); //进入临界段printf ( "\r\n 接收消息的长度:%d 字节,内容:%s\r\n", msg_size, pMsg );OS_CRITICAL_EXIT();}}
}
烧录下载:
完成实验!
这篇关于GD32F103RCT6/GD32F303RCT6-UCOSIII底层移植(4)消息队列实验的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!