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

news2025/4/7 21:50:37

文章目录

  • 前言
  • 一、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、引脚定义

在这里插入图片描述

内部结构图:
在这里插入图片描述

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

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

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

在这里插入图片描述
I/O(sda):这是一个三态门,这需要我们对其进行限制,在我们将数据写入到我们的DS1302时,需要使能,而读的时候令其高阻。然而,在我们读操作的情况下,会写入一个读突发,接着就是读取我们的数据,这需要我们注意sda的变化。

2、命令字节

在这里插入图片描述

这里的第七位必须为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、数据单字节读写时序

在这里插入图片描述

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

2、寄存器地址表

在这里插入图片描述

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

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

在这里插入图片描述

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

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

在这里插入图片描述

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

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

在这里插入图片描述

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

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

在这里插入图片描述

从机前面一字节,从上升沿采样数据,后一字节在下降沿更新我们数据进行输出,这一点手册有提出,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、顶层模块框图

在这里插入图片描述

2、 ds1302控制模块

在这里插入图片描述

3、ds1302接口模块

在这里插入图片描述

4、ds1302接口模块

在这里插入图片描述

5、RTL视图

在这里插入图片描述

三、时序图及状态图

1、DS1302控制模块状态机

在这里插入图片描述

2、DS1302接口模块的分频

在这里插入图片描述

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

3、DS1302接口写时序

在这里插入图片描述

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

4、DS1302控制模块

在这里插入图片描述

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

四、仿真波形

1、顶层模块仿真

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

2、ds1302接口模块仿真

1、SCLK2M分频

在这里插入图片描述

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

2、传输数据的时序

1、写状态

在这里插入图片描述

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

在这里插入图片描述

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

2、读状态

在这里插入图片描述

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

3、ds1302控制模块仿真

1、写状态的控制模块

在这里插入图片描述

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

2、读状态的控制模块

在这里插入图片描述

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

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

在这里插入图片描述

根据上面的串口发送数据分析知,我们串口连续发送23、10的数据,符合我们从ds1302读出的数据。上面数据,具体为2023年、10月,后续的数据也是正常的,仿真通过。
在这里插入图片描述
发送模块发送开始直到结束大致有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源码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1099915.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

✔ ★【备战实习(面经+项目+算法)】 10.16学习时间表(总计学习时间:5h)

✔ ★【备战实习&#xff08;面经项目算法&#xff09;】 坚持完成每天必做如何找到好工作1. 科学的学习方法&#xff08;专注&#xff01;效率&#xff01;记忆&#xff01;心流&#xff01;&#xff09;2. 每天认真完成必做项&#xff0c;踏实学习技术 认真完成每天必做&…

C++指针解读(10)-- 动态内存分配

栈和堆是进程内存空间中非常重要的2块区域。栈用于存放程序临时创建的局部变量&#xff1b;堆用于存放进程运行中被动态分配的内存段。当进程调用malloc等函数分配内存时&#xff0c;新分配的内存就被动态添加到堆上&#xff1b;当利用free等函数释放内存时&#xff0c;被释放的…

虚拟机和Windows共享文件的几种方式(共享文件夹、网络映射、Winscp)

1、共享文件夹 在虚拟机的“/mnt/hgfs/”目录下能看到和Windows共享的文件&#xff1b; 2、网络映射 2.1、安装samba服务器 #安装samba服务器 apt-get install samba samba-common#添加samba账户 smbpasswd -a ckl passwd:1232.2、设置网络映射 3、Winscp共享文件 3.1、Wins…

snk-给github界面加一个有趣的动画

How to enable GitHub Actions on your Profile README for a snake-eating contribution graph &#x1f40d; - DEV Community Platane/Platane (github.com) ① 创建New repository 名字和你自己的Github 用户名一样。 ② 创建之后&#xff0c;再这个Zero-coder仓库下创建…

[C国演义] 第十五章

第十五章 最长湍流子数组环绕字符串中唯⼀的⼦字符串 最长湍流子数组 力扣链接 子数组 ⇒ dp[i]的含义: 以arr[i] 结尾的所有子数组中的最长湍流子数组的长度 子数组 ⇒ 状态转移方程根据 最后一个位置来划分&#x1f447;&#x1f447;&#x1f447; 初始化: 都初始化为…

任务调度框架-如何实现定时任务+RabbitMQ事务+手动ACK

任务调度框架 Java中如何实现定时任务&#xff1f; 比如&#xff1a; 1.每天早上6点定时执行 2.每月最后一个工作日&#xff0c;考勤统计 3.每个月25号信用卡还款 4.会员生日祝福 5.每隔3秒&#xff0c;自动提醒 10分钟的超时订单的自动取消&#xff0c;每隔30秒或1分钟查询…

Gson反序列化原理

前言 序列化和反序列化是日常工作中经常使用的工具&#xff0c;一般用于对象的持久化保存或者对象的网络传输&#xff0c;一般有两种情况&#xff0c;一种是对象本身实现了Serializable接口&#xff0c;这种情况下可以利用jdk自带的功能或者Kryo等这种封装好的序列化反序列化工…

10.Linear Map transformation rules

线性映射 从一个基底到另一个基底 所遵循的转换规则。 假设&#xff1a; 由一个矩阵给出的线性映射在这&#xff0c;并且是在基底e上表示&#xff0c; 该线性映射将e1变成0.5个e1&#xff0c;将e2变成2个e2&#xff1b; 假设有个向量V&#xff0c;其分量是【1&#xff0c;1…

福昕阅读器打开pdf文档时显示的标题不是文件名

0 Preface/Foreword 1 现象 文件名为&#xff1a;Demo-20231017 打开效果&#xff1a;显示名字为 word template 2 解决方法 2.1 利用打印方式将word生产pdf 在word生产pdf文件时&#xff0c;使用打印方式生成pdf文档。 2.2 删除word文档设置的标题 文件---》信息---》标…

“赔率”和“概率”

赔率与概率不同 Odds ! Probability 许多人将“赔率”和“概率”这两个词混为一谈。它们都表示对可能性或机会的估计。我可以理解普通人这样做&#xff0c;但我经常看到数据科学家和统计学家也混淆了这些概念&#xff0c;这是一种遗憾&#xff0c;因为在数学上它们有不同的含…

LVGL_基础控件btnmatrix

LVGL_基础控件btnmatrix 1、创建按钮矩阵 /* 分配按钮以及按钮上的文字 */ // 这里注意不要使用在函数退出时会被销毁的局部变量 // 可以用 "\n" 进行换行&#xff0c;最后一个按钮之后的元素必须是 NULL 或 "" // 换行 "\n" 和 "NULL&quo…

Z-Fighting问题解决(二) - Reverse-z

Z值相关技术概论理清 开始之前先简单理清科普一下涉及的跟三维图形学相关的深度Z相关的概念&#xff1a; Z-Buffer&#xff1a;上个世纪八十年代的图形学飞跃节点算法之一&#xff1b; Z缓冲是一种解决深度排序问题的方法&#xff0c;主要用于确定哪些物体在其他物体的前面。…

【Oracle】分析函数partition by,解决了使用group by后select语句中只能是分组的字段或者是一个聚合函数的问题

首先我们看一下group by的用法&#xff0c;比如根据省份分组。 select province, sum(persons) from t_person group by province;使用了group by后&#xff0c;select语句中只能是分组的字段&#xff08;比如上面的province&#xff09;或者是一个聚合函数&#xff08;比如co…

【特纳斯电子】基于单片机的火灾监测报警系统-仿真设计

视频及资料链接&#xff1a;基于单片机的火灾监测报警系统-仿真设计 - 电子校园网 (mcude.com) 编号&#xff1a; T0152203M-FZ 设计简介&#xff1a; 本设计是基于单片机的火灾监测报警系统&#xff0c;主要实现以下功能&#xff1a; 1.通过OLED显示温度、烟雾、是否有火…

jenkins pipeline使用

1、jenkins全局配置 1.1、maven配置 1.2、jdk配置 1.3、git配置 2、构建环境配置 2.1、安装时间插件 Date Parameter 2.2、Git Parameter 插件安装 3、pipeline如下 pipeline {agent anyenvironment {image_name "192.168.122.150/ken-test/price-service:${date}&…

jmeter监控服务器的资源使用

一. 下载安装包SeverAgent-x.x.x.zip并安装到被监控服务器 下载地址&#xff1a;https://github.com/undera/perfmon-agent 下载完解压后执行运行&#xff0c;windows运行startAgent.bat, linux运行startAgent.sh 二. 在jmeter上添加插件jpgc-PerfMon Metrics Collector监听器…

电商API接口的发展,电商API接口主要应用场景:

随着互联网技术的不断进步和电商行业的迅猛发展&#xff0c;电商API接口在商品交易、物流配送、客户服务等方面发挥着越来越重要的作用。本文将深入探讨电商API接口的技术原理、应用场景、开发方法以及优缺点。 一、技术原理 电商API接口是基于HTTP、TCP、IP等网络协议实现的…

代码随想录二刷 Day41

509. 斐波那契数 这个题简单入门&#xff0c;注意下N小于等于1的情况就可以 class Solution { public:int fib(int n) {if (n < 1) return n; //这句不写的话test能过但是另外的过不了vector<int> result(n 1); //定义存放dp结果的数组&#xff0c;还要定义大小r…

基于复合优化加速算法研究实际问题

import optimtool as oo from optimtool.base import np, sp, pltpip install optimtool>2.5.0加载hybird.nesterov.accer方法 import optimtool.hybrid as oh nes_acc oh.nesterov.accer初始化输入数据 f ( x ) ∑ i 1 n ( ( n − ∑ j 1 n cos ⁡ x j ) i ( 1 − co…

jenkins出错与恢复

如果你的jenkins出现了如下图所示问题&#xff08;比如不能下载插件&#xff0c;无法保存任务等&#xff09;&#xff0c;这个时候就需要重新安装了。 一、卸载干净jenknis 要彻底卸载 Jenkins&#xff0c;您可以按照以下步骤进行操作&#xff1a; 1、停止 Jenkins 服务&…