FreeRTOS入门(07):流缓冲区 & 消息缓冲区

目的

缓冲区是操作系统中常见的一种用于任务间数据传递的机制。这篇文章将对FreeRTOS中相关内容做个介绍。

本文代码测试环境见前面的文章:《FreeRTOS入门(01):基础说明与使用演示》

基础说明

FreeRTOS中的缓冲区是v10.0.0新加入的功能,可能该功能还在调整中,部分内容官方文档和实际的库中有些出入。

缓冲区相当于一片共享的内存,一方面中断和任务中都可以向里面写输入,另一方面中断和任务也可以从里面读数据。这在一些现有的机制不能满足或者大数据传输时比较有用。不过需要注意的是目前FreeRTOS中的缓冲区并不是完全是线程安全的,使用是需要特别注意。

流缓冲区 有一个叫做 触发等级 的参数,只有当向流缓冲区写入数据后,缓冲区中可读数据大于等于触发等级时,才可以从中读取数据,否则将阻塞。但是只要可读取后便可多次读取直到没有数据可读为止。

消息缓冲区 是建立在流缓冲区之上的,但是提供了另外一种机制。每次向消息缓冲区发送的消息都会成为一个消息包。当消息缓冲区中有消息包便可以读取。每次触发读取都是以包为单位读取的。

使用消息缓冲区需要特别注意两点:1、每次向消息缓冲区发送数据时都会额外占用一定空间用于记录这条消息的长度(对于32架构来说将额外占用4字节);读取时以完整的包形式读取,如果接收缓存的长度不足,则一个字节都不会接受,也不会阻塞,会直接返回接收0字节,并且消息缓冲区这条消息还存在,等待下一次接收。

FreeRTOS的缓冲区有 sbSEND_COMPLETED()sbRECEIVE_COMPLETED() 两个宏用来处理发送和接收完成后的额外操作,你可以自行定义这两个宏具体的功能,也可以不管,FreeRTOS默认已经封装了一些基础的操作。

流缓冲区

相关函数

// 创建流缓冲区
// xBufferSizeBytes表示流缓冲区容量(字节)
// xTriggerLevelBytes读取等待字节数(触发等级),如果写0会被重置为1
StreamBufferHandle_t xStreamBufferCreate( size_t xBufferSizeBytes, size_t xTriggerLevelBytes );

// 删除流缓冲区
void vStreamBufferDelete( StreamBufferHandle_t xStreamBuffer );

// 向流缓冲区写数据,如果空间不足无法写入将阻塞直至超时
// pvTxData为要写入的消息首指针
// xDataLengthBytes为要写入的消息长度,写入时会额外占用sizeof(size_t)字节空间,对于32架构来说将额外占用4字节
// 返回写入字节数
size_t xStreamBufferSend( StreamBufferHandle_t xStreamBuffer, const void *pvTxData, size_t xDataLengthBytes, TickType_t xTicksToWait );
// 写数据中断版本,不会阻塞
size_t xStreamBufferSendFromISR( StreamBufferHandle_t xStreamBuffer, const void *pvTxData, size_t xDataLengthBytes, BaseType_t *pxHigherPriorityTaskWoken );

// 从流缓冲区读数据,如果没有数据可读取将阻塞直至超时
// pvRxData为要接收数据的数组首指针
// xBufferLengthBytes接收消息的数组长度
// 返回读取字节数
size_t xStreamBufferReceive( StreamBufferHandle_t xStreamBuffer, void *pvRxData, size_t xBufferLengthBytes, TickType_t xTicksToWait );
// 读数据中断版本,不会阻塞
size_t xStreamBufferReceiveFromISR( StreamBufferHandle_t xStreamBuffer, void *pvRxData, size_t xBufferLengthBytes, BaseType_t *pxHigherPriorityTaskWoken );

// 返回缓冲区可读字节数
size_t xStreamBufferBytesAvailable( StreamBufferHandle_t xStreamBuffer );
// 返回缓冲区可写字节数
size_t xStreamBufferSpacesAvailable( StreamBufferHandle_t xStreamBuffer );
// 查看缓冲区是否为空
BaseType_t xStreamBufferIsEmpty( StreamBufferHandle_t xStreamBuffer );
// 查看缓冲区是否已满
BaseType_t xStreamBufferIsFull( StreamBufferHandle_t xStreamBuffer );

// 设置缓冲区触发等级
BaseType_t xStreamBufferSetTriggerLevel( StreamBufferHandle_t xStreamBuffer, size_t xTriggerLevel );
// 将缓冲区重置为空,只有缓冲区没有在使用时才可设置
BaseType_t xStreamBufferReset( StreamBufferHandle_t xStreamBuffer );

使用演示

#include "debug.h"
#include "FreeRTOS.h"      // 引入头文件
#include "task.h"          // 引入头文件
#include "stream_buffer.h" // 引入头文件

StreamBufferHandle_t xStreamBuffer; // 流缓冲区句柄

void task1(void *pvParameters) {
    uint8_t data[4] = {1, 2, 3, 4};
    while(1) {
        vTaskDelay(500);
        xStreamBufferSend(xStreamBuffer, data, 4, portMAX_DELAY); // 发送数据
    }
}

void task2(void *pvParameters) {
    while(1) {
        uint8_t data[5] = {0};
        size_t len = xStreamBufferReceive(xStreamBuffer, data, 5, portMAX_DELAY); // 等待数据可接收
        printf("%u task2 len: %u  data: %u %u %u %u %u \r\n", xTaskGetTickCount(), len, data[0], data[1], data[2], data[3], data[4]);
    }
}

int main(void) {
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);

    xStreamBuffer = xStreamBufferCreate(128, 5); // 这里设置触发等级为5

    xTaskCreate(task1, "task1", 256, NULL, 5, NULL);
    xTaskCreate(task2, "task2", 256, NULL, 5, NULL);

    vTaskStartScheduler(); // 任务调度,任务将在这里根据情况开始运行,程序将在这里无序循环

    while(1) {} // 程序不会运行到这里
}

在这里插入图片描述

消息缓冲区

相关函数

// 创建消息缓冲区
MessageBufferHandle_t xMessageBufferCreate( size_t xBufferSizeBytes );

// 删除消息缓冲区
void vMessageBufferDelete( MessageBufferHandle_t xMessageBuffer );

// 向消息缓冲区写数据,如果空间不足无法写入将阻塞直至超时
// pvTxData为要写入的消息首指针
// xDataLengthBytes为要写入的消息长度,每次写入时会额外占用sizeof(size_t)字节空间,对于32架构来说将额外占用4字节
size_t xMessageBufferSend( MessageBufferHandle_t xMessageBuffer, const void *pvTxData, size_t xDataLengthBytes, TickType_t xTicksToWait );
// 写数据中断版本,不会阻塞
size_t xMessageBufferSendFromISR( MessageBufferHandle_t xMessageBuffer, const void *pvTxData, size_t xDataLengthBytes, BaseType_t *pxHigherPriorityTaskWoken );

// 从消息冲区读数据,如果没有数据可读取将阻塞直至超时
// pvRxData为要接收数据的数组首指针
// xBufferLengthBytes接收消息的数组长度
// 返回读取字节数
// 从消息缓冲区中读取数据是以一个个消息为单位进行读取的
size_t xMessageBufferReceive( MessageBufferHandle_t xMessageBuffer, void *pvRxData, size_t xBufferLengthBytes, TickType_t xTicksToWait );
// 读数据中断版本,不会阻塞
size_t xMessageBufferReceiveFromISR( MessageBufferHandle_t xMessageBuffer, void *pvRxData, size_t xBufferLengthBytes, BaseType_t *pxHigherPriorityTaskWoken );

// 返回缓冲区可写字节数
size_t xMessageBufferSpacesAvailable( MessageBufferHandle_t xMessageBuffer );
// 查看缓冲区是否为空
BaseType_t xMessageBufferIsEmpty( MessageBufferHandle_t xMessageBuffer );
// 查看缓冲区是否已满
BaseType_t xMessageBufferIsFull( MessageBufferHandle_t xMessageBuffer );

// 将缓冲区重置为空,只有缓冲区没有在使用时才可设置
BaseType_t xMessageBufferReset( MessageBufferHandle_t xMessageBuffer );

使用演示

#include "debug.h"
#include "FreeRTOS.h"       // 引入头文件
#include "task.h"           // 引入头文件
#include "message_buffer.h" // 引入头文件

MessageBufferHandle_t xMessageBuffer; // 消息缓冲区句柄

void task1(void *pvParameters) {
    uint8_t data[5] = {1, 2, 3, 4, 5};
    while(1) {
        vTaskDelay(500);
        xMessageBufferSend(xMessageBuffer, data, 5, portMAX_DELAY); // 发送数据,长度为5
    }
}

void task2(void *pvParameters) {
    uint8_t data[3] = {7, 7, 7};
    while(1) {
        vTaskDelay(1000);
        xMessageBufferSend(xMessageBuffer, data, 3, portMAX_DELAY); // 发送数据,长度为3
    }
}

void task3(void *pvParameters) {
    while(1) {
        uint8_t data[5] = {0};
        size_t len = xMessageBufferReceive(xMessageBuffer, data, 5, portMAX_DELAY); // 等待数据可接收
        printf("%u task3 len: %u  data: %u %u %u %u %u \r\n", xTaskGetTickCount(), len, data[0], data[1], data[2], data[3], data[4]);
    }
}

int main(void) {
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    SystemCoreClockUpdate();
    Delay_Init();
    USART_Printf_Init(115200);

    xMessageBuffer = xMessageBufferCreate(128); // 创建消息缓冲区

    xTaskCreate(task1, "task1", 256, NULL, 5, NULL);
    xTaskCreate(task2, "task2", 256, NULL, 5, NULL);
    xTaskCreate(task3, "task3", 256, NULL, 5, NULL);

    vTaskStartScheduler(); // 任务调度,任务将在这里根据情况开始运行,程序将在这里无序循环

    while(1) {} // 程序不会运行到这里
}

在这里插入图片描述

总结

流缓冲区和消息缓冲区的使用都比较简单,其实就有点像是一个公共的先进先出的队列,这种结构在传输数据流或是不定长数据时是比较常用的。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Naisu Xu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值