ZYNQ-FPGA-RTC (real-time display clock)

1. PCF8563 introduction

The data generation of the real-time clock is completed through the PCF8563 module. PCF8563 has alarm function, timer function, time
Clock output function and interrupt output function.

Memory addresses 00H and 01H are used as control registers and status registers (CONTROL_STATUS); memory addresses 02H~08H are used as TIME timers (seconds~year timers); addresses 09H~0CH are used for alarm (ALARM) registers (define alarm conditions) ;Address 0DH controls the output frequency of CLKOUT pin; addresses 0EH and 0FH are used for timer control register and timer register respectively.

How to use this module to implement clock display?

First we need to wait for 8ms for the device to initialize, and then we need to write an initial time into the module, and then the module will automatically time backwards. Therefore, the overall control module is written once and then read in a loop.

This module communicates with zynq through the IIC protocol, and its device address on the development board is 1010_001. Therefore, we need to select the device in the first cycle of iic, and start writing data in the second cycle.

in

The second register address is 02h, and its 7 bits indicate whether it is accurate. 1 is inaccurate, 0 is accurate, and the seven bits 0-6 use BCD code to represent the seconds.

The sub-register address is 03h, its 7 bits are invalid, and bits 0-6 use BCD code to represent the minute time.

Hour register address bit 04h, 6-7 bits are invalid, 0-5 bits use BCD code to represent the hour

The day register address is 05h, 6-7 digits are invalid, 0-5 digits use BCD code to represent the day (note that there is no 06h)

The month register address is 07h, 7 bits 0 represents the current century, 1 represents the next century, 5-6 bits are useless, 0-4 BCD bits represent the month

The year register address is 08h, and digits 0-7 represent the year in BCD code.

2. System block diagram

iic_dri is the iic driver module in the eeprom experiment, but remember to change the device address to 1010001

lcd_rgb_char is also the code for the previous LCD color bar display experiment, but the display module and top-level calling module need to be modified.

The pcf8565_ctrl module is a module that encapsulates the pcf8563 function.

3. Module code

3.1pcf8565 module

module pcf8563_ctrl #(
    // Initial time setting, from high to low, years to seconds, each occupying 8 bits
    parameter TIME_INIT = 48'h23_11_6_22_00_00)(
    input clk, //clock signal
    input rst_n , //reset signal

    //i2c interface
    output reg i2c_rh_wl , //I2C read and write control signal
    output reg i2c_exec, //I2C triggers execution signal
    output reg [15:0] i2c_addr , //I2C device internal address
    output reg [7:0] i2c_data_w, //data to be written by I2C
    input [7:0] i2c_data_r, //data read by I2C
    input i2c_done, //I2C operation completed once

    //PCF8563T second, minute, hour, day, month, year data
    output reg [7:0] sec, //seconds
    output reg [7:0] min, //minutes
    output reg [7:0] hour, //hour
    output reg [7:0] day, //day
    output reg [7:0] mon, //month
    output reg [7:0] year //year
);

//parameter define
localparam WAIT_TIME = 13'd8_000;

//reg define
reg [3:0] flow_cnt; // Transition flow control
reg [12:0] wait_cnt; // count wait

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

//First write the initialization date and time to PCF8563, and then read the date and time from it
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)
            //Power-on initialization
            4'd0: begin
                if(wait_cnt == (WAIT_TIME - 13'd1)) begin
                    wait_cnt<= 13'd0;
                    flow_cnt<= flow_cnt + 4'b1;
                end
                else
                    wait_cnt<= wait_cnt + 13'b1;
            end
            //Write and read seconds
            4'd1: begin
                i2c_exec <= 1'b1;
                i2c_addr <= 8'h02;
                flow_cnt <= flow_cnt + 4'b1;
                i2c_data_w<= TIME_INIT[7:0];
            end
            4'd2: begin
                if(i2c_done == 1'b1) begin
                    sec <= i2c_data_r[6:0];
                    flow_cnt<= flow_cnt + 4'b1;
                end
            end
            //Write and read points
            4'd3: begin
                i2c_exec <= 1'b1;
                i2c_addr <= 8'h03;
                flow_cnt <= flow_cnt + 4'b1;
                i2c_data_w<= TIME_INIT[15:8];
            end
            4'd4: begin
                if(i2c_done == 1'b1) begin
                    min <= i2c_data_r[6:0];
                    flow_cnt<= flow_cnt + 4'b1;
                end
            end
            //When writing and reading
            4'd5: begin
                i2c_exec <= 1'b1;
                i2c_addr <= 8'h04;
                flow_cnt <= flow_cnt + 4'b1;
                i2c_data_w<= TIME_INIT[23:16];
            end
            4'd6: begin
                if(i2c_done == 1'b1) begin
                    hour <= i2c_data_r[5:0];
                    flow_cnt<= flow_cnt + 4'b1;
                end
            end
            //Write and read days
            4'd7: begin
                i2c_exec <= 1'b1;
                i2c_addr <= 8'h05;
                flow_cnt <= flow_cnt + 4'b1;
                i2c_data_w<= TIME_INIT[31:24];
            end
            4'd8: begin
                if(i2c_done == 1'b1) begin
                    day <= i2c_data_r[5:0];
                    flow_cnt<= flow_cnt + 4'b1;
                end
            end
            //Write and read month
            4'd9: begin
                i2c_exec <= 1'b1;
                i2c_addr <= 8'h07;
                flow_cnt <= flow_cnt + 4'b1;
                i2c_data_w<= TIME_INIT[39:32];
            end
            4'd10: begin
                if(i2c_done == 1'b1) begin
                    mon <= i2c_data_r[4:0];
                    flow_cnt<= flow_cnt + 4'b1;
                end
            end
            //Write and read year
            4'd11: begin
                i2c_exec <= 1'b1;
                i2c_addr <= 8'h08;
                flow_cnt <= flow_cnt + 4'b1;
                i2c_data_w<= TIME_INIT[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

This module code uses a state machine, combined with the read and write selection signals, to cleverly implement the process of writing for the first time and reading several times afterward.

3.2lcd_display module

module lcd_display(
    input lcd_pclk, //LCD pixel clock
    input rst_n , //system reset
    
    //calendar data
    input [7:0] sec, //seconds
    input [7:0] min, //minutes
    input [7:0] hour, //hour
    input [7:0] day, //day
    input [7:0] mon, //month
    input [7:0] year, //year
    
    //LCD data interface
    input [10:0] pixel_xpos, //pixel abscissa
    input [10:0] pixel_ypos, //pixel vertical coordinate
    output reg [23:0] pixel_data //pixel data
);

//parameter define
localparam CHAR_POS_X_1 = 11'd1; //The abscissa of the starting point of the character area in line 1
localparam CHAR_POS_Y_1 = 11'd1; //The ordinate of the starting point of the character area in line 1
localparam CHAR_POS_X_2 = 11'd17; //The abscissa of the starting point of the character area in line 2
localparam CHAR_POS_Y_2 = 11'd17; //The ordinate of the starting point of the character area in line 2
localparam CHAR_WIDTH_1 = 11'd80; //The width of the character area in the first line, a total of 10 characters in the first line (plus spaces)
localparam CHAR_WIDTH_2 = 11'd64; //The width of the character area in the second line, a total of 8 characters in the second line (plus spaces)
localparam CHAR_HEIGHT = 11'd16; //The height of a single character
localparam WHITE = 24'hffffff; //Background color, white
localparam BLACK = 24'h000000; //Character color, black

//reg define
reg [127:0] char [9:0] ; //Character array

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

//Initial value of character array, used to store font model data (generated by modulo software, single number font size: 16*8)
always @(posedge lcd_pclk) begin
    char[0] <= 128'h00000018244242424242424224180000; // "0"
    char[1] <= 128'h000000107010101010101010107C0000; // "1"
    char[2] <= 128'h0000003C4242420404081020427E0000; // "2"
    char[3] <= 128'h0000003C424204180402024244380000; // "3"
    char[4] <= 128'h000000040C14242444447E04041E0000; // "4"
    char[5] <= 128'h0000007E404040586402024244380000; // "5"
    char[6] <= 128'h0000001C244040586442424224180000; // "6"
    char[7] <= 128'h0000007E444408081010101010100000; // "7"
    char[8] <= 128'h0000003C4242422418244242423C0000; // "8"
    char[9] <= 128'h0000001824424242261A020224380000; // "9"
end

//Draw different pixel data in different areas
always @(posedge lcd_pclk or negedge rst_n ) begin
    if (!rst_n) begin
        pixel_data <= BLACK;
    end
    
    //Display the thousands digit of the year in the first line, fixed value "2"
    else if((pixel_xpos >= CHAR_POS_X_1 - 1'b1)
          & amp; & amp; (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1 / 10 * 1 - 1'b1)
          & amp; & amp; (pixel_ypos >= CHAR_POS_Y_1)
          & amp; & amp; (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT)) begin
        if(char[2][(CHAR_HEIGHT + CHAR_POS_Y_1 - pixel_ypos) * 8
                        - ((pixel_xpos - (CHAR_POS_X_1-1'b1)) % 8) - 11'd1])
            pixel_data <= BLACK; //Display characters in black
        else
            pixel_data <= WHITE; //The background of the display character area is white
    end
    
    //Display the hundred's digit of the year in the first line, fixed value "0"
    else if((pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1 / 10 *1 - 1'b1)
          & amp; & amp; (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1 / 10 *2 - 1'b1)
          & amp; & amp; (pixel_ypos >= CHAR_POS_Y_1)
          & amp; & amp; (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT)) begin
        if(char[0][(CHAR_HEIGHT + CHAR_POS_Y_1 - pixel_ypos) * 8
                        - ((pixel_xpos - (CHAR_POS_X_1-1'b1)) % 8) - 11'd1])
            pixel_data <= BLACK;
        else
            pixel_data <= WHITE;
    end
    
    //Display the tenth digit of the year on the first line
    else if((pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1 / 10 * 2 - 1'b1)
          & amp; & amp; (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1 / 10 * 3 - 1'b1)
          & amp; & amp; (pixel_ypos >= CHAR_POS_Y_1)
          & amp; & amp; (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT)) begin
        if(char[year[7:4]][(CHAR_HEIGHT + CHAR_POS_Y_1 - pixel_ypos) * 8
                        - ((pixel_xpos - (CHAR_POS_X_1-1'b1)) % 8) - 11'd1])
            pixel_data <= BLACK;
        else
            pixel_data <= WHITE;
    end
    
    //Display the ones digit of the year in the first line
    else if((pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1 / 10 * 3 - 1'b1)
          & amp; & amp; (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1 / 10 * 4 - 1'b1)
          & amp; & amp; (pixel_ypos >= CHAR_POS_Y_1)
          & amp; & amp; (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT)) begin
        if(char[year[3:0]][(CHAR_HEIGHT + CHAR_POS_Y_1 - pixel_ypos) * 8
                        - ((pixel_xpos - (CHAR_POS_X_1-1'b1)) % 8) - 11'd1])
            pixel_data <= BLACK;
        else
            pixel_data <= WHITE;
    end
    
    //Display spaces on the first line

    
    //Display the tenth digit of the month on the first line
    else if((pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1 / 10 * 5 - 1'b1)
          & amp; & amp; (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1 / 10 * 6 - 1'b1)
          & amp; & amp; (pixel_ypos >= CHAR_POS_Y_1)
          & amp; & amp; (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT)) begin
        if(char[mon[7:4]][(CHAR_HEIGHT + CHAR_POS_Y_1 - pixel_ypos) * 8
                        - ((pixel_xpos - (CHAR_POS_X_1-1'b1)) % 8) -11'd1])
            pixel_data <= BLACK;
        else
            pixel_data <= WHITE;
    end
    
    //Display the ones digit of the month in the first line
    else if((pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1 / 10 * 6 - 1'b1)
          & amp; & amp; (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1 / 10 * 7 - 1'b1)
          & amp; & amp; (pixel_ypos >= CHAR_POS_Y_1)
          & amp; & amp; (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT)) begin
        if(char[mon[3:0]][(CHAR_HEIGHT + CHAR_POS_Y_1 - pixel_ypos) * 8
                        - ((pixel_xpos - (CHAR_POS_X_1-1'b1)) % 8) - 11'd1])
            pixel_data <= BLACK;
        else
            pixel_data <= WHITE;
    end
    
    //Display spaces on the first line


    
    //Display the tenth digit of the day on the first line
    else if((pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1 / 10 * 8 - 1'b1)
               & amp; & amp; (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1 / 10 * 9 - 1'b1)
               & amp; & amp; (pixel_ypos >= CHAR_POS_Y_1)
               & amp; & amp; (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
        if(char[day[7:4]][(CHAR_HEIGHT + CHAR_POS_Y_1 - pixel_ypos) * 8
                        - ((pixel_xpos - (CHAR_POS_X_1-1'b1)) % 8) -11'd1])
            pixel_data <= BLACK;
        else
            pixel_data <= WHITE;
    end
    
    //Display the ones digit of the day on the first line
    else if((pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1 / 10 * 9 - 1'b1)
          & amp; & amp; (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1 - 1'b1)
          & amp; & amp; (pixel_ypos >= CHAR_POS_Y_1)
          & amp; & amp; (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT)) begin
        if(char[day[3:0]][(CHAR_HEIGHT + CHAR_POS_Y_1 - pixel_ypos) * 8
                        - ((pixel_xpos - (CHAR_POS_X_1-1'b1)) % 8) -11'd1])
            pixel_data <= BLACK;
        else
            pixel_data <= WHITE;
    end
    
    //The tens digit when displayed on the second line
    else if((pixel_xpos >= CHAR_POS_X_2 - 1'b1)
          & amp; & amp; (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2 / 8 * 1 - 1'b1)
          & amp; & amp; (pixel_ypos >= CHAR_POS_Y_2)
          & amp; & amp; (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT)) begin
        if(char[hour[7:4]][(CHAR_HEIGHT + CHAR_POS_Y_2 - pixel_ypos) * 8
                        - ((pixel_xpos - (CHAR_POS_X_2-1'b1)) % 8) -11'd1])
            pixel_data <= BLACK;
        else
            pixel_data <= WHITE;
    end
    
    //The ones digit when displayed on the second line
    else if((pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2 / 8 * 1 - 1'b1)
          & amp; & amp; (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2 / 8 * 2 - 1'b1)
          & amp; & amp; (pixel_ypos >= CHAR_POS_Y_2)
          & amp; & amp; (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT)) begin
        if(char[hour[3:0]][(CHAR_HEIGHT + CHAR_POS_Y_2 - pixel_ypos) * 8
                        - ((pixel_xpos - (CHAR_POS_X_2-1'b1)) % 8) -11'd1])
            pixel_data <= BLACK;
        else
            pixel_data <= WHITE;
    end
    

    
    //Display the tens digit of the minute on the second line
    else if((pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2 / 8 * 3 - 1'b1)
          & amp; & amp; (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2 / 8 * 4 - 1'b1)
          & amp; & amp; (pixel_ypos >= CHAR_POS_Y_2)
          & amp; & amp; (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT)) begin
        if(char[min[7:4]][(CHAR_HEIGHT + CHAR_POS_Y_2 - pixel_ypos) * 8
                        - ((pixel_xpos - (CHAR_POS_X_2-1'b1)) % 8) - 11'd1])
            pixel_data <= BLACK;
        else
            pixel_data <= WHITE;
    end
    
    //Display the units digit of the minute on the second line
    else if((pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2 / 8 * 4 - 1'b1)
          & amp; & amp; (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2 / 8 * 5 - 1'b1)
          & amp; & amp; (pixel_ypos >= CHAR_POS_Y_2)
          & amp; & amp; (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT)) begin
        if(char[min[3:0]][(CHAR_HEIGHT + CHAR_POS_Y_2 - pixel_ypos) * 8
                        - ((pixel_xpos - (CHAR_POS_X_2-1'b1)) % 8) -11'd1])
            pixel_data <= BLACK;
        else
            pixel_data <= WHITE;
    end
    

    
    //Display the tens digit of the second on the second line
    else if((pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2 / 8 * 6 - 1'b1)
          & amp; & amp; (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2 / 8 * 7 - 1'b1)
          & amp; & amp; (pixel_ypos >= CHAR_POS_Y_2)
          & amp; & amp; (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT)) begin
        if(char[sec[7:4]][(CHAR_HEIGHT + CHAR_POS_Y_2 - pixel_ypos) * 8
                        - ((pixel_xpos - (CHAR_POS_X_2-1'b1))%8) - 11'd1])
            pixel_data <= BLACK;
        else
            pixel_data <= WHITE;
    end
    
    //Display the units digit of seconds on the second line
    else if((pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2 / 8 * 7 - 1'b1)
          & amp; & amp; (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2 - 1'b1)
          & amp; & amp; (pixel_ypos >= CHAR_POS_Y_2)
          & amp; & amp; (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT)) begin
        if(char[sec[3:0]][(CHAR_HEIGHT + CHAR_POS_Y_2 - pixel_ypos) * 8
                        - ((pixel_xpos - (CHAR_POS_X_2-1'b1)) % 8) -11'd1])
            pixel_data <= BLACK;
        else
            pixel_data <= WHITE;
    end
    else
        pixel_data <= WHITE; //The screen background is white
end

endmodule