FPGA:时钟模块设计-RTC实时时钟芯片DS1302

前言

首先我要明确,这个RTC是在干什么,通过查阅DS1302芯片手册,我知道了,要想使用这块芯片,我需要用FPGA生成3个信号,然后用DS1302去实现具体功能,需要实现什么功能我先不用关心,现在需要关心的是怎么使用FPGA生成这3条信号

补充:
在读操作的测试文件部分学到了wait、force的用法

正文

一、写操作

在这里插入图片描述
通过观察发现,我需要使用FPGA生成3条信号:复位、串行时钟、输入/输出
:当设计很多计数器时,先生成变化快的计数器,即小计数器先生成

1.1 SCK信号

SCK时钟信号实际是对系统时钟进行分频,DS1302不需要50M这么快的变化频率,因此,这里需要用一个计数器对SCK的周期进行计数,我假设对系统时钟进行4分频,于是需要设计一个0-3的计数器。计数器要从CE被拉高的瞬间开始计数,通过观察,在cnt_4[0]=1时,SCK进行翻转,至此SCK信号生成完毕
在这里插入图片描述

1.2 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数据信号生成完毕

1.3 复位信号

CE信号最好生成,只要状态不是IDLE就让CE保持高电平

1.4 设计文件

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

1.5 测试文件

`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

二、读操作

在这里插入图片描述
大体上和写操作是一样的,不同点就在于,读操作的第二个字节是从DS1302芯片中读数传给FPGA,

2.1 inout双向IO

于是IO端口在第一个字节是输出端,在第二个字节就是输入端了,这里就存在一个既能作为输入也能作为输出的端口,因为不知道如何实现这个功能,于是去网上搜,最后知道人家有一个专有名词,叫:双向端口。定义inout实现功能,于是我又去查这个inout是怎么用的,看了不少文章,有的写得太多了,没看明白,随后找到特权同学这篇:特权同学inout的用法

我理解的inout的用法如下:
1.在模块开始时定义一个inout端口

inout        IO  

2.用条件判断语句给端口赋值
当端口被赋值为高阻态就表明此时端口为输入端

assign IO = io_control ? data[0] : 1'bz    ;

其中,io_control =1时,IO为输出端;io_control =0时,IO为输入端,
data就是需要在IO上传输的数据

3.根据data在不同状态的值,就可以得到最后的IO信号

always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)
    data <= 8'd0;
else if(state == IDLE && next_state == DRR)
    data <= ADDRESS_8;
else if(cnt_4 == 2'd3 && state == DRR)
    data <= {1'b0,data[7:1]};
else if(cnt_4 == 2'd3 && state == READ)
    data <= {IO,data[7:1]};
end

2.2 设计文件

module r_ctrl
(
input        clk    ,
input        rst_n ,
input        key    ,

output            CE        ,
output    reg    SCK    ,
output    [7:0]SAVE    ,
output     reg        done    ,
inout                IO        

);
parameter    IDLE    =    3'b001    ,
                DRR    =    3'b010    ,
                READ  =    3'b100;
                
parameter    ADDRESS_8 = 8'b0010_1001;

                
reg        [2:0]        state            ;
reg        [2:0]        next_state    ;

// 计满自动清0
reg        [1:0]        cnt_4            ;
reg        [2:0]        cnt_8            ;    

reg [7:0] data;

// 双向IO控制端
reg        io_control        ;

// 状态机                
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 == 3'd7)&& (cnt_4 == 2'd3) )
        next_state <= READ;
    READ:
        if((cnt_8 == 3'd7)&& (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 <= 8'd0;
else if(state == IDLE && next_state == DRR)
    data <= ADDRESS_8;
else if(cnt_4 == 2'd3 && state == DRR)
    data <= {1'b0,data[7:1]};
else if(cnt_4 == 2'd3 && state == READ)
    data <= {IO,data[7:1]};
end

always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)
done <= 1'd0;
else if(state == READ && next_state == IDLE)
done <= 1'd1;
else 
done <= 1'd0;
end

assign SAVE = data;

assign IO = io_control ? data[0] : 1'bz        ;

always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)
io_control <= 1'b0;
else if(state == DRR)
io_control <= 1'b1;// inout为输出端口
else 
io_control <= 1'b0;
end


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);

// 这个计数器用来计数SCK的时钟周期的大小
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)
    cnt_4 <= 2'b0;
else if(state != IDLE)
    cnt_4 <= cnt_4+2'b1;
end

always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0)
    cnt_8 <= 3'b0;
else if(cnt_4 == 2'd3) begin// 此处cnt_4=3时,cnt_16+1;和IO数据下降沿更新同时改变,这样就能计数传了多少个数了
    cnt_8 <= cnt_8+3'd1;
    if((cnt_8 == 3'd7))
        cnt_8 <= 3'b0;
end
end
endmodule

2.3 测试文件

`timescale 1ns/1ns

module tb_r_ctrl;

reg clk;
reg rst_n ;
reg key;

wire CE;
wire SCK;
wire IO;
wire [7:0] SAVE;
wire done;

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;

wait(r_ctrl_inst.state == r_ctrl_inst.READ)
force IO = 1'b0;
@ (negedge SCK)
force IO = 1'b0;
@ (negedge SCK)
force IO = 1'b1;
@ (negedge SCK)
force IO = 1'b0;
@ (negedge SCK)
force IO = 1'b1;
end

always #10 clk = ~ clk;

r_ctrl r_ctrl_inst
(
.clk(clk)        ,
.rst_n(rst_n)  ,
.key(key)        ,

.CE(CE)            ,
.SCK(SCK)        ,
.SAVE(SAVE)        ,
.done(done)        ,
.IO(IO)        

);

endmodule
  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值