本帖最后由 正点原子运营 于 2023-4-10 15:04 编辑
第二十章 LCD触摸屏实验
1)实验平台:正点原子 DFZU2EG/4EV MPSoC开发板
2) 章节摘自【正点原子】DFZU2EG/4EV MPSoC之FPGA开发指南 V1.0
6)FPGA技术交流QQ群:994244016
现在几乎所有智能手机,包括平板电脑都是采用电容屏作为触摸屏,电容屏是利用人体感应进行触点检测控制,不需要直接接触或只需要轻微接触,通过检测感应电流来定位触摸坐标。在本章中,我们将向大家介绍FPGA控制LCD 电容触摸模块,实现触摸屏驱动,即用手指触碰LCD屏幕时,对应触摸点的坐标会显示在LCD屏幕上。 本章包括以下几个部分: 20.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的驱动,这个驱动对其他几个芯片也是有效的,而且封装也有一样的,完全PINTO 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依次扫描纵向和横向电极,并根据扫描前后的电容变化来确定触摸点坐标位置。笔记本电脑触摸输入板就是采用的这种方式,笔记本电脑的输入板采用X*Y的传感电极阵列形成一个传感格子,当手指靠近触摸输入板时,在手指和传感电极之间产生一个小量电荷。采用特定的运算法则处理来自行、列传感器的信号,以此确定手指的位置。
交互电容又叫做跨越电容,它是在玻璃表面的横向和纵向的ITO电极的交叉处形成电容。交互电容的扫描方式就是扫描每个交叉处的电容变化,来判定触摸点的位置。当触摸的时候就会影响到相邻电极的耦合,从而改变交叉处的电容量,交互电容的扫面方法可以侦测到每个交叉点的电容值和触摸后电容变化,因而它需要的扫描时间与自我电容的扫描方式相比要长一些,需要扫描检测X*Y根电极。目前智能手机/平板电脑等的触摸屏,都是采用交互电容技术。
正点原子所选择的电容触摸屏,采用的是投射式电容屏(交互电容类型),所以后面仅以投射式电容屏作为介绍。
透射式电容触摸屏采用纵横两列电极组成感应矩阵来感应触摸。以两个交叉的电极矩阵(X轴电极和Y轴电极)来检测每一格感应单元的电容变化,如下图所示: 图20.1.1 投射式电容屏电极矩阵示意图
示意图中的电极,实际是透明的,这里是为了方便大家理解故填充了颜色。图中,X、Y轴的透明电极电容屏的精度、分辨率与X、Y轴的通道数有关,通道数越多,精度越高。以上就是电容触摸屏的基本原理,接下来看看电容触摸屏的优缺点: 电容触摸屏的优点:手感好、无需校准、支持多点触摸、透光性好。
电容触摸屏的缺点:成本高、精度不高、抗干扰能力差。
这里特别提醒大家电容触摸屏对工作环境的要求是比较高的,在潮湿、多尘、高低温环境下面,都是不适合使用电容屏的。
电容触摸屏通常需要一个驱动IC来检测电容触摸,且一般是通过IIC接口输出触摸数据的。正点原子不同尺寸和分辨率的触摸屏较多,每款屏幕都配有触摸芯片,不同的触摸屏,配备了相同或者不同的触摸芯片,即使是相同的触摸屏,也有可能配备的是不同的触摸芯片,至于触摸芯片的具体型号,可以查看触摸屏背面触摸芯片上的丝印。对于7寸RGB LCD屏1204*600的分辨率来说,使用过的触摸芯片有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上电设置器件地址的时序图如下图所示: 接下来,介绍一下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)
该寄存器各个位描述如下表所示: 这里,我们仅关心最高位和最低4位,最高位用于表示buffer状态,如果有数据(坐标/按键),buffer就会是1,最低4位用于表示有效触点的个数,范围是:0~5,0表示没有触摸,5表示有5点触摸。最后,该寄存器在每次读取后,如果bit7有效,则必须写0,清除这个位,否则不会输出下一次数据!!这个要特别注意!!! 5,坐标数据寄存器(共20个)
这里共分成5组(5个点),每组4个寄存器存储数据,以触点1的坐标数据寄存器组为例,如下表所示: 我们一般只用到触点的X,Y坐标,所以只需要读取0X8150~0X8153的数据组合,即可得到触点坐标。其他4组分别是:0X8158、0X8160、0X8168和0X8170等开头的16个寄存器组成,分别针对触点2~4的坐标。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的工作模式,该寄存器的描述如下: MODE[2:0]用于控制FT5206的工作模式,一般设置为:000,表示正常工作模式。
2,中断状态控制寄存器(0XA4)
该寄存器用于设置FT5206的中断状态,该寄存器的描述如下:
该寄存器只有最低位有效,M=0:表示查询模式;M=1:触发模式,一般设置为查询模式。
3,有效触摸门限控制寄存器(0X80)
该寄存器用于设置FT5206的中断状态,该寄存器的描述如下:
该寄存器8位数据都有效,用于设置FT5206有效触摸的门限值,计算方式为:
有效触摸门限值=T[7:0]*4,T[7:0]的值越小,触摸越灵敏,默认状态下该值为70。
4,激活周期控制寄存器(0X88)
该寄存器用于设置FT5206的激活周期,该寄存器的描述如下:
该寄存器只有低4位有效,用于设置FT5206的激活周期,P[3:0]的设置范围为3~14,默认值为12。
5,库版本寄存器(0XA1和0XA2)
库版本寄存器由两个寄存器组成:0XA1和0XA2,用于读取FT5206的驱动库版本,0XA1用于读取版本号的高字节,0XA2用于读取版本号的低字节。7寸屏FT5206的版本号为0X3003。
6,触摸状态寄存器(0X02)
该寄存器用于读取FT5206的触摸状态,该寄存器的描述如下:
该寄存器只有低4位有效,TD3[3:0]的取值范围是1~5,表示有多少个有效触摸点。我们可以根据这个寄存器的值来判断有效触摸点的个数,然后通过0X03/0X09/0X0F/0X15和0X1B等寄存器来读取触摸坐标数据。
7,触摸数据寄存器(0X03~0X1E)
这里总共包括20个寄存器,它们是0X03~0X06、0X09~0X0C、0X0F~0X12、0X15~0X18和0X1B~0X1E。每4个寄存器为1组,表示一个触摸点的坐标数据,比如0X03~0X06,则表示触摸点1的坐标数据,其它的以此类推。这里,我们仅介绍0X03~0X06寄存器,该寄存器的描述如下:
这里的Event FLAG用于表示触摸状态,00:按下;01:松开;10:持续触摸;11:保留。一般我们只需要判断该状态是否为10即可,即持续触摸状态,就可以稳定的读取触摸坐标数据了。而Touch ID,一般用不到,最后就是X和Y的坐标数据,这些数据以12位的形式输出。
其它的0X09~0X0C、0X0F~0X12、0X15~0X18和0X1B~0X1E寄存器,则分别用于读取第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.1LCD接口原理图
上图中的关于LCD显示部分的引脚就不再介绍了,这里我们主要看下CT_RST、IIC2_SDA、IIC2_SCL、CT_INT四个引脚,这四个引脚分别连接到了GT911的RST、SDA、SCL和INT四根引脚,我们在代码中通过控制这四个引脚来初始化GT911芯片或者和GT911进行数据交互。
本实验中,各端口信号的管脚分配(由于引脚比较多,这里只给出了GT911的控制引脚,详细引脚请参考例程提供的XDC文件)如下表所示:
对应的约束语句(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 程序设计根据实验任务我们画出了如下的程序框图: 由上图可知,时钟IP核模块为其余模块提供驱动时钟;由于LCD屏的触摸接口是IIC接口,因此通过IIC驱动模块实现整个IIC通信协议的功能,而LCD触摸驱动模块负责整个LCD屏触摸ID的确定、初始化、以及获取触摸点坐标等操作。最后,我们还需要将获取到的触摸点坐标显示在RGB LCD液晶屏上,这部分功能由RGB LCD字符显示模块来实现。
本次实验的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, //TOUCHIIC数据
- 8 output touch_scl, //TOUCHIIC时钟
- 9 inout touch_int, //TOUCHINT信号
- 10 output touch_rst_n,//TOUCH 复位信号
- 11 //RGBLCD接口
- 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 //LCDRGB颜色数据
- 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), // inputreset
- 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屏的触摸驱动,并输出触 摸点的坐标,其模块端口及信号连接如下图所示: 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.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触摸屏的驱动,状态机的跳转图如下: 从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件,下面一一对各个状态进行讲解。
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屏的触摸点坐标。需要注意的是,本次实验的功能是将第一个触摸点的坐标显示在RGBLCD液晶屏上,所以无论触摸屏是否有多个触摸点,这里只读取第一个触摸点的坐标。获取到触摸点坐标之后,跳转到坐标处理状态;
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 坐标寄存器组描述
FT系列:
由上面两张表格对比可以发现,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方向的定义如下: 我们常规对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_软件)软件生成的,生成步骤如下图所示: 首先在资料盘A盘找到“PCtoLCD2002完美版”软件并打开,然后按照上图步骤操作。第一步点击“选项”按钮打开字模属性设置,然后按照上图序号2、3、4、5、6、7、8步骤设置字模参数并点击确定,之后在上图序号9的位置输入我们想要生成的字符(本节实验输入0123456789XY),再到序号10的位置设置字符的宽度和高度(本节实验选择32*32,但是在实际显示英文字符的时候宽度会减半也就是16*32),最后点击“生成字模”按钮,在上图序号11的位置就生成了我们需要的字模数据组,拷贝到代码中即可使用。
生成后的字模在程序中的展示如下(代码位于lcd_display模块): 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/4EVMPSoC开发板上的JTAG接口连接,下载器另外一端与电脑连接,然后将LCD屏连接到开发板上,最后连接电源线后拨动开关按键给开发板上电。
然后将本次实验生成的bit流文件下载到开发板中,此时可以看到LCD屏幕点亮并显示“0000X000Y”,我们用手触摸LCD屏,触摸点的数据就会显示出来了。
实验结果如下图所示: |