文章目录
- 序言
- 什么是消息队列
- 消息队列相关函数
- OSQCreate()
- OSQPend()
- OSQPost()
- 消息队列实验
- 总结
序言
前面我们介绍了信号量,通过信号量我们能够解决优先级反转,资源共享冲突等问题,但是我们不难发现,信号量无法携带具体的数据信息。消息队列就是这样一种具体数据信息传递的有效工具。本篇文章的主题也就是消息队列。
本篇文章的结构大概是这样的,我先介绍一下什么是消息队列,然后介绍消息队列在UCOSIII中的函数,最后就是一个实验,我们通过这个实验可以直观地感受一下消息队列在UCOSIII中的具体使用方法及其效用。
注:实验程序来自正点原子
什么是消息队列
在认识消息队列之前,我们要先明白什么是消息
,在UCOSIII中,一个消息包含指向数据的指针、该数据的大小、时间戳变量。其中的指针可以指向数据区域或者是一个函数。
消息队列
是一个可以包含多个消息
的内核对象,就像信号量一样,我们需要定义它,创建它,请求它,释放它。API的使用是很简单的,我们学习的重点是它的工作思想。下面的图片展示了在创建完成消息队列之后,我们可以对消息队列进行的操作。
从图中我们可以看出,中断服务程序只能使用OSQPost()函数!在UCOSIII中对于消息队列的读取既可以采用FIFO方式,也可以采用LIFO方式,当任务或者中断服务程序需要向任务发送一条紧急消息时,LIFO机制就非常有用。采用后进先出的方式,发布的消息会绕过其它所有的已经位于消息队列中的消息而最先传递给任务。
在上面的图中,OSQPend()函数下面有一个沙漏,它的意思是如果任务在这段时间内没有接收到消息的话就不再继续等待,并且返回一个错误码告诉UCOSIII超时。如果将这个超时时间指定为0的话,任务就一直等待下去,直到接收到消息。
消息队列
中有一个列表,记录了所有正在等待获得消息的任务,如下图所示为多个任务在同一个消息队列中等待,当一则消息被发布到队列中时,最高优先级的等待任务将获得该消息,发布方式也可以向消息队列中所有等待的任务广播一则消息
消息队列相关函数
在开始函数探究之前,我们先来看一下相关的数据结构,用来定义消息队列的数据结构时OS_Q,我们进去看看它的内部结构如下所示
struct os_q {
OS_OBJ_TYPE Type;
CPU_CHAR *NamePtr;
OS_PEND_LIST PendList;
#if OS_CFG_DBG_EN > 0u
OS_Q *DbgPrevPtr;
OS_Q *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
OS_MSG_Q MsgQ;
};
- Type:注释里也没说这是什么意思
- *NamePtr:消息队列的名字
- PendList:用来记录正在这个消息队列上等待的任务
下面的三个参数是和调试有关的,我们暂时不去关心,我们的重点放在最后一个参数:MsgQ,我们进入它的定义看看
struct os_msg_q {
OS_MSG *InPtr;
OS_MSG *OutPtr;
OS_MSG_QTY NbrEntriesSize;
OS_MSG_QTY NbrEntries;
OS_MSG_QTY NbrEntriesMax;
};
这是才是真正的用来维护消息队列的数据结构,具体每个数据成员的含义,注释中都很清楚了,这个时候,有心的同学可能会发现,不是说维护消息吗,消息通过什么数据成员来维护呢,难道不应该有一个指针用来指向数据区域吗?这个我看了一下,是一个相当复杂的机制,等我先把UCOSIII的基本用法学完再来慢慢地啃这些深奥的地方。下面我们就来看看与消息队列相关的函数有哪些。
函数 | 描述 |
---|
OSQCreate() | 创建一个消息队列 |
OSQDel() | 删除一个消息队列 |
OSQFlush() | 清空一个消息队列 |
OSQPend() | 等待消息队列 |
OSQPendAbort() | 取消等待消息队列 |
OSQPost() | 向消息队列发送一条消息 |
其中常用的三个函数:OSQCreate(),OSQPost()和OSQPend()。
OSQCreate()
void OSQCreate (OS_Q *p_q,
CPU_CHAR *p_name,
OS_MSG_QTY max_qty,
OS_ERR *p_err)
- p_q:指向一个消息队列,消息队列的存储空间必须由应用程序分配
- p_name:消息队列的名字
- max_qty:消息队列的长度,必须大于0
- p_err:保存调用此函数后返回的错误码
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)
- p_q:指向一个消息队列
- timeout:等待消息的超时时间
- opt:用来选择是否使用阻塞模式,具体选项含义见注释
- p_msg_size:指向一个变量用来表示接受到的消息的长度
- p_ts:指向一个时间戳,表明什么时候接收到消息
- p_err:用来保存调用此函数后返回的错误码
OSQPost()
void OSQPost (OS_Q *p_q,
void *p_void,
OS_MSG_SIZE msg_size,
OS_OPT opt,
OS_ERR *p_err)
- p_q:指向一个消息队列
- p_void:指向实际发送的内容,p_void是一个指向void类型的指针
- msg_size:设定消息的大小,单位为字节数
- opt:用来选择消息发送操作的类型,具体有哪些类型,可以看源码注释
- p_err:用来保存调用此函数后返回的错误码
消息队列实验
本次实验用到了4个任务、两个消息队列、一个定时器。先介绍四个任务的功能,开始任务start_task用来创建其它三个任务,
- main_task任务:用来进行按键检测,并且将按键的值通过消息队列KEY_Msg发送给任务Keyprocess_task,初次之外还会根据任务的执行次数来控制LED0的闪烁
- Keyprocess_task任务:向KEY_Msg消息队列请求消息,并根据请求到的消息来进行相应的处理
- msgdis_task任务:向DATA_Msg消息队列请求消息,并且将请求到的消息显示在LCD上
- tmr1_callback回调函数:向DATA_Msg消息队列发送消息,如果DATA_Msg满了就停止定时器1
下面我们进入具体代码解析
首先定义四个任务
#define START_TASK_PRIO 3
#define START_STK_SIZE 128
OS_TCB StartTaskTCB;
CPU_STK START_TASK_STK[START_STK_SIZE];
void start_task(void *p_arg);
#define MAIN_TASK_PRIO 4
#define MAIN_STK_SIZE 128
OS_TCB Main_TaskTCB;
CPU_STK MAIN_TASK_STK[MAIN_STK_SIZE];
void main_task(void *p_arg);
#define KEYPROCESS_TASK_PRIO 5
#define KEYPROCESS_STK_SIZE 128
OS_TCB Keyprocess_TaskTCB;
CPU_STK KEYPROCESS_TASK_STK[MAIN_STK_SIZE];
void Keyprocess_task(void *p_arg);
#define MSGDIS_TASK_PRIO 6
#define MSGDIS_STK_SIZE 128
OS_TCB Msgdis_TaskTCB;
CPU_STK MSGDIS_TASK_STK[MSGDIS_STK_SIZE];
void msgdis_task(void *p_arg);
定义定时器
u8 tmr1sta = 0;
OS_TMR tmr1;
定时器回调函数
void tmr1_callback(void *p_tmr, void *p_arg);
下面是两个函数,一个用来加载LCD初始界面,一个用来查询DATA_Msg消息队列中的总队列数量和剩余队列数量
void ucos_load_main_ui(void)
{
POINT_COLOR = RED;
LCD_ShowString(10,10,200,16,16,"ALIENTEK STM32F1");
LCD_ShowString(10,30,200,16,16,"UCOSIII Examp 11-1");
LCD_ShowString(10,50,200,16,16,"Message Queue");
LCD_ShowString(10,70,220,16,16,"KEY0:Refresh LCD");
LCD_ShowString(10,90,200,16,16,"KEY_UP:LED1 KEY1:Tmr1");
POINT_COLOR = BLACK;
LCD_DrawLine(0,107,239,107);
LCD_DrawLine(119,107,119,319);
LCD_DrawRectangle(125,110,234,314);
POINT_COLOR = RED;
LCD_ShowString(0,130,100,16,16,"tmr1 state:");
LCD_ShowString(0,170,120,16,16,"DATA_Msg Size:");
LCD_ShowString(0,210,120,16,16,"DATA_Msg rema:");
LCD_ShowString(0,250,100,16,16,"DATA_Msg:");
POINT_COLOR = BLUE;
LCD_ShowString(10,150,100,16,16,"TMR1 STOP! ");
}
void check_msg_queue(u8 *p)
{
CPU_SR_ALLOC();
u8 msgq_remain_size;
OS_CRITICAL_ENTER();
msgq_remain_size = DATA_Msg.MsgQ.NbrEntriesSize-DATA_Msg.MsgQ.NbrEntries;
p = malloc(20);
sprintf((char*)p,"Total Size:%d",DATA_Msg.MsgQ.NbrEntriesSize);
LCD_ShowString(10,190,100,16,16,p);
sprintf((char*)p,"Remain Size:%d",msgq_remain_size);
LCD_ShowString(10,230,100,16,16,p);
free(p);
OS_CRITICAL_EXIT();
}
main函数和start_task函数就不写了,下面是main_task任务
void main_task(void *p_arg)
{
u8 key,num;
OS_ERR err;
u8 *p;
while(1)
{
key = KEY_Scan(0);
if(key)
{
OSQPost((OS_Q* )&KEY_Msg,
(void* )&key,
(OS_MSG_SIZE)1,
(OS_OPT )OS_OPT_POST_FIFO,
(OS_ERR* )&err);
}
num++;
if(num%10==0) check_msg_queue(p);
if(num==50)
{
num=0;
LED0 = ~LED0;
}
OSTimeDlyHMSM(0,0,0,10,OS_OPT_TIME_PERIODIC,&err);
}
}
按键处理任务
void Keyprocess_task(void *p_arg)
{
u8 num;
u8 *key;
OS_MSG_SIZE size;
OS_ERR err;
while(1)
{
key=OSQPend((OS_Q* )&KEY_Msg,
(OS_TICK )0,
(OS_OPT )OS_OPT_PEND_BLOCKING,
(OS_MSG_SIZE* )&size,
(CPU_TS* )0,
(OS_ERR* )&err);
switch(*key)
{
case WKUP_PRES:
LED1 = ~LED1;
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err);
break;
case KEY0_PRES:
num++;
LCD_Fill(126,111,233,313,lcd_discolor[num%14]);
break;
case KEY1_PRES:
tmr1sta = !tmr1sta;
if(tmr1sta)
{
OSTmrStart(&tmr1,&err);
LCD_ShowString(10,150,100,16,16,"TMR1 START!");
}
else
{
OSTmrStop(&tmr1,OS_OPT_TMR_NONE,0,&err);
LCD_ShowString(10,150,100,16,16,"TMR1 STOP! ");
}
break;
}
}
}
接着是显示消息队列中的消息的函数
void msgdis_task(void *p_arg)
{
u8 *p;
OS_MSG_SIZE size;
OS_ERR err;
while(1)
{
p=OSQPend((OS_Q* )&DATA_Msg,
(OS_TICK )0,
(OS_OPT )OS_OPT_PEND_BLOCKING,
(OS_MSG_SIZE* )&size,
(CPU_TS* )0,
(OS_ERR* )&err);
LCD_ShowString(5,270,100,16,16,p);
free(p);
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err);
}
}
最后是定时器的回调函数
void tmr1_callback(void *p_tmr,void *p_arg)
{
u8 *pbuf;
static u8 msg_num;
OS_ERR err;
pbuf = malloc(10);
if(pbuf)
{
msg_num++;
sprintf((char*)pbuf,"ALIENTEK %d",msg_num);
OSQPost((OS_Q* )&DATA_Msg,
(void* )pbuf,
(OS_MSG_SIZE)10,
(OS_OPT )OS_OPT_POST_FIFO,
(OS_ERR* )&err);
if(err != OS_ERR_NONE)
{
free(pbuf);
OSTmrStop(&tmr1,OS_OPT_TMR_NONE,0,&err);
tmr1sta = !tmr1sta;
LCD_ShowString(10,150,100,16,16,"TMR1 STOP! ");
}
}
}
总结
重点在于理解消息队列
这种任务间传递消息的思想,同时要注意比较不同任务间通信的方式(如信号量,管道,套接字,共享内存等)。当思想理解之后,API的使用是一件十分简单的事情。记住,一定要自己把代码写一遍
。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)