超级版主
- 积分
- 4671
- 金钱
- 4671
- 注册时间
- 2019-5-8
- 在线时间
- 1224 小时
|
1)实验平台:正点原子领航者V2FPGA开发板
2) 章节摘自【正点原子】《领航者ZYNQ之FPGA开发指南 V2.0》
3)购买链接:https://detail.tmall.com/item.htm?id=609032204975
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz(V2).html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流QQ群:712557122
第二十四章 RTC实时时钟LCD显示实验
PCF8563是一款多功能时钟/日历芯片。因其功耗低、控制简单、封装小而广泛应用于电表、水表、传真机、便携式仪器等产品中。本章我们将使用领航者Zynq开发板上的PCF8563器件实现实时时钟的显示。
本章包括以下几个部分:
212424.1PCF8563简介
24.2实验任务
24.3硬件设计
24.4程序设计
24.5下载验证
24.1PCF8563简介
PCF8563是PHILIPS公司推出的一款工业级多功能时钟/日历芯片,具有报警功能、定时器功能、时钟输出功能以及中断输出功能,能完成各种复杂的定时服务。其内部功能模块的框图如下图所示:
图 7.5.13.1 PCF8563功能框图
PCF8563有16个可寻址的8位寄存器,但不是所有位都有用到。前两个寄存器(内存地址00H、01H)用作控制寄存器和状态寄存器(CONTROL_STATUS);内存地址02H~08H用作TIME计时器(秒~年计时器);地址09H~0CH用于报警(ALARM)寄存器(定义报警条件);地址0DH控制CLKOUT管脚的输出频率;地址0EH和0FH分别用于定时器控制寄存器和定时器寄存器。
秒、分钟、小时、日、月、年、分钟报警、小时报警、日报警寄存器中的数据编码格式为BCD,只有星期和星期报警寄存器中的数据不以BCD格式编码。BCD码(Binary-Coded Decimal‎)是一种二进制的数字编码形式,用四个二进制位来表示一位十进制数(0~9),能够使二进制和十进制之间的转换得以快捷的进行。
PCF8563通过I2C接口与Zynq进行通信。使用该器件时,Zynq先通过I2C接口向该器件相应的寄存器写入初始的时间数据(秒~年),然后通过I2C接口读取相应的寄存器的时间数据。有关I2C总线协议详细的介绍请大家参考“EEPROM读写实验”。
下面我们对本次实验用到的寄存器做简要的描述和说明,其他寄存器的描述和说明,请大家参考PCF8563的数据手册。
秒寄存器的的地址为02h,说明如下表所示:
表 24.1.1 秒寄存器描述(地址02h)
当电源电压低于PCF8563器件的最低供电电压时,VL为“1”,表明内部完整的时钟周期信号不能被保证,可能导致时钟/日历数据不准确。
BCD编码的秒数值如下表所示:
表 24.1.2 秒数值的BCD编码
寄存器的地址为03h,说明如下表所示:
表 24.1.3 分钟寄存器描述(地址03h)
小时寄存器的地址为04h,说明如下表所示:
表 24.1.4 小时寄存器描述(地址04h)
天寄存器的地址为05h,说明如下表所示:
表 24.1.5 天寄存器描述(地址05h)
当年计数器的值是闰年时,PCF8563自动给二月增加一个值,使其成为29天。
月/世纪寄存器的地址为07h,说明如下表所示:
表 24.1.6 月/世纪寄存器(地址07h)
表 24.1.7 月份表
年寄存器的地址为08h,说明如下表所示:
表 24.1.8 寄存器(地址08h)
24.2实验任务
本节的实验任务是通过领航者Zynq开发板上的PCF8563实时时钟芯片,在RGB LCD液晶屏上来显示时间。
24.3硬件设计
领航者开发板上PCF8563接口部分的原理图如下图所示。
图 7.5.13.1 PCF8563接口原理图
PCF8563作为I2C接口的从器件与EEPROM等模块统一挂接在领航者开发板上的IIC总线上。 OSCI、OSCO与外部32.768KHz的晶振相连,为芯片提供驱动时钟;SCL和SDA分别是I2C总线的串行时钟接口和串行数据接口。
由于本实验中的管脚较多,这里仅给出XDC约束语句,XDC约束语句如下
- set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
- set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
-
- set_property -dict {PACKAGE_PIN E18 IOSTANDARD LVCMOS33} [get_ports iic_scl]
- set_property -dict {PACKAGE_PIN F17 IOSTANDARD LVCMOS33} [get_ports iic_sda]
- set_property -dict {PACKAGE_PIN W18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[0]}]
- set_property -dict {PACKAGE_PIN W19 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[1]}]
- set_property -dict {PACKAGE_PIN R16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[2]}]
- set_property -dict {PACKAGE_PIN R17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[3]}]
- set_property -dict {PACKAGE_PIN W20 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[4]}]
- set_property -dict {PACKAGE_PIN V20 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[5]}]
- set_property -dict {PACKAGE_PIN P18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[6]}]
- set_property -dict {PACKAGE_PIN N17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[7]}]
- set_property -dict {PACKAGE_PIN V17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[8]}]
- set_property -dict {PACKAGE_PIN V18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[9]}]
- set_property -dict {PACKAGE_PIN T17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[10]}]
- set_property -dict {PACKAGE_PIN R18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[11]}]
- set_property -dict {PACKAGE_PIN Y18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[12]}]
- set_property -dict {PACKAGE_PIN Y19 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[13]}]
- set_property -dict {PACKAGE_PIN P15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[14]}]
- set_property -dict {PACKAGE_PIN P16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[15]}]
- set_property -dict {PACKAGE_PIN V16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[16]}]
- set_property -dict {PACKAGE_PIN W16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[17]}]
- set_property -dict {PACKAGE_PIN T14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[18]}]
- set_property -dict {PACKAGE_PIN T15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[19]}]
- set_property -dict {PACKAGE_PIN Y17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[20]}]
- set_property -dict {PACKAGE_PIN Y16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[21]}]
- set_property -dict {PACKAGE_PIN T16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[22]}]
- set_property -dict {PACKAGE_PIN U17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[23]}]
- set_property -dict {PACKAGE_PIN N18 IOSTANDARD LVCMOS33} [get_ports lcd_hs]
- set_property -dict {PACKAGE_PIN T20 IOSTANDARD LVCMOS33} [get_ports lcd_vs]
- set_property -dict {PACKAGE_PIN U20 IOSTANDARD LVCMOS33} [get_ports lcd_de]
- set_property -dict {PACKAGE_PIN M20 IOSTANDARD LVCMOS33} [get_ports lcd_bl]
- set_property -dict {PACKAGE_PIN P19 IOSTANDARD LVCMOS33} [get_ports lcd_clk]
- set_property -dict {PACKAGE_PIN L17 IOSTANDARD LVCMOS33} [get_ports lcd_rst]
复制代码
24.4程序设计
根据实验任务,我们可以大致规划出系统的控制流程:ZYNQ首先通过I2C总线向PCF8563写入初始时间值,然后不断地读取时间数据,并将读到的时间数据显示到LCD上。由此画出系统的功能框图如下所示:
图 7.5.13.1 PCF8563T实时时钟LCD显示系统框图
由系统框图可知,顶层模块(rtc_lcd)例化了以下三个模块,分别是IIC驱动模块(iic_dri)、PCF8563控制模块(pcf8563_ctrl)和LCD字符显示模块(lcd_disp_char)。其中LCD字符显示模块例化了读取ID模块(rd_id)、时钟分频模块(clk_div)、LCD显示模块(lcd_display)以及LCD驱动模块(lcd_driver)。
各模块端口及信号连接如图 7.5.13.2所示:
图 7.5.13.2 顶层模块原理图
PCF8563实时时钟控制模块(pcf8563_ctrl)通过与IIC驱动模块(iic_dri)进行通信来实现对PCF8563实时时钟数据的读取;PCF8563实时时钟控制模块(pcf8563_ctrl)再将从IIC读取的时间数据送给LCD字符显示模块(lcd_disp_char),以进行显示。
顶层模块的代码如下:
- 1 module rtc_lcd(
- 2 input sys_clk, //系统时钟
- 3 input sys_rst_n, //系统复位
- 4
- 5 //RGB LCD接口
- 6 output lcd_de, //LCD 数据使能信号
- 7 output lcd_hs, //LCD 行同步信号
- 8 output lcd_vs, //LCD 场同步信号
- 9 output lcd_bl, //LCD 背光控制信号
- 10 output lcd_clk, //LCD 像素时钟
- 11 inout [23:0] lcd_rgb, //LCD RGB888颜色数据
- 12
- 13 //RTC实时时钟
- 14 output iic_scl, //RTC的时钟线scl
- 15 inout iic_sda //RTC的数据线sda
- 16 );
- 17
- 18 //parameter define
- 19 parameter SLAVE_ADDR = 7'b101_0001 ; //器件地址(SLAVE_ADDR)
- 20 parameter BIT_CTRL = 1'b0 ; //字地址位控制参数(16b/8b)
- 21 parameter CLK_FREQ = 26'd50_000_000; //i2c_dri模块的驱动时钟频率(CLK_FREQ)
- 22 parameter I2C_FREQ = 18'd250_000 ; //I2C的SCL时钟频率
- 23 parameter TIME_INIT = 48'h19_01_01_09_30_00;//初始时间
- 24
- 25 //wire define
- 26 wire dri_clk ; //I2C操作时钟
- 27 wire i2c_exec ; //I2C触发控制
- 28 wire [15:0] i2c_addr ; //I2C操作地址
- 29 wire [ 7:0] i2c_data_w; //I2C写入的数据
- 30 wire i2c_done ; //I2C操作结束标志
- 31 wire i2c_ack ; //I2C应答标志 0:应答 1:未应答
- 32 wire i2c_rh_wl ; //I2C读写控制
- 33 wire [ 7:0] i2c_data_r; //I2C读出的数据
- 34
- 35 wire [7:0] sec ; //秒
- 36 wire [7:0] min ; //分
- 37 wire [7:0] hour ; //时
- 38 wire [7:0] day ; //日
- 39 wire [7:0] mon ; //月
- 40 wire [7:0] year ; //年
- 41
- 42 //*****************************************************
- 43 //** main code
- 44 //*****************************************************
- 45
- 46 //i2c驱动模块
- 47 i2c_dri #(
- 48 .SLAVE_ADDR (SLAVE_ADDR), //EEPROM从机地址
- 49 .CLK_FREQ (CLK_FREQ ), //模块输入的时钟频率
- 50 .I2C_FREQ (I2C_FREQ ) //IIC_SCL的时钟频率
- 51 ) u_i2c_dri(
- 52 .clk (sys_clk ),
- 53 .rst_n (sys_rst_n ),
- 54 //i2c interface
- 55 .i2c_exec (i2c_exec ),
- 56 .bit_ctrl (BIT_CTRL ),
- 57 .i2c_rh_wl (i2c_rh_wl ),
- 58 .i2c_addr (i2c_addr ),
- 59 .i2c_data_w (i2c_data_w),
- 60 .i2c_data_r (i2c_data_r),
- 61 .i2c_done (i2c_done ),
- 62 .i2c_ack (i2c_ack ),
- 63 .scl (iic_scl ),
- 64 .sda (iic_sda ),
- 65 //user interface
- 66 .dri_clk (dri_clk )
- 67 );
- 68
- 69 //PCF8563控制模块
- 70 pcf8563_ctrl #(
- 71 .TIME_INIT (TIME_INIT)
- 72 )u_pcf8563_ctrl(
- 73 .clk (dri_clk ),
- 74 .rst_n (sys_rst_n ),
- 75 //IIC
- 76 .i2c_rh_wl (i2c_rh_wl ),
- 77 .i2c_exec (i2c_exec ),
- 78 .i2c_addr (i2c_addr ),
- 79 .i2c_data_w (i2c_data_w),
- 80 .i2c_data_r (i2c_data_r),
- 81 .i2c_done (i2c_done ),
- 82 //时间和日期
- 83 .sec (sec ),
- 84 .min (min ),
- 85 .hour (hour ),
- 86 .day (day ),
- 87 .mon (mon ),
- 88 .year (year )
- 89 );
- 90
- 91 //LCD字符显示模块
- 92 lcd_disp_char u_lcd_disp_char(
- 93 .sys_clk (sys_clk ),
- 94 .sys_rst_n (sys_rst_n ),
- 95 //时间和日期
- 96 .sec (sec ),
- 97 .min (min ),
- 98 .hour (hour ),
- 99 .day (day ),
- 100 .mon (mon ),
- 101 .year (year ),
- 102 //RGB LCD接口
- 103 .lcd_de (lcd_de ),
- 104 .lcd_hs (lcd_hs ),
- 105 .lcd_vs (lcd_vs ),
- 106 .lcd_bl (lcd_bl ),
- 107 .lcd_clk (lcd_clk ),
- 108 .lcd_rgb (lcd_rgb )
- 109 );
- 110
- 111 endmodule
复制代码
代码中第18至23行定义了一些参数,其中TIME_INIT表示RTC实时时钟的初始日期和时间,可以通过修改此参数值使PCF8563从不同的时间开始计时,例如从2019年1月1号09:30:00开始计时,需要将该参数值设置为48’h190101093000。
顶层模块中主要完成对其余模块的例化。其中I2C驱动模块(iic_dri)的代码与“EEPROM读写实验”章节中的IIC驱动模块完全相同,只是在例化时对字地址位控制(BIT_CTRL)和IIC器件地址(SLAVE_ADDR)两个参数作了修改,有关IIC驱动模块的详细介绍请大家参考“EEPROM读写实验”。
PCF8563实时时钟控制模块的代码如下所示:
- 1 module pcf8563_ctrl #(
- 2 // 初始时间设置,从高到低为年到秒,各占8bit
- 3 parameter TIME_INIT = 48'h19_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),先将初始日期和时间(TIME_INIT)写入PCF8563中,然后会循环从PCF8563中读出秒、分、时、日、月和年。在写操作是i2c_rh_wl(I2C读写控制信号)为低电平,读操作时拉高i2c_rh_wl信号。
LCD字符显示模块(lcd_disp_char)的代码由“RGB LCD字符和图片显示”实验的代码修改而来,除lcd_disp_char顶层模块外,唯一不同的地方在LCD显示模块。
LCD显示模块的代码如下所示:
- 1 module lcd_display(
- 2 input lcd_pclk ,
- 3 input rst_n ,
- 4
- 5 //日历数据
- 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 //LCD数据接口
- 14 input [10:0] pixel_xpos, //像素点横坐标
- 15 input [10:0] pixel_ypos, //像素点纵坐标
- 16 output reg [23:0] pixel_data //像素点数据
- 17 );
- 18
- 19 //parameter define
- 20 localparam CHAR_POS_X_1 = 11'd1; //第1行字符区域起始点横坐标
- 21 localparam CHAR_POS_Y_1 = 11'd1; //第1行字符区域起始点纵坐标
- 22 localparam CHAR_POS_X_2 = 11'd17; //第2行字符区域起始点横坐标
- 23 localparam CHAR_POS_Y_2 = 11'd17; //第2行字符区域起始点纵坐标
- 24 localparam CHAR_WIDTH_1 = 11'd80; //第1行字符区域的宽度,第1行共10个字符(加空格)
- 25 localparam CHAR_WIDTH_2 = 11'd64; //第2行字符区域的宽度,第2行共8个字符(加空格)
- 26 localparam CHAR_HEIGHT = 11'd16; //单个字符的高度
- 27 localparam WHITE = 24'hffffff; //背景色,白色
- 28 localparam BLACK = 24'h000000; //字符颜色,黑色
- 29
- 30 //reg define
- 31 reg [127:0] char [9:0] ; //字符数组
- 32
- 33 //*****************************************************
- 34 //** main code
- 35 //*****************************************************
- 36
- 37 //字符数组初始值,用于存储字模数据(由取模软件生成,单个数字字体大小:16*16)
- 38 always @(posedge lcd_pclk ) begin
- 39 char[0] <= 128'h00000018244242424242424224180000 ; // "0"
- 40 char[1] <= 128'h000000107010101010101010107C0000 ; // "1"
- 41 char[2] <= 128'h0000003C4242420404081020427E0000 ; // "2"
- 42 char[3] <= 128'h0000003C424204180402024244380000 ; // "3"
- 43 char[4] <= 128'h000000040C14242444447E04041E0000 ; // "4"
- 44 char[5] <= 128'h0000007E404040586402024244380000 ; // "5"
- 45 char[6] <= 128'h0000001C244040586442424224180000 ; // "6"
- 46 char[7] <= 128'h0000007E444408081010101010100000 ; // "7"
- 47 char[8] <= 128'h0000003C4242422418244242423C0000 ; // "8"
- 48 char[9] <= 128'h0000001824424242261A020224380000 ; // "9"
- 49 end
- 50
- 51 //不同的区域绘制不同的像素数据
- 52 always @(posedge lcd_pclk or negedge rst_n ) begin
- 53 if (!rst_n) begin
- 54 pixel_data <= BLACK;
- 55 end
- 56
- 57 //在第一行显示年的千位 固定值"2"
- 58 else if( (pixel_xpos >= CHAR_POS_X_1)
- 59 && (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*1)
- 60 && (pixel_ypos >= CHAR_POS_Y_1)
- 61 && (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
- 62 if(char [2] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- 63 - ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
- 64 pixel_data <= BLACK; //显示字符为黑色
- 65 else
- 66 pixel_data <= WHITE; //显示字符区域背景为白色
- 67 end
- 68
- 69 //在第一行显示年的百位 固定值"0"
- 70 else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*1)
- 71 && (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*2)
- 72 && (pixel_ypos >= CHAR_POS_Y_1)
- 73 && (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
- 74 if(char [0] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- 75 - ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
- 76 pixel_data <= BLACK;
- 77 else
- 78 pixel_data <= WHITE;
- 79 end
- 80
- 81 //在第一行显示年的十位
- 82 else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*2)
- 83 && (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*3)
- 84 && (pixel_ypos >= CHAR_POS_Y_1)
- 85 && (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
- 86 if(char [year[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- 87 - ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
- 88 pixel_data <= BLACK;
- 89 else
- 90 pixel_data <= WHITE;
- 91 end
- 92
- 93 //在第一行显示年的个位
- 94 else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*3)
- 95 && (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*4)
- 96 && (pixel_ypos >= CHAR_POS_Y_1)
- 97 && (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
- 98 if(char [year[3:0]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- 99 - ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
- 100 pixel_data <= BLACK;
- 101 else
- 102 pixel_data <= WHITE;
- 103 end
- 104
- 105 //在第一行显示空格
- 106 else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*4)
- 107 && (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*5)
- 108 && (pixel_ypos >= CHAR_POS_Y_1)
- 109 && (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
- 110 pixel_data <= WHITE;
- 111 end
- 112
- 113 //在第一行显示月的十位
- 114 else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*5)
- 115 && (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*6)
- 116 && (pixel_ypos >= CHAR_POS_Y_1)
- 117 && (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT)) begin
- 118 if(char [mon[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- 119 - ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
- 120 pixel_data <= BLACK;
- 121 else
- 122 pixel_data <= WHITE;
- 123 end
- 124
- 125 //在第一行显示月的个位
- 126 else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*6)
- 127 && (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*7)
- 128 && (pixel_ypos >= CHAR_POS_Y_1)
- 129 && (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
- 130 if(char [mon[3:0]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- 131 - ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
- 132 pixel_data <= BLACK;
- 133 else
- 134 pixel_data <= WHITE;
- 135 end
- 136
- 137 //在第一行显示空格
- 138 else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*7)
- 139 && (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*8)
- 140 && (pixel_ypos >= CHAR_POS_Y_1)
- 141 && (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
- 142 pixel_data <= WHITE;
- 143 end
- 144
- 145 //在第一行显示日的十位
- 146 else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*8)
- 147 && (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1/10*9)
- 148 && (pixel_ypos >= CHAR_POS_Y_1)
- 149 && (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
- 150 if(char [day[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- 151 - ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
- 152 pixel_data <= BLACK;
- 153 else
- 154 pixel_data <= WHITE;
- 155 end
- 156
- 157 //在第一行显示日的个位
- 158 else if( (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*9)
- 159 && (pixel_xpos < CHAR_POS_X_1 + CHAR_WIDTH_1)
- 160 && (pixel_ypos >= CHAR_POS_Y_1)
- 161 && (pixel_ypos < CHAR_POS_Y_1 + CHAR_HEIGHT) ) begin
- 162 if(char [day[3:0]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
- 163 - ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
- 164 pixel_data <= BLACK;
- 165 else
- 166 pixel_data <= WHITE;
- 167 end
- 168
- 169 //在第二行显示时的十位
- 170 else if( (pixel_xpos >= CHAR_POS_X_2)
- 171 && (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*1)
- 172 && (pixel_ypos >= CHAR_POS_Y_2)
- 173 && (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT) ) begin
- 174 if(char [hour[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8
- 175 - ((pixel_xpos-CHAR_POS_X_2)%8) -1 ] )
- 176 pixel_data <= BLACK;
- 177 else
- 178 pixel_data <= WHITE;
- 179 end
- 180
- 181 //在第二行显示时的个位
- 182 else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*1)
- 183 && (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*2)
- 184 && (pixel_ypos >= CHAR_POS_Y_2)
- 185 && (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT) ) begin
- 186 if(char [hour[3:0]] [ (CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8
- 187 - ((pixel_xpos-CHAR_POS_X_2)%8) -1 ] )
- 188 pixel_data <= BLACK;
- 189 else
- 190 pixel_data <= WHITE;
- 191 end
- 192
- 193 //在第二行显示空格
- 194 else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*2)
- 195 && (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*3)
- 196 && (pixel_ypos >= CHAR_POS_Y_2)
- 197 && (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT) ) begin
- 198 pixel_data <= WHITE;
- 199 end
- 200
- 201 //在第二行显示分的十位
- 202 else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*3)
- 203 && (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*4)
- 204 && (pixel_ypos >= CHAR_POS_Y_2)
- 205 && (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT) ) begin
- 206 if(char [min[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8
- 207 - ((pixel_xpos-CHAR_POS_X_2)%8) -1 ] )
- 208 pixel_data <= BLACK;
- 209 else
- 210 pixel_data <= WHITE;
- 211 end
- 212
- 213 //在第二行显示分的个位
- 214 else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*4)
- 215 && (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*5)
- 216 && (pixel_ypos >= CHAR_POS_Y_2)
- 217 && (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT) ) begin
- 218 if(char [min[3:0]] [ (CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8
- 219 - ((pixel_xpos-CHAR_POS_X_2)%8) -1 ] )
- 220 pixel_data <= BLACK;
- 221 else
- 222 pixel_data <= WHITE;
- 223 end
- 224
- 225 //在第二行显示空格
- 226 else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*5)
- 227 && (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*6)
- 228 && (pixel_ypos >= CHAR_POS_Y_2)
- 229 && (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT) ) begin
- 230 pixel_data <= WHITE;
- 231 end
- 232
- 233 //在第二行显示秒的十位
- 234 else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*6)
- 235 && (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2/8*7)
- 236 && (pixel_ypos >= CHAR_POS_Y_2)
- 237 && (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT) ) begin
- 238 if(char [sec[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8
- 239 - ((pixel_xpos-CHAR_POS_X_2)%8) -1 ] )
- 240 pixel_data <= BLACK;
- 241 else
- 242 pixel_data <= WHITE;
- 243 end
- 244
- 245 //在第二行显示秒的个位
- 246 else if( (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*7)
- 247 && (pixel_xpos < CHAR_POS_X_2 + CHAR_WIDTH_2)
- 248 && (pixel_ypos >= CHAR_POS_Y_2)
- 249 && (pixel_ypos < CHAR_POS_Y_2 + CHAR_HEIGHT) ) begin
- 250 if(char [sec[3:0]] [ (CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8
- 251 - ((pixel_xpos-CHAR_POS_X_2)%8) -1 ] )
- 252 pixel_data <= BLACK;
- 253 else
- 254 pixel_data <= WHITE;
- 255 end
- 256
- 257 else begin
- 258 pixel_data <= WHITE; //屏幕背景为白色
- 259 end
- 260 end
- 261
- 262 endmodule
复制代码
我们的显示内容首先分成两行,第一行显示年月日,第二行显示时分秒。程序中第19至28行代码定义了一些参数,前4个参数定义每一行字符显示的参考点,结合具体参数值我们知道:第一行是(1,1),第二行是(17,17),接着后面两个参数分别定义了每一行各自显示的宽度(长度),分别是80和64,最后两个参数定义了字符的颜色和背景色,字符颜色为黑色,背景色为白色。
代码第38到49行定义了0到9每个阿拉伯数字所对应的数组,具体的每个数组的字模数据都是一个长度为128的数组,实际上我们把二维数组的所有数据都放在了第一行上,使用时把它看成一个二维数组,大小为16*8bit,16行,每一行有8位数据。
代码第57到67行是一个具体的字符显示的逻辑。首先判断当前像素坐标的位置,如代码第58到61行,如果处在字符显示的区域则开始根据字符数组值来显示像素。显示时,数组参数pixel_xpos,pixel_ypos分别从小到大取不同的值时,代入数组,此时我们实际上就是在从左到右,从上到下扫描一个字符像素平面,pixel_xpos变化表示行扫描,pixel_ypos则表示列扫描。
对于第62行的代码,“ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8”,我们不难理解“*8”的由来,因为在查找数组元素的时候,pixel_ypos的每次变化表示换到下一行扫描,一行跨过8个数据,所有乘以8。这里就可总结一下:字符数组一行的128个数据从高位到低位,每8位代表一行,分别对应点阵中该行从左向右的每一个像素点。
代码第62行到63行是对数组的每个元素分别赋值,具体是数组元素为1的点赋值为黑色,否则为白色。其它字符的显示逻辑和上面类似,这里不再赘述。
程序中第37至49行代码初始化字符数组的值,即数字“0”~“9”的字模数据,由取模软件生成,先将软件设置成字符模式,取模软件的设置如下:
图 7.5.13.3 字符软件设置
这里将点阵设置为16,即一个数字的字符用一行来表示。
生成字模的界面如下:
图 7.5.13.4 生成字模的软件设置
程序中第51行至260行代码根据输入的日期、时间和字符区域的坐标显示在LCD上,字符颜色为黑色,背景色为白色。
24.5下载验证
首先将FPC排线一端与RGB LCD模块上的J1接口连接,另一端与领航者开发板上的RGB TFTLCD接口连接。然后将下载器一端连电脑,另一端与开发板上的JTAG端口连接,最后连接电源线并打开电源开关。
接下来我们下载程序,验证RGB LCD字符和图片显示的功能。下载完成后观察RGB LCD液晶屏上显示出日期和时间,并且时间在不断的计时,如下图所示,说明RGB TFT-LCD字符和图片显示程序下载验证成功。
图 7.5.13.1 实验结果
|
|