700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 【正点原子FPGA连载】 第二十章 LCD触摸屏实验 -摘自【正点原子】领航者ZYNQ之FPGA

【正点原子FPGA连载】 第二十章 LCD触摸屏实验 -摘自【正点原子】领航者ZYNQ之FPGA

时间:2022-04-09 02:16:26

相关推荐

【正点原子FPGA连载】 第二十章 LCD触摸屏实验 -摘自【正点原子】领航者ZYNQ之FPGA

1)实验平台:正点原子领航者ZYNQ开发板

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

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

4)对正点原子FPGA感兴趣的同学可以加群讨论:994244016

5)关注正点原子公众号,获取最新资料更新

第二十章 LCD触摸屏实验

触摸屏(Touch Panel)又称为触控屏、触控面板,是一种可接收触头等输入讯号的感应式液晶显示装置,当接触屏幕时,屏幕上的触觉反馈系统可根据预先编程的程式驱动各种连结装置,可用以取代机械式的按钮面板,并借由液晶显示画面制造出生动的影音效果。本节LCD触摸实验将实现手指触碰LCD屏幕,对应触摸点的坐标就会显示在LCD屏幕上的功能。

本章包括以下几个部分:

20.1 简介

20.2 实验任务

20.3 硬件设计

20.4 程序设计

20.5 下载验证

20.1 简介

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

1)电阻式触摸屏

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

正点原子2.4/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轴电极)来检测每一格感应单元的电容变化,如下图所示:

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

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

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

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

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

电容触摸屏一般都需要一个驱动IC来检测电容触摸,且一般是通过IIC接口输出触摸数据的。正点原子7’LCD模块的电容触摸屏,使用FT5206/FT5426做为驱动IC,该IC采用的是1528的驱动结构(15个感应通道,28个驱动通道)。正点原子4.3’LCD模块则使用GT9147作为驱动IC,该IC采用1710的驱动结构(10个感应通道,17个驱动通道)。

这两种不同尺寸的屏幕都只支持最多5点触摸,本例程除CPLD方案的V1版本7寸屏模块不支持外,其他所有正点原子的RGB LCD模块都支持,电容触摸驱动IC,这里只介绍GT9147的驱动,FT5206和FT5426的驱动同GT9147类似,大家可以参考着学习即可。

下面我们简单介绍下GT9147,该芯片是深圳汇顶科技研发的一颗电容触摸屏驱动IC,支持100Hz触点扫描频率,支持5点触摸,支持18*10个检测通道,适合小于4.5寸的电容触摸屏使用。

GT9147与FPGA连接是通过4根线:SDA、SCL、RST和INT。其中:SDA和SCL是IIC通信用的,RST是复位脚(低电平有效),INT是中断输出信号。

GT9147的IIC地址,可以是0X14或者0X5D,当复位结束后的5ms内,如果INT是高电平,则使用0X14作为地址,否则使用0X5D作为地址,具体的设置过程请参考GT9147数据手册.pdf这个文档。本章我们使用0X14作为器件地址(不含最低位,换算成读写命令则是读:0X29,写:0X28)。接下来,介绍一下GT9147的几个重要的寄存器。

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

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

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

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

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

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

4,状态寄存器(0X814E)

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

表 20.1.1 寄存器定义

寄存器 bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0

0X814E buffer状态 大点 接近有效 按键 有效触点个数

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

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

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

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

寄存器 bit7~0 寄存器 bit7~0

0X8150 触点1 x坐标低8位 0X8150 触点1 x坐标低8位

0X8150 触点1 x坐标低8位 0X8150 触点1 x坐标低8位

0X8150 触点1触摸尺寸低8位 0X8150 触点1触摸尺寸高8位

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

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

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

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

特别说明:FT5206和FT5426的驱动代码完全一模一样,他们只是版本号读取的时候稍有差异,读坐标数据和配置等操作动完全是一模一样的。所以,这两个电容屏驱动IC,可以共用一个驱动程序。电容式触摸屏部分,就介绍到这里。

20.2 实验任务

本节的实验任务是使用领航者开发板驱动LCD显示屏,用手触摸显示屏,在屏幕上显示触摸点的坐标。

20.3 硬件设计

领航者板载的LCD接口原理图如图 7.5.13.1所示。

图 7.5.13.1 LCD接口原理图

上图中的关于LCD显示部分的引脚就不再介绍了,这里我们主要看下CT_RST、IIC2_SDA、IIC2_SCL、CT_INT四个引脚,这四个引脚分别连接到了GT9147的RST、SDA、SCL和INT四根引脚,我们在代码中通过控制这四个引脚来初始化GT9147芯片或者和GT9147进行数据交互。

本实验中,各端口信号的管脚分配(由于引脚比较多,这里只给出了GT9147的控制引脚,详细引脚请参考例程提供的XDC文件)如下表所示:

表 20.3.1 触摸显示实验管脚分配

信号名 方向 管脚 端口说明 电平标准

touch_scl output R19 IIC通信引脚 LVCMOS33

touch_sda inout P20 IIC通信引脚 LVCMOS33

touch_int inout U19 中断引脚 LVCMOS33

touch_rst output M19 WM8960的数据线 LVCMOS33

对应的约束语句(GT9147的引脚约束语句)如下所示:

set_property -dict {PACKAGE_PIN R19 IOSTANDARD LVCMOS33} [get_ports touch_scl]set_property -dict {PACKAGE_PIN P20 IOSTANDARD LVCMOS33} [get_ports touch_sda]set_property -dict {PACKAGE_PIN U19 IOSTANDARD LVCMOS33} [get_ports touch_int]set_property -dict {PACKAGE_PIN M19 IOSTANDARD LVCMOS33} [get_ports touch_rst]

20.4 程序设计

根据实验任务我们画出了如下的程序框图:

图 7.5.13.1 LCD触摸实验程序框图

从上图的程序框架中可以看出本次实验的软件工程主要分成两个大模块:一个是GT9147配置模块(本节实验文档是以GT9147为例的,其他触摸芯片的配置和GT9147基本雷同),这个模块当中包含了IIC通信协议模块,触摸控制模块和信号切换模块,其主要作用就是配置触摸芯片,并和触摸芯片进行数据交互;另一个模块就是LCD显示模块了,这个模块大家应该很熟悉了,在前面LCD显示相关的实验中已经和大家讲解过了,在此就不再重复赘述了。下面我们来一起分析一下本节实验的代码。

本次LCD触摸实验的代码可以分成三个大模块:顶层例化模块(LCD_Touch_top)、触摸配置顶层模块(top_touch)、LCD屏幕显示顶层模块(lcd_rgb_char)。

其中顶层例化模块(LCD_Touch_top)模块代码如下:

1 module LCD_Touch_top(2 3//时钟和复位接口4inputclk_50m,//晶振时钟5inputrst_n, //按键复位6//SDRAM 接口7inouttouch_sda,8outputtouch_scl,9inouttouch_int,10outputtouch_rst, 11//RGB LCD接口12outputlcd_de,//LCD 数据使能信号13outputlcd_hs,//LCD 行同步信号14outputlcd_vs,//LCD 场同步信号15outputlcd_bl,//LCD 背光控制信号16outputlcd_rst_n,17outputlcd_clk,//LCD 像素时钟18inout [23:0] lcd_rgb//LCD RGB888颜色数据19 );20 21 //wire define22 wire clk_100m ;23 wire clk_50m_pll;24 wire locked;25 wire sys_rst_n ;26 wire touch_valid;27 wire [15:0] lcd_id;28 wire [31:0] tp1_xy; 29 wire [31:0] data ;30 wire tft_sda_i ;31 wire tft_sda_o ;32 wire tft_sda_t ;33 //*****************************************************34 //**main code35 //*****************************************************36 37 assign sys_rst_n = rst_n & locked;38 assign data = {tp1_xy[31:16],tp1_xy[15:0]}; 39 assign lcd_rst_n = 1'b1;40 assign touch_sda = tft_sda_t ? tft_sda_o : 1'bz;41 assign tft_sda_i = touch_sda;42 43 //例化锁相环模块44 clk_wiz_0 instance_name45 (46// Clock out ports47.clk_out1(clk_50m_pll), // output clk_out148.clk_out2(clk_100m), // output clk_out249// Status and control signals50.reset(!rst_n), // input reset51.locked(locked), // output locked52 // Clock in ports53.clk_in1(clk_50m));// input clk_in154 55 //触摸驱动56 top_touch u_top_touch(57.sys_clk (clk_100m),58.sys_rst_n(sys_rst_n),59.lcd_init_done (1), 60.lcd_id (lcd_id),//LCD ID 6162 .tft_sda_i (tft_sda_i),63 .tft_sda_o (tft_sda_o),64 .tft_sda_t (tft_sda_t),65 .tft_scl (touch_scl),66 .tft_tcs (touch_rst), 67 .tp1_xy(tp1_xy),68 .touch_valid(touch_valid), 69 .tp_num()70);71 72 //例化LCD显示模块73 lcd_rgb_char u_lcd_rgb_char74 (75 .sys_clk (clk_50m_pll),76 .sys_rst_n (sys_rst_n),77 .data (data),78 //RGB LCD接口 79 .lcd_id(lcd_id),80 .lcd_hs(lcd_hs), //LCD 行同步信号81 .lcd_vs(lcd_vs), //LCD 场同步信号82 .lcd_de(lcd_de), //LCD 数据输入使能83 .lcd_rgb (lcd_rgb),//LCD RGB888颜色数据84 .lcd_bl(lcd_bl), //LCD 背光控制信号85 .lcd_clk (lcd_clk) //LCD 采样时钟86 ); 87 88 endmodule

顶层代码比较简单主要就是例化锁相环(clk_wiz_0)模块、触摸配置顶层模块(top_touch)和LCD屏幕显示顶层模块(lcd_rgb_char),需要注意的一点就是代码第40和41行做了一个双向的判断,因为IIC协议不仅仅是主机(FPGA)对从机(GT9147)写数据,还要接收从机反馈的数据,因此touch_sda信号什么时候作为输入什么时候作为输出必须给出一个判断条件(tft_sda_t)来判断,这个判断条件是由IIC驱动模块(i2c_dri_m)发出的;还有一个GT9147自身中断控制信号(touch_int)也是双向信号,它是在触摸控制模块做的处理。

接下来我们继续分析顶层模块(LCD_Touch_top)所例化的子模块代码,首先看一下锁相环(clk_wiz_0)模块,它主要是用来生成一路50M时钟(clk_50m_pll)和一路100M(clk_100m)时钟。可能有人会疑惑系统时钟不就是50M时钟,为什么还要重新生成一次50M时钟?其实将系统时钟过一遍锁相环是常用的一种消除时钟抖动、扭斜的方法,并且锁相环还输出了一个locked信号,这个信号拉高代表时钟稳定,此时再去执行逻辑语句会使整个工程的时序更加稳定。

看完锁相环(clk_wiz_0)模块后我们继续分析触摸配置顶层模块(top_touch),顾名思义配置顶层模块是整个触摸操作的顶层模块,它同样例化了2个子模块,分别是寄存器配置模块(touch_gt_cfg)和触摸控制模块(touch_ctrl),下面给出触摸配置顶层模块(top_touch)的代码:

1 module top_touch(2 //module clock3 input sys_clk,// 系统时钟信号4 input sys_rst_n, // 复位信号(低有效)5 6 //tft interface7// inout tft_sda,8 input tft_sda_i,9 output tft_sda_o,10output tft_sda_t,11output tft_scl,12inout tft_int,13output reg tft_tcs,14 15//touch lcd interface 16output reg touch_valid,// 连续触摸标志17output [ 2:0] tp_num,1819input lcd_init_done,20input [15:0] lcd_id,21output reg [31:0] tp1_xy22 );23 24 //parameter define25 parameter WIDTH = 5'd8;26 //reg define27 reg bigger_en ; //电容屏使能信号28 //wire define29 wire sda_out ;30 wire sda_dir ;31 wire ack;32 wire i2c_exec ;33 wire i2c_rh_wl ;34 wire [15:0] i2c_addr ;35 wire [ 7:0] i2c_data_w;36 wire [WIDTH-1'b1:0] reg_num ;37 wire [ 7:0] i2c_data_r;38 wire i2c_done ;39 wire once_done ;40 wire bit_ctrl ;41 wire clk ;42 wire cfg_done ;43 wire cfg_switch;44 wire gf_cs;45 wire gf_done ;46 wire gf_valid ;47 wire [31:0] gf_xy;48 49 //*****************************************************50 //**main code51 //*****************************************************52 53 assign tft_sda_o=sda_out;54 assign tft_sda_t=sda_dir;55 56 always @(*) begin57if(!lcd_init_done) begin58bigger_en = 1'b0;59end 60else begin 61 bigger_en = 1'b1;62 tft_tcs= gf_cs;63 touch_valid = gf_valid;64 tp1_xy= gf_xy;65end66end67 68 touch_gt_cfg #(.WIDTH(4'd8)) u_touch_gt_cfg(69//module clock70.clk(sys_clk ), // 时钟信号71.rst_n (sys_rst_n ), // 复位信号72//port interface73.scl(tft_scl ),// 时钟线scl74.sda_in (tft_sda_i ),// 数据线sda75.sda_out (sda_out),76.sda_dir (sda_dir), 77//I2C interface78.ack(ack ),79.i2c_exec (i2c_exec ),// i2c触发控制80.i2c_rh_wl(i2c_rh_wl ),// i2c读写控制81.i2c_addr (i2c_addr ),// i2c操作地址82.i2c_data_w (i2c_data_w),// i2c写入的数据83.reg_num (reg_num ),84.i2c_data_r (i2c_data_r),// i2c读出的数据85.i2c_done (i2c_done ),// i2c操作结束标志86.once_done(once_done ),// 一次读写操作完成87.bit_ctrl (bit_ctrl ),88.clk_i2c (clk ),// I2C操作时钟89.cfg_done (cfg_done ),// 寄存器配置完成标志90//user interfacd91.cfg_switch (cfg_switch),92.lcd_id (lcd_id ) //LCD ID93 );94 95 touch_ctrl96#(.WIDTH(4'd8)) // 一次读写寄存器的个数的位宽97 u_touch_ctrl(98//module clock99.sys_clk (sys_clk ),100.clk(clk), // 时钟信号101.rst_n (sys_rst_n), // 复位信号(低有效)102.cfg_done (cfg_done ), // 配置完成标志103.tft_tcs (gf_cs ),104.tft_int (tft_int ),105 106//I2C interface107.ack(ack ),108.i2c_exec (i2c_exec ),// i2c触发控制109.i2c_rh_wl(i2c_rh_wl ),// i2c读写控制110.i2c_addr (i2c_addr ),// i2c操作地址111.i2c_data_w (i2c_data_w),// i2c写入的数据112.i2c_data_r (i2c_data_r),// i2c读出的数据113.once_done(once_done ),// 一次读写操作完成114.i2c_done (i2c_done ),// i2c操作结束标志115.bit_ctrl (bit_ctrl ),116.reg_num (reg_num ),// 一次读写寄存器的个数117 118//touch lcd interface 119.touch_valid (gf_valid),120.tp_num (tp_num),121.tp1_xy (gf_xy),122.tp2_xy (),123.tp3_xy (),124.tp4_xy (),125.tp5_xy (),126 127//user interface128.cfg_switch (cfg_switch),129.lcd_id (lcd_id ),//LCD ID130.bigger_en(bigger_en )131 );132 133 endmodule

触摸配置顶层模块(top_touch)的代码很容易理解,它就是单纯的作为一个顶层模块去例化寄存器配置模块(touch_gt_cfg)和触摸控制模块(touch_ctrl),下面我们来重点分析这两个子模块。

首先给出触摸控制模块(touch_ctrl)的代码(由于这个模块的代码太长我们一段一段的分析):

87 assign tft_int = int_dir ? int_out : 1'bz;88 89 always @(*) begin90if(lcd_id[15:8] == 8'h70 ) begin // 7寸屏的FT系列触摸芯片91 bit_ctrl= 1'b0 ; 92 CTRL_REG= 8'h00; // 控制寄存器地址93 GTCH_REG= 8'h02; // 检测到的当前触摸情况94 TP1_REG= 8'h03; // 第一个触摸点数据地址95 TP2_REG= 8'h09; // 第二个触摸点数据地址96 TP3_REG= 8'h0f; // 第三个触摸点数据地址97 TP4_REG= 8'h15; // 第四个触摸点数据地址98 TP5_REG= 8'h1b; // 第五个触摸点数据地址99end 100else begin101 bit_ctrl= 1'b1 ;102 CTRL_REG= 16'h8040;// 控制寄存器地址103 GTCH_REG= 16'h814e;// 检测到的当前触摸情况104 TP1_REG= 16'h8150;// 第一个触摸点数据地址105 TP2_REG= 16'h8158;// 第二个触摸点数据地址106 TP3_REG= 16'h8160;// 第三个触摸点数据地址107 TP4_REG= 16'h8168;// 第四个触摸点数据地址108 TP5_REG= 16'h8170;// 第五个触摸点数据地址109end110 end111 112 //计时控制113 always @(posedge clk or negedge rst_n) begin114if(!rst_n) begin115 cnt_1us_cnt <= 20'd0;116end117else if(cnt_1us_en)118 cnt_1us_cnt <= cnt_1us_cnt + 1'b1;119else120 cnt_1us_cnt <= 'd0;121 end122 123 //状态跳转124 always @ (posedge clk or negedge rst_n) begin125if(!rst_n)126 cur_state <= init;127else128 cur_state <= next_state;129 end130 131 //组合逻辑状态判断转换条件132 always @( * ) begin133case(cur_state)134 init: begin135 if(st_done)136 if(lcd_id == 16'h4384 || lcd_id == 16'h4342)137next_state = chk_touch;138 else139next_state = cfg_state; 140 else141 next_state = init;142 end143 144 cfg_state: begin145 if(st_done) begin146 next_state = chk_touch;147 end 148 else149 next_state = cfg_state;150 end151 chk_touch: begin152 if(st_done)153 next_state = change_addr;154 else155 next_state = chk_touch;156 end157 change_addr: begin158 if(st_done)159 next_state = getpos_xy;160 else161 next_state = change_addr;162 end163 getpos_xy: begin164 if(st_done)165 next_state = id_handle;166 else167 next_state = getpos_xy;168 end169 id_handle: begin170 if(st_done)171 next_state = tp_xy;172 else173 next_state = id_handle;174 end175 tp_xy: begin176 if(st_done) begin177 if(tp_num_t == tp_num)178 next_state = chk_touch;179 else180 next_state = change_addr;181 end182 else183 next_state = tp_xy;184 end185 default: next_state = init;186endcase187 end

因为这个模块的代码特别长,所以乍一看可能觉得特别难,但是你只要把这个模块的内容理顺了你就会发现这个模块的代码并不是很难,它是由一个LCD显示屏ID判断模块、一个计数器和一个经典三段状态机组成。代码的第89110行就是LCD显示屏ID判断模块,根据不同的屏幕ID来判断触摸芯片是七寸屏的FT系列触摸芯片还是4.3寸屏的GT系列触摸芯片(10.1寸屏也是GT系列),不同系列触摸芯片的配置寄存器地址不同(具体寄存器地址可以查找对应芯片的数据手册),同一系列芯片的寄存器地址相同(这里仅仅是指触摸数据的存放地址相同,其它功能性寄存器地址即使是同一系列的也存在差异,具体的要看对应芯片数据手册)。代码的第113121行是一个计数器,通过计数器使能信号(cnt_1us_en)来控制这个计数器的持续累加还是清零。

接下来的代码就是经典的三段状态机了,其中代码第12412行是状态机第一段,主要就是时序逻辑下让状态机发生状态跳转,而代码第132188行则是组合逻辑下判断状态机发生改变的执行条件,整个第二段(三段状态机的第二段)的状态变化条件都有st_done信号,这个信号是状态机每个状态内逻辑执行完成的标志,由状态机第三段控制。我们可以看到整个状态机共有七个状态分别是init(初始化状态)、cfg_state(配置寄存器状态)、chk_touch(触摸检测状态)、change_addr(触摸数据寄存器地址改变状态)、getpos_xy(读取触摸点坐标状态)、id_handle(坐标数据处理状态)和tp_xy(输出触摸点坐标状态)。首先进入初始化状态,在初始化状态会检查外接的显示屏ID,如果是4.3寸屏幕就跳过寄存器配置状态(因为出厂的时候厂家已经将触摸芯片的寄存器配置好了不需要我们再去重复配置,如果大家一定想自己配置寄存器,也可以不跳过配置状态进行寄存器配置,但是前提是一定要将整个工程完全看懂并且熟悉了芯片手册,否则一旦寄存器配置错误LCD显示屏将无法正常工作,即使断电重启也不行,因为配置信息会存储在FLASH中。所以这里提醒读者修改寄存器一定要慎重)进入触摸检测状态,如果是10寸屏或者7寸屏就会进入寄存器配置状态,等待寄存器配置完成再进入触摸检测状态;进入触摸检测状态后等待触摸检测状态的逻辑执行完(st_done拉高)然后进入触摸数据寄存器地址改变状态,同理等待逻辑执行完成后进入下一状态,直到进入最后一个状态(输出触摸点坐标状态),然后开始检测触摸点的个数(除了10.1寸屏幕支持最大10点触摸之外其他屏幕都是支持最大5点触摸),如果触摸点个数没有达到最大个数,则进入触摸数据寄存器地址改变状态,改变寄存器读取地址读出下一个点的坐标;如果达到了最大触摸点个数(注意这里的最大触摸点个数是可以设置的不一定就是10.1寸屏的最大10点触摸或者其他尺寸的最大5点触摸,小于10或者小于5都是可以的)则进入触摸检测状态进行下一轮的触摸点坐标检测,最终状态机会在chk_touch(触摸检测状态)、change_addr(触摸数据寄存器地址改变状态)、getpos_xy(读取触摸点坐标状态)、id_handle(坐标数据处理状态)和tp_xy(输出触摸点坐标状态)之间循环执行,以达到将连续触摸的点都输出出来的功能。

清楚了整个状态机的运作机制后我们再来看看每个状态都执行了哪些逻辑,也就是第三段状态机的内容,第三段状态机代码如下:

189 always @ (posedge clk or negedge rst_n) begin190 if(!rst_n) begin191 touch_valid <= 1'b0;192 flow_cnt<= 4'b0;193 st_done<= 1'b0;194 cnt_1us_en <= 1'b0;195 i2c_exec<= 1'b0;196 tft_tcs<= 1'b0;197 int_dir<= 1'b0;198 int_out<= 1'b0;199 i2c_addr<= 16'b0;200 i2c_data_w <= 8'd0;201 i2c_rh_wl <= 1'd0;202 cfg_switch <= 1'd0;203 tp_num <= 3'b0;204 tp_num_t<= 3'd0;205 reg_addr<= 16'd0;206 tp_x <= 16'd0;207 tp_y <= 16'd0;208 tp1_xy <= 32'd0;209 tp2_xy <= 32'd0;210 tp3_xy <= 32'd0;211 tp4_xy <= 32'd0;212 tp5_xy <= 32'd0;213 end214 else begin215 i2c_exec <= 1'b0;216 st_done <= 1'b0;217 case(next_state)218 init: begin219 case(flow_cnt)220'd0: begin221 if(bigger_en) begin222 flow_cnt <= flow_cnt + 1'b1;223 int_dir <= 1'b1;224 int_out <= 1'b1; 225 end 226end227'd1: begin228 cnt_1us_en <= 1'b1;229 if(cnt_1us_cnt <= 20000) begin // 延时20ms230 tft_tcs <= 1'b0;231 end232 else begin233 tft_tcs <= 1'b1;234 cnt_1us_en <= 1'b0;235 flow_cnt <= flow_cnt + 1'b1;236 end237end238'd2 : begin239 cnt_1us_en <= 1'b1;240 if(cnt_1us_cnt == 10000) begin//延时10ms设置IIC地址241 int_dir <= 1'b0;242 end 243 else if(cnt_1us_cnt == 100000) begin //延时100ms244 cnt_1us_en <= 1'b0;245 st_done <= 1'b1;246 int_dir <= 1'b0;247 flow_cnt <= 'd0;248 end249end250default : ;251 endcase252 end253 254 cfg_state: begin255 st_done <= 1'b0;256 case(flow_cnt)257'd0 : begin258 i2c_exec <= 1'b1;259 i2c_addr <= CTRL_REG;260 if(lcd_id[15:8] == 8'h70)261 i2c_data_w<= 8'h00;262 else263 i2c_data_w<= 8'h02;264 reg_num <= 'd1;265 i2c_rh_wl <= 1'b0;266 flow_cnt <= flow_cnt + 1'b1;267end268'd1: begin269 if(i2c_done) begin270 if(ack)271 flow_cnt <= flow_cnt - 1'b1;272 else273 flow_cnt <= flow_cnt + 1'b1;274 end275end276'd2 : begin277 i2c_exec <= 1'b1;278 if(lcd_id[15:8] == 8'h70 )279 i2c_addr <= FT_ID_G_MODE;280 else281 i2c_addr <= CTRL_REG; 282 i2c_data_w <= 8'h0;283 i2c_rh_wl <= 1'b0;284 reg_num <= 'd1;285 flow_cnt <= flow_cnt + 1'b1;286end287'd3 : begin288 if(i2c_done) begin289 if(ack)290 flow_cnt <= flow_cnt - 1'b1;291 else292 flow_cnt <= flow_cnt + 1'b1;293 end294end295'd4 : begin296 if(lcd_id[15:8] == 8'h70 )297 flow_cnt <= 'd7;298 else begin299 flow_cnt <= flow_cnt + 1'b1;300 cfg_switch <= 1'b1;301 end302end 303'd5: begin304 if(cfg_done) begin305 cfg_switch <= 1'b0;306 flow_cnt <= flow_cnt + 1'b1;307 end308end309'd6 : begin310 if(i2c_done) begin311 st_done <= 1'b1;312 flow_cnt <= 'd0;313 end314end315'd7: begin//设置触摸有效值316 i2c_exec <= 1'b1;317 i2c_addr <= FT_ID_G_THGROUP;318 i2c_data_w<= 8'd22;319 reg_num <= 'd1;320 i2c_rh_wl <= 1'b0;321 flow_cnt <= flow_cnt + 1'b1;322end323'd8: begin324 if(i2c_done) begin325 if(!ack)326 flow_cnt <= flow_cnt + 1'b1;327 else328 flow_cnt <= flow_cnt - 1'b1;329end330end331'd9: begin//激活周期,不能小于12,最大14332 i2c_exec <= 1'b1;333 i2c_addr <= FT_ID_G_PERIODACTIVE;334 i2c_data_w<= 8'd12;335 reg_num <= 'd1;336 i2c_rh_wl <= 1'b0;337 flow_cnt <= flow_cnt + 1'b1;338end339'd10: begin340 if(i2c_done) begin341 flow_cnt <= 'b0;342 st_done <= 1'b1;343 end344end 345default : ;346 endcase347 end348 chk_touch: begin // 检测触摸状态349 st_done <= 1'b0;350 case(flow_cnt)351'd0: begin352 tp_num_t <= 3'd0;353 tp_num <= 3'd0;354 if(lcd_id[15:8] == 8'h70 )355 flow_cnt <= flow_cnt + 1'b1; 356 else if(lcd_id[15:8] == 8'h10)begin357 cnt_1us_en <= 1'b1;358 if(cnt_1us_cnt == 50000) 359 flow_cnt <= flow_cnt + 1'b1; 360 end 361 else begin362 cnt_1us_en <= 1'b1;363 if(cnt_1us_cnt == 17000)364 //模块驱动时钟周期为625ns(频率为I2C时钟的4倍,400Kbps*4)365 flow_cnt <= flow_cnt + 1'b1; 366 //此处每隔625ns*17000采样一次,即LCD每隔10.625ms更新一次坐标 367 end 368end369'd1: begin //'d2370if(lcd_id==16'h7016||lcd_id==16'h7084)begin371cnt_1us_en <= 1'b1;372 if(cnt_1us_cnt == 2000) begin373 cnt_1us_en <= 1'b0;374 i2c_exec <= 1'b1;375 i2c_addr <= GTCH_REG;376 i2c_rh_wl<= 1'b1;377 reg_num <= 'd1;378 flow_cnt <= flow_cnt + 1'b1; 379 end 380end381else begin 382383 cnt_1us_en <= 1'b0;384 i2c_exec <= 1'b1;385 i2c_addr <= GTCH_REG;386 i2c_rh_wl<= 1'b1;387 reg_num <= 'd1;388 flow_cnt <= flow_cnt + 1'b1;389end390end391 392'd2: begin393 if(i2c_done) begin394 if(ack)395 flow_cnt <= flow_cnt - 1'b1;396 else397 flow_cnt <= flow_cnt + 1'b1;398end399end400'd3: begin401 if(lcd_id[15:8] == 8'h70 )402 flow_cnt <= flow_cnt + 1'b1;403 else404 flow_cnt <= flow_cnt + 2'd2;405end406'd4: begin407 if(i2c_data_r != 8'hff && i2c_data_r[3:0] != 4'd0 408 && i2c_data_r[3:0] < 4'd6) begin409 flow_cnt <= 'd0;410 st_done <= 1'b1; 411 touch_valid<= 1'b1; 412 tp_num <= i2c_data_r[2:0];413 end414 else begin415 touch_valid<= 1'b0;416 flow_cnt <= 'd1;417 end418end419'd5: begin420 if(i2c_data_r[7]== 1'b1 && i2c_data_r[3:0] != 4'd0)421 begin422 flow_cnt <= flow_cnt + 1'b1;423 touch_valid<= 1'b1; //i2c_data_r[4];424 tp_num <= i2c_data_r[2:0];425 end426 else begin427 touch_valid<= 1'b0; //i2c_data_r[4];428 flow_cnt <= flow_cnt + 1'b1;429 end430end431'd6: begin432 i2c_exec <= 1'b1;433 i2c_addr <= GTCH_REG;434 i2c_rh_wl<= 1'b0;435 i2c_data_w<= 8'd0;436 reg_num <= 'd1;437 flow_cnt <= flow_cnt + 1'b1; 438end439'd7: begin440 if(i2c_done) begin441 if(ack)442 flow_cnt <= flow_cnt - 1'b1;443 else if(tp_num) begin444 st_done <= 1'b1;445 flow_cnt <= 'd0;446 end447 else 448 flow_cnt <= 'd0; 449 end450end451default : ;452 endcase453 end // case: touch_statue454 change_addr: begin455 st_done <= 1'b0;456 if(tp_num_t < tp_num) begin457case(tp_num_t)458 'd0 : begin459 reg_addr <= TP1_REG;460 st_done <= 1'b1;461 end462 'd1 : begin463 reg_addr <= TP2_REG;464 st_done <= 1'b1;465 end466 'd2 : begin467 reg_addr <= TP3_REG;468 st_done <= 1'b1;469 end470 'd3 : begin471 reg_addr <= TP4_REG;472 st_done <= 1'b1;473 end474 'd4 : begin475 reg_addr <= TP5_REG;476 st_done <= 1'b1;477 end478 default : ;479endcase480 end481 else begin482st_done <= 1'b1;483 end484 end485 getpos_xy: begin486 st_done <= 1'b0;487 case(flow_cnt)488'd0 : begin489 i2c_exec <= 1'b1;490 i2c_rh_wl<= 1'b1;491 i2c_addr <= reg_addr;492 reg_num <= 'd4;493 flow_cnt <= flow_cnt + 1'b1;494end495'd1 : begin496 if(ack)497 flow_cnt <= flow_cnt - 1'b1;498 if(once_done) begin499 tp_x[7:0] <= i2c_data_r;500 flow_cnt <= flow_cnt + 1'b1;501 end502end503'd2 : begin504 if(once_done) begin505 tp_x[15:8] <= i2c_data_r;506 flow_cnt <= flow_cnt + 1'b1;507 end508end509'd3 : begin510 if(once_done) begin511 tp_y[7:0] <= i2c_data_r;512 flow_cnt <= flow_cnt + 1'b1;513end514end515'd4 : begin516 if(i2c_done) begin517 tp_y[15:8] <= i2c_data_r;518 st_done <= 1'b1;519 flow_cnt <= 'd0;520 end521end522default : ;523 endcase524 end525 id_handle: begin526 st_done <= 1'b0;527 case(lcd_id)52816'h4342: begin// 4.3' RGB 529 tp_x <= tp_x; 530 tp_y <= tp_y;531 st_done <= 1'b1;532end53316'h7084,16'h1963: begin534 tp_x <= {4'd0,tp_y[3:0],tp_y[15:8]};535 tp_y <= {4'd0,tp_x[3:0],tp_x[15:8]};536 st_done <= 1'b1; 537end53816'h7016: begin539 tp_x <={4'd0,tp_y[3:0],tp_y[15:8]};540 tp_y <={4'd0,tp_x[3:0],tp_x[15:8]};541 st_done <= 1'b1; 542end54316'h1018: begin544 tp_x <= tp_x;545 tp_y <= tp_y; 546 st_done <= 1'b1;547end 54816'h4384: begin549tp_x <=tp_x;550tp_y <=tp_y;551st_done <= 1'b1; 552end553554default: st_done <= 1'b1; 555 endcase556 end557 tp_xy: begin558 st_done <= 1'b0;559 case(tp_num_t) 560'd0: begin561 tp1_xy <= {tp_x,tp_y};562 st_done <= 1'b1;563 tp_num_t <= tp_num_t + 1'b1;564end565'd1 : begin566 tp2_xy <= {tp_x,tp_y};567 st_done <= 1'b1;568 tp_num_t <= tp_num_t + 1'b1;569end570'd2: begin571 tp3_xy <= {tp_x,tp_y};572 st_done <= 1'b1;573 tp_num_t <= tp_num_t + 1'b1;574end575'd3: begin576 tp4_xy <= {tp_x,tp_y};577 st_done <= 1'b1;578 tp_num_t <= tp_num_t + 1'b1;579end580'd4: begin581 tp5_xy <= {tp_x,tp_y};582 st_done <= 1'b1;583 tp_num_t <= tp_num_t + 1'b1;584end585default : ;586 endcase587 end588 default : ;589 endcase590 end591 end592 593 endmodule

第三段状态机就比较长了,不过没关系我们一个状态一个状态的分析。首先是初始化状态,此状态执行了一个case语句,它包含了flow_cnt等于0、1、2三个小状态,当flow_cnt等于0的时候要检测bigger_en信号(bigger_en信号就是LCD初始化完成信号,这个接口其实是一个预留接口在顶层中直接赋常值1了),当其为高电平时int_dir(触摸芯片tft_int中断信号的方向判定)和int_out(触摸芯片中断信号的值)输出值为1,接着进入flow_cnt等于1状态进行延迟(延迟主要通过上文提到的计数器来完成),然后对应输出tft_tcs(触摸芯片的复位信号)的值,之后进入flow_cnt等于2状态再次改变int_dir的值(改变触摸芯片tft_int中断信号的方向),同时清空flow_cnt的值(flow_cnt这个寄存器是重复使用的,每个状态逻辑执行完后要对它清零)并拉高st_done信号(让三段状态机的第二段进入下一个状态),至于为什么要延迟并给中断信号和复位信号进行不同的赋值主要是根据芯片手册的时序来的,具体上电时序可以查看芯片数据手册。

初始化状态结束后是寄存器配置状态,进入这个状态同样是执行一个case语句,然后判断外接屏幕是7寸的还是10寸的(因为4.3寸屏幕会跳过这个状态,所以这里只判断是7寸还是10寸),不同尺寸的屏幕配置的寄存器地址与对应写入的值是不相同的。这里尤其要注意flow_cnt等于4的时候如果是10寸屏就会激活cfg_switch控制信号,它一旦被激活就会传递到另外一个信号切换子模块(signal_switch)然后再传递到寄存器配置子模块(i2c_reg_cfg),之后会激活整个寄存器组重新配置,总共会配置188个寄存器,其他尺寸的屏幕其实都没有真正意义上的重新配置寄存器,仅仅只是配置了控制寄存器,并读取存放触摸数据的寄存器的值,只有10寸屏是真正意义上的配置完整寄存器组。如果你想对其他尺寸的屏幕进行整个寄存器组的重配置这里也可以修改一下,比如识别到其他尺寸屏幕ID时也可以仿照10寸屏的写法激活cfg_switch信号,在寄存器配置子模块(i2c_reg_cfg)中我们是存放了三种尺寸屏幕的完整寄存器组的,只要修改了这里的状态机就可以激活所有屏幕的完整寄存器配置(这里提醒读者慎重修改,防止寄存器组配置错误导致屏幕无法正常工作)。

接下来进入触摸检测状态(chk_touch),同样的要检测外接LCD屏的ID,判断是什么尺寸的,不同尺寸的屏幕对应检测的时序不同,主要体现在延迟时间的长短上,然后根据不同尺寸的ID,访问触摸芯片的触摸控制寄存器(GTCH_REG),根据触摸芯片给出的反馈(ack)和应答(i2c_data_r读出的数据)判断触摸是否有效(touch_valid),最大触摸点数是多少(tp_num)。之后进入触摸数据存放寄存器地址改变状态,根据上一状态得到的最大触摸点数改变相应的地址。有了地址之后就可以进入读取触摸数据坐标状态(getpos_xy),在这一状态主要就是通过IIC协议把对应地址里的数据读取出来就行。再之后进入数据处理状态(id_handle),在此状态中根据不同屏幕的尺寸把对应数值做一下处理,主要就是作数据拼接(7寸屏的数据读取出来需要拼接)或者改变x轴y轴的方向(例如ID为4384的屏幕分辨率为800*480,它的x轴坐标本身是从左往右坐标递增,现在假如将第531行代码改写成“tp_x <=799-tp_x;”那么x轴坐标就变成从左往右递减了)。数据处理完后就会进入最后一个状态数据输出状态(tp_xy),在这个状态坐标会被输出,并且每输出一个坐标tp_num_t(已读出的数据个数)计数器就会加一,用来配合触摸数据存放寄存器地址改变状态(change_addr)使用。

到这里整个触摸控制子模块就给大家讲解完了,下面我们继续分析另一个寄存器配置子模块(touch_gt_cfg),其代码如下:

1 module touch_gt_cfg #(parameter WIDTH = 4'd8)2 (3 //system clock4 input clk ,// 时钟信号5 input rst_n,// 复位信号6 7 //port interface8 output scl ,// 时钟线scl9 input sda_in ,// 数据线sda10output sda_out ,11output sda_dir ,12 13//I2C interface14input bit_ctrl ,15input i2c_exec ,// i2c触发控制16input i2c_rh_wl ,// i2c读写控制17input [15:0] i2c_addr ,// i2c操作地址18input [ 7:0] i2c_data_w,// i2c写入的数据19input [WIDTH-1'b1:0] reg_num ,20input cfg_switch,21output [ 7:0] i2c_data_r,// i2c读出的数据22output i2c_done ,// i2c操作结束标志23output once_done ,// 一次读写操作完成24output clk_i2c ,// I2C操作时钟25output ack ,26//user interface27output cfg_done ,// 寄存器配置完成标志28input [15:0] lcd_id 29 );30 31 //parameter define32 //localparam SLAVE_ADDR = 7'h5d ; // 器件地址(SLAVE_ADDR)33 //localparam BIT_CTRL = 1'b1 ; // 字地址位控制参数(16b/8b)34 localparam CLK_FREQ = 27'd100_000_000; 35 //i2c_dri模块的驱动时钟频率(CLK_FREQ)36 localparam I2C_FREQ = 19'd400_000 ; // I2C的SCL时钟频率37 38 //wire define39 wire cfg_i2c_exec ;// i2c触发控制40 wire cfg_i2c_rh_wl ;// i2c读写控制41 wire [15:0] cfg_i2c_addr ;// i2c操作地址42 wire [ 7:0] cfg_i2c_data ;// i2c写入的数据43 wire cfg_once_done ;// i2c操作结束标志44 wire [WIDTH-1'b1:0] cfg_reg_num ;// i2c读出的数据45 wire m_i2c_exec ;46 wire m_i2c_rh_wl ;47 wire [15:0] m_i2c_addr ;48 wire [ 7:0] m_i2c_data_w ;49 wire [ 7:0] m_i2c_data_r ;50 wire [WIDTH-1'b1:0] m_reg_num;51 wire m_once_done ;52 reg [6:0]slave_addr ;53 54 //*****************************************************55 //**main code56 //*****************************************************57 58 always @(*) begin59if(lcd_id[15:8] == 8'h70 || lcd_id[15:8]== 8'h19)60slave_addr = 7'h38;61else 62slave_addr = 7'h14;63 end 64 65 //信号转换66 signal_switch #(.WIDTH(WIDTH)67 ) u1_signal_switch(68//module169.m1_0 (i2c_exec),70.m1_1 (i2c_rh_wl),71.m1_2 (i2c_addr),72.m1_3 (i2c_data_w ),73.m1_4 (i2c_data_r ),74.m1_5 (reg_num ),75.m1_6 (once_done),76//module277.m2_0 (cfg_i2c_exec ),78.m2_1 (cfg_i2c_rh_wl ),79.m2_2 (cfg_i2c_addr ),80.m2_3 (cfg_i2c_data ),81.m2_4 ( ),82.m2_5 (cfg_reg_num ),83.m2_6 (cfg_once_done ),84//module385.m3_0 (m_i2c_exec ), // i2c触发控制86.m3_1 (m_i2c_rh_wl ), // i2c读写控制87.m3_2 (m_i2c_addr ), // i2c寄存器地址88.m3_3 (m_i2c_data_w ), // i2c写入的数据89.m3_4 (m_i2c_data_r ), // i2c读出的数据90.m3_5 (m_reg_num), // 一次读写寄存器的个数91.m3_6 (m_once_done ), // 一次读写操作完成92//ctrl signal93.ctrl_switch (cfg_switch ) // 切换信号94 );95 96 //例化i2c_dri_m97 i2c_dri_m #(98 // .SLAVE_ADDR (SLAVE_ADDR ), //slave address从机地址,放此处方便参数传递99.CLK_FREQ (CLK_FREQ ), //i2c_dri模块的驱动时钟频率(CLK_FREQ)100.I2C_FREQ (I2C_FREQ ), //I2C的SCL时钟频率101.WIDTH (WIDTH )102 ) u_i2c_dri(103//global clock104.clk (clk ), // i2c_dri模块的驱动时钟(CLK_FREQ)105.rst_n (rst_n ), // 复位信号106//i2c interface107.slave_addr (slave_addr ),108.i2c_exec (m_i2c_exec ), // I2C触发执行信号109.bit_ctrl (bit_ctrl ), // 器件地址位控制(16b/8b)110.i2c_rh_wl (m_i2c_rh_wl ), // I2C读写控制信号111.i2c_addr (m_i2c_addr ), // I2C寄存器地址112.i2c_data_w (m_i2c_data_w), // I2C要写的数据113.i2c_data_r (m_i2c_data_r), // I2C读出的数据114.i2c_done (i2c_done ), // I2C操作完成115.once_done (m_once_done ), // 一次读写操作完成116.scl (scl ), // I2C的SCL时钟信号117.sda_in(sda_in), // I2C的SDA信号118.sda_out(sda_out),119.sda_dir(sda_dir), 120.ack (ack ), 121//user interface122.reg_num(m_reg_num ), // 一次读写寄存器的个数123.dri_clk(clk_i2c),// I2C操作时钟124.lcd_id(lcd_id)125 );126 127 //例化i2c_reg_cfg模块128 i2c_reg_cfg u_i2c_reg_cfg(129//clock & reset130.clk (clk_i2c), // i2c_reg_cfg驱动时钟(一般取1MHz)131.rst_n (rst_n ), // 复位信号132//i2c interface133.i2c_exec (cfg_i2c_exec ), // I2C触发执行信号134.i2c_rh_wl (cfg_i2c_rh_wl), // I2C读写控制信号135.i2c_addr (cfg_i2c_addr ), // 寄存器地址136.i2c_data (cfg_i2c_data ), // 寄存器数据137.once_done (cfg_once_done), // 一次读写操作完成138.cfg_done (cfg_done), // 配置完成139//user interface140.reg_num(cfg_reg_num ), // 一次读写寄存器的个数141.cfg_switch (cfg_switch ), // 切换信号142.lcd_id(lcd_id ) 143 );144 145 endmodule

寄存器配置子模块(touch_gt_cfg)同样例化了三个子模块分别是信号切换模块(signal_switch)、IIC驱动模块(i2c_dri_m)和寄存器组控制模块(i2c_reg_cfg)。

要想理解整个寄存器配置子模块(touch_gt_cfg)是如何工作的就要先理清它所例化的三个子模块之间的关系。首先IIC驱动模块(i2c_dri_m)的功能很好理解,它就是执行IIC协议通信,给出触发信号(i2c_exec)、读写切换信号(i2c_rh_wl)、读写地址(i2c_addr)、写数据(i2c_data_w)它就能和从器件进行数据交互。关键就在这里,这四个控制线由谁发出呢?在上文已经说过了touch_ctrl模块的状态机会执行配置触摸芯片控制寄存器,会访问触摸芯片的触摸检测寄存器和触摸数据存放寄存器,它也是通过IIC协议去进行数据交互的那么它也就必然也会给出相应的i2c_exec、i2c_rh_wl、i2c_addr和i2c_data_w信号。同样的在上文touch_ctrl模块中我也说了对于10寸屏是需要配置整个寄存器组的,而寄存器组是存放在寄存器组控制模块(i2c_reg_cfg)中的,那么i2c_reg_cfg模块必然也要给出i2c_exec、i2c_rh_wl、i2c_addr和i2c_data_w信号,这就会造成冲突。因此我们引入了信号切换模块(signal_switch),寄存器组控制模块(i2c_reg_cfg)和touch_ctrl模块给出的IIC驱动信号都先接入信号切换模块(signal_switch),然后信号切换模块(signal_switch)会判断cfg_switch信号是否被激活,如果激活说明需要对触摸芯片的整个寄存器组进行重配置,那么寄存器组控制模块(i2c_reg_cfg)给出的IIC驱动信号有效,反之touch_ctrl模块给出的IIC驱动信号有效,这样就完美的解决了IIC驱动信号冲突的问题。

当我们了解了信号切换模块(signal_switch)、IIC驱动模块(i2c_dri_m)和寄存器组控制模块(i2c_reg_cfg)三者之间的关系我们再来分析它的代码,先看信号切换模块(signal_switch)的代码:

1 module signal_switch #(parameter WIDTH = 4'd8 //一次读写寄存器的个数的位宽2 )(3//module14input m1_0,5input m1_1,6input [15:0] m1_2,7input [ 7:0] m1_3,8output reg [ 7:0] m1_4,9input [WIDTH-1'b1:0] m1_5,10output regm1_6,11 12//module2 13input m2_0,14input m2_1,15input [15:0] m2_2,16input [ 7:0] m2_3,17output reg [ 7:0] m2_4,18input [WIDTH-1'b1:0] m2_5,19output regm2_6,20 21//module2 22output regm3_0, 23output regm3_1, 24output reg [15:0] m3_2, 25output reg [ 7:0] m3_3, 26input [ 7:0] m3_4, 27output reg [WIDTH-1'b1:0] m3_5, 28input m3_6, 2930//ctrl signal31input ctrl_switch // 切换信号3233 );34 35 //*****************************************************36 //**main code37 //*****************************************************38 39 //信号转换40 always @(*) begin41if(ctrl_switch) begin42 m3_0 = m2_0;43 m3_1 = m2_1;44 m3_2 = m2_2;45 m3_3 = m2_3;46 m2_4 = m3_4;47 m3_5 = m2_5;48 m2_6 = m3_6;49end50else begin51 m3_0 = m1_0;52 m3_1 = m1_1;53 m3_2 = m1_2;54 m3_3 = m1_3;55 m1_4 = m3_4;56 m3_5 = m1_5;57 m1_6 = m3_6;58end 59 end60 61 endmodule

信号切换模块(signal_switch)的代码比较简单就不讲解了,下面我们直接来看寄存器组控制模块(i2c_reg_cfg)的代码:

1 module i2c_reg_cfg #(parameter WIDTH = 4'd82 )(3 input clk , //i2c_reg_cfg驱动时钟(一般取1MHz)4 input rst_n, // 复位信号5 input once_done , // I2C一次操作完成反馈信号6 7 output reg i2c_exec , // I2C触发执行信号8 output reg i2c_rh_wl , // I2C读写控制信号9 output reg [15:0]i2c_addr , // 寄存器地址10output reg [ 7:0]i2c_data , // 寄存器数据11output reg cfg_done , // WM8978配置完成12 13//user interface14input cfg_switch, // 配置切换15input [15:0]lcd_id,16output reg [WIDTH-1'b1:0] reg_num17 );18 19 //parameter define20 localparam MODE = 8'h1 ; 21 // 0X8100用于控制是否将配置保存在本地,写 0,则不保存配置,写 1 则保存配置。22 //localparam REG_NUM_4 = 8'd186; // 总共需要配置的寄存器个数23 //GT9147 部分寄存器定义24 localparam GT_CTRL_REG = 16'h8040; // GT系列控制寄存器25 localparam GT_CFGS_REG = 16'h8047; // GT系列配置起始地址寄存器26 localparam GT_CHECK_REG = 16'h80FF; // GT系列校验和寄存器27 28 //reg define29 reg [2:0] start_init_cnt; // 初始化时间计数30 reg [7:0] init_reg_cnt ; // 寄存器配置个数计数器31 reg [7:0] sum_t1;// 计算校验和32 reg [7:0] REG_NUM;// 总共需要配置的寄存器个数33 34 //wire define35 wirerd_en ;36 wire [7:0] sum_t2;// 计算校验和37 reg [9:0] address;38 wire [7:0] q;39 40 //*****************************************************41 //**main code42 //*****************************************************43 44 //计算校验和45 assign sum_t2 = init_reg_cnt == REG_NUM - 'd3 ? (~sum_t1 + 1'd1) : sum_t2;46 assign rd_en = init_reg_cnt <= REG_NUM - 'd3 ? 1'b1: 1'b0;47 48 always @(*) begin49if(lcd_id ==16'h4342)50address =init_reg_cnt;51else if(lcd_id ==16'h1018)52address =init_reg_cnt+ 8'd184;53else if(lcd_id == 16'h4384)54address =init_reg_cnt+ 9'd370;55 end56 57 always @(*) begin58if(lcd_id[15:12] == 4'h1) // 10.1'59REG_NUM = 8'd188;60else 61REG_NUM = 8'd186; // 4.3'62 end63 64 //I2C开始操作控制65 always @(posedge clk or negedge rst_n) begin66if(!rst_n) begin67start_init_cnt <= 3'b0;68end69else if(cfg_switch) begin70if(start_init_cnt < 3'h2)71 start_init_cnt <= start_init_cnt + 1'b1;72end73 end74 75 // 触发I2C操作控制76 always @(posedge clk or negedge rst_n) begin77if(!rst_n)78i2c_exec <= 1'b0;79else if(cfg_switch) begin80if(start_init_cnt == 9'h1)81 i2c_exec <= 1'b1; 82else83 i2c_exec <= 1'b0;84end85 end86 87 //配置寄存器个数计数88 always @(posedge clk or negedge rst_n) begin89if(!rst_n) begin90init_reg_cnt <= 8'd0;91end92else if(cfg_switch & once_done)93init_reg_cnt <= init_reg_cnt + 1'b1;94 end95 96 //寄存器配置完成信号97 always @(posedge clk or negedge rst_n) begin98if(!rst_n)99cfg_done <= 1'b0;100else if(init_reg_cnt == REG_NUM)101 cfg_done <= 1'b1;102 end103 104 //计算校验和105 always @(posedge clk) begin106if(once_done & (init_reg_cnt <= REG_NUM - 'd3))107 sum_t1 = sum_t1 + i2c_data;108else109 sum_t1 = sum_t1;110 end111 112 always @(posedge clk) begin113if(cfg_switch) begin114 i2c_rh_wl<= 1'b0;115 i2c_addr <= GT_CFGS_REG;116 reg_num <= REG_NUM;117 if(lcd_id[15:12] == 4'h1) begin // 16'h1018 10.1'118 case(init_reg_cnt)119 8'd186: i2c_data <= sum_t2;120 8'd187: i2c_data <= MODE ;121 default: i2c_data <= q;122 endcase123 end124 else begin125 case(init_reg_cnt)126 8'd184: i2c_data <= sum_t2;127 8'd185: i2c_data <= MODE ;128 default: i2c_data <= q;129 endcase130 end131end132 end133 134 gt_cfg gt_cfg_u (135 .clka(clk), // input wire clka136 .ena(rd_en),// input wire ena137 .addra(address), // input wire [8 : 0] addra138 .douta(q) // output wire [7 : 0] douta139 );140 141 endmodule

这个模块的代码其实也很简单,就是当cfg_switch信号激活后生成对应的IIC控制信号,需要注意的是整个寄存器组不是直接存放在代码中而是放在了ROM中。代码的第134~139行例化了一个ROM IP核,这个ROM会加载一个coe文件(gt_cfg1.coe文件放在工程目录下的doc文件夹中),coe文件包含了三种尺寸屏幕的寄存器组数值。因此代码的第4855行就根据LCD屏的不同ID选择ROM读取数据地址的起始位置,不同的起始位置对应不同屏幕的寄存器组。代码第5762行是根据屏幕ID选择具体配置寄存器的个数。代码第105~132行是计算校验和,这个仅仅是10寸屏需要执行校验,其他两种屏幕不需要。

最后我们再来看看IIC驱动模块的代码,因为IIC驱动模块在前面的例程中已经讲解过了,这里就不再详细讲解了,主要就是看一下修改的地方,本节实验的IIC驱动模块新增了连续读写的功能,代码如下(因为代码比较长,这里只给出局部代码,如果对整个IIC协议不了解的请参考前面eeprom例程):

77 assign clk_divide =(lcd_id==16'h7016||lcd_id==16'h7084)?250:25;78 assign reg_done = reg_cnt == reg_num ? 1'b1 : 1'b0;79 80 //生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作81 always @(posedge clk or negedge rst_n) begin82if(rst_n == 1'b0) begin83dri_clk <= 1'b1;84clk_cnt <= 10'd0;85end86else if(clk_cnt == clk_divide - 1'd1) begin87clk_cnt <= 10'd0;88dri_clk <= ~dri_clk;89end90else91clk_cnt <= clk_cnt + 1'b1;92 end93 94 //寄存器个数计数95 always @(posedge dri_clk or negedge rst_n) begin96if(!rst_n)97reg_cnt <= 'd0;98else if(once_done)99reg_cnt <= reg_cnt + 1'd1;100else if(i2c_done)101 reg_cnt <= 'd0; 102 end103 104 //(三段式状态机)同步时序描述状态转移105 always @(posedge dri_clk or negedge rst_n) begin106if(rst_n == 1'b0)107 cur_state <= st_idle;108else109 cur_state <= next_state;110 end111 112 //组合逻辑判断状态转移条件113 always @( * ) begin114 // next_state = st_idle;115case(cur_state)116 st_idle: begin // 空闲状态117 if(i2c_exec) begin118next_state = st_sladdr;119 end120 else121next_state = st_idle;122 end123 st_sladdr: begin124 if(st_done) begin125 if(!ack) begin126 if(bit_ctrl)// 判断是16位还是8位字地址127next_state = st_addr16;128 else129next_state = st_addr8 ;130 end131 else 132 next_state = st_stop; 133 end134 else135 next_state = st_sladdr;136 end137 st_addr16: begin// 写16位字地址138 if(st_done) begin139 if(!ack) 140 next_state = st_addr8;141 else 142 next_state = st_stop; 143 end144 else145 next_state = st_addr16;146 end147 st_addr8: begin // 8位字地址148 if(st_done) begin149 if(!ack) begin150 if(wr_flag==1'b0) // 读写判断151next_state = st_data_wr;152 else153next_state = st_addr_rd;154 end155 else 156 next_state = st_stop; 157 end158 else159 next_state = st_addr8;160 end161 st_data_wr: begin // 写数据(8 bit)162 if(st_done) begin163 if(reg_done)164 next_state = st_stop;165 else166 next_state = st_data_wr;167 end168 else169 next_state = st_data_wr;170 end171 st_addr_rd: begin // 写地址以进行读数据172 if(st_done) begin173 if(!ack)174 next_state = st_data_rd;175 else176 next_state = st_stop;177 end178 else179 next_state = st_addr_rd;180 end181 st_data_rd: begin // 读取数据(8 bit)182 if(st_done) begin183 if(reg_done)184 next_state = st_stop;185 else186 next_state = st_data_rd;187 end188 else189 next_state = st_data_rd;190 end191 st_stop: begin// 结束I2C操作192 if(st_done)193 next_state = st_idle;194 else195 next_state = st_stop ;196 end197 default: next_state= st_idle;198endcase199 end

ID为7016跟7084的屏幕使用的触摸芯片是CST340,其它屏幕使用的触摸芯片是GT9147,这两款芯片的驱动时钟不一样,第77行代码的主要作用是兼容两款触摸芯片。代码的第78行定义了reg_done信号,它就是连续写完成的标志,当“reg_cnt == reg_num”的时候reg_done才会拉高。reg_num是需要配置寄存器的个数由touch_ctrl模块或者i2c_reg_cfg模块提供,代码第95102行是寄存器个数计数器,每完成一个寄存器写入reg_cnt就会加一,直到i2c_done信号拉高为止。i2c_done信号是当IIC状态机进入最后一个状态(st_stop)时才会拉高,因此当i2c_done拉高代表连续读写多个寄存器完成。代码第105199行是状态机第二段(IIC通信三段状态机的第二段),我们可以看到代码第161170行以及代码第181190行分别是IIC通信的写数据和读数据状态,这两个状态都判断了reg_done信号是否拉高,如果reg_done信号未拉高代表连续读写寄存器还没完成,那么读写状态循环执行,直到把所有寄存器全部写完或者读完,reg_done信号才会拉低,状态机进入st_stop状态,然后拉高i2c_done信号,告诉其它模块一次连续读写完成。

到这里整个触摸部分就全部给大家分析完了,接下来再带领大家一起看一下LCD显示部分代码,LCD显示部分代码在前面的例程中也都讲解过了,本节实验仅仅分析一下字模显示模块的代码,至于LCD驱动部分的代码如果大家有不熟悉的可以参考前面LCD彩条显示实验。

字模显示模块(lcd_display)代码如下(因为本模块的字模比较长,直接粘贴代码不好看,所以这里以截图的方式一段一段给大家讲解):

图 7.5.13.2字模显示模块(lcd_display)代码

上图展示的是数字0~9和字母“X”、字母“Y”的字模,它是使用“PCtoLCD2002完美版”(软件存放路径:[正点原子]领航者ZYNQ开发板光盘资料\领航者ZYNQ开发板资料盘(A盘)\6_软件资料\1_软件)软件生成的,生成步骤如下图所示:

图 7.5.13.3字模生成步骤

首先在资料盘A盘找到“PCtoLCD2002完美版”软件并打开,然后按照上图步骤操作。第一步点击“选项”按钮打开字模属性设置,然后按照上图序号2、3、4、5、6、7、8步骤设置字模参数并点击确定,之后在上图序号9的位置输入我们想要生成的字符(本节实验输入0123456789XY),再到序号10的位置设置字符的宽度和高度(本节实验选择3232,但是在实际显示英文字符的时候宽度会减半也就是1632),最后点击“生成字模”按钮,在上图序号11的位置就生成了我们需要的字模数据组,拷贝到代码中即可使用。

接下来我们再来看看怎么把touch_ctrl模块读取到的坐标显示到LCD屏幕上去,首先我们要把坐标点数据接入字模显示模块(lcd_display),然后将X轴Y轴数据转换成能显示的个位、十位、百位、千位(只有X轴坐标会到达千位,因为最大分辨率是1280*800),与上文的字模数组对应起来,代码如下:

36 assign data6 = data[31:16] / 10'd1000 % 4'd10 ; // X轴坐标千位数37 assign data5 = data[31:16] / 7'd100 % 4'd10 ; // X轴坐标百位数38 assign data4 = data[31:16] / 4'd10 % 4'd10 ; // X轴坐标十位数39 assign data3 = data[31:16] % 4'd10 ; // X轴坐标个位数40 assign data2 = data[15:0] / 7'd100 % 4'd10 ; // Y轴坐标百位数41 assign data1 = data[15:0] / 4'd10 % 4'd10 ; // Y轴坐标十位数42 assign data0 = data[15:0] % 4'd10 ; // Y轴坐标个位数

这里的data就是touch_ctrl模块读到的数据点坐标,data的高16位是X轴坐标,低16位是Y轴坐标,将坐标数据拆分成个位、十位、百位、千位后分别存到寄存器data0~ data6中,最后就是将实际坐标数据与字模数组联系起来,代码如下:

图 7.5.13.4显示区域赋值

上图中的代码就是将具体要显示的坐标数据和字模数组结合到一起,达到显示坐标数据的功能(上图只截了一部分,完整版请参考例程源代码),那么它的原理是什么呢?我们来分析一下代码,首先代码的第101~107行是显示第一个字符,也就是X轴坐标的最高位,其中代码第101和102行是用来确定第一个字符显示的位置,pixel_xpos与pixel_ypos是LCD屏幕行列扫描信号,CHAR_POS_X与CHAR_POS_Y是显示区域的起始坐标,CHAR_WIDTH和CHAR_HEIGHT是整个显示区域的总宽度和高度(X轴最大显示四位数,Y轴最大显示三位数,再加上“X”和“Y”两个字母,整个显示区域需要显示九个英文字符所以总宽度为169等于144个像素点宽度),第一个字符的显示位置就是从起始点开始算起,X轴方向大于起始点小于起始点加16,Y轴方向就是大于起始点,小于起始点加32;依次类推第二个字符的位置就是X轴方向大于起始点加16小于起始点加32,Y轴方向还是大于起始点,小于起始点加32。这样就将九个字符的位置确定下来了,那么每个位置具体显示什么数字呢?看代码第103行,我们要显示第一个数字也就是X轴坐标的最高位,它被存放在寄存器data6中,把它带入字模数组即char [data6], data6的值是几就定位到字模对应的数字为几,例如data6的值为“1”就定位到数组char [1],对应的数字就是“1”的字模。找到这个字模后就要把它显示到LCD屏幕上去,还拿data6的值为“1”举例,“1”的字模其实就是1632等于512个像素点,每个像素点都有自己的值,要么“1”,要么“0”。我们需要做的就是把第一个字符显示区域的512个像素点代入字模的512个像素点中去,如果字模的像素点值为“1”那么对应的字符显示区域像素点赋值颜色数值为黑色,反之给白色数值。举个例子现在要显示第一个字符的第一个像素点,对应LCD屏幕上的像素点就是起始点,对应字模数组的值就是char [data6] [511],同理第二个像素点数据对应的就是char [data6] [510],依次类推最后一个像素点数据就是char [data6] [0]。因此代码第103行执行的功能就是把pixel_xpos与pixel_ypos对应的LCD实际显示像素点转换成对应的字模数组char中的元素,顺便提一句Y轴的值每增减1则像素点增减16,所以代码中要“*16”。

清楚了第一个字符的显示原理后,其他字符的显示原理是一模一样的就不再赘述了,到此整个LCD触摸实验代码就分析完了,接下来就可以生成bit流文件下板验证了。

20.5 下载验证

首先将下载器与领航者底板上的JTAG接口连接,下载器另外一端与电脑连接,然后将LCD屏连接到开发板上,最后连接电源线并打开电源开关。

然后将本次实验生成的bit流文件下载到开发板中,此时可以看到LCD屏幕点亮并显示“0000X000Y”,我们用手触摸LCD屏,触摸点的数据就会显示出来了。

实验结果如下图所示:

图 7.5.13.1 触摸显示

【正点原子FPGA连载】 第二十章 LCD触摸屏实验 -摘自【正点原子】领航者ZYNQ之FPGA开发指南_V2.0

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