RTC实时时钟

1. PCF8653简介

PCF8653是PHILIPS公司推出的一款工业级多功能时钟/日历芯片,具有报警功能、定时器功能、时钟输出功能以及中断输出功能,能完成各种复杂的定时服务,该芯片通过IIC接口和FPGA连接。
在这里插入图片描述

1.1 PCF8563寄存器描述

PCF8563有一系列寄存器,由这些寄存器来配置PCF8563的日期和时间。
在这里插入图片描述

1.2 PCF8563秒寄存器(地址:0x02)BCD码

在这里插入图片描述

1.3 PCF8563星期寄存器(地址:0x06)BCD码

在这里插入图片描述

1.4 PCF8563月份寄存器(地址:0x07)BCD码

在这里插入图片描述

1.5 PCF8563年份寄存器(地址:0x08)BCD码

在这里插入图片描述

1.6 PCF8563写寄存器

在这里插入图片描述
图中,先发送PCF8563的地址(7’h51),最低位0表示写数据,随后发送8位寄存器地址,最后发送8位寄存器值。其中:S,表示IIC起始信号;A,表示应答信号;P,表示IIC停止信号。

1.7 PCF8563读寄存器

在这里插入图片描述
图中,同样是先发送7位地址+写操作,然后再发送寄存器地址,随后,重新发送起始信号(S),再次发送7位地址+读操作,然后读取寄存器值。其中第二个S,表示重新发送IIC起始信号;P,表示IIC停止信号。

2. 程序设计

使用FPGA开发板配置RTC实时时钟日期和时间,配置完成后读取RTC实时时钟的日期和时间,并显示在数码管上,通过按键来切换数码管显示日期和时间。

2.1 系统框图

在这里插入图片描述

其中IIC的驱动设计参考:EEPROM读写–IIC协议
PCF8563芯片数据手册如下:
在这里插入图片描述

2.2 源码

module rtc(
    //system clock
    input           sys_clk    ,             // 系统时钟
    input           sys_rst_n  ,             // 系统复位

    //pcf8563 interface
	output          i2c_ack    ,             // I2C应答标志 0:应答 1:未应答
    output          rtc_scl    ,             // i2c时钟线
    inout           rtc_sda    ,             // i2c数据线

    //user interface
    input           key2       ,             // 开关按键
    output   [5:0]  sel        ,             // 数码管位选
    output   [7:0]  seg_led                  // 数码管段选
);

//parameter define
parameter      SLAVE_ADDR =  7'h51        ;  // 器件地址
parameter      BIT_CTRL   =  1'b0         ;  // 字地址位控制参数(16b/8b)
parameter      CLK_FREQ   = 26'd50_000_000;  // i2c_dri模块的驱动时钟频率(CLK_FREQ)
parameter      I2C_FREQ   = 18'd250_000   ;  // I2C的SCL时钟频率
parameter      POINT      = 6'b010100     ;  // 控制点亮数码管小数点的位置
//初始时间设置,从高到低为年到秒,各占8bit
parameter      TIME_INI   = 48'h18_05_23_09_30_00;

//wire define
wire           clk       ;                   // I2C操作时钟
wire           i2c_exec  ;                   // i2c触发控制
wire   [15:0]  i2c_addr  ;                   // i2c操作地址
wire   [ 7:0]  i2c_data_w;                   // i2c写入的数据
wire           i2c_done  ;                   // i2c操作结束标志
wire           i2c_rh_wl ;                   // i2c读写控制
wire   [ 7:0]  i2c_data_r;                   // i2c读出的数据
wire   [23:0]  num       ;                   // 数码管要显示的数据
wire           key_value ;                   // 按键消抖后的数据

//*****************************************************
//**                    main code
//*****************************************************

//例化i2c_dri,调用IIC协议
i2c_dri #(
    .SLAVE_ADDR  (SLAVE_ADDR),    // slave address从机地址,放此处方便参数传递
    .CLK_FREQ    (CLK_FREQ  ),    // i2c_dri模块的驱动时钟频率(CLK_FREQ)
    .I2C_FREQ    (I2C_FREQ  )     // I2C的SCL时钟频率
) u_i2c_dri(
    //global clock
    .clk         (sys_clk   ),    // i2c_dri模块的驱动时钟(CLK_FREQ)
    .rst_n       (sys_rst_n ),    // 复位信号
    //i2c interface
    .i2c_exec    (i2c_exec  ),    // I2C触发执行信号
    .bit_ctrl    (BIT_CTRL  ),    // 器件地址位控制(16b/8b)
    .i2c_rh_wl   (i2c_rh_wl ),    // I2C读写控制信号
    .i2c_addr    (i2c_addr  ),    // I2C器件内地址
    .i2c_data_w  (i2c_data_w),    // I2C要写的数据
    .i2c_data_r  (i2c_data_r),    // I2C读出的数据
    .i2c_done    (i2c_done  ),    // I 2C一次操作完成
	 .i2c_ack     (i2c_ack   ),    // I2C应答标志 0:应答 1:未应答
    .scl         (rtc_scl   ),    // I2C的SCL时钟信号
    .sda         (rtc_sda   ),    // I2C的SDA信号
    //user interface
    .dri_clk     (clk       )     // I2C操作时钟
);

//例化PCF8563测量模块
pcf8563 #(.TIME_INI(TIME_INI)
) u_pcf8563(
    //system clock
    .clk         (clk       ),    // 时钟信号
    .rst_n       (sys_rst_n ),    // 复位信号
    //i2c interface
    .i2c_rh_wl   (i2c_rh_wl ),    // I2C读写控制信号
    .i2c_exec    (i2c_exec  ),    // I2C触发执行信号
    .i2c_addr    (i2c_addr  ),    // I2C器件内地址
    .i2c_data_w  (i2c_data_w),    // I2C要写的数据
    .i2c_data_r  (i2c_data_r),    // I2C读出的数据
    .i2c_done    (i2c_done  ),    // I2C一次操作完成
    //user interface
    .key_value   (key_value ),    // 按键切换输入
    .num         (num       )     // 数码管要显示的数据
); 

//例化数码管驱动模块
seg_bcd_dri u_seg_bcd_dri(
   //input
   .clk          (sys_clk   ),    // 时钟信号
   .rst_n        (sys_rst_n ),    // 复位信号
   .num          (num       ),    // 6个数码管要显示的数值
   .point        (POINT     ),    // 小数点具体显示的位置,从高到低,高有效
   //output
   .sel          (sel       ),    // 数码管位选
   .seg_led      (seg_led   )     // 数码管段选
);

//例化消抖模块
key_debounce u_key_debounce(
    .clk         (sys_clk   ),    //外部50M时钟
    .rst_n       (sys_rst_n ),    //外部复位信号,低有效
    .key         (key2      ),    //外部按键输入
	.key_value   (key_value ),    //按键消抖后的数据
    .key_flag    ()               //按键数据有效信号
);

endmodule
module pcf8563 #(
    // 初始时间设置,从高到低为年到秒,各占8bit
    parameter  TIME_INI = 48'h18_03_19_09_30_00)(
    //system clock 50MHz
    input                 clk        ,    // 时钟信号
    input                 rst_n      ,    // 复位信号

    //i2c interface
    output   reg          i2c_rh_wl  ,    // I2C读写控制信号
    output   reg          i2c_exec   ,    // I2C触发执行信号
    output   reg  [15:0]  i2c_addr   ,    // I2C器件内地址
    output   reg  [ 7:0]  i2c_data_w ,    // I2C要写的数据
    input         [ 7:0]  i2c_data_r ,    // I2C读出的数据
    input                 i2c_done   ,    // I2C一次操作完成

    //user interface
    input                 key_value  ,    // 按键切换输入
    output        [23:0]  num             // 数码管要显示的数据
);

//reg define
reg            key_dy0   ;                // 延迟打拍
reg            key_dy1   ;                // 延迟打拍
reg            switch    ;                // 按键切换显示日期、时间
reg   [3:0]    flow_cnt  ;                // 状态流控制
reg  [12:0]    wait_cnt  ;                // 计数等待

//PCF8563T的秒、分、时、日、月、年数据
reg   [7:0]    sec       ;                // 秒
reg   [7:0]    min       ;                // 分
reg   [7:0]    hour      ;                // 时
reg   [7:0]    day       ;                // 日
reg   [7:0]    mon       ;                // 月
reg   [7:0]    year      ;                // 年
wire  [23:0]   rtc_time  ;                // 时间,从低位到高位依次是秒、分、时,各8bit
wire  [23:0]   rtc_date  ;                // 日期,从低位到高位依次是日、月、年,各8bit

//wire define
wire           neg_sap   ;                // 采下降沿得到的信号

//*****************************************************
//**                    main code
//*****************************************************

assign neg_sap  = (~key_dy0 & key_dy1);   // 按键按下时,得到一个周期的高电平信号
assign rtc_time = {
    
    hour,min,sec};
assign rtc_date = {
    
    year,mon,day};
//通过switch切换时间/日期显示
assign num      = switch ? rtc_time : rtc_date;

//打拍(采按键时的下降沿)
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        key_dy0  <= 1'b1;
        key_dy1  <= 1'b1;
    end
    else begin
        key_dy0 <= key_value;
        key_dy1 <= key_dy0  ;
    end
end

//按键切换
always @(posedge clk or negedge rst_n ) begin
    if(!rst_n)
        switch<= 1'b0;
    else if (neg_sap)
        switch <= ~switch;
end

//从PCF8563T读出的时间、日期数据
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        sec        <= 8'h0;
        min        <= 8'h0;
        hour       <= 8'h0;
        day        <= 8'h0;
        mon        <= 8'h0;
        year       <= 8'h0;
        i2c_exec   <= 1'b0;
        i2c_rh_wl  <= 1'b0;
        i2c_addr   <= 8'd0;
        i2c_data_w <= 8'd0;
        flow_cnt   <= 4'd0;
        wait_cnt   <= 13'd0;
    end
    else begin
        i2c_exec <= 1'b0;
        case(flow_cnt)
            //上电初始化
            4'd0: begin
                if(wait_cnt == 13'd8000) begin
                    wait_cnt<= 12'd0;
                    flow_cnt<= flow_cnt + 1'b1;
                end
                else
                    wait_cnt<= wait_cnt + 1'b1;
            end
            //写读秒
            4'd1: begin
                i2c_exec  <= 1'b1;
                i2c_addr  <= 8'h02;
                flow_cnt  <= flow_cnt + 1'b1;
                i2c_data_w<= TIME_INI[7:0];
            end
            4'd2: begin
                if(i2c_done == 1'b1) begin
                    sec     <= i2c_data_r[6:0];
                    flow_cnt<= flow_cnt + 1'b1;
                end
            end
            //写读分
            4'd3: begin
                i2c_exec  <= 1'b1;
                i2c_addr  <= 8'h03;
                flow_cnt  <= flow_cnt + 1'b1;
                i2c_data_w<= TIME_INI[15:8];
            end
            4'd4: begin
                if(i2c_done == 1'b1) begin
                    min     <= i2c_data_r[6:0];
                    flow_cnt<= flow_cnt + 1'b1;
                end
            end
            //写读时
            4'd5: begin
                i2c_exec  <= 1'b1;
                i2c_addr  <= 8'h04;
                flow_cnt  <= flow_cnt + 1'b1;
                i2c_data_w<= TIME_INI[23:16];
            end
            4'd6: begin
                if(i2c_done == 1'b1) begin
                    hour    <= i2c_data_r[5:0];
                    flow_cnt<= flow_cnt + 1'b1;
                end
            end
            //写读天
            4'd7: begin
                i2c_exec  <= 1'b1;
                i2c_addr  <= 8'h05;
                flow_cnt  <= flow_cnt + 1'b1;
                i2c_data_w<= TIME_INI[31:24];
            end
            4'd8: begin
                if(i2c_done == 1'b1) begin
                    day     <= i2c_data_r[5:0];
                    flow_cnt<= flow_cnt + 1'b1;
                end
            end
            //写读月
            4'd9: begin
                i2c_exec  <= 1'b1;
                i2c_addr  <= 8'h07;
                flow_cnt  <= flow_cnt + 1'b1;
                i2c_data_w<= TIME_INI[39:32];
            end
            4'd10: begin
                if(i2c_done == 1'b1) begin
                    mon     <= i2c_data_r[4:0];
                    flow_cnt<= flow_cnt + 1'b1;
                end
            end
            //写读年
            4'd11: begin
                i2c_exec  <= 1'b1;
                i2c_addr  <= 8'h08;
                flow_cnt  <= flow_cnt + 1'b1;
                i2c_data_w<= TIME_INI[47:40];
            end
            4'd12: begin
                if(i2c_done == 1'b1) begin
                    year     <= i2c_data_r;
                    i2c_rh_wl<= 1'b1;
                    flow_cnt <= 4'd1;
                end
            end
            default: flow_cnt <= 4'd0;
        endcase
    end
end

endmodule
module seg_bcd_dri(
   //input
   input                  clk    ,        // 时钟信号
   input                  rst_n  ,        // 复位信号
   input        [23:0]    num    ,        // 6个数码管要显示的数值
   input        [5:0]     point  ,        // 小数点具体显示的位置,从高到低,高有效

   //output
   output  reg  [5:0]     sel    ,        // 数码管位选
   output  reg  [7:0]     seg_led         // 数码管段选
);

//parameter define
parameter  WIDTH0 = 50_000;

//reg define
reg    [15:0]             cnt0;           // 1ms计数
reg    [2:0]              cnt;            // 切换显示数码管用
reg    [3:0]              num1;           // 送给要显示的数码管,要亮的灯
reg                       point1;         // 要显示的小数点

//*****************************************************
//**                    main code
//*****************************************************

//计数1ms
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        cnt0 <= 15'b0;
    else if(cnt0 < WIDTH0)
        cnt0 <= cnt0 + 1'b1;
    else
        cnt0 <= 15'b0;
end

//计数器,用来计数6个状态(因为有6个灯)
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        cnt <= 3'b0;
    else if(cnt < 3'd6) begin
        if(cnt0 == WIDTH0)
            cnt <= cnt + 1'b1;
        else
            cnt <= cnt;
    end
    else
        cnt <= 3'b0;
end

//6个数码管轮流显示,完成刷新(从右到左)
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        sel     <= 6'b000001;
        num1 <= 4'b0;
    end
    else begin
        case (cnt)
            3'd0:begin
                 sel    <= 6'b111110;
                 num1   <= num[3:0] ;
                 point1 <= point[0] ;
            end
            3'd1:begin
                 sel    <= 6'b111101;
                 num1   <= num[7:4] ;
                 point1 <= point[1] ;
            end
            3'd2:begin
                 sel    <= 6'b111011;
                 num1   <= num[11:8];
                 point1 <= point[2] ;
            end
            3'd3:begin
                 sel    <= 6'b110111 ;
                 num1   <= num[15:12];
                 point1 <= point[3]  ;
            end
            3'd4:begin
                 sel    <= 6'b101111 ;
                 num1   <= num[19:16];
                 point1 <= point[4]  ;
            end
            3'd5:begin
                 sel    <= 6'b011111 ;
                 num1   <= num[23:20];
                 point1 <= point[5]  ;
            end
            default: begin
                 sel    <= 6'b000000;
                 num1   <= 4'b0;
                 point1 <= 1'b1;
            end
        endcase
    end
end

//数码管显示数据
always @ (posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        seg_led <= 7'b0;
    else begin
        case(num1)
            4'd0: seg_led <= {~point1,7'b1000000};
            4'd1: seg_led <= {~point1,7'b1111001};
            4'd2: seg_led <= {~point1,7'b0100100};
            4'd3: seg_led <= {~point1,7'b0110000};
            4'd4: seg_led <= {~point1,7'b0011001};
            4'd5: seg_led <= {~point1,7'b0010010};
            4'd6: seg_led <= {~point1,7'b0000010};
            4'd7: seg_led <= {~point1,7'b1111000};
            4'd8: seg_led <= {~point1,7'b0000000};
            4'd9: seg_led <= {~point1,7'b0010000};
            default: seg_led <= {
    
    point1,7'b1000000};
        endcase
    end
end
endmodule

猜你喜欢

转载自blog.csdn.net/gemengxia/article/details/115460399