1)实验平台:正点原子新起点V2开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113
2)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html
3)对正点原子FPGA感兴趣的同学可以加群讨论:
4)关注正点原子公众号,获取最新资料更新

讯享网
第三十一章RTC实时时钟数码管显示实验
PCF8563是一款多功能时钟/日历芯片。因其功耗低、控制简单、封装小而广泛应用于电表、水表、传真机、便携式仪器等产品中。本章我们将使用新起点开发板上的PCF8563器件实现实时时钟的显示。
本章包括以下几个部分:
3030.1简介
30.2实验任务
30.3硬件设计
30.4程序设计
30.5下载验证
31.1简介
PCF8563是PHILIPS公司推出的一款工业级多功能时钟/日历芯片,具有报警功能、定时器功能、时钟输出功能以及中断输出功能,能完成各种复杂的定时服务。其内部功能模块的框图如下图所示:

图 31.1.1 PCF8563功能框图
PCF8563有16个可寻址的8位寄存器,但不是所有位都有用到。前两个寄存器(内存地址00H、01H)用作控制寄存器和状态寄存器(CONTROL_STATUS);内存地址02H08H用作TIME计时器(秒年计时器);地址09H~0CH用于报警(ALARM)寄存器(定义报警条件);地址0DH控制CLKOUT管脚的输出频率;地址0EH和0FH分别用于定时器控制寄存器和定时器寄存器。
秒、分钟、小时、日、月、年、分钟报警、小时报警、日报警寄存器中的数据编码格式为BCD,只有星期和星期报警寄存器中的数据不以BCD格式编码。BCD码(Binary-Coded Decimal)是一种二进制的数字编码形式,用四个二进制位来表示一位十进制数(0~9),能够使二进制和十进制之间的转换得以快捷的进行。
PCF8563通过I2C接口与FPGA芯片进行通信。使用该器件时,FPGA芯片先通过I2C接口向该器件相应的寄存器写入初始的时间数据(秒~年),然后通过I2C接口读取相应的寄存器的时间数据。有关I2C总线协议详细的介绍请大家参考“EEPROM读写实验”。
下面对本次实验用到的寄存器做简要的描述和说明,其他寄存器的描述和说明,请大家参考PCF8563的数据手册。
秒寄存器的地址为02h,说明如下表所示:

当电源电压低于PCF8563器件的最低供电电压时,VL为“1”,表明内部完整的时钟周期信号不能被保证,可能导致时钟/日历数据不准确。
BCD编码的秒数值如下表所示:

图 31.1.2 秒数值的BCD编码
分寄存器的地址为03h,说明如下表所示:

小时寄存器的地址为04h,说明如下表所示:

天寄存器的地址为05h,说明如下表所示:

当年计数器的值是闰年时,PCF8563自动给二月增加一个值,使其成为29天。
月/世纪寄存器的地址为07h,说明如下表所示:
表 31.1.5 月/世纪寄存器(地址07h)

表 31.1.6 月份表

年寄存器的地址为08h,说明如下表所示:
表 31.1.7 寄存器(地址08h)

本节的实验任务是通过新起点开发板上的PCF8563实时时钟芯片,根据输入按键KEY0来切换数码管显示时间或者日期。
31.3硬件设计
新起点开发板上PCF8563接口部分的原理图如下图所示。


图 31.3.1 PCF8563接口原理图
PCF8563作为I2C接口的从器件与EEPROM等模块统一挂接在新起点开发板上的IIC总线上。 OSCI、OSCO与外部32.768KHz的晶振相连,为芯片提供驱动时钟;SCL和SDA分别是I2C总线的串行时钟接口和串行数据接口。
本实验中,各管脚分配如下表所示。
表 31.3.1 RTC实时时钟数码管显示管脚分配

set_location_assignment PIN_M2 -to sys_clk set_location_assignment PIN_M1 -to sys_rst_n #KEY0 set_location_assignment PIN_E16 -to key #IIC set_location_assignment PIN_D8 -to iic_scl set_location_assignment PIN_C8 -to iic_sda #数码管 set_location_assignment PIN_N16 -to seg_sel[0] set_location_assignment PIN_N15 -to seg_sel[1] set_location_assignment PIN_P16 -to seg_sel[2] set_location_assignment PIN_P15 -to seg_sel[3] set_location_assignment PIN_R16 -to seg_sel[4] set_location_assignment PIN_T15 -to seg_sel[5] set_location_assignment PIN_M11 -to seg_led[0] set_location_assignment PIN_N12 -to seg_led[1] set_location_assignment PIN_C9 -to seg_led[2] set_location_assignment PIN_N13 -to seg_led[3] set_location_assignment PIN_M10 -to seg_led[4] set_location_assignment PIN_N11 -to seg_led[5] set_location_assignment PIN_P11 -to seg_led[6] set_location_assignment PIN_D9 -to seg_led[7]
讯享网
31.4程序设计
根据实验任务,我们可以大致规划出系统的控制流程:首先通过I2C总线向PCF8563写入初始日期值(年月日)和时间值(时分秒),然后不断地读取日期和时间数据,并根据输入的按键,选择将日期或者时间数据显示到数码管上。由此画出系统的功能框图如下图所示:

图 31.4.1 系统框图
由系统框图可知,FPGA顶层模块例化了以下五个模块、分别是IIC驱动模块(i2c_dri)、PCF8563控制模块(pcf8563_ctrl)、按键消抖模块(key_debounce)、显示值切换模块(key_sw_disp)以及数码管BCD驱动模块(seg_bcd_dri)。
各模块端口及信号连接如下图所示:

图 31.4.2 顶层模块原理图
PCF8563控制模块通过调用IIC驱动模块来实现对PCF8563实时时钟数据的配置和读取;而显示值切换模块根据按键消抖模块输出的按键数据(key_value)选择显示日期或者时间(年月日/时分秒),并将其传递给数码管BCD驱动模块(seg_bcd_dri),最终在数码管上显示日期或者时间。
顶层模块的代码如下:
讯享网1 module rtc_seg_led( 2 input sys_clk, //系统时钟 3 input sys_rst_n, //系统复位 4 5 //按键 6 input key, //输入按键KEY0 7 8 //数码管 9 output [5:0] seg_sel, //数码管位选信号 10 output [7:0] seg_led, //数码管段选信号 11 12 //RTC实时时钟 13 output iic_scl, //RTC的时钟线scl 14 inout iic_sda //RTC的数据线sda 15 ); 16 17 //parameter define 18 parameter SLAVE_ADDR = 7'b101_0001 ; //器件地址(SLAVE_ADDR) 19 parameter BIT_CTRL = 1'b0 ; //字地址位控制参数(16b/8b) 20 parameter CLK_FREQ = 26'd50_000_000; //i2c_dri模块的驱动时钟频率(CLK_FREQ) 21 parameter I2C_FREQ = 18'd250_000 ; //I2C的SCL时钟频率 22 parameter TIME_INIT = 48'h20_04_01_09_30_00;//初始时间 23 24 //wire define 25 wire dri_clk ; //I2C操作时钟 26 wire i2c_exec ; //I2C触发控制 27 wire [15:0] i2c_addr ; //I2C操作地址 28 wire [ 7:0] i2c_data_w; //I2C写入的数据 29 wire i2c_done ; //I2C操作结束标志 30 wire i2c_ack ; //I2C应答标志 0:应答 1:未应答 31 wire i2c_rh_wl ; //I2C读写控制 32 wire [ 7:0] i2c_data_r; //I2C读出的数据 33 34 wire [7:0] sec ; //秒 35 wire [7:0] min ; //分 36 wire [7:0] hour ; //时 37 wire [7:0] day ; //日 38 wire [7:0] mon ; //月 39 wire [7:0] year ; //年 40 41 wire key_value; //消抖后的按键值 42 wire [5:0] point ; //数码管小数点控制 43 wire [23:0] disp_data; //数码管显示的数值控制 44 45 //* 46 // main code 47 //* 48 49 //i2c驱动模块 50 i2c_dri #( 51 .SLAVE_ADDR (SLAVE_ADDR), //从机地址 52 .CLK_FREQ (CLK_FREQ ), //模块输入的时钟频率 53 .I2C_FREQ (I2C_FREQ ) //IIC_SCL的时钟频率 54 ) u_i2c_dri( 55 .clk (sys_clk ), 56 .rst_n (sys_rst_n ), 57 //i2c interface 58 .i2c_exec (i2c_exec ), 59 .bit_ctrl (BIT_CTRL ), 60 .i2c_rh_wl (i2c_rh_wl ), 61 .i2c_addr (i2c_addr ), 62 .i2c_data_w (i2c_data_w), 63 .i2c_data_r (i2c_data_r), 64 .i2c_done (i2c_done ), 65 .i2c_ack (i2c_ack ), 66 .scl (iic_scl ), 67 .sda (iic_sda ), 68 //user interface 69 .dri_clk (dri_clk ) 70 ); 71 72 //PCF8563控制模块 73 pcf8563_ctrl #( 74 .TIME_INIT (TIME_INIT) 75 )u_pcf8563_ctrl( 76 .clk (dri_clk ), 77 .rst_n (sys_rst_n ), 78 //IIC 79 .i2c_rh_wl (i2c_rh_wl ), 80 .i2c_exec (i2c_exec ), 81 .i2c_addr (i2c_addr ), 82 .i2c_data_w (i2c_data_w), 83 .i2c_data_r (i2c_data_r), 84 .i2c_done (i2c_done ), 85 //时间和日期 86 .sec (sec ), 87 .min (min ), 88 .hour (hour ), 89 .day (day ), 90 .mon (mon ), 91 .year (year ) 92 ); 93 94 //消抖模块 95 key_debounce u_key_debounce( 96 .sys_clk (sys_clk ), //外部50M时钟 97 .sys_rst_n (sys_rst_n ), //外部复位信号,低有效 98 .key (key ), //外部按键输入 99 .key_value (key_value ), //按键消抖后的数据 100 .key_flag () //按键数据有效信号 101 ); 102 103 //显示值切换模块 104 key_sw_disp u_key_sw_disp( 105 .clk (sys_clk), 106 .rst_n (sys_rst_n), 107 108 .key_value (key_value), 109 .sec (sec ), 110 .min (min ), 111 .hour (hour), 112 .day (day ), 113 .mon (mon ), 114 .year (year), 115 116 .point (point), 117 .disp_data (disp_data) 118 ); 119 120 //数码管驱动模块 121 seg_bcd_dri u_seg_bcd_dri( 122 //input 123 .clk (sys_clk ), //时钟信号 124 .rst_n (sys_rst_n ), //复位信号 125 .data (disp_data ), //6个数码管要显示的数值 126 .point (point ), //小数点具体显示的位置,从左往右,高有效 127 //output 128 .seg_sel (seg_sel ), //数码管位选 129 .seg_led (seg_led ) //数码管段选 130 ); 131 132 endmodule
程序中第18行至第22行定义了一些参数,其中TIME_INIT表示PCF8563初始化时的时间数据,可以通过修改此参数值使PCF8563从不同的时间开始计时,例如从2020年4月1号09:30:00开始计时,需要将该参数值设置为48’h20_04_01_09_30_00。
顶层模块中主要完成对其余模块的例化。其中I2C驱动模块(i2c_dri)程序与“EEPROM读写实验”章节中的IIC驱动模块(i2c_dri)程序完全相同,有关IIC驱动模块的详细介绍请大家参考“EEPROM读写实验”。按键消抖模块可参考“按键控制蜂鸣器实验”。
PCF8563实时时钟控制模块的代码如下所示:
1 module pcf8563_ctrl #( 2 // 初始时间设置,从高到低为年到秒,各占8bit 3 parameter TIME_INIT = 48'h20_10_26_09_30_00)( 4 input clk , //时钟信号 5 input rst_n , //复位信号 6 7 //i2c interface 8 output reg i2c_rh_wl , //I2C读写控制信号 9 output reg i2c_exec , //I2C触发执行信号 10 output reg [15:0] i2c_addr , //I2C器件内地址 11 output reg [7:0] i2c_data_w, //I2C要写的数据 12 input [7:0] i2c_data_r, //I2C读出的数据 13 input i2c_done , //I2C一次操作完成 14 15 //PCF8563T的秒、分、时、日、月、年数据 16 output reg [7:0] sec, //秒 17 output reg [7:0] min, //分 18 output reg [7:0] hour, //时 19 output reg [7:0] day, //日 20 output reg [7:0] mon, //月 21 output reg [7:0] year //年 22 ); 23 24 //reg define 25 reg [3:0] flow_cnt ; // 状态流控制 26 reg [12:0] wait_cnt ; // 计数等待 27 28 //* 29 // main code 30 //* 31 32 //先向PCF8563中写入初始化日期和时间,再从中读出日期和时间 33 always @(posedge clk or negedge rst_n) begin 34 if(!rst_n) begin 35 sec <= 8'h0; 36 min <= 8'h0; 37 hour <= 8'h0; 38 day <= 8'h0; 39 mon <= 8'h0; 40 year <= 8'h0; 41 i2c_exec <= 1'b0; 42 i2c_rh_wl <= 1'b0; 43 i2c_addr <= 8'd0; 44 i2c_data_w <= 8'd0; 45 flow_cnt <= 4'd0; 46 wait_cnt <= 13'd0; 47 end 48 else begin 49 i2c_exec <= 1'b0; 50 case(flow_cnt) 51 //上电初始化 52 4'd0: begin 53 if(wait_cnt == 13'd8000) begin 54 wait_cnt<= 12'd0; 55 flow_cnt<= flow_cnt + 1'b1; 56 end 57 else 58 wait_cnt<= wait_cnt + 1'b1; 59 end 60 //写读秒 61 4'd1: begin 62 i2c_exec <= 1'b1; 63 i2c_addr <= 8'h02; 64 flow_cnt <= flow_cnt + 1'b1; 65 i2c_data_w<= TIME_INIT[7:0]; 66 end 67 4'd2: begin 68 if(i2c_done == 1'b1) begin 69 sec <= i2c_data_r[6:0]; 70 flow_cnt<= flow_cnt + 1'b1; 71 end 72 end 73 //写读分 74 4'd3: begin 75 i2c_exec <= 1'b1; 76 i2c_addr <= 8'h03; 77 flow_cnt <= flow_cnt + 1'b1; 78 i2c_data_w<= TIME_INIT[15:8]; 79 end 80 4'd4: begin 81 if(i2c_done == 1'b1) begin 82 min <= i2c_data_r[6:0]; 83 flow_cnt<= flow_cnt + 1'b1; 84 end 85 end 86 //写读时 87 4'd5: begin 88 i2c_exec <= 1'b1; 89 i2c_addr <= 8'h04; 90 flow_cnt <= flow_cnt + 1'b1; 91 i2c_data_w<= TIME_INIT[23:16]; 92 end 93 4'd6: begin 94 if(i2c_done == 1'b1) begin 95 hour <= i2c_data_r[5:0]; 96 flow_cnt<= flow_cnt + 1'b1; 97 end 98 end 99 //写读天 100 4'd7: begin 101 i2c_exec <= 1'b1; 102 i2c_addr <= 8'h05; 103 flow_cnt <= flow_cnt + 1'b1; 104 i2c_data_w<= TIME_INIT[31:24]; 105 end 106 4'd8: begin 107 if(i2c_done == 1'b1) begin 108 day <= i2c_data_r[5:0]; 109 flow_cnt<= flow_cnt + 1'b1; 110 end 111 end 112 //写读月 113 4'd9: begin 114 i2c_exec <= 1'b1; 115 i2c_addr <= 8'h07; 116 flow_cnt <= flow_cnt + 1'b1; 117 i2c_data_w<= TIME_INIT[39:32]; 118 end 119 4'd10: begin 120 if(i2c_done == 1'b1) begin 121 mon <= i2c_data_r[4:0]; 122 flow_cnt<= flow_cnt + 1'b1; 123 end 124 end 125 //写读年 126 4'd11: begin 127 i2c_exec <= 1'b1; 128 i2c_addr <= 8'h08; 129 flow_cnt <= flow_cnt + 1'b1; 130 i2c_data_w<= TIME_INIT[47:40]; 131 end 132 4'd12: begin 133 if(i2c_done == 1'b1) begin 134 year <= i2c_data_r; 135 i2c_rh_wl<= 1'b1; 136 flow_cnt <= 4'd1; 137 end 138 end 139 default: flow_cnt <= 4'd0; 140 endcase 141 end 142 end 143 144 endmodule
程序中定义了一个状态流控制计数器(flow_cnt),flow_cnt初始值为0,在flow_cnt等于0进行延时,随后从0开始累加至12,将初始日期和时间(TIME_INIT)写入PCF8563中;在flow_cnt等于12时,i2c_rh_wl(I2C读写控制信号)由低电平改为高电平,即IIC由写操作切换成读操作,与此同时,flow_cnt赋值为1,循环从PCF8563中读取秒、分、时、日、月和年。
下图为SignalTap抓取的波形图,从图中可以看到当前读到的时间为20年4月1日09:31:29。需要说明的是,flow_cnt在循环从0累加至12,由于flow_cnt等于奇数值的时间很短,所以图中奇数值被隐藏。

讯享网1 module key_sw_disp( 2 input clk , //时钟 3 input rst_n , //复位 4 5 input key_value , //按键 6 input [7:0] sec , //秒 7 input [7:0] min , //分钟 8 input [7:0] hour , //小时 9 input [7:0] day , //日 10 input [7:0] mon , //月 11 input [7:0] year , //年 12 13 output [5:0] point , //数码管小数点控制 14 output [23:0] disp_data //数码管显示的数值控制 15 ); 16 17 //reg define 18 reg sw_flag ; 19 reg key_value_d0; 20 reg key_value_d1; 21 22 //wire define 23 wire neg_key_value; 24 25 //* 26 // main code 27 //* 28 29 //采集输入信号的下降沿 30 assign neg_key_value = key_value_d1 & (~key_value_d0); 31 //切换输出数码管要显示的数据 32 assign disp_data = (sw_flag == 1'b0) ? {
hour,min,sec} : {
year,mon,day}; 33 //数码管小数点显示位置 34 assign point = 6'b010100; 35 36 //对输入的按键信号打两拍 37 always @(posedge clk or negedge rst_n) begin 38 if(!rst_n) begin 39 key_value_d0 <= 1'b0; 40 key_value_d1 <= 1'b0; 41 end 42 else begin 43 key_value_d0 <= key_value; 44 key_value_d1 <= key_value_d0; 45 end 46 end 47 48 //控制sw_flag信号翻转 49 always @(posedge clk or negedge rst_n) begin 50 if(!rst_n) 51 sw_flag <= 1'b0; 52 else if(neg_key_value) 53 sw_flag <= ~sw_flag; 54 end 55 56 endmodule
显示值切换模块的代码比较简单,由于数码管总共可以显示6位数据,没有办法同时显示日期和时间,因此根据输入的按键来切换数码管显示日期和时间。
sw_flag信号用来切换数码管显示日期和时间,sw_flag初始值为0,当检查到输入按键的下降沿后,sw_flag取反,如程序中第49至54行所示。当sw_flag等于0时,数码管显示时间值;当sw_flag等于1时,数码管显示日期值,如程序中第32行代码所示。
数码管BCD驱动模块的代码如下所示:
1 module seg_bcd_dri( 2 //input 3 input clk , // 时钟信号 4 input rst_n , // 复位信号 5 input [23:0] data , // 6个数码管要显示的数值 6 input [5:0] point , // 小数点具体显示的位置,从高到低,高有效 7 8 //output 9 output reg [5:0] seg_sel, // 数码管位选 10 output reg [7:0] seg_led // 数码管段选 11 ); 12 13 //parameter define 14 parameter WIDTH0 = 50_000; 15 16 //reg define 17 reg [15:0] cnt0; // 1ms计数 18 reg [2:0] cnt; // 切换显示数码管用 19 reg [3:0] data1; // 送给要显示的数码管,要亮的灯 20 reg point1; // 要显示的小数点 21 22 //* 23 // main code 24 //* 25 26 //计数1ms 27 always @(posedge clk or negedge rst_n) begin 28 if(rst_n == 1'b0) 29 cnt0 <= 15'b0; 30 else if(cnt0 < WIDTH0) 31 cnt0 <= cnt0 + 1'b1; 32 else 33 cnt0 <= 15'b0; 34 end 35 36 //计数器,用来计数6个状态(因为有6个灯) 37 always @(posedge clk or negedge rst_n) begin 38 if(rst_n == 1'b0) 39 cnt <= 3'b0; 40 else if(cnt < 3'd6) begin 41 if(cnt0 == WIDTH0) 42 cnt <= cnt + 1'b1; 43 else 44 cnt <= cnt; 45 end 46 else 47 cnt <= 3'b0; 48 end 49 50 //6个数码管轮流显示,完成刷新(从右到左) 51 always @(posedge clk or negedge rst_n) begin 52 if(rst_n == 1'b0) begin 53 seg_sel <= 6'b000001; 54 data1 <= 4'b0; 55 end 56 else begin 57 case (cnt) 58 3'd0:begin 59 seg_sel <= 6'b; 60 data1 <= data[3:0] ; 61 point1 <= point[0] ; 62 end 63 3'd1:begin 64 seg_sel <= 6'b; 65 data1 <= data[7:4] ; 66 point1 <= point[1] ; 67 end 68 3'd2:begin 69 seg_sel <= 6'b; 70 data1 <= data[11:8]; 71 point1 <= point[2] ; 72 end 73 3'd3:begin 74 seg_sel <= 6'b ; 75 data1 <= data[15:12]; 76 point1 <= point[3] ; 77 end 78 3'd4:begin 79 seg_sel <= 6'b ; 80 data1 <= data[19:16]; 81 point1 <= point[4] ; 82 end 83 3'd5:begin 84 seg_sel <= 6'b011111 ; 85 data1 <= data[23:20]; 86 point1 <= point[5] ; 87 end 88 default: begin 89 seg_sel <= 6'b000000; 90 data1 <= 4'b0; 91 point1 <= 1'b1; 92 end 93 endcase 94 end 95 end 96 97 //数码管显示数据 98 always @ (posedge clk or negedge rst_n) begin 99 if(rst_n == 1'b0) 100 seg_led <= 7'b0; 101 else begin 102 case(data1) 103 4'd0: seg_led <= {~point1,7'b}; 104 4'd1: seg_led <= {~point1,7'b}; 105 4'd2: seg_led <= {~point1,7'b0}; 106 4'd3: seg_led <= {~point1,7'b0}; 107 4'd4: seg_led <= {~point1,7'b0011001}; 108 4'd5: seg_led <= {~point1,7'b0010010}; 109 4'd6: seg_led <= {~point1,7'b0000010}; 110 4'd7: seg_led <= {~point1,7'b}; 111 4'd8: seg_led <= {~point1,7'b0000000}; 112 4'd9: seg_led <= {~point1,7'b0010000}; 113 default: seg_led <= {
point1,7'b}; 114 endcase 115 end 116 end 117 endmodule
由于是PCF8563的数据是BCD编码,从低到高每4位二进制数代表一位十进制数,所以在第57行的case语句块中,我们只需把data信号相应位的值赋给data1即可。有关数码管显示更详细的解释可参考“动态数码管显示实验”。
31.5下载验证
首先我们将下载器与开发板上的JTAG接口连接,下载器另外一端与电脑连接,然后连接电源线并打开电源开关。
最后我们下载程序,验证RTC实时时钟数码管显示功能。程序下载完成后观察到开发板上数码管显示的值为我们设置的初始日期值,并且在不断实时变化;当按下KEY0按键后,数码管显示年月日,说明PCF8563实时时钟数码管显示实验程序下载验证成功。
数码管显示的日期如下图所示:

图 31.5.1 数码管显示日期
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/127381.html