大侠好,欢迎来到FPGA技术江湖。本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的“傻瓜式”讲解,让电子、信息、通信类专业学生、初入职场小白及打算进阶提升的职业开发者都可以有系统性学习的机会。
系统性的掌握技术开发以及相关要求,对个人就业以及职业发展都有着潜在的帮助,希望对大家有所帮助。本次带来Vivado系列,RTC实时时钟系统设计。话不多说,上货。
实时时钟系统设计
作者:李西锐 校对:陆辉
RTC(real time clock)实时时钟,在电脑、手机等电子产品中都有,应用较多。它的主要作用就是,在产品断电之后,时间还可以继续走数。这样我们在重新使用电子产品时,时间仍然正确。芯片本身可以通过纽扣电池供电,接下来我们一起学习一下RTC的驱动。
此次,RTC用到的主芯片为DS1302,优点为控制简单,接下来我们一起了解一下芯片的特性以及控制。
一、芯片特征
在官方文档中,给出了以下重要特点描述:
1、实时时钟系统可以计数时、分、秒、日、月、星期、区分闰年平年的年份等,最多支持2100年。
2、有31*8bit的存储容量的RAM。
3、实时时钟系统或者RAM的读写,可以单字节或者多字节(突发模式)进行数据传输。
在芯片计时过程中,可以准确的计出时分秒等,还有每个月各有多少天。有30、31、28、29天的区分,年份有闰年平年的区分。小时有12小时制和24小时制。有AM和PM的区分。
芯片的控制是通过CE、I/O(data line)和SCLK。数据线可以一次传输1字节或者31字节。
二、端口
图中的MCU在此时是指的我们的FPGA,那么,FPGA与芯片进行数据交互时,是通过三个串行线进行的,并且I/O为双端口类型。芯片的端口中,除了三个主控端口(CE、I/O、SCLK)外,还有X1和X2。
这两个端口为晶振接口,芯片需要一个外挂晶振来提供时钟,以便用来计时。VCC1和VCC2为两路电源,其中VCC2为板卡提供的电源,VCC1为纽扣电池供电。供电关系会在下面的管脚说明里面进行讲解。
GND为电源地。
三、管脚说明
四、命令格式
下图展示了命令字节,一个命令字节由数据的发送者决定。最高位必须为1,如果是0,将禁止向芯片写数据。bit6如果是0将与实时时钟系统通信,如果是1将与RAM通信。bit1到bit5为寄存器地址;bit0如果为0为写操作,1为读操作。
五、读写控制
首先是写操作,在8个SCLK时钟周期内,主机发送一个写命令字节,数据输入在接下来的8个SCLK时钟的上升沿,数据开始为bit0,也就是说,数据在发送时,从低位开始发送。
数据读操作,在8个SCLK时钟周期内,主机发送一个读命令,数据输出在接下来的8个SCLK时钟下降沿。第一个数据bit出现在命令字节最后一个bit被写入之后的第一个下降沿。通常数据传输需要在CE为高时。读数据时,也是从低位开始。
六、接口协议
基于芯片的读写方式,我们可以使用SPI协议进行数据读写,那么接下来我们介绍一下SPI协议。
SPI协议有四种模式,如下图:
SPI的四种模式是按照其时钟极性(CPOL)和时钟相位(CPHA)共同决定的,CPOL=0,即SCLK=0,表示SCLK时钟信号线在空闲状态时的电平为低电平,因此有效状态为高电平。CPHA=0,即表示在时钟的第一个岩信号进行采样。CPOL和CPHA共有四种组合,固有四种通信模式。
SPI为主从模式,在通信线上,需要4通信线:
CS – 从设备使能信号,由主设备控制
SCL – 时钟信号,由主设备产生
MISO – 主设备数据输入,从设备数据输出
MOSI – 主设备数据输出,从设备数据输入
但是,一般为了节省资源,会使用3跟通信线,分别为CE、SCL、SDA。其中SDA为双端口。数据的输出和输入都使用这条线。
在我们的DS1302时序图中,读写时序跟SPI的第一种模式一样,所以我们在写代码的时候可以使用SPI协议去写。
接下来我们开始新建工程写代码。
新建文件,按照写时序,通过线性序列机写出写模块。
代码如下:
1 module rtc_wr(
2
3 input wire clk,
4 input wire rst_n,
5
6 input wire wr_en,
7 input wire [7:0] wr_addr,
8 input wire [7:0] wr_data,
9
10 output wire wr_done,
11 output reg wr_scl,
12 output reg wr_sda,
13 output reg wr_ce
14 );
15
16 parameter f_clk = 50_000_000;
17 parameter f = 100_000;
18 parameter t = f_clk / f / 2;
19
20 reg [13:0] cnt;
21
22 always @ (posedge clk, negedge rst_n)
23 begin
24 if(rst_n == 1'b0)
25 cnt <= 14'd0;
26 else if(wr_en)
27 begin
28 if(cnt == 33 * t - 1)
29 cnt <= 14'd0;
30 else
31 cnt <= cnt + 1'b1;
32 end
33 else
34 cnt <= 14'd0;
35 end
36
37 always @ (posedge clk, negedge rst_n)
38 begin
39 if(rst_n == 1'b0)
40 begin
41 wr_scl <= 1'b0;
42 wr_sda <= 1'b0;
43 wr_ce <= 1'b0;
44 end
45 else if(wr_en)
46 case(cnt)
47 0 : begin wr_ce <= 1'b1; wr_sda <= wr_addr[0]; end
48 1 * t - 1 : begin wr_scl <= 1'b1; end
49 2 * t - 1 : begin wr_scl <= 1'b0; wr_sda <= wr_addr[1]; end
50 3 * t - 1 : begin wr_scl <= 1'b1; end
51 4 * t - 1 : begin wr_scl <= 1'b0; wr_sda <= wr_addr[2]; end
52 5 * t - 1 : begin wr_scl <= 1'b1; end
53 6 * t - 1 : begin wr_scl <= 1'b0; wr_sda <= wr_addr[3]; end
54 7 * t - 1 : begin wr_scl <= 1'b1; end
55 8 * t - 1 : begin wr_scl <= 1'b0; wr_sda <= wr_addr[4]; end
56 9 * t - 1 : begin wr_scl <= 1'b1; end
57 10* t - 1 : begin wr_scl <= 1'b0; wr_sda <= wr_addr[5]; end
58 11* t - 1 : begin wr_scl <= 1'b1; end
59 12* t - 1 : begin wr_scl <= 1'b0; wr_sda <= wr_addr[6]; end
60 13* t - 1 : begin wr_scl <= 1'b1; end
61 14* t - 1 : begin wr_scl <= 1'b0; wr_sda <= wr_addr[7]; end
62 15* t - 1 : begin wr_scl <= 1'b1; end
63 16* t - 1 : begin wr_scl <= 1'b0; wr_sda <= wr_data[0]; end
64 17* t - 1 : begin wr_scl <= 1'b1; end
65 18* t - 1 : begin wr_scl <= 1'b0; wr_sda <= wr_data[1]; end
66 19* t - 1 : begin wr_scl <= 1'b1; end
67 20* t - 1 : begin wr_scl <= 1'b0; wr_sda <= wr_data[2]; end
68 21* t - 1 : begin wr_scl <= 1'b1; end
69 22* t - 1 : begin wr_scl <= 1'b0; wr_sda <= wr_data[3]; end
70 23* t - 1 : begin wr_scl <= 1'b1; end
71 24* t - 1 : begin wr_scl <= 1'b0; wr_sda <= wr_data[4]; end
72 25* t - 1 : begin wr_scl <= 1'b1; end
73 26* t - 1 : begin wr_scl <= 1'b0; wr_sda <= wr_data[5]; end
74 27* t - 1 : begin wr_scl <= 1'b1; end
75 28* t - 1 : begin wr_scl <= 1'b0; wr_sda <= wr_data[6]; end
76 29* t - 1 : begin wr_scl <= 1'b1; end
77 30* t - 1 : begin wr_scl <= 1'b0; wr_sda <= wr_data[7]; end
78 31* t - 1 : begin wr_scl <= 1'b1; end
79 32* t - 1 : begin wr_scl <= 1'b0; wr_ce <= 1'b0; end
80 default : ;
81 endcase
82 end
83
84 assign wr_done = (cnt == 33 * t - 1) ? 1'b1 : 1'b0;
85
86 endmodule
1 module rtc_rd(
2
3 input wire clk,
4 input wire rst_n,
5
6 input wire rd_en,
7 input wire [7:0] rd_addr,
8 input wire SDA,
9
10 output wire rd_done,
11 output reg [7:0] rd_data,
12 output reg rd_sda,
13 output reg rd_scl,
14 output reg rd_ce,
15 output reg o_en
16 );
17
18 parameter f_clk = 50_000_000;
19 parameter f = 100_000;
20 parameter t = f_clk / f / 2;
21
22 reg [13:0] cnt;
23
24 always @ (posedge clk, negedge rst_n)
25 begin
26 if(rst_n == 1'b0)
27 cnt <= 14'd0;
28 else if(rd_en)
29 begin
30 if(cnt == 33 * t - 1)
31 cnt <= 14'd0;
32 else
33 cnt <= cnt + 1'b1;
34 end
35 else
36 cnt <= 14'd0;
37 end
38
39 always @ (posedge clk, negedge rst_n)
40 begin
41 if(rst_n == 1'b0)
42 begin
43 rd_data <= 8'd0;
44 rd_scl <= 1'b0;
45 rd_sda <= 1'b0;
46 rd_ce <= 1'b0;
47 o_en <= 1'b1;
48 end
49 else if(rd_en)
50 case(cnt)
51 0 : begin rd_ce <= 1'b1; rd_sda <= rd_addr[0]; o_en <= 1'b1;end
52 1 * t - 1 : begin rd_scl <= 1'b1; end
53 2 * t - 1 : begin rd_scl <= 1'b0; rd_sda <= rd_addr[1]; end
54 3 * t - 1 : begin rd_scl <= 1'b1; end
55 4 * t - 1 : begin rd_scl <= 1'b0; rd_sda <= rd_addr[2]; end
56 5 * t - 1 : begin rd_scl <= 1'b1; end
57 6 * t - 1 : begin rd_scl <= 1'b0; rd_sda <= rd_addr[3]; end
58 7 * t - 1 : begin rd_scl <= 1'b1; end
59 8 * t - 1 : begin rd_scl <= 1'b0; rd_sda <= rd_addr[4]; end
60 9 * t - 1 : begin rd_scl <= 1'b1; end
61 10* t - 1 : begin rd_scl <= 1'b0; rd_sda <= rd_addr[5]; end
62 11* t - 1 : begin rd_scl <= 1'b1; end
63 12* t - 1 : begin rd_scl <= 1'b0; rd_sda <= rd_addr[6]; end
64 13* t - 1 : begin rd_scl <= 1'b1; end
65 14* t - 1 : begin rd_scl <= 1'b0; rd_sda <= rd_addr[7]; end
66 15* t - 1 : begin rd_scl <= 1'b1; end
67 16* t - 1 : begin rd_scl <= 1'b0; o_en <= 1'b0; end
68 17* t - 1 : begin rd_scl <= 1'b1; rd_data[0] <= SDA; end
69 18* t - 1 : begin rd_scl <= 1'b0; end
70 19* t - 1 : begin rd_scl <= 1'b1; rd_data[1] <= SDA; end
71 20* t - 1 : begin rd_scl <= 1'b0; end
72 21* t - 1 : begin rd_scl <= 1'b1; rd_data[2] <= SDA; end
73 22* t - 1 : begin rd_scl <= 1'b0; end
74 23* t - 1 : begin rd_scl <= 1'b1; rd_data[3] <= SDA; end
75 24* t - 1 : begin rd_scl <= 1'b0; end
76 25* t - 1 : begin rd_scl <= 1'b1; rd_data[4] <= SDA; end
77 26* t - 1 : begin rd_scl <= 1'b0; end
78 27* t - 1 : begin rd_scl <= 1'b1; rd_data[5] <= SDA; end
79 28* t - 1 : begin rd_scl <= 1'b0; end
80 29* t - 1 : begin rd_scl <= 1'b1; rd_data[6] <= SDA; end
81 30* t - 1 : begin rd_scl <= 1'b0; end
82 31* t - 1 : begin rd_scl <= 1'b1; rd_data[7] <= SDA; end
83 32* t - 1 : begin rd_scl <= 1'b0; rd_ce <= 1'b0; o_en <= 1'b1; end
84 default : ;
85 endcase
86 end
87
88 assign rd_done = (cnt == 33 * t - 1) ? 1'b1 : 1'b0;
89
90 endmodule
在读写模块中,按照框架设计,计数器必须在使能有效的条件下进行,所以,在写计数器时,必须判断使能信号。
控制模块如下:
1 module rtc_ctrl(
2
3 input wire clk,
4 input wire rst_n,
5
6 //wr
7 input wire wr_done,
8 input wire wr_scl,
9 input wire wr_sda,
10 input wire wr_ce,
11 output reg wr_en,
12 output reg [7:0] wr_addr,
13 output reg [7:0] wr_data,
14
15 //rd
16 input wire rd_done,
17 input wire rd_scl,
18 input wire rd_sda,
19 input wire rd_ce,
20 input wire [7:0] rd_data,
21 output reg rd_en,
22 output reg [7:0] rd_addr,
23
24 output reg SCL,
25 output reg SDA,
26 output reg CE,
27 output reg [7:0] s,
28 output reg [7:0] m,
29 output reg [7:0] h
30 );
31
32 parameter t = 1_000_000;
33
34 reg [19:0] cnt;
35 reg [2:0] state;
36
37 always @ (posedge clk, negedge rst_n)
38 begin
39 if(rst_n == 1'b0)
40 begin
41 wr_en <= 1'b0;
42 wr_addr <= 8'd0;
43 wr_data <= 8'd0;
44 rd_en <= 1'b0;
45 rd_addr <= 8'd0;
46 SCL <= 1'b0;
47 SDA <= 1'b0;
48 CE <= 1'b0;
49 s <= 8'd0;
50 m <= 8'd0;
51 h <= 8'd0;
52 cnt <= 20'd0;
53 state <= 3'd0;
54 end
55 else
56 case(state)
57 3'd0 : begin
58 if(wr_done)
59 state <= 3'd1;
60 else
61 begin
62 SCL <= wr_scl;
63 SDA <= wr_sda;
64 CE <= wr_ce;
65 wr_addr <= 8'h80;
66 wr_data <= 8'h30;
67 wr_en <= 1'b1;
68 end
69 end
70 3'd1 : begin
71 if(wr_done)
72 state <= 3'd2;
73 else
74 begin
75 SCL <= wr_scl;
76 SDA <= wr_sda;
77 CE <= wr_ce;
78 wr_addr <= 8'h82;
79 wr_data <= 8'h12;
80 wr_en <= 1'b1;
81 end
82 end
83 3'd2 : begin
84 if(wr_done)
85 begin
86 state <= 3'd3;
87 wr_en <= 1'b0;
88 end
89 else
90 begin
91 SCL <= wr_scl;
92 SDA <= wr_sda;
93 CE <= wr_ce;
94 wr_addr <= 8'h84;
95 wr_data <= 8'h23;
96 wr_en <= 1'b1;
97 end
98 end
99 3'd3 : begin
100 if(rd_done)
101 begin
102 state <= 3'd4;
103 s <= {1'b0,rd_data[6:0]};
104 end
105 else
106 begin
107 SCL <= rd_scl;
108 SDA <= rd_sda;
109 CE <= rd_ce;
110 rd_addr <= 8'h81;
111 rd_en <= 1'b1;
112 state <= 3'd3;
113 end
114 end
115 3'd4 : begin
116 if(rd_done)
117 begin
118 state <= 3'd5;
119 m <= rd_data;
120 end
121 else
122 begin
123 SCL <= rd_scl;
124 SDA <= rd_sda;
125 CE <= rd_ce;
126 rd_addr <= 8'h83;
127 rd_en <= 1'b1;
128 state <= 3'd4;
129 end
130 end
131 3'd5 : begin
132 if(rd_done)
133 begin
134 state <= 3'd6;
135 h <= rd_data;
136 rd_en <= 1'b0;
137 end
138 else
139 begin
140 SCL <= rd_scl;
141 SDA <= rd_sda;
142 CE <= rd_ce;
143 rd_addr <= 8'h85;
144 rd_en <= 1'b1;
145 state <= 3'd5;
146 end
147 end
148 3'd6 : begin
149 if(cnt == t - 1)
150 begin
151 state <= 3'd3;
152 cnt <= 20'd0;
153 end
154 else
155 begin
156 state <= 3'd6;
157 cnt <= cnt + 1'b1;
158 end
159 end
160 endcase
161 end
162
163 endmodule
在控制模块中,我们前三个状态要把时间的初值写进芯片,比如我们写入时分秒,那么我们需要按照手册给出相应的命令。
在这里我们需要解释一下小时的数据格式。BIT7如果为0代表使用的是24小时制,如果为1代表使用的是12小时制。BIT6恒为0。BIT5,如果是12小时制,0代表上午,1代表下午,如果是24小时制,BIT5和BIT4共同组成了小时的十位。BIT3到BIT0为小时的个位。
顶层模块代码如下:
1 module RTC( //real time clock
2
3 input wire clk,
4 input wire rst_n,
5
6 output wire SCL,
7 inout wire SDA,
8 output wire CE,
9
10 output wire [5:0] sel,
11 output wire [7:0] seg
12 );
13
14 wire wr_en;
15 wire [7:0] wr_addr;
16 wire [7:0] wr_data;
17 wire wr_done;
18 wire wr_scl;
19 wire wr_sda;
20 wire wr_ce;
21 wire rd_en;
22 wire [7:0] rd_addr;
23 wire rd_done;
24 wire [7:0] rd_data;
25 wire rd_sda;
26 wire rd_scl;
27 wire rd_ce;
28 wire o_en;
29 wire o_buf;
30 wire [7:0] s;
31 wire [7:0] m;
32 wire [7:0] h;
33
34 rtc_wr rtc_wr_inst(
35
36 .clk (clk),
37 .rst_n (rst_n),
38
39 .wr_en (wr_en),
40 .wr_addr (wr_addr),
41 .wr_data (wr_data),
42
43 .wr_done (wr_done),
44 .wr_scl (wr_scl),
45 .wr_sda (wr_sda),
46 .wr_ce (wr_ce)
47 );
48
49 rtc_rd rtc_rd_inst(
50
51 .clk (clk),
52 .rst_n (rst_n),
53
54 .rd_en (rd_en),
55 .rd_addr (rd_addr),
56 .SDA (SDA),
57
58 .rd_done (rd_done),
59 .rd_data (rd_data),
60 .rd_sda (rd_sda),
61 .rd_scl (rd_scl),
62 .rd_ce (rd_ce),
63 .o_en (o_en)
64 );
65
66 rtc_ctrl rtc_ctrl_inst(
67
68 .clk (clk),
69 .rst_n (rst_n),
70
71 //wr
72 .wr_done (wr_done),
73 .wr_scl (wr_scl),
74 .wr_sda (wr_sda),
75 .wr_ce (wr_ce),
76 .wr_en (wr_en),
77 .wr_addr (wr_addr),
78 .wr_data (wr_data),
79
80 //rd
81 .rd_done (rd_done),
82 .rd_scl (rd_scl),
83 .rd_sda (rd_sda),
84 .rd_ce (rd_ce),
85 .rd_data (rd_data),
86 .rd_en (rd_en),
87 .rd_addr (rd_addr),
88
89 .SCL (SCL),
90 .SDA (o_buf),
91 .CE (CE),
92 .s (s), //数码管数据
93 .m (m),
94 .h (h)
95 );
96
97 assign SDA = (o_en) ? o_buf : 1'hz;
98
99 seven_tube_driver seven_tube_driver_inst(
100
101 .clk (clk),
102 .rst_n (rst_n),
103 .s (s),
104 .m (m),
105 .h (h),
106
107 .sel (sel),
108 .seg (seg)
109 );
110
111 endmodule
在这里需要大家注意的是三态门的编写。
作为输入时,将数据线置为高祖态。
仿真代码如下:
1 `timescale 1ns / 1ps
2
3 module rtc_tb;
4
5 reg clk;
6 reg rst_n;
7
8 wire SCL;
9 wire SDA;
10 wire CE;
11
12 wire [5:0] sel;
13 wire [7:0] seg;
14
15 initial begin
16 clk = 0;
17 rst_n = 0;
18 #105;
19 rst_n = 1;
20 #100000;
21 $stop;
22 end
23
24 always #10 clk = ~clk;
25
26 RTC RTC_inst( //real time clock
27
28 .clk (clk),
29 .rst_n (rst_n),
30
31 .SCL (SCL),
32 .SDA (SDA),
33 .CE (CE),
34
35 .sel (sel),
36 .seg (seg)
37 );
38
39 endmodule
仿真图如下:
前三个状态,分别写入了时分秒等数据,3 4 5三个状态分别是读时分秒的状态,最后一个状态是做的延时,在一秒时间内,读出的数据是没有变化的,因此我们可以减少读操作的频率来降低工作频率。在rd_done信号拉高时,可以看到时分秒都有数据被赋值,及读出正常。
- End -
FPGA技术江湖广发江湖帖
无广告纯净模式,给技术交流一片净土,从初学小白到行业精英业界大佬等,从军工领域到民用企业等,从通信、图像处理到人工智能等各个方向应有尽有,QQ微信双选,FPGA技术江湖打造最纯净最专业的技术交流学习平台。
FPGA技术江湖微信交流群
加群主微信,备注姓名+公司/学校+岗位/专业进群
FPGA技术江湖QQ交流群
备注姓名+公司/学校+岗位/专业进群