读者在上一个实验所玩弄过的 TFT LCD模块,除了显示大小为 320 × 240,颜色为16位RGB的图像信息以外,它还支持触屏。所谓触屏就是鼠标还有键盘以外的输入手段,例如现在流行平板还有智能手机,触屏输入对我们来说,已经成为日常的一部分。描述语言一门偏向硬件的语言,面对触屏,它顶多只能做做一些驱动的工作,其余如滤波,还有像素转换等计算,它必须交由高级语言去负责。
面向无能为力的描述语言,笔者不禁联想过去的自己 ... 好痛苦,好难受。话虽如此,天生我才必有用,描述语言虽然完成不了触屏的伟业,不过只要做做驱动,平稳过着日子,它便心满意足了。
图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读取。好奇的同学可能会继续好奇什么是物理像素?这个问题说来话长 ... 假设屏幕是一个容器,藏在容器里边的像素就称为屏幕像素,然而围绕容器外边的像素就称为物理像素。
比喻来讲,同是人形生物,住在地球的人形生物就称为地球人,住在地球以外的人形生物就称为外星人。结果而言,地球人并不等价外星人,虽然两者都有相似的外表。所以说,物理像素还有屏幕像素,它们听起来都是像素,不过实际上是不等价的东西。对此,物理像素为了成为屏幕像素,它们必须经过“转换算法”才能成为如假包换的屏幕像素。不过,“转换算法”对描述语言来说,这项工作实在太巨大了。
好了好了,笔者也差不多要进入主题了,废话再说下去,口水就会把电脑淹没了。
图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则是从机。
图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行则是由高至低逐位赋值。
图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哪里暂存结果。
图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信号。
图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
准备知识理解完毕以后,我们便可以开始建模了。
图28.7 Touch基础模块的建模图。
图28.7是Touch基础模块,内容包含控制模块还有功能模块。功能模块左边有两位宽的Call/Done,其中[1]为读取物理像素X,[0]为读取物理像素Y。至于右边则是一些顶层信号。控制模块的左边,除了顶层信号IRQ是输入以外,其余信号都呈现输出状态。16位宽的Data对应物理像素X还有物理像素Y,而Done则是沟通作用的触发信号。
整体来说,一旦触碰触屏,IRQ拉低,控制模块利用功能模块读取物理像素X与Y,然后再产生完成信号以示一次性的触屏结果。
图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的高八位。
图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
以上内容为相关的输出驱动声明。
有关这个模块的连线部署请参考图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基础模块的连线部署,读者自己看着办吧。
图28.10 实验二十八的建模图。
图28.10是实验二十八的建模图,首先核心操作会初始化还有清屏TFT基础模块,然后触屏信息会经由Touch基础模块传至核心操作,随后核心操作再将触屏信息作为屏幕像素写入TFT屏当中。此外,笔者也更动基础模块的绘图功能,它会将32位宽的像素X,像素Y,以及颜色信息写入其中。废话少说,让我们看看该绘图功能究竟发生什么改变呢?
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则是用来产生完成信号。读者是不是觉得很简单呢?
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基础模块充其量还是半身熟的鸡蛋,因为当中缺省重要的环节,然而这个环节却是描述语言难以负担的重任。
图28.11 物理像素转换为屏幕像素的概念图。
如图28.11所示,原始的物理像素一般都包含噪声,对此物理像素必须预先经过一层滤波。滤波以后的物理像素才会开始进入转换阶段,转换阶段必须借用转换算法的力量,论道算法,想必有些同学会不经意缩紧眉心,因为我们必须向数学打交道不可。那怕对象是简单乘法或者除法,读过《时序篇》的朋友一定会晓得算法都很麻烦。
物理像素成功转换为屏幕像素以后,它还不能立即纳入使用,因为转换算法有可能存在细节上的缺失。对此,屏幕像素必须经过校正 ... 校正期间不仅有出现算法,我们还要和屏幕发生互动,实在烦死人了。现阶段而言,如果描述语言如果不及顺序语言那么便捷,什么物理像素转换屏幕像素,什么校正 ... 我们最好想也不要想,不然结果只有自讨苦吃。
虽然笔者有可能被指责为胆小鬼,不负责任之类的渣渣,对此笔者不否认,理性而言,那些危及生命的事情,笔者另可背负屈辱也要逃跑,因为没有什么东西比生命更可贵。此刻,只要承认懦弱,我们才会安全成长,未来的事情就让未来的自己去打算吧!少年少女们!
细节二:时序参数
老实说,笔者也觉得懦弱的自己太没出息了 ... 不过,作为补偿,让我们来瞧瞧硬件XTP2046的时序参数吧。
图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 |
图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的死粉,读者随时都可以驾驶藤原豆腐车超频。
图28.12 CS相关的时序参数。
接下来让我们看看 TCSS 还有 TCSH,前者可以视为CS拉低所需的最小时间,后者则是CS拉高所需的最小时间。前者需要100ns,后者则是10ns,如果无法满足它们,CS的有效性就会存在风险。话虽如此,因为手册比较怕死,所以参数略显保险(夸张),我们只要随便应付就好。
图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都无法完全覆盖数据,结果数据的有效性是得到人头担保的。
图28.14 数据相关的时序参数(主机视角)。
图28.14还是数据相关的时序参数,不过方向是从机给主机发送数据,当从机借由下降沿设置数据的时候,必须被TDO拖后腿诺干时间。根据理想时序(左图),TDO只是单纯覆盖Data而已 ... 反之,右图的物理时序则会推挤Data。根据表28.2所示,TDO最大有可能拖后腿半个周期,反过来就是不拖任何后腿。活着就要乐观一点,凡事都往好的方向去想,所以我们可以无视TDO。
最后还有一些仅是与CS信号扯上关系的小啰嗦,如TDV,TTR等参数。我们仅要读写数据之前死拉高CS不放,读写数据之间死拉低CS不放,然后读写数据之后又死拉高CS不放,我们就能成功打发它们。