触摸屏

1.触摸屏简介

目前最常用的触摸屏有两种:电阻式触摸屏和电容式触摸屏

1.1 电阻式触摸屏

电阻式的触摸屏结构如下图示,它主要由表面硬涂层、两个ITO层、间隔点以及玻璃底层构成,这些结构层都是透明的,整个触摸屏覆盖在液晶面板上,透过触摸屏可看到液晶面板。表面涂层起到保护作用,玻璃底层起承载的作用,而两个ITO层是触摸屏的关键结构,它们是涂有铟锡金属氧化物的导电层。两个ITO层之间使用间隔点使两层分开,当触摸屏表面受到压力时,表面弯曲使得上层ITO与下层ITO接触,在触点处连通电路。

STM32CubeMX系列|触摸屏_c语言

两个ITO涂层的两端分别引出X-、X+、Y-、Y+四个电极,这是电阻屏最常见的四线结构,通过这些电极,外部电路向这两个涂层可以施加匀强电场或检测电压。

STM32CubeMX系列|触摸屏_stm32_02

电阻触摸屏的校准:确定位置和对应的电压值之间的比例关系


X0 = Xfac * ADC_X + Xoff
Y0 = Yfac * ADC_Y + Yoff


电阻触摸屏的优缺点


  • 优点:精度高、价格便宜、抗干扰能力强、稳定性好
  • 缺点:容易被划伤、透光性不太好、不支持多点触控

触摸屏都需要一个AD转换器来将电压变化读取出来,供主机算出触摸的位置。本例程中的TFTLCD模块使用的是四线电阻式触摸屏,触摸屏控制芯片为XPT2046,XPT2046是一款4导线制触摸屏控制器,采用SPI模式进行通讯,内含12位分辨率125KHz转换速率逐步逼近型A/D转换器。XPT2046引脚图(SOP-16封装)及引脚说明如下图示

STM32CubeMX系列|触摸屏_单片机_03

STM32CubeMX系列|触摸屏_c语言_04

1.2 电容式触摸屏

与电阻式触摸屏不同,电容式触摸屏不需要通过压力使触点变形。它的基本原理是利用充电时间检测电容大小,若手指触摸屏幕,会影响触摸点附近两个电极之间的耦合,从而改变两个电极之间的电容量,若检测到某电容的电容量发生了改变,即可获知该电容处有触摸动作从而通过检测出电容值的变化来获知触摸信号。

STM32CubeMX系列|触摸屏_嵌入式_05

由于本例程使用的是电阻式触摸屏,这里电容式触摸屏不做详细介绍

2. 硬件设计

D1指示灯用来提示系统运行状态,K_UP按键用来强制校准电阻触摸屏(电容屏无需校准),AT24C02用来存储电阻触摸屏校准数据,TFTLCD模块用来显示触摸



  • D1指示灯
  • K_UP按键
  • TFTLCD模块
  • AT24C02
  • USART1


STM32CubeMX系列|触摸屏_触摸屏_06

3. 软件设计

3.1 STM32CubeMX设置

  • RCC设置外接HSE,时钟设置为72M
  • PC0设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平
  • PA0设置为GPIO输入模式、下拉模式
  • PD6设置为GPIO输出模式(TOUCH_CS);PD7设置为GPIO输入模式(TOUCH_PEN)
  • USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位
  • 激活I2C2,选择标准传输模式,选择7位寻址地址,其余默认设置
  • 激活FSMC,详细请参考​​TFTLCD显示​​章节的设置
  • 激活SPI1,不开启NSS,数据长度8位,MSB先输出,分频因子16,CPOL为HIGH,CPHA为第二个边沿,不开启CRC检验,NSS为软件控制

STM32CubeMX系列|触摸屏_触摸屏_07

  • 输入工程名,选择工程路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码
3.2 MDK-ARM编程

  • 添加按键驱动文件key.c和key.h,参考​​按键输入​​例程
  • 添加AT24C02驱动文件24cxx.c和24cxx.h,参考​​I2C总线​​例程
  • 添加TFTLCD驱动文件tftlcd.c 和tftlcd.h,参考​​TFTLCD显示​​例程
  • 添加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文件下编写触摸屏测试代码
int 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);
}
}

4. 下载验证

编译无误下载到开发板后,可以看到D1指示灯不断闪烁,触摸屏显示界面如下图示

STM32CubeMX系列|触摸屏_单片机_08


关注我的公众号,在公众号里发如下消息,即可获取相应的工程源代码:

玩转STM32CubeMX | 触摸屏

STM32CubeMX系列|触摸屏_单片机_09