一、实验任务
使用领航者 Zynq 开发板上的 PCF8563 器件实现实时时钟的显示
二、实验介绍
(1)PCF8563 简介
PCF8563 是 PHILIPS 公司推出的一款工业级多功能时钟/日历芯片,具有报警功能、定时器功能、时钟 输出功能以及中断输出功能,能完成各种复杂的定时服务。其内部功能模块的框图如下图所示:
先看外左上角,那一块可以看作是提供时钟的一个模块,同时还具有分频功能,一个用来输出一个驱动功能。左下角是一个IIC接口和一个看门狗,看门狗当芯片工作不正常提供报警和复位。中间的功能模块是通过IIC协议和外部通信的。
PCF8563 有 16 个可寻址的 8 位寄存器,但不是所有位都有用到。前两个寄存器(内存地址 00H、01H) 用作控制寄存器和状态寄存器(CONTROL_STATUS);内存地址 02H~08H 用作 TIME 计时器(秒~年计时 器);地址 09H~0CH 用于报警(ALARM)寄存器(定义报警条件);地址 0DH 控制 CLKOUT 管脚的输出频率(时钟输出控制);地址 0EH 和 0FH 分别用于定时器控制寄存器和定时器寄存器。
右下角是中断,当定时器和报警达到一定条件就输出中断
(2)寄存器配置
N配置0,X任意。上图是每个寄存器的配置介绍。一般情况默认模式就可以。下面介绍时间功能。以秒为例
最高位并不参与时间显示,当电源电压低于 PCF8563 器件的最低供电电压时,VL 为“1”,表明内部完整的时钟周期信号不能被保证,可能导致时钟/日历数据不准确。
而计数的编码是BCD8421码
其他部分详细见数据手册。
(3)硬件设计
接口如图
左边是晶振提供时钟,中间是功能芯片PCF8563,6和5接口时IIC接口。8是接电源,由于我们的时钟断电后就会停止归零,所以用了右上角的设计,断电时还会有个电池供电。IIC通信就是我们FPGA要设计的了。
三、程序设计
根据实验任务,我们可以大致规划出系统的控制流程:ZYNQ 首先通过 I2C 总线向 PCF8563 写入初始时间值,然后不断地读取时间数据,并将读到的时间数据显示到 LCD 上。由此画出系统的功能框图如下所 示:
我们需要设计的就是FPGA中的三个模块。
设计好的原理图如下:
每个模块需要完成的功能可以从接口看。
IIC_DRI是和PCF8563通信的模块,1)负责写入初始值(PCF8563模块提供)和读取芯片内时间数据然后传给pcf8563_ctrl。2)负责输出驱动时钟 3)完成信号的输出
pcf8563_ctrl模块 1)接受来自i2c模块的数据,输出时间信息。2)负责控制读写和使能dri模块 3)提供写入地址和数据给dri模块即时间初始值。
lcd_disp显示模块 1)将控制模块输出的时间信息转换成RGB信号显示到显示屏上 2)注意这个模块的时钟不是dri_clk,而是系统时钟sys_clk。
以下是模块的详细介绍
控制模块 ,当前时间由顶层例化的设置优先TIME_INIT。这个模块主要是和IIC通信,写入数据和读芯片时间数据。最后是输出秒分时等信息。这里要按照PCF的配置去写入初始值,比如02h是秒,所以在ADDR为02h时写入TIME_INIT[7:0].因为都是8421码,都是十位数所以用8位表示。
读数据按照PCF8563有些是读6:0是时间数据。时间上,秒分时等十位都不超过7,只有年会。
同理天读[5:0]即可,5:4表示十位,天最多31天,就是3。两位刚好够其他的同理。不过写入的时候都是写八位。我们要按规定从秒开始写读,所以状态流如下。代码还是好理解的,要注意的是芯片上电要求等待8ms以上.所以状态0是等待。我们设置成16ms.且我们不写星期数据
写完年之后要拉高读写控制,使其为读状态。当其为读状态,就不会写数据了。以数据流1为例,读状态下,状态流和地址都会有作用但是无法写入数据所以初始值之后就不起作用了,写完地址接着执行2状态读数据。和之前的哑写一样。
module pcf8563_ctrl #(
// 初始时间设置,从高到低为年到秒,各占8bit
parameter TIME_INIT = 48'h19_10_26_09_30_00)(
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一次操作完成
//PCF8563T的秒、分、时、日、月、年数据
output reg [7:0] sec, //秒
output reg [7:0] min, //分
output reg [7:0] hour, //时
output reg [7:0] day, //日
output reg [7:0] mon, //月
output reg [7:0] year //年
);
//reg define
reg [3:0] flow_cnt ; // 状态流控制
reg [12:0] wait_cnt ; // 计数等待
//*****************************************************
//** main code
//*****************************************************
//先向PCF8563中写入初始化日期和时间,再从中读出日期和时间
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_INIT[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_INIT[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_INIT[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_INIT[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_INIT[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_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
这里补充一个分频的算法。E2PROM里写得不够细致。对于一个你要驱动的时钟,比如CLK,我们用SYS_CLK除以他得到他的分频系数,由于要驱动他还需要除以4以上。即分频系数=SYS_CLK/CLK/4,这个数表示在SYS_CLK下计数到这个数然后跳变,其实真正的周期还包括另一半,所以再除2。所得到的时钟频率SYS_CLK/分频系数。如这里的IIC驱动时钟分频系数是50,而系统时钟是50MHZ所以dri_clk是1mhz. 注意,ZDYZ对应视频中该讲代码没有再除二,所以得到的时钟频率是500khz对于等待时间是16ms.
源代码里有除2,即取[8:1]舍弃最低为。垃圾视频还不改正!
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
dri_clk <= 1'b0;
clk_cnt <= 10'd0;
end
else if(clk_cnt == clk_divide[8:1] - 1'd1) begin
clk_cnt <= 10'd0;
dri_clk <= ~dri_clk;
end
else
clk_cnt <= clk_cnt + 1'b1;
end
该模块将读出的数据传给LCD显示模块。下面介绍该模块
LCD_DISPLAY_CHAR MODULE
其中我们重点当然是lcd_display模块,我们要把其中接收到的时间信息输出为RGB信号(当接受到来自driver模块的坐标时,输出对应字符数据),送给driver模块,driver模块负责输出RGB信号给显示屏和输出像素坐标给display以及给ID模块读ID选择相应的显示屏型号然后输出型号ID给分频模块输出需要的像素时钟,来驱动dispplay和driver。关于ID读取在RGB彩条模块有详细介绍。
关于display模块的设计,目的是将pcf8563读出的数据和dri模块给出的对应的像素坐标结合输出RGB数据。
因此我们需要设计字符大小,字符相距宽度,颜色等。我们把字符第一行设计成年月日,第二行为现在时间。
数字字模用取模软件生成。C51格式。关于字模软件要在选项中设置生成的数据格式。比如前缀后缀我们不需要就清空。字符大小16*16,即每个字符有256个点,但是要注意英文和数字是8*16,共128个点,用128位来表示。
128'h00000018244242424242424224180000 ; // "0"每一行有8位,其实很简单比如前三行全是0,所以对应h000000。值得一提的是像素是从第一个点开始赋值,对应的是该字符字模数据的最高位,比如要对(1,1)赋值对应的是char[*][127],这里*指要赋值的数字比如实验中是2。
于是在每个字符区域要赋值的时候要注意,拿以下举例,其中[ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8 - ((pixel_xpos-CHAR_POS_X_1)%8) -1 ]是指对应的第一个数字字符区域的像素坐标,确切的来说是这128个像素里的第几个,由于是最高位对应第一个像素,所以有此算法。比如带入(1,1)则表示char[2][127],即第一个像素。在这里我们是想给他 赋值2,给其中为1的赋值黑色,其他就是背景色。当然这里是第一行字符。第二行要计算就是用[(CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8 - ((pixel_xpos-CHAR_POS_X_2)%8) -1 ]
对应视频中对此的算法又是不一样的。。。
我们如果要改变字符大小等,像字模还有各种参数都是要改变的。
if(char [2] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
而关于其他非固定的值,如年的十位。则需要从pcf8563控制模块输出的信息获取。该芯片的作用就是输出时间信息,年月日等,都是8位。年份因为也是8位只有十位个位,所以百位千位需要我们自己动手赋值。其他值由芯片提供。关于其中的进制等可以看手册。这里我们把他当成一块表就行。char[year[7:4]]其实也就是BCD8421码。翻译人话就是如果是数字x,在其坐标点指为1时,给其赋值成黑色。
if(char [year[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
这个模块主要作用就是1)在当前字符区域内,2)用提供的数字信息,3)显示字符。
module lcd_display(
input lcd_pclk ,
input rst_n ,
//日历数据
input [7:0] sec, //秒
input [7:0] min, //分
input [7:0] hour, //时
input [7:0] day, //日
input [7:0] mon, //月
input [7:0] year, //年
//LCD数据接口
input [10:0] pixel_xpos, //像素点横坐标
input [10:0] pixel_ypos, //像素点纵坐标
output reg [23:0] pixel_data //像素点数据
);
//parameter define
localparam CHAR_POS_X_1 = 11'd1; //第1行字符区域起始点横坐标
localparam CHAR_POS_Y_1 = 11'd1; //第1行字符区域起始点纵坐标
localparam CHAR_POS_X_2 = 11'd17; //第2行字符区域起始点横坐标
localparam CHAR_POS_Y_2 = 11'd17; //第2行字符区域起始点纵坐标
localparam CHAR_WIDTH_1 = 11'd80; //第1行字符区域的宽度,第1行共10个字符(加空格)
localparam CHAR_WIDTH_2 = 11'd64; //第2行字符区域的宽度,第2行共8个字符(加空格)
localparam CHAR_HEIGHT = 11'd16; //单个字符的高度
localparam WHITE = 24'hffffff; //背景色,白色
localparam BLACK = 24'h000000; //字符颜色,黑色
//reg define
reg [127:0] char [9:0] ; //字符数组
//*****************************************************
//** main code
//*****************************************************
//字符数组初始值,用于存储字模数据(由取模软件生成,单个数字字体大小:8*16)
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
//不同的区域绘制不同的像素数据
always @(posedge lcd_pclk or negedge rst_n ) begin
if (!rst_n) begin
pixel_data <= BLACK;
end
//在第一行显示年的千位 固定值"2"
else if( (pixel_xpos >= CHAR_POS_X_1)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*1)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (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)%8) -1 ] )
pixel_data <= BLACK; //显示字符为黑色
else
pixel_data <= WHITE; //显示字符区域背景为白色
end
//在第一行显示年的百位 固定值"0"
else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*1)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*2)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (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)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第一行显示年的十位
else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*2)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*3)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (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)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第一行显示年的个位
else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*3)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*4)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (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)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第一行显示空格
else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*4)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*5)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
pixel_data <= WHITE;
end
//在第一行显示月的十位
else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*5)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*6)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (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)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第一行显示月的个位
else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*6)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*7)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (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)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第一行显示空格
else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*7)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*8)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
pixel_data <= WHITE;
end
//在第一行显示日的十位
else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*8)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*9)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (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)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第一行显示日的个位
else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*9)
&& (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1)
&& (pixel_ypos >= CHAR_POS_Y_1)
&& (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)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第二行显示时的十位
else if( (pixel_xpos >= CHAR_POS_X_2)
&& (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*1)
&& (pixel_ypos >= CHAR_POS_Y_2)
&& (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)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第二行显示时的个位
else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*1)
&& (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*2)
&& (pixel_ypos >= CHAR_POS_Y_2)
&& (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)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第二行显示空格
else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*2)
&& (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*3)
&& (pixel_ypos >= CHAR_POS_Y_2)
&& (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT) ) begin
pixel_data <= WHITE;
end
//在第二行显示分的十位
else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*3)
&& (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*4)
&& (pixel_ypos >= CHAR_POS_Y_2)
&& (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)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第二行显示分的个位
else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*4)
&& (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*5)
&& (pixel_ypos >= CHAR_POS_Y_2)
&& (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)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第二行显示空格
else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*5)
&& (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*6)
&& (pixel_ypos >= CHAR_POS_Y_2)
&& (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT) ) begin
pixel_data <= WHITE;
end
//在第二行显示秒的十位
else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*6)
&& (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*7)
&& (pixel_ypos >= CHAR_POS_Y_2)
&& (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)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
//在第二行显示秒的个位
else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*7)
&& (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2)
&& (pixel_ypos >= CHAR_POS_Y_2)
&& (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)%8) -1 ] )
pixel_data <= BLACK;
else
pixel_data <= WHITE;
end
else begin
pixel_data <= WHITE; //屏幕背景为白色
end
end
endmodule
下面再介绍iic_dri模块,这个模块是FPGA和PCF8563芯片通信的模块,负责写入初始值(由于模块的通用性该模块的w_data是由外部提供的,这里是pcf8563模块),和从芯片读取时间数据,提供给FPGA中的pcf8563控制模块由该模块提取除年月日等信息。提取方式:所用的IIC协议是每八位读完就是完成一次操作,dri模块拉高done信号给pcf8563模块,从秒到年来读,比如先读的8位就是秒,再pcf模块中寄存带sec[7:0]中。依次。
在本次实验中bit_ctrl是0用的是8位器件内地址。器件地址是1010001,和上个实验不一样。
代码太长不放了。
总结:本次实验就是利用FPGA与PCF8563进行IIC通信写入初始数据,然后读取时间信息,传递给LCD模块输出在显示屏上。重点是PCF8563控制模块的设计和显示模块。其中控制模块是将iic_dri模块读取的数据提取成我们需要的时间信息,同时也负责初始值的写入,即将要写的数据和对应的地址提供给dri模块,由它来完成和芯片的通信。
而显示模块,主要就是字符显示模块的设计。包括字模的提取等等。有时间可以尝试更换显示的样子,大小排版等等。