【时钟模块设计】-RTC实时时钟芯片DS1302 写操作
分析
首先我要明确,这个RTC是在干什么,通过查阅DS1302芯片手册,我知道了,要想使用这块芯片,我需要用FPGA生成3个信号,
然后用DS1302去实现具体功能,需要实现什么功能我先不用关心,现在需要关心的是怎么使用FPGA生成这3条信号
接下来我需要把问题进行拆解,先考虑RTC的写操作,即第二个时序图
通过观察发现,我需要使用FPGA生成3条信号:复位、串行时钟、输入/输出
注:当设计很多计数器时,先生成变化快的计数器,即小计数器先生成
分析SCK信号
SCK时钟信号实际是对系统时钟进行分频,DS1302不需要50M这么快的变化频率
因此,这里需要用一个计数器对SCK的周期进行计数,我假设对系统时钟进行4分频,于是需要设计一个0-3的计数器
计数器要从CE被拉高的瞬间开始计数,通过观察,在cnt_4[0]=1时,SCK进行翻转
至此SCK信号生成完毕
分析I/O信号
从图上观察知,IO信号在CE被拉高瞬间就开始传输数据了,并且在SCK的下降沿更新数据
此时我们已经有了一个cnt_4计数器,需要借助这个计数器生成一个cnt_16计数器,用来记录已经传输的数据位数
由于IO信号的数据是在SCK的下降沿进行更新的,于是就在SCK下降沿对cnt_16进行+1操作,即当cnt_4=3时,对cnt_16+1,表示传完一个数据
但这个cnt_16只有一个功能:用来记录传输数据是否传完,如果16位传完了,状态就跳转到IDLE,就停止传输数据,
对于IO信号本身的采样和更新,到目前为止还没有考虑
接下来就考虑如何得到IO信号:
1.由于在写操作中,两个字节都是进行写操作,不涉及IO转换,于是将2个字节看成一个状态:DRR,
2.设计状态机:
当外部有按键输入,就开始传输数据
当数据位数计数器cnt_16=15,且时钟计数器cnt_4=3就代表16位数据传输完成,可以进入空闲状态了
3.接下来就是IO信号的设计难点了,图中显示,在CE拉高瞬间数据就已经开始传输了
那这个时候有两种思路:
- 使用CE上升沿检测,当检测到CE上升沿,立马更新IO信号
- 使用状态机,当前状态=IDLE并且下一个状态=DRR
此处选用状态机方法:
上面使用状态机,只是判断IO信号第一个数据的传输,但是后面的数据位就没有状态机上面的判断条件
于是有了下面这个写法,
注:这个写法是一个大佬告诉我的,不得不说,真的很巧妙
always @(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) data <= 16'd0; else if(state == IDLE && next_state == DRR) data <= {W_DATA,ADDRESS_8}; else if(cnt_4 == 2'd3) data <= {1'b0,data[15:1]}; end assign IO = data[0];
巧妙1:声明一个data中间量,然后用组合逻辑把data给IO
巧妙2:在状态机判断处,将要传输的2个字节数据拼接,data变化瞬间,组合逻辑的IO同时改变,实现了CE拉高瞬间,传输data[0]
巧妙3:在SCK下降沿(即cnt_4=3)进行右移操作,这样每次data一改变,IO就同时改变
注:IO传输数据时,按照先传低位,再传高位的顺序
至此IO数据信号生成完毕
分析CE信号
CE信号最好生成,只要状态不是IDLE就让CE保持高电平
程序
一、设计文件
module w_ctrl_02 ( input clk , input rst_n , input key , output CE , output reg SCK , output IO ); parameter IDLE = 2'b01 , DRR = 2'b10 ; parameter ADDRESS_8 = 8'b0010_1001,//8位地址 W_DATA = 8'b0101_0011;//8位数据 reg [2:0] state ; reg [2:0] next_state ; // 计满自动清0 reg [1:0] cnt_4 ; reg [3:0] cnt_8 ; reg [15:0] data; // 状态机 always @(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) state <= IDLE; else state <= next_state; end always @(*) case(state) IDLE: if(key == 1'b1) next_state <= DRR; DRR: if((cnt_8 == 4'd15)&& (cnt_4 == 2'd3) ) next_state <= IDLE; default:next_state <= IDLE; endcase always @(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) data <= 16'd0; else if(state == IDLE && next_state == DRR) data <= {W_DATA,ADDRESS_8}; else if(cnt_4 == 2'd3) data <= {1'b0,data[15:1]}; end assign IO = data[0]; always @(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) SCK <= 1'b0; else if(cnt_4[0]) SCK <= ~SCK; end assign CE = (state != IDLE); always @(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) cnt_4 <= 2'b0; //else if(state != IDLE) else if(CE == 1'd1) cnt_4 <= cnt_4+2'b1; end always @(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) cnt_8 <= 4'b0; else if(cnt_4 == 2'd3) begin cnt_8 <= cnt_8+1'b1; if((cnt_8 == 4'd15)) cnt_8 <= 4'b0; end end endmodule
二、测试文件
`timescale 1ns/1ns module tb_w_ctrl; reg clk; reg rst_n ; reg key; wire CE; wire SCK; wire IO; initial begin clk = 1'b0; rst_n = 1'b0; key = 1'b0; #10 rst_n =1'b1; #10 key = 1'b1; #20 key = 1'b0; end always #10 clk = ~ clk; w_ctrl_02 w_ctrl_02_inst ( .clk(clk) , .rst_n(rst_n) , .key(key) , .CE(CE) , .SCK(SCK) , .IO(IO) ); endmodule
三、波形图