细说STM32单片机FreeRTOS流缓冲区及其应用实例

目录

一、流缓冲区功能概述

二、流缓冲区操作的相关函数

1、相关函数概述

2、部分函数详解

(1)创建流缓冲区

(2)设置触发水平

(3)写入数据流

(4)读取数据流

(5)流缓冲区状态查询

(6)流缓冲区复位

3、表示发送完成和接收完成的宏

三、流缓冲区使用示例

1、示例功能与CubeMX项目设置

(1)RCC、SYS、Code Generator、USART3、TIM6

(2)定时器TIM3的设置

(3)ADC3_IN6的设置

(4)GPIO

(5)FreeRTOS的设置

(6)NVIC

2、程序功能实现

(1)主程序

(2)FreeRTOS对象初始化

(3)ADC3的中断处理

(4)任务Task_Main的功能

3、运行调试


        流缓冲区(stream buffer)是一种优化的进程间通信机制,专门用于只有一个写入者和一个读取者的场景,能通过流缓冲区写入和读取任意长度的字节数据流。消息缓冲区(message buffer)是基于流缓冲区的,能写入和读取固定长度的离散的消息数据。

一、流缓冲区功能概述

        流缓冲区是一种进程间通信的对象和方法。用户需要先创建一个流缓冲区,然后才可以使用它。使用流缓冲区进行进程间通信的基本原理如下图所示,有一个ISR或任务向流缓冲区写入数据,称为写入者(writer),有一个ISR或任务从流缓冲区读出数据,称为读取者(reader)。

        创建流缓冲区时,用户需要设定其存储容量,例如1024字节。使用流缓冲区时,写入者一次可以写入任意长度的字节数据流,但是不能超过流缓冲区的存储容量。读取者一次可以读出任意长度的字节数据流,字节数据流没有起始符和结束符。使用流缓冲区在进程间传输数据时,使用的是复制数据的方式,即写入者将数据复制到流缓冲区,读取者从流缓冲区复制出数据。流缓冲区就像一个管道,字节数据流在其中流动。

        流缓冲区使用了任务通知技术,因此,调用流缓冲区相关的API函数使任务进入阻塞状态时,会改变任务的通知状态和通知值。

        流缓冲区特别适用于单个写入者、单个读取者的应用场景,例如从一个ISR向一个任务传输数据,或在多核处理器上从一个内核向另一内核传输数据。在使用流缓冲区时假设只有一个写入者,只有一个读取者,如果非要使用多个写入者或多个读取者,那么一定要注意:每个写入者在调用写入流缓冲区的API函数时,代码必须置于一个临界代码段内,且写入阻塞时间必须设置为0。同样的,如果有多个读取者,那么每个读取者在调用读取流缓冲区的API函数时,代码必须置于一个临界代码段内,且读取阻塞时间必须设置为0。

        流缓冲区与队列有点像,但两者是有区别的:队列的数据分为基本的项(item),项的格式是固定的,例如uint32_t类型的项,每次写入或读取一个项;流缓冲区的数据只是字节数据流,写入和读出的数据长度是任意的。

 

二、流缓冲区操作的相关函数

1、相关函数概述

        流缓冲区相关的函数头文件是stream_buffer.h,源程序文件是stream_buffer.c。流缓冲区相关的函数见下表。在CubeMX的FreeRTOS设置中没有任何与流缓冲区相关的设置,在配置文件FreeRTOSConfig.h和FreeRTOS.h中也没有与流缓冲区相关的配置参数。要在程序中使用流缓冲区,只需包含文件stream_buffer.h即可。

分组

函数

功能

创建

删除

复位

xStreamBufferCreate()

创建一个流缓冲区,需设定缓冲区大小和触发水平

xStreamBufferCreateStatic()

创建一个流缓冲区,静态分配内存方式

vStreamBufferDelete()

删除一个流缓冲区

xStreamBufferReset()

复位一个流缓冲区到其初始状态,清空缓冲区。没有任务在阻塞状态下读或写流缓冲区时,流缓冲区才可以被复位

写入

xStreamBufferSend()

向流缓冲区写入一定长度的字节数据

xStreamBufferSendFromISR()

xStreamBufferSend()的ISR版本

读取

xStreamBufferReceive()

从流缓冲区读取一定长度的字节数据流。达到触发水平就会解除阻塞状态,而不一定读取指定个数的字节数据才会退出阻塞状态

xStreamBufferReceiveFromISR()

xStreamBufferReceive()的ISR版本

参数

设置

查询

xStreamBufferSetTriggerLevel()

设置流缓冲区的触发水平

xStreamBufferBytesAvailable()

查询流缓冲区有多少字节数据可以被读取

xStreamBufferSpacesAvailable()

查询流缓冲区还有多少字节的剩余空间

xStreamBufferIsEmpty()

查询一个流缓冲区是否为空,返回值pdTRUE表示缓冲区为空

xStreamBufferIsFull()

查询一个流缓冲区是否满了,返回值pdTRUE表示缓冲区满了

2、部分函数详解

(1)创建流缓冲区

        函数xStreamBufferCreate()以动态分配内存方式创建一个流缓冲区,它是一个宏函数,其原型定义如下:

/**
* \defgroup xStreamBufferCreate xStreamBufferCreate
* \ingroup StreamBufferManagement
*/
#define xStreamBufferCreate( xBufferSizeBytes, xTriggerLevelBytes ) xStreamBufferGenericCreate( xBufferSizeBytes, xTriggerLevelBytes, pdFALSE )

        它调用了函数xStreamBufferGenericCreate(),这个函数既可创建流缓冲区,也可以创建消息缓冲区。其原型定义如下:

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

StreamBufferHandle_t xStreamBufferGenericCreate( size_t xBufferSizeBytes, size_t xTriggerLevelBytes, BaseType_t xIsMessageBuffer )
{
}
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

        其中,参数xBufferSizeBytes是缓冲区的大小,单位是字节;参数xTriggerLevelBytes是触发水平(trigger level),单位是字节;参数xIsMessageBuffer表示创建的是否是消息缓冲区,如果传递的这个参数值是pdFALSE表示创建的是流缓冲区

        函数xStreamBufferCreate()的返回值是一个StreamBufferHandle_t类型的对象,也就是所创建的流缓冲区的对象指针。

        创建流缓冲区时需要指定缓冲区大小和触发水平,创建流缓冲区的示例代码如下:

streamBufferHandle_t streamBuf = xStreamBuffercreate(20,5);

        缓冲区大小就是缓冲区总的存储空间大小。触发水平是指读取者在阻塞状态下读取流缓冲区时,为解除读取者的阻塞状态,流缓冲区内的数据所必须达到的字节数。例如,上面的代码中设置触发水平为5,那么一个任务在读取流缓冲区时,当流缓冲区里的数据达到5字节时,就会解除读取者的阻塞状态。

        触发水平可以设置为1,但是不能超过流缓冲区大小。若设置为0,等效于1。在创建流缓冲区后,还可以用函数xStreamBufferSetTriggerLevel()修改触发水平大小。

(2)设置触发水平

        函数xStreamBufferSetTriggerLevel()用于重新设置一个流缓冲区的触发水平,其原型定义如下:

/**
* \defgroup xStreamBufferSetTriggerLevel xStreamBufferSetTriggerLevel
* \ingroup StreamBufferManagement
*/
BaseType_t xStreamBufferSetTriggerLevel( StreamBufferHandle_t xStreamBuffer, size_t xTriggerLevel ) PRIVILEGED_FUNCTION;

        其中,xStreamBuffer是所操作的流缓冲区对象指针,xTriggerLevel是设置的触发水平大小。

        如果传递的参数xTriggerLevel小于或等于流缓冲区长度,触发水平被更新,函数返回pdTRUE,否则返回pdFALSE。

(3)写入数据流

        在任务里向流缓冲区写入数据使用函数xStreamBufferSend(),其原型定义如下:

/**
* \defgroup xStreamBufferSend xStreamBufferSend
* \ingroup StreamBufferManagement
*/
size_t xStreamBufferSend( StreamBufferHandle_t xStreamBuffer,
const void *pvTxData,
size_t xDataLengthBytes,
TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;

        其中,xStreamBuffer是流缓冲区句柄,pvTxData是需要写入数据的缓冲区指针,xDataLengthBytes是需要写入数据的字节数,xTicksToWait是用节拍数表示的超时等待时间。

        向流缓冲区写入数据使用的是数据复制的方式,也就是将指针pvTxData表示的缓冲区内长度为xDataLengthBytes个字节的数据复制到流缓冲区。如果流缓冲区剩余存储空间不够容纳这次要写入的数据量,任务就进入阻塞状态并最多等待xTicksToWait个节拍。如果xTicksToWait设置为0就是不等待,如果是portMAX_DELAY就是一直等待。

        函数的返回值是实际写入流缓冲区的数据字节数。如果函数因等待超时而退出,它仍然会向流缓冲区写入尽量多的数据,函数的返回值就是实际写入的字节数。

        如果只有一个写入者向流缓冲区写入数据,可以安全地使用函数xStreamBufferSend(),如果有多个写入者向一个流缓冲区写入数据,调用xStreamBufferSend()的代码必须置于临界代码段内,也就是用taskENTER_CRITICAL()和taskEXIT_CRITICAL()界定的代码段,并且超时等待时间必须设置为0。

        在ISR中向流缓冲区写入数据的函数是xStreamBufferSendFromISR(),其原型定义如下:

/**
* \defgroup xStreamBufferSendFromISR xStreamBufferSendFromISR
* \ingroup StreamBufferManagement
*/
size_t xStreamBufferSendFromISR( StreamBufferHandle_t xStreamBuffer,
const void *pvTxData,
size_t xDataLengthBytes,
BaseType_t * const pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;

        其中的参数pxHigherPriorityTaskWoken是一个数据指针,返回pdTRUE或pdFALSE,表示退出ISR时是否要进行任务切换申请,其用法与其他函数里的同名参数一样。

(4)读取数据流

        在任务里从流缓冲区读取数据使用的函数是xStreamBufferReceive(),其原型定义如下:

/**
* \defgroup xStreamBufferReceive xStreamBufferReceive
* \ingroup StreamBufferManagement
*/
size_t xStreamBufferReceive( StreamBufferHandle_t xStreamBuffer,
void *pvRxData,
size_t xBufferLengthBytes,
TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;

        其中,xStreamBuffer是流缓冲区句柄,pvRxData是读出数据保存的缓冲区指针,xBufferLengthBytes是准备读出的数据字节数,xTicksToWait是用节拍数表示的等待时间。

        从流缓冲区读出数据使用的是数据复制的方式,也就是说,从流缓冲区中复制出xBufferLengthBytes个字节的数据并保存到指针pvRxData表示的缓冲区里。如果流缓冲区里没有数据,或者数据长度不到触发水平所设置的字节数,任务就进入阻塞状态并等待。xTicksToWait设置为0表示不等待,设置为portMAX_DELAY表示一直等待

        函数的返回值是实际读取数据的字节数。函数xStreamBufferReceive()因等待超时而退出,它仍然可以读出一些数据,只是实际数据长度小于所设置的xBufferLengthBytes个字节。

        如果程序中只有一个读取者从流缓冲区读出数据,可以安全地使用函数xStreamBufferReceive(),如果有多个读取者,则调用xStreamBufferReceive()的代码必须置于临界代码段内,并且等待时间必须设置为0。

        在ISR中读取流缓冲区数据的函数是xStreamBufferReceiveFromISR(),其原型定义如下:

/**
* \defgroup xStreamBufferReceiveFromISR xStreamBufferReceiveFromISR
* \ingroup StreamBufferManagement
*/
size_t xStreamBufferReceiveFromISR( StreamBufferHandle_t xStreamBuffer,
void *pvRxData,
size_t xBufferLengthBytes,
BaseType_t * const pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;

        其中的参数pxHigherPriorityTaskWoken是一个数据指针,返回pdTRUE或pdFALSE,表示退出ISR时是否要进行任务切换申请。

(5)流缓冲区状态查询

        以下几个函数可以用于查询流缓冲区的状态或参数,它们只需使用流缓冲区句柄作为函数的输入参数。

  • xStreamBufferBytesAvailable(),返回值类型uint32_t,查询一个流缓冲区当前存储数据的字节数
  • xStreamBufferSpacesAvailable(),返回值类型uint32_t,查询一个流缓冲区剩余的存储空间字节数
  • xStreamBufferIsEmpty(),查询一个流缓冲区当前是否为空,若返回值为pdTRUE表示流缓冲区没有任何可以被读取的数据。
  • xStreamBufferIsFull(),查询一个流缓冲区是否已经存满了,若返回值为pdTRUE,表示已经存满了。

(6)流缓冲区复位

        可以使用函数xStreamBufferReset()使一个流缓冲区复位,其原型定义如下:

BaseType_t xStreamBufferReset( StreamBufferHandle_t xStreamBuffer );

         其中,xStreamBuffer是要复位的流缓冲区句柄。函数返回值为pdTRUE或pdFALSE。

        复位一个流缓冲区会使其恢复到初始状态,清空缓冲区内的数据。只有当没有任务在阻塞状态下读写流缓冲区时,流缓冲区才可以被复位,否则流缓冲区不能被复位,且函数xStreamBufferReset()的返回值为pdFALSE。

3、表示发送完成和接收完成的宏

        在文件stream_buffer.h中还有函数xStreamBufferSendCompletedFromISR()和xStreamBufferReceiveCompletedFromISR(),这两个函数用于高级用途。

        在文件stream_buffer.c中有一个宏函数sbSEND_COMPLETED(pxStreamBuffer),参数pxStreamBuffer是流缓冲区句柄。在完成数据写入流缓冲区操作时,这个函数由FreeRTOS的API函数内部调用。这个函数可以用于检查是否有任务在阻塞状态等待流缓冲区的数据,如果有,就解除任务的阻塞状态,使其可以读取流缓冲区的数据。

        一般情况下,不用去管sbSEND_COMPLETED()这个函数,它是由API函数内部调用的,但是在某些情况下,可以在文件FreeRTOSConfig.h中重新定义这个宏,使其应用于特殊场合。例如,在一个多核处理器上使用流缓冲区在两个内核之间传输数据。当内核A向流缓冲区写完数据后会调用sbSEND_COMPLETED(),就可以在文件FreeRTOSConfig.h中重新定义这个宏,在其中产生内核B的某个中断。内核B在此中断的ISR里调用函数xStreamBufferSendCompletedFromISR()检查是否有任务处于阻塞状态等待从流缓冲区读取数据,如果有,就解除任务的阻塞状态,使其可以及时读取流缓冲区的数据。

        宏函数sbRECEIVE_COMPLETED()与sbSEND_COMPLETED()对应,用于接收完成时进行处理。在多核应用场景下,我们可以为内核A重定义宏sbRECEIVE_COMPLETED(),在这个宏里向内核B产生中断,内核B在此中断里调用函数xStreamBufferReceiveCompletedFromISR()检查是否有等待写入流缓冲区的任务。

三、流缓冲区使用示例

1示例功能与CubeMX项目设置

        本示例用于演示流缓冲区的使用,实例的功能和使用流程如下。

  • 创建一个流缓冲区和一个任务Task_Main。
  • ADC3_IN6在定时器TIM3的触发下进行ADC数据采集,在ADC3的ISR里向流缓冲区写入ADC转换结果数据。
  • 在任务Task_Main里,一次读取流缓冲区内多个数据点的数据,求平均值后显示。
  • 继续使用旺宝红龙开发板STM32F407ZGT6 KIT V1.0。

  • 一些设置可以参考本文作者写的其他文章:

        细说STM32单片机FreeRTOS任务通知及其应用实例-CSDN博客  https://wenchm.blog.csdn.net/article/details/148030468?spm=1011.2415.3001.5331

(1)RCC、SYS、Code Generator、USART3、TIM6

        配置时钟树,将HCLK设置为100MHz,将APB1和APB2定时器时钟频率都设置为50MHz;设置TIM6作为基础时钟源;该部分的其它设置可见参考文章。

(2)定时器TIM3的设置

        设置TIM3,将其时钟源设置为使用内部时钟。将Prescaler设置为49999,使TIM3内部计数器时钟频率为1000Hz;将Counter Period设置为200,定时溢出周期为200ms;将Trigger Event Selection设置为Update Event,使TIM3的更新事件可以作为ADC3的外部触发源。TIM3的中断无须开启。

(3)ADC3_IN6的设置

        ADC3使用IN6通道,独立转换模式,12位精度,数据右对齐,外部触发源选择Timer 3 Trigger Out event。 

 

(4)GPIO

        引用KEYLED的文件,保留2个LED的GPIO引脚设置。

(5)FreeRTOS的设置

        启用FreeRTOS,设置接口为CMSIS_V2,所有“Config”和“Include”参数使用默认值。在FreeRTOS里创建一个任务Task_Main。在CubeMX中不能可视化地创建流缓冲区。

 

(6)NVIC

        开启ADC3的全局中断,并设置中断优先级为5。

2、程序功能实现

(1)主程序

        完成设置后,在CubeMX中生成代码。在CubeIDE里打开项目后,引用KEY_LED驱动程序。添加用户功能代码后,主程序代码如下:

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"
#include "adc.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void MX_FREERTOS_Init(void);

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* MCU Configuration--------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART3_UART_Init();
  MX_ADC3_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
  //Start Menu
  uint8_t startstr[] = "Demo9_1: Using Stream Buffer.\r\n\r\n";
  HAL_UART_Transmit(&huart3,startstr,sizeof(startstr),0xFFFF);

  HAL_ADC_Start_IT(&hadc3);
  HAL_TIM_Base_Start(&htim3);
  /* USER CODE END 2 */

  /* Init scheduler */
  osKernelInitialize();

  /* Call init function for freertos objects (in cmsis_os2.c) */
  MX_FREERTOS_Init();

  /* Start scheduler */
  osKernelStart();

  /* We should never get here as control is now taken by the scheduler */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

        在完成ADC3和TIM3的初始化之后,程序将执行HAL_ADC_Start_IT(&hadc3)以中断方式启动ADC3,执行HAL_TIM_Base_Start(&htim3)启动定时器TIM3,这样ADC3就能在TIM3的触发下周期性地进行ADC转换。

(2)FreeRTOS对象初始化

        自动生成的函数MX_FREERTOS_Init()中只有创建任务的代码,需要用户自行添加代码以创建流缓冲区。请在文件frertos.c中定义常量和流缓冲区句柄变量,在函数MX_FREERTOS_Init()中增加创建流缓冲区的代码。完成后的代码如下:

         自动生成includes,并手动添加私有includes:

/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stream_buffer.h"
#include "usart.h"
#include "keyled.h"
#include <stdio.h>
/* USER CODE END Includes */

         手动添加私有宏定义:

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define  BUFFER_LEN     80			//流缓存区大小,字节数
#define  TRIGGER_LEVEL  20			//触发水平,字节数
/* USER CODE END PD */

         手动添加流缓冲区句柄变量,自动生成任务函数句柄变量:

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
StreamBufferHandle_t  streamBuf;	//流缓冲区句柄变量
/* USER CODE END Variables */
/* Definitions for Task_Main */
osThreadId_t Task_MainHandle;
const osThreadAttr_t Task_Main_attributes = {
  .name = "Task_Main",
  .stack_size = 256 * 4,
  .priority = (osPriority_t) osPriorityNormal,
};

        自动生成函数原型:

/* Private function prototypes -----------------------------------------------*/

void AppTask_Main(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */

        自动RTOS初始化:

/**
  * @brief  FreeRTOS initialization
  * @param  None
  * @retval None
  */
void MX_FREERTOS_Init(void) 
{
  /* Create the thread(s) */
  /* creation of Task_Main */
  Task_MainHandle = osThreadNew(AppTask_Main, NULL, &Task_Main_attributes);

  /* USER CODE BEGIN RTOS_THREADS */
  /* add threads, ... */
  streamBuf=xStreamBufferCreate(BUFFER_LEN, TRIGGER_LEVEL);   //创建流缓存区
}

        上述程序定义了两个宏:BUFFER_LEN用于定义流缓冲区大小,TRIGGER_LEVEL用于定义流缓冲区的触发水平,它们的单位都是字节。

        上述程序还定义了一个StreamBufferHandle_t类型的变量streamBuf,这是流缓冲区句柄变量。我们在函数MX_FREERTOS_Init()最后的代码沙箱段内创建了流缓冲区,创建流缓冲区时,需要传递流缓冲区长度和触发水平两个参数。

(3)ADC3的中断处理

        ADC3以中断方式进行数据转换,在文件stm32f4xx_it.c中自动创建了ADC3中断的ISR。ADC转换完成事件中断的回调函数是HAL_ADC_ConvCpltCallback()。为便于使用流缓冲区句柄变量streamBuf,请直接在文件freertos.c中重新实现这个回调函数。这个回调函数的代码如下:

/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
	if (hadc->Instance != ADC3)
		return;

	uint32_t adc_value=HAL_ADC_GetValue(hadc);	//ADC转换原始数据
	BaseType_t  highTaskWoken=pdFALSE;
	if (streamBuf != NULL)
	{
		xStreamBufferSendFromISR(streamBuf, &adc_value, 4, &highTaskWoken);
		portYIELD_FROM_ISR(highTaskWoken);  	//申请进行任务切换
	}
}

int __io_putchar(int ch)
{
	HAL_UART_Transmit(&huart3,(uint8_t*)&ch,1,0xFFFF);
	return ch;
}
/* USER CODE END Application */

        这个回调函数在ADC3每完成一次转换时执行一次。使用函数HAL_ADC_GetValue()读取ADC3的转换结果,并保存为变量adc_value,转换结果是一个uin32_t类型的数据,也就是4字节;然后,调用函数xStreamBufferSendFromISR()向流缓冲区写入这个变量的数据。写入数据时,用户需要使用待写入数据的地址,数据长度以字节为单位。

(4)任务Task_Main的功能

        在任务Task_Main里读取流缓冲区里的数据(可以一次读取多个数据点),然后求平均值。其任务函数代码如下:

使用函数xStreamBufferReceive()从流缓冲区里读取数据,执行的代码如下:

/* USER CODE BEGIN Header_AppTask_Main */
/**
  * @brief  Function implementing the Task_Main thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_AppTask_Main */
void AppTask_Main(void *argument)
{
	/* USER CODE BEGIN AppTask_Main */
	/* Infinite loop */
	printf("Stream Buffer length= %d\r\n",BUFFER_LEN);
	printf("Trigger Level= %d\r\n",TRIGGER_LEVEL);

	uint16_t requiredBytes=32;  //TRIGGER_LEVEL=20;
	printf("Required bytes= %d\r\n",requiredBytes);

	uint32_t adcArray[10];	    //最多读取10个数据点求平均,40字节

	for(;;)
	{
		uint16_t actualReadBytes=xStreamBufferReceive(streamBuf,  adcArray,
				requiredBytes, portMAX_DELAY);
		LED1_Toggle();
		printf("Actual read bytes= %d\r\n",actualReadBytes);

		uint8_t	 actualItems=actualReadBytes/4;    //实际的数据点个数, 每个数据点4字节
		uint32_t sum=0;
		for( uint8_t i=0; i<actualItems; i++)
			sum += adcArray[i];
		sum= sum/actualItems;                      //计算平均值
		printf("Average ADC Value= %ld\r\n",sum);  //显示平均值
	}
	/* USER CODE END AppTask_Main */
}

         使用函数xStreamBufferReceive()从流缓冲区里读取数据,执行的代码如下:

uint16_t actualReadBytes = xStreamBufferReceive(streamBuf, adcArray, requiredBytes, portMAX_DELAY);

        其中,adcArray是保存读出数据的数组,有10个元素,数组名就是指针;requiredBytes是所要读取数据的字节数。函数实际读取数据的字节数保存在变量actualReadBytes里。

        程序根据实际读出数据的字节数计算数据点个数,一个数据点是uint32_t类型的4字节数据,然后计算实际读取数据点的平均值。

3、运行调试

        构建项目后,我们将其下载到开发板上并运行测试,会发现如下现象。

  • 当requiredBytes≥TRIGGER_LEVEL时,实际读取的字节数就是TRIGGER_LEVEL。这验证了触发水平的作用,即流缓冲区里的数据量达到触发水平时,就解除等待任务的阻塞状态。
  • 当requiredBytes<TRIGGER_LEVEL时,实际读出的字节数是不确定的。如,requiredBytes设置为12,实际读出为8;requiredBytes设置为8或16,实际读出为4。

        所以,一般情况下,调用xStreamBufferReceive()时,用户应该将要读取的字节数设置为等于或大于触发水平,这样才能预测实际读出的字节数。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

wenchm

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

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

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

打赏作者

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

抵扣说明:

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

余额充值