文章目录
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