600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > 【正点原子FPGA连载】 第二十九章TFT LCD画板实验 摘自【正点原子】DFZU2EG_4EV M

【正点原子FPGA连载】 第二十九章TFT LCD画板实验 摘自【正点原子】DFZU2EG_4EV M

时间:2023-08-25 04:55:25

相关推荐

【正点原子FPGA连载】 第二十九章TFT LCD画板实验 摘自【正点原子】DFZU2EG_4EV M

1)实验平台:正点原子MPSoC开发板

2)平台购买地址:/item.htm?id=692450874670

3)全套实验源码+手册+视频下载地址: /thread-340252-1-1.html

第二十九章TFT LCD画板实验

现在几乎所有智能手机,包括平板电脑都是采用电容屏作为触摸屏,电容屏是利用人体感应进行触点检测控制,不需要直接接触或只需要轻微接触,通过检测感应电流来定位触摸坐标。在本章中,我们将向大家介绍MPSOC控制ALIENTKE LCD电容触摸模块,实现触摸屏驱动,最终实现一个手写板的功能。

本章包括以下几个部分:

2929.1简介

29.2实验任务

29.3硬件设计

29.4软件设计

29.5下载验证

29.1简介

目前最常用的触摸屏有两种:电阻式触摸屏与电容式触摸屏。下面,我们来分别介绍。

(一)电阻式触摸屏:

在Iphone面世之前,几乎清一色的都是使用电阻式触摸屏,电阻式触摸屏利用压力感应进行触点检测控制,需要直接应力接触,通过检测电阻来定位触摸位置。

正点原子2.8/3.5寸LCD模块自带的触摸屏都属于电阻式触摸屏,下面简单介绍下电阻式触摸屏的原理。

电阻触摸屏的主要部分是一块与显示器表面非常配合的电阻薄膜屏,这是一种多层的复合薄膜,它以一层玻璃或硬塑料平板作为基层,表面涂有一层透明氧化金属(透明的导电电阻)导电层,上面再盖有一层外表面硬化处理、光滑防擦的塑料层、它的内表面也涂有一层涂层、在它们之间有许多细小的(小于1/1000英寸)的透明隔离点把两层导电层隔开绝缘。当手指触摸屏幕时,两层导电层在触摸点位置就有了接触,电阻发生变化,在X和Y两个方向上产生信号,然后送触摸屏控制器。控制器侦测到这一接触并计算出(X,Y)的位置,再根据获得的位置模拟鼠标的方式运作。这就是电阻技术触摸屏的最基本的原理。

电阻触摸屏的优点:精度高、价格便宜、抗干扰能力强、稳定性好。

电阻触摸屏的缺点:容易被划伤、透光性不太好、不支持多点触摸。

从以上介绍可知,触摸屏都需要一个AD转换器,一般来说是需要一个控制器的。正点原子 LCD模块选择的是四线电阻式触摸屏,这种触摸屏的控制芯片有很多,包括:ADS7843、ADS7846、TSC2046、XPT2046和AK4182等。这几款芯片的驱动基本上是一样的,也就是你只要写出了ADS7843的驱动,这个驱动对其他几个芯片也是有效的。而且封装也有一样的,完全PIN TO PIN兼容。所以在替换起来,很方便。

正点原子 LCD模块自带的触摸屏控制芯片为XPT2046。XPT2046是一款4导线制触摸屏控制器,内含12位分辨率125KHz转换速率逐步逼近型A/D转换器。XPT2046支持从1.5V到5.25V的低电压I/O接口。XPT2046能通过执行两次A/D转换查出被按的屏幕位置,除此之外,还可以测量加在触摸屏上的压力。内部自带2.5V参考电压可以作为辅助输入、温度测量和电池监测模式之用,电池监测的电压范围可以从0V到6V。XPT2046片内集成有一个温度传感器。在2.7V的典型工作状态下,关闭参考电压,功耗可小于0.75mW。XPT2046采用微小的封装形式:TSSOP-16,QFN-16(0.75mm厚度)和VFBGA-48。工作温度范围为-40℃~+85℃。

该芯片完全是兼容ADS7843和ADS7846的,关于这个芯片的详细使用,可以参考这两个芯片的datasheet。

电阻式触摸屏就介绍到这里。

(二)电容式触摸屏:

现在几乎所有智能手机,包括平板电脑都是采用电容屏作为触摸屏,电容屏是利用人体感应进行触点检测控制,不需要直接接触或只需要轻微接触,通过检测感应电流来定位触摸坐标。

正点原子 4.3/7/10.1寸LCD模块自带的触摸屏采用的是电容式触摸屏,下面简单介绍下电容式触摸屏的原理。

电容式触摸屏主要分为两种:

1、表面电容式电容触摸屏。

表面电容式触摸屏技术是利用ITO(铟锡氧化物,一种透明的导电材料)导电膜,通过电场感应方式感测屏幕表面的触摸行为进行。但是表面电容式触摸屏有一些局限性,它只能识别一个手指或者一次触摸。

2、投射式电容触摸屏。

投射电容式触摸屏是传感器利用触摸屏电极发射出静电场线。一般用于投射电容传感技术的电容类型有两种:自我电容和交互电容。

自我电容又称绝对电容,是最广为采用的一种方法,自我电容通常是指扫描电极与地构成的电容。在玻璃表面有用ITO制成的横向与纵向的扫描电极,这些电极和地之间就构成一个电容的两极。当用手或触摸笔触摸的时候就会并联一个电容到电路中去,从而使在该条扫描线上的总体的电容量有所改变。在扫描的时候,控制IC依次扫描纵向和横向电极,并根据扫描前后的电容变化来确定触摸点坐标位置。笔记本电脑触摸输入板就是采用的这种方式,笔记本电脑的输入板采用XY的传感电极阵列形成一个传感格子,当手指靠近触摸输入板时,在手指和传感电极之间产生一个小量电荷。采用特定的运算法则处理来自行、列传感器的信号来确定手指的位置。

交互电容又叫做跨越电容,它是在玻璃表面的横向和纵向的 ITO 电极的交叉处形成电容。交互电容的扫描方式就是扫描每个交叉处的电容变化,来判定触摸点的位置。当触摸的时候就会影响到相邻电极的耦合,从而改变交叉处的电容量,交互电容的扫面方法可以侦测到每个交叉点的电容值和触摸后电容变化,因而它需要的扫描时间与自我电容的扫描方式相比要长一些,需要扫描检测XY根电极。目前智能手机/平板电脑等的触摸屏,都是采用交互电容技术。

正点原子 所选择的电容触摸屏,也是采用的是投射式电容屏(交互电容类型),所以后面仅以投射式电容屏作为介绍。

投射式电容触摸屏采用纵横两列电极组成感应矩阵,来感应触摸。以两个交叉的电极矩阵,即:X轴电极和Y轴电极,来检测每一格感应单元的电容变化,如下图所示:

图 30.1.1 投射式电容屏电极矩阵示意图

示意图中的电极,实际是透明的,这里是为了方便大家理解。图中,X、Y轴的透明电极电容屏的精度、分辨率与X、Y轴的通道数有关,通道数越多,精度越高。以上就是电容触摸屏的基本原理,接下来看看电容触摸屏的优缺点:

电容触摸屏的优点:手感好、无需校准、支持多点触摸、透光性好。

电容触摸屏的缺点:成本高、精度不高、抗干扰能力差。

这里特别提醒大家电容触摸屏对工作环境的要求是比较高的,在潮湿、多尘、高低温环境下面,都是不适合使用电容屏的。

电容触摸屏一般都需要一个驱动IC来检测电容触摸,且一般是通过IIC接口输出触摸数据的。正点原子不同尺寸和分辨率的触摸屏较多,每款屏幕都配有触摸芯片,不同的触摸屏,配备了相同或者不同的触摸芯片,即使是相同的触摸屏,也有可能配备不同的触摸芯片。至于触摸芯片的具体型号,可以查看触摸屏背面触摸芯片上的丝印。对于7寸RGB LCD屏1024600分辨率来说,使用过的触摸芯片有CST340、FT5206和GT911,CST340和FT5206的驱动是兼容的,而GT911和这两款芯片驱动不兼容,因此在程序设计的时候,需要先获取触摸芯片的ID或者版本号,采用不同的时序来驱动触摸屏。需要注意的是,后续不排除继续更换触摸芯片,因为众所周知的原因,部分芯片可能会缺货或者价格高到非常离谱,因此可能会采用其它替换方案。不过大家不用担心,在寻找替换方案时,一般会采用驱动能够兼容的芯片,即使不兼容,我们也会及时更新触摸屏的驱动程序。

正点原子RGB LCD液晶屏使用的触摸芯片整体上分为GT系列(如GT911、GT1151、GT9271等)和FT系列(如FT5206、FT5426等),GT系列和FT系列的寄存器不兼容,但是不同GT系列芯片之间的寄存器是兼容的,同样的,不同 FT 系列芯片之间的寄存器也是兼容的,因此我们只需要学习 GT 系列中的一款芯片和FT 系列中的一款芯片即可。

4.3寸800480和7寸1024*600的RGB LCD屏使用的触摸芯片为GT911(最新的屏幕可能会替换成其它触摸芯片),这里以GT911为例,讲解GT系列触摸芯片的驱动方法,而其它触摸芯片的使用方法非常类似,详情可以查看相关芯片的数据手册。

下面我们简单介绍下GT911,该芯片是深圳汇顶科技研发的一颗电容触摸屏驱动IC,支持100Hz 触点扫描频率,支持5点触摸,支持14(感应通道)*26(驱动通道)个检测通道,适合小于4.5寸的电容触摸屏使用。GT911与FPGA连接是通过4根线:SDA、SCL、RST和INT。其中:SDA和SCL是IIC通信用的,RST是复位脚(低电平有效),INT是中断输出信号。GT911采用标准的IIC通信,最大通信速率为400KHz。GT911的IIC器件地址,可以是0X14或者0X5D,当复位结束后的5ms内,如果INT是高电平,则使用0X14作为地址,否则使用0X5D作为地址。本章我们使用7’h14作为器件地址(不含最低位,换算成读写命令则是读:0X29,写:0X28)。GT911上电设置器件地址的时序图如下图所示:

图 30.1.2上电设置器件地址时序图

接下来,介绍一下GT911的几个重要的寄存器。

1,控制命令寄存器(0X8040)

该寄存器可以写入不同值,实现不同的控制,我们一般使用0和2这两个值,写入2,即可软复位GT911;写入0,即可正常读取坐标数据(并且会结束软复位)。

2,配置寄存器组(0X8047~0X8100)

这里共186个寄存器,用于配置GT911的各个参数,这些配置一般由厂家提供给我们(一个数组),所以我们只需要将厂家给我们的配置,写入到这些寄存器里面,即可完成GT911的配置。由于GT911可以保存配置信息(可写入内部FLASH,从而不需要每次上电都更新配置),这里有几点注意的地方提醒大家:1,0X8047寄存器用于指示配置文件版本号,程序写入的版本号,必须大于等于GT911本地保存的版本号,才可以更新配置。2,0X80FF寄存器用于存储校验和,使得0X8047~0X80FF之间所有数据之和为0。3,0X8100用于控制是否将配置保存在本地,写0,则不保存配置,写1则保存配置。

3,产品ID寄存器(0X8140~0X8143)

这里总共由 4 个寄存器组成,用于保存产品 ID,对于GT911,这4个寄存器读出来就是:9,1,4,7四个字符(ASCII码格式)。因此,我们可以通过这4个寄存器的值,来判断驱动IC的型号,从而判断是GT911还是FT5206,以便执行不同的初始化。

4,状态寄存器(0X814E)

该寄存器各个位描述如下表所示:

表 30.1.1 寄存器定义

这里,我们仅关心最高位和最低4位,最高位用于表示buffer状态,如果有数据(坐标/按键),buffer就会是1,最低4位用于表示有效触点的个数,范围是:0~5,0表示没有触摸,5表示有5点触摸。最后,该寄存器在每次读取后,如果bit7有效,则必须写0,清除这个位,否则不会输出下一次数据!!这个要特别注意!!!

5,坐标数据寄存器(共20个)

这里共分成5组(5个点),每组4个寄存器存储数据,以触点1的坐标数据寄存器组为例,如下表所示:

表 30.1.2 触点1坐标寄存器组描述

我们一般只用到触点的X,Y坐标,所以只需要读取0X81500X8153的数据组合,即可得到触点坐标。其他4组分别是:0X8158、0X8160、0X8168和0X8170等开头的16个寄存器组成,分别针对触点24的坐标。GT911支持寄存器地址自增,我们只需要发送寄存器组的首地址,然后连续读取即可,GT911会自动地址自增,从而提高读取速度。

GT911相关寄存器的介绍就介绍到这里,更详细的资料,请参考:GT911编程指南.pdf这个文档。

GT911只需要经过简单的初始化就可以正常使用了,初始化流程:硬复位→延时10ms→结束硬复位→设置IIC地址→延时50ms→更新配置(需要时)。此时GT911即可正常使用了。

然后,我们不停的查询0X814E寄存器,判断是否有有效触点,如果有,则读取坐标数据寄存器,得到触点坐标,特别注意,如果0X814E读到的值最高位为1,就必须对该位写0,否则无法读到下一次坐标数据。

FT系列的触摸芯片,我们以FT5206为例进行讲解,FT5206、FT5426和CST340的触摸代码一样,它们只是在读取版本号的时候稍有差异,在读坐标数据和配置等操作上没有区别,所以FT系列的触摸芯片可以共用一个驱动程序。

FT5206采用标准的IIC通信,最大通信速率为400KHz,该芯片的器件地址为0X70(写)和0X71(读),不含最低位(读写位)为7’h38。

FT5206的寄存器比较多,这里我们着重介绍比较重要的寄存器。

1,工作模式寄存器(0X00)

该寄存器用于设置FT5206的工作模式,该寄存器的描述如下:

表 30.1.3寄存器定义

寄存器 bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0

0X00 0 MODE[2:0] 0 0 0 0

MODE[2:0]用于控制FT5206的工作模式,一般设置为:000,表示正常工作模式。

2,中断状态控制寄存器(0XA4)

该寄存器用于设置FT5206的中断状态,该寄存器的描述如下:

表 30.1.4 寄存器定义

寄存器 bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0

0XA4 0 0 0 0 0 0 0 M

该寄存器只有最低位有效,M=0:表示查询模式;M=1:触发模式,一般设置为查询模式。

3,有效触摸门限控制寄存器(0X80)

该寄存器用于设置FT5206的中断状态,该寄存器的描述如下:

表 30.1.5 寄存器定义

寄存器 bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0

0X80 T7 T6 T5 T4 T3 T2 T1 T0

该寄存器8位数据都有效,用于设置FT5206有效触摸的门限值,计算方式为:

有效触摸门限值=T[7:0]*4,T[7:0]的值越小,触摸越灵敏,默认状态下该值为70。

4,激活周期控制寄存器(0X88)

该寄存器用于设置FT5206的激活周期,该寄存器的描述如下:

表 30.1.6 寄存器定义

寄存器 bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0

0X88 0 0 0 0 P3 P2 P1 P0

该寄存器只有低4位有效,用于设置FT5206的激活周期,P[3:0]的设置范围为3~14,默认值为12。

5,库版本寄存器(0XA1和0XA2)

库版本寄存器由两个寄存器组成:0XA1和0XA2,用于读取FT5206的驱动库版本,0XA1用于读取版本号的高字节,0XA2用于读取版本号的低字节。7寸屏FT5206的版本号为0X3003。

6,触摸状态寄存器(0X02)

该寄存器用于读取FT5206的触摸状态,该寄存器的描述如下:

表 30.1.7 寄存器定义

寄存器 bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0

0X02 0 0 0 0 TD3 TD2 TD1 TD0

该寄存器只有低4位有效,TD3[3:0]的取值范围是1~5,表示有多少个有效触摸点。我们可以根据这个寄存器的值来判断有效触摸点的个数,然后通过0X03/0X09/0X0F/0X15和0X1B等寄存器来读取触摸坐标数据。

7,触摸数据寄存器(0X03~0X1E)

这里总共包括20个寄存器,它们是0X030X06、0X090X0C、0X0F0X12、0X150X18和0X1B0X1E。每4个寄存器为1组,表示一个触摸点的坐标数据,比如0X030X06,则表示触摸点1的坐标数据,其它的以此类推。这里,我们仅介绍0X03~0X06寄存器,该寄存器的描述如下:

表 30.1.8 寄存器定义

寄存器 bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0

0X03 Event FLAG 0 0 X[11:8]

0X04 X[7:0]

0X05 Touch ID 0 0 Y[11:8]

0X06 Y[7:0]

这里的Event FLAG用于表示触摸状态,00:按下;01:松开;10:持续触摸;11:保留。一般我们只需要判断该状态是否为10即可,即持续触摸状态,就可以稳定的读取触摸坐标数据了。而Touch ID,一般用不到,最后就是X和Y的坐标数据,这些数据以12位的形式输出。

其它的0X090X0C、0X0F0X12、0X150X18和0X1B0X1E寄存器,则分别用于读取第2~5个触摸点坐标的数据。

FT5206的初始化流程非常简单,首先通过CT_RST引脚对FT5206进行一次复位,然FT5206进入正常工作模式。然后设置工作模式、中断状态、触摸阈值和激活周期等参数,就完成了对FT5206的初始化。

初始化完成便可以读取触摸坐标数据了,先读取0X02寄存器,判断有多少个有效触摸点,然后读取0X03~0X1E等寄存器,便可以获得触摸坐标数据。

需要注意的是,FT5206的寄存器地址为1个字节,即8位;而GT911的寄存器地址为2个字节,即16位,因此在程序设计时需要特别留意。

29.2实验任务

本章我们使用MPSOC结合TFT-LCD驱动,实现在LCD屏幕上触摸画线功能。

29.3硬件设计

本章实验的PL端的硬件框架是在“PS通过VDMA驱动LCD显示”实验的基础上搭建起来的,整个系统的架构图如下图所示:

图 30.3.1 系统框图

可以看出,与“PS通过VDMA驱动LCD显示”实验相比,我们加入了通过EMIO来检测LCD屏触摸状态的功能。这一部分功能是由PS端来实现的。PS不断地扫描LCD的触摸状态,包括是否有触摸动作发生、当前触摸点的坐标等信息,根据扫描到的信息来决定是否向VDMA的Frame Buffer中写入数据以及向Frame Buffer的哪个地址写入数据等等。然后位于PL端的VDMA读取逻辑不断地将Frame Buffer中存储的数据送给LCD进行显示。

PS与LCD的触摸芯片之间的通信(即图中最右侧的粉红色线),是由PS端GPIO引出的4个EMIO来完成的,包括CT_RST、CT_INT、IIC3_SCL、IIC3_SDA。CT_RST是MPSOC送给触摸芯片的复位信号。CT_INT是触摸芯片送给MPSOC的中断信号,用于指示是否有触摸事件发生。IIC3_SCL、IIC3_SDA是触摸芯片内置的IIC总线,MPSOC使用它来访问触摸芯片的内部寄存器,来完成对触摸芯片的初始化、读取触摸点的坐标等动作。这4个引脚如下图所示:

图 30.3.2 LCD的4个触摸驱动芯片引脚

在Vivado IP Integrator设计画布中对PS添加4个EMIO的设置如下图所示:

图 30.3.3 添加4个EMIO的设置

添加后的4个EMIO如下图所示:

图 30.3.4 添加后的4个EMIO

对这4个EMIO进行管脚约束,如下所示:

#IIC_SCLset_property PACKAGE_PIN G11 [get_ports {GPIO_EMIO_tri_io[0]}]#IIC_SDAset_property PACKAGE_PIN H12 [get_ports {GPIO_EMIO_tri_io[1]}]#CT_RSTset_property PACKAGE_PIN H11 [get_ports {GPIO_EMIO_tri_io[2]}]#CT_INTset_property PACKAGE_PIN F12 [get_ports {GPIO_EMIO_tri_io[3]}]set_property IOSTANDARD LVCMOS33 [get_ports {GPIO_EMIO_tri_io[3]}]set_property IOSTANDARD LVCMOS33 [get_ports {GPIO_EMIO_tri_io[2]}]set_property IOSTANDARD LVCMOS33 [get_ports {GPIO_EMIO_tri_io[1]}]set_property IOSTANDARD LVCMOS33 [get_ports {GPIO_EMIO_tri_io[0]}]

在生成Bitstream之后,在菜单栏中选择 File > Export > Export hardware导出硬件,并在弹出的对话框中,勾选“Include bitstream”。 将导出的“design_1_wrapper.xsa”文件放到vitis文件夹,然后在菜单栏选择Tools > Launch Vitis,启动VITIS软件。

29.4软件设计

PS端软件的设计与“PS通过VDMA驱动LCD显示”实验部分相同,有差异的地方在于,加入了EMIO底层配置和驱动函数、触摸芯片底层驱动函数以及主函数中的扫描控制代码。工程源文件的目录结构如下图所示:

图 30.4.1 工程目录结构

我们先来看看main.c源文件,这里就不全部贴出来了,仅介绍main函数,代码如下:

50 int main(void)51 {52//获取LCD的ID53XGpio_Initialize(&axi_gpio_inst,AXI_GPIO_0_ID);54XGpio_SetDataDirection(&axi_gpio_inst,AXI_GPIO_0_CHANEL,0x07); //设置AXI GPIO为输入55lcd_id = LTDC_PanelID_Read(&axi_gpio_inst,AXI_GPIO_0_CHANEL);56XGpio_SetDataDirection(&axi_gpio_inst,AXI_GPIO_0_CHANEL,0x00); //设置AXI GPIO为输出57xil_printf("LCD ID: %x\r\n",lcd_id);58 59//根据获取的LCD的ID号来进行video参数的选择60switch(lcd_id){61case 0x4342 : vd_mode = VMODE_480x272; break; //4.3寸屏,480*272分辨率62case 0x4384 : vd_mode = VMODE_800x480; break; //4.3寸屏,800*480分辨率63case 0x7084 : vd_mode = VMODE_800x480; break; //7寸屏,800*480分辨率64case 0x7016 : vd_mode = VMODE_1024x600; break; //7寸屏,1024*600分辨率65case 0x1018 : vd_mode = VMODE_1280x800; break; //10.1寸屏,1280*800分辨率66default : vd_mode = VMODE_800x480; break;67}68 69emio_init();70 71//配置VDMA72run_vdma_frame_buffer(&vdma, VDMA_ID, vd_mode.width, vd_mode.height,73frame_buffer_addr,0, 0,ONLY_READ);74 75//清空DDR4帧缓存空间76memset(frame_buffer_addr,0xFF,vd_mode.height*vd_mode.width*FRAME_BUFFER_NUM*BYTES_PIXEL);//关闭cache,否则由于对Frame Buffer的写入延时比较大,画板的效果会很慢77Xil_DCacheDisable(); 78 79//设置时钟IP核输出的时钟频率80clk_wiz_cfg(CLK_WIZ_ID,vd_mode.freq);81 82//初始化Display controller83DisplayInitialize(&dispCtrl, DISP_VTC_ID);84//设置VideoMode85DisplaySetMode(&dispCtrl, &vd_mode);86DisplayStart(&dispCtrl);87 88tp_dev.init(); //触摸屏初始化89delay_ms(1500);90 91if (tp_dev.touchtype & 0X80) //如果是电容屏92{93u8 t = 0;94u8 i = 0;95u16 lastpos[10][2]; //最后一次的数据96u8 maxp = 5; //最大触摸点数97if (lcd_id == 0X1018)98 maxp = 10;99while(1)100 {101 //扫描当前触摸屏的触摸状态102 tp_dev.scan();103 104 //对每个触摸点进行处理105 for (t = 0; t < maxp; t++)106 {107 if( (tp_dev.sta) & (1 << t) ) //如果有触摸108 {109 //如果当前触摸点的坐标在LCD的宽度和高度范围内110 if( (tp_dev.x[t] < vd_mode.height) && (tp_dev.y[t] < vd_mode.width) )111 {112if (lastpos[t][0] == 0XFFFF)113{114 lastpos[t][0] = tp_dev.x[t];115 lastpos[t][1] = tp_dev.y[t];116}117lcd_draw_bline( lastpos[t][0] , lastpos[t][1],118 tp_dev.x[t] , tp_dev.y[t],119 2 , POINT_COLOR_TBL[t]120 ); //画线121lastpos[t][0] = tp_dev.x[t];122lastpos[t][1] = tp_dev.y[t];123 124if( (tp_dev.x[t] < 30) && (tp_dev.y[t] < 30) )125{126 //清空DDR4帧缓存空间127 memset(frame_buffer_addr,0xFF,vd_mode.height*vd_mode.width*FRAME_BUFFER_NUM*BYTES_PIXEL);128}129 }130 }131 //如果无触摸132 else133 lastpos[t][0] = 0XFFFF;134 }135 delay_ms(5);136 i++;137 }138}139return 0;140 }

main 函数比较简单,获取LCD的ID,初始化相关外设,输出提示信息。然后判断当前的触摸屏是不是电容屏,如果是,则在while(1)死循环中不断地扫描是否有触摸发生,如代码中第102行所示。我们采用tp_dev.sta来标记当前按下的触摸屏点数,所以判断是否有电容触摸屏按下,也就是判断tp_dev.sta的最低5位,如果有数据,则划线,如果没数据则忽略,且5个点划线的颜色各不一样,方便区分。另外,电容触摸屏不需要校准,所以没有校准程序。

此外还加入清屏功能,如果检测到XY方向的坐标值均小于30,即当前触摸点在屏幕的最边角处,则清除整个Frame Buffer,如代码第124行至128行所示。

APP文件夹下是延时函数的源文件。delay.c文件内容如下:

1 #include "delay.h"2 #include "xil_types.h"3 4 //延时函数,单位毫秒5 void delay_ms(u32 n)6 {7 usleep(n*1000);8 }9 10 //延时函数,单位微秒11 void delay_us(u32 n)12 {13usleep(n);14 }

可以看到,其封装了两个函数,一个延时函数以毫秒为单位,另一个以微秒为单位。

emio_iic_cfg文件夹存放用于EMIO底层配置和驱动函数的源文件。

emio_iic_cfg.c源文件的代码如下所示:

1 #include"emio_iic_cfg.h"2 3 #define GPIOPS_ID XPAR_XGPIOPS_0_DEVICE_ID //PS端 GPIO 器件 ID4 5 static XGpioPs gpiops_inst; //PS 端 GPIO 驱动实例6 7 //EMIO初始化8 void emio_init(void)9 {10XGpioPs_Config *gpiops_cfg_ptr; //PS 端 GPIO 配置信息11 12//根据器件 ID 查找配置信息13gpiops_cfg_ptr = XGpioPs_LookupConfig(GPIOPS_ID);14 15//初始化器件驱动16XGpioPs_CfgInitialize(&gpiops_inst, gpiops_cfg_ptr, gpiops_cfg_ptr->BaseAddr);17 18//设置 iic端口 为输出19XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_SCL_NUM, 1);20XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_SDA_NUM, 1);21XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_CT_RST_NUM, 1);22 23//使能iic端口 输出24XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_SCL_NUM, 1);25XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_SDA_NUM, 1);26XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_CT_RST_NUM, 1);27 28//将iic的SCLK和SDA都拉高29XGpioPs_WritePin(&gpiops_inst, EMIO_SCL_NUM, 1);30XGpioPs_WritePin(&gpiops_inst, EMIO_SDA_NUM, 1);31 }32 33 //设置 iic的SDA端口为输入34 void SDA_IN( void )35 {36XGpioPs_SetOutputEnablePin (&gpiops_inst, EMIO_SDA_NUM, 0);37XGpioPs_SetDirectionPin (&gpiops_inst, EMIO_SDA_NUM, 0);38 }39 40 //设置 iic的SDA端口为输出41 void SDA_OUT( void )42 {43XGpioPs_SetDirectionPin (&gpiops_inst, EMIO_SDA_NUM, 1);44XGpioPs_SetOutputEnablePin (&gpiops_inst, EMIO_SDA_NUM, 1);45 }46 47 //赋值 iic的SCL端口48 void IIC_SCL( u8 x )49 {50XGpioPs_WritePin(&gpiops_inst, EMIO_SCL_NUM, x);51 }52 53 //赋值 iic的SDA端口54 void IIC_SDA( u8 x )55 {56XGpioPs_WritePin(&gpiops_inst, EMIO_SDA_NUM, x);57 }58 59 //读取 iic的SDA端口60 u8 READ_SDA ( void )61 {62return XGpioPs_ReadPin(&gpiops_inst, EMIO_SDA_NUM) ;63 }64 65 //赋值 触摸芯片的RST端口66 void CT_RST( u8 x )67 {68XGpioPs_WritePin(&gpiops_inst, EMIO_CT_RST_NUM, x);69 }70 71 //设置触摸芯片的INT端口的方向72 void INT_DIR( u8 x )73 {74 75XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_CT_INT_NUM, x);76XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_CT_INT_NUM, x);77 }78 79 //赋值触摸芯片的INT端口80 void INT( u8 x )81 {82XGpioPs_WritePin(&gpiops_inst, EMIO_CT_INT_NUM, x);83 }84 85 //读取触摸芯片的INT端口86 u8 INT_RD( void )87 {88return XGpioPs_ReadPin(&gpiops_inst, EMIO_CT_INT_NUM) ;89 }

可以看到,其中包含了对EMIO(包括IIC的SCK和SDA引脚、触摸芯片的RST和INT引脚)进行初始化和操作的各个函数。我们可以在程序中的其他地方直接调用这些函数,以完成所需的功能。

TOUCH文件夹下的文件用于驱动正点原子不同型号的采用IIC协议的电容触摸屏,myiic文件是用C语言模拟的IIC协议。

我们先来看一下使用C语言来模拟IIC协议的myiic.c文件,如下所示:

1 #include "myiic.h"2 #include"../emio_iic_cfg/emio_iic_cfg.h"3 4 //初始化IIC5 void IIC_Init(void)6 {7 IIC_SCL(1);8 IIC_SDA(1);9 }10 11 //产生IIC起始信号12 void IIC_Start(void)13 {14 SDA_OUT(); //sda线输出15 IIC_SDA(1);16 IIC_SCL(1);17 usleep(2);18 IIC_SDA(0); //START信号:SCL为高时,SDA由高变低19 usleep(2);20 IIC_SCL(0); //钳住I2C总线,准备发送或接收数据21 }22 23 //产生IIC停止信号24 void IIC_Stop(void)25 {26 SDA_OUT(); //sda线输出27 IIC_SCL(0);28 IIC_SDA(0); //STOP信号:SCL为高时,SDA由低变高29 usleep(2);30 IIC_SCL(1);31 usleep(2);32 IIC_SDA(1); //发送I2C总线结束信号33 usleep(2);34 }35 36 //等待应答信号到来37 //返回值:1,接收应答失败38 // 0,接收应答成功39 u8 IIC_Wait_Ack(void)40 {41 u8 ucErrTime=0;42 SDA_IN();//SDA设置为输入43 IIC_SDA(1);44 usleep(2);45 IIC_SCL(1);46 usleep(2);47 while( READ_SDA() ) {48 ucErrTime++;49 if(ucErrTime>250) {50IIC_Stop();51return 1;52 }53 }54 IIC_SCL(0);//时钟输出055 return 0;56 }57 58 //产生ACK应答59 void IIC_Ack(void)60 {61 IIC_SCL(0);62 SDA_OUT();63 IIC_SDA(0);64 usleep(2);65 IIC_SCL(1);66 usleep(2);67 IIC_SCL(0);68 }69 70 //不产生ACK应答71 void IIC_NAck(void)72 {73 IIC_SCL(0);74 SDA_OUT();75 IIC_SDA(1);76 usleep(1);77 IIC_SCL(1);78 usleep(1);79 IIC_SCL(0);80 }81 82 //IIC发送一个字节83 //返回从机有无应答84 //1,有应答85 //0,无应答86 void IIC_Send_Byte(u8 txd)87 {88 u8 t;89 SDA_OUT();90 IIC_SCL(0);//拉低时钟开始数据传输91 for(t=0; t<8; t++) {92 IIC_SDA((txd&0x80)>>7);93 txd<<=1;94 usleep(1);95 IIC_SCL(1);96 usleep(1);97 IIC_SCL(0);98 usleep(1);99 }100 }101 102 //读1个字节,ack=1时,发送ACK,ack=0,发送nACK103 u8 IIC_Read_Byte(unsigned char ack)104 {105unsigned char i,receive=0;106SDA_IN();//SDA设置为输入107for(i=0; i<8; i++ ) {108IIC_SCL(0);109usleep(1);110IIC_SCL(1);111receive<<=1;112if( READ_SDA() )113 receive++;114usleep(1);115}116if (!ack)117IIC_NAck();//发送nACK118else119IIC_Ack(); //发送ACK120return receive;121 }

该部分为IIC驱动代码,实现包括IIC的初始化(IO口)、IIC开始、IIC结束、ACK、IIC读写等功能,在其他函数里面,只需要调用相关的IIC函数就可以和外部IIC器件通信了,这里并不局限于TOUCH的驱动,该段代码可以用在任何IIC设备上。

接下来我们看一下电容触摸屏的驱动代码。touch.h头文件的代码如下所示:

1 #ifndef __TOUCH_H__2 #define __TOUCH_H__3 4 #include "gt9147.h"5 #include "gt9271.h"6 #include "ft5206.h"7 #include "xil_types.h"8 9 #define TP_PRES_DOWN 0x8000//触屏被按下10 #define TP_CATH_PRES 0x4000//有按键按下了11 #define CT_MAX_TOUCH 10 //电容屏支持的点数,固定为10点12 13 #define PEN IORD_ALTERA_AVALON_PIO_DATA(TOUCH_INT_BASE) //T_PEN14 15 //触摸屏控制器16 typedef struct17 {18u8 (*init)(void); //初始化触摸屏控制器19u8 (*scan)(void); //扫描触摸屏.0,屏幕扫描;1,物理坐标;20u16 x[CT_MAX_TOUCH]; //当前坐标21u16 y[CT_MAX_TOUCH]; 22//电容屏最多有10组坐标,电阻屏则用x[0],y[0]代表:此次扫描时,触屏的坐标,用23 //x[9],y[9]存储第一次按下时的坐标.24u16 sta;//笔的状态25 //b15:按下1/松开0;26 //b14:0,没有按键按下;1,有按键按下.27 //b13~b10:保留28 //b9~b0:电容触摸屏按下的点数(0,表示未按下,1表示按下)29 //新增的参数,当触摸屏的左右上下完全颠倒时需要用到.30 //b0:0,竖屏(适合左右为X坐标,上下为Y坐标的TP)31 // 1,横屏(适合左右为Y坐标,上下为X坐标的TP)32 //b1~6:保留.33 //b7:0,电阻屏34 // 1,电容屏35u8 touchtype;36 }_m_tp_dev;37 38 extern _m_tp_dev tp_dev; //触屏控制器在touch.c里面定义39 40 //电容屏 共用函数41 u8 TP_Scan(); //扫描42 void TP_Init(void); //初始化43 44 #endif

上述代码,我们重点看看_m_tp_dev结构体,该结构体用于管理和记录触摸屏相关信息。通过结构体,在使用的时候,我们一般直接调用tp_dev的相关成员函数/变量,即可达到需要的效果,这种设计简化了接口,且方便管理和维护,大家可以效仿一下。

现在,我们看一下touch.c里面的代码:

1 #include "touch.h"2 #include "../APP/delay.h"3 #include "stdlib.h"4 #include "math.h"5 #include "../main.h"6 #include "xil_types.h"7 #include"../emio_iic_cfg/emio_iic_cfg.h"8 9 _m_tp_dev tp_dev= {10TP_Init,11TP_Scan,120,130,140,150,16 };17 18 //触摸按键扫描19 //tp:0,屏幕坐标;1,物理坐标(校准等特殊场合用)20 //返回值:当前触屏状态.21 //0,触屏无触摸;1,触屏有触摸22 u8 TP_Scan(void)23 {24if( INT_RD() == 0 ) //如果有按键按下25{26if( (tp_dev.sta & TP_PRES_DOWN) == 0 )//之前没有被按下27{28 tp_dev.sta = TP_PRES_DOWN | TP_CATH_PRES ; //按键按下29 30 tp_dev.x[4] = tp_dev.x[0]; //记录第一次按下时的坐标31 tp_dev.y[4] = tp_dev.y[0];32}33printf("tp_dev.sta = %X\r\n",tp_dev.sta);34}35else //如果无按键按下36{37if(tp_dev.sta & TP_PRES_DOWN) //之前是被按下的38{39 tp_dev.sta &= ~(1<<7) ; //标记按键松开40}41else42{//之前就没有被按下43 tp_dev.x[4]=0;44 tp_dev.y[4]=0;45 tp_dev.x[0]=0xffff;46 tp_dev.y[0]=0xffff;47}48}49printf("tp_dev.sta = %X\r\n",tp_dev.sta);50return tp_dev.sta & TP_PRES_DOWN ; //返回当前的触屏状态51 }52 53 //触摸屏初始化54 void TP_Init(void)55 {56if( lcd_id==0X5510 || lcd_id==0X4342 || lcd_id==0X4384) //电容触摸屏,4.3寸屏57{58GT9147_Init();59tp_dev.scan=GT9147_Scan; //扫描函数指向GT9147触摸屏扫描60tp_dev.touchtype|=0X80; //电容屏61}62//SSD1963 7寸屏或者 7寸800*480/1024*600 RGB屏63else if( lcd_id==0X1963 || lcd_id==0X7084 || lcd_id==0X7016) {64FT5206_Init();65tp_dev.scan=FT5206_Scan; //扫描函数指向FT5206触摸屏扫描66tp_dev.touchtype|=0X80; //电容屏67} else if( lcd_id==0X1018 ) {68GT9271_Init();69tp_dev.scan=GT9271_Scan; //扫描函数指向GT9271触摸屏扫描70tp_dev.touchtype|=0X80; //电容屏71}72 }

第56行的TP_Init()函数根据LCD的ID(即main()函数中的lcd_id变量)判别不同的电容屏,然后执行不同的初始化。该函数比较简单,重点说一下:tp_dev.scan,这个结构体函数指针,默认是指向TP_Scan的,如果是电阻屏则用默认的即可;如果是电容屏,则指向新的扫描函数GT9147_Scan或FT5206_Scan(根据芯片ID判断到底指向那个),执行电容触摸屏的扫描函数。

因为GT9147和GT9271为同一系列的触摸屏,除配置参数有差别外,其它的差别不大,而FT5206的代码与GT9147的差别在于无需通过int引脚来选择不同的i2c器件地址,也无需配置繁杂的寄存器,其余的和GT9147差别不大。所以下面我们主要针对GT9147的驱动进行介绍。

首先我们看下gt9147.h代码,如下:

1 #ifndef __GT9147_H2 #define __GT9147_H3 4 #include "xil_types.h"5 6 //I2C读写命令7 #define GT_CMD_WR 0X28 //写命令8 #define GT_CMD_RD 0X29 //读命令9 10 //GT9147 部分寄存器定义11 #define GT_CTRL_REG0X8040//GT9147控制寄存器12 #define GT_CFGS_REG0X8047//GT9147配置起始地址寄存器13 #define GT_CHECK_REG 0X80FF//GT9147校验和寄存器14 #define GT_PID_REG0X8140//GT9147产品ID寄存器15 16 #define GT_GSTID_REG 0X814E//GT9147当前检测到的触摸情况17 #define GT_TP1_REG0X8150//第一个触摸点数据地址18 #define GT_TP2_REG0X8158//第二个触摸点数据地址19 #define GT_TP3_REG0X8160//第三个触摸点数据地址20 #define GT_TP4_REG0X8168//第四个触摸点数据地址21 #define GT_TP5_REG0X8170//第五个触摸点数据地址22 23 u8 GT9147_Send_Cfg(u8 mode);//发送GT9147配置参数24 u8 GT9147_WR_Reg(u16 reg,u8 *buf,u8 len); //向GT9147写入一次数据25 void GT9147_RD_Reg(u16 reg,u8 *buf,u8 len); //从GT9147读出一次数据26 u8 GT9147_Init(void); //初始化GT9147触摸屏27 u8 GT9147_Scan(u8 mode);//扫描触摸屏(采用查询方式)28 29 #endif在该头文件中我们宏定义了GT9147的寄存器地址,并声明了GT9147的操作函数,主要用于方便修改和管理。接下来看下gt9147.c里面的代码,这里我们仅介绍GT9147_Init和GT9147_Scan两个函数,代码如下:98 u8 GT9147_Init(void)99 {100u8 temp[5];101INT_DIR(1); //TOUCH_INT引脚设置为输出102INT(1);//TOUCH_INT输出为1103IIC_Init(); //初始化电容屏的I2C总线104CT_RST(0); //复位105delay_ms(10);106CT_RST(1); //释放复位107delay_ms(10);108INT_DIR(0);109delay_ms(100);110GT9147_RD_Reg(GT_PID_REG,temp,4);//读取产品ID111temp[4]=0;112xil_printf("CTP ID:%s\r\n",temp); //打印ID113if(strcmp((char*)temp,"9147")==0) {//ID==9147114 temp[0]=0X02;115 GT9147_WR_Reg(GT_CTRL_REG,temp,1);//软复位GT9147116 GT9147_RD_Reg(GT_CFGS_REG,temp,1);//读取GT_CFGS_REG寄存器117 if(temp[0]<0X60) {//默认版本比较低,需要更新flash配置118 if(lcd_id==0X5510)119 GT9147_Send_Cfg(1);//仅4.3寸MCU屏,更新并保存配置120 }121 delay_ms(10);122 temp[0]=0X00;123 GT9147_WR_Reg(GT_CTRL_REG,temp,1);//结束复位124 return 0;125}126return 0;127 }128

以上代码,GT9147_Init用于初始化GT9147,该函数通过读取0X8140~0X8143这4个寄存器,并判断是否是:“9147”,来确定是不是GT9147芯片,在读取到正确的ID后,软复位GT9147,然后根据当前芯片版本号,确定是否需要更新配置,通过GT9147_Send_Cfg函数,发送配置信息(一个数组),配置完后,结束软复位,即完成GT9147初始化。

132 //扫描触摸屏(采用查询方式)133 //mode:0,正常扫描.134 //返回值:当前触屏状态.135 //0,触屏无触摸;1,触屏有触摸136 u8 GT9147_Scan(u8 mode)137 {138u8 buf[4];139u8 i=0;140u8 res=0;141u16 temp;142u16 tempsta;143static u8 t=0;//控制查询间隔,从而降低CPU占用率144t++;145if((t%10)==0||t<10)//空闲时,每进入10次CTP_Scan函数才检测1次,从而节省CPU使用率146{147 GT9147_RD_Reg(GT_GSTID_REG,&mode,1); //读取触摸点的状态148 if(mode&0X80&&((mode&0XF)<6))149 {150 i=0;151 GT9147_WR_Reg(GT_GSTID_REG,&i,1);//清标志152 }153 if((mode&0XF)&&((mode&0XF)<6))154 {155 temp=0XFFFF<<(mode&0XF); //将点的个数转换为1的位数,匹配tp_dev.sta定义156 tempsta=tp_dev.sta; //保存当前的tp_dev.sta值157 tp_dev.sta=(~temp)|TP_PRES_DOWN|TP_CATH_PRES;158 tp_dev.x[4]=tp_dev.x[0]; //保存触点0的数据159 tp_dev.y[4]=tp_dev.y[0];160 for(i=0;i<5;i++)161 {162 if(tp_dev.sta&(1<<i)) //触摸有效?163 {164 GT9147_RD_Reg(GT9147_TPX_TBL[i],buf,4); //读取XY坐标值165 if(lcd_id==0X4384) //4.3寸800*480 RGB屏166 {167if(tp_dev.touchtype&0X01)//横屏168{169 tp_dev.x[i]=((u16)buf[1]<<8)+buf[0];170 tp_dev.y[i]=((u16)buf[3]<<8)+buf[2];171 172}else173{174 tp_dev.y[i]=((u16)buf[1]<<8)+buf[0];175 tp_dev.x[i]=480-(((u16)buf[3]<<8)+buf[2]);176}177 }else if(lcd_id==0X4342) //4.3寸480*272 RGB屏178 {179if(tp_dev.touchtype&0X01)//横屏180{181 tp_dev.x[i]=(((u16)buf[1]<<8)+buf[0]);182 tp_dev.y[i]=(((u16)buf[3]<<8)+buf[2]);183}else184{185 tp_dev.y[i]=((u16)buf[1]<<8)+buf[0];186 tp_dev.x[i]=272-(((u16)buf[3]<<8)+buf[2]);187}188 }189 }190 }191 res=1;192 if(tp_dev.x[0] > vd_mode.height || tp_dev.y[0] > vd_mode.width) (坐标超出了)193 {194 if((mode&0XF)>1) //其他点有数据,则复第二个触点的数据到第一个触点.195 {196 tp_dev.x[0]=tp_dev.x[1];197 tp_dev.y[0]=tp_dev.y[1];198 t=0;//触发一次,则会最少连续监测10次,从而提高命中率199 }else //非法数据,则忽略此次数据(还原原来的)200 {201 tp_dev.x[0]=tp_dev.x[4];202 tp_dev.y[0]=tp_dev.y[4];203 mode=0X80;204 tp_dev.sta=tempsta; //恢复tp_dev.sta205 }206 }else t=0; //触发一次,则会最少连续监测10次,从而提高命中率207 }208}209if((mode&0X8F)==0X80)//无触摸点按下210{211 if(tp_dev.sta&TP_PRES_DOWN)//之前是被按下的212 {213 tp_dev.sta&=~TP_PRES_DOWN; //标记按键松开214 }else //之前就没有被按下215 {216 tp_dev.x[0]=0xffff;217 tp_dev.y[0]=0xffff;218 tp_dev.sta&=0XE000; //清除点有效标记219 }220}221if(t>240)t=10;//重新从10开始计数222return res;223 }

GT9147_Scan函数用于扫描电容触摸屏是否有按键按下,由于我们不是用的中断方式来读取GT9147的数据的,而是采用查询的方式,所以这里使用了一个静态变量来提高效率,当无触摸的时候,尽量减少对CPU的占用,当有触摸的时候,又保证能迅速检测到。对GT9147数据的读取,先读取手势ID寄存器(GT_GSTID_REG),判断是不是有有效数据,如果有,则读取,否则直接忽略,继续后面的处理。

软件部分就介绍到这里,接下来看看下载验证。

29.5下载验证

讲完了软件工程,接下来我们就将该实验下载至我们的MPSOC开发板进行验证。

首先我们将4.3寸的ATK-4.3’TFT LCD与开发板上的RGB LCD接口连接。然后将下载器与开发板上的JTAG接口连接,下载器另外一端与电脑连接。再使用USB连接线将USB_UART(开发板PS PORT)接口与电脑连接,用于串口通信。最后连接开发板的电源,给开发板上电。

我们在VITIS软件中将程序下载至我们的MPSOC开发板,手指在触摸屏上划动时显示划动的轨迹(写一个“正”字),结果如下图所示。

图 30.5.1 实验结果图

至此,我们的RGB LCD触摸屏实验就完成了。

【正点原子FPGA连载】 第二十九章TFT LCD画板实验 摘自【正点原子】DFZU2EG_4EV MPSoC之嵌入式Vitis开发指南

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。