一、关于Stream Buffer的介绍
Stream Buffer,顾名思义,是一种用于存储和传输流式数据的缓冲区。它能够在不同的组件或任务之间高效地传递和处理连续的数据流,如音频、视频、文件传输等。Stream Buffer通过提供一个环形缓冲区(ring buffer)来存储数据,允许数据连续地写入和读取,从而优化了数据的处理效率。
应用场景
实时操作系统(RTOS):在RTOS中,Stream Buffer是FreeRTOS等系统提供的一种重要机制,用于管理和传输音频、视频等流式数据。通过Stream Buffer,系统可以高效地处理实时数据流,满足对时间敏感的应用需求。
文件I/O :在处理大文件传输时,Stream Buffer可以作为一个中间缓存区,分批处理数据,避免一次性将大量数据读入内存导致的性能问题。例如,在Node.js中,Stream Buffer(或更广泛地说,Stream和Buffer机制)用于高效地处理文件读写操作。
网络通信:在网络通信中,Stream Buffer用于在发送和接收数据时进行缓冲,确保数据的连续性和完整性。例如,TCP/IP协议栈中的缓冲区就起到了类似的作用。
工作原理
Stream Buffer通过以下步骤实现数据的存储和传输:
1.创建Stream Buffer:使用特定的函数(如FreeRTOS中的xStreamBufferCreate)创建一个新的Stream Buffer,并指定其大小和触发级别。
2.发送数据:任务或组件使用发送函数(如xStreamBufferSend)将数据发送到Stream Buffer中。数据被写入环形缓冲区,等待被其他任务或组件读取。
3.接收数据:另一个任务或组件使用接收函数(如xStreamBufferReceive)从Stream Buffer中读取数据。如果缓冲区中有足够的数据,则直接读取;否则,任务可以选择等待直到有足够的数据可用。
优缺点
优点:通过环形缓冲区的设计,Stream Buffer能够高效地处理连续数据流,减少数据处理的延迟和开销。另外Buffer的大小和触发级别可以根据实际需求进行调整,以适应不同的应用场景。在数据传输过程中,Stream Buffer能够确保数据的连续性和完整性,减少数据丢失或损坏的风险。
缺点:Stream Buffer需要占用一定的内存资源来存储数据,如果处理的数据量非常大,可能会消耗较多的内存。在某些复杂的应用场景中,可能需要同时管理多个Stream Buffer,增加了系统的复杂性和维护难度。
二、FreeRTOS提供的主要API
主要API
主要完成平常的操作
1.xStreamBufferCreate(): 创建Stream Buffer
StreamBufferHandle_t xStreamBufferCreate( size_t xBufferSizeBytes, size_t xTriggerLevelBytes );
xBufferSizeBytes: 创建的 Stream Buffer 的总大小,以字节为单位。
xTriggerLevelBytes: 触发级别,指定了当缓冲区中数据量达到多少字节时触发任务挂起等待数据的条件。
2.xStreamBufferSend(): 向Stream Buffer发送数据
size_t xStreamBufferSend( StreamBufferHandle_t xStreamBuffer, const void *pvTxData, size_t xDataLengthBytes, TickType_t xTicksToWait );
xStreamBuffer: 操作的 Stream Buffer 句柄。
pvTxData: 指向待发送数据的指针。
xDataLengthBytes: 待发送数据的长度,以字节为单位。
xTicksToWait: 发送数据时,任务最长等待时间。
3.xStreamBufferReceive(): 从Stream Buffer接收数据
size_t xStreamBufferReceive( StreamBufferHandle_t xStreamBuffer, void *pvRxData, size_t xBufferLengthBytes, TickType_t xTicksToWait );
xStreamBuffer: 操作的 Stream Buffer 句柄。
pvRxData: 接收数据存储的缓冲区指针。
xBufferLengthBytes: 接收数据缓冲区的大小,以字节为单位。
xTicksToWait: 接收数据时,任务最长等待时间。
4.xStreamBufferReset(): 重置Stream Buffer状态
BaseType_t xStreamBufferReset( StreamBufferHandle_t xStreamBuffer );
xStreamBuffer: 要重置的 Stream Buffer 的句柄。
其他API
FreeRTOS 还提供了一些其他的 API 函数用于更复杂的数据交换和管理操作(根据自己的嵌入式系统和软件开发接口标准来选择使用)
1. vStreamBufferDelete:用于删除不再需要的 Stream Buffer,并释放其占用的资源
2. uxStreamBufferSpacesAvailable: 返回当前可用于写入数据的空间大小,以字节为单位
3. xStreamBufferSetTriggerLevel: 设置 Stream Buffer 的触发级别,即缓冲区中数据量达到多少字节时触发任务挂起等待数据的条件
4. xStreamBufferIsEmpty: 检查 Stream Buffer 是否为空,返回一个布尔值
5. xStreamBufferIsFull: 检查 Stream Buffer 是否已满,返回一个布尔值
三、STM32cubeMX配置
1. 选择MCU的型号
2. 使能外部高速晶振
3. 根据自己的情况来配置管脚实现功能
在这里我使用了一个声音传感器和光强传感器,配置使能ADC
4. 时钟树配置
ADC时钟最好不要设置得太高,这里设置的是12MHz:
5. 串口配置异步通信
我使用的是串口1
6. 软件开发接口标准
V1和V2都可以,这里我选择的是V2
7. 创建两个任务用于实现功能
这里我已经提前创建了两个任务(如果软件cubeMX提示对象已经使用的堆空间超过限制了,可以去Config parameters那里修改限制)
四、程序编写
生成keil5工程,并编写代码
1. 在usart.c文件中添加标准输出重定向
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
2. 在adc.c文件中添加光强传感器和声音传感器采样实现代码
enum
{
ADCCHN_NOISY,
ADCCHN_LUX,
ADCCHN_MAX,
};
int adc_sample_lux_noisy(uint32_t *lux, uint32_t *noisy)
{
uint8_t i;
uint32_t timeout = 0xffffff;
for(i=0; i<ADCCHN_MAX; i++)
{
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, timeout);
if(ADCCHN_LUX == i)
{
*lux = HAL_ADC_GetValue(&hadc1);
}
else if(ADCCHN_NOISY == i)
{
*noisy = HAL_ADC_GetValue(&hadc1);
}
HAL_Delay(10);
}
HAL_ADC_Stop(&hadc1);
return 0;
}
3. freertos.c添加头文件
4. 定义流缓冲区大小和触发水平
5. 声明streamBuf句柄
6. 创建流缓冲区
7. 任务1
获取采样数据,并打包成json格式发送到Stream Buffer中去
void StartTask04(void *argument)
{
uint32_t lux, noisy;
char buf[64];
int len = 0;
for(;;)
{
adc_sample_lux_noisy( &lux, &noisy);//adc采样
printf("adc_value is %d,%d\r\n",lux,noisy);
len = snprintf(buf, sizeof(buf), "{\"lux\":\"%d\", \"noisy\":\"%d\"}",lux,noisy);//将采样数据打包成json格式
if(streamBuf != NULL)
{
xStreamBufferSend(streamBuf,buf,len+1,0);//向Stream Buffer发送数据
}
osDelay(1000);
}
}
8. 任务2
从Stream Buffer获取数据,打印输出,并把当次获取的数据从流缓冲区里清除
void StartTask05(void *argument)
{
uint16_t requireBytes=64;
char adcArray[64];
for(;;)
{
uint16_t actualReadBytes = xStreamBufferReceive(streamBuf, adcArray, requireBytes, portMAX_DELAY);//从Stream Buffer接收数据
if (actualReadBytes > 0)
{
printf("actualReadBytes is %d\r\n", actualReadBytes);//实际读到的字节
printf("Received array is %.*s\r\n", actualReadBytes, adcArray);//内容
uint16_t remainingBytes = requireBytes - actualReadBytes;//计算缓冲区剩余长度
memmove(adcArray, adcArray + actualReadBytes, remainingBytes);//将剩余的未读取数据向数组的开头移动
}
else
{
printf("No data received from stream buffer\r\n");
}
osDelay(1000);
}
}
五、结果验证
可以看到,流缓冲区所存储的字节数达到触发水平,就接触读者(任务2)的阻塞状态,通过串口以设定的格式输出获取的光强和声音强度值,改变光强和声音强度环境会引起相应的变化