1、概述
消息队列是线程间通信的一个重要手段之一,本人目前正在开发一款8轴的步进电机驱动器,已经完成了底层的步进电机驱动、RS485接口驱动、CAN总线驱动、USB虚拟CDC串口以及FreeMODBUS协议移植等各方面的工作,后续将进入到APP层面的软件开发。在开发APP的过程中,各个模块之间免不了需要传递各种数据,故而打算采用消息队列的方式来进行各模块之间进行数据传递。
下面是创建一个消息队列,并在二个线程之间传递消息的完整例程,在本例中,我所使用的处理器是STM32F407ZGT6。
2、消息队列创建过程记录
第1步,创建一个基于STM32F407ZG处理器的软件工程。
启动RT-Thread Studio。
点击File->New->RT-Thread Project进入项目配置界面。
根据实际所使用的板卡配置软件工程。
在软件工程配置界面中,设置好项目名称、RT-Thread SDK版本、芯片参数、控制字台使用的串口信息、调试器类型和数所使用的调试端口等信息,然后点击Finish返回。
软件工程创建中,稍等片刻。
软件工程创建完成,点击查看软件工程目录。
在图2-1-7中可见到完整的软件工程目录。
第2步,编译软件工程,确认软件工程是否可以被编译。
点击选中软件工程,按下CTRL+B快捷键进行编译。
编译出现报错信息,此为软件版本升级所带来的BUG,为缺少了RT_WEAK宏定义所导致,需自己手工添加一个宏定义。
打开drinver->board.h文件,添加一条宏定义:
#define RT_WEAK rt_weak
再次编译软件工程。
软件工程已经编译成功。
第3步,下载代码到目标板中运行。
点击图标进入Debug界面。
代码下载成功,已经进入Debug界面,程序已经成功运行到了main()函数的入口断点处。
点击图标全速运行代码。
PC端启动Puttty工具软件,选中目标板的串口1在PC端虚拟的USB串口号,点击Open按钮进入下一步界面。
在出现的界面中,可以见到目标板发送的LOG信息。
至此软件工程已经可以正常工作,可以做下一步的编程测试了。
第4步,编写消息队列测试代码。
在application目录下创建一个新的文件usr_mq.c。
输入以下的代码:
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2024-01-07 hubo the first version
*/
/*
* 程序清单:消息队列例程
*
* 这个程序会创建2个动态线程,一个线程会从消息队列中收取消息;一个线程会定时发消息
*/
#include <rtthread.h>
#define THREAD_PRIORITY 25
#define THREAD_TIMESLICE 5
/* 消息队列控制块 */
static struct rt_messagequeue mq;
/* 消息队列中用到的放置消息的内存池 */
static rt_uint8_t msg_pool[2048];
typedef struct __TypeDefMessage{
rt_uint8_t msg_id;
rt_uint16_t msg_info[16];
}TypeDefMessage;
rt_align(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
/* 线程1入口函数 */
static void thread1_entry(void *parameter)
{
rt_ssize_t rev_len;
TypeDefMessage msg;
rt_uint8_t cnt = 0;
rt_uint16_t i;
while (1)
{
/* 从消息队列中接收消息 , 并返回接收消息的长度*/
rev_len = rt_mq_recv(&mq, &msg, sizeof(msg), RT_WAITING_FOREVER);
if ( rev_len == sizeof(msg) )
{
rt_kprintf("thread1: received msg_id=%d\r\n", msg.msg_id);
for( i = 0 ; i < 16 ; i ++ )
{
rt_kprintf("thread1: received msg_info[%d]=%d\r\n" ,i,msg.msg_info[i]);
}
if (cnt == 19)
{
break;
}
}
/* 延时50ms */
cnt++;
rt_thread_mdelay(300);
}
rt_kprintf("thread1: detach mq \r\n");
rt_mq_detach(&mq);
}
rt_align(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
/* 线程2入口 */
static void thread2_entry(void *parameter)
{
int result, i;
TypeDefMessage msg;
rt_uint8_t cnt = 0;
msg.msg_id = 50;
for( i = 0 ; i < 16 ; i ++ )
{
msg.msg_info[i] = i;
}
while (1)
{
if (cnt >= 20)/* 发送20次消息之后退出 */
{
rt_kprintf("message queue stop send, thread2 quit!\r\n");
break;
}
else
{
/* 发送消息到消息队列中 */
result = rt_mq_send(&mq, &msg, sizeof(msg));
if (result != RT_EOK)
{
rt_kprintf("rt_mq_send ERR\n");
}
rt_kprintf("thread2: sent messages:%d\r\n", msg.msg_id);
}
msg.msg_id++;
cnt++;
/* 延时5ms */
rt_thread_mdelay(500);
}
}
/* 消息队列示例的初始化 */
int msgq_sample(void)
{
rt_err_t result;
/* 初始化消息队列 */
result = rt_mq_init(&mq, "mqt", &msg_pool[0], /* 内存池指向msg_pool */
sizeof(TypeDefMessage), /* 每个消息的大小*/
sizeof(msg_pool), /* 内存池的大小是msg_pool的大小 */
RT_IPC_FLAG_FIFO); /* 如果有多个线程等待,按照先来先得到的方法分配消息 */
if (result != RT_EOK)
{
rt_kprintf("init message queue failed.\n");
return -1;
}
result = rt_thread_init(&thread1, "thread1", thread1_entry,
RT_NULL, &thread1_stack[0], sizeof(thread1_stack),
THREAD_PRIORITY, THREAD_TIMESLICE);
if (result != RT_EOK)
{
rt_kprintf("Init thread1 failed.\n");
return -1;
}
result = rt_thread_startup(&thread1);
if (result != RT_EOK)
{
rt_kprintf("Start thread1 failed.\n");
return -1;
}
result = rt_thread_init(&thread2, "thread2", thread2_entry,
RT_NULL, &thread2_stack[0], sizeof(thread2_stack),
THREAD_PRIORITY, THREAD_TIMESLICE);
if (result != RT_EOK)
{
rt_kprintf("Init thread2 failed.\n");
return -1;
}
result = rt_thread_startup(&thread2);
if (result != RT_EOK)
{
rt_kprintf("Start thread2 failed.\n");
return -1;
}
return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(msgq_sample, msgq sample);
编译下载运行一下,看看结果如何。
程序运行结果符合预期。
本次消息队列的格式为一个结构体,格式定义如下:
typedef struct __TypeDefMessage{
rt_uint8_t msg_id;
rt_uint16_t msg_info[16];
}TypeDefMessage;
采用结构体形式的消息队列,可以定义任何形式的结构体,极大地方便了消息传递的灵活性。
3、程序调试中遇到的问题
(1)ALIGN宏编译报错
在本次消息发送的实验的过程中,发现了以下的版本兼容问题:
官方的网站的参考例程中使用了宏:
ALIGN(RT_ALIGN_SIZE)ALIGN(RT_ALIGN_SIZE)
但在RT-Thread 5.0.2的SDK中,没有这个宏,需要使用下面这个宏替换:
rt_align(RT_ALIGN_SIZE)
(2)rt_mq_recv函数返回值类型与老版本不同
在老版本中,rt_mq_recv函数返回的值为RT_EOK。但在RT-Thread 5.0.2 SDK中的返回值为接收到的消息的长度值。
如果使用老版本的消息发送示范代码参考代码在5.0.2中运行,将会导致程序无法按照预想的流程运行,这一点需要引起注意。