文章目录
一、FreeRTOS消息队列
1.1 创建消息队列
xQueueHandle xQueueCreate( unsigned portBASE_TYPE uxQueueLength, // 队列消息最大个数
unsigned portBASE_TYPE uxItemSize ); // 单个消息最大字节
uxQueueLength 队列能够存储的最大单元数目,即队列深度。
uxItemSize 队列中数据单元的长度,以字节为单位。
返回值
NULL 表示没有足够的堆空间分配给队列而导致创建失败。
非 NULL 值表示队列创建成功。此返回值应当保存下来,以作为
操作此队列的句柄。
1.2 发送消息
xQueueSendToBack()
用于将数据发送到队列尾;
xQueueSendToFront()
用于将数据发送到队列首。
xQueueSend()
完全等同于 xQueueSendToBack()
。
portBASE_TYPE xQueueSendToFront( xQueueHandle xQueue, // 队列句柄
const void * pvItemToQueue, // 数据指针
portTickType xTicksToWait ); // 阻塞时间
portBASE_TYPE xQueueSendToBack( xQueueHandle xQueue,
const void * pvItemToQueue,
portTickType xTicksToWait );
切 记 不 要 在 中 断 服 务 例 程 中 调 用 xQueueSendToFront()
或 xQueueSendToBack()
。系统提供中断安全版本的xQueueSendToFrontFromISR()
与xQueueSendToBackFromISR()
用于在中断服务中实现相同的功能。
1.3 接受消息
xQueueReceive()
用于从队列中接收(读取)数据单元。接收到的单元同时会从队列中删除。
xQueuePeek()
也是从从队列中接收数据单元,不同的是并不从队列中删出接收到的单元。xQueuePeek()
从队列首接收到数据后,不会修改队列中的数据,也不会改变数据在队列中的存储序顺。
切记不要在中断服务例程中调用 xQueueRceive()
和xQueuePeek()
。中断安全版本的替代 API 函数 xQueueReceiveFromISR()
。
portBASE_TYPE xQueueReceive( xQueueHandle xQueue, // 队列句柄
const void * pvBuffer, // 数据指针
portTickType xTicksToWait ); // 超时时间
portBASE_TYPE xQueuePeek( xQueueHandle xQueue,
const void * pvBuffer,
portTickType xTicksToWait );
1.4 查询队列信息
uxQueueMessagesWaiting()
用于查询队列中当前有效数据单元个数。
切记不要在中断服务例程中调用 uxQueueMessagesWaiting()
。应当在中断服务中使用其中断安全版本 uxQueueMessagesWaitingFromISR()
。
unsigned portBASE_TYPE uxQueueMessagesWaiting( xQueueHandle xQueue ); // 消息队列句柄
xQueue
被查询队列的句柄。这个句柄即是调用 xQueueCreate()创建该队列时的返回值。返回值
当前队列中保存的数据单元个数。返回 0 表明队列为空。
1.5 大量数据传输-注意事项
如果队列存储的数据单元尺寸较大,那最好是利用队列来传递数据的指针而不是对数据本身在队列上一字节一字节地拷贝进或拷贝出。传递指针无论是在处理速度上还是内存空间利用上都更有效。但是,当你利用队列传递指针时,一定要十分小心地做到以下两点:
-
指针指向的内存空间的所有权必须明确
当任务间通过指针共享内存时,应该从根本上保证所不会有任意两个任务同时
修改共享内存中的数据,或是以其它行为方式使得共享内存数据无效或产生一致性
问题。原则上,共享内存在其指针发送到队列之前,其内容只允许被发送任务访问;
共享内存指针从队列中被读出之后,其内容亦只允许被接收任务访问。 -
指针指向的内存空间必须有效
如果指针指向的内存空间是动态分配的,只应该有一个任务负责对其进行内存
释放。当这段内存空间被释放之后,就不应该有任何一个任务再访问这段空间。
切忌用指针访问任务栈上分配的空间。因为当栈帧发生改变后,栈上的数据将不再
有效。
二、RT-Thread 消息队列
2.1 创建和删除消息队列
- 创建
rt_mq_t rt_mq_create(const char* name, rt_size_t msg_size, // 队列名称, 队列单条消息最大空间
rt_size_t max_msgs, rt_uint8_t flag); // 消息最对容纳个数 消息队列灯等待方式
- 删除
rt_err_t rt_mq_delete(rt_mq_t mq); // 消息队列的句柄
删除消息队列时,如果有线程被挂起在该消息队列等待队列上,则内核先唤醒挂起在该消息等待队列上的所有线程(线程返回值是 - RT_ERROR),然后再释放消息队列使用的内存,最后删除消息队列对象。下表描述了该函数的输入参数与返回值
2.2 初始话和脱离消息队列
- 初始话
初始化静态消息队列对象跟创建消息队列对象类似,只是静态消息队列对象的内存是在系统编译时由编译器分配的,一般放于读数据段或未初始化数据段中。在使用这类静态消息队列对象前,需要进行初始化。初始化消息队列对象的函数接口如下
rt_err_t rt_mq_init(rt_mq_t mq, const char* name, // 消息队列句柄 消息队列名称
void *msgpool, rt_size_t msg_size,
rt_size_t pool_size, rt_uint8_t flag);
初始化消息队列时,该接口需要用户已经申请获得的消息队列对象的句柄(即指向消息队列对象控制块的指针)、消息队列名、消息缓冲区指针、消息大小以及消息队列缓冲区大小。如下图所示,消息队列初始化后所有消息都挂在空闲消息链表上,消息队列为空。下表描述了该函数的输入参数与返回值
-
- 脱离
脱离消息队列将使消息队列对象被从内核对象管理器中脱离。脱离消息队列使用下面的接口:
rt_err_t rt_mq_detach(rt_mq_t mq);
使用该函数接口后,内核先唤醒所有挂在该消息等待队列对象上的线程(线程返回值是 -RT_ERROR),然后将该消息队列对象从内核对象管理器中脱离。下表描述了该函数的输入参数与返回值
2.3 发送消息
2.3.1 直接发送消息
线程或者中断服务程序都可以给消息队列发送消息。当发送消息时,消息队列对象先从空闲消息链表上取下一个空闲消息块,把线程或者中断服务程序发送的消息内容复制到消息块上,然后把该消息块挂到消息队列的尾部。当且仅当空闲消息链表上有可用的空闲消息块时,发送者才能成功发送消息;当空闲消息链表上无可用消息块,说明消息队列已满,此时,发送消息的的线程或者中断程序会收到一个错误码
(-RT_EFULL)
。发送消息的函数接口如下:
rt_err_t rt_mq_send (rt_mq_t mq, void* buffer, rt_size_t size);
发送消息时,发送者需指定发送的消息队列的对象句柄(即指向消息队列控制块的指针),并且指定发送的消息内容以及消息大小。如下图所示,在发送一个普通消息之后,空闲消息链表上的队首消息被转移到了消息队列尾。下表描述了该函数的输入参数与返回值:
2.3.2 延迟发送消息
rt_err_t rt_mq_send_wait(rt_mq_t mq,
const void *buffer,
rt_size_t size,
rt_int32_t timeout);
如果消息队列已经满了,那么发送线程将根据设定的 timeout 参数进行等待。如果设置的超时时间到达依然没有空出空间,这时发送线程将被
唤醒并返回错误码。下表描述了该函数的输入参数与返回值:
2.3.3 发送紧急消息
当发送紧急消息时,从空闲消息链表上取下来的消息块不是挂到消息队列的队尾,而是挂到队首,这样,接收者就能够优先接收到紧急消息,从而
及时进行消息处理。发送紧急消息的函数接口如下:
rt_err_t rt_mq_urgent(rt_mq_t mq, void* buffer, rt_size_t size);
2.4 接收消息
当消息队列中有消息时,接收者才能接收消息,否则接收者会根据超时时间设置,或挂起在消息队列的等待线程队列上,或直接返回。接收消息函数接口如下:
rt_err_t rt_mq_recv (rt_mq_t mq, void* buffer,
rt_size_t size, rt_int32_t timeout);
接收消息时,接收者需指定存储消息的消息队列对象句柄,并且指定一个内存缓冲区,接收到的消息内容将被复制到该缓冲区里。此外,还需指定未能及时取到消息时的超时时间。如下图所示,接收一个消息后消息队列上的队首消息被转移到了空闲消息链表的尾部。下表描述了该函数的输入参数与返回值:
3. 消息队列的使用场景
消息队列可以应用于发送不定长消息的场合,包括线程与线程间的消息交换,以及中断服务例程中给线程发送消息(中断服务例程不能接收消息)。下面分发送消息和同步消息两部分来介绍消息队列的使用。
- 发送消息
a task通过消息队列给b task发送一个结构体,结构体里面包含数据信息,这样 a 和 b task就可以进行通信和交换数据了、
- 同步消息
a task通过消息队列给 b task发送结构体,完成数据交互, b task通过邮件简短回复a task 。 可以节省内存消耗。