在ESP-IDF环境中如何进行多文件中的数据流转-FreeRTOS实时操作系统_流缓存区“xMessageBuffer”

时间:2024-10-25 07:42:59

一、建立三个源文件和对应的头文件

建立文件名,如图所示

图 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。大家使用的时候一定要注意区分。

为什么使用不同的方法

  1. 数组
  • 数组的大小是固定的,并且包含了所有元素的总大小。使用 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.
 }
}

我的程序:
 

参数解释

  1. 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)循环

  1. 任务的持续性
    无限循环:while (1) 循环确保任务不会退出。一旦任务函数返回,FreeRTOS 会自动删除该任务并释放其资源。如果任务需要持续运行以执行某些操作(如周期性地发送或接收数据、监控传感器等),则必须使用无限循环。
    避免任务结束:如果任务函数没有无限循环并且直接返回,任务将被删除,导致该任务的功能无法继续执行。
  2. 任务的状态管理
    阻塞和等待:在 while (1) 循环中,任务可以执行一些操作,然后进入阻塞状态(例如通过 vTaskDelay 或其他阻塞 API)。这样可以有效地管理 CPU 资源,避免任务一直占用 CPU 时间。
    事件驱动:任务可以在 while (1) 循环中等待特定的事件(例如消息队列中的消息、信号量等),并在事件发生时进行处理。
  3. 任务调度
    抢占式调度: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