【工程源码】基于FPGA的XPT2046触摸控制器设计

本文和设计代码由FPGA爱好者小梅哥编写,未经作者许可,本文仅允许网络论坛复制转载,且转载时请标明原作者。


XPT2046是一款设计用于移动电话、个人数字助理、便携式一起、付款中断设备、触摸屏显示器等设备的4线制电阻触摸屏控制器。该芯片实质为一个多通道ADC+电压输出芯片,通过在不同时刻对电阻触摸屏的两组不同电极上分别施加电压,然后测量另一组电极上的电压值,从而获取触摸点的X或Y位置坐标,进而提供给处理器进行处理。
电阻触摸屏简介
四线电阻式触摸屏,主要由两层镀有ITO镀层的薄膜组成。其中一层在屏幕的左右边缘各有一条垂直总线,另一层在屏幕的底部和顶部各有一条水平总线,如果在一层薄膜的两条总线上施加电压,在ITO镀层上就会形成均匀电场。当使用者触击触摸屏时,触击点处两层薄膜就会接触,在另一层薄膜上就可以测量到接触点的电压值。
1【工程源码】基于FPGA的XPT2046触摸控制器设计_第1张图片


为了在X轴方向进行测量,将左侧总线偏置为0V,右侧总线偏置为VCC。将顶部或底部总线连接到ADC,当顶层和底层相接触时即可作一次测量。
为了在Y轴方向进行测量,将顶部总线偏置为VCC,底部总线偏置为0V。将ADC输入端接左侧总线或右侧总线,当顶层与底层相接触时即可对电压进行测量。
如下图,测量出来的电压值与接触点的位置线性相关,即可以由VPX和VPY分别计算出接触点P的X和Y坐标。
在实际测量中,控制电路会交替在X和Y电极组上施加VCC电压,进行电压测量和计算接触点的坐标。举例说明测量流程:
第一步,在X+上施加VCC,X-上施加0V电压,测量Y+(或Y-)电极上的电压值VPX,计算出接触点P的X坐标;
第二步,在Y+上施加VCC,Y-上施加0V电压,测量X+(或X-)电极上的电压值VPY,计算出接触点P的Y坐标;
以上两步组成一个测量周期,可以得到一组(X,Y)坐标。
2

【工程源码】基于FPGA的XPT2046触摸控制器设计_第2张图片

图2.1:触摸屏工作原理示意图
电阻触摸屏控制器XPT2046
通过以上介绍,可知要实现对某个触摸点的坐标测量,需要对电阻触摸屏模组的两层导电薄膜分时施加电压,在对其中一个导电薄膜的电极施加电压时,使用ADC去测量另一层导电薄膜的电极上的电压。由此可知,触摸控制器必须能够支持两个功能:
触摸控制器能够对连接的电极施加电压
触摸控制器能够测量电极上的电压(ADC)
即触摸控制器不仅仅是简单的ADC,因为其还要能够给电极提供电压,所以我们无法使用通用的ADC来完成4线电阻触摸屏的控制。为了实现对电阻触摸屏的控制,以TI为代表的众多厂商推出了专用的触摸控制器,如TI的TSC2046、ADS7843,两者功能相同,封装兼容,可以直接替换。同时,国内也有厂商推出了能够完全兼容的器件,最典型的如深圳矽普特公司推出的XPT2046,该芯片可完全兼容TI的TSC2046器件。本教程主要以该芯片为依据进行讲解。
XPT2046特性:
工作电压范围为 2.2V~5.25V
支持 1.5V~5.25V 的数字 I/O 口
内建 2.5V 参考电压源
电源电压测量( 0V~6)
内建结温测量功能
触摸压力测量
采用 SPI 3 线控制通信接口
具有自动 power-down 功能
封装: QFN-16、 TSSOP-16 和 VFBGA-48
与 TSC2046、 AK4182A 完全兼容
XPT2046 在 125KHz 转换速率和 2.7V 电压下的功耗仅为750 µW。 XPT2046 以其低功耗和高速率等特性,被广泛应用在采用电池供电的小型手持设备上,比如 PDA、手机等。
下图为XPT2046的功能框图,可见XPT2046内部包含了一个多路选择器,能够测量电池电压、AUX电压、芯片温度。一个12位的ADC用于对选择的模拟输入通道进行模数转换,得到数字量,然后送入控制逻辑电路,供主控CPU进行读取,同时,具体选择哪个通道进行转换,也是由主控CPU发送命令给控制逻辑来设置的。
3

【工程源码】基于FPGA的XPT2046触摸控制器设计_第3张图片

XPT支持笔触中断,即当触摸屏检测到触摸被按下时,可以立即产生笔触中断,通知主控制器可以控制开始转换并读取数据。在转换过程中,通过busy信号指示当前忙状态,以避免主控发出新的命令中断之前的命令。
XPT2046引脚
XPT2046通过SPI接口与主控制器进行通信,其与主控制器的接口包括以下信号:
PENIRQ_N:笔触中断信号,当设置了笔触中断信号有效时,每当触摸屏被按下,该引脚被拉为低电平。当主控检测到该信号后,可以通过发控制信号来禁止笔触中断,从而避免在转换过程中误触发控制器中断。该引脚内部连接了一个50K的上拉电阻。
CS_N:芯片选中信号,当CS_N被拉低时,用来控制转换时序并使能串行输入/输出寄存器以移出或移入数据。当该引脚为高电平时,芯片(ADC)进入掉电模式。
DCLK:外部时钟输入,该时钟用来驱动SAR ADC的转换进程并驱动数字IO上的串行数据传输。
DIN:芯片的数据串行输入脚,当CS为低电平时,数据在串行时钟DCLK的上升沿被锁存到片上的寄存器。
DOUT:串行数据输出,在串行时钟DCLK的下降沿数据从此引脚上移出,当CS_N引脚为高电平时,该引脚为高阻态。
BUSY:忙输出信号,当芯片接收完命令并开始转换时,该引脚产生一个DCLK周期的高电平。当该引脚由高点平变为低电平的时刻,转换结果的最高位数据呈现在DOUT引脚上,主控可以读取DOUT的值。当CS_N引脚为高电平时,BUSY引脚为高阻态。
XPT2046工作原理
XPT2046 是一种典型的逐次逼近型模数转换器( SAR ADC),包含了采样/保持、模数转换、串口数据输出等功能。同时芯片集成有一个 2.5V的内部参考电压源、温度检测电路,工作时使用外部时钟。 XPT2046 可以单电源供电,电源电压范围为 2.7V~5.5V。参考电压值直接决定ADC的输入范围,参考电压可以使用内部参考电压,也可以从外部直接输入1V~VCC范围内的参考电压(要求外部参考电压源输出阻抗低)。 X、 Y、 Z、 VBAT、 Temp和AUX模拟信号经过片内的控制寄存器选择后进入ADC, ADC可以配置为单端或差分模式。选择VBAT、 Temp和AUX时可以配置为单端模式;作为触摸屏应用时,可以配置为差分模式,这可有效消除由于驱动开关的寄生电阻及外部的干扰带来的测量误差,提高转换准确度。
下图为XPT2046的典型工作电路:
4

【工程源码】基于FPGA的XPT2046触摸控制器设计_第4张图片

XPT2046有四个引脚,用于连接到四线制电阻屏的FPC上,分别为XP、XN、YP、YN,连接到对应的四线制电阻屏的X电极的正端、负端和Y电极的正端、负端。此四个引脚每个都能工作于两种状态,分别为电源/GND输出、ADC输入。例如设置ADC工作在差分模式,当测量X方向的坐标时,XP输出VCC、XN连接到GND,此时,YP和YN作为ADC的差分输入脚连接到ADC上,通过测量YP和YN之间的电压差来得到当前触摸点的X位置。同理,当测量Y方向的坐标时,YP输出VCC、YN连接到GND、此时,XN和XP作为ADC的差分输入脚连接到ADC上,通过测量YP和YN之间的电压差来得到当前触摸点的Y位置。
XPT2046控制驱动方案
了解了XPT2046的接口电路,接下来我们就可以通过主控MCU或FPGA来控制该芯片实现坐标的读取了。要想正确的读到X、Y坐标,需要按照芯片规定的控制协议进行数据的读写。XPT2046实现一次X、Y坐标的读取需要完成两次转换,单一一次转换只能得到单一X或Y的坐标,因此,我们必须通过两次控制才能到到结果。至于每一次转换的对象为X或Y坐标,由控制器发出的控制字决定。ADC在转换时能够被配置为单端或差分模式,具体的控制字在每次传输开始的时候,由主控MCU驱动DIN信号传输。下图为XPT2046典型的24时钟周期转换控制时序:
5

【工程源码】基于FPGA的XPT2046触摸控制器设计_第5张图片

XPT2046 数据接口是串行接口,其典型工作时序如上图所示,图中展示的信号来自带有基本串行接口的单片机或数据信号处理器。处理器和转换器之间的的通信需要 8 个时钟周期,可采用 SPI、 SSI 和 Microwire 等同步串行接口。一次完整的转换需要 24 个串行同步时钟( DCLK)来完成。
前 8 个时钟用来通过DIN引脚输入控制字节。当转换器获取有关下一次转换的足够信息后,接着根据获得的信息设置输入多路选择器和参考源输入,并进入采样模式,如果需要,将启动触摸面板驱动器。 3 个多时钟周期后,控制字节设置完成,转换器进入转换状态。这时,输入采样-保持器进入保持状态,触摸面板驱动器停止工作(单端工作模式)。接着的12 个时钟周期将完成真正的模数转换。如果是度量比率转换方式( SER/DFR ——=0),驱动器在转换过程中将一直工作,第13 个时钟将输出转换结果的最后一位。剩下的 3 个多时钟周期将用来完成被转换器忽略的最后字节( DOUT置低)
控制字节由 DIN 输入的控制字如下表所示,它用来启动转换,寻址,设置 ADC 分辨率,配置和对 XPT2046 进行掉电控制。
起始位:第一位,即 S 位。控制字的首位必须是 1,即 S=1。在 XPT2046 的 DIN 引脚检测到起始位前,所有的输入将被忽略。
地址:接下来的 3 位( A2、 A1 和 A0)选择多路选择器的现行通道(见表 1、表 2),触摸屏驱动和参考源输入。
MODE:模式选择位,用于设置 ADC 的分辨率。 MODE=0,下一次的转换将是 12 位模式; MODE=1,下一次的转换将是 8 位模式。
SER/DFR:SER/ DFR位控制参考源模式,选择单端模式( SER/DFR=1),或者差分模式( SER/DFR=0)。在X坐标、 Y坐标和触摸压力测量中,为达到最佳性能,首选差分工作模式。参考电压来自开关驱动器的电压。在单端模式下,转换器的参考电压固定为VREF相对于GND引脚的电压(更详细的说明,见表 1 和表 2)。
PD0 和 PD1:表 5 展示了掉电和内部参考电压配置的关系。 ADC 的内部参考电压可以单独关闭或者打开,但是,在转换前,需要额外的时间让内部参考电压稳定到最终稳定值;如果内部参考源处于掉电状态,还要确保有足够的唤醒时间。 ADC 要求是即时使用,无唤醒时间的。另外还得注意,当 BUSY 是高电平的时候,内部参考源禁止进入掉电模式。XPT2046 的通道改变后,如果要关闭参考源,则要重新对 XPT2046 写入命令。
表1 单端模式下的地址与通道对应关系
6

【工程源码】基于FPGA的XPT2046触摸控制器设计_第6张图片

表2 差分模式下的地址与通道对应关系
7

【工程源码】基于FPGA的XPT2046触摸控制器设计_第7张图片

表3 控制字段的每一位功能
8

 

表4 控制字段每一位功能的具体说明
9【工程源码】基于FPGA的XPT2046触摸控制器设计_第8张图片

 

表5 PD位功能说明
10【工程源码】基于FPGA的XPT2046触摸控制器设计_第9张图片

 

上述通过24时钟周期的转换时序讲解了单次转换的时序,在实际应用中,为了提高转换效率,XTP2046提供了16时钟转换模式和15时钟转换模式。
16 时钟周期转换 
第 n+1 次转换的控制位可以与第 n 次转换部分重叠,所以可以用 16 个时钟周期完成一次转换,如图 16 所示。图 16 也说明了处理器和转换器之间的串行通信是可以双向独立进行的。此时,每次转换必须在开始后(接收到 start)的 1.6mS 内完成,否则输入采样保持电路取样的信号会逐渐被放电衰减,影响转换结果。另外,在转换过程中另一串行通信的存在会使 XPT2046 工作于全功耗状态下。
11

【工程源码】基于FPGA的XPT2046触摸控制器设计_第10张图片

8 位总线接口,无 DCLK 时钟延迟 16 时钟周期转换时序
该模式下,DCLK的时钟高电平和低电平均要求最小值为200ns,即DCLK的时钟周期为2.5MHz。
15 时钟周期转换 
下图给出了 XPT2046 的最快时序。这种方法不支持大部分的微控制器和数字信号处理器的串行接口,因为它们一般都不提供 15 周期的串行传输方式。但是,这种方法适用于 FPGA 和 ASIC。需要注意的是,这样有效地提高了转换器的最大转换速率。
 12

【工程源码】基于FPGA的XPT2046触摸控制器设计_第11张图片


最快转换速率, 15 时钟周期转换
在不影响输出精度的前提下提高数据吞吐量, XPT2046 可以采用 8 位的转换模式。切换到 8 位转换模式,完成提前 4 个时钟完成一次转换。不仅每次转换缩短了都 4 位(数据吞吐量提高了 25%),而且由于精度的降低,可以工作在更快的转换速率下,时钟速度可以提高 50%,时钟速度的提高和转换周期的减少,共同可以使转换速率提高 2 倍。
XPT2046驱动设计
通过上述介绍,我们了解了电阻触摸屏的工作原理以及常用的触摸控制器XPT2046的特性以及时序接口,接下来,我们将针对XPT2046的接口特性以及FPGA的工作特点,使用Verilog设计一个能够控制XPT2046完成坐标转换并最终得到触摸坐标点的逻辑驱动电路。
设计的控制器希望能够具有以下特性:
1. 12位转换精度
2. 高转换效率
3. 使用笔触中断
4. 使用差分模式测量坐标
另外,根据工程实践应用经验,触摸屏的按压过程中会存在抖动,因此,我们需要对转换结果进行抖动滤波处理,滤波最简单的算法就是多次采用,去掉最大值和最小值后计算平均值。即我们需要连续多次采样,然后对采样的结果进行处理后,再作为最终的XY坐标,送给下一级使用。
上述需求主要与控制字段有关。
条件1,要得到12位采样精度,根据表4,控制字段的第3位MODE位应该为0。
条件2,要实现高转换效率,由于我们使用FPGA进行控制,因此可以使用专用的15时钟周期转换时序。
条件3,使用笔触模式,则在转换过程中设置PD始终为00。
条件4,设置为差分模式,则设置SER/DFR位为0。
当实际进行转换时,根据表2,测量X坐标时,设置A2—A0为101。测量Y坐标时,设置A2—A0为001。
为了实现多次测量求均值,可以选择每检测到一次笔触中断,分别进行18次X坐标和Y坐标转换,然后分别找出其中的最大值和最小值并去除,再将余下的16次结果除以16(右移4位),即可获得当前位置的坐标滤波后的值。
13【工程源码】基于FPGA的XPT2046触摸控制器设计_第12张图片

 

根据XPT2046提供的时序接口可知,要实现该控制接口,最方便的方式就是使用线性序列机。因为在每一个DCLK的上升沿或者下降沿需要发出或者读取什么数据是完全已知的,符合线性序列机的设计特点。
设计实现:本实例代码中均做了详细注释,本例中不再逐行讲解,请大家在学习时候,将看不明白的地方指出来,方便我们针对性进行补充讲解。
XPT2046驱动代码

module xpt2046(
    Clk50m,
    Rst_n,
    EN,
    X_Value,
    Y_Value,
    Get_Flag,
    
    PenIrq_n,
    DCLK,
    DIN,
    DOUT,
    CS_N,
    BUSY
);

    input Clk50m;
    input Rst_n;
    input EN;
    output reg [11:0]X_Value;
    output reg [11:0]Y_Value;
    
    output reg Get_Flag;
    
    input PenIrq_n;
    input BUSY;
    output reg DCLK;
    output reg DIN;
    output reg CS_N;
    input  DOUT;
    
    wire pen_flag;
    wire pen_state;
    
    reg [4:0]DIV_CNT;//得到DCLK时钟两倍的采样时钟以产生DCLK
    reg [5:0]CLK_GEN_CNT;//产生DCLK时钟计数器
    reg [5:0]CONV_CNT;//记录完成了多少次转换
    
    reg [19:0]PEN_CNT;
    
    reg DCLK2X;
    reg CONV_DONE;
    reg [11:0]Dtmp;
    reg EN_CONV;
    
    reg [16:0]tmp_X_Value,tmp_Y_Value;
    reg [11:0]X_MAX,X_MIN,Y_MAX,Y_MIN;
    reg r_Get_Flag;
    
    localparam S = 1'b1;    //起始位
    localparam MODE = 1'b0; //采样精度
    localparam SER_DFR = 1'b0; //单端/差分采样模式
    localparam PD = 2'b00;  //功耗控制
    parameter CONV_TIMES = 36;  //每多少次转换计算一次均值
    parameter FILTER_PARAM = 4; //除以16 == 右移4位
    
    parameter CNT_TOP = 20'd499999; //对PEN引脚信号滤波延时
    
    wire [2:0]ADDR; //采样通道控制
    
    assign ADDR = (CONV_CNT[0])?3'b101:3'b001;//CONV_CNT值为偶数,选择测量X通道
    
    wire cnt_full;//PEN引脚信号滤波计数器计数满标志
    
    //PEN引脚延时滤波计数器
    always@(posedge Clk50m or negedge Rst_n)
    if(!Rst_n)
        PEN_CNT <= 20'd0;
    else if(!PenIrq_n)begin //笔触为低电平
        if(cnt_full)    //计满归零
            PEN_CNT <= 20'd0;
        else    //未计满累加
            PEN_CNT <= PEN_CNT + 1'b1;
    end else    //笔触为高电平,禁止计数
        PEN_CNT <= 20'd0;
        
    assign cnt_full = (PEN_CNT == CNT_TOP);
    
    assign pen_state = cnt_full;//在PenIrq_n引脚为低电平的时候,每计数满产生一次pen_state信号,触发一36次采样

    //2倍DCLK采样时钟分频计数器   
    always@(posedge Clk50m or negedge Rst_n)
    if(!Rst_n)
        DIV_CNT <= 5'd0;
    else if(EN_CONV)begin
        if(DIV_CNT == 5'd24)
            DIV_CNT <= 5'd0;
        else 
            DIV_CNT <= DIV_CNT + 1'b1;
    end
    else
        DIV_CNT <= 5'd0;
    
    //产生2倍DCLK使能时钟
    always@(posedge Clk50m or negedge Rst_n)
    if(!Rst_n)
        DCLK2X <= 1'b0;
    else if(DIV_CNT == 5'd24)
        DCLK2X <= 1'b1;
    else
        DCLK2X <= 1'b0;

    //对2倍DCLK采样时钟进行技术,以产生序列机基本序列
    always@(posedge Clk50m or negedge Rst_n)
    if(!Rst_n)
        CLK_GEN_CNT <= 6'b0;
    else if(EN_CONV)begin
        if(DCLK2X)begin
            if(CLK_GEN_CNT == 6'd45)//计数到46以后,回到16开始重新计数
                CLK_GEN_CNT <= 6'd16;
            else
                CLK_GEN_CNT <= CLK_GEN_CNT + 1'b1;
        end
    end
    else
        CLK_GEN_CNT <= 6'b0;

    //根据CLK_GEN_CNT值控制序列,发送控制字并读取采样结果
    always@(posedge Clk50m or negedge Rst_n)
    if(!Rst_n)begin
        DIN <= 1'b1;
        Dtmp <= 12'd0;
        DCLK <= 1'd0;
        CONV_CNT <= 6'd0;
    end     
    else if(EN_CONV)begin
        if(DCLK2X)begin
            case(CLK_GEN_CNT)
                0:begin DIN <= S; DCLK <= 1'b0; end //发送首次转换起始位
                1:begin DCLK <= 1'b1; end
                
                2:begin DIN <= ADDR[2]; DCLK <= 1'b0; end   //发送A2
                3:begin DCLK <= 1'b1; end
                
                4:begin DIN <= ADDR[1]; DCLK <= 1'b0; end//发送A1
                5:begin DCLK <= 1'b1; end
                
                6:begin DIN <= ADDR[0]; DCLK <= 1'b0; end//发送A0
                7:begin DCLK <= 1'b1; end
                
                8:begin DIN <= MODE; DCLK <= 1'b0; end//发送采样精度设置位
                9:begin DCLK <= 1'b1; end
                
                10:begin DIN <= SER_DFR; DCLK <= 1'b0; end//发送ADC输入模式位
                11:begin DCLK <= 1'b1;end
                
                12:begin DIN <= PD[1]; DCLK <= 1'b0; end//发送功耗控制位PD1
                13:begin DCLK <= 1'b1; end
                
                14:begin DIN <= PD[0]; DCLK <= 1'b0; end//发送功耗控制位PD0
                15:begin DCLK <= 1'b1; end
                
                16:begin DIN <= 0; DCLK <= 1'b0; end//等待采样保持电路工作
                17:begin DCLK <= 1'b1; end
                
                18:begin DIN <= 0; DCLK <= 1'b0; end
                19:begin Dtmp[11] <= DOUT; DCLK <= 1'b1; end//读取第11位转换结果
                
                20:begin DIN <= 0; DCLK <= 1'b0; end
                21:begin Dtmp[10] <= DOUT; DCLK <= 1'b1; end//读取第10位转换结果
                
                22:begin DIN <= 0; DCLK <= 1'b0; end
                23:begin Dtmp[9] <= DOUT; DCLK <= 1'b1; end//读取第9位转换结果
                
                24:begin DIN <= 0; DCLK <= 1'b0; end
                25:begin Dtmp[8] <= DOUT;DCLK <= 1'b1; end//读取第8位转换结果
                
                26:begin DIN <= 0; DCLK <= 1'b0; end
                27:begin Dtmp[7] <= DOUT; DCLK <= 1'b1; end//读取第7位转换结果
                
                28:begin DIN <= 0; DCLK <= 1'b0; end
                29:begin Dtmp[6] <= DOUT; DCLK <= 1'b1; end//读取第6位转换结果
                
                30:begin DIN <= S; DCLK <= 1'b0; end    //发送下次转换的控制字起始位
                31:begin Dtmp[5] <= DOUT; DCLK <= 1'b1; end//读取第5位转换结果
                
                32:begin DIN <= ADDR[2]; DCLK <= 1'b0; end//发送下次转换的A2
                33:begin Dtmp[4] <= DOUT; DCLK <= 1'b1; end
                
                34:begin DIN <= ADDR[1]; DCLK <= 1'b0; end//发送下次转换的A1
                35:begin Dtmp[3] <= DOUT; DCLK <= 1'b1; end
                
                36:begin DIN <= ADDR[0]; DCLK <= 1'b0; end//发送下次转换的A0
                37:begin Dtmp[2] <= DOUT; DCLK <= 1'b1; end
                
                38:begin DIN <= MODE; DCLK <= 1'b0; end//发送下次转换的采样精度设置位
                39:begin Dtmp[1] <= DOUT; DCLK <= 1'b1; end
                
                40:begin DIN <= SER_DFR; DCLK <= 1'b0; end//发送下次采样ADC输入模式位
                41:begin Dtmp[0] <= DOUT; DCLK <= 1'b1; CONV_CNT <= CONV_CNT + 1'b1; end
        
                42:begin DIN <= PD[1]; DCLK <= 1'b0; end//发送功耗控制位PD1
                43:begin DCLK <= 1'b1; end
                
                44:begin DIN <= PD[0]; DCLK <= 1'b0; end//发送功耗控制位PD0
                45:begin DCLK <= 1'b1; CONV_DONE <= 1'b1; end   
            endcase
        end else
            CONV_DONE <= 1'b0;
    end else if(!EN_CONV)begin
        CONV_CNT <= 0;
        CONV_DONE <= 1'b0;
    end
    
    //将36次采样中18次的X通道的采样结果累加
    always@(posedge Clk50m or negedge Rst_n)
    if(!Rst_n)
        tmp_X_Value <= 17'd0;
    else if(EN_CONV == 1'b0)
        tmp_X_Value <= 17'd0;
    else if(CONV_DONE && CONV_CNT[0])//转换完成,转换计数为奇数,将转换结果累加到X临时寄存器
        tmp_X_Value <= tmp_X_Value + Dtmp;

    //记录18次X通道采样的最大值      
    always@(posedge Clk50m or negedge Rst_n)
    if(!Rst_n)
        X_MAX <= 12'd0;
    else if(EN_CONV == 1'b0)
        X_MAX <= 12'd0;
    else if(CONV_DONE && CONV_CNT[0])begin//转换完成,转换计数为奇数,判断当前值是否大于已存最大值
        if(Dtmp > X_MAX)
            X_MAX <= Dtmp;
        else
            X_MAX <= X_MAX;
    end
    
    //记录18次X通道采样的最小值      
    always@(posedge Clk50m or negedge Rst_n)
    if(!Rst_n)
        X_MIN <= 12'd0;
    else if(EN_CONV == 1'b0)
        X_MIN <= 12'd4095;
    else if(CONV_DONE && CONV_CNT[0])begin//转换完成,转换计数为奇数,判断当前值是否小于已存最小值
        if(Dtmp < X_MIN)
            X_MIN <= Dtmp;
        else
            X_MIN <= X_MIN;
    end
    
    //将36次采样中18次的Y通道的采样结果累加
    always@(posedge Clk50m or negedge Rst_n)
    if(!Rst_n)
        tmp_Y_Value <= 17'd0;
    else if(EN_CONV == 1'b0)
        tmp_Y_Value <= 17'd0;
    else if(CONV_DONE && (!CONV_CNT[0]))//转换完成,转换计数为偶数,将转换结果累加到Y临时寄存器
        tmp_Y_Value <= tmp_Y_Value + Dtmp;
    
    //记录18次Y通道采样的最大值  
    always@(posedge Clk50m or negedge Rst_n)
    if(!Rst_n)
        Y_MAX <= 12'd0;
    else if(EN_CONV == 1'b0)
        Y_MAX <= 12'd0;
    else if(CONV_DONE && (~CONV_CNT[0]))begin//转换完成,转换计数为奇数,判断当前值是否大于已存最大值
        if(Dtmp > Y_MAX)
            Y_MAX <= Dtmp;
        else
            Y_MAX <= Y_MAX;
    end
    
    //记录18次Y通道采样的最小值
    always@(posedge Clk50m or negedge Rst_n)
    if(!Rst_n)
        Y_MIN <= 12'd0;
    else if(EN_CONV == 1'b0)
        Y_MIN <= 12'd4095;
    else if(CONV_DONE && (~CONV_CNT[0]))begin//转换完成,转换计数为奇数,判断当前值是否小于已存最小值
        if(Dtmp < Y_MIN)
            Y_MIN <= Dtmp;
        else
            Y_MIN <= Y_MIN;
    end
    
    //使能一个36次转换
    always@(posedge Clk50m or negedge Rst_n)
    if(!Rst_n)
        EN_CONV <= 1'b0;
    else if(EN)begin
        if(pen_state)
            EN_CONV <= 1'b1;
        else if((CONV_CNT == CONV_TIMES) && CLK_GEN_CNT == 29)//转换完成,对齐15周期时序
            EN_CONV <= 1'b0;
        else
            EN_CONV <= EN_CONV;
    end
    else
        EN_CONV <= 1'b0;

    //
    always@(posedge Clk50m or negedge Rst_n)
    if(!Rst_n)
        r_Get_Flag <= 1'b0;
    else if((CONV_CNT == CONV_TIMES) && CONV_DONE)
            r_Get_Flag <= 1'b1;
    else
        r_Get_Flag <= 1'b0;
        
    always@(posedge Clk50m)
        Get_Flag <= r_Get_Flag;
    
    always@(posedge Clk50m)
        CS_N <= ~EN_CONV;
        
    reg [11:0]r_X_Value,r_Y_Value;
    
    //计算当前X均值,X均值 = (18次累加值 - 最大值 - 最小值)/ 16
    always@(posedge Clk50m or negedge Rst_n)
    if(!Rst_n)
        r_X_Value <= 12'd0;
    else if(r_Get_Flag)
        r_X_Value <= (tmp_X_Value - X_MAX - X_MIN) >> FILTER_PARAM;
    else
        r_X_Value <= r_X_Value;
    
    //计算当前Y均值,Y均值 = (18次累加值 - 最大值 - 最小值)/ 16  
    always@(posedge Clk50m or negedge Rst_n)
    if(!Rst_n)
        r_Y_Value <= 12'd0;
    else if(r_Get_Flag)
        r_Y_Value <= (tmp_Y_Value - Y_MAX - Y_MIN) >> FILTER_PARAM;
    else
        r_Y_Value <= r_Y_Value;

    //存储上一次X结果作为输出,为了滤除最后一次转换结果,因为最后一次转换结果存在按压释放时刻,结果不太稳定
    always@(posedge Clk50m or negedge Rst_n)
    if(!Rst_n)
        X_Value <= 12'd0;
    else if(r_Get_Flag)
        X_Value <= r_X_Value;

    //存储上一次Y结果作为输出,为了滤除最后一次转换结果,因为最后一次转换结果存在按压释放时刻,结果不太稳定       
    always@(posedge Clk50m or negedge Rst_n)
    if(!Rst_n)
        Y_Value <= 12'd0;
    else if(r_Get_Flag)
        Y_Value <= r_Y_Value;

endmodule

 


XPT2046设计验证

`timescale 1ns/1ns

module xpt2046_tb;

    reg Clk50m;
    reg Rst_n;
    reg EN;
    wire [11:0]X_Value;
    wire [11:0]Y_Value;
    
    wire Get_Flag;
    
    reg PenIrq_n;
    reg BUSY;
    wire DCLK;
    wire DIN;
    wire CS_N;
    reg  DOUT;

    initial Clk50m = 1;
    always #10 Clk50m = ~Clk50m;
    
    initial begin
        Rst_n = 0;
        PenIrq_n = 1;
        EN = 0;
        #201;
        Rst_n = 1;
        EN = 1;
        #300;
        PenIrq_n = 0;
        #5000000;
        PenIrq_n = 1;
        #5000000;
        $stop;
        
    end
    
//  initial begin
//      DOUT = 1'b0;
//      forever begin
//          DOUT = ~DOUT;
//          #30154;
//      end
//  end

    initial DOUT = 1;
    
    xpt2046 xpt2046(
        .Clk50m(Clk50m),
        .Rst_n(Rst_n),
        .EN(EN),
        .X_Value(X_Value),
        .Y_Value(Y_Value),
        .Get_Flag(Get_Flag),
        
        .PenIrq_n(PenIrq_n),
        .DCLK(DCLK),
        .DIN(DIN),
        .DOUT(DOUT),
        .CS_N(CS_N),
        .BUSY(BUSY)
    );

endmodule


以下为简单的XPT2046测试脚本,该测试脚本没有产生复杂的激励,仅仅产生了时钟和笔触中断,用以观察对应的控制信号在整个转换过程中是否正常工作。

 

XPT2046在开发板上验证

【工程源码】基于FPGA的XPT2046触摸控制器设计_第13张图片

【工程源码】基于FPGA的XPT2046触摸控制器设计_第14张图片

【工程源码】基于FPGA的XPT2046触摸控制器设计_第15张图片

【工程源码】基于FPGA的XPT2046触摸控制器设计_第16张图片

【工程源码】基于FPGA的XPT2046触摸控制器设计_第17张图片

你可能感兴趣的:(【工程源码】基于FPGA的XPT2046触摸控制器设计)