DS1302这只硬件虽然曾在《建模篇》介绍过,所以重复的内容请怒笔者懒惰唠叨了,笔者尽可以一笑带过,废话少说让我们进入正题吧。DS1302是执行事实时钟(Real Time Clock)的硬件,采用SPI传输。
表示23.1 访问(地址)字节。
[7] |
[6] |
[5] |
[4] |
[3] |
[2] |
[1] |
[0] |
1 |
A5 |
A4 |
A3 |
A2 |
A1 |
A0 |
R/W |
DS1302作为从机任由主机蹂躏 ... 啊,是任由主机访问才对。对此,访问便有方向之分。如表23.1所示,访问字节(地址字节)[0]为访问方向1读0写。[6..1]为地址。[7]为常量1。除了访问字节以外,DS1302也有数据字节。
图23.1 写操作的理想时序(主机视角)。
图23.1是写操作的理想时序图,SCLK为串行时钟,CS为拉高有效的片选(又名为RESET),DATA是数据进出的I/O。忘了说,DS1302由于使用SPI传输的关系,所以下降沿设置数据,上升沿锁存数据。如图23.1所示,左边为访问字节,右边则是数据字节,CS拉高为写操作有效。对此,主机先将访问字节写入,再将数据字节写入指定的位置。闲置状态,SCLK信号还有CS信号都是拉低发呆,而且读写数据都是由低至高。
至于Verilog则可以这样描述,结果如代码23.1所示:
1. 0:
2. begin { rRST,rSCLK } <= 2'b10; T <= iAddr; i <= FF_Write; Go <= i + 1'b1; end
3. 1:
4. begin T <= iData; i <= FF_Write; Go <= i + 1'b1; end
5. 2:
6. begin { rRST,rSCLK } <= 2'b00; i <= i + 1'b1; end
7. ...
8. 16,17,18,19,20,21,22,23:
9. begin
10. isQ = 1'b1;
11. rSIO <= T[i-16];
12. if( C1 == 0 ) rSCLK <= 1'b0;
13. else if( C1 == FHALF ) rSCLK <= 1'b1;
14. if( C1 == FCLK -1) begin C1 <= 6'd0; i <= i + 1'b1; end
15. else C1 <= C1 + 1'b1;
16. end
17. 24:
18. i <= Go;
代码23.1
步骤0拉高片选,拉低时钟,准备访问字节,然后进入伪函数。步骤1准备数据字节,然后进入伪函数。步骤2拉低使能,拉低时钟。
步骤16~23为写入一个字节的伪函数,isQ为IO的输出控制,rSIO为DATA的输出驱动,rSCLK为SCLK的输出驱动,FCLK为一个时钟周期,FHALF为半周期。写操作只要任由 T 全程驱动 rSIO即可,期间C1为0拉低时钟,C1为半个周期便拉高时钟。
步骤24则返回步骤。
图23.2 写操作的理想时序(从机视角)。
图23.2则是从机视角的写操作时序,从机任何时候都是利用上升沿读取数据。
图23.3 读操作的理想时序(主机视角)。
图23.3为读操作的理想时序。T0~T7,主机写入访问字节并且指定读出地址。T8~T15,从机读出数据字节,期间DATA为输入状态,从机根据下降沿设置(更新)数据,主机为上升沿读取。至于Verilog则可以这样描述,结果如代码23.2所示:
1. 0 :
2. begin { rRST,rSCLK } <= 2'b10; T <= iAddr; i <= FF_Write; Go <= i + 1'b1; end
3. 1:
4. begin i <= FF_Read; Go <= i + 1'b1; end
5. 2:
6. begin { rRST,rSCLK } <= 2'b00; D1 <= T; i <= i + 1'b1; end
7. ...
8. 16,17,18,19,20,21,22,23:
9. begin
10. isQ = 1'b1;
11. rSIO <= T[i-16];
12. if( C1 == 0 ) rSCLK <= 1'b0;
13. else if( C1 == FHALF ) rSCLK <= 1'b1;
14. if( C1 == FCLK -1) begin C1 <= 6'd0; i <= i + 1'b1; end
15. else C1 <= C1 + 1'b1;
16. end
17. 24:
18. i <= Go;
19. ...
20. 32,33,34,35,36,37,38,39:
21. begin
22. isQ = 1'b0;
23. if( C1 == 0 ) rSCLK <= 1'b0;
24. else if( C1 == FHALF ) begin rSCLK <= 1'b1; T[i-32] <= RTC_DATA; end
25. if( C1 == FCLK -1) begin C1 <= 6'd0; i <= i + 1'b1; end
26. else C1 <= C1 + 1'b1;
27. end
28. 40:
29. i <= Go;
代码23.2
步骤0拉低使能,拉低时钟,准备访问字节,进入伪函数写。步骤1准备读数据,进入为函数读。步骤2为读取数据,拉低使能,拉低时钟。步骤16~24为协议一个字节的伪函数,步骤32~40为读取一个字节的伪函数。拉低isQ让IO为输入状态,C1为0拉低 rSCLK,C1为半个周期拉高 rSCLK并且读取数据,上述操作重复8次便搞定。
图23.4 读操作的理想时序(从机视角)。
图23.4为从机视角的读操作,从机在T0~T7利用上升沿读取数据,然后在T8~T15利用下降沿输出数据。
表23.2 访问内容/寄存器内容。
访问地址 |
寄存器内容 |
|||||||||
读 |
写 |
[7] |
[6] |
[5] |
[4] |
[3] |
[2] |
[1] |
[0] |
范围 |
81H |
80H |
CH |
秒十位 |
秒个位 |
59~00 |
|||||
83H |
82H |
分十位 |
分个位 |
59~00 |
||||||
85H |
84H |
12/24 |
时十位 |
时个位 |
12/24~00 |
|||||
87H |
86H |
日十位 |
日个位 |
31~1 |
||||||
89H |
88H |
月十位 |
月个位 |
12~1 |
||||||
8BH |
8AH |
天 |
7~1 |
|||||||
8DH |
8CH |
年十位 |
年个位 |
99~00 |
||||||
8FH |
8EH |
读保护 |
||||||||
C1H |
C0H |
FFH~00H |
||||||||
... |
... |
... |
||||||||
FDH |
FCH |
FFH~00H |
表23.2为DS1302的访问内容,或者说为寄存器地址与寄存器内容,其中秒寄存器的[7]为0开始计时,为1则停止计时。调用过程大致如下:
初始化:
l 发送8EH访问字节,关闭写保护;
l 发送84H访问字节,初始化“时种”;
l 发送82H访问字节,初始化“分钟”;
l 发送80H访问字节,初始化“秒钟”,开始计时。
调用:
l 发送81H访问字节,读取“秒钟”内容;
l 发送83H访问字节,读取“分钟”内容;
l 发送85H访问字节,读取“时种”内容;
l 重复上述内容。
至于详细过程还有具体的寄存器内容,笔者已在《建模篇》解释过,所以读者自己看着办吧。接下来,让我们进入本实验的重点内容吧。
表32.3 DS1302的时序参数①。
最小 |
最大 |
||||
时序参数 |
标示 |
时间 |
时钟(50Mhz) |
时间 |
时钟(50Mhz) |
Clock Frequency |
FCLK |
2Mhz |
25 |
||
Clock High Time |
TCH |
250ns |
12.5 |
||
Clock Low Time |
TCL |
250ns |
12.5 |
||
Clock Rise and Fall |
TR,TF |
0ns |
0 |
500ns |
25 |
首先让我们先来瞧瞧相关的时序参数。表23.3为速率为2Mhz的时序参数。DS1302最高速率为2Mhz并且无下限,50Mhz的量化结果为25。时钟信号拉高TCH或拉低TCL至少需要保持250ns,量化结果为12.5。至于时钟信号上山TR或者下山TF最大时间为500ns,极端说是最小时间是0ns。
图23.5 时序参数①。
如图23.5所示,那是时钟信号还有相关的时序参数,左图为理想时序,右图为物理时序。
TR+TH造就前半时钟周期,TF+TL造就后半时钟周期,然后TR+TH+TF+TL 为一个时钟周期。
表23.4 DS1302的时序参数②。
最小 |
最大 |
||||
时序参数 |
标示 |
时间 |
时钟(50Mhz) |
时间 |
时钟(50Mhz) |
CE to Clock Setup |
TCC |
1us |
50 |
||
Data to Clock Setup |
TDC |
50ns |
2.5 |
图23.6 时序参数②。
如表23.4是TCC还有TDC的时序参数,虽然两者都有 Setup 字眼,实际上它们都是推挤作用的时序参数。如图23.6的右图所示,那是物理时序图,TCC向右推挤SCLK信号,TDC向右推挤TDC信号。换做左图的理想时序图,TCC使SCLK被覆盖,TDC使DATA被覆盖。有些同学可能会被TCC的要求吓到,既然是1us。原理上,它是时钟周期的两倍,不过那是推挤作用的时序参数,只要操作结束之前一直拉高CS即可无视。
最小 |
最大 |
||||
时序参数 |
标示 |
时间 |
时钟(50Mhz) |
时间 |
时钟(50Mhz) |
CLOCK to Data Delay |
TCDD |
200ns |
10 |
||
Clock to Data Hold |
TCDH |
70ns |
3.5 |
图23.7 时序参数③。
表23.4显示TCDD还有TCDH两只时序参数,如23.7的右图所示,那是物理时序。我们知道DS1302是下降沿设置(输出)数据,上升沿锁存数据,期间TCDD将数据向右推挤,TCDH则就是典型的 THold,即锁存数据以后的确保时间。如23.7的左图所示,那是理想时序,由于这两只都是小家伙,一般都无法完全覆盖数据。
最小 |
最大 |
||||
时序参数 |
标示 |
时间 |
时钟(50Mhz) |
时间 |
时钟(50Mhz) |
CE Inactive Time |
TCWH |
1us |
50 |
||
CE to I/O High Impedance |
TCDZ |
70ns |
3.5 |
||
SCLK to I/O High Impedance |
TCCZ |
70ns |
3.5 |
表23.5有3个比较奇怪的时序参数,TCWH为片选信号进入静态所需的时间,也认为是释放片选所需的最小时间。至于 TCDZ 与 TCCZ 都是与高阻态有关的时序参数,而高阻态也与I/O息息相关。感觉上,高阻态好比一只切断输出的大刀,而且这只大刀必须由人操纵,其中TCDZ就是CE切断输出状态所需的最小时间,TCCZ就是SCLK切断输出状态所需的最小时间。
具体而言,如代码代码23.3所示:
module (...)
...
input Call,
inout SIO,
...
assign SIO = !Call ? D1 : 1’bz;
endmodule
代码23.3
代码23.3告诉我们,SIO由 D1 驱动输出,而且Call拉高便将SIO的输出切断。假设TSSZ(Call to I/O High Impedance)是Call切断输出所需的时间 ... 细心观察,Call一旦拉低,SIO并不会立即输出高阻态,则是必须经历一段时间。至于高阻态的大小姨妈,一般都是硬件内部(从机)的纠纷,鲜少与驱动方(主机)扯上关系,所以我们在此聊聊就好。
图23.8 写操作的物理时序(时序参数)。
图23.9 读操作的物理时序(时序参数)。
最后附上两张官方的原图23.8还有图23.9作为本说明的谢幕,读者就自己慢慢看着办吧。理解完毕,我们便可以开始建模了。
图23.10 DS1302基础模块的建模图。
图23.10是DS1302基础模块的建模图,内容包含控制模块与功能模块,功能模块负责最基础的读写操作,控制模块则负责功能调度,准备访问字节等任务。换之,功能模块的右边是驱动硬件资源的顶层信号。其中,功能模块的oData穿过控制模块直接驱动外围。
图23.11 DS1302功能模块的建模图。
图23.11是DS1302功能模块的建模图,Call/Done位宽为2,其中 [1]为写操作,[0]为读操作。具体内容让我们来看代码吧:
1. module ds1302_funcmod
2. (
3. input CLOCK, RESET,
4. output RTC_NRST,RTC_SCLK,
5. inout RTC_DATA,
6. input [1:0]iCall,
7. output oDone,
8. input [7:0]iAddr,iData,
9. output [7:0]oData
10. );
以上内容为相关的出入端声明。
11. parameter FCLK = 6'd25, FHALF = 6'd12; // 2Mhz,(1/2Mhz)/(1/50Mhz)
12. parameter FF_Write = 6'd16, FF_Read = 6'd32;
13.
以上内容为伪函数入口地址以及常量声明。FCLK为一个周期,FHALF为半周期。
14. reg [5:0]C1;
15. reg [5:0]i,Go;
16. reg [7:0]D1,T;
17. reg rRST, rSCLK, rSIO;
18. reg isQ,isDone;
19.
20. always @ ( posedge CLOCK or negedge RESET )
21. if( !RESET )
22. begin
23. C1 <= 6'd0;
24. { i,Go } <= { 6'd0,6'd0 };
25. { D1,T } <= { 8'd0,8'd0 };
26. { rRST, rSCLK, rSIO } <= 3'b000;
27. { isQ, isDone } <= 2'b00;
28. end
以上内容为相关的寄存器还有复位操作。D1为暂存读取结果,T为伪函数的操作空间,isQ为 IO的控制输出。
29. else if( iCall[1] )
30. case( i )
31.
32. 0:
33. begin { rRST,rSCLK } <= 2'b10; T <= iAddr; i <= FF_Write; Go <= i + 1'b1; end
34.
35. 1:
36. begin T <= iData; i <= FF_Write; Go <= i + 1'b1; end
37.
38. 2:
39. begin { rRST,rSCLK } <= 2'b00; i <= i + 1'b1; end
40.
41. 3:
42. begin isDone <= 1'b1; i <= i + 1'b1; end
43.
44. 4:
45. begin isDone <= 1'b0; i <= 6'd0; end
46.
47. /******************/
48.
49. 16,17,18,19,20,21,22,23:
50. begin
51. isQ = 1'b1;
52. rSIO <= T[i-16];
53.
54. if( C1 == 0 ) rSCLK <= 1'b0;
55. else if( C1 == FHALF ) rSCLK <= 1'b1;
56.
57. if( C1 == FCLK -1) begin C1 <= 6'd0; i <= i + 1'b1; end
58. else C1 <= C1 + 1'b1;
59. end
60.
61. 24:
62. i <= Go;
63.
64. endcase
以上内容为部分核心操作。以上内容是写操作,步骤16~24是写一个字节的伪函数。步骤0拉高片选,准备访问字节,并且进入伪函数。步骤1准备写入数据并且进入伪函数。步骤2拉低片选,步骤3~4则是用来产生完成信号。
65. else if( iCall[0] )
66. case( i )
67.
68. 0 :
69. begin { rRST,rSCLK } <= 2'b10; T <= iAddr; i <= FF_Write; Go <= i + 1'b1; end
70.
71. 1:
72. begin i <= FF_Read; Go <= i + 1'b1; end
73.
74. 2:
75. begin { rRST,rSCLK } <= 2'b00; D1 <= T; i <= i + 1'b1; end
76.
77. 3:
78. begin isDone <= 1'b1; i <= i + 1'b1; end
79.
80. 4:
81. begin isDone <= 1'b0; i <= 6'd0; end
82.
83. /*********************/
84.
85. 16,17,18,19,20,21,22,23:
86. begin
87. isQ = 1'b1;
88. rSIO <= T[i-16];
89.
90. if( C1 == 0 ) rSCLK <= 1'b0;
91. else if( C1 == FHALF ) rSCLK <= 1'b1;
92.
93. if( C1 == FCLK -1) begin C1 <= 6'd0; i <= i + 1'b1; end
94. else C1 <= C1 + 1'b1;
95. end
96.
97. 24:
98. i <= Go;
99.
100. /*********************/
101.
102. 32,33,34,35,36,37,38,39:
103. begin
104. isQ = 1'b0;
105.
106. if( C1 == 0 ) rSCLK <= 1'b0;
107. else if( C1 == FHALF ) begin rSCLK <= 1'b1; T[i-32] <= RTC_DATA; end
108.
109. if( C1 == FCLK -1) begin C1 <= 6'd0; i <= i + 1'b1; end
110. else C1 <= C1 + 1'b1;
111. end
112.
113. 40:
114. i <= Go;
115.
116. endcase
117.
以上内容为部分核心操作。以上内容是读操作,步骤16~24是写一个字节的伪函数,步骤32~40则是读一个字节的伪函数。步骤0拉高使能,准备访问字节并且进入写函数。步骤1进入读函数。步骤2拉低使能之余,也将读取结果暂存至D。步骤3~4用来产生完成信号。
118. assign { RTC_NRST,RTC_SCLK } = { rRST,rSCLK };
119. assign RTC_DATA = isQ ? rSIO : 1'bz;
120. assign oDone = isDone;
121. assign oData = D1;
122.
123. endmodule
以上内容为相关输出驱动声明,其中rSIO驱动RTC_DATA,D驱动oData。
图23.11 DS1302控制模块的建模图。
图23.11是该控制模块的建模图,右边信号用来调用功能模块,左边信号则被调用,其中Call/Done 为8位宽,位宽内容如表23.6所示:
表23.6 Call/Done 的位宽内容。
位 |
内容 |
[7] |
关闭写保护 |
[6] |
写入时钟 |
[5] |
写入分钟 |
[4] |
写入秒钟 |
[3] |
开启写保护 |
[2] |
读取时钟 |
[1] |
读取分钟 |
[0] |
读取秒钟 |
1. module ds1302_ctrlmod
2. (
3. input CLOCK, RESET,
4. input [7:0]iCall,
5. output oDone,
6. input [7:0]iData,
7. output [1:0]oCall,
8. input iDone,
9. output [7:0]oAddr, oData
10. );
以上内容为相关的出入端声明。
11. reg [7:0]D1,D2;
12.
13. always @ ( posedge CLOCK or negedge RESET )
14. if( !RESET )
15. begin
16. D1 <= 8'd0;
17. D2 <= 8'd0;
18. end
19. else
20. case( iCall[7:0] )
21.
22. 8'b1000_0000 : // Write unprotect
23. begin D1 = 8'h8E; D2 = 8'b0000_0000; end
24.
25. 8'b0100_0000 : // Write hour
26. begin D1 = 8'h84; D2 = iData; end
27.
28. 8'b0010_0000 : // Write minit
29. begin D1 = 8'h82; D2 = iData; end
30.
31. 8'b0001_0000 : // Write second
32. begin D1 = 8'h80; D2 = iData; end
33.
34. 8'b0000_1000 : // Write protect
35. begin D1 = 8'h8E; D2 = 8'b1000_0000; end
36.
37. 8'b0000_0100 : // Read hour
38. begin D1 = 8'h85; end
39.
40. 8'b0000_0010 : // Read minit
41. begin D1 = 8'h83; end
42.
43. 8'b0000_0001 : // Read second
44. begin D1 = 8'h81; end
45.
46. endcase
47.
以上内容为准备访问字节还有数据节还的周边操作,它会根据iCall 的内容准备D1与D2。
48. reg [1:0]i;
49. reg [1:0]isCall;
50. reg isDone;
51.
52. always @ ( posedge CLOCK or negedge RESET )
53. if( !RESET )
54. begin
55. i <= 2'd0;
56. isCall <= 2'b00;
57. isDone <= 1'b0;
58. end
以上内容为相关寄存器声明还有复位操作。
59. else if( iCall[7:3] ) // Write action
60. case( i )
61.
62. 0 :
63. if( iDone ) begin isCall[1] <= 1'b0; i <= i + 1'b1; end
64. else begin isCall[1] <= 1'b1; end
65.
66. 1 :
67. begin isDone <= 1'b1; i <= i + 1'b1; end
68.
69. 2 :
70. begin isDone <= 1'b0; i <= 2'd0; end
71.
72. endcase
以上内容为调用写操作。
73. else if( iCall[2:0] ) // Read action
74. case( i )
75.
76. 0 :
77. if( iDone ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
78. else begin isCall[0] <= 1'b1; end
79.
80. 1 :
81. begin isDone <= 1'b1; i <= i + 1'b1; end
82.
83. 2 :
84. begin isDone <= 1'b0; i <= 2'd0; end
85.
86. endcase
87.
以上内容为调用读操作。
88. assign oDone = isDone;
89. assign oCall = isCall;
90. assign oAddr = D1;
91. assign oData = D2;
92.
93. endmodule
以上内容为相关的输出驱动。
该模块的连线部署请参考图23.10。
1. module ds1302_basemod
2. (
3. input CLOCK, RESET,
4. output RTC_NRST, RTC_SCLK,
5. inout RTC_DATA,
6. input [7:0]iCall,
7. output oDone,
8. input [7:0]iData,
9. output [7:0]oData
10. );
11. wire [7:0]AddrU1;
12. wire [7:0]DataU1;
13. wire [1:0]CallU1;
14.
15. ds1302_ctrlmod U1
16. (
17. .CLOCK( CLOCK ),
18. .RESET( RESET ),
19. .iCall( iCall ), // < top
20. .oDone( oDone ), // > top
21. .iData( iData ), // > top
22. .oCall( CallU1 ), // > U2
23. .iDone( DoneU2 ), // < U2
24. .oAddr( AddrU1 ), // > U2
25. .oData( DataU1 ) // > U2
26. );
27.
28. wire DoneU2;
29.
30. ds1302_funcmod U2
31. (
32. .CLOCK( CLOCK ),
33. .RESET( RESET ),
34. .RTC_NRST( RTC_NRST ), // > top
35. .RTC_SCLK( RTC_SCLK ), // > top
36. .RTC_DATA( RTC_DATA ), // <> top
37. .iCall( CallU1 ), // < U1
38. .oDone( DoneU2 ), // > U1
39. .iAddr( AddrU1 ), // > U1
40. .iData( DataU1 ), // > U1
41. .oData( oData ) // > top
42. );
43.
44. endmodule
图23.12 实验二十三的建模图。
图23.12是实验二十三的基础模块。核心操作先初始化DS1302基础模块,然后无间断从哪里读取时钟,分钟还有秒钟,最后驱动至SMG基础模块。具体内容让我们来看代码吧。
1. module ds1302_demo
2. (
3. input CLOCK, RESET,
4. output RTC_NRST, RTC_SCLK,
5. inout RTC_DATA,
6. output [7:0]DIG,
7. output [5:0]SEL
8. );
以上内容为相关出入端声明。
9. wire DoneU1;
10. wire [7:0]DataU1;
11.
12. ds1302_basemod U1
13. (
14. .CLOCK( CLOCK ),
15. .RESET( RESET ),
16. .RTC_NRST( RTC_NRST ), // > top
17. .RTC_SCLK( RTC_SCLK ), // > top
18. .RTC_DATA( RTC_DATA ), // > top
19. .iCall( isCall ), // < core
20. .oDone( DoneU1 ), // > core
21. .iData( D1 ), // < core
22. .oData( DataU1 ) // > core
23. );
24.
以上内容为DS1302基础模块的实例化。
25. smg_basemod U2
26. (
27. .CLOCK( CLOCK ),
28. .RESET( RESET ),
29. .DIG( DIG ), // > top
30. .SEL( SEL ), // > top
31. .iData( D2 ) // < core
32. );
以上内容为SMG基础模块的实例化。
34. reg [3:0]i;
35. reg [7:0]isCall;
36. reg [7:0]D1;
37. reg [23:0]D2;
38.
39. always @ ( posedge CLOCK or negedge RESET )
40. if( !RESET )
41. begin
42. i <= 4'd0;
43. isCall <= 8'd0;
44. D1 <= 8'd0;
45. D2 <= 24'd0;
46. end
47. else
以上内容为相关寄存器声明还有复位操作。
48. case( i )
49.
50. 0:
51. if( DoneU1 ) begin isCall[7] <= 1'b0; i <= i + 1'b1; end
52. else begin isCall[7] <= 1'b1; D1 <= 8'h00; end
53.
54. 1:
55. if( DoneU1 ) begin isCall[6] <= 1'b0; i <= i + 1'b1; end
56. else begin isCall[6] <= 1'b1; D1 <= { 4'd2, 4'd1 }; end
57.
58. 2:
59. if( DoneU1 ) begin isCall[5] <= 1'b0; i <= i + 1'b1; end
60. else begin isCall[5] <= 1'b1; D1 <= { 4'd5, 4'd9 }; end
61.
62. 3:
63. if( DoneU1 ) begin isCall[4] <= 1'b0; i <= i + 1'b1; end
64. else begin isCall[4] <= 1'b1; D1 <= { 4'd5, 4'd0 }; end
65.
66. /*************/
67.
以上内容为核心操作的部分内容,步骤0关闭写保护,步骤1初始化时钟,步骤2初始化分钟,步骤3初始化秒钟并且开启计时。
68. 4:
69. if( DoneU1 ) begin D2[7:0] <= DataU1; isCall[0] <= 1'b0; i <= i + 1'b1; end
70. else begin isCall[0] <= 1'b1; end
71.
72. 5:
73. if( DoneU1 ) begin D2[15:8] <= DataU1; isCall[1] <= 1'b0; i <= i + 1'b1; end
74. else begin isCall[1] <= 1'b1; end
75.
76. 6:
77. if( DoneU1 ) begin D2[23:16] <= DataU1; isCall[2] <= 1'b0; i <= 4'd4; end
78. else begin isCall[2] <= 1'b1; end
79.
80. endcase
81.
82. endmodule
步骤4读取秒钟然后暂存至D2[7:0],步骤5读取分钟然后暂存至D2[15:8],步骤6读取时钟然后暂存至D2[23:16]。综合完毕情切下载程序,如果数码管从21-59-50开始起跑,表示实验成功。
细节一:完整的个体模块
本实验的DS1302基础模块虽然是就绪的完整模块,不过依然无法满足那些欲无止境的读者 ... 例如,读年或者读天,还是利用DS1302的RAM。对此,读者请自己探索然后尝试扩展该基础模块吧。