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.mzph.cn/news/735377.shtml

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

相关文章

集合框架(一)Set系列集合

Set<E>是一个接口 特点 无序&#xff1a;添加数据的顺序和获取出的数据顺序不一致&#xff1b;不重复&#xff0c;无索引 注意&#xff1a;Set要用到的常用方法&#xff0c;基本上就是collection提供的!自己几乎没有额外新增一些常用功能! HashSet集合的底层原理 前置知…

denied: requested access to the resource is denied报错解决

Docker 镜像在文件中包含一组指令&#xff0c;可在 Docker 容器中执行代码。大多数情况下&#xff0c;在创建 docker 镜像之后&#xff0c;当尝试将镜像推送到远程仓库时&#xff0c;会发生这种类型的报错“Docker denied: requested access to the resource is denied” 由于错…

RabbitMQ - 07 - 通过注解创建队列和交换机

之前消息模型的实现,都是通过rabbitMQ Management 控制台来手动创建 queue 和 exchange 的 在项目开发中有两种方式通过代码声明 创建 一种是通过 Bean 方式,这种代码量较大 稍繁琐 一种是通过注解的方式声明 先编写消费者代码 通过注解绑定了 消息队列,交换机,还有 routin…

零售EDI:劳氏 Lowe‘s EDI项目案例

通过 EDI&#xff0c;企业与Lowes之间可以直接交换各种商业文档&#xff0c;如订单、发票、收据等&#xff0c;从而实现信息的实时交换&#xff0c;提高了供应链的效率和准确性。在现代供应链管理中&#xff0c;EDI 已经成为了不可或缺的重要工具。 作为一家拥有多条业务线的企…

SpringCloudFeign远程调用

文章目录 1. Feign 是什么2. Feign 的使用2.1 引入依赖2.2 写接口2.3 服务调用方2.4 启动测试 3. Feign 日志配置4. Feign 使用优化5. 注意包扫描问题6. 注意的问题6.1 参数问题6.2 Feign 自动装配 1. Feign 是什么 Feign 是一个声明式、模板化的 HTTP 客户端&#xff0c;它是…

Intel® Extension for PyTorch*详细安装教程

最近在研究Intel的pytorch的加速拓展Intel Extension for PyTorch*,但是发现官网的文档全是英文的&#xff0c;不太好找安装教程。所以特此分享Intel Extension for PyTorch*的详细安装教程。 文章目录 一、安装所需系统要求1.1 硬件需求1.2 软件需求 二、准备2.1 安装驱动程序…

Linux 进程程序替换

&#x1f493;博主CSDN主页:麻辣韭菜-CSDN博客&#x1f493;   ⏩专栏分类&#xff1a;http://t.csdnimg.cn/G90eI⏪   &#x1f69a;代码仓库:Linux: Linux日常代码练习&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Linux知识   &#x1f51d;&#x1f5…

力扣530. 二叉搜索树的最小绝对差

思路1&#xff1a;中序遍历&#xff0c;递归排序成有序数组&#xff1b;因为是有序&#xff0c;只需要求相邻两个值的最小差值。 class Solution {ArrayList <Integer> list new ArrayList();int ans 100001;//题目最大 100000public int getMinimumDifference(TreeNo…

动态规划|【路径问题】|174.地下城游戏

题目 174. 地下城游戏 恶魔们抓住了公主并将她关在了地下城 dungeon 的 右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里&#xff0c;他必须穿过地下城并通过对抗恶魔来拯救公主。 骑士的初始健康点数为一个正整数。如果他的健…

前端加密面面观:常见场景与方法解析

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

FIT介绍-0

1、背景 FIT是flattened image tree的简称&#xff0c;它采用了device tree source file&#xff08;DTS&#xff09;的语法&#xff0c;生成的image文件也和dtb文件类似&#xff08;称做itb&#xff09;。 结构如下图&#xff1a; 其中image source file(.its)和device tree …

2024年【G2电站锅炉司炉】最新解析及G2电站锅炉司炉复审模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 G2电站锅炉司炉最新解析是安全生产模拟考试一点通总题库中生成的一套G2电站锅炉司炉复审模拟考试&#xff0c;安全生产模拟考试一点通上G2电站锅炉司炉作业手机同步练习。2024年【G2电站锅炉司炉】最新解析及G2电站锅…

IOS覆盖率报告info文件解读

一&#xff0c;IOS覆盖率报告的生成 在做前端精准测试的时候&#xff0c;对于iOS端&#xff0c;通常会做如下操作&#xff1a; &#xff08;1&#xff09;合并覆盖率数据 如下操作&#xff1a; xcrun llvm-profdata merge coverage_file1657885040728.profraw coverage_fil…

LeetCode刷题日志-17.电话号码的字母组合

纯暴力解法&#xff0c;digits有多长&#xff0c;就循环多少次进行字母组合 class Solution {public List<String> letterCombinations(String digits) {List<String> reslut new ArrayList<>();if(digits.equals(""))return reslut;Map<Inte…

视图【MySQL】

文章目录 概念操作视图创建视图查询视图更新视图删除视图 视图规则和限制 概念 MySQL 中的视图&#xff08;View&#xff09;是一个虚拟表&#xff0c;其内容由查询定义。视图本身不包含数据&#xff0c;这些数据是从一个或多个实际表中派生出来的&#xff0c;通过执行视图定义…

LLM RAG系统中消除数据幻觉的几个绝招-OPENAI公司内称的“大招”

前言-什么是数据幻觉&#xff1f;它到底有什么危害呢 我们直接来举例&#xff1a; 我是金银花一区的&#xff0c;附近有什么小学&#xff1f; 此时RAG带出如下信息&#xff1a; 金银花小区一区、二区、三区附近教育资源有&#xff1a;银树大学、建设小学金银花校区、金树高…

使用Python模拟绘制自由落体运动过程中的抛物线

目录 一、引言 二、自由落体运动的基本原理 三、使用Python模拟自由落体运动 四、扩展功能&#xff1a;添加速度曲线和动画效果 五、总结与展望 一、引言 自由落体运动是物理学中最基础的运动形式之一&#xff0c;它描述了一个物体在仅受重力作用下的运动轨迹。在这个…

Django工具

一、分页器介绍 1.1、介绍 分页,就是当我们在页面中显示一些信息列表,内容过多,一个页面显示不完,需要分成多个页面进行显示时,使用的技术就是分页技术 在django项目中,一般是使用3种分页的技术: 自定义分页功能,所有的分页功能都是自己实现django的插件 django-pagin…

springboot260火锅店管理系统

火锅店管理系统设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装火锅店管理系统软件来发挥其高效…

方法的使用

1.什么是方法(method) 在java中方法就是一个代码片段.。几乎相当于c语言的函数。 2.方法定义 方法跟函数是几乎一样的。所以语法是大差不差的。就多了一点东西。之前我们在c语言里已经很详细讲过了函数。这里就简便的讲一下。 相比c语言函数多了个修饰符 。 现在看下其注意…