一、建立三个源文件和对应的头文件
建立文件名,如图所示
图 1-1
二、包含相应的头文件
main.h
图 2-1
mess_send.h
mess_rece.h和这个中类似,不明白的大家看我最后面的源码分享
图2-2
三、声明消息缓存区的句柄
大家注意,在main.c中定义的是全局变量图3-1所示
mess_send.c和mess_rece.c则定义的是外部变量,如图3-2所示
图 3-1
图3-2
四、xMessageBufferCreate函数
xMessageBufferCreate:此函数的作用是为句柄创造一个消息缓存区,用来存储在不同任务之间流转的数据,需要在流转任务执行之间进行创建。
configASSERT(xMessageBuffer != NULL);
此函数是用来确认是否创建成功了,是否成功初始化了资源
xMessageBuffer = xMessageBufferCreate(1024); // 缓冲区大小为100字节
configASSERT(xMessageBuffer != NULL);
图 4-1
五、xMessageBufferSend发送函数
注意图 5-1中所示的方框,这里要用strlen,千万注意,用错了,会有发送错误的可能
图 5-1
官方示例程序:
大家注意看,当发送的信息是数组形式的时候,用的sizeof;发送字符串的时候用的是strlen。大家使用的时候一定要注意区分。
为什么使用不同的方法
- 数组:
- 数组的大小是固定的,并且包含了所有元素的总大小。使用
sizeof
可以确保你发送整个数组的内容。 - 如果数组不是以空字符结尾的,使用
strlen
会得到错误的结果,因为它会一直读取直到遇到第一个空字符,可能会导致越界访问或未定义行为。
2.字符串:
- 字符串是以空字符
\0
结尾的字符数组。strlen
函数计算的是从字符串开始到第一个空字符之间的字符数,不包括空字符本身。 - 使用
strlen
可以确保只发送字符串的有效内容,而不包括结尾的空字符。这对于消息缓冲区来说是合理的,因为你通常不需要发送多余的空字符。
void vAFunction( MessageBufferHandle_t xMessageBuffer )
{
size_t xBytesSent;
uint8_t ucArrayToSend[] = { 0, 1, 2, 3 };
char *pcStringToSend = "String to send";
const TickType_t x100ms = pdMS_TO_TICKS( 100 );// Send an array to the message buffer, blocking for a maximum of 100ms to// wait for enough space to be available in the message buffer.xBytesSent = xMessageBufferSend( xMessageBuffer, ( void * ) ucArrayToSend, sizeof( ucArrayToSend ), x100ms );if( xBytesSent != sizeof( ucArrayToSend ) ){
// The call to xMessageBufferSend() times out before there was enough
// space in the buffer for the data to be written.}// Send the string to the message buffer. Return immediately if there is// not enough space in the buffer.xBytesSent = xMessageBufferSend( xMessageBuffer, ( void * ) pcStringToSend, strlen( pcStringToSend ), 0 );if( xBytesSent != strlen( pcStringToSend ) ){
// The string could not be added to the message buffer because there was
// not enough free space in the buffer.}
}
我的程序:
参数解释
xMessageBuffer
:
- 类型:
MessageBufferHandle_t
- 描述: 这是消息缓冲区的句柄,由
xMessageBufferCreate
创建并返回。你需要传递这个句柄来指定将数据发送到哪个消息缓冲区。
2.pvTxData
:
- 类型:
const void *
- 描述: 这是一个指向要发送的数据的指针。函数将从这个指针开始复制数据到消息缓冲区中。
3.xDataLengthBytes
:
- 类型:
size_t
- 描述: 这是要发送的数据的长度(以字节为单位)。函数将尝试将
xDataLengthBytes
字节的数据发送到消息缓冲区。如果消息缓冲区没有足够的空间来容纳这些数据,任务可能会被阻塞,直到有足够的空间。
4.xTicksToWait
:
- 类型:
TickType_t
- 描述: 这是指定任务在消息缓冲区没有足够空间时等待的最大时间。你可以使用以下几种值:
0
: 不等待,立即返回。如果消息缓冲区没有足够的空间,则返回 0。portMAX_DELAY
: 无限等待,直到消息缓冲区有足够空间。- 其他正值: 指定等待的滴答数(ticks)。例如,
pdMS_TO_TICKS(100)
表示等待 100 毫秒。
#include "mess_send.h"const static char *TAG = "mess_send";
extern MessageBufferHandle_t xMessageBuffer;/* */void mess_send()
{while (1){char *jsondata = "da_di_ji_de_xiao_bai";size_t xBytesSent = xMessageBufferSend(xMessageBuffer, (void *)jsondata, strlen(jsondata) ,0); // 包括终止符if ( xBytesSent != strlen(jsondata)) { ESP_LOGE(TAG, "Failed to send message");} else {ESP_LOGI(TAG, "Message sent: %s", jsondata); }vTaskDelay(pdMS_TO_TICKS(1000)); // 适当延时}
}
六、xMessageBufferReceive函数
char received_message [1024];参考我的程序
使用接收缓存区的时候,上来一定要先定义一个接收信息的地方,要不然,你一直接收人家,也不给人家安排地方休息,那不是让人家不得不乱转,那你到时候最后出事了,你就不能怪别人了。
你不仅要安排,你还要安排足够多的空间。
官方示例程序:
void vAFunction( MessageBuffer_t xMessageBuffer )
{
uint8_t ucRxData[ 20 ];
size_t xReceivedBytes;
const TickType_t xBlockTime = pdMS_TO_TICKS( 20 );// Receive the next message from the message buffer. Wait in the Blocked// state (so not using any CPU processing time) for a maximum of 100ms for// a message to become available.xReceivedBytes = xMessageBufferReceive( xMessageBuffer,( void * ) ucRxData,sizeof( ucRxData ),xBlockTime );if( xReceivedBytes > 0 ){
// A ucRxData contains a message that is xReceivedBytes long. Process
// the message here....}
}
我的程序:
我的程序比较多,因为我加入了一些调试信息,这样运行后可以清楚的看出错误出在哪里
参数解释:
1.xMessageBuffer
:
- 类型:
MessageBufferHandle_t
- 描述: 这是消息缓冲区的句柄,由
xMessageBufferCreate
创建并返回。你需要传递这个句柄来指定从哪个消息缓冲区接收数据。
2.pvRxData
:
- 类型:
void *
- 描述: 这是一个指向接收数据的缓冲区的指针。函数将从消息缓冲区中读取的数据复制到这个缓冲区中。你需要确保这个缓冲区足够大,以容纳你希望接收的数据。
3.xBufferLengthBytes
:
- 类型:
size_t
- 描述: 这是
pvRxData
缓冲区的大小(以字节为单位)。函数将尝试从消息缓冲区中读取最多xBufferLengthBytes
字节的数据。如果消息缓冲区中的数据少于xBufferLengthBytes
,则只读取可用的数据量。
4.xTicksToWait
:
- 类型:
TickType_t
- 描述: 这是指定任务在没有数据可接收时等待的最大时间。你可以使用以下几种值:
0
: 不等待,立即返回。如果没有数据可接收,则返回 0。portMAX_DELAY
: 无限等待,直到有数据可接收。- 其他正值: 指定等待的滴答数(ticks)。例如,
pdMS_TO_TICKS(100)
表示等待 100 毫秒。
#include "mess_rece.h"
extern MessageBufferHandle_t xMessageBuffer;
const static char *TAG = "mess_rece";void mess_rece()
{while(1){char received_message [1024];const TickType_t xBlockTime = pdMS_TO_TICKS(100);size_t received_size = xMessageBufferReceive(xMessageBuffer, (void*)received_message,sizeof(received_message)-1, xBlockTime);if(received_size >0 ){received_message[received_size] = '\0'; // 添加终止符// 打印接收到的消息ESP_LOGI(TAG, "mess_rece: %s", received_message);// 检查字符串是否有效if (strlen(received_message) != received_size){ESP_LOGE(TAG, "String length mismatch: expected %zu, got %zu", received_size, strlen(received_message));continue; // 跳过无效的字符串}} else {ESP_LOGW(TAG, "No message received, waiting...");}vTaskDelay(pdMS_TO_TICKS(900)); // 如果没有接收到数据,则等待一段时间后再次尝试}}
七、关于创建任务的一些补充(挺重要的,建议多看)
补充点:为什么创建任务对应的函数内必须有一个while(1)循环
- 任务的持续性
无限循环:while (1) 循环确保任务不会退出。一旦任务函数返回,FreeRTOS 会自动删除该任务并释放其资源。如果任务需要持续运行以执行某些操作(如周期性地发送或接收数据、监控传感器等),则必须使用无限循环。
避免任务结束:如果任务函数没有无限循环并且直接返回,任务将被删除,导致该任务的功能无法继续执行。 - 任务的状态管理
阻塞和等待:在 while (1) 循环中,任务可以执行一些操作,然后进入阻塞状态(例如通过 vTaskDelay 或其他阻塞 API)。这样可以有效地管理 CPU 资源,避免任务一直占用 CPU 时间。
事件驱动:任务可以在 while (1) 循环中等待特定的事件(例如消息队列中的消息、信号量等),并在事件发生时进行处理。 - 任务调度
抢占式调度:FreeRTOS 是一个抢占式实时操作系统。当高优先级任务准备好运行时,当前正在运行的低优先级任务会被中断并切换到高优先级任务。因此,任务函数中的 while (1) 循环允许任务在需要时被调度器暂停和恢复。
时间片轮转:在相同优先级的任务之间,FreeRTOS 可以使用时间片轮转调度。while (1) 循环允许任务在每次时间片结束后重新开始执行。
八、源码分享
main.c
#include "main.h"
MessageBufferHandle_t xMessageBuffer;const static char *TAG = "main";void app_main(void)
{xMessageBuffer = xMessageBufferCreate(1024); // 缓冲区大小为100字节configASSERT(xMessageBuffer != NULL);// 创建任务xTaskCreate(mess_send, // 任务函数"mess_sendTask", // 任务名称4096, // 任务堆栈大小(以字为单位)NULL, // 传递给任务函数的参数1, // 任务优先级NULL); // 任务句柄(不需要)xTaskCreate(mess_rece, // 任务函数"mess_receTask", // 任务名称4096, // 任务堆栈大小(以字为单位)NULL, // 传递给任务函数的参数1, // 任务优先级NULL); // 任务句柄(不需要)// 主任务可以继续执行其他操作while (1) {vTaskDelay(pdMS_TO_TICKS(5000)); // 主任务每 5 秒打印一次ESP_LOGI(TAG, "successful");}
}
main.h
#ifndef __MAIN_H
#define __MAIN_H#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
#include "esp_log.h"
#include "freertos/message_buffer.h" //使用消息缓存区应该包含的头文件#include "stdio.h"
#include "string.h"#include "mess_rece.h" //包含创建的接收和发送的两个文件的头文件
#include "mess_send.h"#endif
mess_send.c
#include "mess_send.h"const static char *TAG = "mess_send";
extern MessageBufferHandle_t xMessageBuffer;/* */void mess_send()
{while (1){char *jsondata = "da_di_ji_de_xiao_bai";size_t xBytesSent = xMessageBufferSend(xMessageBuffer, (void *)jsondata, strlen(jsondata) ,0); // 包括终止符if ( xBytesSent != strlen(jsondata)) { ESP_LOGE(TAG, "Failed to send message");} else {ESP_LOGI(TAG, "Message sent: %s", jsondata); }vTaskDelay(pdMS_TO_TICKS(1000)); // 适当延时}
}
mess_send.h
#ifndef __MESS_SEND_H
#define __MESS_SEND_H#include "main.h"/* 函数声明区 */void mess_send();
#endif
mess_rece.c
#include "mess_rece.h"
extern MessageBufferHandle_t xMessageBuffer;
const static char *TAG = "mess_rece";void mess_rece()
{while(1){char received_message [1024];const TickType_t xBlockTime = pdMS_TO_TICKS(100);size_t received_size = xMessageBufferReceive(xMessageBuffer, (void*)received_message,sizeof(received_message)-1, xBlockTime);if(received_size >0 ){received_message[received_size] = '\0'; // 添加终止符// 打印接收到的消息ESP_LOGI(TAG, "mess_rece: %s", received_message);// 检查字符串是否有效if (strlen(received_message) != received_size){ESP_LOGE(TAG, "String length mismatch: expected %zu, got %zu", received_size, strlen(received_message));continue; // 跳过无效的字符串}} else {ESP_LOGW(TAG, "No message received, waiting...");}vTaskDelay(pdMS_TO_TICKS(900)); // 如果没有接收到数据,则等待一段时间后再次尝试}}
mess_rece.h
#ifndef __MESS_RECE_H
#define __MESS_RECE_H#include "main.h"void mess_rece();
#endif