输入类设备简介
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>= 3; //右移3位,得到12位有效数据//禁用触摸芯片: CS输出高电平 SET_SPI_Touch_CS;}//采样值从大到小排序排序for(i=0; i<(Touch_READ_TIMES-1); i++){for(j=i+1; j = xValue_Buf[1])xValue_Error = xValue_Buf[0] - xValue_Buf[1];elsexValue_Error = xValue_Buf[1] - xValue_Buf[0];if(yValue_Buf[0] >= yValue_Buf[1])yValue_Error = yValue_Buf[0] - yValue_Buf[1];elseyValue_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 ********************************************************/