FreeRTOS-消息缓冲区和流缓冲区(STM32Cubemx高效开发教程)

V10.0.0版本开始,Freertos引入了两个新特性:流缓冲区和消息缓冲区。

在这里插入图片描述

一、流缓冲区

(一)概述

流缓冲区是一种进程间通信的对象和方法。用户需要创建一个流缓冲区,然后才可以使用。
在这里插入图片描述

在这里插入图片描述

(二)流缓冲区函数解析

函数所在文件为:stream_buffer.h
在这里插入图片描述

1、创建流缓冲区

在这里插入图片描述
宏定义调用的函数既可以创建流缓冲区也可以创建消息缓冲区

StreamBufferHandle_t xStreamBufferGenericCreate( size_t xBufferSizeBytes, size_t xTriggerLevelBytes, BaseType_t xIsMessageBuffer )
/*
参数1:缓冲区大小-单位字节
参数2:触发水平-单位字节(触发时需要达到的字节数)必须小于缓冲区大小。
参数3:决定是不是消息缓冲区(默认传入pdfalse表示创建的是流缓冲区,pdtrue表示消息缓冲区)

*/

返回值: 流缓冲区的句柄

2、修改流缓冲区触发水平

调用下面函数修改流缓冲区的xTriggerLevelBytes
在这里插入图片描述

BaseType_t xStreamBufferSetTriggerLevel( StreamBufferHandle_t xStreamBuffer, size_t xTriggerLevel )
/*
参数1:操作的流缓冲区
参数2:要设置的触发水平
*/

注意: 如果设置的触发水平小于等于流缓冲区大小,会更新这个数值并且返回pdtrue。反之,返回pdfalse

3、向流缓冲区中写入数据

(1)在任务中向流缓冲区写入数据
在这里插入图片描述
参数1:流缓冲区句柄;参数2:要写入的数据的缓冲区指针;
参数3:需要写入的字节数;参数4:超时时间
返回值:实际写入流缓冲区的字节数

注意: 如果有一个写入者向缓冲区写入数据,可以直接调用上面函数;如果有多个写入者向一个流缓冲区写入数据,需要将调用这个函数的代码放入到临界代码段执行,且超时时间必须设为0。
在这里插入图片描述
(2)在ISR中向流缓冲区中写入数据
在这里插入图片描述

size_t xStreamBufferSendFromISR( StreamBufferHandle_t xStreamBuffer,
								 const void *pvTxData,
								 size_t xDataLengthBytes,
								 BaseType_t * const pxHigherPriorityTaskWoken ) PRIVILEGED_FUNCTION;

注意: 和任务中的写入函数相比少了一个超时参数,但多了一个是否进行上下文切换申请的参数。

4、从流缓冲区中读取数据

(1)任务中读取数据

/*
参数1:句柄;参数2:读出数据保存的缓冲区指针
参数3:准备读出的字节数;参数4:超时等待时间
返回值:实际读取数据的字节数
*/
size_t xStreamBufferReceive( StreamBufferHandle_t xStreamBuffer,
							 void *pvRxData,
							 size_t xBufferLengthBytes,
							 TickType_t xTicksToWait )

注意1: 如果流缓冲区中没有数据或者长度达不到触发水平要读取的数据长度则需要进入阻塞状态进行等待。
注意2: 只有一个读取者从流缓冲区读取数据时可以直接使用此函数,当多个读取者想要从一个流缓冲区读取数据时需要放到临界代码段中,等待时间只能设置为0。

(2)ISR中读取数据

size_t xStreamBufferReceiveFromISR( StreamBufferHandle_t xStreamBuffer,
									void *pvRxData,
									size_t xBufferLengthBytes,
									BaseType_t * const pxHigherPriorityTaskWoken )

5、状态查询函数(传入的参数是句柄)

(1)查询流缓冲区当前存储字节个数

//传入一个句柄就能查询出流缓冲区当前已经存了多少个字节
size_t xStreamBufferBytesAvailable( StreamBufferHandle_t xStreamBuffer )
//返回值类型uint32_t

(2)查询一个流缓冲区剩余的字节数

size_t xStreamBufferSpacesAvailable( StreamBufferHandle_t xStreamBuffer ) 
//返回值类型uint32_t

(3)查询一个流缓冲区当前是否为空

BaseType_t xStreamBufferIsEmpty( StreamBufferHandle_t xStreamBuffer )
//返回值:pdTRUE表示流缓冲区没有任何数据 

(3)查询一个流缓冲区当前是否为满

BaseType_t xStreamBufferIsFull( StreamBufferHandle_t xStreamBuffer )
//返回值:pdTRUE表示已经满了

6、流缓冲区复位函数

BaseType_t xStreamBufferReset( StreamBufferHandle_t xStreamBuffer )

注意: 复位可以使缓冲区恢复到初始状态,清空缓冲区内的数据,只有流缓冲区不在阻塞状态时,才可以复位,否则不能被复位

(三)流缓冲区实验

1、目标要求

(1)创建一个流缓冲区和一个任务task_main
(2)adc1在定时器3的触发下进行数据采集,在adc1的ISR中向流缓冲区写入adc转换后的结果数据
(3)在任务中一次读取流缓冲区中多个数据点的数据,求平均值后在lcd进行数据显示

2、stm32cubemx配置

(1)复制包含LCD、按键、LED的cubemx工程进行重命名
在这里插入图片描述
(2)将四个按键的引脚复位。保留好LCD的引脚配置和LED引脚的配置
在这里插入图片描述
(3)RCC保持不变
在这里插入图片描述
(4)SYS中选择TIM6作为FreeRTOS基础时钟源
在这里插入图片描述
(5)定时器设置
使用定时器3产生一个200ms的定时,不同的定时器挂载到不同的APB总线上,定时器3挂载到APB1总线,最大频率84MHZ=84000KHZ,
在这里插入图片描述
定时器内部时钟频率最大84MHZ,定时器分频系数最大可以设置65535,如果设置84000分频则内部时钟频率为1000HZ,但是超了频率上限不符合要求,可以设置8400-1分频,内部计数器时钟频率分频为10000HZ。

f=1/T,f=1HZ时,T=1s,f=10000HZ时,则T=1/10000s,1s=1000ms,则T=0.1ms,由于需要定时200ms,所以可以设置计数周期为2000-1

启动定时器3,选择内部时钟作为时钟源,预分频系数设置8400-1,计数周期设置2000-1,选择更新事件
在这里插入图片描述
(4)ADC设置
设置定时器1通道5,外部触发源选择定时器3触发,采样事件略微高一些
在这里插入图片描述
使能ADC中断
在这里插入图片描述

(5)启动FREERTOS
选择V2版本,修改默认任务,并设置栈空间256
在这里插入图片描述
随后生成代码

3、代码编写

(1)移植BSP文件(主要是LCD、按键、LED灯的源文件和头文件)并在工程中添加头文件,验证LCD显示是否正常。将BSP文件夹复制到drives文件夹下,并且bulid一下然后去添加lcd头文件的路径到工程中

在这里插入图片描述
添加路径

在这里插入图片描述
在这里插入图片描述
删除前面多余部分,去掉选择框
在这里插入图片描述在这里插入图片描述
路径添加完毕,重新build即可

调用头文件
将main.c里面的部分头文件进行复制,并在main.h文件中粘贴并添加lcd和led的头文件部分
在这里插入图片描述
main.h文件添加头文件,为了在其他文件中无需再添加头文件可以直接调用(很多文件都调用了main.h头文件)
在这里插入图片描述

在main函数中添加:lcd初始化、lcd显示测试、启动adc、启动定时器的代码
在这里插入图片描述

TFTLCD_Init();
LCD_ShowString(10,10+0*20,tftlcd_data.width,tftlcd_data.height,12,"stream_buffer_test");
//启动adc1
HAL_ADC_Start_IT(&hadc1);
//启动定时器3
HAL_TIM_Base_Start(&htim3);

此时编译下载即可验证lcd显示数据

(2)Freertos文件进行编辑进行

宏定义缓冲区长度和触发水平的数值(在函数中进行传参使用)–触发水平务必小于流缓冲区大小,定义流缓冲区变量
在这里插入图片描述

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

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
#define BUFFER_LEN 80
#define TRIGGLE_LEVEL 20
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
StreamBufferHandle_t streamBuffer;
/* USER CODE END PD */

创建流缓冲区(参数1缓冲区大小,参数2触发水平)
在这里插入图片描述

  /* USER CODE BEGIN RTOS_THREADS */
  /* add threads, ... */
  //创建一个流缓冲区,返回值是一个流缓冲区句柄
   streamBuffer=xStreamBufferCreate( BUFFER_LEN, TRIGGLE_LEVEL );
  /* USER CODE END RTOS_THREADS */

创建流缓冲区函数返回值是一个句柄,数据类型可以由下图可知
在这里插入图片描述

adc中断反馈函数中向流缓冲区中写入数据
ADC中断反馈函数代码
(1)判断是否是ADC1发生了中断
(2)定义是否需要上下文切换变量(初始化设置不需要上下文切换)
(3)读取ADC的数值并存放在变量中
(4)判断流缓冲区是否存在(存在的话执行接下来的代码)
(5)发送数据到流缓冲区(使用ISR中写入数据函数)
(6)是否需要进行上下文切换请求的判断

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{

	if(hadc->Instance==ADC1)//判断是adc1
	{
		BaseType_t  HigherPriorityTaskWoken=pdFALSE;//不进行上下文切换(初始化)
		uint32_t adc_value=HAL_ADC_GetValue(hadc);//相当于占用了4个字节,向流缓冲区中发送数据长度也要设为4
		if(streamBuffer!=NULL)//streamBuffer存在也就是没有被删除(防止出现错误进行检测流缓冲区是否存在)
		{
			//发送数据到流缓冲区
			xStreamBufferSendFromISR(streamBuffer,&adc_value,sizeof(uint32_t), &HigherPriorityTaskWoken );
			portYIELD_FROM_ISR(HigherPriorityTaskWoken);//是否需要上下文切换
		}
	}
}

任务中编写数据读取、求平均值、lcd显示等操作

void APPtask_main(void *argument)
{
  /* USER CODE BEGIN APPtask_main */
  /* Infinite loop */

	//参数2void* 指针数据
	uint32_t adc_arr[10];//存储接收的数据
	uint16_t requireBytes=32;//读取的数据长度
	
	//lcd显示数据
	uint8_t temp_str[30];//lcd字符串显示
	sprintf(temp_str,"strema buffer length = %d  ",BUFFER_LEN);//缓冲区长度
	LCD_ShowString(10,10+1*20,tftlcd_data.width,tftlcd_data.height,12,temp_str);
	sprintf(temp_str,"trigger level = %d  ",TRIGGLE_LEVEL);//触发水平
	LCD_ShowString(10,10+2*20,tftlcd_data.width,tftlcd_data.height,12,temp_str);
	sprintf(temp_str,"require bytes  = %d  ",requireBytes);//读取的最大字节数
	LCD_ShowString(10,10+3*20,tftlcd_data.width,tftlcd_data.height,12,temp_str);

	for(;;)
	{

		//读32个字节数据,每4个字节为一个adc值,使用返回的值计算实际读取的字节数求出平均值
		//读取的字节数(32)一定要大于流缓冲区的水平值(20),否则接收到的不是那么准确
		uint16_t actualreadBytes=xStreamBufferReceive( streamBuffer,adc_arr,requireBytes,portMAX_DELAY ) ;
		uint8_t actrualItems=actualreadBytes/4;//实际接收的次数(触发水平就是写入流缓冲区中传输的字节数)
		
		uint32_t sum=0;
		for(uint8_t i=0;i<actrualItems;i++)
		{
		sum+=adc_arr[i];
		}
		sum=sum/actrualItems;//求出平均值
		
		//lcd显示实际读取的自己数和平均值
		sprintf(temp_str,"actural read bytes  = %d   ",actualreadBytes);
		LCD_ShowString(10,10+4*20,tftlcd_data.width,tftlcd_data.height,12,temp_str);
		sprintf(temp_str,"average value  = %d   ",sum);
		LCD_ShowString(10,10+5*20,tftlcd_data.width,tftlcd_data.height,12,temp_str);
		//接收到的数据依次进行显示
		for(int i=0;i<actrualItems;i++)
		{
			sprintf(temp_str,"[%d]=%d ",i,adc_arr[i]);
			LCD_ShowString(10,10+(6+i)*20,tftlcd_data.width,tftlcd_data.height,12,temp_str);
		}
  }
  /* USER CODE END APPtask_main */
}

二、消息缓冲区

(一)消息缓冲区概述

在这里插入图片描述

在这里插入图片描述

(二)消息缓冲区函数解析

函数在message_buffer.h
在这里插入图片描述

1、创建消息缓冲区

#define xMessageBufferCreate( xBufferSizeBytes ) ( MessageBufferHandle_t ) xStreamBufferGenericCreate( xBufferSizeBytes, ( size_t ) 0, pdTRUE )

调用的函数如下:(创建流缓冲区也是此函数,不过此处参数2传输了pdTRUE表明是消息缓冲区)

/*
参数1:传递缓冲区的大小-单位是字节;
参数2:消息缓冲区没有触发水平,所以参数2默认设为0;
参数3:是否为消息缓冲区(pdtrue表示是)
*/
StreamBufferHandle_t xStreamBufferGenericCreate( size_t xBufferSizeBytes, size_t xTriggerLevelBytes, BaseType_t xIsMessageBuffer )

2、删除消息缓冲区函数

//传入一个消息缓冲区句柄
#define vMessageBufferDelete( xMessageBuffer ) vStreamBufferDelete( ( StreamBufferHandle_t ) xMessageBuffer )

3、发送数据到消息缓冲区函数

(1)任务中写入数据到消息缓冲区

#define xMessageBufferSend( xMessageBuffer, pvTxData, xDataLengthBytes, xTicksToWait ) xStreamBufferSend( ( StreamBufferHandle_t ) xMessageBuffer, pvTxData, xDataLengthBytes, xTicksToWait )

实质调用的函数为

/*
参数1:句柄
参数2:准备要写入的缓冲区指针
参数3:消息数据的字节数
返回值:表示实际写入的字节数(不包含头的4字节),超时退出返回0

*/

size_t xStreamBufferSend( StreamBufferHandle_t xStreamBuffer,
						  const void *pvTxData,
						  size_t xDataLengthBytes,
						  TickType_t xTicksToWait )

注意: 流缓冲区如果因为等待超时而退出仍然可能向流缓冲区已经写入了一部分数据,就不会返回0。消息缓冲区写入数据只有两种可能,全程写入成功或写入失败,不会像流缓冲区一样可以写入几个字节数据
(2)ISR版本写入数据
多了最后一个参数判断是否需要上下文切换

#define xMessageBufferSendFromISR( xMessageBuffer, pvTxData, xDataLengthBytes, pxHigherPriorityTaskWoken ) xStreamBufferSendFromISR( ( StreamBufferHandle_t ) xMessageBuffer, pvTxData, xDataLengthBytes, pxHigherPriorityTaskWoken )

调用函数如下:

size_t xStreamBufferSendFromISR( StreamBufferHandle_t xStreamBuffer,
								 const void *pvTxData,
								 size_t xDataLengthBytes,
								 BaseType_t * const pxHigherPriorityTaskWoken )

4、从消息缓冲区读取数据

(1)任务中读取消息缓冲区数据

#define xMessageBufferReceive( xMessageBuffer, pvRxData, xBufferLengthBytes, xTicksToWait ) xStreamBufferReceive( ( StreamBufferHandle_t ) xMessageBuffer, pvRxData, xBufferLengthBytes, xTicksToWait )

调用函数如下:

/*
参数1:句柄
参数2:存储数据的缓冲区指针
参数3:缓冲区长度(能读取的最大字节数)
参数4:读取超时时间
返回值:实际读取到的字节数(不包含头部4字节),等待超时返回0
*/
size_t xStreamBufferReceive( StreamBufferHandle_t xStreamBuffer,
							 void *pvRxData,
							 size_t xBufferLengthBytes,
							 TickType_t xTicksToWait )

(2)ISR中读取消息缓冲区数据

#define xMessageBufferReceiveFromISR( xMessageBuffer, pvRxData, xBufferLengthBytes, pxHigherPriorityTaskWoken ) xStreamBufferReceiveFromISR( ( StreamBufferHandle_t ) xMessageBuffer, pvRxData, xBufferLengthBytes, pxHigherPriorityTaskWoken )

宏调用函数如下:

size_t xStreamBufferReceiveFromISR( StreamBufferHandle_t xStreamBuffer,
									void *pvRxData,
									size_t xBufferLengthBytes,
									BaseType_t * const pxHigherPriorityTaskWoken )

5、状态查询函数

(1)消息缓冲区是否为空

//消息缓冲区是否为空-返回pdtrue表示不含有任何消息
#define xMessageBufferIsEmpty( xMessageBuffer ) xStreamBufferIsEmpty( ( StreamBufferHandle_t ) xMessageBuffer )

(2)消息缓冲区是否已满

//查询消息缓冲区是否已经满了(返回pdtue表示消息缓冲区已满)
#define xMessageBufferIsFull( xMessageBuffer ) xStreamBufferIsFull( ( StreamBufferHandle_t ) xMessageBuffer )

(3)查询消息缓冲区剩余字节数

//两个函数等价
//返回值是一个32位的整型
#define xMessageBufferSpaceAvailable( xMessageBuffer ) xStreamBufferSpacesAvailable( ( StreamBufferHandle_t ) xMessageBuffer )
#define xMessageBufferSpacesAvailable( xMessageBuffer ) xStreamBufferSpacesAvailable( ( StreamBufferHandle_t ) xMessageBuffer ) /* Corrects typo in original macro name. */

在这里插入图片描述

(三)消息缓冲区实验

1、目标要求

(1)创建一个消息缓冲区
(2)RTC的唤醒中断唤醒周期为1s,在RTC唤醒中断里面读取当前的时间,将其转化为字符串后转化为消息写入消息缓冲区(每次写入的消息长度不一样)
(3)创建一个任务,在任务里读取消息缓冲区的数据,将数据在LCD上显示

2、STM32cubemx设置

新建一个文件夹,复制只有LCD的cubemx工程并重命名
在这里插入图片描述
(1)RCC启用外部低速时钟
需要用到RTC,启用外部低速时钟
在这里插入图片描述
(2)Sys选择TIM6作为时钟源
在这里插入图片描述
(3)启用RTC
启用RTC,
在这里插入图片描述
在时钟树上将LSE作为RTC的时钟源
在这里插入图片描述
启用日历,启用周期唤醒功能,二进制数据,设置唤醒周期为1S(wake up count设为0表示1s唤醒一次,如果设为1表示2s唤醒一次)
在这里插入图片描述

使能RTC唤醒中断
在这里插入图片描述
启用freertos选择V2版本
在这里插入图片描述

在NVIC里面将RTC唤醒中断抢占优先级设为5(在ISR函数中要使用API函数)–默认即为5
在这里插入图片描述
将默认任务修改如下
在这里插入图片描述
clock设置
在这里插入图片描述
随后即可生成代码

3、代码编写

将BSP文件夹复制到Drivers文件夹下,并将路径添加到工程中(目的是可以使用lcd显示数据,具体操作流程参考流缓冲区)

main.h添加头文件
在这里插入图片描述
main.c的主函数中添加LCD初始化和显示函数
在这里插入图片描述

  /* USER CODE BEGIN 2 */

   TFTLCD_Init();
   LCD_ShowString(10, 10+0*20, tftlcd_data.width, tftlcd_data.height, 12, "message buffer start");
  /* USER CODE END 2 */

freertos.c中添加消息缓冲区头文件,sprintf格式化函数的头文件和字符串头文件
创建宏定义参数:消息缓冲区大小,和传输的最大值
定义存放消息缓冲区句柄的变量
在这里插入图片描述

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include"message_buffer.h"//消息缓冲区头文件
#include "stdio.h"
#include "string.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define message_buffer_len 50//消息缓冲区大小(创建缓冲区时用的到)
#define  message_max_len 20//临时存储消息的数组大小(在写入消息和读取消息时会用到)
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
	MessageBufferHandle_t message_buffer;//定义一个变量存放创建消息缓冲区函数的返回值--句柄
/* USER CODE END Variables */

创建消息缓冲区
在这里插入图片描述

 /* add threads, ... */
  //创建一个消息缓冲区,传入的参数是消息缓冲区长度
  //返回值是消息句柄
  message_buffer=xMessageBufferCreate( message_buffer_len ) ;//50
  /* USER CODE END RTOS_THREADS */

编写RTC唤醒中断回调函数负责往消息缓冲区中写入数据
(1)在能够获取到时间和日期的前提下进行
(2)将获取到的时间存放到消息数组中,并使用strlen计算实际的字节个数
(3)进一步判断消息缓冲区句柄是否存在(存在的情况下继续下一步)
(4)将消息数组中的数据写入到消息缓冲区中, 并在LCD上显示实际写入了多少字节数据
(5)根据写入消息缓冲区函数的返回值判断是否需要进行上下文切换

/* USER CODE BEGIN Application */
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{

	//读取时间-创建结构体
	RTC_TimeTypeDef sTime;
	RTC_TimeTypeDef sDate;
	if(HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BIN)==HAL_OK)//获取时间成功
	{
		if(HAL_RTC_GetDate(hrtc, &sDate, RTC_FORMAT_BIN)==HAL_OK)//获取日期成功
		{
			uint8_t temp_str[20];//创建存放消息的数组
			sprintf(temp_str,"%d:%d:%d",sTime.Hours,sTime.Minutes,sTime.Seconds);
			
			uint8_t bytesCount=strlen(temp_str);//记录字符串的长度--字节计数
	
			if(message_buffer!=NULL)
			{
				BaseType_t HigherPriorityTaskWoken=pdFALSE;//不进行下文切换
				//向消息缓冲区中写入数据
				//size_t是任意类型,
				//发送到创建的消息缓冲区中(参数1:缓冲区句柄,参数2:数据,参数3:长度+1表示将字符串结束字符也添加进去,参数4:是否进行上下文切换)
				//返回值为实际发送的字节数(不包含消息头4字节数据)
				uint16_t realCount=xMessageBufferSendFromISR(message_buffer, temp_str, bytesCount+1, &HigherPriorityTaskWoken );
	
				//lcd显示数据
				uint8_t str_lcd[20];
				sprintf(str_lcd,"write bytes = %d",realCount);//实际写入的字节数
				LCD_ShowString(10, 10+2*20, tftlcd_data.width, tftlcd_data.height, 12, str_lcd);
	
				portYIELD_FROM_ISR(HigherPriorityTaskWoken);//判断是否需要上下文切换
			}
		}
	}

}
/* USER CODE END Application */

编写任务函数代码

(1)创建数据接收消息缓冲区
(2)读取消息缓冲区域中的数据存放到数据缓冲区数组中
(3)LCD显示实际读取到的字节数
(4)LCD显示消息缓冲区读取到的数据

void AppTask_show(void *argument)
{
  /* USER CODE BEGIN AppTask_show */
	uint8_t RxData[message_max_len];//缓冲区的大小必须大于实际接收的大小,否则会导致无法读取出一条完整的消息
  /* Infinite loop */
	for(;;)
	{
	  uint32_t realReadCount=xMessageBufferReceive( message_buffer, RxData, message_max_len, portMAX_DELAY );

	  //显示读取的字节数以及读取出的字符串
	  uint8_t temp_str[30];
	  sprintf(temp_str,"real read bytes=%d ",realReadCount);//实际读取的消息字节数(不包含消息头4字节)
	  LCD_ShowString(10, 10+3*20, tftlcd_data.width, tftlcd_data.height, 12, temp_str);


	  sprintf(temp_str,"%s       ",RxData);
	  LCD_ShowString(10, 10+4*20, tftlcd_data.width, tftlcd_data.height, 12, temp_str);
	 }
  /* USER CODE END AppTask_show */
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

永栀哇

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

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

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

打赏作者

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

抵扣说明:

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

余额充值