STM32实战总结:HAL之触摸屏

news2024/11/27 14:31:07

输入类设备简介

IO输入输出,是计算机系统中的一个概念。计算机的主要功能就是从外部获取数据然后进行计算加工得到目标数据并输出给外部(计算机可以看成数据处理器)。计算机和外部交互就是通过IO。每一台计算机都有个标准输入和标准输出。

常见输入类设备:键盘、鼠标、触摸屏、游戏摇杆、传感器、(摄像头并不是一个典型的输入类设备)

触摸屏的特点
(1)触摸屏和人的关系很紧密,尤其是电容式触摸屏。
(2)触摸屏和显示器关系很紧密。
(3)典型应用:手机、平板电脑、收银机、工业领域。

触摸屏的分类
常见的触摸屏分为2种:电阻式触摸屏和电容式触摸屏。早期用电阻式触摸屏,后来发明了电容式触摸屏。
这两种的特性不同、接口不同、编程方法不同、原理不同。

触摸屏和显示屏的联系与区别
首先要搞清楚:触摸屏是触摸屏,用来响应人的触摸事件的;显示屏是显示屏,用来显示的。现在用的显示屏一般都是LCD。
为什么很多人会搞混这两个概念,主要是因为一般产品上触摸屏和显示屏是做在一起的。一般外层是一层触摸屏,触摸屏是透明的,很薄;底下是显示屏用来显示图像,平时看到的图像是显示屏显示并且透过触摸屏让人看到的。

电阻式触摸屏的原理 

电阻式触摸屏使用教程_哔哩哔哩_bilibili

薄膜+玻璃(需要尖锐硬物点击)
要点是薄、透明。前面板硬度稍弱,可以被硬物按下弯曲,后面板硬度很高,不会弯曲。
前面板和后面板在平时没有挨住,在外力按下之下,前面板发生(局部)形变,在这一点上前后面板会挨住。

ITO(导电+透明+均匀压降)
ITO是一种材料,其实是一种涂料,特点就是透明、导电、均匀涂抹。
本来玻璃和塑料都是不导电的,但是涂上ITO之后就变成导电了(同时还保持着原来透明的特性)。
ITO不但导电而且有电阻,所以中间均匀涂抹了ITO之后就相当于在x1和y1之间接了一个电阻,在x2和y2之间也接了一个电阻。因为ITO形成的等效电阻在整个板上是均匀分布的,所在在板子上某一点的电压值和这一点的位置值成正比。
触摸屏经过操作,按下之后要的就是按下的坐标,坐标其实就是位置信息,这个位置信息和电压成正比了,而这一点的电压可以通过AD转换得到。这就是整个电阻式触摸屏的工作原理。

电阻式触摸屏使用教程_哔哩哔哩_bilibili

X/Y轴分时AD转换
下面要研究如何得到按下的这点的电压。
在第一个面板的一对电极上加电压,然后在另一个面板的一个电极和第一个面板的地之间去测量。在没有按下时测试无结果,但是在有人按下时在按下的那一点2个面板接触,接触会导致第二个面板上整体的电压值和接触处的电压值相等,所以此时测量到的电压就是接触处在第一个面板上的电压值。
以上过程在一个方向进行一次即可测得该方向的坐标值,进行完之后撤掉电压然后在另一个方向的电极上加电压,故伎重施,即可得到另一个方向的坐标。至此一次触摸事件结束。

电压值对应坐标值(校准)
电压值和坐标值成正比的,所以需要去校准它。校准就是去计算(0, 0)坐标点的电压值是多少。

电阻式触摸屏不支持多点触摸。

电容触摸屏的原理

电容式触摸屏_百度百科

人体电流感应(手机用的就是电容触摸屏)
利用人体电流感应现象,在手指和屏幕之间形成一个电容,手指触摸时吸走一个微小电流,这个电流会导致触摸板上4个电极上发生电流流动,控制器通过计算这4个电流的比例就能算出触摸点的坐标(这个计算过程中涉及到AD转换)。

 

专用电路计算坐标
电阻式触摸屏本身是一个完全被动器件,里面没有任何IC和电路,它的工作逻辑完全在SoC控制器上;但是电容式触摸屏不同,电容式触摸屏需要自带一个IC进行坐标计算。因此电容式触摸屏工作时不需要主机SoC控制器参与。
为什么这样设计?主要原因是因为电容式触摸屏的坐标计算太复杂,普通程序员无法写出合适的代码解决这个问题,因此在电容式触摸屏中除了触摸板之外还附加了一个IC进行专门的坐标点计算和统计。这个IC全权负责操控触摸板得到触摸操作信息,然后再通过数字接口和主机SoC进行通信。

多个区块支持多点触摸
电阻触摸屏不支持多点触摸,这是它本身的原理所限制,无法改变无法提升。
电容式触摸屏可以支持多点触摸(也可以单点触摸)。按照之前讲的电容式触摸屏的原理,单个电容式触摸屏面板也无法支持多点触摸,但是可以将一个大的触摸面板分成多个小的区块,每个区块相当于是一个独立的小的电容式触摸屏面板。
多个区块支持多点触摸让电容触摸屏坐标计算变复杂了,但是这个复杂性被电容触摸IC吸收了,还是通过数字接口和主机SoC通信报告触摸信息(触摸点数、每个触摸点的坐标等)。


电阻式触摸屏和电容式触摸屏的特点对比
(1)耐久性        电容式触摸屏不容易坏,电阻式触摸屏易坏
(2)抗干扰性(比如屏幕有水按下没反应)   电容式触摸屏差一些,电阻式触摸屏要好一些
(3)精准度        电容式触摸屏差一些,电阻式触摸屏好一些
(4)用户体验        电容式触摸屏要好一些,电阻式触摸屏要差一些
(5)价格(自带IC电路)        电容式触摸屏贵一些,电阻式触摸屏便宜很多

思考:为什么工业应用中要用电阻式触摸屏?
消费电子产品(手机、平板电脑)用电容式触摸屏。但是在工业领域都是用电阻式触摸屏,就是因为工业领域环境比较恶劣,电容式触摸屏容易受干扰,所以不合适。

触摸屏的发展方向
更薄、更透明、更精准、支持点数更多。
把电容触摸屏和LCD做在一起。可以做到更薄、更透明、价格更低。但是面临的困难是抗干扰性要求更高。

硬件原理图

这次用的是个电阻式触摸屏。

接口用的是SPI,单片机可以通过SPI接口来读取触摸的坐标信息。

用到的驱动IC是XT2046,本质上是个AD转换芯片。对触摸后的电压数据进行采样,然后通过SPI发送给单片机进行识别处理。

关于XT2046,具体查看数据手册。不再赘述。

关键代码

/* Includes ------------------------------------------------------------------*/
#include "MyApplication.h"

/* Private define-------------------------------------------------------------*/

/* Private variables----------------------------------------------------------*/

/* Private function prototypes------------------------------------------------*/      
static void Palette_Init(void);                               //调色板初始化
static void Palette_DrawPoint(uint16_t,uint16_t,LCD_Color_t); //绘图区域画点
static uint8_t Touch_Read_ADC_XY(uint16_t *, uint16_t *);     //读触摸IC的XY坐标值
static uint8_t Touch_Calibrate(void);                         //触摸屏校准
static uint8_t Touch_Scan(void);                              //触摸屏扫描
	
/* Public variables-----------------------------------------------------------*/
Touch_Calibrate_Para_t Touch_Calibrate_Para;

Touch_t Touch = 
{
	FALSE,
	FALSE,
	0,
	0,
	0,
	0,
	Color_RED,
	
	Palette_Init,
	Palette_DrawPoint,
	Touch_Read_ADC_XY,
	Touch_Calibrate,
	Touch_Scan
};
/* Private function prototypes------------------------------------------------*/
static uint8_t  SPI_Touch_ReadByte(void);        //从Flash读1个字节
static void     SPI_Touch_WriteByte(uint8_t);    //给Flash写1个字节
static uint16_t Touch_Read_ADC(uint8_t);         //读取触摸屏的ADC值

static void     LCD_DrawCross(uint16_t,uint16_t);//校正触摸时画十字专用 

/*
	* @name   SPI_Touch_ReadByte
	* @brief  从触摸IC读1个字节
	* @param  None
	* @retval 读取到的数据     
*/
static uint8_t SPI_Touch_ReadByte()
{	
	uint8_t ReceiveByte;
	//等待模式读出1个字节
	if(HAL_SPI_Receive(&hspi2,&ReceiveByte,1,0x0A) != HAL_OK)
		ReceiveByte = Dummy_Byte;
	//返回字节
	return ReceiveByte;
}

/*
	* @name   SPI_Touch_ReadByte
	* @brief  给触摸IC写1个字节
	* @param  Byte -> 待写入的字节
	* @retval 读取到的数据     
*/
static void SPI_Touch_WriteByte(uint8_t Byte)
{	
	uint8_t SendByte = Byte;
	//等待模式写入1个字节
	HAL_SPI_Transmit(&hspi2,&SendByte,1,0x0A);
}

/*
	* @name   Palette_Init
	* @brief  调色板初始化
	* @param  None
	* @retval None      
*/
static void Palette_Init()
{
	//屏幕填充灰色
	TFT_LCD.FillColor(0,0,LCD_WIDTH,LCD_HEIGTH,Color_GRAY);  
	
	//绘图区域填充白色
	TFT_LCD.FillColor(0,0,240,LCD_HEIGTH-48,Color_WHITE);
	
	//显示调色板
	TFT_LCD.FillColor(0,  LCD_HEIGTH-48,24,24,Color_RED);
	TFT_LCD.FillColor(24, LCD_HEIGTH-48,24,24,Color_GREEN);
	TFT_LCD.FillColor(48, LCD_HEIGTH-48,24,24,Color_BLUE);
	TFT_LCD.FillColor(72, LCD_HEIGTH-48,24,24,Color_YELLOW);
	TFT_LCD.FillColor(96, LCD_HEIGTH-48,24,24,Color_MAGENTA);
	TFT_LCD.FillColor(120,LCD_HEIGTH-48,24,24,Color_CYAN);
	TFT_LCD.FillColor(144,LCD_HEIGTH-48,24,24,Color_BROWN);
	TFT_LCD.FillColor(168,LCD_HEIGTH-48,24,24,Color_LIGHTBLUE);
	TFT_LCD.FillColor(192,LCD_HEIGTH-48,24,24,Color_GRAY);
	TFT_LCD.FillColor(216,LCD_HEIGTH-48,24,24,Color_BLACK);
	
	//显示坐标信息
	TFT_LCD.LCD_ShowString(0,  LCD_HEIGTH-24,"X:",  Color_GRAY,Color_RED,ASCII_font_24);
	TFT_LCD.LCD_ShowString(24, LCD_HEIGTH-24,"----",Color_GRAY,Color_RED,ASCII_font_24);	
	TFT_LCD.LCD_ShowString(96, LCD_HEIGTH-24,"Y:",  Color_GRAY,Color_RED,ASCII_font_24);
	TFT_LCD.LCD_ShowString(120,LCD_HEIGTH-24,"----",Color_GRAY,Color_RED,ASCII_font_24);
	
	//显示画笔色彩
	TFT_LCD.FillColor(LCD_WIDTH-48, LCD_HEIGTH-24,24,24,Touch.Color_PEN);
	
	//显示清除画板信息
	TFT_LCD.FillColor(LCD_WIDTH-24,LCD_HEIGTH-24,24,24,Color_WHITE);
	TFT_LCD.LCD_ShowString(LCD_WIDTH-20,LCD_HEIGTH-20,"CL",Color_WHITE,Color_RED,ASCII_font_16);
}

/*
	* @name   Palette_DrawPoint
	* @brief  绘图区域画点
	* @param  x:x坐标
  *         y:y坐标
  *         Color:点的颜色
	* @retval None      
*/
static void Palette_DrawPoint(uint16_t x, uint16_t y,LCD_Color_t Color)
{
	TFT_LCD.LCD_SetPointPiexl ( x-1, y-1, Color);
  TFT_LCD.LCD_SetPointPiexl (   x, y-1, Color);
  TFT_LCD.LCD_SetPointPiexl ( x+1, y-1, Color);
  TFT_LCD.LCD_SetPointPiexl ( x-1,   y, Color);  
  TFT_LCD.LCD_SetPointPiexl (   x,   y, Color);  
  TFT_LCD.LCD_SetPointPiexl ( x+1,   y, Color);
	//调色板边界处理
	if((y+1) < LCD_HEIGTH-48)
	{
		TFT_LCD.LCD_SetPointPiexl ( x-1, y+1, Color);  
		TFT_LCD.LCD_SetPointPiexl (   x, y+1, Color);  
		TFT_LCD.LCD_SetPointPiexl ( x+1, y+1, Color);
	}
}
/*
	* @name   Touch_Read_ADC
	* @brief  读取触摸屏的ADC值
	* @param  XT2046_CMD:触摸IC命令
  * @retval ADC值     
*/
static uint16_t Touch_Read_ADC(uint8_t XT2046_CMD)
{
	uint8_t  i,j;
	uint16_t Value_Buf[Touch_READ_TIMES],usTemp;
	uint32_t SumValue = 0;
	
	//通过SPI接口循环读取Touch_READ_TIMES次数
	for(i=0; i<Touch_READ_TIMES; i++)
	{
		//选择触摸芯片: CS输出低电平
		CLR_SPI_Touch_CS;
		
		/* 在差分模式下,XPT2046转换需要24个时钟,8个时钟输入命令,延时一会, 
       之后1个时钟去除忙信号,接着输出12位转换结果,剩下3个时钟是忽略位*/
		
		//发送控制命令
	  SPI_Touch_WriteByte(XT2046_CMD);	
		//延时一会,等待ADC转换
		for(j=0; j<100; j++);	
		//读取数据
		Value_Buf[i] = SPI_Touch_ReadByte(); //获取前面7个字节,其中最高位无效
		Value_Buf[i] &= (~BIT7);             //最高位无效
		Value_Buf[i] <<= 8;                  //左移至高字节
		Value_Buf[i] += SPI_Touch_ReadByte();//获取后面5字节,其中低3位无效
		Value_Buf[i] >>= 3;	                 //右移3位,得到12位有效数据
    //禁用触摸芯片: CS输出高电平		
		SET_SPI_Touch_CS;
	}
	
	//采样值从大到小排序排序
	for(i=0; i<(Touch_READ_TIMES-1); i++)
	{
		for(j=i+1; j<Touch_READ_TIMES; j++)
		{
			if(Value_Buf[i] < Value_Buf[j])
			{
				usTemp        = Value_Buf[i];
				Value_Buf[i]  = Value_Buf[j];
				Value_Buf[j]  = usTemp;
			}   
		}       
	}
	
	//去掉最大与最小值,求和
	for(i=1; i<Touch_READ_TIMES-1; i++)
	{
		SumValue += Value_Buf[i];
	}
	
	//返回平均值
	return  SumValue/(Touch_READ_TIMES-2);
}

/*
	* @name   Touch_Read_ADC_XY
	* @brief  读触摸IC的XY坐标值
	* @param  *xValue:保存读取到X轴ADC值的地址
  *         *yValue:保存读取到Y轴ADC值的地址
  * @retval TRUE:读取成功,FALSE:读取失败      
*/
static uint8_t Touch_Read_ADC_XY(uint16_t *xValue, uint16_t *yValue)
{
	uint16_t xValue_Buf[2],yValue_Buf[2];
	uint16_t xValue_Error,yValue_Error;
	
	//读取坐标X,Y的值,各2次
	xValue_Buf[0] = Touch_Read_ADC(Touch_X_CMD);
	yValue_Buf[0] = Touch_Read_ADC(Touch_Y_CMD);
	xValue_Buf[1] = Touch_Read_ADC(Touch_X_CMD);
	yValue_Buf[1] = Touch_Read_ADC(Touch_Y_CMD);
	
	//计算2次采样的差值
	if(xValue_Buf[0] >= xValue_Buf[1])
		xValue_Error = xValue_Buf[0] - xValue_Buf[1];
	else
		xValue_Error = xValue_Buf[1] - xValue_Buf[0];
	
	if(yValue_Buf[0] >= yValue_Buf[1])
		yValue_Error = yValue_Buf[0] - yValue_Buf[1];
	else
		yValue_Error = yValue_Buf[1] - yValue_Buf[0];
	
	//两次采样差值超过误差,丢弃
	if((xValue_Error > Touch_Error) || (yValue_Error > Touch_Error))
	{
		return  FALSE;
	}
	
	//求平均
	*xValue = (xValue_Buf[0] + xValue_Buf[1]) / 2;
	*yValue = (yValue_Buf[0] + yValue_Buf[1]) / 2;
	
	//判断值是否有效
	if((*xValue > Touch_X_MAX) || (*xValue < Touch_X_MIN))
		return  FALSE;	
	if((*yValue > Touch_Y_MAX) || (*yValue < Touch_Y_MIN))
		return  FALSE;
	
	//打印坐标值
	//printf("触摸屏坐标值(ADC):X=%u,Y=%u\r\n",*xValue,*yValue);
	
  //返回
	return TRUE;
}

/*
	* @name   LCD_DrawCross
	* @brief  校正触摸时画十字专用
	* @param  x:十字中点x轴
  *         y:十字中点y轴
  * @retval None    
*/
static void LCD_DrawCross(uint16_t x,uint16_t y)
{
	TFT_LCD.LCD_DrawLine(x-10,y,x+10,y,Color_RED);
  TFT_LCD.LCD_DrawLine(x,y-10,x,y+10,Color_RED);
}

/*
	* @name   Touch_ReadCalibrateValue
	* @brief  读取校准点坐标值
	* @param  x:x轴
  *         y:y轴
	*         *xValue:x轴坐标值
	*         *xValue:y轴坐标值
  * @retval None    
*/
static uint8_t Touch_ReadCalibrateValue(uint16_t x, uint16_t y, uint16_t *xValue, uint16_t *yValue)
{
	uint8_t Cnt = 0;
	
	//显示校准位置
	LCD_DrawCross(x,y);	
	while(1)
	{
		if(Touch_Read_ADC_XY(xValue,yValue))
		{
			//取第十次读到的值,数据更稳定
			if(Cnt++ > 10)
			{
				TFT_LCD.FillColor(0,0,240,320,Color_BLACK);
				return TRUE;
			}
		}
	}
}


/*
	* @name   Touch_Calibrate
	* @brief  触摸屏幕校准
	* @param  None
  * @retval TRUE:校准成功,FALSE:校准失败   
*/

static uint8_t Touch_Calibrate()  
{
	//5个校准位置,中间的校验用
	uint16_t Calibrate_xyLCD[5][2] = 
  {
		{20,20},
		{20,LCD_HEIGTH-20},
		{LCD_WIDTH-20,LCD_HEIGTH-20},
		{LCD_WIDTH-20,20},
		{LCD_WIDTH/2,LCD_HEIGTH/2}    //屏幕中央,校验用
	};
	uint16_t xValue[5],yValue[5];         //5个校准位置对应的坐标值
	uint16_t xOpposite[2],yOpposite[2];   //计算得到对角的坐标
	uint16_t Avr_xOpposite,Avr_yOpposite; //对角坐标的平均值,用于与屏幕中央的坐标值比较
	uint8_t  i;
	
	//读取5个校准点的坐标值
	TFT_LCD.FillColor(0,0,240,320,Color_BLACK);
	for(i=0;i<5;i++)
	{
		Touch_ReadCalibrateValue(Calibrate_xyLCD[i][0],Calibrate_xyLCD[i][1],&xValue[i],&yValue[i]);
		//适当延时,读取下一个校准点
		HAL_Delay(800);
	}
	
	//将正方形的4个校准点整合成对角两点,减小触摸误差
	xOpposite[0] = (xValue[0] + xValue[1]) / 2;
	yOpposite[0] = (yValue[0] + yValue[3]) / 2;
	xOpposite[1] = (xValue[2] + xValue[3]) / 2;
	yOpposite[1] = (yValue[1] + yValue[2]) / 2;
	//计算对角两点的平均值
	Avr_xOpposite = (xOpposite[0]+xOpposite[1])/2;
	Avr_yOpposite = (yOpposite[0]+yOpposite[1])/2;
	
	printf("触摸屏坐标值(ADC):xAvr=%u,yAvr=%u\r\n",Avr_xOpposite,Avr_yOpposite);
	printf("触摸屏坐标值(ADC):xMid=%u,yMid=%u\r\n",xValue[4],yValue[4]);
	
	//对校准点进行校验
	if(Avr_xOpposite >= xValue[4]) 
	{
		if((Avr_xOpposite - xValue[4]) > 100)
		{
			printf("校准失败\r\n");
			TFT_LCD.LCD_ShowString(24,160,"Calibrate Fail",Color_BLACK,Color_RED,ASCII_font_24);
			HAL_Delay(1000);
			return FALSE;
		}
	}
	else
	{
		if((xValue[4] - Avr_xOpposite) > 100)
		{
			printf("校准失败\r\n");
			TFT_LCD.LCD_ShowString(24,160,"Calibrate Fail",Color_BLACK,Color_RED,ASCII_font_24);
			HAL_Delay(1000);
			return FALSE;
		}
	}
	
	if(Avr_yOpposite >= yValue[4]) 
	{
		if((Avr_yOpposite - yValue[4]) > 100)
		{
			printf("校准失败\r\n");
			TFT_LCD.LCD_ShowString(24,160,"Calibrate Fail",Color_BLACK,Color_RED,ASCII_font_24);
			HAL_Delay(1000);
			return FALSE;
		}
	}
	else
	{
		if((yValue[4] - Avr_yOpposite) > 100)
		{
			printf("校准失败\r\n");
			TFT_LCD.LCD_ShowString(24,160,"Calibrate Fail",Color_BLACK,Color_GREEN,ASCII_font_24);
			HAL_Delay(1000);
			return FALSE;
		}
	}
	
	//计算比例因素
	Touch_Calibrate_Para.xFactor = (float)(LCD_WIDTH - 40)  / (xOpposite[1] - xOpposite[0]);
	Touch_Calibrate_Para.yFactor = (float)(LCD_HEIGTH - 40) / (yOpposite[1] - yOpposite[0]);
	
	//计算偏移量
	Touch_Calibrate_Para.xOffset = (uint8_t)(Touch_Calibrate_Para.xFactor*Avr_xOpposite - LCD_WIDTH/2);
	Touch_Calibrate_Para.yOffset = (uint8_t)(Touch_Calibrate_Para.yFactor*Avr_yOpposite - LCD_HEIGTH/2);
	
	//设置校准标志位
	Touch_Calibrate_Para.Calibrate_Flag = Touch_Calibrate_OK;
	
	printf("校准成功\r\n");
	printf("校准因素xFactor:%.2f\r\n",Touch_Calibrate_Para.xFactor);
	printf("校准因素yFactor:%.2f\r\n",Touch_Calibrate_Para.yFactor);
	printf("偏移量xOffset:  %u\r\n",Touch_Calibrate_Para.xOffset);
	printf("偏移量yOffset:  %u\r\n",Touch_Calibrate_Para.yOffset);
	TFT_LCD.LCD_ShowString(12,160,"Calibrate Success",Color_BLACK,Color_GREEN,ASCII_font_24);
	HAL_Delay(1000);
	
	保存参数
	//扇区擦除
	SPI_Flash.EraseSector(Touch_Calibrate_Para_Addr);
	SPI_Flash.WriteUnfixed(&Touch_Calibrate_Para.Calibrate_Flag,Touch_Calibrate_Para_Addr,sizeof(Touch_Calibrate_Para));
	
	return TRUE;
}

/*
	* @name   Touch_Scan
	* @brief  触摸屏扫描
	* @param  None
  * @retval TRUE:有触摸,获取到坐标值,FALSE:无触摸
   说明:降低干扰,每次获取两次LCD屏幕坐标值,计算误差,求平均。
*/
static uint8_t Touch_Scan()              
{
	uint16_t LCD_X1,LCD_Y1,LCD_X2,LCD_Y2;
	
	//if(HAL_GPIO_ReadPin(TP_NIRQ_GPIO_Port,TP_NIRQ_Pin) == GPIO_PIN_RESET)
	//{
		//第一次读取触摸屏的坐标值
	  if(Touch.Read_ADC_XY(&Touch.ADC_X,&Touch.ADC_Y) == FALSE)
		{
			return FALSE;
		}
		
		//第二次计算LCD屏幕坐标值
		LCD_X1 = (uint16_t)(Touch.ADC_X*Touch_Calibrate_Para.xFactor) - Touch_Calibrate_Para.xOffset;
		LCD_Y1 = (uint16_t)(Touch.ADC_Y*Touch_Calibrate_Para.yFactor) - Touch_Calibrate_Para.yOffset;
		
		if(Touch.Read_ADC_XY(&Touch.ADC_X,&Touch.ADC_Y) == FALSE)
		{
			return FALSE;
		}
		
		//第二次计算LCD屏幕坐标值
		LCD_X2 = (uint16_t)(Touch.ADC_X*Touch_Calibrate_Para.xFactor) - Touch_Calibrate_Para.xOffset;
		LCD_Y2 = (uint16_t)(Touch.ADC_Y*Touch_Calibrate_Para.yFactor) - Touch_Calibrate_Para.yOffset;
		
		//误差检查
		if(LCD_X1 >= LCD_X2)
		{
			if((LCD_X1 - LCD_X2) > 2)
				return FALSE;
		}
		else
		{
			if((LCD_X2 - LCD_X1) > 2)
				return FALSE;
		}
		
		if(LCD_Y1 >= LCD_Y2)
		{
			if((LCD_Y1 - LCD_Y2) > 2)
				return FALSE;
		}
		else
		{
			if((LCD_Y2 - LCD_Y1) > 2)
				return FALSE;
		}
		
		//计算两次的平均值,得到LCD屏幕坐标值
		Touch.LCD_X = (LCD_X1 + LCD_X2)/2;
		Touch.LCD_Y = (LCD_Y1 + LCD_Y2)/2;
		
		if(Touch.LCD_X > (LCD_WIDTH - 1))
		{
			Touch.LCD_X = LCD_WIDTH - 1;
		}
		
		if(Touch.LCD_Y > (LCD_HEIGTH - 1))
		{
			Touch.LCD_X = LCD_WIDTH - 1;
		}
		
		
		//LCD屏幕上显示触摸屏的坐标值
		TFT_LCD.LCD_ShowChar(24, LCD_HEIGTH-24,Touch.ADC_X/1000+'0',    Color_GRAY,Color_RED,ASCII_font_24);
		TFT_LCD.LCD_ShowChar(36, LCD_HEIGTH-24,Touch.ADC_X%1000/100+'0',Color_GRAY,Color_RED,ASCII_font_24);
		TFT_LCD.LCD_ShowChar(48, LCD_HEIGTH-24,Touch.ADC_X%100/10+'0',  Color_GRAY,Color_RED,ASCII_font_24);
		TFT_LCD.LCD_ShowChar(60, LCD_HEIGTH-24,Touch.ADC_X%10+'0',      Color_GRAY,Color_RED,ASCII_font_24);
		
		TFT_LCD.LCD_ShowChar(120, LCD_HEIGTH-24,Touch.ADC_Y/1000+'0',    Color_GRAY,Color_RED,ASCII_font_24);
		TFT_LCD.LCD_ShowChar(132, LCD_HEIGTH-24,Touch.ADC_Y%1000/100+'0',Color_GRAY,Color_RED,ASCII_font_24);
		TFT_LCD.LCD_ShowChar(144, LCD_HEIGTH-24,Touch.ADC_Y%100/10+'0',  Color_GRAY,Color_RED,ASCII_font_24);
		TFT_LCD.LCD_ShowChar(156, LCD_HEIGTH-24,Touch.ADC_Y%10+'0',      Color_GRAY,Color_RED,ASCII_font_24);
		
		return TRUE;
			
	//}	
	//else
	//{
		//return FALSE;
	//}
}
/********************************************************
  End Of File
********************************************************/

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

业务:财务会计业务知识

一、引言 会计是以货币为主要计量单位&#xff0c;对企业、事业、机关、团体及其他经济组织的经济活动进行记录、计算、控制、分析、报告&#xff0c;以提供财务和管理信息的工作。会计的职能主要是反映和控制经济活动过程&#xff0c;保证会计信息的合法、真实、准确和完整&a…

像素坐标和实际坐标的转换

文章目录像素坐标系图像坐标系小孔成像原理相机坐标系畸变参数像素坐标系 像素坐标系uov是一个二维直角坐标系&#xff0c;反映了相机CCD/CMOS芯片中像素的排列情况。原点o位于图像的左上角&#xff0c;u轴、v轴分别于像面的两边平行。像素坐标系中坐标轴的单位是像素&#xf…

新手python的自学总结(已拿到百度offer)

今天给大家分享一个小伙伴自学Python找到工作的文章。 先简单介绍一下小伙伴的情况。是非科班出身的&#xff0c;虽然高中读的是理科&#xff0c;但是进入大学学了一个文科专业&#xff0c;之后就再没接触过理工科的东西。对计算机也是一窍不通&#xff0c;大学才刚开始接触电…

Linux源码——启动流程1

主要 大概流程就是&#xff1a; 通电自检-----》bios----》bootloader —》内核。其中内核这部分是这篇主要的&#xff0c;通过bootloader初始化好一些条件&#xff0c;然后调用kernel_entry()开始正式进入内核之中&#xff0c;后面就是内核操作了&#xff0c;启动进程什么的…

Java知识点

Java浅拷贝 Java的深拷贝和浅拷贝 - YSOcean - 博客园 (cnblogs.com) Clone 是 Object 类中的一个方法&#xff0c;通过对象A.clone() 方法会创建一个内容和对象 A 一模一样的对象 B&#xff0c;clone 克隆&#xff0c;顾名思义就是创建一个一模一样的对象出来&#xff08;浅…

如何确保海外服务器的高可用性?

服务器正常运行时间和站点可用性是每个企业的首要任务。但对于那些通过在线业务盈利的公司来说&#xff0c;确保可靠性是最重要的。然而&#xff0c;任何意外停机都是有代价的&#xff0c;而硬件故障是最常见的原因。不过好在这也是可以预防的。那么&#xff0c;如何确保海外服…

怎样把网页上的音频转换成mp3格式?试试这几个转换方法

大家平时喜欢听音乐吗&#xff1f;我经常会在网上保存一些不错的音频到设备上&#xff0c;这样子就方便我可以随时播放了。那你们有遇到过该音频格式不支持播放的情况吗&#xff1f;这种情况是因为播放器兼容的音频格式比较少&#xff0c;需要我们将音频格式转换成播放器兼容的…

如何发现Python依赖库漏洞

因为python编程的流行&#xff0c;python的各种库也越来越多&#xff0c;但许多小伙伴可能只注意到了自己编程所要依赖的环境&#xff0c;但是却忽略了库的版本也有可能存在漏洞的风险&#xff0c;如果不及时检查和更新python依赖库&#xff0c;那么很有可能你写的代码本身就存…

[附源码]SSM计算机毕业设计医院药房管理系统JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

《500强高管谈VE》-以VE ORIENTED管理为目标

文章出处&#xff1a;日本VE协会杂志文章翻译&#xff1a;泰泽项目部 关注泰泽&#xff1a;实现高利润企业《500强高管谈VE》-以VE ORIENTED管理为目标 作者&#xff1a;代表董事兼副社长近藤一 以阪神大地震为首的诸多事件等&#xff0c;社会动荡中&#xff0c;经济的动向也以…

HTML + CSS 实现矩形/圆形进度条效果 - SVG

本文记录通过 HTML CSS 部分原生 JS 使用 SVG 嵌入 HTML 文档的用法实现常见的圆形和矩形进度条效果&#xff0c;效果图如下&#xff1a;&#xff08;实际运行效果是进度条从 0 过渡到一个目标值比如 100%&#xff09; 下面直接上代码&#xff1a; 圆形 HTML&#xff1a;线…

【数据结构】第七章 图

1.单选(2分) 已知一个有向图的邻接矩阵表示&#xff0c;要删除所有从第i个结点发出的边&#xff0c;操作为&#xff08; &#xff09;。 ‏A.将邻接矩阵的第i列删除 B.将邻接矩阵的第i行元素全部置为0 C.将邻接矩阵的第i列元素全部置为0 D.将邻接矩阵的第i行删除 2.单选(2分)…

UE5——动画混合

一、引言 关于动画的一些基础可以看我往期的文章&#xff1a;《UE5——动画重定向》 二、动画混合 1、动画混合的原理 动画&#xff1a; 我们知道动画实际上就是控制静态模型中的某些点按照一定的预定轨迹移动&#xff0c;简言之就是 “一组变换信息的集合” 动画混合&…

多功能手持读数仪VH03如何连接手机蓝牙

VH03 内置有基于 SPP&#xff08;Serial Port Profile&#xff09;协议的蓝牙接口&#xff0c;蓝牙名称为“VH03”。 使用任何支持 SPP 协议的蓝牙设备均可实现与 VH03 的连接。当蓝牙建立连接后&#xff0c;可向 VH03 发送指令进行交互&#xff08;前述 MODBUS、AABB、字符串…

CET-4 week9 阅读 写译

去# 阅读 question 定位 寻找有意义有目的 的动词符号 – &#xff0c; 转折词从句的解释说明理解超刚词不完全一样的地方要注意 注意匹配对应 answer 是否出现比较 比较对象 结果 article 重点长难句考点不在简单词上 选相反的答案或其他 仔细阅读 严格翻译题目 知道重点…

采用策略分布曲线评估信用风险模型的效果

在信贷业务的风控体系中&#xff0c;模型的构建与应用始终是一项重点内容&#xff0c;最常见的莫过于贷前环节的申请信用风险模型。作为典型的二分类模型&#xff0c;为了有效识别好坏用户群体&#xff0c;我们经常选取某些评价指标来量化模型的综合性能&#xff0c;例如KS、AU…

Java定时任务技术分析

《从零打造项目》系列文章 工具 比MyBatis Generator更强大的代码生成器 ORM框架选型 SpringBoot项目基础设施搭建SpringBoot集成Mybatis项目实操SpringBoot集成MybatisPlus项目实操SpringBoot集成Spring Data JPA项目实操 数据库变更管理 数据库变更管理&#xff1a;Liquibase…

内存 管理

内存管理c/c中内存分布sizeof 与 strlenc 语言中动态内存管理方式malloccallocreallocc 中动态内存管理new 与 delete自定义类型空间的动态分配new 与 delete 的实现operator new 与 operator delete基本概念辨识malloc/free 与 new/delete 区别 *****内存泄漏c/c中内存分布 c…

手把手教你:CSS + JS实现文本交替

1. Koa 中间件 Koa 的中间件通过一种更加传统的方式进行级联&#xff0c;摒弃了以往 node 频繁的回调函数造成的复杂代码逻辑。使用异步函数&#xff0c;我们可以实现"真正" 的中间件。与之不同&#xff0c;当执行到 yield next 语句时&#xff0c;Koa 暂停了该中间…

Qt 在linux上检测内存泄漏,用valgrind的问题

我在ubuntu上装了Qt5.15.2, 打开我的项目后&#xff0c;准备检测内存泄漏问题。 此时&#xff1a; 内存检测工具都是可用状态&#xff0c;但点击内存检测后&#xff0c;进度条走完后&#xff0c;就结束了。项目都没启动起来&#xff0c;这怎么检测内存问题&#xff1f; 然后&…