FPGA通过读写突发对DS1302时钟的配置&驱动

文章目录

  • 前言
  • 一、pandas是什么?
    • 1、理论原理
      • 1、DS1302实时时钟芯片
        • 1、特性
        • 2、引脚定义
        • 3、有关读写操作的详细内容
          • 1、读写的几个端口信号
          • 2、命令字节
          • 3、突发读写时钟寄存器
          • 4、写保护位
        • 4、数据读写时序&寄存器地址表
          • 1、数据单字节读写时序
          • 2、寄存器地址表
        • 5、数据传输时序的理想状态
          • 1、写操作(主机理想视角)
          • 2、写操作(从机理想视角)
          • 3、读操作(主机理想视角)
          • 4、读操作(从机理想视角)
      • 2、DS1302接口模块
      • 3、DS1302控制模块
      • 4、串口发送控制模块
  • 二、系统设计及模块框图
    • 1、顶层模块框图
    • 2、 ds1302控制模块
    • 3、ds1302接口模块
    • 4、ds1302接口模块
    • 5、RTL视图
  • 三、时序图及状态图
    • 1、DS1302控制模块状态机
    • 2、DS1302接口模块的分频
    • 3、DS1302接口写时序
    • 4、DS1302控制模块
  • 四、仿真波形
    • 1、顶层模块仿真
    • 2、ds1302接口模块仿真
      • 1、SCLK2M分频
      • 2、传输数据的时序
        • 1、写状态
        • 2、读状态
    • 3、ds1302控制模块仿真
      • 1、写状态的控制模块
      • 2、读状态的控制模块
    • 4、串口发送控制模块仿真
  • 五、源码
      • 1、DS1302接口模块、
      • 2、DS1302控制模块
      • 3、串口发送控制模块
      • 4、效果
  • 六、总结
  • 七、参考资料


前言

环境:
1、Quartus18.0
2、vscode
3、板子型号:EP4CE6F17C8
4、实时时钟模块:DS1302
要求:
使用 EP4CE6F17C8开发板驱动 实时时钟模块(DS1302 ),并将配置完后的时钟传回到上位机。我们上电后直接对实时时钟进行配置,在按键按下后,向上位机发送我们的时钟数据。


一、pandas是什么?

1、理论原理

这里我们会先对我们使用的实时时钟器件进行介绍,接着对我们的各模块进行一个概述。

1、DS1302实时时钟芯片

DS1302是由美国一公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等多种功能,是一种实时时钟芯片。

1、特性

1、实时时钟计算秒、分钟

2、支持单字节或多字节(突发模式的操作):这里由于我们的仿真模型是突发模式,为了更好的观察仿真波形,我们选择多字节的突发模式进行配置时钟、读取时钟。

3、简单的三线接口

4、工作温度:(-40~85)

2、引脚定义

FPGA通过读写突发对DS1302时钟的配置&驱动_第1张图片

内部结构图:
FPGA通过读写突发对DS1302时钟的配置&驱动_第2张图片

引脚名 作用
VCC2 主电源(当VCC2小于VCC1时,VCC1为DS1302供电。)
VCC1 备用电池
GND 电源接地
X1,X2 32.768kHz晶振
CE 芯片使能(高有效)
IO 数据输入/输出(双向端口)
SCLK 串行时钟
3、有关读写操作的详细内容
1、读写的几个端口信号

**CE:**首先,这个信号需要我们的mcu产生。只要我们需要对DS1302进行操作,这就需要我们将CE进行拉高。CE具有两个功能。首先,CE打开控制逻辑,允许访问地址/命令序列的移位寄存器。其次,CE信号提供了一种终止单字节或多字节CE数据传输的方法,只要我们在进行任何操作时,CE无效后,均会中止。

SCLK:这个信号同样也是需要我们产生,从设备会从我们的上升沿接收数据,下降沿输出数据,而我们的主机则是上升沿采样。这需要我们合理的设计我们的SCLK与我们的sda的关系,使其得到最好的效果。对于SCLK的周期我们根据下面这张表(这里我选用的是2M的频率):

FPGA通过读写突发对DS1302时钟的配置&驱动_第3张图片
I/O(sda):这是一个三态门,这需要我们对其进行限制,在我们将数据写入到我们的DS1302时,需要使能,而读的时候令其高阻。然而,在我们读操作的情况下,会写入一个读突发,接着就是读取我们的数据,这需要我们注意sda的变化。

2、命令字节

FPGA通过读写突发对DS1302时钟的配置&驱动_第4张图片

这里的第七位必须为1,否则我们的DS1302会被取消使能。第六位用于指定我们是对时钟寄存器进行操作还是对RAM进行操作,1~5用于确定我们需要操作的地址,最低位用于指示我们是读还是写。注:这里我们只用到时钟寄存器所以,我们的第六位默认为0。并且我们这里是小端方式,我们需要先发低位。

举个例子,对时钟操作、秒地址操作、读:

10000001(0x81)
对时钟操作、秒地址操作、写:
10000000(0x80)

3、突发读写时钟寄存器

1、从我们的命令字节可以推测我们的读写是共用的,但是在不同的操作下,数据存储容能力不同。在我们的时钟/日历寄存器中的位置9至31是无效的,而在RAM中,31无效。因此,我们在使用读/写突发操作时,草果上面的寄存器位置是无效的,需要注意。

2、在突发模式下写入时钟寄存器时,必须写入前8个寄存器才能传输数据。然而,当以突发模式写入RAM时,无需写入所有31个字节莱传输数据。无论是否全部31个字节都被写入,写入的每个字节都将被传输到RAM.

4、写保护位

控制寄存器的位7是写保护位。前七位(位 0 到 6)强制为 0,读取时始终读取 0。在对时钟或 RAM 执行任何写入操作之前,位 7 必须为 0。当高电平时,写保护位可防止对任何其他寄存器执行写操作。未定义初始开机状态。因此,在尝试写入设备之前,应清除 WP 位

4、数据读写时序&寄存器地址表
1、数据单字节读写时序

FPGA通过读写突发对DS1302时钟的配置&驱动_第5张图片

手册只给出了单字节读写的时序图,但是我们需要进行突发读写,关于突发读写的时序图,看后面我画的即可。

2、寄存器地址表

FPGA通过读写突发对DS1302时钟的配置&驱动_第6张图片

这张表尤其重要,涵盖了我们需要的所有命令,我们可以通过突发读/写命令,将我们的数据写入或读出,在写入数据时我们根据一些特定的位来控制我们的时钟是否开始计时、采用的是12/24小时制,上午/下午等

5、数据传输时序的理想状态
1、写操作(主机理想视角)

FPGA通过读写突发对DS1302时钟的配置&驱动_第7张图片

这里我们可以看到,我们的data需要在SCLK下降沿更新,并且在第一个SCLK上升沿来临时,提前将我们的数据准备好,为什么说这样是理想的能,看下面的从机视角你就明白了。

2、写操作(从机理想视角)

FPGA通过读写突发对DS1302时钟的配置&驱动_第8张图片

从这里我们可以看到,从机从上升沿对数据进行采样相对稳定。

3、读操作(主机理想视角)

FPGA通过读写突发对DS1302时钟的配置&驱动_第9张图片

同样,在读操作时,前面一字节仍然需要写入,继续按照上面写的理想状态进行操作。后面一字节是我们主机需要接收的,我们主机从上升沿接收,同样也处于我们的数据中间进行采样,较为稳定。那么我们主机在前面一字节需要在CE上升沿及SCLK下降沿更新我们的数据,保证从机能够从中间读数据,后面与读的SCLK的衔接需要我们注意。

4、读操作(从机理想视角)

FPGA通过读写突发对DS1302时钟的配置&驱动_第10张图片

从机前面一字节,从上升沿采样数据,后一字节在下降沿更新我们数据进行输出,这一点手册有提出,ds1302上升沿采样,下降沿输出。

2、DS1302接口模块

这里起初我设置的是字节写/读的接口,后面看到给到是突发读写的仿真模型,突然蒙圈了。好在我里面设计的CE、SCLK等是根据我们的计数器来进行处理,因此我只需要简单修改即可。

修改后的思路是这样的,我们通过控制模块给到我们接口操作的标志信号,读写的持续信号,以及88位的数据,是的88位。因为我们需要发送写保护取消命令、写保护数据、写突发以及写突发后跟的需要我们初始化的数据等11个字节,同样我们在突发读的时候同样给到88个数据,但是有效的只有前8位,后续我们会将sda拉为高阻。在操作标志信号有效,CE有效,产生SCLK,移位将数组数据进行发送。

这样进行处理过后我们需要在接口模块里操作的东西就多了,但是我们控制模块写到就少了。具体的思路看下面:

1、时钟(分频)

首先我们需要给出我们的SCLK(2M),这需要对系统时钟进行奇分频,我们分别用两个奇数器进行奇数,一个上升沿进行计数,一个下降沿进行计数,因此会错开半个时钟周期,接着我们进行或运算即可取到50%的时钟。并且两个计数器的的add条件均为CE有效,这样便同一了我们的CE与我们的SCLK,由于我们的从机是上升沿采用还需要或运算后对SCLK取反,以防止亚稳态。产生的波形会如下图:

在这里插入图片描述

2、CE(使能)

在我们的操作信号有效拉高为1,在我们读或写的bit计数器end时置0,同时给出操作结束信号。由于我们读写的字节不同,因此需要不同的计数器。

3、sda(三态门)

我们在写状态下将我们88位数据写完或读状态下写入读突发后将sda拉为高阻,进入数据接收状态。

需要注意的是在我们的SCLK第一个上升沿就数据位就应该更新为我们需要发送的数据的,否则会错位一个数据,那么这时候我们会利用到我们的CE上升沿,在我们的CE上升沿或者SCLK的下降沿时移位更新我们待发送的数据效果是最好的,因为从机会从上升沿读取数据,切好处于数据的稳定状态。

3、DS1302控制模块

其实接口模块以及进行了大量的操作,我们的控制模块极其简单,只需要在不同状态更新写入的值以及给出读写信号或操作的标志信号即可。思路如下:

我们上电后需要等待20ms,根据我们的初始化标志,未初始化,直接进入我们的写状态,将我们需要配置的时钟直接进行写入配置,在接口模块配置结束后返回操作结束标志进入DONE状态,接着回到IDLE。在IDLE,只要我们的按键信号有效,则进入读状态,同样接口模块配置结束后返回操作结束标志进入DONE状态。在我们状态由READ跳到DONE时,给出uart_vld信号,指示我们的串口发送控制模块更新我们的数据。

4、串口发送控制模块

这里我们需要根据发送过来的数据以及数据有效位更新我们的数据,以及在1s内将我们的数据发送完毕。例如:xxxx年xx月xx日xx时xx分xx秒。通过字节计数器,在不同字节数下给到发送模块不同的数据。

二、系统设计及模块框图

1、顶层模块框图

FPGA通过读写突发对DS1302时钟的配置&驱动_第11张图片

2、 ds1302控制模块

FPGA通过读写突发对DS1302时钟的配置&驱动_第12张图片

3、ds1302接口模块

FPGA通过读写突发对DS1302时钟的配置&驱动_第13张图片

4、ds1302接口模块

FPGA通过读写突发对DS1302时钟的配置&驱动_第14张图片

5、RTL视图

FPGA通过读写突发对DS1302时钟的配置&驱动_第15张图片

三、时序图及状态图

1、DS1302控制模块状态机

FPGA通过读写突发对DS1302时钟的配置&驱动_第16张图片

2、DS1302接口模块的分频

FPGA通过读写突发对DS1302时钟的配置&驱动_第17张图片

这里和前面说的一样,分别利用两个计数器,分别采用上升沿、下降沿进行计数,得到2M的50%的SCLK,仅在我们CE有效时产生。

3、DS1302接口写时序

FPGA通过读写突发对DS1302时钟的配置&驱动_第18张图片

这里我们根据上面的时序图得知,在操作命令wr_vld有效后,我们拉高CE,直到我们的比特计数器计满88个将其拉低。我们的三态门在CE有效时,在CE上升沿或SCLK下降沿更新数据,保证我们的从机采集的数据位于数据中心。在CE拉高过程,sda处于输出状态,在比特计数器计满最大值后,CE拉低,SCLK不再产生,SDA位于高阻态,数据写入结束。由于读过程与写类似,这里不再赘述,不同点就是我们需要在读突发时,在比特计数器计满一字节,将其置为高阻,接收数据。

4、DS1302控制模块

FPGA通过读写突发对DS1302时钟的配置&驱动_第19张图片

我们的控制模块主要就是通过按键、接口的操作结束标志信号以及初始化信号来控制我们的状态跳转。我们上电后需要等待20ms,根据我们的初始化标志,未初始化,直接进入我们的写状态,将我们需要配置的时钟直接进行写入配置,在接口模块配置结束后返回操作结束标志进入DONE状态,接着回到IDLE。在IDLE,只要我们的按键信号有效,则进入读状态,同样接口模块配置结束后返回操作结束标志进入DONE状态。在我们状态由READ跳到DONE时,给出uart_vld信号,指示我们的串口发送控制模块更新我们的数据。

四、仿真波形

1、顶层模块仿真

FPGA通过读写突发对DS1302时钟的配置&驱动_第20张图片

在我们上电后,无需任何操作,直接进行写入初始化配置时钟。

FPGA通过读写突发对DS1302时钟的配置&驱动_第21张图片

在写入结束后,我们按键标志信号有效,此时写入突发读命令,这是方框内我们的数据不进行更新的,写入结束后开始更新数据,更新后的输入如上,一会我们两秒后再进行读取,查看我们的时间是否有变化。

FPGA通过读写突发对DS1302时钟的配置&驱动_第22张图片

对比上图我们发现秒钟增加了,仿真通过。

2、ds1302接口模块仿真

1、SCLK2M分频

FPGA通过读写突发对DS1302时钟的配置&驱动_第23张图片

由上面我们可以知道,我们成功的在系统时钟下计数了25次,将50M时钟分频成了2M,并且占空比为50%。

2、传输数据的时序

1、写状态

FPGA通过读写突发对DS1302时钟的配置&驱动_第24张图片

上面这里的仿真是需要我们去关注的一个地方,就是我们需要在SCLK上升沿来临之时将我们要发送的数据进行更新,否则会引起数据错乱。我们低位先发,首先发的应该是E,为0,仿真的第一个采样结果也为0,仿真通过。

FPGA通过读写突发对DS1302时钟的配置&驱动_第25张图片

通过仔细对比我们发现,按照低位先发的情况,我们的数据全部成功发送,并且我们在写过程中,计数突发读的比特计数一直为0,只有在写突发的计数器达到最大值时,CE被拉低,同时产生opera_done标志信号。

2、读状态

FPGA通过读写突发对DS1302时钟的配置&驱动_第26张图片

我们从上面的仿真可以看到,在我们的wr_vld(按键读)有效后我们的CE被拉高,此时我们的wr为1处于读状态。此时我们的读突发计数器开始工作,写突发计数器维持0。并且在我们的读突发命令后,我们的data数据才开始接收,并且在突发读操作结束后的时间数据,与我们写入的一致。这时,我们的比特计数器2将我们的CE拉低,同时产生opera_done标志信号。

3、ds1302控制模块仿真

1、写状态的控制模块

FPGA通过读写突发对DS1302时钟的配置&驱动_第27张图片

控制模块的信号就比较少了,上电后20ms跳到写状态,等待我们的接口返回操作结束信号,跳到IDLE状态,等待下一次的wr_vld信号。

2、读状态的控制模块

FPGA通过读写突发对DS1302时钟的配置&驱动_第28张图片

同样按键按下后,我们跳转到读状态,等待我们的接口返回操作结束信号,跳到IDLE状态,等待下一次的wr_vld信号。并且这时,我们的数据以及变为了突发命令。

4、串口发送控制模块仿真

FPGA通过读写突发对DS1302时钟的配置&驱动_第29张图片

根据上面的串口发送数据分析知,我们串口连续发送23、10的数据,符合我们从ds1302读出的数据。上面数据,具体为2023年、10月,后续的数据也是正常的,仿真通过。
FPGA通过读写突发对DS1302时钟的配置&驱动_第30张图片
发送模块发送开始直到结束大致有1s左右,符合我们对发送速度的控制,仿真通过。

五、源码

1、DS1302接口模块、

module ds1302_intf (
    input               clk         ,//系统时钟
    input               rst_n       ,//系统复位
    input               wr_vld      ,//操作有效标志 
    input [87:0]        din         ,//需要写入ds1302的数据
    input               wr          ,//标志读写 0:写 1:读

    output reg [55:0]   data        ,//读回的数据
    output              opera_done  ,//CE拉低 end_cnt_bit || end_cnt_bit2;//两个字节操作结束
 
    output reg          CE          ,//使能工作信号 高有效
    output              sclk        ,//2M的时钟 CE有效产生
    inout               sda          //三态门,不同SCLK给出不同数据
);

//变量
    reg          [04:00]    cnt_clk         ; //Counter 
    wire                    add_cnt_clk     ; //Counter Enable
    wire                    end_cnt_clk     ; //Counter Reset 
    reg          [04:00]    cnt_clk2         ; //Counter 
    wire                    add_cnt_clk2     ; //Counter Enable
    wire                    end_cnt_clk2     ; //Counter Reset 

    wire                    clk_pos          ;
    wire                    clk_neg          ;

    wire                    clk_2M           ;

    reg          [07:00]    cnt_bit         ; //Counter 
    wire                    end_cnt_bit     ; //Counter Reset   需要发送16bit

    reg                     dout            ;//写时发送的数据

    reg          [07:00]    cnt_bit2         ; //Counter 
    wire                    end_cnt_bit2     ; //Counter Reset   需要发送16bit
    reg                     dout2           ;//读时的数据

    reg          [07:00]    cnt_bit3         ; //Counter 用于写保护
    wire                    end_cnt_bit3     ; //Counter Reset   需要发送16bit
    reg                     dout3           ;//读时的数据

    reg          [1:0]      sclk_r          ;
    reg          [1:0]      CE_r            ;
    wire                    sclk_neg        ;
    wire                    CE_pos          ;

//仿真测试 2s
    parameter      max_1s = 50_000_000,
                   max_3s = 3;
    reg          [26:00]    cnt_1s         ; //Counter 
    wire                    add_cnt_1s     ; //Counter Enable
    wire                    end_cnt_1s     ; //Counter Reset 
    reg          [03:00]    cnt_3s         ; //Counter 
    wire                    add_cnt_3s     ; //Counter Enable
    wire                    end_cnt_3s     ; //Counter Reset 

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt_1s <= 27'd0;
        end
        else if(add_cnt_1s)begin
            if(end_cnt_1s)begin
                cnt_1s <= 27'd0;
            end
            else begin
                cnt_1s <= cnt_1s + 1'b1;
            end
        end
    end

    assign add_cnt_1s = 1'b1;
    assign end_cnt_1s = add_cnt_1s && cnt_1s == max_1s - 1;

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt_3s <= 4'd0;
        end
        else if(add_cnt_3s)begin
            if(end_cnt_3s)begin
                cnt_3s <= 4'd0;
            end
            else begin
                cnt_3s <= cnt_3s + 1'b1;
            end
        end
    end

    assign add_cnt_3s = end_cnt_1s;
    assign end_cnt_3s = add_cnt_3s && cnt_3s == max_3s - 1;
//测试

    assign opera_done = end_cnt_bit || end_cnt_bit2;//两个字节操作结束

//时钟分频 2M
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt_clk <= 5'd0;
        end
        else if(add_cnt_clk)begin
            if(end_cnt_clk)begin
                cnt_clk <= 5'd0;
            end
            else begin
                cnt_clk <= cnt_clk + 1'b1;
            end
        end
        else begin
            cnt_clk <= 5'd0;
        end
    end

    assign add_cnt_clk = CE;
    assign end_cnt_clk = add_cnt_clk && cnt_clk == 24 ;
    assign clk_pos = (cnt_clk < 5'd12) ? 1'b1 : 1'b0;

    always @(negedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt_clk2 <= 5'd0;
        end
        else if(add_cnt_clk2)begin
            if(end_cnt_clk2)begin
                cnt_clk2 <= 5'd0;
            end
            else begin
                cnt_clk2 <= cnt_clk2 + 1'b1;
            end
        end
        else begin
            cnt_clk2 <= 5'd0;
        end
    end

    assign add_cnt_clk2 = CE;
    assign end_cnt_clk2 = add_cnt_clk2 && cnt_clk2 == 24 ;
    assign clk_neg = (cnt_clk2 < 5'd12) ? 1'b1 : 1'b0;

    assign clk_2M = ~(clk_neg || clk_pos);//在CE有效时输出

    //读写有效,拉高
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            CE <= 1'b0;
        end
        else if(wr_vld)begin
            CE <= 1'b1;
        end
        else if((end_cnt_bit && wr == 1'b0) || (end_cnt_bit2 && wr == 1'b1))begin
            CE <= 1'b0;
        end
        else begin
            CE <= CE;
        end
    end

    //赋值参考时钟
    assign sclk = CE ? clk_2M : 1'b0;

    //打拍
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            sclk_r <= 1'b0;
            CE_r   <= 1'b0;
        end
        else begin
            sclk_r <= {sclk_r[0],sclk};
            CE_r   <= {CE_r  [0],CE}  ;
        end
    end

    assign sclk_neg =  ~sclk_r[0] && sclk_r[1];
    assign CE_pos   =  CE_r  [0] && ~CE_r  [1];

    //写
    always @(negedge clk or negedge rst_n) begin// or posedge CE
        if(!rst_n)begin
            dout <= 1'b0;
            cnt_bit <= 8'd0;
        end
        else begin
            if(end_cnt_bit || (CE == 1'b0))begin
                cnt_bit <= 8'd0;
            end
            if((sclk_neg || CE_pos)&&(!wr)  )begin
                    dout <= din[cnt_bit];
                    cnt_bit <= cnt_bit + 1'b1;
            end
        end
    end

    //读
    always @(negedge clk or negedge rst_n) begin// or posedge CE
        if(!rst_n)begin
            dout2 <= 1'b0;
            cnt_bit2 <= 8'd0;
        end
        else begin
            if(end_cnt_bit2 || (CE == 1'b0))begin
                cnt_bit2 <= 8'd0;
            end
            else if((sclk_neg || CE_pos)&& wr )begin
                    dout2 <= din[cnt_bit2];
                    cnt_bit2 <= cnt_bit2 + 1'b1;
            end
        end
    end

    //写保护
    always @(posedge sclk or negedge rst_n) begin
        if(!rst_n)begin
            data <= 8'd0;
        end
        else if((cnt_bit2 > 8'd7)&&wr&&CE)begin//接收数据
            data <= {sda,data[55:1]};
        end
    end

    assign end_cnt_bit = cnt_bit > 8'd88;//写
    assign end_cnt_bit2 = cnt_bit2 > 8'd64;

    assign sda = ((wr == 1'b0 && CE)||(wr == 1'b1 && cnt_bit2 <= 5'd8)) ? ((wr == 1'b0 && CE) ? dout: dout2) : 1'bz;
endmodule

2、DS1302控制模块

module ds1302_ctrl (
    input           clk             ,//系统时钟
    input           rst_n           ,//系统复位
    input           rd_key          ,//消抖后的按键标志信号
    input           opera_done      ,//读/写操作结束

    output          wr_vld          ,//指示开始操作(读/写)标志
    output [87:00]  dout            ,//需要写的数据 写需要88,读需要8,其余已经将sda拉高阻
    output          wr              ,//指示读或写 0:写 1:读
    output          uart_vld         //指示我们的串口发送有效
);
    
//变量
    parameter IDLE  = 4'b0001,
              WRITE = 4'b0010,
              READ  = 4'b0100,
              DONE  = 4'b1000;

    parameter max_20ms = 1_000_000;//20ms

    reg [3:0] state_c,state_n;

    reg          [19:00]    cnt_20ms         ; //Counter 
    wire                    add_cnt_20ms     ; //Counter Enable
    wire                    end_cnt_20ms     ; //Counter Reset 

    wire                    IDLE2WRITE      ;
    wire                    IDLE2READ       ;
    wire                    WRITE2DONE      ;
    wire                    READ2DONE       ;
    wire                    DONE2IDLE       ; 

    reg                     initflag        ;//标志是否初始化时间

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            state_c <= IDLE;
        end
        else begin
            state_c <= state_n;
        end
    end

    always @(*) begin
        if(!rst_n)begin
            state_n = IDLE;
        end
        case(state_c)
            IDLE : begin
                if(IDLE2WRITE)begin
                    state_n = WRITE;
                end
                else if(IDLE2READ)begin
                    state_n = READ;
                end
                else begin
                    state_n = IDLE;
                end
            end
            WRITE: begin
                if(WRITE2DONE)begin
                    state_n = DONE;
                end
                else begin
                    state_n = WRITE;
                end
            end
            READ : begin
                if(READ2DONE)begin
                    state_n = DONE;
                end
                else begin
                    state_n = READ;
                end
            end
            DONE : begin
                state_n = IDLE;
            end
            default: state_n = IDLE;
        endcase
    end

    assign IDLE2WRITE = state_c == IDLE  && (end_cnt_20ms);//上电20ms且初始化flag为低
    assign IDLE2READ  = state_c == IDLE  && (rd_key);//读按键按下
    assign WRITE2DONE = state_c == WRITE && (opera_done);//接口操作结束
    assign READ2DONE  = state_c == READ  && (opera_done);//接口操作结束
    assign DONE2IDLE  = state_c == DONE  && 1'b1;    

    //20ms计数
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt_20ms <= 20'd0;
        end
        else if(add_cnt_20ms)begin
            if(end_cnt_20ms)begin
                cnt_20ms <= 20'd0;
            end
            else begin
                cnt_20ms <= cnt_20ms + 1'b1;
            end
        end
        else begin
            cnt_20ms <= 20'd0;
        end
    end

    assign add_cnt_20ms = (state_c == IDLE) && (~initflag); 
    assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == max_20ms - 1'b1;

    //标志是否初始化时间
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            initflag <= 1'b0;
        end
        else if(IDLE2WRITE)begin
            initflag <= 1'b1;
        end
        else begin
            initflag <= initflag;
        end
    end

    assign wr_vld = IDLE2WRITE || IDLE2READ;
    assign dout   = (state_c == WRITE) ? 88'h0023011015130101BE008E : 88'h00BF;    
    assign wr     = (state_c == WRITE) ? 1'b0 : 1'b1;    
    assign uart_vld = READ2DONE;//读结束时的数据刚好读完,使能发送
endmodule

3、串口发送控制模块

module uart_ctrl (
    input           clk         ,//系统时钟
    input           rst_n       ,//系统复位
    input  [55:0]   din         ,//需要发送的时间、日期
    input           uart_vld    ,//数据更新、使能发送

    output          uart_tx     //串口发送
);

    parameter clk_50M = 50_000_000;

    wire tx_done;

    reg     [23:0]            cnt_clk  ;
    wire                      add_cnt_clk;
    wire                      end_cnt_clk;

    reg     [4:0]             cnt_bit  ;
    wire                      add_cnt_bit;
    wire                      end_cnt_bit;

    wire                      flag/* synthesis keep="1" */;
    reg     [7:0]             data/* synthesis keep="1" */;

    reg     [55:00]           data_r;

    reg                       send_en;


    assign flag = cnt_clk == (clk_50M/27);//1s发送1次数据

    //数据有效时、此时应该更新数据
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            data_r <= 56'd0;
        end
        else if(uart_vld) begin
            data_r <= din;
        end
    end

    //使能发送时间、日历
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            send_en <= 1'b0;
        end
        else if(uart_vld)begin
            send_en <= 1'b1;
        end
        else if(end_cnt_bit)begin
            send_en <= 1'b0;
        end
        else begin
            send_en <= send_en;
        end
    end


    //时钟计数器
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt_clk <= 24'd0;
        end
        else if(add_cnt_clk)begin
            if(end_cnt_clk)begin
                cnt_clk <= 24'd0;
            end
            else begin
                cnt_clk <= cnt_clk + 1'b1;
            end
        end
        else begin
            cnt_clk <= 24'd0;
        end
    end

    assign add_cnt_clk = send_en;//发送一次,需要发送过程的标志信号
    assign end_cnt_clk = add_cnt_clk && cnt_clk == (clk_50M/27);

    //比特计数器
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt_bit <= 5'd0;
        end
        else if(add_cnt_bit)begin
            if(end_cnt_bit)begin
                cnt_bit <= 5'd0;
            end
            else begin
                cnt_bit <= cnt_bit + 1'b1;
            end
        end
        else begin
            cnt_bit <= cnt_bit;
        end
    end

    assign add_cnt_bit = tx_done;
    assign end_cnt_bit = add_cnt_bit && cnt_bit == 5'd27;

    //根据计数器的值发送相应位置的数据
    always @(*) begin
        case (cnt_bit)
            1 : data = 50; // 2
            2 : data = 48; // 0
            3 : data = data_r[55:52]+ 48;
            4 : data = data_r[51:48]+ 48;//47:40为星期、不要
            5 : data = 8'hC4; // 年 C4 EA   月 D4 C2 日 C8 D5 时 CA B1 分 B7 D6 秒 C3 EB 
            6 : data = 8'hEA;
            7 : data = data_r[39:36]+ 48;
            8 : data = data_r[35:32]+ 48;
            9 : data = 8'hD4;//月 D4 C2
            10: data = 8'hC2;
            11: data = data_r[31:28]+ 48;
            12: data = data_r[27:24]+ 48;
            13: data = 8'hC8;//日 C8 D5
            14: data = 8'hD5;
            15: data = data_r[23:20]+ 48;
            16: data = data_r[19:16]+ 48;
            17: data = 8'hCA;//时 CA B1
            18: data = 8'hB1;
            19: data = data_r[15:12]+ 48;
            20: data = data_r[11:08]+ 48;
            21: data = 8'hB7;//分 B7 D6
            22: data = 8'hD6;
            23: data = data_r[07:04]+ 48;
            24: data = data_r[03:00]+ 48;
            25: data = 8'hC3;//秒 C3 EB 
            26: data = 8'hEB;
            27: data = "\n";
            default: data = 0;
        endcase
    end
    uart_tx uart_tx_inst(
    	/*input      */                 .Clk        (clk    ), //system clock 50MHz
    	/*input      */                 .Rst_n      (rst_n  ), //reset, low valid
    
    	/*input                     */  .tx_en  	(flag   ), //发送使能
    	/*input              [07:00]*/  .tx_data    (data   ), //待发送数据
    	/*output    reg             */  .txd  	    (uart_tx), //uart 数据发送端口 
    	/*output             		*/	.tx_done    (tx_done),  //一帧数据发送完成标志
    	/*output 					*/	.tx_busy    ()
    );    
endmodule

这里就主要贴这三个模块,全部的工程在结尾。

4、效果

DS1302

我们可以看到时间是走的,但是串口前面多发了\0,当时没注意,应该是串口0多发了,但我们的重点是对DS1302操作。


六、总结

我在写ds1302的时候,发现鲜有人写这个,然后就说总结一下吧。我的方法不是很好,最初写的是字节操作的,但是为了使用仿真模型,改成了突发模式,其实这钟方式更简单,接口模块全干了,控制模块就不用写很多。下面的源码中并没有仿真文件只有工程和源码,因为仿真模型不是我写的就不放了。

七、参考资料

1、一天一个设计实例-FPGA和实时时钟芯片DS1302
2、ds1302源码

你可能感兴趣的:(FPGA,fpga开发)