STM32FreeRTOS消息队列(STM32Cube高效开发)

文章目录

  • 一、队列
    • (一)简介
    • (二)FreeRTOS队列特点
      • 1、入队阻塞:队列满了,此时无法继续写入数据
      • 2、出队阻塞:队列为空,此时无法读出数据
      • 3、入队阻塞解除,有多个任务等待时,哪一个会进入就绪态?
      • 4、队列操作
  • 二、队列函数
    • (一)队列的创建和存储
    • (二)向队列写入数据
      • 1、任务中向队列中写入数据函数
      • 2、写入函数原型
      • 3、ISR版本
    • (三)从队列读取数据
      • 1、从队列头部读取消息并删除消息
      • 2、从队列头部读取消息
    • (四)队列操作相关函数
      • 1、xQueueReset()队列复位函数
      • 2、 vQueueDelete()删除队列函数
      • 3、pcQueueGetName()获取队列的名称
      • 4、vQueueSetQueueNumber()为队列设置一个编号
      • 5、uxQueueGetQueueNumber()获取队列的编号
      • 6、uxQueueSpacesAvailable()获取队列剩余空间个数
      • 7、uxQueueMessagesWaiting()获取队列中等待读取的消息个数
      • 8、xQueueIsQueueEmptyFromISR()查询队列是否为空
      • 9、xQueueIsQueueFullFromISR()查询队列是否已满
  • 三、队列使用示例
    • (一)使用消息队列传输字符串和按键值
      • 1、目标
      • 2、STM32cubemx配置
      • 3、代码编写
      • 4、队列的长度和项大小的确定
    • (二)根据不同的按键值LCD上画不同方向的线
      • 1、目标
      • 2、代码

在实际的项目开发中,经常会遇到在任务于任务之间或任务于中断之间需要进行“沟通交流”,即消息传递的过程。在不使用操作系统的情况下,函数与函数,或函数与中断之间的“沟通交流”一般使用一个或多多个全局变量来完成,但是在操作系统中,因为会涉及“资源管理”的问题,比方说读写冲突,因此使用全局变量在任务于任务或任务于中断之间进行消息传递,并不是很好的解决方案。队列读写队列做好了保护,防止多任务同时访问冲突;我们只需要直接调用API函数即可,简单易用。

一、队列

(一)简介

队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递)。任务和ISR统称为进程,任务与任务之间,或任务与ISR之间有时需要进行通讯或同步,称为进程间通信

在这里插入图片描述

在这里插入图片描述
在创建队列时,就要指定队列长度以及队列项目的大小!

(二)FreeRTOS队列特点

在这里插入图片描述

1、入队阻塞:队列满了,此时无法继续写入数据

在这里插入图片描述

2、出队阻塞:队列为空,此时无法读出数据

在这里插入图片描述

3、入队阻塞解除,有多个任务等待时,哪一个会进入就绪态?

  • 优先级最高的任务
  • 如果优先级相同,那等待时间最久的任务会进入就绪态

4、队列操作

队列操作的过程:包括创建队列、往队列中写入消息、从队列中读取消息等操作
(1)创建队列
在这里插入图片描述
创建了一个用于任务 A 与任务 B 之间“沟通交流”的队列,这个队列最大可容纳 5 个队列项目,即队列的长度为 5。刚创建的队列是不包含内容的,因此这个队列为空。

(2)写入队列
在这里插入图片描述
任务 A 将一个私有变量写入队列的尾部。由于在写入队列之前,队列是空的,因此新写入的消息,既是是队列的头部,也是队列的尾部。
在这里插入图片描述
任务 A 改变了私有变量的值,并将新值写入队列。现在队列中包含了队列 A写入的两个值,其中第一个写入的值在队列的头部,而新写入的值在队列的尾部。 这时队列还有 3 个空闲的位置。

(3)读取队列
在这里插入图片描述
任务 B 从队列中读取消息,任务 B 读取的消息是处于队列头部的消息,这是任务 A 第一次往队列中写入的消息。在任务 B 从队列中读取消息后,队列中任务 A 第二次写入的消息,变成了队列的头部,因此下次任务 B 再次读取消息时,将读取到这个消息。此时队列中剩余 4 个空闲的位置。

二、队列函数

使用队列的主要流程:创建队列 ->写队列 -> 读队列。

(一)队列的创建和存储

队列创建时被分配固定个数的存储单元,每个存储单元存储
固定大小的数据,进程间传递的数据就保存在队列的存储单元里
1、xQueueCreate()创建队列
函数xQueueCreate()以动态分配内存的方式创建队列,队列需要用的存储空间由FreeRTOS从堆空间自动分配
在这里插入图片描述

#define xQueueCreate( uxQueueLength, uxItemSize ) 
xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), 
( queueQUEUE_TYPE_BASE ) )
//参数1:队列的长度
//参数2:每个存储单元的大小
//返回值:创建队列的句柄,若返回NULL表示创建失败

函数xQueueGenericCreate()是创建队列、信号量、互斥量等对象的通用函数。函数原型是:

QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, 
									const UBaseType_t uxItemSize, 
									const uint8_t ucQueueType )

//参数1:队列长度,也就是存储单元的个数
//参数2:每个存储单元的字节数
//参数3:表示创建对象的类型
//返回值:表示创建队列的句柄

ucQueueType表示创建对象的类型一般有以下几种:
在这里插入图片描述

通过参数3来决定是创建的类型:创建队列函数
在这里插入图片描述
创建队列官方代码示例:参数1是数据个数,参数2是每个数据的存储空间字节数
在这里插入图片描述

xQueue1 = xQueueCreate( 10, sizeof( uint32_t ) );

创建了一个具有10个存储单元的队列,每个单元占用sizeof(uint32_t)个字节,也就是4个字节。

 struct AMessage
 {
	char ucMessageID;
	char ucData[ 20 ];
 };
// Create a queue capable of containing 10 pointers to AMessage structures.
// These should be passed by pointer as they contain a lot of data.
xQueue2 = xQueueCreate( 10, sizeof( struct AMessage * ) );

创建一个包含10个指向结构体AMessageID的队列,每个队列大小也就是sizeof( struct AMessage * ) 的大小
注意:
在这里插入图片描述

(二)向队列写入数据

一个任务或ISR向队列写入数据称为发送消息。队列是一个共享的存储区域,可以被多个进程写入,也可以被多个进程读取
在这里插入图片描述

1、任务中向队列中写入数据函数

在这里插入图片描述
在这里插入图片描述
可以发现几个写入函数调用的是同一个函数xQueueGenericSend( ),只是指定了不同的写入位置!

2、写入函数原型

BaseType_t xQueueGenericSend( QueueHandle_t xQueue, 
							const void * const pvItemToQueue, 
							TickType_t xTicksToWait, 
							const BaseType_t xCopyPosition )
参数1:队列句柄
参数2:待写入消息
参数3:阻塞超时时间
参数4:表示数据写入队列的位置,有3种常数定义
返回值:pdTRUE表示写入成功,反之失败

写入队列的位置:
在这里插入图片描述
注意:覆写方式写入队列,只有在队列的队列长度为 1 时,才能够使用

//向队列尾部写入数据
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) 
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )

//向队列尾部写入数据
#define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) 
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )

//向队列头部写入数据
#define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) 
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )

函数在队列未满时能正常向队列写入数据,函数返回值为pdTRUE;
如果队列已满,这两个函数不能再向队列写入数据,函数返回值为errQUEUE_FULL;

//以覆写的形式写入数据(队列长度为1的队列)
#define xQueueOverwrite( xQueue, pvItemToQueue ) 
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )

xQueueOverwrite()也可以用于向队列写入数据,但是这个函数只用于队列长度为1的队列,在队列已满时,它会覆盖队列原来的数据。
在这里插入图片描述

3、ISR版本

在这里插入图片描述
只是比任务版本函数多一个是否需要进行上下文切换的申请
例如:

//任务版本
参数1:写入的队列
参数2:要写入的数据
参数3:阻塞超时时间
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) 
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
//ISR版本
//参数1:写入的队列
//参数2:要写入的数据
//参数3:是否进行上下文切换
#define xQueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) 
xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )

(三)从队列读取数据

可以在任务或ISR里读取队列的数据,称为接收消息。总是从队列头读取数据,读出后删除这个单元的数据,后面的数据前移
在这里插入图片描述
在这里插入图片描述

1、从队列头部读取消息并删除消息

如果设置了等待节拍数,但是队列中没有数据,任务会进入阻塞状态等待指定的时间。如果在等待时间内,队列里有了数据就会退出阻塞状态进入就绪状态,随后调度进入运行状态后从队列里读取数据。反之超过了等待时间队列里还是没有数据函数就是返回pdfalse,任务退出阻塞状态,进入就绪状态。

BaseType_t xQueueReceive( QueueHandle_t xQueue, 
						void * const pvBuffer,
						 TickType_t xTicksToWait ) 

//参数1:读取的队列句柄
//参数2:数据缓冲区(存储的位置)
//参数3:阻塞超时时间
//返回值是pdTRUE或pdFALSE
//ISR版本:
BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue, 
								void * const pvBuffer,
								 BaseType_t * const pxHigherPriorityTaskWoken ) 
//参数1:读取的队列句柄
//参数2:数据缓冲区(存储的位置)
//参数3:是否进行上下文切换


官方demo

 // Task to receive from the queue.
 void vADifferentTask( void *pvParameters )
 {
 struct AMessage *pxRxedMessage;

	if( xQueue != 0 )
	{
		// Receive a message on the created queue.  Block for 10 ticks if a
		// message is not immediately available.
		if( xQueueReceive( xQueue, &( pxRxedMessage ), ( TickType_t ) 10 ) )
		{
			// pcRxedMessage now points to the struct AMessage variable posted
			// by vATask.
		}
	}

	// ... Rest of task code.
 }

2、从队列头部读取消息

//任务版本
BaseType_t xQueuePeek( QueueHandle_t xQueue, 
						void * const pvBuffer, 
						TickType_t xTicksToWait ) 
//ISR版本
BaseType_t xQueuePeekFromISR( QueueHandle_t xQueue, 
							void * const pvBuffer ) 


(四)队列操作相关函数

1、xQueueReset()队列复位函数

将队列复位为空的状态,队列内的所有数据都被丢弃

#define xQueueReset( xQueue ) xQueueGenericReset( xQueue, pdFALSE )

2、 vQueueDelete()删除队列函数

删除一个队列,也可以用于删除一个信号量

void vQueueDelete( QueueHandle_t xQueue ) 

3、pcQueueGetName()获取队列的名称

获取队列的名称,也就是创建队列时设置的队列名称字符串

#if( configQUEUE_REGISTRY_SIZE > 0 )
	const char *pcQueueGetName( QueueHandle_t xQueue ) PRIVILEGED_FUNCTION; /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
#endif

4、vQueueSetQueueNumber()为队列设置一个编号

为队列设置一个编号,这个编号由用户设置并使用

void vQueueSetQueueNumber( QueueHandle_t xQueue, UBaseType_t uxQueueNumber )

5、uxQueueGetQueueNumber()获取队列的编号

UBaseType_t uxQueueGetQueueNumber( QueueHandle_t xQueue ) 

6、uxQueueSpacesAvailable()获取队列剩余空间个数

获取队列剩余空间个数,也就是还可以写入的消息个数

UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue ) PRIVILEGED_FUNCTION;

7、uxQueueMessagesWaiting()获取队列中等待读取的消息个数

UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue )

8、xQueueIsQueueEmptyFromISR()查询队列是否为空

查询队列是否为空,返回值为pdTRUE表示队列为空

BaseType_t xQueueIsQueueEmptyFromISR( const QueueHandle_t xQueue ) 

9、xQueueIsQueueFullFromISR()查询队列是否已满

查询队列是否满了,返回值为pdTRUE表示队列满了

BaseType_t xQueueIsQueueFullFromISR( const QueueHandle_t xQueue )

三、队列使用示例

(一)使用消息队列传输字符串和按键值

1、目标

(1)创建三个任务,创建两个队列

(2)任务1:负责按键扫描,根据按下的不同按键值执行不同的写入操作
按键1按下或按键2按下将按键值拷贝到队列Queue_keys中
唤醒按键按下将提前准备好的字符串拷贝到队列Queue_string中
(3)任务2:读取队列Queue_keys中的按键值,并在LCD上显示按下的键值
(4)任务3:队列队列Queue_string中传输的字符串,并在LCD上显示传输的字符串数据

2、STM32cubemx配置

(1)复制已经配置好LCD、和按键引脚的stm32cube工程修改名字
在这里插入图片描述
(2)RCC部分保持原来配置
在这里插入图片描述
(3)SYS选择TIM6作为FreeRTOS基础时钟源
在这里插入图片描述
(3)GPIO引脚是配置好的LCD背光和按键引脚
在这里插入图片描述
(4)启动FreeRTOS并选择V2版本,创建三个任务(动态形式创建)和两个队列
在这里插入图片描述
创建3个任务,设置不同的任务优先级(按键检测任务优先级可以设置最大),占空间大小都设置为128字,动态形式创建3个任务。
第一个任务:实现按键检测,将按键值或字符串写入到相关的队列中
第二个任务:读取队列数据获取按键按下的值,并在lcd上显示数据
第三个任务:读取队列数据获取字符串数据,并在LCD上显示数据
在这里插入图片描述
队列1用于传输按键按下的值,队列长度为10,项的大小是sizeof(uint8_t)个字节。
队列2用于传输字符串数据,队列长度为10,项的大小为sizeof(char*)个字节。
在这里插入图片描述

(5)clock保持原来配置不变
在这里插入图片描述
(6)工程名和存储地址无需配置(就是刚开始打开时的工程名和地址)-无法修改
如果是新建的工程可以随意填写(不要包含中文路径和汉字)
在这里插入图片描述
在这里插入图片描述
生成代码

3、代码编写

(1)复制BSP文件夹到工程目录下,并添加相对应路径,添加头文件实现LCD的数据显示
(2)main.h中添加头文件
在这里插入图片描述

/* USER CODE BEGIN Includes */
#include "cmsis_os.h"
#include "gpio.h"
#include "fsmc.h"
#include"tftlcd.h"
#include "keyled.h"
/* USER CODE END Includes */

(3)main.c文件主函数中LCD初始化和LCD显示测试
在这里插入图片描述
(4)FreeRTOS.c文件添加两个头文件
在这里插入图片描述

/* USER CODE BEGIN Includes */
#include"queue.h"
#include "stdio.h"
/* USER CODE END Includes */

(5)按键扫描任务

  • 主要根据不同的按键值写入不同的数据,并在LCD上进行按键按下提示和写入成功提示
    按键1按下写入数值1
    按键2按下写入数值2
    唤醒按键按下写入"abcdefgh"字符串
void AppTask_keyscan(void *argument)
{
  /* USER CODE BEGIN AppTask_keyscan */
	//按键扫描
	KEYS key_value=KEY_NONE; //无按键按下
	char *str="abcdefgh";   //要发送的字符串
//	str=&buf[0];
	BaseType_t result=pdFALSE; //函数返回值初始化
  /* Infinite loop */
  for(;;)
  {
	  key_value=key_scan(0);//按键扫描

	  if(key_value==KEY_1||key_value==KEY_2)//key1或者key2按下
	  {
		  //参数1:要写入的队列句柄,参数2:是按键值,参数3是一直等待
		  result=xQueueSend( Queue_keysHandle, &key_value, portMAX_DELAY );

		  uint8_t data[20];	  //缓冲数组
		  if(result==pdTRUE)//表明已经成功写入到队列中
		  {
			  sprintf(data,"key%d_write_ok  ",key_value);
			  LCD_ShowString(10, 10+2*20, tftlcd_data.width, tftlcd_data.height, 12, data);
		  }


		  //格式化字符串(key1_press或key2_press格式化存储到字符数组data中)
		  sprintf(data,"key%d_press   ",key_value);
		  //LCD显示哪个按键按下
		  LCD_ShowString(10, 10+1*20, tftlcd_data.width, tftlcd_data.height, 12, data);


	  }
	  else if(key_value==KEY_UP)//唤醒按键按下将字符串写入到Queue_stringHandle队列中
	  {
		  result=xQueueSend( Queue_stringHandle, &str , portMAX_DELAY );
		  LCD_ShowString(10, 10+1*20, tftlcd_data.width, tftlcd_data.height, 12, "key_up_press");
		  if(result==pdTRUE)
		  {
			  LCD_ShowString(10, 10+2*20, tftlcd_data.width, tftlcd_data.height, 12, "string_write_ok");

		  }

	  }
	  vTaskDelay(20);
  }
  /* USER CODE END AppTask_keyscan */
}

(6)读取按键队列,将接收到的按键值在lcd显示

void AppTask_keyshow(void *argument)
{
  /* USER CODE BEGIN AppTask_keyshow */
	uint8_t key;

  /* Infinite loop */
  for(;;)
  {		//参数1是读取的队列句柄,参数2:读取到的数值存放的变量,参数3:一直等到,返回值:是否读取成功
	  BaseType_t result= xQueueReceive( Queue_keysHandle, &key,portMAX_DELAY );
	  if(result=pdTRUE)
	  {
		  uint8_t str[20];
		  sprintf(str,"press key is %d     ",key);//显示接收到的按键值
		  LCD_ShowString(10, 10+4*20, tftlcd_data.width, tftlcd_data.height, 12, str);
	  }

  }
  /* USER CODE END AppTask_keyshow */
}

(7)读取字符串队列,将接收到的数据在lcd上显示

void AppTask_stringshow(void *argument)
{
  /* USER CODE BEGIN AppTask_stringshow */

	char* str;//存放接收的字符串数据
  /* Infinite loop */
  for(;;)
  {
	  BaseType_t result= xQueueReceive( Queue_stringHandle, &str,portMAX_DELAY );
	  if(result=pdTRUE)
	  {
		  uint8_t data[20];

		  sprintf(data,"RXstr is %s ",str);
		  LCD_ShowString(10, 10+4*20, tftlcd_data.width, tftlcd_data.height, 12, data);
	  }

  }
  /* USER CODE END AppTask_stringshow */
}

编译下载即可

4、队列的长度和项大小的确定

在这里插入图片描述
例如:要传输一个字符串长度为10的数据,设置不同的队列长度和不同的项大小有啥区别,要传输的数据和队列的创建上有啥关系?
在定义队列和项的大小时,不同的配置会对数据传输和处理产生不同的影响。
在这里插入图片描述
综上所述,选择哪种配置取决于具体的应用需求:
(1)如果需要逐字符或逐对字符处理,第一项和第二项配置可能更合适。
(2)如果需要并行处理或传输多个部分,第三项配置可能更好。
(3)如果整个字符串作为一个单元被处理或传输,并且不需要进一步分割,那么第四项配置可能最简单。
在实际应用中,还需要考虑网络带宽、延迟、内存使用和其他系统资源来做出最佳决策。

(二)根据不同的按键值LCD上画不同方向的线

1、目标

创建两个任务一个队列即可

2、代码

(1)按键扫描任务:向队列中写入数据

void AppTASK_Scankeys(void *argument)
{
  /* USER CODE BEGIN AppTASK_Scankeys */
	//扫描键�?�并写入到队列中
	GPIO_PinState keystate=GPIO_PIN_SET;//按键是低电平有效,初始化为高电平
	KEYS key=KEY_NONE;//按键值暂时初始化为按键按�?



  /* Infinite loop */
  for(;;)
  {
	  key=KEY_NONE;
	  keystate=HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin);
	  if(keystate==GPIO_PIN_RESET)//表明按键0按下
	  {
		  key=KEY_0;//赋�?�为枚举型变�?
	  }

	  keystate=HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin);
	  if(keystate==GPIO_PIN_RESET)//表明按键0按下
	  {
		  key=KEY_1;//赋�?�为枚举型变�?
	  }

	  keystate=HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin);
	  if(keystate==GPIO_PIN_RESET)//表明按键0按下
	  {
		  key=KEY_2;//赋�?�为枚举型变�?
	  }

	  keystate=HAL_GPIO_ReadPin(KEY_UP_GPIO_Port, KEY_UP_Pin);
	  if(keystate==GPIO_PIN_SET)//表明按键0按下
	  {
		  key=KEY_UP;//赋�?�为枚举型变�?
	  }

	  //如果按键按下了,则向队列中写�?
	  if(key!=KEY_NONE)
	  {
		  //队列句柄,放进队列的项(把key的地�?放进去)�?50ms阻塞延时
		  BaseType_t err= xQueueSend(Queue_keysHandle,&key,50);//返回数据类型为BaseType_t
		  if(err==errQUEUE_FULL)//如果队列已经满了
		  {
			  //队列复位
			  xQueueReset(Queue_keysHandle);
		  }
		  vTaskDelay(300);//�?300ms只允许传入一个键值(起到�?个消抖的作用�?
	  }
	  else//没有扫描到按键的情况
	  {
		  //如果不写这一条其他任务无法进行调�?
		  vTaskDelay(5);//5ms的延迟(进入阻塞状�?�让其他任务得到调度�?
	  }

    osDelay(1);
  }
  /* USER CODE END AppTASK_Scankeys */
}

(2)画线任务:读取队列数据
四个按键分别代表上下左右方向,根据不同的按键值往不同的方向画线

void AppTASK_Draw(void *argument)
{
  /* USER CODE BEGIN AppTASK_Draw */
	//读取队列数据并在lcd操作
	//const char *pcQueueGetName( QueueHandle_t xQueue )
	//先测试获取任务名称函�?
	uint8_t queueName[30];
	sprintf(queueName,"queue name =%s",pcQueueGetName(Queue_keysHandle));
	LCD_ShowString(10, 10+1*20,tftlcd_data.width,tftlcd_data.height, 12, queueName);


	//打印队列的大�?
	uint8_t queue_size[30];
	sprintf(queue_size,"queue size =%d",uxQueueSpacesAvailable(Queue_keysHandle));
	LCD_ShowString(10, 10+2*20,tftlcd_data.width,tftlcd_data.height, 12, queue_size);

	KEYS keyvalue;


  /* Infinite loop */
  for(;;)
  {

	//等待读取的消息个�?
	uint8_t tempStr[30];
	sprintf(tempStr,"queue wait number:%d",uxQueueMessagesWaiting(Queue_keysHandle));
	LCD_ShowString(10, 10+3*20,tftlcd_data.width,tftlcd_data.height, 12, tempStr);

	//剩余的空间个�?(当前)
	sprintf(tempStr,"queue size =%d",uxQueueSpacesAvailable(Queue_keysHandle));
	LCD_ShowString(10, 10+4*20,tftlcd_data.width,tftlcd_data.height, 12, tempStr);

	//下面获取任务高水位�?�来判断是否会发生栈空间溢出(如果某个任务高位�?�接�?0,就有栈溢出的风险,�?要增加栈空间大小�?
	sprintf(tempStr,"TASK_Draw HighWaterMark =%d",uxTaskGetStackHighWaterMark( TASK_DrawHandle ));
	LCD_ShowString(10, 10+5*20,tftlcd_data.width,tftlcd_data.height, 12, tempStr);

	sprintf(tempStr," TASK_Scankeys HighWaterMark=%d",uxTaskGetStackHighWaterMark( TASK_ScankeysHandle ));
	LCD_ShowString(10, 10+6*20,tftlcd_data.width,tftlcd_data.height, 12, tempStr);

//	//读取消息队列
	if(xQueueReceive(Queue_keysHandle,&keyvalue,50)!=pdTRUE)
	{
		continue;//继续运行,跳�?
	}
	if(keyvalue==KEY_0)//�?
	{
		curScreenX+=10;
	}
	else if(keyvalue==KEY_2)
	{
		curScreenX-=10;
	}
	else if(keyvalue==KEY_1)
	{
		curScreenY+=10;
	}
	else if(keyvalue==KEY_UP)
	{
		curScreenY-=10;
	}



	//如果坐标大于屏幕尺寸320*480则进行处�?
	if(curScreenX>tftlcd_data.width)//限制x坐标
	{
		curScreenX=tftlcd_data.width;
	}
	if(curScreenY>tftlcd_data.height)//限制y坐标
	{
		curScreenY=tftlcd_data.height;
	}
	LCD_DrawLine(lastScreenX, lastScreenY, curScreenX, curScreenY);
//	LCD_DrawLine_Color(lastScreenX, lastScreenY, curScreenX, curScreenY,RED);
	//记录�?下当前的变量
	lastScreenX=curScreenX;
	lastScreenY=curScreenY;
	vTaskDelay(400);
  }
  /* USER CODE END AppTASK_Draw */
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/433014.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

实战:基于特征词的语音唤醒

本章前面介绍了纯理论知识,目的是阐述语音识别的方法。接着搭建了开发环境,让读者可以动手编写代码。下面以识别特定词为例,使用深度学习方法和Python语言实现一个实战项目——基于特征词的语音唤醒。 说明:本例的目的是演示一个…

当Sora风靡,AI风潮吹醒金融科技

以下文章来源:凤凰网 前有OpenAI发布了Sora, 后有苹果放弃了秘密进行了十年的造车项目,转身拥抱AI, 再有国内市场上此起彼伏的AI呐喊声, 一场以AI为主导的新热浪,正在来袭。 当AI的风潮开始兴盛&#x…

JavaScript 原型链继承:掌握面向对象的基础

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…

springboot + jpa + 达梦数据库兼容 Mysql的GenerationType.IDENTITY主键生成策略

导入达梦数据库对hibernate的方言包 <dependency><groupId>com.dameng</groupId><artifactId>DmDialect-for-hibernate5.6</artifactId><version>8.1.2.192</version></dependency>配置文件中添加方言配置和主键生成策略配置…

(译) 理解 Prometheus 的范围向量 (Range Vector)

Prometheus 中 Range Vector 的概念是有一点不直观的&#xff0c;除非你彻底阅读并理解了官方提供的文档。谁会这样做呢&#xff0c;去读官方文档&#xff1f;大多的人应该会花些错误的时间去做了一些错误的事情&#xff0c;然后随机去寻找一篇像本文一样的文章去理解这个概念&…

2024年Java者未来的出路在哪里,java多线程面试

重要 大环境对于我们能力要求越来越高&#xff0c;医学专家又说今年冬天新冠肺炎将“席卷重来”。 如果疫情再次爆发&#xff0c;势必将再次影响企业的正常运作&#xff0c;一波裁员浪潮你又能否抗住&#xff1f; 不管如何&#xff0c;明年金三银四又是一波跳槽时机&#xf…

AbaqusCST仿真软件功能对比简介

一、功能对比 支持维度CST&#xff1a;用于设计、分析和优化电磁部件及系统。适用于整个 EM 范围内各类应用领域的电磁场解算。Abaqus&#xff1a;ABAQUS 是一套功能强大的工程模拟的有限元软件&#xff0c;其解决问题的范围从相对简单的线性分析到复杂的非线性问题。 ABAQUS 包…

基于springboot+vue的精简博客系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

flutter弹窗动画,2024年上半年最接地气的Android面经

正文 腾讯研发人数将近 2 万人&#xff0c;T4 级别的人数大概也不超过 500 人&#xff0c;这还是在近两年 T3 到 T4 级别人数增多的情况下。 该资料一共有五大章节&#xff0c;452页&#xff0c;是这位腾讯T4大佬耗时半个月熬夜整理出来的。 目录 第一章 深入解析 Binder. …

Java面试题【必知必会】Mybatis常见面试题(2024)

近期一直在准备面试&#xff0c;所以为了巩固知识&#xff0c;也为了梳理&#xff0c;整理了一些java的基础面试题&#xff01;同时也希望各位英雄和女侠能够补充&#xff01;不胜荣幸&#xff01;&#xff01;&#xff01; 名称地址Java面试题【必知必会】基础&#xff08;202…

Web3 赛道屠夫:「铁顺」是谁?

撰文&#xff1a;Terry 加密世界从不缺传奇故事&#xff0c;从不会编程的「失业青年」Hayden Adams 一入 Web3 便推出巅峰之作 Uniswap&#xff08;《交易平台搅局者「Uniswap 之父」&#xff0c;不会编程的「失业青年」&#xff0c;出手即巅峰》&#xff09;&#xff0c;到 An…

VMware虚拟机安装linux教程

CentOS7下载 下载 (centos.org)https://www.centos.org/download/新建虚拟机 选择自定义安装 这里要注意兼容性&#xff0c;如果是VMware12创建的虚拟机复制到VM11、10或者更低的版本会出现一不兼容的现象。如果是用VMware10创建的虚拟机在VMware12中打开则不会出现兼容性问题…

二,几何相交----2.线段相交测试----(1)bruteforce

将与X轴平行的线段扩展到一般平面上的线段。 则可以使用burteforce两两测试&#xff0c;使用四次toleft即可。比如&#xff0c;线段(a,b)和线段(c,d)相交&#xff0c;必然线段的两个端点在另一个线段的两侧。这样时间复杂度就是成为了o(n2) 其实&#xff0c;时间复杂度可以降低…

半导体分析实验常用清洗器皿特氟龙塑料PFA实验室耗材

晶圆是一种用于制造集成电路和其他半导体器件的基础材料&#xff0c;通常是由单晶硅制成的圆形薄片&#xff0c;随着半导体行业的兴起&#xff0c;其作为行业内常用元件的基础材料&#xff0c;为了保证它可以正常工作&#xff0c;晶圆表面要保持洁净&#xff0c;无不相关的颗粒…

酷开科技以酷开系统为媒介,打造欢乐生活场景

家人相聚在一起的时光总是那么美好&#xff0c;在欢聚的日子里&#xff0c;我们也总是希望能够让时间变得慢一点&#xff0c;再慢一点&#xff0c;但是随着春节假期的结束&#xff0c;很多人已经开始了新一年的忙碌&#xff0c;大家纷纷回到工作、学习岗位&#xff0c;回归之前…

自动驾驶预测与决策规划(nuplan数据集)

欢迎大家关注我的B站&#xff1a; 偷吃薯片的Zheng同学的个人空间-偷吃薯片的Zheng同学个人主页-哔哩哔哩视频 (bilibili.com) 目录 1.概述 2 数据采集 3.开环与闭环仿真 4.数据注释 5.场景 6.规划框架 6.1Train 6.2Simulation 6.3Metric 6.4Visualization 7.下载…

如何使用DS file+cpolar内网穿透实现远程访问本地群晖NAS传输文件

文章目录 1. 群晖安装Cpolar2. 创建TCP公网地址3. 远程访问群晖文件4. 固定TCP公网地址5. 固定TCP地址连接 DS file 是一个由群晖公司开发的文件管理应用程序&#xff0c;主要用于浏览、访问和管理存储在群晖NAS&#xff08;网络附加存储&#xff09;中的文件。这个应用程序具有…

阿里云2024年有什么优惠活动?阿里云30个热门优惠活动汇总

对于想要购买阿里云各种云产品的用户来说&#xff0c;不管是新用户还是老用户&#xff0c;最为关心的是阿里云当下有哪些优惠活动&#xff0c;本文汇集了2024年阿里云官方目前正在进行中的所有优惠活动&#xff0c;其中优惠券和代金券活动4个&#xff0c;官方精选活动10个&…

鹅厂打工8年,我为啥突然裸辞?

公众号&#xff1a;程序员白特&#xff0c;欢迎一起交流学习~ 原文&#xff1a;以下文章来源于沐洒 &#xff0c;作者ASCII26 今天跟大家分享一个重磅消息&#xff0c;沐洒终于从腾讯离职了&#xff01; 不知不觉已经在鹅厂打了8年工&#xff0c;如果说在大厂里工作如同在高校…

基于 HBase Phoenix 构建实时数仓(1)—— Hadoop HA 安装部署

目录 一、主机规划 二、环境准备 1. 启动 NTP 时钟同步 2. 修改 hosts 文件 3. 配置所有主机间 ssh 免密 4. 修改用户可打开文件数与进程数&#xff08;可选&#xff09; 三、安装 JDK 四、安装部署 Zookeeper 集群 1. 解压、配置环境变量 2. 创建配置文件 3. 创建新…