1)实验平台:正点原子MPSoC开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html
现在几乎所有智能手机,包括平板电脑都是采用电容屏作为触摸屏,电容屏是利用人体感应进行触点检测控制,不需要直接接触或只需要轻微接触,通过检测感应电流来定位触摸坐标。在本章中,我们将向大家介绍 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电极的交叉处形成电容。交互电容的扫描方式就是扫描每个交叉处的电容变化,来判定触摸点的位置。当触摸的时候就会影响到相邻电极的耦合,从而改变交叉处的电容量,交互电容的扫面方法可以侦测到每个交叉点的电容值和触摸后电容变化,因而它需要的扫描时间与自我电容的扫描方式相比要长一些,需要扫描检测XY根电极。目前智能手机/平板电脑等的触摸屏,都是采用交互电容技术。
正点原子所选择的电容触摸屏,采用的是投射式电容屏(交互电容类型),所以后面仅以投射式电容屏作为介绍。
透射式电容触摸屏采用纵横两列电极组成感应矩阵来感应触摸。以两个交叉的电极矩阵(X轴电极和Y轴电极)来检测每一格感应单元的电容变化,如下图所示:
图 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寸800480的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上电设置器件地址的时序图如下图所示:
图 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所示。
图 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 触摸显示实验管脚分配
对应的约束语句(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程序设计
根据实验任务我们画出了如下的程序框图:
图 20.4.1 LCD触摸实验程序框图
由上图可知,时钟IP核模块为其余模块提供驱动时钟;由于LCD屏的触摸接口是IIC接口,因此通过IIC驱动模块实现整个IIC通信协议的功能,而LCD触摸驱动模块负责整个LCD屏触摸ID的确定、初始化、以及获取触摸点坐标等操作。最后,我们还需要将获取到的触摸点坐标显示在RGB LCD液晶屏上,这部分功能由RGB LCD字符显示模块来实现。
本次实验的RTL视图如下图所示:
图 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屏的触摸驱动,并输出触 摸点的坐标,其模块端口及信号连接如下图所示:
图 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个寄存器为例,在线调试抓取的波形图如下:
图 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触摸屏的驱动,状态机的跳转图如下:
图 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; //第一个触摸点数据地址
程序中第54至63行代码,定义了触摸芯片为FT系列的参数,包括器件地址、字地址位数和寄存器;第66至70行代码定义了触摸芯片为GT系列的参数,包括器件地址、字地址位数和寄存器。这些寄存器在简介部分中已经作了介绍,需要注意的是,FT系列的字地址位数是8位,所以FT_BIT_CTRL参数等于0;而GT系列的字地址位数是16位,所以GT_BIT_CTRL参数等于1。
72 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;//针对不对尺寸的触摸的坐标数据进行处理
程序中第72至78行代码定义了状态机相关参数,共分为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.3寸480*272、4.3寸800*480、10.1寸1280*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以清除触摸。由于笔者在实际测试时发现,在第一次读取触摸状态时,手指未接触触摸屏,偶尔也会读到有效触摸状态,因此这里无论有没有检测到触摸状态,都会进行写0。
423 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方向的定义如下:
图 20.4.7GT系列对X和Y方向的定义
图 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_软件)软件生成的,生成步骤如下图所示:
图 20.4.9字模生成步骤
首先在资料盘A盘找到“PCtoLCD2002完美版”软件并打开,然后按照上图步骤操作。第一步点击“选项”按钮打开字模属性设置,然后按照上图序号2、3、4、5、6、7、8步骤设置字模参数并点击确定,之后在上图序号9的位置输入我们想要生成的字符(本节实验输入0123456789XY),再到序号10的位置设置字符的宽度和高度(本节实验选择3232,但是在实际显示英文字符的时候宽度会减半也就是1632),最后点击“生成字模”按钮,在上图序号11的位置就生成了我们需要的字模数据组,拷贝到代码中即可使用。
生成后的字模在程序中的展示如下(代码位于lcd_display模块):
图 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屏,触摸点的数据就会显示出来了。
实验结果如下图所示:
图 20.5.1 触摸显示