【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

这篇具有很好参考价值的文章主要介绍了【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1)实验平台:正点原子MPSoC开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html

第二十章 LCD触摸屏实验

现在几乎所有智能手机,包括平板电脑都是采用电容屏作为触摸屏,电容屏是利用人体感应进行触点检测控制,不需要直接接触或只需要轻微接触,通过检测感应电流来定位触摸坐标。在本章中,我们将向大家介绍 FPGA控制LCD 电容触摸模块,实现触摸屏驱动,即用手指触碰LCD屏幕时,对应触摸点的坐标会显示在LCD屏幕上。
本章包括以下几个部分:
2020.1简介
20.2实验任务
20.3硬件设计
20.4程序设计
20.5下载验证
20.1简介
目前最常用的触摸屏有两种:电阻式触摸屏和电容式触摸屏。下面,我们来分别介绍这两种触摸屏。
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。
电阻式触摸屏就介绍到这里。
2)电容式触摸屏
现在几乎所有智能手机,包括平板电脑都是采用电容屏作为触摸屏,电容屏是利用人体感应进行触点检测控制,不需要直接接触或只需要轻微接触,通过检测感应电流来定位触摸坐标。
正点原子4.3/7/10.1寸LCD模块自带的触摸屏采用的是电容式触摸屏,下面简单介绍下电容式触摸屏的原理。
电容式触摸屏主要分为两种:
1、表面电容式电容触摸屏。
表面电容式触摸屏技术是利用ITO(铟锡氧化物,一种透明的导电材料)导电膜,通过电场感应方式感测屏幕表面的触摸行为进行。但是表面电容式触摸屏有一些局限性,它只能识别一个手指或者一次触摸。
2、投射式电容触摸屏
投射电容式触摸屏是传感器利用触摸屏电极发射出静电场线。一般用于投射电容传感技术的电容类型有两种:自我电容和交互电容。
自我电容又称绝对电容,是最广为采用的一种方法,自我电容通常是指扫描电极与地构成的电容。在玻璃表面有用ITO制成的横向与纵向的扫描电极,这些电极和地之间就构成一个电容的两极。当用手或触摸笔触摸的时候就会并联一个电容到电路中去,从而使在该条扫描线上的总体的电容量有所改变。在扫描的时候,控制IC依次扫描纵向和横向电极,并根据扫描前后的电容变化来确定触摸点坐标位置。笔记本电脑触摸输入板就是采用的这种方式,笔记本电脑的输入板采用XY的传感电极阵列形成一个传感格子,当手指靠近触摸输入板时,在手指和传感电极之间产生一个小量电荷。采用特定的运算法则处理来自行、列传感器的信号,以此确定手指的位置。
交互电容又叫做跨越电容,它是在玻璃表面的横向和纵向的ITO电极的交叉处形成电容。交互电容的扫描方式就是扫描每个交叉处的电容变化,来判定触摸点的位置。当触摸的时候就会影响到相邻电极的耦合,从而改变交叉处的电容量,交互电容的扫面方法可以侦测到每个交叉点的电容值和触摸后电容变化,因而它需要的扫描时间与自我电容的扫描方式相比要长一些,需要扫描检测X
Y根电极。目前智能手机/平板电脑等的触摸屏,都是采用交互电容技术。
正点原子所选择的电容触摸屏,采用的是投射式电容屏(交互电容类型),所以后面仅以投射式电容屏作为介绍。
透射式电容触摸屏采用纵横两列电极组成感应矩阵来感应触摸。以两个交叉的电极矩阵(X轴电极和Y轴电极)来检测每一格感应单元的电容变化,如下图所示:
【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

图 20.1.1 投射式电容屏电极矩阵示意图
示意图中的电极,实际是透明的,这里是为了方便大家理解故填充了颜色。图中,X、Y轴的透明电极电容屏的精度、分辨率与X、Y轴的通道数有关,通道数越多,精度越高。以上就是电容触摸屏的基本原理,接下来看看电容触摸屏的优缺点:
电容触摸屏的优点:手感好、无需校准、支持多点触摸、透光性好。
电容触摸屏的缺点:成本高、精度不高、抗干扰能力差。
这里特别提醒大家电容触摸屏对工作环境的要求是比较高的,在潮湿、多尘、高低温环境下面,都是不适合使用电容屏的。
电容触摸屏通常需要一个驱动IC来检测电容触摸,且一般是通过IIC接口输出触摸数据的。正点原子不同尺寸和分辨率的触摸屏较多,每款屏幕都配有触摸芯片,不同的触摸屏,配备了相同或者不同的触摸芯片,即使是相同的触摸屏,也有可能配备的是不同的触摸芯片,至于触摸芯片的具体型号,可以查看触摸屏背面触摸芯片上的丝印。对于7寸RGB LCD屏1204600的分辨率来说,使用过的触摸芯片有CST340、FT5206和GT911,CST340和FT5206的驱动是兼容的,而GT911和这两款芯片驱动不兼容,因此在程序设计的时候,需要先获取触摸芯片的ID或者版本号,采用不同的时序来驱动触摸屏。需要注意的是,后续不排除继续更换触摸芯片,因为众所周知的原因,部分芯片可能会缺货或者价格高到非常离谱,因此可能会采用其它替换方案。不过大家不用担心,在寻找替换方案时,一般会采用驱动能够兼容的芯片,即使不兼容,我们也会及时更新触摸屏的驱动程序。
正点原子RGB LCD液晶屏使用的触摸芯片整体上分为GT系列(如GT911、GT1151等)和FT系列(如FT5206、FT5426等),GT系列和FT系列的寄存器不兼容,但是不同GT系列芯片之间的寄存器是兼容的,同样的,不同FT系列芯片之间的寄存器也是兼容的,因此我们只需要学习GT系列中的一款芯片和FT系列中的一款芯片即可。
4.3寸800
480的RGB LCD屏使用的触摸芯片为GT911(最新的屏幕可能会替换成其它触摸芯片),这里以GT911为例,讲解GT系列触摸芯片的驱动方法,而其它触摸芯片的使用方法非常类似,详情可以查看相关芯片的数据手册。
下面我们简单介绍下GT911,该芯片是深圳汇顶科技研发的一颗电容触摸屏驱动IC,支持100Hz触点扫描频率,支持5点触摸,支持18*10个检测通道,适合小于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上电设置器件地址的时序图如下图所示:
【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

图 20.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)
该寄存器各个位描述如下表所示:
表 20.1.1 寄存器定义
寄存器 bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
0X814E buffer状态 large detect 接近有效 按键 有效触点个数
这里,我们仅关心最高位和最低4位,最高位用于表示buffer状态,如果有数据(坐标/按键),buffer就会是1,最低4位用于表示有效触点的个数,范围是:0~5,0表示没有触摸,5表示有5点触摸。最后,该寄存器在每次读取后,如果bit7有效,则必须写0,清除这个位,否则不会输出下一次数据!!这个要特别注意!!!
5,坐标数据寄存器(共20个)
这里共分成5组(5个点),每组4个寄存器存储数据,以触点1的坐标数据寄存器组为例,如下表所示:
表 20.1.2 触点1坐标寄存器组描述
寄存器 bit7~0 寄存器 bit7~0
0X8150 触点1X坐标低8位 0X8151 触点1 X坐标高8位
0X8152 触点1Y坐标低8位 0X8153 触点1 Y坐标高8位
我们一般只用到触点的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的工作模式,该寄存器的描述如下:
表 20.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的中断状态,该寄存器的描述如下:
表 20.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的中断状态,该寄存器的描述如下:
表 20.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的激活周期,该寄存器的描述如下:
表 20.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的触摸状态,该寄存器的描述如下:
表 20.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寄存器,该寄存器的描述如下:
表 20.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位,因此在程序设计时需要特别留意。
20.2实验任务
本节的实验任务是使用DFZU2EG/4EV MPSoC开发板驱动LCD显示屏,用手触摸显示屏,在屏幕上显示触摸点的坐标。
20.3硬件设计
MPSoC板载的LCD接口原理图如图 20.3.1所示。
【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

图 20.3.1 LCD接口原理图
上图中的关于LCD显示部分的引脚就不再介绍了,这里我们主要看下CT_RST、IIC2_SDA、IIC2_SCL、CT_INT四个引脚,这四个引脚分别连接到了GT911的RST、SDA、SCL和INT四根引脚,我们在代码中通过控制这四个引脚来初始化GT911芯片或者和GT911进行数据交互。
本实验中,各端口信号的管脚分配(由于引脚比较多,这里只给出了GT911的控制引脚,详细引脚请参考例程提供的XDC文件)如下表所示:
表 20.3.1 触摸显示实验管脚分配
【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

对应的约束语句(GT911的引脚约束语句)如下所示:
set_property -dict {PACKAGE_PIN G11 IOSTANDARD LVCMOS33} [get_ports touch_scl]
set_property -dict {PACKAGE_PIN H12 IOSTANDARD LVCMOS33} [get_ports touch_sda]
set_property -dict {PACKAGE_PIN F12 IOSTANDARD LVCMOS33} [get_ports touch_int]
set_property -dict {PACKAGE_PIN H11 IOSTANDARD LVCMOS33} [get_ports touch_rst_n]
20.4程序设计
根据实验任务我们画出了如下的程序框图:
【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

图 20.4.1 LCD触摸实验程序框图
由上图可知,时钟IP核模块为其余模块提供驱动时钟;由于LCD屏的触摸接口是IIC接口,因此通过IIC驱动模块实现整个IIC通信协议的功能,而LCD触摸驱动模块负责整个LCD屏触摸ID的确定、初始化、以及获取触摸点坐标等操作。最后,我们还需要将获取到的触摸点坐标显示在RGB LCD液晶屏上,这部分功能由RGB LCD字符显示模块来实现。
本次实验的RTL视图如下图所示:
【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

图 20.4.2 RTL视图
由上图可知,FPGA顶层模块例化了以下三个模块,分别是时钟IP核模块(pll)、触摸顶层模块(touch_top)和LCD字符显示模块(lcd_rgb_char)。各模块功能如下:
顶层模块(top_lcd_touch):顶层模块主要完成各个模块的例化,实现各模块之间的数据交互。
时钟IP核模块(pll):时钟IP核模块通过调用开发工具官方IP核实现,输出一个50Mhz的时钟,输入的时钟经时钟IP核后,时钟的抖动和偏斜更小。
触摸顶层模块(touch_top):触摸顶层模块实现了整个RGB LCD屏的触摸驱动,输出触摸点的坐标,该模块例化了IIC驱动模块和LCD触摸驱动模块。
RGB LCD字符显示模块(lcd_rgb_char):RGB LCD字符显示模块实现了将输入的触摸点坐标,显示在RGB LCD液晶屏的功能。该模块的代码和程序设计思路和“RTC实时时钟LCD显示实验”非常类似,因此可以参考该实验。
其中顶层例化模块(top_lcd_touch)模块代码如下:

1  module top_lcd_touch(
2      //时钟和复位接口                                          
3      input                sys_clk_p ,  //系统差分输入时钟      
4      input                sys_clk_n ,  //系统差分输入时钟       
5      input                sys_rst_n,  //按键复位
6      //TOUCH 接口                 
7      inout            touch_sda,  //TOUCH IIC数据
8      output           touch_scl,  //TOUCH IIC时钟
9      inout            touch_int,  //TOUCH INT信号
10     output           touch_rst_n,//TOUCH 复位信号
11     //RGB LCD接口
12     output           lcd_de,     //LCD 数据使能信号
13     output           lcd_hs,     //LCD 行同步信号
14     output           lcd_vs,     //LCD 场同步信号
15     output           lcd_bl,     //LCD 背光控制信号
16     output           lcd_rst_n,  //LCD 复位
17     output           lcd_clk,    //LCD 像素时钟
18     inout    [23:0]  lcd_rgb     //LCD RGB颜色数据
19 );
20 
21 //wire define
22 wire          clk_50m      ;
23 wire          locked       ;
24 wire          rst_n        ;
25 
26 wire          touch_int_in ;
27 wire          touch_int_dir;
28 wire          touch_int_out;
29 wire          touch_sda_in ;
30 wire          touch_sda_out;
31 wire          touch_sda_dir;
32 
33 wire  [31:0]  data         ;
34 wire  [15:0]  lcd_id       ;
35 wire          touch_valid  ;
36 wire  [15:0]  tp_x_coord   ;
37 wire  [15:0]  tp_y_coord   ;
38 
39 //*****************************************************
40 //**                    main code
41 //*****************************************************
42 
43 assign rst_n = sys_rst_n & locked;
44 assign data = {tp_x_coord,tp_y_coord};
45 assign lcd_rst_n = 1'b1;
46 assign touch_sda = touch_sda_dir ? touch_sda_out : 1'bz;
47 assign touch_sda_in = touch_sda;
48 assign touch_int = touch_int_dir ? touch_int_out : 1'bz;
49 assign touch_int_in = touch_int;
50     
51 //例化锁相环
52   clk_wiz_0 u_clk_wiz_0
53    (
54     // Clock out ports
55     .clk_50m(clk_50m),     // output clk_50m
56     // Status and control signals
57     .reset(~sys_rst_n), // input reset
58     .locked(locked),       // output locked
59    // Clock in ports
60     .clk_in1_p(sys_clk_p),    // input clk_in1_p
61     .clk_in1_n(sys_clk_n));    // input clk_in1_n
62 
63 //触摸驱动顶层模块    
64 touch_top  u_touch_top(
65     .clk            (clk_50m),
66     .rst_n          (rst_n),
67 
68     .touch_rst_n    (touch_rst_n  ),
69     .touch_int_in   (touch_int_in ),
70     .touch_int_dir  (touch_int_dir),
71     .touch_int_out  (touch_int_out),
72     .touch_scl      (touch_scl    ),
73     .touch_sda_in   (touch_sda_in ),
74     .touch_sda_out  (touch_sda_out),
75     .touch_sda_dir  (touch_sda_dir),
76 
77     .lcd_id         (lcd_id     ),
78     .touch_valid    (touch_valid),
79     .tp_x_coord     (tp_x_coord ),
80     .tp_y_coord     (tp_y_coord )
81     ); 
82 
83 //例化LCD显示模块
84 lcd_rgb_char  u_lcd_rgb_char
85 (
86    .sys_clk         (clk_50m),
87    .sys_rst_n       (rst_n  ),
88    .data            (data   ),     //触摸点坐标
89    //RGB LCD接口 
90    .lcd_id          (lcd_id),
91    .lcd_hs          (lcd_hs),       //LCD 行同步信号
92    .lcd_vs          (lcd_vs),       //LCD 场同步信号
93    .lcd_de          (lcd_de),       //LCD 数据输入使能
94    .lcd_rgb         (lcd_rgb),      //LCD RGB颜色数据
95    .lcd_bl          (lcd_bl),       //LCD 背光控制信号
96    .lcd_clk         (lcd_clk)       //LCD 采样时钟
97 );
98 
99 endmodule 

在代码的第44行,将两个16位的X方向坐标和Y方向坐标,赋值给32位的data,data是RGB LCD字符显示模块的输入端口,该模块会将data的值显示在RGB LCD液晶屏上(分两行显示,第一行显示X方向的坐标,第二行显示Y方向的坐标)。
第46行至49行是对双向信号的处理,LCD触摸端口的中断引脚(touch_int)和IIC的SDA引脚(touch_sda)是双向引脚,这里根据_dir方向控制信号,来切换双向引脚的方向,即赋值_out或者高阻态。
触摸顶层模块实现了整个RGB LCD屏的触摸驱动,并输出触 摸点的坐标,其模块端口及信号连接如下图所示:
【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

图 20.4.3 触摸顶层模块RTL视图
IIC驱动模块(i2c_dri_m):该模块实现了IIC通信协议的功能。需要说明的是,该模块是在“EEPROM读写测试实验”中的IIC驱动模块(i2c_dri)的基础上做了修改,增加了对IIC寄存器连续读写的功能,以提高IIC的读写效率,关于该模块的详细介绍以及IIC的时序可以参考“EEPROM读写测试实验” 。
LCD触摸驱动模块(touch_dri):LCD触摸驱动模块实现了触摸屏ID的获取、初始化、配置、获取触摸点坐标等功能。
触摸顶层模块(touch_top)的代码如下:

1   module touch_top(
2       input             clk          ,
3       input             rst_n        ,
4       //LCD触摸相关信号              
5       output            touch_rst_n  , //触摸屏复位
6       input             touch_int_in , //INT输入信号
7       output            touch_int_dir, //INT方向控制信号
8       output            touch_int_out, //INT输出信号    
9       output            touch_scl    , //I2C的SCL时钟信号
10      input             touch_sda_in , //I2C的SDA输入信号
11      output            touch_sda_out, //I2C的SDA输出信号
12      output            touch_sda_dir, //I2C的SDA方向控制   
13      //用户端口
14      input     [15:0]  lcd_id       , //LCD ID
15      output            touch_valid  , //触摸标志
16      output    [15:0]  tp_x_coord   , //X方向触摸点的坐标
17      output    [15:0]  tp_y_coord     //Y方向触摸点的坐标          
18  );
19  
20  //parameter define
21  parameter CLK_FREQ = 50_000_000   ;  //i2c_dri模块的驱动时钟频率(CLK_FREQ)
22  parameter I2C_FREQ = 250_000      ;  //I2C的SCL时钟频率
23  parameter REG_NUM_WID = 8         ;  //一次读写寄存器的个数的位宽
24                                       
25  //wire define                        
26  wire  [6:0]             slave_addr;  //器件地址
27  wire                    i2c_exec  ;  //I2C触发执行信号
28  wire                    i2c_rh_wl ;  //I2C读写控制信号
29  wire  [15:0]            i2c_addr  ;  //I2C器件内地址
30  wire  [7:0]             i2c_data_w;  //I2C要写的数据
31  wire                    bit_ctrl  ;  //字地址位控制(0:8b,1:16b)
32  wire  [REG_NUM_WID-1:0] reg_num   ;  //一次读写寄存器的个数  
33  wire  [7:0]             i2c_data_r;  //I2C读出的数据
34  wire                    i2c_done  ;  //I2C操作完成
35  wire                    once_done ;  //一次读写操作完成
36  wire                    i2c_ack   ;  //应答标志
37  wire                    dri_clk   ;  //I2C驱动时钟
38  
39  //*****************************************************
40  //**                    main code
41  //*****************************************************
42  
43  //I2C驱动模块
44  i2c_dri_m  #(
45      .CLK_FREQ      (CLK_FREQ),       //i2c_dri模块的驱动时钟频率(CLK_FREQ)
46      .I2C_FREQ      (I2C_FREQ),       //I2C的SCL时钟频率
47      .WIDTH         (REG_NUM_WID)     //一次读写寄存器的个数的位宽
48      )                             
49      u_i2c_dri_m(   
50      .clk           (clk          ),  //i2c_dri模块的驱动时钟(CLK_FREQ)
51      .rst_n         (rst_n        ),  //复位信号
52                                   
53      .slave_addr    (slave_addr   ),  //器件地址
54      .i2c_exec      (i2c_exec     ),  //I2C触发执行信号
55      .i2c_rh_wl     (i2c_rh_wl    ),  //I2C读写控制信号
56      .i2c_addr      (i2c_addr     ),  //I2C器件内地址
57      .i2c_data_w    (i2c_data_w   ),  //I2C要写的数据
58      .bit_ctrl      (bit_ctrl     ),  //字地址位控制(16b/8b)
59      .reg_num       (reg_num      ),  //一次读写寄存器的个数          
60      .i2c_data_r    (i2c_data_r   ),  //I2C读出的数据
61      .i2c_done      (i2c_done     ),  //I2C操作完成
62      .once_done     (once_done    ),  //一次读写操作完成
63      .scl           (touch_scl    ),  //I2C的SCL时钟信号
64      .sda_in        (touch_sda_in ),  //I2C的SDA输入信号
65      .sda_out       (touch_sda_out),  //I2C的SDA输出信号
66      .sda_dir       (touch_sda_dir),  //I2C的SDA方向控制   
67      .ack           (i2c_ack      ),  //应答标志
68                                   
69      .dri_clk       (dri_clk      )   //驱动I2C操作的驱动时钟
70      );
71  
72  //触摸驱动模块    
73  touch_dri  #(
74      .WIDTH   (REG_NUM_WID)  //一次读写寄存器的个数的位宽     
75       )
76      u_touch_dri(
77      .clk           (dri_clk       ),  //时钟信号
78      .rst_n         (rst_n         ),  //复位信号(低有效)
79                                    
80      .slave_addr    (slave_addr    ),  //i2c器件地址
81      .i2c_exec      (i2c_exec      ),  //i2c触发控制
82      .i2c_rh_wl     (i2c_rh_wl     ),  //i2c读写控制
83      .i2c_addr      (i2c_addr      ),  //i2c操作地址
84      .i2c_data_w    (i2c_data_w    ),  //i2c写入的数据
85      .bit_ctrl      (bit_ctrl      ),  //位控制信号 
86      .reg_num       (reg_num       ),  //一次读写寄存器的个数
87                                    
88      .i2c_data_r    (i2c_data_r    ),  //i2c读出的数据
89      .i2c_ack       (i2c_ack       ),  //i2c应答信号
90      .i2c_done      (i2c_done      ),  //i2c操作结束标志
91      .once_done     (once_done     ),  //一次读写操作完成    
92       
93      .lcd_id        (lcd_id        ),  //LCD ID
94      .touch_valid   (touch_valid   ),  //触摸标志
95      .tp_x_coord    (tp_x_coord    ),  //X方向触摸点的坐标
96      .tp_y_coord    (tp_y_coord    ),  //Y方向触摸点的坐标   
97      .touch_rst_n   (touch_rst_n   ),  //触摸屏复位
98      .touch_int_in  (touch_int_in  ),  //INT输入信号
99      .touch_int_dir (touch_int_dir ),  //INT方向控制信号
100     .touch_int_out (touch_int_out )   //INT输出信号
101     );
102 
103 endmodule

在代码的第21行定义了该模块输入系统时钟的参数,为50_000_000,表示50Mhz;第22行定义了I2C时钟的频率为250Khz,一般该时钟频率要小于400Khz;第23行定义了一次读写寄存器个数的位宽,该值为8。
IIC驱动模块和LCD触摸驱动模块之间通过I2C的用户接口进行交互,需要注意的是,IIC驱动模块输出的dri_clk作为LCD触摸驱动模块的输入操作时钟,以方便数据的交互,该时钟的频率为1Mhz。
IIC驱动模块的部分代码如下:

23  module i2c_dri_m
24    #(
25      parameter   CLK_FREQ   = 26'd50_000_000, //i2c_dri模块的驱动时钟频率(CLK_FREQ)
26      parameter   I2C_FREQ   = 18'd250_000   , //I2C的SCL时钟频率
27      parameter   WIDTH      =  4'd8           //一次读写寄存器的个数的位宽
28     )(
29      input               clk        ,  //i2c_dri模块的驱动时钟(CLK_FREQ)
30      input               rst_n      ,  //复位信号
31      //i2c interface
32      input        [6:0]  slave_addr ,  //器件地址
33      input               i2c_exec   ,  //I2C触发执行信号
34      input               i2c_rh_wl  ,  //I2C读写控制信号
35      input        [15:0] i2c_addr   ,  //I2C器件内地址
36      input        [7:0]  i2c_data_w ,  //I2C要写的数据
37      input               bit_ctrl   ,  //字地址位控制(0:8b,1:16b)
38      input   [WIDTH-1:0] reg_num    ,  //一次读写寄存器的个数          
39      output  reg  [7:0]  i2c_data_r ,  //I2C读出的数据
40      output  reg         i2c_done   ,  //I2C操作完成
41      output  reg         once_done  ,  //一次读写操作完成
42      output  reg         scl        ,  //I2C的SCL时钟信号
43      input               sda_in     ,  //I2C的SDA输入信号
44      output  reg         sda_out    ,  //I2C的SDA输出信号
45      output  reg         sda_dir    ,  //I2C的SDA方向控制
46      output  reg         ack        ,  //应答标志
47      //user interface
48      output  reg         dri_clk       //驱动I2C操作的驱动时钟
49       );

以上是IIC驱动模块的端口信号,从贴出的代码可以发现,和“EEPROM读写测试实验”中的IIC驱动模块相比,端口中多了slave_addr(器件地址)、bit_ctrl(字地址位控制)、reg_num(一次读写寄存器的个数)和once_done(I2C单次读写完成),并且将SDA双向引脚改成了三个端口,分别是sda_in、sda_out和sda_dir端口。
在“EEPROM读写测试实验” IIC驱动模块中,器件地址和字地址位控制都是以参数的形式定义的,而本次实验改成了端口的形式,这是由于不同的触摸芯片具有不同的器件地址和字节地址位,因此这两个信号必须定义成端口的形式,而如果定义成参数的形式,无法做到程序运行时实时修改。
除此之外,本模块还支持连续读写的功能,reg_num表示单次读写的寄存器个数,如果这个值等于1,则等价于单次读写,而当这个值大于1时,表示此时对I2C进行连续读写,这可以大大提升IIC的读写效率。每当读写一个字节完成时,once_done信号会拉高一次,而i2c_done信号拉高则表示对I2C的读写操作完成。
至于SDA端口由一个双向端口改成了三个单向的端口,则仅仅是因为为了兼容不同的开发平台,后续可能会对代码进行自定义IP核,方便对代码进行封装,因此进行了端口的修改,对于本次实验来说,也可以继续使用一个双向的SDA端口。

83  assign  clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2;//模块驱动时钟的分频系数
84  assign  reg_done = reg_cnt == reg_num ? 1'b1 : 1'b0;
省略部分代码……
100 //寄存器个数计数
101 always @(posedge dri_clk or negedge rst_n) begin
102     if(!rst_n)
103         reg_cnt <= 'd0;
104     else if(once_done)
105         reg_cnt <= reg_cnt + 1'd1;
106     else if(i2c_done)
107         reg_cnt <= 'd0;   
108 end

由以上代码可知,once_done每拉高一次,reg_cnt累加一次,当reg_cnt的值等于输入的reg_num时,表示当前连续读写的寄存器达到预设值,此时可以开始结束读写操作。

166         st_data_wr: begin                          //写数据(8 bit)
167             if(st_done) begin
168                 if(reg_done)
169                     next_state = st_stop;
170                 else
171                     next_state = st_data_wr;
172             end
173             else
174                 next_state = st_data_wr;
175         end
176         st_addr_rd: begin                          //写地址以进行读数据
177             if(st_done) begin
178                 if(!ack)
179                     next_state = st_data_rd;
180                 else
181                     next_state = st_stop;
182             end
183             else
184                 next_state = st_addr_rd;
185         end
186         st_data_rd: begin                          //读取数据(8 bit)
187             if(st_done) begin
188                 if(reg_done)
189                     next_state = st_stop;
190                 else
191                     next_state = st_data_rd;
192             end
193             else
194                 next_state = st_data_rd;
195         end

由程序的第168行和第188行代码可知,只有在reg_done信号拉高,即读写寄存器的个数达到预设值,状态机才会跳转到结束状态。
接下来以读取起始寄存器地址3,连续读4个寄存器为例,在线调试抓取的波形图如下:
【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

图 20.4.4在线调试波形图
【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

图 20.4.5在线调试波形图
由图 20.4.4可知,i2c_exec信号拉高,此时i2c_rh_wl信号为高电平,表示发起的一次读操作。由图 20.4.5可知,起始地址为3,reg_num等于4,因此该模块通过i2c_data_r信号共输出寄存器地址3~6对应的4个数据,once_done也拉高了4次,最后i2c_done信号拉高,表示单次I2C读操作完成。至此,I2C驱动模块介绍完成。
在简介部分中向大家介绍过,触摸屏所使用的触摸芯片主要分为两类,GT系列和FT系列,这两个系列的寄存器、寄存器地址位数和初始化过程等都不一样,因此在操作之前,需要先确定当前连接的触摸屏所使用的触摸芯片属于哪一类,然后再对相关寄存器进行配置(只有FT系列的触摸芯片才需要配置)、检查是否有手指按下以及获取触摸点坐标。在程序设计时,以上的每个步骤可以当成一个状态,通过状态机实现整个触摸驱动会非常的方便,因此我们通过一个三段式的状态机实现LCD触摸屏的驱动,状态机的跳转图如下:
【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

图 20.4.6 LCD驱动模块状态机跳转图
从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件,下面一一对各个状态进行讲解。
st_idle:初始状态。在此状态中,只是做了简单的延时,就会跳转到上电初始化状态;
st_init:上电初始化状态。在此状态中,会控制CT_RST(硬件复位)信号的高低变化,完成对触摸芯片的硬件复位,对于GT系列来说,在这个过程中会配置触摸芯片的器件地址。在初始化完成之后,会跳转到获取触摸ID状态;
st_get_id:获取触摸ID状态。在此状态中,会根据LCD ID,以及获取到的触摸芯片的ID,来确定当前触摸屏连接的是GT系列还是FT系列。如果确定当前的触摸芯片是GT系列,那么接下来会跳转到检测触摸状态;而如果触摸芯片是FT系列,接下来会跳转到配置寄存器状态;
st_cfg_reg:配置寄存器状态。FT系列相比于GT系列,需要额外配置一些寄存器,如工作模式寄存器、中断状态控制寄存器、有效触摸门限控制寄存器和激活周期控制寄存器。在此状态中,会对这些寄存器进行配置,配置完成后会跳转到检测触摸状态;
st_check_touch:检测触摸状态。在此状态中,会读取触摸状态寄存器,以检测触摸屏当前有没有手指按下,并清除该寄存器的值。如果检测到有效触摸,则跳转到获取触摸点坐标状态;
st_get_coord:获取触摸点坐标状态。在此状态中,会读取LCD屏的触摸点坐标。需要注意的是,本次实验的功能是将第一个触摸点的坐标显示在RGB LCD液晶屏上,所以无论触摸屏是否有多个触摸点,这里只读取第一个触摸点的坐标。获取到触摸点坐标之后,跳转到坐标处理状态;
st_coord_handle:坐标处理状态。在上一个状态获取到的触摸点坐标,并不是真正的触摸屏X和Y方向的坐标,还需要做额外的处理,这个处理在此状态中完成。在对坐标点处理完成后,跳转到获取触摸点坐标状态,重新来检测触摸屏是否有手指按下,在最后三个状态中不停的循环。
LCD触摸模块的代码如下:

23  module touch_dri #(parameter   WIDTH = 4'd8) //一次读写寄存器的个数的位宽
24  (
25      input                   clk          , //时钟信号
26      input                   rst_n        , //复位信号(低有效)
27      //I2C用户端口                        
28      output  reg [6:0]       slave_addr   , //i2c器件地址
29      output  reg             i2c_exec     , //i2c触发控制
30      output  reg             i2c_rh_wl    , //i2c读写控制
31      output  reg [15:0]      i2c_addr     , //i2c操作地址
32      output  reg [7:0]       i2c_data_w   , //i2c写入的数据
33      output  reg             bit_ctrl     , //字地址位控制(0:8b,1:16b)
34      output  reg [WIDTH-1:0] reg_num      , //一次读写寄存器的个数
35                                           
36      input       [7:0]       i2c_data_r   , //i2c读出的数据
37      input                   i2c_ack      , //i2c应答信号
38      input                   i2c_done     , //i2c操作结束标志
39      input                   once_done    , //一次读写操作完成    
40                                            
41      //LCD相关端口                         
42      input       [15:0]      lcd_id       , //LCD ID
43      output  reg             touch_valid  , //触摸标志
44      output  reg [15:0]      tp_x_coord   , //X方向触摸点的坐标
45      output  reg [15:0]      tp_y_coord   , //Y方向触摸点的坐标                           
46      output  reg             touch_rst_n  , //触摸屏复位
47      input                   touch_int_in , //INT输入信号
48      output  reg             touch_int_dir, //INT方向控制信号
49      output  reg             touch_int_out  //INT输出信号
50   );

在I2C用户端口中,这些端口都是连接到I2C驱动模块,实现和I2C驱动模块的数据交互。在程序的第43至45行代码,是本模块比较关键的三个端口,分别表示触摸有效标志、X方向触摸点坐标和Y方向触摸点坐标。其中触摸有效标志为高时,表示当前触摸屏有手指按下,否则表示松开手指。

52  //parameter define
53  //FT系列
54  localparam FT_SLAVE_ADDR    = 7'h38;     //FT系列器件地址
55  localparam FT_BIT_CTRL      = 1'b0;      //FT系列位控制
56                                           
57  localparam FT_ID_LIB_VERSION= 8'hA1;     //版本
58  localparam FT_DEVIDE_MODE   = 8'h00;     //模式控制寄存器
59  localparam FT_ID_MODE       = 8'hA4;     //FT中断模式控制寄存器
60  localparam FT_ID_THGROUP    = 8'h80;     //触摸有效值设置寄存器
61  localparam FT_ID_PERIOD_ACT = 8'h88;     //激活状态周期设置寄存器
62  localparam FT_STATE_REG     = 8'h02;     //触摸状态寄存器
63  localparam FT_TP1_REG       = 8'h03;     //第一个触摸点数据地址
64  
65  //GT系列
66  localparam GT_SLAVE_ADDR    = 7'h14;     //GT系列器件地址
67  localparam GT_BIT_CTRL      = 1'b1;      //GT系列位控制
68                                           
69  localparam GT_STATE_REG     = 16'h814E;  //触摸状态寄存器
70  localparam GT_TP1_REG       = 16'h8150;  //第一个触摸点数据地址
程序中第5463行代码,定义了触摸芯片为FT系列的参数,包括器件地址、字地址位数和寄存器;第6670行代码定义了触摸芯片为GT系列的参数,包括器件地址、字地址位数和寄存器。这些寄存器在简介部分中已经作了介绍,需要注意的是,FT系列的字地址位数是8位,所以FT_BIT_CTRL参数等于0;而GT系列的字地址位数是16位,所以GT_BIT_CTRL参数等于172  localparam st_idle          = 7'b000_0001;//空闲状态
73  localparam st_init          = 7'b000_0010;//上电初始化
74  localparam st_get_id        = 7'b000_0100;//获取触摸芯片ID
75  localparam st_cfg_reg       = 7'b000_1000;//配置寄存器
76  localparam st_check_touch   = 7'b001_0000;//检测触摸状态
77  localparam st_get_coord     = 7'b010_0000;//获取触摸点坐标
78  localparam st_coord_handle  = 7'b100_0000;//针对不对尺寸的触摸的坐标数据进行处理
程序中第7278行代码定义了状态机相关参数,共分为7个状态。
80  //reg define                            
81  reg    [6:0]        cur_state   ;
82  reg    [6:0]        next_state  ;
83  
84  reg                 cnt_1us_en  ;  //使能计时
85  reg    [19:0]       cnt_1us_cnt ;  //计时计数器
86  reg    [15:0]       chip_version;  //芯片版本号
87  reg                 ft_flag     ;  //FT系列芯片的标志
88  reg    [15:0]       touch_s_reg ;  //触摸状态寄存器
89  reg    [15:0]       coord_reg   ;  //触摸点坐标寄存器
90  reg    [15:0]       tp_x_coord_t;  //X方向触摸点临时坐标
91  reg    [15:0]       tp_y_coord_t;  //Y方向触摸点临时坐标
92  reg    [3:0]        flow_cnt    ;  //流程计数器
93  reg                 st_done     ;  //操作完成信号
94  
95  //*****************************************************
96  //**                    main code
97  //*****************************************************
98  
99  //计时控制
100 always @(posedge clk or negedge rst_n) begin
101     if(!rst_n) begin
102         cnt_1us_cnt <= 20'd0;
103     end
104     else if(cnt_1us_en)
105         cnt_1us_cnt <= cnt_1us_cnt + 1'b1;
106     else
107         cnt_1us_cnt <= 20'd0;
108 end
程序中第100行至108行代码的always语句,实现了计时的功能,由于本模块输入的时钟clk为1Mhz,周期为1us,因此cnt_1us_cnt的值实际上表示了计时多少us。当cnt_1us_en拉高时,表示此时使能计时的功能;否则结束计时。
110 //状态跳转
111 always @ (posedge clk or negedge rst_n) begin
112     if(!rst_n)
113         cur_state <= st_idle;
114     else
115         cur_state <= next_state;
116 end
117 
118 //组合逻辑状态判断转换条件
119 always @(*) begin
120     case(cur_state)
121         st_idle : begin
122             if(st_done)
123                 next_state = st_init;
124             else 
125                 next_state = st_idle;
126         end
127         st_init : begin
128             if(st_done)
129                 next_state = st_get_id; 
130             else
131                 next_state = st_init;
132         end
133         st_get_id : begin
134             if(st_done) begin
135                 if(ft_flag)  //仅FT系列需要配置寄存器
136                     next_state = st_cfg_reg;
137                 else
138                     next_state = st_check_touch;
139             end
140             else
141                 next_state = st_get_id;
142         end       
143         st_cfg_reg : begin
144             if(st_done)
145                 next_state = st_check_touch;
146             else
147                 next_state = st_cfg_reg;
148         end
149         st_check_touch: begin
150             if(st_done)
151                 next_state = st_get_coord;
152             else
153                 next_state = st_check_touch;
154         end
155         st_get_coord : begin
156             if(st_done)
157                 next_state = st_coord_handle;
158             else
159                 next_state = st_get_coord;
160         end
161         st_coord_handle : begin
162             if(st_done)
163                 next_state = st_check_touch;
164             else
165                 next_state = st_coord_handle;        
166         end
167         default: next_state = st_idle;
168     endcase
169 end
以上代码的两个always语句分别是三段式状态机的第一段和第二段,尤其是关于第二段式状态机的状态跳转,可以结合着前面状态机跳转图来理解代码,会更加直观和易懂。
171 always @ (posedge clk or negedge rst_n) begin
172     if(!rst_n) begin
173         cnt_1us_en   <= 1'b0;
174         chip_version <= 1'b0;
175         ft_flag      <= 1'b0;
176         touch_s_reg  <= 1'b0;
177         coord_reg    <= 1'b0;
178         tp_x_coord_t <= 1'b0;
179         tp_y_coord_t <= 1'b0;
180         flow_cnt     <= 1'b0;
181         st_done      <= 1'b0;
182         touch_int_dir<= 1'b0;
183         touch_int_out<= 1'b0;
184         
185         slave_addr   <= 1'b0;
186         i2c_exec     <= 1'b0;
187         i2c_rh_wl    <= 1'b0;
188         i2c_addr     <= 1'b0;
189         i2c_data_w   <= 1'b0;
190         bit_ctrl     <= 1'b0;
191         reg_num      <= 1'b0;
192         
193         touch_valid  <= 1'b0;
194         tp_x_coord   <= 1'b0;
195         tp_y_coord   <= 1'b0;
196         touch_rst_n  <= 1'b0;
197     end
198     else begin
199         i2c_exec <= 1'b0;
200         st_done <= 1'b0;
201         case(next_state)
202             st_idle : begin
203                 cnt_1us_en <= 1'b1;
204                 touch_int_dir <= 1'b1;               //TOUCH INT端口方向设置为输出
205                 touch_int_out <= 1'b1;               //TOUCH INT端口输出高电平
206                 if(cnt_1us_cnt >= 10) begin
207                     st_done <= 1'b1;
208                     cnt_1us_en <= 1'b0;
209                 end
210             end
空闲状态比较简单,首先将双向INT引脚设置为输出,并输出高电平。对于GT系列来说,INT引脚会影响到器件地址的选择,在后续正常工作的时候,INT才会作为输入的引脚,不过INT引脚作为输入时,一般作为MCU处理器的中断引脚,本次实验没有使用到。在空闲状态延时了10us之后,进入到下一个状态。
211             st_init : begin
212                 cnt_1us_en <= 1'b1;
213                 if(cnt_1us_cnt < 10_000)             //延时10ms
214                     touch_rst_n <= 1'b0;             //开始复位
215                 else if(cnt_1us_cnt == 10_000)
216                     touch_rst_n <= 1'b1;             //结束复位
217                 else if(cnt_1us_cnt == 60_000) begin //再次延时50ms(60_000-10_000)
218                     touch_int_dir <= 1'b0;           //将INT引脚设置为输入
219                     cnt_1us_en <= 1'b0;
220                     st_done <= 1'b1;
221                     flow_cnt <= 'd0;                 
222                 end    
223             end
在上电初始化状态实现对触摸芯片的硬件复位,和设置GT系列触摸芯片的器件地址。首先延时10ms之后,拉低touch_rst_n信号进行复位,再次延时50ms之后结束复位,此时将INT引脚设置为输入。在硬件复位期间,INT为高电平,表示GT系列触摸芯片的器件地址为7’h14。
224             st_get_id : begin
225                 case(flow_cnt)
226                     'd0 : begin
227                         //这几款屏幕是GT系列
228                         if(lcd_id == 16'h4384 || lcd_id == 16'h4342 || 
229                         lcd_id == 16'h1018) begin 
230                             flow_cnt <= 'd5;
231                             ft_flag  <= 1'b0;        //ft_flag=0,说明触摸芯片为GT系列 
232                         end    
233                         else
234                             flow_cnt <= flow_cnt + 1'b1;
235                     end
236                     'd1 : begin  //读FT系列版本号
237                         i2c_exec <= 1'b1;
238                         i2c_rh_wl <= 1'b1;
239                         i2c_addr <= FT_ID_LIB_VERSION;
240                         reg_num <= 'd2;
241                         slave_addr <= FT_SLAVE_ADDR;
242                         bit_ctrl <= FT_BIT_CTRL;
243                         flow_cnt <= flow_cnt + 1'b1;
244                     end
245                     'd2 : begin
246                         if(once_done) begin
247                             chip_version[15:8] <= i2c_data_r;
248                             flow_cnt <= flow_cnt + 1'b1;
249                         end    
250                         else if(i2c_done && i2c_ack) begin  //未应答,说明是GT系列
251                             chip_version = "GT";
252                             flow_cnt <= 'd4;
253                         end
254                     end
255                     'd3 : begin
256                         if(i2c_done) begin
257                             chip_version[7:0] <= i2c_data_r;
258                             flow_cnt <= flow_cnt + 1'b1;
259                         end
260                     end
261                     'd4 : begin
262                         flow_cnt <= flow_cnt + 1'b1;
263                         //FT系列版本:0X3003/0X0001/0X0002/CST340
264                         if(chip_version == 16'h3003 || chip_version == 16'h0001  
265                         || chip_version == 16'h0002 || chip_version == 16'h0000)
266                             ft_flag <= 1'b1;         //ft_flag=1,说明触摸芯片为FT系列 
267                         else 
268                             ft_flag <= 1'b0;         //ft_flag=0,说明触摸芯片为GT系列 
269                     end
270                     'd5 : begin
271                         st_done <= 1'b1;
272                         flow_cnt <= 'd0;
273                         if(ft_flag) begin            //将参数配置为FT系列
274                             touch_s_reg <= FT_STATE_REG;
275                             coord_reg <= FT_TP1_REG;
276                             bit_ctrl <= FT_BIT_CTRL;
277                             slave_addr <= FT_SLAVE_ADDR;   
278                         end
279                         else begin                   //将参数配置为GT系列
280                             touch_s_reg <= GT_STATE_REG;
281                             coord_reg <= GT_TP1_REG;
282                             bit_ctrl <= GT_BIT_CTRL;
283                             slave_addr <= GT_SLAVE_ADDR;   
284                         end                        
285                     end
286                     default :;
287                 endcase    
288             end
以上代码中的flow_cnt是一个流程控制计数器,方便在不同时刻下进行顺序操作。
在获取触摸ID状态中,正点原子4.3480*2724.3800*48010.11280*800分辨率的屏幕固定使用的是GT系列芯片,所以这里首先判断LCD的ID是否为16’h4342、16’h4384和16’h1018,如果是的话,则确定使用的是GT系列触摸芯片,当然也可以通过读取触摸ID来判断是否为GT系列触摸芯片。
如果不是以上三种屏幕,则先假设当前连接的是FT系列触摸芯片,按照FT系列相关的参数来获取ID,如果能够获取到正确的触摸版本号或者ID,则确认为FT系列触摸芯片;否则则认为是GT系列触摸芯片,包括I2C未应答、获取到的版本号不正确等都认为是GT系列芯片。
在flow_cnt等于5时,通过ft_flag的值可以知道当前连接的触摸芯片是GT系列还是FT系列,然后给相关的变量进行赋值,如器件地址(slave_addr)、字节地址位控制(bit_ctrl)等。
289             st_cfg_reg : begin
290                 case(flow_cnt)
291                     //配置FT系列
292                     'd0 : begin
293                         i2c_exec <= 1'b1;
294                         i2c_rh_wl <= 1'b0;
295                         i2c_addr <= FT_DEVIDE_MODE;        
296                         i2c_data_w <= 8'd0;          //进入正常模式               
297                         reg_num <= 'd1;
298                         flow_cnt <= flow_cnt + 1'b1;  
299                     end                          
300                     'd1 : begin
301                         if(i2c_done) begin
302                             if(i2c_ack == 1'b0)      //I2C应答
303                                 flow_cnt <= flow_cnt + 1'b1;
304                             else                     //I2C未应答
305                                 flow_cnt <= flow_cnt - 1'b1;
306                         end        
307                     end
308                     'd2 : begin
309                         i2c_exec <= 1'b1;
310                         i2c_rh_wl <= 1'b0;
311                         i2c_addr <= FT_ID_MODE;          
312                         i2c_data_w <= 8'd0;          //查询模式         
313                         reg_num <= 'd1;
314                         flow_cnt <= flow_cnt + 1'b1;     
315                     end                           
316                     'd3 : begin
317                         if(i2c_done) begin
318                             if(i2c_ack == 1'b0)      //I2C应答
319                                 flow_cnt <= flow_cnt + 1'b1;
320                             else                     //I2C未应答
321                                 flow_cnt <= flow_cnt - 1'b1;
322                         end        
323                     end
324                     'd4 : begin
325                         i2c_exec <= 1'b1;
326                         i2c_rh_wl <= 1'b0;
327                         i2c_addr <= FT_ID_THGROUP;       
328                         i2c_data_w <= 8'd22;         //设置触摸有效值,值越小,越灵敏          
329                         reg_num <= 'd1;
330                         flow_cnt <= flow_cnt + 1'b1;    
331                     end                        
332                     'd5 : begin
333                         if(i2c_done) begin
334                             if(i2c_ack == 1'b0)      //I2C应答
335                                 flow_cnt <= flow_cnt + 1'b1;
336                             else                     //I2C未应答
337                                 flow_cnt <= flow_cnt - 1'b1;
338                         end        
339                     end      
340                     'd6 : begin
341                         i2c_exec <= 1'b1;
342                         i2c_rh_wl <= 1'b0;
343                         i2c_addr <= FT_ID_PERIOD_ACT;       
344                         i2c_data_w <= 8'd12;         //激活周期,12~14          
345                         reg_num <= 'd1;
346                         flow_cnt <= flow_cnt + 1'b1;  
347                     end                          
348                     'd7 : begin
349                         if(i2c_done) begin
350                             if(i2c_ack == 1'b0) begin//I2C应答
351                                 flow_cnt <= 'd0;
352                                 st_done <= 1'b1;
353                             end    
354                             else                     //I2C未应答
355                                 flow_cnt <= flow_cnt - 1'b1;
356                         end
357                     end
358                     default : ;
359                 endcase                       
360             end
配置寄存器状态主要配置FT系列触摸芯片的寄存器,只需要按照简介部分介绍的,配置工作模式寄存器、中断状态控制寄存器、有效触摸门限控制寄存器和激活周期控制寄存器即可。如果当前寄存器没有配置成功,即i2c_ack信号未应答则重新配置该寄存器,直到配置完所需的寄存器。
361             st_check_touch : begin
362                 case(flow_cnt)
363                     'd0: begin  //延时
364                         cnt_1us_en <= 1'b1;
365                         if(cnt_1us_cnt == 20_000) begin
366                             flow_cnt <= flow_cnt + 1'b1;
367                             cnt_1us_en <= 1'b0;
368                         end    
369                     end        
370                     'd1 : begin
371                         i2c_exec <= 1'b1;
372                         i2c_rh_wl <= 1'b1;
373                         i2c_addr <= touch_s_reg;     //读取触摸点状态   
374                         reg_num <= 'd1;
375                         flow_cnt <= flow_cnt + 1'b1;
376                     end
377                     'd2 : begin
378                         if(i2c_done) begin
379                             if(i2c_ack == 1'b0)
380                                 flow_cnt <= flow_cnt + 1'b1;
381                             else
382                                 flow_cnt <= flow_cnt - 1'b1;                                    
383                         end
384                     end
385                     'd3 : begin
386                         flow_cnt <= flow_cnt + 1'b1;
387                         if(ft_flag) begin
388                             if(i2c_data_r[3:0] > 4'd0 && i2c_data_r[3:0] <= 4'd5)
389                                 touch_valid <= 1'b1; //检测到触摸 
390                             else
391                                 touch_valid <= 1'b0; //未检测到触摸
392                         end
393                         else begin
394                             if(i2c_data_r[7]== 1'b1 && i2c_data_r[3:0] > 4'd0 
395                             && i2c_data_r[3:0] <= 4'd5) begin      
396                                 touch_valid <= 1'b1; //检测到触摸 
397                             end
398                             else 
399                                 touch_valid <= 1'b0; //未检测到触摸
400                         end
401                     end
402                     'd4 : begin
403                         i2c_exec <= 1'b1;
404                         i2c_rh_wl <= 1'b0;
405                         i2c_addr <= touch_s_reg;
406                         i2c_data_w <= 8'd0;          //清除触摸标志
407                         reg_num <= 'd1;
408                         flow_cnt <= flow_cnt + 1'b1;
409                     end
410                     'd5 : begin
411                         if(i2c_done) begin
412                             if(i2c_ack == 1'b0) begin
413                                 st_done <= touch_valid;
414                                 flow_cnt <= 1'b0;
415                             end
416                             else
417                                 flow_cnt <= flow_cnt - 1'b1;   
418                         end                            
419                     end
420                     default : ;
421                 endcase    
422             end
以上代码通过读取查询触摸状态寄存器,来判断当前有没有手指按下。需要注意的是,一般触摸点转换时间需要7~20ms左右,这里固定延时20ms再去获取触摸点状态。当检查到有效触摸之后,需要向该寄存器写0以清除触摸。由于笔者在实际测试时发现,在第一次读取触摸状态时,手指未接触触摸屏,偶尔也会读到有效触摸状态,因此这里无论有没有检测到触摸状态,都会进行写0423             st_get_coord : begin
424                 case(flow_cnt)   
425                     'd0 : begin
426                         i2c_exec <= 1'b1;
427                         i2c_rh_wl <= 1'b1;
428                         i2c_addr <= coord_reg;       //获取X和Y方向坐标点
429                         reg_num <= 'd4;              //连续读四个寄存器
430                         flow_cnt <= flow_cnt + 1'b1;
431                     end
432                     'd1 : begin
433                         if(once_done) begin
434                             if(i2c_ack == 1'b0) begin
435                                 tp_x_coord_t[7:0] <= i2c_data_r;
436                                 flow_cnt <= flow_cnt + 1'b1;
437                             end    
438                             else
439                                 flow_cnt <= 1'b0;                                    
440                         end
441                     end
442                     'd2 : begin
443                         if(once_done) begin
444                             flow_cnt <= flow_cnt + 1'b1;
445                             tp_x_coord_t[15:8] <= i2c_data_r;   
446                         end                            
447                     end
448                     'd3 : begin
449                         if(once_done) begin
450                             flow_cnt <= flow_cnt + 1'b1;
451                             tp_y_coord_t[7:0] <= i2c_data_r;    
452                         end                            
453                     end    
454                     'd4 : begin
455                         if(once_done) begin
456                             st_done  <= 1'b1;
457                             flow_cnt <= 'd0;
458                             tp_y_coord_t[15:8] <= i2c_data_r;  
459                         end                            
460                     end
461                     default:;
462                 endcase    
463             end
读取触摸点状态用于读取第一个有效触摸点的X和Y方向坐标,读取完成后进入下一个状态。
464             st_coord_handle : begin
465                 st_done <= 1'b1;
466                 if(ft_flag) begin                    //FT系列需对数据做处理
467                     tp_x_coord <= {4'd0,tp_y_coord_t[3:0],tp_y_coord_t[15:8]};
468                     tp_y_coord <= {4'd0,tp_x_coord_t[3:0],tp_x_coord_t[15:8]};
469                 end
470                 else begin
471                     tp_x_coord <= tp_x_coord_t;
472                     tp_y_coord <= tp_y_coord_t;                        
473                 end
474             end
475             default : ;
476         endcase
477     end
478 end
479
480 endmodule 

这里值得一提的是,由于FT系列和GT系列输出的坐标顺序不一致,这里我们再来回顾一下。
GT系列:
表 20.4.1 GT系列触点 1 坐标寄存器组描述
寄存器 bit7~0 寄存器 bit7~0
0X8150 触点1 X坐标低8位 0X8151 触点1 X坐标高8位
0X8152 触点1 Y坐标低8位 0X8153 触点1 Y坐标高8位
FT系列:
表 20.4.2 FT系列触摸点1寄存器组描述
寄存器 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]
由上面两张表格对比可以发现,GT系列先输出的是X方向坐标的低8位,而FT系列输出的是X方向的高4位,因此在坐标处理状态中,需要对最终输出的X和Y方向的坐标点进行处理。由于程序是按照先接收低8位,再接收高8位的顺序,和GT系列寄存器定义的顺序一致,因此只需要对FT系列的坐标点做处理。
最后还需要注意的一点是,在对FT系列进行坐标处理时,除了调换高低位之外,还讲X和Y方向的坐标进行了调换,这是由于FT系列和GT系列对于X和Y方向的定义不同。GT系列是以横屏方式定义X和Y方向,而FT系列是以竖屏方式定义X和Y方向,这两者的差异通过两幅图片进行展示(以800*480分辨率为例):
GT系列对X和Y方向的定义如下:
【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

图 20.4.7GT系列对X和Y方向的定义

【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

图 20.4.8FT系列对X和Y方向的定义
我们常规对LCD屏X和Y方向的定义是按照GT系列的方式,所以对于GT系列的坐标不用做任何处理,而对FT系列的坐标处理除高低位互换外,X和Y方向的坐标也需要调换。
至此LCD触摸驱动介绍结束。
关于RGB LCD字符显示模块,实现的功能是将LCD触摸驱动模块输出的X和Y方向坐标点显示在RGB LCD液晶屏上,该模块的代码和程序设计思路和“RTC实时时钟LCD显示实验” 非常类似,因此可以参考该实验。只不过原来生成的字模宽度和高度是16x16,这里改成了32x32,并且字模数据除“0123456789”之外,还加了“XY”字模。
字模是使用“PCtoLCD2002”(软件存放路径:开发板资料盘(A盘)\6_软件资料\1_软件)软件生成的,生成步骤如下图所示:
【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

图 20.4.9字模生成步骤
首先在资料盘A盘找到“PCtoLCD2002完美版”软件并打开,然后按照上图步骤操作。第一步点击“选项”按钮打开字模属性设置,然后按照上图序号2、3、4、5、6、7、8步骤设置字模参数并点击确定,之后在上图序号9的位置输入我们想要生成的字符(本节实验输入0123456789XY),再到序号10的位置设置字符的宽度和高度(本节实验选择3232,但是在实际显示英文字符的时候宽度会减半也就是1632),最后点击“生成字模”按钮,在上图序号11的位置就生成了我们需要的字模数据组,拷贝到代码中即可使用。
生成后的字模在程序中的展示如下(代码位于lcd_display模块):
【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

图 20.4.10字模代码
lcd_display模块部分代码如下:

29  module lcd_display(
30      input             lcd_pclk,                 //lcd驱动时钟
31      input             sys_rst_n,                //复位信号
32      
33      input      [31:0] data ,
34      
35      input      [10:0] pixel_xpos,               //像素点横坐标
36      input      [10:0] pixel_ypos,               //像素点纵坐标    
37      output reg [15:0] pixel_data                //像素点数据,
38  );
39  
40  //parameter define
41  localparam CHAR_POS_X  = 11'd1;                 //字符区域起始点横坐标
42  localparam CHAR_POS_Y  = 11'd1;                 //字符区域起始点纵坐标
43  localparam CHAR_WIDTH  = 11'd144;               //字符区域宽度
44  localparam CHAR_HEIGHT = 11'd32;                //字符区域高度
45  
46  localparam WHITE  = 24'b11111_111111_11111;     //背景色,白色
47  localparam BLACK  = 24'b00000_000000_00000;     //字符颜色,黑色
48  
49  //reg define
50  reg  [511:0]  char  [11:0] ;                    //字符数组
51  
52  //wire define
53  wire   [3:0]              data0    ;        // 十万位数
54  wire   [3:0]              data1    ;        // 万位数
55  wire   [3:0]              data2    ;        // 千位数
56  wire   [3:0]              data3    ;        // 百位数
57  wire   [3:0]              data4    ;        // 十位数
58  wire   [3:0]              data5    ;        // 个位数
59  wire   [3:0]              data6    ;        
60  
61  //*****************************************************
62  //**                    main code
63  //*****************************************************
64  assign  data6 = data[31:16] / 10'd1000 % 4'd10 ;  // X轴坐标千位数          
65  assign  data5 = data[31:16] / 7'd100 % 4'd10   ;  // X轴坐标百位数
66  assign  data4 = data[31:16] / 4'd10 % 4'd10    ;  // X轴坐标十位数
67  assign  data3 = data[31:16] % 4'd10            ;  // X轴坐标个位数
68  assign  data2 = data[15:0]  / 7'd100 % 4'd10   ;  // Y轴坐标百位数
69  assign  data1 = data[15:0]  / 4'd10 % 4'd10    ;  // Y轴坐标十位数
70  assign  data0 = data[15:0]  % 4'd10            ;  // Y轴坐标个位数

第33行代码输入的32位data数据,就是由两个16位X和Y方向坐标拼接成的32位;而程序中第41行至44行代码定义了X和Y坐标在RGB LCD液晶屏上显示的位置。第46行和第47行代码定义了背景色和字符颜色,分别是白色和黑色。
我们将X方向和Y方向坐标显示在相应的位置上,那么需要分别求出X方向坐标的千位、百位、十位和个位,以及Y方向坐标的百位、十位和个位。计算方式见程序第64行至70行代码。

124 //给不同的区域赋值不同的像素数据
125 always @(posedge lcd_pclk or negedge sys_rst_n) begin
126     if (!sys_rst_n)  begin
127         pixel_data <= BLACK;
128     end
129     else if((pixel_xpos >= CHAR_POS_X)     && (pixel_xpos < CHAR_POS_X + CHAR_WIDTH/9*1)
130          && (pixel_ypos >= CHAR_POS_Y) && (pixel_ypos < CHAR_POS_Y + CHAR_HEIGHT)) begin
131    if(char[data6][(CHAR_HEIGHT+CHAR_POS_Y-pixel_ypos)*16-((pixel_xpos-CHAR_POS_X)%16)-1])
132             pixel_data <= BLACK;
133         else
134             pixel_data <= WHITE;
135     end    
省略部分代码……
192     else begin
193         pixel_data <= WHITE;              //绘制屏幕背景为白色
194     end
195 end
196 endmodule 

以上代码是根据输入的pixel_xpos(像素点横坐标)和pixel_ypos(像素点纵坐标),来将当前像素点赋值为背景色或者字符的颜色。
我们显示的内容首先分成两行,第一行从左往右依次显示X轴坐标千位数、X轴坐标百位数、X轴坐标十位数、X轴坐标个位数和字符“X”;第二行从左往右依次显示Y轴坐标百位数、Y轴坐标十位数、Y轴坐标个位数和字符“Y”。
代码第50行定义了一个元素个数为12,每个元素的位宽为512位的数组(char),这12个元素分别对应阿拉伯数字“0~9”、“X”和“Y”字模数据,每一个字模(数字)所占的像素大小是长32个像素,宽16个像素,共32*16=512位像素数据。这里将一个数字的字模数据存放在了数组的一个元素中,因此数组元素的位宽是512位。
代码第129到135行是一个具体的字符显示的逻辑。首先判断当前像素坐标的位置,如代码第129到130行,如果处在字符显示的区域则开始根据字符数组值来显示像素。显示时,数组参数pixel_xpos,pixel_ypos分别从小到大取不同的值时,代入数组,此时我们实际上就是在从左到右,从上到下扫描一个字符像素平面,pixel_xpos变化对于行扫描,pixel_ypos则对于列扫描。
对于131行的代码 “ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*16”,我们不难理解“*16”的由来,因为在查找数组元素的时候,pixel_ypos的每次变化代表换到下一行扫描,一行跨过16个数据,所有乘以16。这里总结一下:字符数组一行的512个数据从高位到低位,每16位代表另一行,分别对应点阵中该行从左向右的每一个像素点。
程序中第132行到134行是对数组的每个元素分别赋值,具体是数组元素为1的点赋值为黑色,否则为白色。
往下,每一个字符显示逻辑的分析和上面类似,请大家自行分析。
20.5下载验证
首先将下载器与DFZU2EG/4EV MPSoC开发板上的JTAG接口连接,下载器另外一端与电脑连接,然后将LCD屏连接到开发板上,最后连接电源线后拨动开关按键给开发板上电。
然后将本次实验生成的bit流文件下载到开发板中,此时可以看到LCD屏幕点亮并显示“0000X000Y”,我们用手触摸LCD屏,触摸点的数据就会显示出来了。
实验结果如下图所示:
【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

图 20.5.1 触摸显示文章来源地址https://www.toymoban.com/news/detail-422314.html

到了这里,关于【正点原子FPGA连载】 第二十章 LCD触摸屏实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包