【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二十八:TFT模块 - 触屏

实验二十八:TFT模块 - 触屏

读者在上一个实验所玩弄过的 TFT LCD模块,除了显示大小为 320 × 240,颜色为16位RGB的图像信息以外,它还支持触屏。所谓触屏就是鼠标还有键盘以外的输入手段,例如现在流行平板还有智能手机,触屏输入对我们来说,已经成为日常的一部分。描述语言一门偏向硬件的语言,面对触屏,它顶多只能做做一些驱动的工作,其余如滤波,还有像素转换等计算,它必须交由高级语言去负责。

面向无能为力的描述语言,笔者不禁联想过去的自己 ... 好痛苦,好难受。话虽如此,天生我才必有用,描述语言虽然完成不了触屏的伟业,不过只要做做驱动,平稳过着日子,它便心满意足了。

clip_image002

图28.1 触屏的硬件部署。

如图28.1所示,那是触屏的硬件部署。根据理论,TFT显示屏上面布满一层敏感的电磁层(触屏层)作为输入或者传感,而且传感的强弱会随着位置不同而不同。如果笔者不小心触碰任何一点,控制器 SSD 1289 会经由差分的 X+/-信号 还有 Y+/- 信号,将传感结果发送出去。在此,TFT显示屏的工作已经结束。

差分是什么?估计失忆的同学会非常好奇。假设笔者不小心触碰位置X20,然后像素X20所对应的模拟信号,例如1.2v,其中正电压 +1.2v会经由X+ 信号发送出去,至于负电压 -1.2v 则会经由 X- 信号后发送出去。差分的目的一般都为了使电压更稳定更准确,读者只要这样理解即可。

事后,差分的传感结果便会传入A/D硬件的耳中,如果输入源是差分,A/D硬件理应长出一对接收差分的耳朵,对此也称为差分A/D硬件。如果读者不知道A/D是什么,读者必须抓去枪毙了 ... A/D全名是 Analogue to Digital Convert,即模拟转为数字。这只命运般的A/D硬件就是鼎鼎大名的 XPT2046 硬件。

硬件XPT2046就是差分的A/D硬件,同时也是本实验的主角。如图28.1所示,它将TFT显示屏哪里发来的传感结果转换为数字信号,然后再经由 FPGA读取。好奇的同学可能会继续好奇什么是物理像素?这个问题说来话长 ... 假设屏幕是一个容器,藏在容器里边的像素就称为屏幕像素,然而围绕容器外边的像素就称为物理像素。

比喻来讲,同是人形生物,住在地球的人形生物就称为地球人,住在地球以外的人形生物就称为外星人。结果而言,地球人并不等价外星人,虽然两者都有相似的外表。所以说,物理像素还有屏幕像素,它们听起来都是像素,不过实际上是不等价的东西。对此,物理像素为了成为屏幕像素,它们必须经过“转换算法”才能成为如假包换的屏幕像素。不过,“转换算法”对描述语言来说,这项工作实在太巨大了。

好了好了,笔者也差不多要进入主题了,废话再说下去,口水就会把电脑淹没了。

clip_image004

图28.2 硬件XTP2046链接FPGA。

图28.2显示硬件XTP2046链接FPGA所需要的连线,由于硬件XTP2046是SPI传输协议的信奉者,所以这些信号多少也有SPI的影子。CS信号也是拉低有效的使能信号,CLK信号是串行时钟,IRQ是拉低有效的沟通信号,DI与DO是读写作用的串行数据信号,BY是BUSY的缩写,亦即表达状态的状态信号。如图28.2所示,因为FPGA控制 CLK信号还有CS信号,所以FPGA是主机,硬件XTP2046则是从机。

clip_image006

图28.3 写时序/写命令(主机视角)。

首先让我们来瞧瞧XTP2046的写时序,如图28.3所示,写时序也称为写命令,因为主机访问从机只有写命令而没有写数据。我们知道SPI都是下降沿设置数据,上升沿锁存数据 ... 对此,写一个字节命令需要用到8下降沿,期间CS必须一直拉低。如图28.3所示,一字节命令当中却有各个不同的位作用,结果如表28.1所示:

表28.1 命令位说明。

位宽

名称

说明

Bit 7

Call

起始位,值1为起始。

Bit 6~4

A2~A0

通道地址。X通道 = 3’b001,Y通道 3’b101。

Bit 3

Mode

12位/8位分辨率选择。值0为12位分辨率,反之亦然。

Bit 2

SER/DFR

输入源选择。值0为差分输入,值1为单端输入。

Bit 1~0

PD1~PD0

功率选择。2’b00为低功率,2’b11为平常。

如表28.1所示,那是命令位的说明。Call为起始,值1表示开始命令,A2~A0为通道地址,常用有X通道与Y通道。Mode为分辨率选择,即转换结果是8位还是12位,常见为12位分辨率。SER/DFR为输入源选择,虽然XTP2046是差分A/D,不过它可以选择输入源。PD1~PD0为功率选择,默认为低功率。

表28.1虽然令人眼花缭乱,不过常见的命令只有以下两个:

读取X通道转化结果,12位分辨率,差分输入,低功率,8’b1_001_0000或者8’h90。

读取Y通道转换结果,12位分辨率,差分输入,低功率,8’b1_101_0000或者8’hD0。

此外,Verilog可以这样描述图28.3,结果如代码28.1所示:

1.        0,1,2,3,4,5,6,7:
2.        begin
3.            rDI <= T1[7-i];
4.                             
5.            if( C1 == 0 ) rCLK <= 1'b0;
6.            else if( C1 == FHALF ) rCLK <= 1'b1;
7.                            
8.            if( C1 == FCLK -1) begin C1 <= 8'd0; i <= i + 1'b1; end
9.            else begin C1 <= C1 + 1'b1; end
10.        end

代码28.1

如代码28.1所示,第1行表示相同的操作有8次,第8~9行表示一个步骤所停留的时间

,其中的FCLK表示一个时钟周期。第5~6行表示,C1为0拉低CLK,C1为半个周期(FHALF)则拉高时钟。第3行则是由高至低逐位赋值。

clip_image008

图28.4 读时序/读数据(主机视角)。

如图28.4所示,那是XTP2046的读时序或者说为读数据。我们知道SPI利用时钟信号的上升沿锁存数据,如果数据的分辨率设为12位(Mode为0值),那么一次性的读数据需要12个上升沿,期间CS必须拉低。根据手册,虽然一次性的读数据有12位,不过仅有当中的高八位有利用价值,亦即 Bit 11~Bit 4,结果低四位被无视。对此,Verilog可以这样表示,结果如代码28.2所示:

1.    0,1,2,3,4,5,6,7,8,9,10,11:
2.    begin 
3.        if( C1 == FHALF ) T2[11-i] <= TP_DO;
4.         
5.        if( C1 == 0 ) rCLK <= 1'b0;
6.        else if( C1 == FHALF ) rCLK <= 1'b1;
7.                            
8.        if( C1 == FCLK -1) begin C1 <= 8'd0; i <= i + 1'b1; end
9.        else begin C1 <= C1 + 1'b1; end
10.    end

代码28.2

如代码28.2所示,第1行表示该操作重复12次,第8~9行表示该步骤停留FCLK周期

。第5~6行表示,C1为0拉低CLK,C1为FHALF则拉高CLK。第3行表示,C1为HALF就从TP_DO哪里暂存结果。

clip_image010

图28.5 一次性写命令与读数据的时序图(主机视角)。

图28.5是一次性写命令与读数据的时序图。操作期间,CS必须持续拉低,首先主机利用下降沿向从机发送一字节的写命令,事后从机会拉高BY一个时钟以示忙状态,期间主机必须给足一个空时钟。从机忙完以后便会拉低BY信号,紧接着便会发送12位宽的转换结果,期间主机必须利用上升沿读取数据。对此,Verilog 可以这样描述,结果如代码28.3所示:

1.     1: // Write byte
2.     begin rCS <= 1'b0; T1 <= Command; i <= FF_Write; Go <= i + 1'b1; end
3.                        
4.     2:  // Wait Busy 
5.     begin
6.         if( C1 == 0 ) rCLK <= 1'b0;
7.          else if( C1 == FHALF ) rCLK <= 1'b1;
8.                            
9.         if( C1 == FCLK -1) begin C1 <= 8'd0; i <= i + 1'b1; end
10.         else begin C1 <= C1 + 1'b1; end
11.     end
12.                        
13.     3: // Read 12 bit
14.     begin i <= FF_Read; Go <= i + 1'b1; end
15.                        
16.     4:
17.     begin D1 <= T2[11:4]; rCS <= 1'b1; i <= i + 1'b1; end

代码28.3

如代码28.3所示,步骤0拉低CS之余,它也赋值写命令,然后i指向写命令的伪函数(代码28.1)。写命令完成以后,主机必须给足一个时钟,对此步骤1发呆一个时钟周期。步骤2将i指向读数据的伪函数(代码28.2),随后在步骤4保存高8位的结果,最后也顺便拉高一下CS信号。

clip_image012

图28.6 IRQ信号。

初期阶段(放手阶段)IRQ呈现拉高状态,如果我们不小心触碰屏幕,XTP2046便会察觉输入源发生变化,然后便会拉低IRQ信号以示触屏事件发生了,结果如图28.6所示。

对此,Verilog 可以这样描述,结果如代码28.4所示:

1.    0:
2.    if( !TP_IRQ ) begin ...; i <= i + 1’b1; end
3.     
4.    1:
5.    ......

代码28.4

准备知识理解完毕以后,我们便可以开始建模了。

clip_image014

图28.7 Touch基础模块的建模图。

图28.7是Touch基础模块,内容包含控制模块还有功能模块。功能模块左边有两位宽的Call/Done,其中[1]为读取物理像素X,[0]为读取物理像素Y。至于右边则是一些顶层信号。控制模块的左边,除了顶层信号IRQ是输入以外,其余信号都呈现输出状态。16位宽的Data对应物理像素X还有物理像素Y,而Done则是沟通作用的触发信号。

整体来说,一旦触碰触屏,IRQ拉低,控制模块利用功能模块读取物理像素X与Y,然后再产生完成信号以示一次性的触屏结果。

touch_funcmod.v

clip_image016

图28.8 Touch功能模块的建模图。

图28.8是Touch功能模块的建模图,具体内容我们还是来看代码吧。

1.    module touch_funcmod
2.    (
3.         input CLOCK,RESET,
4.         output TP_CS_N,
5.         output TP_CLK,
6.         output TP_DI,
7.         input TP_DO,
8.         
9.         input [1:0]iCall,
10.         output oDone,
11.         output [7:0]oData
12.    );

以上内容为相关的出入端声明。

13.         parameter FCLK = 8'd20, FHALF = 8'd10; // 2.5Mhz 
14.         parameter FF_Write = 6'd16, FF_Read = 6'd32;

以上内容为常量声明,内容包括2.5Mhz周期,半周期,写命令还有读数据入口地址。

16.         reg [5:0]i,Go;
17.         reg [11:0]D1;
18.         reg [7:0]C1;
19.         reg rCS,rCLK,rDI;
20.         reg isDone;
21.         
22.         always @ ( posedge CLOCK or negedge RESET )
23.            if( !RESET )
24.                 begin
25.                        { i,Go } <= { 6'd0, 6'd0 };
26.                        D1 <= 12'd0;
27.                        C1 <= 8'd0;
28.                        { rCS,rCLK,rDI } <= 3'b111;
29.                        isDone <= 1'b0;
30.                  end

以上内容为相关的寄存器声明还有复位操作。

31.             else if( iCall )
32.                 case( i )
33.                  
34.                        0: // [1] Read 12bit X, [0],Read 12 bit Y
35.                        if( iCall[1] ) begin D1[7:0] <= 8'h90; i <= i + 1'b1; end
36.                        else if( iCall[0] ) begin D1[7:0] <= 8'hd0; i <= i + 1'b1; end
37.                        
38.                        1: // Write byte
39.                        begin rCS <= 1'b0; i <= FF_Write; Go <= i + 1'b1; end
40.                        
41.                        2:  // Wait Busy 
42.                        begin
43.                             if( C1 == 0 ) rCLK <= 1'b0;
44.                             else if( C1 == FHALF ) rCLK <= 1'b1;
45.                            
46.                            if( C1 == FCLK -1) begin C1 <= 8'd0; i <= i + 1'b1; end
47.                             else begin C1 <= C1 + 1'b1; end
48.                        end
49.                        
50.                        3: // Read 12 bit
51.                        begin i <= FF_Read; Go <= i + 1'b1; end
52.                        
53.                        4:
54.                        begin rCS <= 1'b1; i <= i + 1'b1; end
55.                        
56.                        5:
57.                        begin isDone <= 1'b1; i <= i + 1'b1; end
58.                        
59.                        6:
60.                        begin isDone <= 1'b0; i <= 6'd0; end
61.                     

以上内容为该功能模块的核心操作。步骤0会根据 iCall 为 D1赋值 8’h90(读X),还是 8’hD0(读Y)。步骤1拉低CS之余也写命令,步骤2则是给足一个空时间。步骤3为读数据,步骤4拉低CS,步骤5~6则产生完成信号。

62.                        /********************/
63.                        
64.                        16,17,18,19,20,21,22,23:
65.                        begin
66.                            rDI <= D1[23-i];
67.                             
68.                            if( C1 == 0 ) rCLK <= 1'b0;
69.                             else if( C1 == FHALF ) rCLK <= 1'b1;
70.                            
71.                            if( C1 == FCLK -1) begin C1 <= 8'd0; i <= i + 1'b1; end
72.                             else begin C1 <= C1 + 1'b1; end
73.                        end
74.                        
75.                        24:
76.                        i <= Go;
77.                        

步骤16~23为写一个字节。

78.                        /********************/
79.                        
80.                        32,33,34,35,36,37,38,39,40,41,42,43:
81.                        begin 
82.                            if( C1 == FHALF ) D1[43-i] <= TP_DO;
83.                             
84.                             if( C1 == 0 ) rCLK <= 1'b0;
85.                             else if( C1 == FHALF ) rCLK <= 1'b1;
86.                            
87.                            if( C1 == FCLK -1) begin C1 <= 8'd0; i <= i + 1'b1; end
88.                             else begin C1 <= C1 + 1'b1; end
89.                        end
90.                        
91.                        44:
92.                        i <= Go;
93.                  
94.                  endcase
95.                  

步骤32~92为读12位数据。

96.            assign TP_CS_N = rCS;
97.            assign TP_CLK = rCLK;
98.            assign TP_DI = rDI;
99.            assign oDone = isDone;
100.            assign oData = D1[11:4];
101.    
102.    endmodule

以上内容为输出驱动声明。注意第100行,oData赋值D1的高八位。

touch_ctrlmod.v

clip_image018

图28.9 Touch控制模块的建模图。

图28.9是Touch控制模块的建模图,基本上也没有什么好说的,具体内容让我们来看代码吧。

1.    module touch_ctrlmod
2.    (
3.          input CLOCK,RESET,
4.          input TP_IRQ,
5.          output oDone,
6.          output [15:0]oData,
7.          
8.          output [1:0]oCall,
9.          input iDone,
10.          input [7:0]iData
11.    );

以上内容为相关的出入端声明。

12.         reg [5:0]i;
13.         reg [7:0]D1,D2;
14.         reg [1:0]isCall;
15.         reg isDone;
16.         
17.         always @ ( posedge CLOCK or negedge RESET )
18.             if( !RESET )
19.                  begin
20.                         i <= 6'd0;
21.                         { D1,D2 } <= { 8'd0,8'd0 };
22.                         isCall <= 2'd0;
23.                         isDone <= 1'b0;
24.                    end

以上内容为相关的寄存器声明还有复位操作。

25.              else
26.                  case( i )
27.                    
28.                         0:
29.                         if( !TP_IRQ ) i <= i + 1'b1; 
30.                         
31.                         1: // Read X
32.                         if( iDone ) begin isCall[1] <= 1'b0; D1 <= iData; i <= i + 1'b1; end
33.                         else begin  isCall[1] <= 1'b1; end
34.                         
35.                         2: // Read Y
36.                         if( iDone ) begin isCall[0] <= 1'b0; D2 <= iData; i <= i + 1'b1; end
37.                         else begin  isCall[0] <= 1'b1; end
38.                         
39.                         3:
40.                         begin isDone <= 1'b1; i <= i + 1'b1; end
41.                         
42.                         4:
43.                         begin isDone <= 1'b0; i <= 6'd0; end
44.                    
45.                    endcase

以上内容为该控制模块的核心内容。步骤0用来检测 IRQ信号拉低,步骤1读取物理像素X,步骤2读取物理像素Y。步骤3~4则是用来产生完成信号。

47.         assign oDone = isDone;
48.         assign oData = {D1,D2};
49.         assign oCall = isCall;
50.                    
51.    endmodule

以上内容为相关的输出驱动声明。

touch_basemod.v

有关这个模块的连线部署请参考图28.7。

1.    module touch_basemod
2.    (
3.         input CLOCK,RESET,
4.         output TP_CS_N,
5.         output TP_CLK,
6.         input TP_IRQ,
7.         output TP_DI,
8.         input TP_DO,
9.         
10.         output oDone,
11.         output [15:0]oData
12.    );
13.        wire [1:0]CallU1;
14.    
15.        touch_ctrlmod U1
16.         (
17.              .CLOCK( CLOCK ),
18.              .RESET(  RESET ),
19.              .TP_IRQ( TP_IRQ ),  // < top
20.              .oDone( oDone ),        // > top
21.              .oData( oData ),         // > top
22.              .oCall( CallU1 ),         // > U2
23.              .iDone( DoneU2 ),     // < U1
24.              .iData( DataU2 )      // < U1
25.         );
26.         
27.         wire DoneU2;
28.         wire [7:0]DataU2;
29.         
30.         touch_funcmod U2
31.         (
32.             .CLOCK( CLOCK ),
33.              .RESET( RESET ),
34.              .TP_CS_N( TP_CS_N ),      // > top
35.              .TP_CLK( TP_CLK ),        // > top
36.              .TP_DI( TP_DI ),          // > top
37.              .TP_DO( TP_DO ),          // < top
38.              .iCall( CallU1 ),           // < U1
39.              .oDone( DoneU2 ),        // > U1
40.              .oData( DataU2 )          // > U1
41.         );
42.    
43.    endmodule

以上内容为Touch基础模块的连线部署,读者自己看着办吧。

clip_image020

图28.10 实验二十八的建模图。

图28.10是实验二十八的建模图,首先核心操作会初始化还有清屏TFT基础模块,然后触屏信息会经由Touch基础模块传至核心操作,随后核心操作再将触屏信息作为屏幕像素写入TFT屏当中。此外,笔者也更动基础模块的绘图功能,它会将32位宽的像素X,像素Y,以及颜色信息写入其中。废话少说,让我们看看该绘图功能究竟发生什么改变呢?

tft_ctrlmod.v

6. input [7:0]iData,

首先是tft控制模块多了两个出入端的声明。

29.    else if( iCall[2] )  
30.                  case( i )
31.                    
32.                         0: // X0
33.                         if( iDone ) begin isCall[2] <= 1'b0; i <= i + 1'b1; end
34.                         else begin isCall[2] <= 1'b1; D1 <= 8'h4E; D2 <= { 8'd0, iData[31:24] }; end
35.                         
36.                         1: // Y0
37.                         if( iDone ) begin isCall[2] <= 1'b0; i <= i + 1'b1; end
38.                         else begin isCall[2] <= 1'b1; D1 <= 8'h4F; D2 <= { 8'd0, iData[23:16] }; end
39.                         
40.                         2: // Write data to ram 0x22
41.                         if( iDone ) begin isCall[1] <= 1'b0; i <= i + 1'b1; end
42.                         else begin isCall[1] <= 1'b1; D1 <= 8'h22; end
43.                         
44.                         /**********/
45.                         
46.                         3: // Write color
47.                         if( iDone ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
48.                         else begin isCall[0] <= 1'b1; D2 <= [15:0]; end
49.                        
50.                         /**********/
51.                         
52.                         4:
53.                         begin isDone <= 1'b1; i <= i + 1'b1; end
54.                         
55.                         5:
56.                         begin isDone <= 1'b0; i <= 6'd0; end
57.                         
58.                    endcase

紧接着是受更动的绘图功能。步骤0根据 Data[31:24] 设置X像素,步骤1根据Data[23:16]设置Y像素,然后步骤2锁定数据该写入的地址,最后步骤3再将Data[15:0]的图像信息写进去。步骤4~5则是用来产生完成信号。读者是不是觉得很简单呢?

touch_demo.v
1.    module touch_demo
2.    (
3.         input CLOCK, RESET,
4.         output TFT_RST,
5.         output TFT_RS,     
6.         output TFT_CS_N,  
7.         output TFT_WR_N,
8.         output TFT_RD_N,
9.         output [15:0]TFT_DB,
10.         
11.         output TP_CS_N,
12.         output TP_CLK,
13.         input TP_IRQ,
14.         output TP_DI,
15.         input TP_DO
16.    );

以上内容为相关的出入端声明。

17.         wire DoneU1;
18.         wire [15:0]DataU1;
19.        
20.         touch_basemod U1
21.         (
22.              .CLOCK( CLOCK ),
23.              .RESET( RESET ),
24.              .TP_CS_N( TP_CS_N ),
25.              .TP_CLK( TP_CLK ),
26.              .TP_IRQ( TP_IRQ ),
27.              .TP_DI( TP_DI ),
28.              .TP_DO( TP_DO ),
29.              .oDone( DoneU1 ),
30.              .oData( DataU1 ),
31.         );
32.         

以上内容为Touch基础模块的实例化。

33.         wire DoneU2; 
34.         
35.         tft_basemod U2
36.         (
37.              .CLOCK( CLOCK ),
38.              .RESET( RESET ),
39.              .TFT_RST( TFT_RST ),
40.              .TFT_RS( TFT_RS ),
41.              .TFT_CS_N( TFT_CS_N ),
42.              .TFT_WR_N( TFT_WR_N ),
43.              .TFT_RD_N( TFT_RD_N ),
44.              .TFT_DB( TFT_DB ),
45.              .iCall( isCall ),
46.              .oDone( DoneU2 ),
47.              .iData( {D1,D2,D3} ),
48.         );
49.          

以上内容为TFT基础模块的实例化。

50.         reg [5:0]i,Go;
51.         reg [2:0]isCall;
52.         reg [7:0]D1;
53.         reg [8:0]D2;
54.         reg [15:0]D3;
55.         
56.         always @ ( posedge CLOCK or negedge RESET )
57.             if( !RESET )
58.                  begin
59.                        {i,Go} <= { 6'd0,6'd0 };
60.                         isCall <= 3'd0;
61.                         { D1,D2,D3 } <= { 8'd0,9d0,16'd0 };
62.                    end
63.              else

以上内容为相关的寄存器声明还有复位操作。

64.                  case( i )
65.                    
66.                         0: // Inital TFT
67.                         if( DoneU2 ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
68.                         else begin isCall[0] <= 1'b1; end
69.                         
70.                         1: // Clear Screen
71.                         if( DoneU2 ) begin isCall[1] <= 1'b0; i <= i + 1'b1; end
72.                         else begin isCall[1] <= 1'b1; end
73.                         
74.                         2:
75.                         if( DoneU1 ) begin i <= i + 1'b1; end
76.                         
77.                         3:
78.                         if( DoneU2 ) begin isCall[2] <= 1'b0; i <= i + 1'b1;  end
79.                         else begin isCall[2] <= 1'b1; D1 <= DataU1[15:8]; D2 <= { 1'b0, DataU1[7:0] }; D3 <= 16'd0; end
80.                         
81.                         4:
82.                         i <= 2;
83.    
84.                    endcase
85.       
86.    endmodule

以上内容为核心操作。步骤0初始化TFT显示屏,步骤1则是清屏。步骤2等待Touch基础模块反馈完成,然后继续步骤。步骤3将反馈过来的X与Y以及黑色写入其中。完后,步骤步骤2~3不停来回重复。

综合完毕便下载程序,然后我们便可以用手指在屏幕上涂鸦了,虽然方向还有涂鸦大小有点暴走,那是因为物理像素对屏幕像素是不同性质的东西,结果还是视为实验成功。

细节一:完整的个体模块

本实验的Touch基础模块充其量还是半身熟的鸡蛋,因为当中缺省重要的环节,然而这个环节却是描述语言难以负担的重任。

clip_image022

图28.11 物理像素转换为屏幕像素的概念图。

如图28.11所示,原始的物理像素一般都包含噪声,对此物理像素必须预先经过一层滤波。滤波以后的物理像素才会开始进入转换阶段,转换阶段必须借用转换算法的力量,论道算法,想必有些同学会不经意缩紧眉心,因为我们必须向数学打交道不可。那怕对象是简单乘法或者除法,读过《时序篇》的朋友一定会晓得算法都很麻烦。

物理像素成功转换为屏幕像素以后,它还不能立即纳入使用,因为转换算法有可能存在细节上的缺失。对此,屏幕像素必须经过校正 ... 校正期间不仅有出现算法,我们还要和屏幕发生互动,实在烦死人了。现阶段而言,如果描述语言如果不及顺序语言那么便捷,什么物理像素转换屏幕像素,什么校正 ... 我们最好想也不要想,不然结果只有自讨苦吃。

虽然笔者有可能被指责为胆小鬼,不负责任之类的渣渣,对此笔者不否认,理性而言,那些危及生命的事情,笔者另可背负屈辱也要逃跑,因为没有什么东西比生命更可贵。此刻,只要承认懦弱,我们才会安全成长,未来的事情就让未来的自己去打算吧!少年少女们!

细节二:时序参数

老实说,笔者也觉得懦弱的自己太没出息了 ... 不过,作为补偿,让我们来瞧瞧硬件XTP2046的时序参数吧。

clip_image024

图28.11 XTP2046 的物理时序图。

图28.11是从官方手册哪里拷贝过来的物理时序图,这张图虽然有可能是吓跑小朋友的虎姑婆 ... 不过,我们只要习惯以后,它也可能是弱小的小虎猫。换之,表28.2是各个时序参数的详细信息。

表28.2 时序参数的详细信息。

参数

说明

最大

最小

单位

tDS

DIN在 DCLK上升沿前生效

100

 

ns

tDH

DIN 保持在DCLK高电平后

50

 

ns

tDO

DCLK 下降沿到 DOUT生效

 

200

ns

tDV

CS 下降沿到 DOUT 使能

 

200

ns

tTR

CS 上升沿到 DOUT禁止

 

200

ns

tCSS

CS 下降沿到第一个 DCLK 上升沿

100

 

ns

tCSH

CS 上升沿到 DCLK被忽略

10

 

ns

tCH

DCLK 高电平

200

 

ns

tCL

DCLK低电平

200

 

ns

tBD

DCLK下降沿到 BUSY上升/下降

 

200

ns

tBDV

CS 下降沿到 BUSY 使能

 

200

ns

tBTR

CS 上升沿到BUSY 禁止

 

200

ns

clip_image026

图28.11 DCLK相关的时序参数。

首先是时序第一员的TCH 还有TCL,如图28.11所示,TBD+TCL+ TBD+TCH造就一个时钟周期。根据表28.2所示,由于TBD最小为0ns,所以可以无视(TBD可以视为上山信号还有下上信号)。对此,造就一个时钟周期的成分只有TCH+TCL,而且两者最小皆是200ns,所以最高速率是:

1/(200ns + 200ns) = 2.5Mhz

话虽如此,这仅仅是手册给出的安全速率,如果读者是一名头文字D的死粉,读者随时都可以驾驶藤原豆腐车超频。

clip_image028

图28.12 CS相关的时序参数。

接下来让我们看看 TCSS 还有 TCSH,前者可以视为CS拉低所需的最小时间,后者则是CS拉高所需的最小时间。前者需要100ns,后者则是10ns,如果无法满足它们,CS的有效性就会存在风险。话虽如此,因为手册比较怕死,所以参数略显保险(夸张),我们只要随便应付就好。

clip_image030

图28.13 数据相关的时序参数(主机视角)。

再者就是TDS还有TDH,前者是典型的setup要求,后者则是hold要求,只要两者得到满足,数据打入寄存器才能得到确保。首先,读者必须注意一下图28.13是主机视角的写时序(从机读数据),所以Data是主机发给从机的食物。饿昏的从机会借用上升沿锁存时序,此刻只要 TBD+TCL 大于 TDS,又或者 TBD+TCH大于 TDH,数据就会成功被锁存。

根据表28.2所示,TDS是100ns,TDH则是50ns,换之TBD+TCL是200ns,TBD+TCH则是200ns。简单来说,TDS还有TDH都无法完全覆盖数据,结果数据的有效性是得到人头担保的。

clip_image032

图28.14 数据相关的时序参数(主机视角)。

图28.14还是数据相关的时序参数,不过方向是从机给主机发送数据,当从机借由下降沿设置数据的时候,必须被TDO拖后腿诺干时间。根据理想时序(左图),TDO只是单纯覆盖Data而已 ... 反之,右图的物理时序则会推挤Data。根据表28.2所示,TDO最大有可能拖后腿半个周期,反过来就是不拖任何后腿。活着就要乐观一点,凡事都往好的方向去想,所以我们可以无视TDO。

最后还有一些仅是与CS信号扯上关系的小啰嗦,如TDV,TTR等参数。我们仅要读写数据之前死拉高CS不放,读写数据之间死拉低CS不放,然后读写数据之后又死拉高CS不放,我们就能成功打发它们。

你可能感兴趣的:(FPGA)