基於FPGA的XPT2046觸摸控制器設計
小梅哥編寫,未經許可,文章內容和所涉及代碼不得用於其他商業銷售的板卡
本實例所涉及代碼均可通過向 xiaomeige_fpga@foxmail.com 發送郵件獲取。
XPT2046是一款設計用於移動電話、個人數字助理、便攜式一起、付款中斷設備、觸摸屏顯示器等設備的4線制電阻觸摸屏控制器。該芯片實質為一個多通道ADC+電壓輸出芯片,通過在不同時刻對電阻觸摸屏的兩組不同電極上分別施加電壓,然后測量另一組電極上的電壓值,從而獲取觸摸點的X或Y位置坐標,進而提供給處理器進行處理。
電阻觸摸屏簡介
四線電阻式觸摸屏,主要由兩層鍍有ITO鍍層的薄膜組成。其中一層在屏幕的左右邊緣各有一條垂直總線,另一層在屏幕的底部和頂部各有一條水平總線,如果在一層薄膜的兩條總線上施加電壓,在ITO鍍層上就會形成均勻電場。當使用者觸擊觸摸屏時,觸擊點處兩層薄膜就會接觸,在另一層薄膜上就可以測量到接觸點的電壓值。
- 為了在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.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發送命令給控制邏輯來設置的。
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的典型工作電路:
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時鍾周期轉換控制時序:
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 單端模式下的地址與通道對應關系
表2 差分模式下的地址與通道對應關系
表3 控制字段的每一位功能
表4 控制字段每一位功能的具體說明
表5 PD位功能說明
上述通過24時鍾周期的轉換時序講解了單次轉換的時序,在實際應用中,為了提高轉換效率,XTP2046提供了16時鍾轉換模式和15時鍾轉換模式。
16 時鍾周期轉換
第 n+1 次轉換的控制位可以與第 n 次轉換部分重疊,所以可以用 16 個時鍾周期完成一次轉換,如圖 16 所示。圖 16 也說明了處理器和轉換器之間的串行通信是可以雙向獨立進行的。此時,每次轉換必須在開始后(接收到 start)的 1.6mS 內完成,否則輸入采樣保持電路取樣的信號會逐漸被放電衰減,影響轉換結果。另外,在轉換過程中另一串行通信的存在會使 XPT2046 工作於全功耗狀態下。
8 位總線接口,無 DCLK 時鍾延遲 16 時鍾周期轉換時序
該模式下,DCLK的時鍾高電平和低電平均要求最小值為200ns,即DCLK的時鍾周期為2.5MHz。
15 時鍾周期轉換
下圖給出了 XPT2046 的最快時序。這種方法不支持大部分的微控制器和數字信號處理器的串行接口,因為它們一般都不提供 15 周期的串行傳輸方式。但是,這種方法適用於 FPGA 和 ASIC。需要注意的是,這樣有效地提高了轉換器的最大轉換速率。
最快轉換速率, 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位),即可獲得當前位置的坐標濾波后的值。
根據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在AC620開發板上驗證
本設計驗證實例涉及到AC620開發板上的數碼管和5寸/4.3寸觸摸顯示屏,完整工程代碼請前往AC620開發板套件下載地址下載,或者進入芯航線FPGA技術支持群詢問獲取,或者通過向xiaomeige_fpga@foxmail.com 發送郵件獲取。


















