背景
- RT-Thread 的消息队列,用于线程间的通信,命令、数据的传输,非常的方便
- 消息队列,比邮箱,更方便使用,传输较短的不定长的数据,无须动态申请内存
- 消息队列接收需要线程
- 消息队列的发送,可以在中断回调、线程、shell cmd中发送。
消息队列可以做什么
- 线程之间数据与命令的传输,状态机的实现,有了队列缓存,不会丢失操作
- 消息队列,内容可以不定长,可以不是全局变量,消息队列会备份传输的数据
- 消息队列可以传输命令与数据,复杂的数据,可以使用自定义的消息体。
操作实例
#ifndef MAX_PHONE_MSG_MQ_SIZE
#define MAX_PHONE_MSG_MQ_SIZE 32
#endif
/* messagequeue struct */
struct mq_msg
{
rt_uint32_t cmd; /* command or event, msg id */
rt_uint32_t dat; /* mq load data */
rt_uint32_t len; /* mq load buffer length */
char *buf; /* mq load buffer pointer */
};
enum PHONE_MSG_
{
MSG_PHONE_OPEN_DIAL = 0,
MSG_PHONE_INCOMING,
MSG_PHONE_ANSWER,
MSG_PHONE_END_DIAL,
};
static rt_err_t phone_mq_init(void)
{
rt_err_t ret = RT_EOK;
ret = rt_mq_init(&phone_mq, "phone_mq", &phone_msg_buf,
sizeof(struct mq_msg), sizeof(phone_msg_buf), RT_IPC_FLAG_FIFO);
if (ret != RT_EOK)
{
LOG_E("%s : error!", __func__);
return -RT_ERROR;
}
return ret;
}
- 创建消息队列的接收处理线程,接收是堵塞的,需要放在线程
static void phone_task_entry(void *param)
{
struct mq_msg recv_msg = { 0 };
while(1)
{
if (rt_mq_recv(&phone_mq, &recv_msg, sizeof(recv_msg), RT_WAITING_FOREVER) == RT_EOK)
{
switch (recv_msg.cmd)
{
case MSG_PHONE_OPEN_DIAL:
LOG_D("%s: MSG_PHONE_OPEN_DIAL.", __func__);
break;
case MSG_PHONE_INCOMING:
LOG_D("%s: MSG_PHONE_INCOMING.", __func__);
break;
case MSG_PHONE_END_DIAL:
LOG_D("%s: MSG_PHONE_END_DIAL.", __func__);
break;
case MSG_PHONE_ANSWER:
LOG_D("%s: MSG_PHONE_ANSWER.", __func__);
break;
default:
break;
}
}
phone_mq_free(&recv_msg);
}
}
/* 消息队列接收线程初始化 */
int phone_task_init(void)
{
rt_thread_t tid;
phone_mq_init();
tid = rt_thread_create("phone", phone_task_entry, RT_NULL, 2048, 10, 20);
if (tid != RT_NULL)
{
rt_thread_startup(tid);
LOG_D("%s: end.", __func__);
return 0;
}
else
{
LOG_E("%s: error!", __func__);
return -1;
}
}
INIT_APP_EXPORT(phone_task_init);
- 发送消息,这里使用shell命令,发送可以放在线程、中断、回调里面,注意不要在中断中申请内存。
/* 发送消息 */
rt_err_t phone_send_msg(struct mq_msg *msg)
{
if (msg == RT_NULL)
return -RT_ERROR;
return rt_mq_send(&phone_mq, msg, sizeof(struct mq_msg));
}
/* 消息中free掉用户动态申请的内存【按需】 */
static void phone_mq_free(struct mq_msg *msg)
{
if (msg)
{
if (msg->buf)
{
rt_free(msg->buf);
msg->buf = RT_NULL;
}
}
}
/* shell命令,用于验证发送命令 */
void phone_open_dial(void)
{
struct mq_msg msg = { 0 };
msg.cmd = MSG_PHONE_OPEN_DIAL;
phone_send_msg(&msg);
}
void phone_end_dial(void)
{
struct mq_msg msg = { 0 };
msg.cmd = MSG_PHONE_END_DIAL;
phone_send_msg(&msg);
}
void phone_answer_dial(void)
{
struct mq_msg msg = { 0 };
msg.cmd = MSG_PHONE_ANSWER;
phone_send_msg(&msg);
}
MSH_CMD_EXPORT(phone_open_dial, phone_open_dial);
MSH_CMD_EXPORT(phone_end_dial, phone_end_dial);
MSH_CMD_EXPORT(phone_answer_dial, phone_answer_dial);
编译、下载与功能验证
- 使用STM32L4平台,或PC 模拟器
- list_thread查看消息队列接收线程
msh >list_thread
thread pri status sp stack size max used left tick error
-------- --- ------- ---------- ---------- ------ ---------- ---
tshell 20 running 0x00000088 0x00001000 12% 0x00000005 000
phone 10 suspend 0x000000ac 0x00000800 08% 0x00000005 000
tidle0 31 ready 0x00000080 0x00000400 12% 0x00000003 000
main 10 suspend 0x0000008c 0x00000800 13% 0x00000009 000
msh >phone_open_dial
[D/phone] phone_task_entry: MSG_PHONE_OPEN_DIAL.
msh >phone_end_dial
[D/phone] phone_task_entry: MSG_PHONE_END_DIAL.
msh >phone_answer_dial
[D/phone] phone_task_entry: MSG_PHONE_ANSWER.
问题总结
- 消息队列初始化后才能使用,如果使用模拟器器,自动初始化功能可能失效,需要手动初始化。
- rt_mq_recv接收函数,需要判断是否执行成功,== RT_EOK不能省掉,否则接收消息成功,判断失败造成不处理消息。
if (rt_mq_recv(&phone_mq, &recv_msg, sizeof(recv_msg), RT_WAITING_FOREVER) == RT_EOK) /* 【== RT_EOK不能丢,否则无法进入接收,原因是RT_EOK是0 】 */
总结
- 正确使用RT-Thread,解决线程间的数据与命令交换问题,可以用于数据的传输
- 熟悉线程间的通信IPC。