1.触摸屏简介
目前最常用的触摸屏有两种:电阻式触摸屏和电容式触摸屏
1.1 电阻式触摸屏
电阻式的触摸屏结构如下图示,它主要由表面硬涂层、两个ITO层、间隔点以及玻璃底层构成,这些结构层都是透明的,整个触摸屏覆盖在液晶面板上,透过触摸屏可看到液晶面板。表面涂层起到保护作用,玻璃底层起承载的作用,而两个ITO层是触摸屏的关键结构,它们是涂有铟锡金属氧化物的导电层。两个ITO层之间使用间隔点使两层分开,当触摸屏表面受到压力时,表面弯曲使得上层ITO与下层ITO接触,在触点处连通电路。
两个ITO涂层的两端分别引出X-、X+、Y-、Y+四个电极,这是电阻屏最常见的四线结构,通过这些电极,外部电路向这两个涂层可以施加匀强电场或检测电压。
当触摸屏被按下时,两个 ITO 层相互接触,从触点处把 ITO 层分为两个电阻,且由于 ITO 层均匀导电,两个电阻的大小与触点离两电极的距离成比例关系,利用这个特性,可通过以下过程来检测坐标,这也正是电阻触摸屏名称的由来。
计算 X 坐标时,在 X+ 电极施加驱动电压 Vref,X-极接地,所以 X+ 与 X-处形成了匀强电场,而触点处的电压通过 Y+ 电极采集得到,由于 ITO 层均匀导电,触点电压与 Vref 之比等于触点 X 坐标与屏宽度之比,从而:
计算 Y 坐标时,在 Y+ 电极施加驱动电压 Vref,Y-极接地,所以 Y+ 与 Y-处形成了匀强电场,而触点处的电压通过 X+ 电极采集得到,由于 ITO 层均匀导电,触点电压与 Vref 之比等于触点 Y 坐标与屏高度之比,从而:
电阻触摸屏的优缺点
- 优点:精度高、价格便宜、抗干扰能力强、稳定性好
- 缺点:容易被划伤、透光性不太好、不支持多点触控
触摸屏都需要一个AD转换器来将电压变化读取出来,供主机算出触摸的位置。本例程中的TFTLCD模块使用的是四线电阻式触摸屏,触摸屏控制芯片为XPT2046,XPT2046是一款4导线制触摸屏控制器,采用SPI模式进行通讯,内含12位分辨率125KHz转换速率逐步逼近型A/D转换器。XPT2046引脚图(SOP-16封装)及引脚说明如下图示
1.2 电容式触摸屏
与电阻式触摸屏不同,电容式触摸屏不需要通过压力使触点变形。它的基本原理是利用充电时间检测电容大小,若手指触摸屏幕,会影响触摸点附近两个电极之间的耦合,从而改变两个电极之间的电容量,若检测到某电容的电容量发生了改变,即可获知该电容处有触摸动作从而通过检测出电容值的变化来获知触摸信号。
由于本例程使用的是电阻式触摸屏,这里电容式触摸屏不做详细介绍
FSMC简介在这里不再赘述,有需要请查看STM32CubeMX学习笔记17--- FSMC-CSDN博客
2、 硬件设计
led2指示灯用来提示系统运行状态,s1按键用来强制校准电阻触摸屏(电容屏无需校准),AT24C02用来存储电阻触摸屏校准数据,TFTLCD模块用来显示触摸
- LED1指示灯
- S1按键
- TFTLCD模块
- AT24C02
- USART1
3、 STM32CubeMX设置
- RCC设置外接HSE,时钟设置为72M
- PC0设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平
- PA0设置为GPIO输入模式、下拉模式
- USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位
- 激活FSMC,详细请参考STM32CubeMX学习笔记17--- FSMC-CSDN博客的设置
模拟SPI
- 由于开发板上画的是普通的IO口,所以这里只能用模拟SPI。实际项目可使用硬件SPI接口。
在
System Core
中选择GPIO
设置。配置以下 5 个引脚:
- PB1设置为GPIO推挽输出模式、浮空、高速、默认输出电平为低电平,重命名为:
XPT2046_SPI_CS
PB2
设置为GPIO推挽输出模式、浮空、高速、默认输出电平为高电平,重命名为:XPT2046_SPI_CLK
- PF9设置为GPIO推挽输出模式、浮空、高速、默认输出电平为低电平,重命名为:
XPT2046_SPI_MOSI
- PF8设置为GPIO推挽输入模式、上拉,重命名为:
XPT2046_SPI_MISO
- PF10设置为GPIO推挽输入模式、上拉,重命名为:
XPT2046_SPI_PENIRQ
笔接触中断引脚
输入工程名,选择工程路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码
4、程序编程
修改代码优化级别
STM32CubeMX生成的代码默认优化级别为Level 3,在此优化级别下编译无误下载到开发板后,LCD屏不能正常运行;将优化级别调整到Level 0编译下载后,LCD屏能够正常运行读取到ID。
KEIL5中C/C++优化等级介绍:
-O0:最少的优化,可以最大程度上配合产生代码调试信息,可以在任何代码行打断点,特别是死代码处。
-O1:有限的优化,去除无用的inline和无用的static函数、死代码消除等,在影响到调试信息的地方均不进行优化。在适当的代码体积和充分的调试之间平衡,代码编写阶段最常用的优化等级。
-O2:高度优化,调试信息不友好,有可能会修改代码和函数调用执行流程,自动对函数进行内联等。
-O3:最大程度优化,产生极少量的调试信息。会进行更多代码优化,例如循环展开,更激进的函数内联等。
- 添加按键驱动文件key.c和key.h
- 添加AT24C02驱动文件24cxx.c和24cxx.h
- 添加TFTLCD驱动文件tftlcd.c 和tftlcd.h,参考FSMC例程
- 添加XPT2046触摸芯片驱动文件touch.c和touch.h 两个重要的结构体:
typedef struct{ /*存储触点读取到的数据*/uint16_t x; //x轴物理坐标值uint16_t y; //y轴物理坐标值uint16_t lcdx; //x轴彩屏坐标值uint16_t lcdy; //y轴彩屏坐标值
}TouchTypeDef;typedef struct{ /*保存校正因素*/uint8_t posState; //校正参数标志int16_t xOffset; //x轴偏移量int16_t yOffset; //y轴偏移量float xFactor; //x轴比例因数float yFactor; //y轴比例因数
}PosTypeDef;
触摸屏初始化及读取物理坐标(ADC值)函数:
void TOUCH_Init(void){//读取AT24C02的TOUCH_ADJ_ADDR地址处的值HAL_I2C_Mem_Read(&hi2c2,ADDR_24CXX_READ,TOUCH_ADJ_ADDR,I2C_MEMADD_SIZE_8BIT,&TouchAdj.posState,sizeof(TouchAdj),0xff); if(TouchAdj.posState != TOUCH_ADJ_OK){ //检查是否有校正数据TOUCH_Adjust(); //如果没有校正数据,则启动校正函数 }
}
/*封装SPI读取数据函数*/
uint8_t WR_Cmd(uint8_t cmd){uint8_t Tx_DATA = cmd;uint8_t Rx_DATA = 0;HAL_SPI_TransmitReceive(&hspi1,&Tx_DATA,&Rx_DATA,1,500);return Rx_DATA;
}
/*读取X轴或Y轴的ADC值,并进行滤波处理*/
uint16_t TOUCH_Read_AD(uint8_t cmd){uint8_t i, j;uint16_t NUMH,NUML;uint16_t NUM[TOUCH_READ_TIMES] = {0};uint16_t temp,value;uint32_t totalValue; //注意数据类型for(i=0; i<TOUCH_READ_TIMES; i++){TOUCH_CS_LOW(); //开始SPI通讯 WR_Cmd(cmd);NUMH = WR_Cmd(0XFF);NUML = WR_Cmd(0XFF);NUM[i] = (NUMH<<8)+ NUML;NUM[i] >>= 3; //最低三位无用 TOUCH_CS_HIGH(); //结束SPI通讯}//滤波处理:1.从大到小排序for(i=0; i<(TOUCH_READ_TIMES - 1); i++){for(j=i+1; j<TOUCH_READ_TIMES; j++){if(NUM[i] < NUM[j]){temp = NUM[i];NUM[i] = NUM[j];NUM[j] = temp;} } }//滤波处理:2.去掉最大值和最小值,求余下的平均值j = TOUCH_READ_TIMES - 1;totalValue = 0;for(i=1; i<j; i++){totalValue += NUM[i];}value = totalValue/(TOUCH_READ_TIMES - 2); return value;
}
/*读取X轴和Y轴的ADC值,再次进行滤波*/
uint8_t TOUCH_ReadXY(uint16_t *xValue, uint16_t *yValue){ uint16_t xValue1, yValue1, xValue2, yValue2;xValue1 = TOUCH_Read_AD(TOUCH_X_CMD);yValue1 = TOUCH_Read_AD(TOUCH_Y_CMD);xValue2 = TOUCH_Read_AD(TOUCH_X_CMD);yValue2 = TOUCH_Read_AD(TOUCH_Y_CMD);//计算两点之间的采样差值if(xValue1 > xValue2)*xValue = xValue1 - xValue2;else*xValue = xValue2 - xValue1;if(yValue1 > yValue2)*yValue = yValue1 - yValue2;else*yValue = yValue2 - yValue1;//判断采样差值是否在可控范围内if((*xValue > TOUCH_MAX+0) || (*yValue > TOUCH_MAX+0)) return 0xFF;//求平均值*xValue = (xValue1 + xValue2) / 2;*yValue = (yValue1 + yValue2) / 2;//判断得到的值,是否在取指范围内,避免出现飞点现象 if((*xValue > TOUCH_X_MAX+0) || (*xValue < TOUCH_X_MIN) || (*yValue > TOUCH_Y_MAX+0) || (*yValue < TOUCH_Y_MIN)) return 0xFF;return 0;
}
触摸屏校准函数:
/*读校准点的物理坐标*/
uint8_t TOUCH_ReadAdjust(uint16_t x, uint16_t y, uint16_t *xValue, uint16_t *yValue){uint8_t i;uint32_t timeCont;LCD_Clear(BACK_COLOR);LCD_DrowSign(x, y, RED); //x和y为指定的校准点的彩屏坐标值i = 0;while(1){ //读该指定校准点的物理坐标值if(!TOUCH_ReadXY(xValue, yValue)){i++;if(i > 10){LCD_DrowSign(x, y, BACK_COLOR);return 0;} }timeCont++;if(timeCont > 0xFFFFFFFE){ //超时退出 LCD_DrowSign(x, y, BACK_COLOR); return 0xFF;} }
}
/*触摸屏校准函数*/
void TOUCH_Adjust(void){uint16_t px[2], py[2], xPot[4], yPot[4];float xFactor, yFactor;//读取4个校准点的物理坐标值if(TOUCH_ReadAdjust(LCD_ADJX_MIN, LCD_ADJY_MIN, &xPot[0], &yPot[0]))return;HAL_Delay(500);if(TOUCH_ReadAdjust(LCD_ADJX_MIN, LCD_ADJY_MAX, &xPot[1], &yPot[1]))return;HAL_Delay(500);if(TOUCH_ReadAdjust(LCD_ADJX_MAX, LCD_ADJY_MIN, &xPot[2], &yPot[2]))return;HAL_Delay(500);if(TOUCH_ReadAdjust(LCD_ADJX_MAX, LCD_ADJY_MAX, &xPot[3], &yPot[3]))return; HAL_Delay(500);//处理读取到的四个点的数据,整合成对角的两个点px[0] = (xPot[0] + xPot[1]) / 2;py[0] = (yPot[0] + yPot[2]) / 2;px[1] = (xPot[3] + xPot[2]) / 2;py[1] = (yPot[3] + yPot[1]) / 2;//求出比例因数xFactor = (float)LCD_ADJ_X / (px[1] - px[0]);yFactor = (float)LCD_ADJ_Y / (py[1] - py[0]); //求出偏移量TouchAdj.xOffset = (int16_t)LCD_ADJX_MAX - ((float)px[1] * xFactor);TouchAdj.yOffset = (int16_t)LCD_ADJY_MAX - ((float)py[1] * yFactor);//将比例因数进行数据处理,并保存TouchAdj.xFactor = xFactor ;TouchAdj.yFactor = yFactor ; TouchAdj.posState = TOUCH_ADJ_OK;//将得出的校正因数保存到AT24C02中的TOUCH_ADJ_ADDR地址处HAL_I2C_Mem_Write(&hi2c2,ADDR_24CXX_WRITE,TOUCH_ADJ_ADDR,I2C_MEMADD_SIZE_8BIT,&TouchAdj.posState,sizeof(TouchAdj),0xff);
}
触摸屏扫描函数:
uint8_t TOUCH_Scan(void){if(TOUCH_ReadXY(&TouchData.x, &TouchData.y)) return 0xFF; //没有触摸,直接返回0xFF//根据物理坐标值,计算出彩屏坐标值TouchData.lcdx = TouchData.x * TouchAdj.xFactor + TouchAdj.xOffset;TouchData.lcdy = TouchData.y * TouchAdj.yFactor + TouchAdj.yOffset;//查看彩屏坐标值是否超过彩屏大小if(TouchData.lcdx > tftlcd_data.width)TouchData.lcdx = tftlcd_data.width;if(TouchData.lcdy > tftlcd_data.height)TouchData.lcdy = tftlcd_data.height;return 0;
}
修改main.c
加入 ILI9341_Init()
LCD屏驱动初始化后,进行 Palette_Init()
绘制触摸画板界面,随后在while循环里面检测触摸 XPT2046_TouchEvenHandler()
。
nt main(void){uint8_t key;uint16_t penColor = BLUE;HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_FSMC_Init();MX_I2C2_Init();MX_SPI1_Init();MX_USART1_UART_Init();/* USER CODE BEGIN 2 */TFTLCD_Init();AT24CXX_Init();HAL_Delay(2000); LCD_Clear(WHITE);TOUCH_Init();/* USER CODE END 2 */while (1){key=KEY_Scan(0);if(key==KEY_UP_PRES){ //按下KEY_UP键进入校准函数TOUCH_Adjust(); } if(TOUCH_Scan() == 0){ if(TouchData.lcdy > tftlcd_data.height - 18){//选择画笔颜色if(TouchData.lcdx>220)penColor = YELLOW;else if(TouchData.lcdx>200)penColor = CYAN; else if(TouchData.lcdx>180)penColor = GREEN;else if(TouchData.lcdx>160)penColor = MAGENTA;else if(TouchData.lcdx>140)penColor = RED;else if(TouchData.lcdx>120)penColor = BLUE; }else{ //画点LCD_Fill(TouchData.lcdx-1, TouchData.lcdy-1, TouchData.lcdx+2,TouchData.lcdy+2, penColor);}//清屏 if ((TouchData.lcdx > tftlcd_data.width-8*4) && (TouchData.lcdy < 16))//215 = TFT_XMAX - 24{LCD_Fill(0, 0, tftlcd_data.width,tftlcd_data.height-16, BACK_COLOR);LCD_ShowString(tftlcd_data.width-8*4,0,tftlcd_data.width,tftlcd_data.height,16,(uint8_t *)"RST");} }HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);HAL_Delay(10);}
}
工程代码
使用模拟SPI:链接:百度网盘 请输入提取码 提取码:ugpp
使用标准SPI: 链接:https://pan.baidu.com/s/1fvtCLxaVPn3FIKQzHKR8og 提取码:9pfr
5、 下载验证
编译无误下载到开发板后,可以看到LED2指示灯不断闪烁,触摸屏显示界面如下图示
6、参考文献
STM32CubeMX学习笔记(39)——FSMC接口使用(TFT-LCD屏触摸)_stm32f207vc cubemx fsmc lcd-CSDN博客
STM32CubeMX系列 | 触摸屏 - 知乎 (zhihu.com)