RT-Thread记录(七、IPC机制之邮箱、消息队列)_rtthread的消息队列应用(3)

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

如果你需要这些资料,可以戳这里获取

前言

与上篇文章的介绍的信号量、互斥量和事件集,邮箱、消息队列同样为 RT-Thread IPC机制。但是信号量它们属于线程同步机制,并不能在线程之间传递消息,我们本文介绍的 邮箱、消息队列就是实现线程间消息传递的机制。

相对于上一篇文章的内容,线程通讯的学习会相对复杂些,因为涉及到消息的传递,消息在实际项目中的可能存在多种不同的情况,所以 邮箱和消息队列的使用场景和方式是关键,尤其是消息队列。基本上实际项目中的所有消息类型都可以使用消息队列的方式。消息队列应用于串口通信我会单独用一篇博文来说明,本文先做基础介绍和基本示例的讲解。

本 RT-Thread 专栏记录的开发环境:
RT-Thread记录(一、RT-Thread 版本、RT-Thread Studio开发环境 及 配合CubeMX开发快速上手)
RT-Thread记录(二、RT-Thread内核启动流程 — 启动文件和源码分析
RT-Thread 内核篇系列博文链接:
RT-Thread记录(三、RT-Thread 线程操作函数及线程管理与FreeRTOS的比较)
RT-Thread记录(四、RT-Thread 时钟节拍和软件定时器)
RT-Thread记录(五、RT-Thread 临界区保护)
RT-Thread记录(六、IPC机制之信号量、互斥量和事件集)

一、邮箱

RT-Thread 中的邮件是线程、中断服务、定时器向线程发送消息的有效手段(中断和定时器需要非阻塞方式,不能等待发送,也不能接收)。

邮箱中的每一封邮件只能容纳固定的 4 字节内容(32位内核正好可以传递一个指针)。

邮箱特点 RAM空间占用少,效率较高。

RT-Thread 有点类似 FreeRTOS 的任务通知,同样的只能传递4个字节内容。
但是 FreeRTOS 的任务通知是属于任务自己的,每个任务有且只有一个通知,
而 RT-Thread 的邮箱由邮箱控制块统一管理,新建一个邮箱,可以包含多封邮件(每封4个字节)。

1.1 邮箱控制块

老规矩用源码,解释看注释(使用起来也方便复制 ~ ~!)

#ifdef RT\_USING\_MAILBOX
/\*\*
 \* mailbox structure
 \*/
struct rt\_mailbox
{
    struct rt\_ipc\_object parent;             /\*\*< inherit from ipc\_object \*/
    rt\_ubase\_t          \*msg_pool;           /\*\*< 邮箱缓冲区的开始地址 \*/
    rt\_uint16\_t          size;               /\*\*< 邮箱缓冲区的大小 \*/
    rt\_uint16\_t          entry;              /\*\*< 邮箱中邮件的数目 \*/
    rt\_uint16\_t          in_offset;          /\*\*< 邮箱缓冲的入口指针 \*/
    rt\_uint16\_t          out_offset;         /\*\*< 邮箱缓冲的出口指针 \*/
    rt\_list\_t            suspend_sender_thread;   /\*\*< 发送线程的挂起等待队列 \*/
};
typedef struct rt\_mailbox \*rt\_mailbox\_t;
#endif

1.2 邮箱操作

1.2.1 创建和删除

同以前的线程那些一样,动态的方式,先定义一个邮箱结构体的指针变量,接收创建好的句柄。

创建邮箱:

/\*\*
参数的含义:
1、name 邮箱名称
2、size 邮箱容量(就是多少封邮件,4的倍数)
3、flag 邮箱标志,它可以取如下数值: RT\_IPC\_FLAG\_FIFO 或 RT\_IPC\_FLAG\_PRIO
返回值:
RT\_NULL 创建失败
邮箱对象的句柄 创建成功 
 \*/
rt\_mailbox\_t rt\_mb\_create(const char \*name, rt\_size\_t size, rt\_uint8\_t flag)

最后的 flag 和信号量一样建议 RT_IPC_FLAG_PRIO
在这里插入图片描述
删除邮箱:

/\*\*
参数的含义:
mb 邮箱对象的句柄
返回
RT\_EOK 成功
 \*/
rt\_err\_t rt\_mb\_delete(rt\_mailbox\_t mb)

1.2.2 初始化和脱离

静态的方式,先定义一个邮箱结构体,然后对他进行初始化。

这里要注意,还要定义一个数组,用来做邮箱的内存空间,和静态初始化线程一样。

初始化邮箱:

/\*\*
参数含义:
1、mb 邮箱对象的句柄,需要取自定义的结构体地址
2、name 邮箱名称
3、msgpool 缓冲区指针(用户自定义的数组的地址,第一个数组元素的地址)
4、size 邮箱容量(就是数组的大小/4)
5、flag 邮箱标志,它可以取如下数值: RT\_IPC\_FLAG\_FIFO 或 RT\_IPC\_FLAG\_PRIO
返回
RT\_EOK 成功
 \*/
rt\_err\_t rt\_mb\_init(rt\_mailbox\_t mb,
                    const char  \*name,
                    void        \*msgpool,
                    rt\_size\_t    size,
                    rt\_uint8\_t   flag)

脱离邮箱:

/\*\*
参数的含义:
mb 邮箱对象的句柄
返回
RT\_EOK 成功
 \*/
rt\_err\_t rt\_mb\_detach(rt\_mailbox\_t mb)

1.2.3 发送邮件

在 RT-Thread 中发送邮件分为 有无等待方式发送邮件,以及发送紧急邮件。

在我建的工程版本中,并没有发送紧急邮件函数了,这里按照工程源码来说明,就不介绍发送紧急邮件的函数了,在一般的 STM32 应用中,个人认为紧急邮件有没有都没有影响!

无等待方式适用于所有的线程和中断,等待方式不能用于中断中!

无等待发送邮件:

/\*\*
参数:
1、mb 邮箱对象的句柄
2、value 邮件内容
返回
RT\_EOK 发送成功
-RT\_EFULL 邮箱已经满了
看函数原型,其实就是把等待方式发送的时间改成了0
 \*/
rt\_err\_t rt\_mb\_send(rt\_mailbox\_t mb, rt\_ubase\_t value)
{
    return rt\_mb\_send\_wait(mb, value, 0);
}

无等待发送其实就是使用等待方式发送邮件,等待时间为0:。

等待方式发送邮件:

/\*\*
参数:
1、mb 邮箱对象的句柄
2、value 邮件内容
3、timeout 超时时间
返回:
RT\_EOK 发送成功
-RT\_ETIMEOUT 超时
-RT\_ERROR 失败,返回错误
 \*/
rt\_err\_t rt\_mb\_send\_wait(rt\_mailbox\_t mb,
                         rt\_ubase\_t   value,
                         rt\_int32\_t   timeout)

1.2.4 接收邮件

接收邮件时,除了指定接收邮件的邮箱句柄,并指定接收到的邮件存放位置(需要有一个变量来保存接收到的数据)。

/\*\*
参数含义:
1、mb 邮箱对象的句柄,从哪个邮件控制块取邮件
2、value 邮件内容,需要用一个变量保存
3、timeout 超时时间
返回值:
RT\_EOK 接收成功
-RT\_ETIMEOUT 超时
-RT\_ERROR 失败,返回错误
 \*/
rt\_err\_t rt\_mb\_recv(rt\_mailbox\_t mb, rt\_ubase\_t \*value, rt\_int32\_t timeout)

1.3 示例(指针传递)

2个示例,第一个是正常的消息传递,第二个是与邮箱创建个数有关的引导示例。

1.3.1 邮箱消息传递

前面说到过,邮箱中的每一封邮件只能容纳固定的 4 字节内容,但是4字节可以传递指针,我们分别做个简单的演示。

示例中,我们使用两个不同的按键来发送邮件,通过一个事件来接收邮件,并打印收到的邮件内容。

按键key3,发送4字节的内容,按键Key2,发送一个字符串指针:
在这里插入图片描述

邮件创建:
在这里插入图片描述

在接收线程中,我们打印出接收到的数值:
在这里插入图片描述
测试结果,两个按键按下,线程不仅能收到直接传过来的4字节数据,还能通过传递的指针发送一个字符串:
在这里插入图片描述

1.3.2 邮箱个数示例

在上面的例子中,我们开始创建的邮箱大小就一个,我们测试下,如果没有线程接收,是不是就会打印邮箱满的消息,我们把线程接收邮箱代码注释掉,其他还是和前面测试一样:
在这里插入图片描述
我们再来改一下,使用一个按键测试一下这个 size 是字节呢,还是直接是邮件个数,直接看图说明:

在这里插入图片描述
在静态初始化邮件时候,我们需要注意我们开辟的空间大小,需要是4的倍数,我们一般都是用数组除以4直接表示邮箱的size大小,如下:
在这里插入图片描述

RT-Thread 是通过控制块来管理这些IPC机制,在实际测试中,为了加深对某个对象的理解,比如这里的邮箱,可以直接打印出邮箱的参数来查看当前邮箱的状态。学会测试!!!
在这里插入图片描述

二、消息队列

消息队列能够接收来自线程或中断服务例程中不固定长度的消息,并把消息缓存在自己的内存空间中。

消息队列和邮箱的区别是长度并不限定在 4 个字节以内,但是如果如果把消息队列的每条消息的最大字节规定在4个字节以内,那么消息队列就和邮箱一样了。

典型应用,使用串口接收不定长数据(后期会单独有博文介绍消息队列在串口接收上的应用)。

2.1 消息队列控制块

消息队列控制块的这些属性,我们等会用示例来打印出来看,加深一下对这些属性的认识。

#ifdef RT\_USING\_MESSAGEQUEUE
/\*\*
 \* message queue structure
 \*/
struct rt\_messagequeue
{
    struct rt\_ipc\_object parent;                        /\*\*< inherit from ipc\_object \*/

    void                \*msg_pool;                      /\*\*< 消息队列的开始地址 \*/

    rt\_uint16\_t          msg_size;                      /\*\*< 每个消息长度 \*/
    rt\_uint16\_t          max_msgs;                      /\*\*< 最大的消息数量 \*/

    rt\_uint16\_t          entry;                         /\*\*< 已经有的消息数 \*/

    void                \*msg_queue_head;                /\*\*< list head 链表头 \*/
    void                \*msg_queue_tail;                /\*\*< list tail 链表尾\*/
    void                \*msg_queue_free;                /\*\*< 空闲消息链表 \*/

    rt\_list\_t            suspend_sender_thread;         /\*\*< 挂起的发送线程 \*/
};
typedef struct rt\_messagequeue \*rt\_mq\_t;
#endif

2.2 消息队列操作

2.2.1 创建和删除

先定义一个邮箱结构体的指针变量,接收创建好的句柄。

创建消息队列:

/\*\*
参数:
1、name 消息队列的名称
2、msg\_size 消息队列中一条消息的最大长度,单位字节
3、max\_msgs 消息队列的最大个数
4、flag 消息队列采用的等待方式,它可以取如下数值: RT\_IPC\_FLAG\_FIFO 或 RT\_IPC\_FLAG\_PRIO
返回:
RT\_EOK 发送成功
消息队列对象的句柄 成功
RT\_NULL 失败
 \*/
rt\_mq\_t rt\_mq\_create(const char \*name,
                     rt\_size\_t   msg_size,
                     rt\_size\_t   max_msgs,
                     rt\_uint8\_t  flag)

注意!msg_size 单位是字节,在32位系统中 RT-Thread 默认#define RT_ALIGN_SIZE 4 ,所以如果 msg_size 不是4字节对齐,系统会自动补全。

比如用户定义为9,那么系统会自动把消息队列大小设置为 12,定义为1,设置为4。

还有flag的使用,依然得注意一下,和邮箱信号量等一样,注意实时性问题。

删除消息队列:

/\*\*
参数
mq 消息队列对象的句柄
返回
RT\_EOK 成功
 \*/
rt\_err\_t rt\_mq\_delete(rt\_mq\_t mq)

2.2.2 初始化和脱离

静态的方式,先定义一个消息队列结构体,然后对他进行初始化。

初始化消息队列:

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

如果你需要这些资料,可以戳这里获取

2.2.2 初始化和脱离

静态的方式,先定义一个消息队列结构体,然后对他进行初始化。

初始化消息队列:

[外链图片转存中…(img-fCvNc9x2-1715902953676)]
[外链图片转存中…(img-otnemY7D-1715902953676)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

如果你需要这些资料,可以戳这里获取

  • 18
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值