STM32CubeMX学习笔记18——FSMC(TFT-LCD屏触摸)

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) 

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何轻松打造属于自己的水印相机小程序?

水印相机小程序源码 描述&#xff1a;微信小程序。本文将为您详细介绍小程序水印相机源码的搭建过程&#xff0c;教您如何轻松打造属于自己的水印相机小程序。无论您是初学者还是有一定基础的开发者&#xff0c;都能轻松掌握这个教程。 一&#xff1a;水印相机搭建教程 1 隐…

Linux:kubernetes(k8s)Deployment的操作(12)

创建deployment 命令 kubectl create deploy nginx-deploy --imagenginx:1.7.9 再去使用以下命令分别查询 ubectl get deploy kubectl get replicaset kubectl get pod 他是一个层层嵌套的一个关系 首先是创建了一个 deploy 里面包含着replicaset replicaset里面含有…

ChatGPT等AI使用的过程苦笑不得瞬间

引言&#xff1a; 在人工智能的浪潮中&#xff0c;我们见证了技术的飞速发展和智能应用的广泛渗透。特别是随着语言模型的进步&#xff0c;AI如ChatGPT、文心一言、通义千问、讯飞星火等已经成为人们日常生活和工作中不可或缺的助手。然而&#xff0c;与任何新兴技术一样&#…

025—pandas 根多列判断不在其他列的数据

思路 是有两个相同结构的数据表&#xff0c;已知第二个表是第一个表的部分数据&#xff0c;需要以其中两列为单位&#xff0c;判断在第一个表中存在&#xff0c;在另外一个表中不存在的数据。 思路&#xff1a; 我们先将 df1 和 df2 的 x、y 列取出&#xff0c;组合为元组形成…

分享axios+signalr简单封装示例

Ajax Axios Axios 是一个基于 promise 网络请求库&#xff0c;作用于node.js 和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生 node.js http 模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests。 从浏览器创建 XMLHttpReque…

C++容器适配器stack、queue、priority_queue

文章目录 C容器适配器stack、queue、priority_queue1、stack1.1、stack的介绍1.2、stack的使用1.3、stack的模拟实现 2、queue2.1、queue的介绍2.2、queue的使用2.3、queue的模拟实现 3、priority_queue3.1、priority_queue的介绍3.2、priority_queue的使用3.3、仿函数3.4、pri…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:Navigation)

Navigation组件是路由导航的根视图容器&#xff0c;一般作为Page页面的根容器使用&#xff0c;其内部默认包含了标题栏、内容区和工具栏&#xff0c;其中内容区默认首页显示导航内容&#xff08;Navigation的子组件&#xff09;或非首页显示&#xff08;NavDestination的子组件…

mybatisplus的条件构造器

条件构造器wrapper&#xff0c;主要用于构造sql语句的where条件&#xff0c;他更擅长这个&#xff0c;但也可以用于构造其他类型的条件&#xff0c;比如order by、group by等。 条件构造器的使用经验&#xff1a; 基于QueryWrapper的查询 练习1. void testQueryWrapper(){Q…

分库分表浅析原理

数据库存放数据大了&#xff0c;查询等操作就会存在瓶颈&#xff0c;怎么办&#xff1f; 1. 如果是单张表数据大了&#xff0c;可以在原有库上新建几张表table_0、table_1、table_2、.....table_n 写程序对数据进行分表&#xff1a; --这里提供一种一种分表策略,这里只需维护…

2024蓝桥杯每日一题(时间日期)

一、第一题&#xff1a;日期差值 解题思路&#xff1a;模拟 写一个计算时间的板子两者相减 【Python程序代码】 mon [0,31,28,31,30,31,30,31,31,30,31,30,31] def pd(x):if x%4000 or (x%40 and x%100!0):return Truereturn False def get_day(y,m,d):res 0for i …

【机器学习】无监督学习算法之:层次聚类

层次聚类 1、引言2、层次聚类2.1 定义2.2 原理2.3 实现方式2.4 算法公式2.5 代码示例 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c; 这周末过的滋润啊。 小鱼&#xff1a;… 每个周末都挺滋润的啊。 小屌丝&#xff1a;啊~ ~ 你这… 小鱼&#xff1a;周末加班&#xf…

C语言实现贪吃蛇

前言&#xff1a;今天给大家详细介绍一下小游戏贪吃蛇的代码。 目录 一 .贪吃蛇实现的功能 二.贪吃蛇游戏设计与分析 1.贪吃蛇以及贪吃蛇所需要维护的数据 &#xff08;1&#xff09;贪吃蛇蛇体 &#xff08;2&#xff09;数据维护 2.地图设计 &#xff08;1&#x…