正点原子 发表于 2021-11-11 17:13:12

《领航者ZYNQ之FPGA开发指南_V2》第二十四章 RTC实时时钟LCD显示

1)实验平台:正点原子领航者V2 ZYNQ开发板
2)章节摘自【正点原子】《领航者ZYNQ之FPGA开发指南_V2 》
3)购买链接:https://detail.tmall.com/item.htm?id=609032204975
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-329957-1-1.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流QQ群:90562473







第二十四章 RTC实时时钟LCD显示实验
       PCF8563是一款多功能时钟/日历芯片。因其功耗低、控制简单、封装小而广泛应用于电表、水表、传真机、便携式仪器等产品中。本章我们将使用领航者Zynq开发板上的PCF8563器件实现实时时钟的显示。
       本章包括以下几个部分:
       1.1PCF8563简介
       1.2实验任务
       1.3硬件设计
       1.4程序设计
       1.5下载验证

1.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)

1.2实验任务
       本节的实验任务是通过领航者Zynq开发板上的PCF8563实时时钟芯片,在RGB LCD液晶屏上来显示时间。
1.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}
set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33}

set_property -dict {PACKAGE_PIN E18 IOSTANDARD LVCMOS33}
set_property -dict {PACKAGE_PIN F17 IOSTANDARD LVCMOS33}

set_property -dict {PACKAGE_PIN W18 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN W19 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN R16 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN R17 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN W20 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN V20 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN P18 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN N17 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN V17 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN V18 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN T17 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN R18 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN Y18 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN Y19 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN P15 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN P16 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN V16 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN W16 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN T14 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN T15 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN Y17 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN Y16 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN T16 IOSTANDARD LVCMOS33} }]
set_property -dict {PACKAGE_PIN U17 IOSTANDARD LVCMOS33} }]

set_property -dict {PACKAGE_PIN N18 IOSTANDARD LVCMOS33}
set_property -dict {PACKAGE_PIN T20 IOSTANDARD LVCMOS33}
set_property -dict {PACKAGE_PIN U20 IOSTANDARD LVCMOS33}
set_property -dict {PACKAGE_PIN M20 IOSTANDARD LVCMOS33}
set_property -dict {PACKAGE_PIN P19 IOSTANDARD LVCMOS33}
set_property -dict {PACKAGE_PIN L17 IOSTANDARD LVCMOS33} 1.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      lcd_rgb,   //LCD RGB888颜色数据
12      
13      //RTC实时时钟
14      output               iic_scl,   //RTC的时钟线scl
15      inout                iic_sda      //RTC的数据线sda   
16      );                                                      
17
18//parameter define
19parameter    SLAVE_ADDR = 7'b101_0001   ; //器件地址(SLAVE_ADDR)
20parameter    BIT_CTRL   = 1'b0          ; //字地址位控制参数(16b/8b)
21parameter    CLK_FREQ   = 26'd50_000_000; //i2c_dri模块的驱动时钟频率(CLK_FREQ)
22parameter    I2C_FREQ   = 18'd250_000   ; //I2C的SCL时钟频率
23parameter    TIME_INIT= 48'h19_01_01_09_30_00;//初始时间
24
25//wire define
26wire          dri_clk   ;   //I2C操作时钟
27wire          i2c_exec;   //I2C触发控制
28wirei2c_addr;   //I2C操作地址
29wire[ 7:0]i2c_data_w;   //I2C写入的数据
30wire          i2c_done;   //I2C操作结束标志
31wire          i2c_ack   ;   //I2C应答标志 0:应答 1:未应答
32wire          i2c_rh_wl ;   //I2C读写控制
33wire[ 7:0]i2c_data_r;   //I2C读出的数据
34
35wire    sec      ;   //秒
36wire    min      ;   //分
37wire    hour   ;   //时
38wire    day      ;   //日
39wire    mon      ;   //月
40wire    year   ;   //年
41
42//*****************************************************
43//**                  main code
44//*****************************************************
45
46//i2c驱动模块
47i2c_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控制模块
70pcf8563_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字符显示模块
92lcd_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       parameterTIME_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   regi2c_addr, //I2C器件内地址
11      output   reg   i2c_data_w, //I2C要写的数据
12      input            i2c_data_r, //I2C读出的数据
13      input               i2c_done, //I2C一次操作完成
14
15      //PCF8563T的秒、分、时、日、月、年数据
16      output   reg   sec,      //秒
17      output   reg   min,      //分
18      output   reg   hour,       //时
19      output   reg   day,      //日
20      output   reg   mon,      //月
21      output   reg   year      //年
22);
23
24//reg define
25reg      flow_cnt;            // 状态流控制
26reg       wait_cnt;            // 计数等待
27
28//*****************************************************
29//**                  main code
30//*****************************************************
31
32//先向PCF8563中写入初始化日期和时间,再从中读出日期和时间
33always @(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;
66            end
67            4'd2: begin
68                  if(i2c_done == 1'b1) begin
69                      sec   <= i2c_data_r;
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;
79            end
80            4'd4: begin
81                  if(i2c_done == 1'b1) begin
82                      min   <= i2c_data_r;
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;
92            end
93            4'd6: begin
94                  if(i2c_done == 1'b1) begin
95                      hour    <= i2c_data_r;
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;
105             end
106             4'd8: begin
107               if(i2c_done == 1'b1) begin
108                     day   <= i2c_data_r;
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;
118             end
119             4'd10: begin
120               if(i2c_done == 1'b1) begin
121                     mon   <= i2c_data_r;
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;
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         sec,      //秒
7       input         min,      //分
8       input         hour,       //时
9       input         day,      //日
10      input         mon,      //月
11      input         year,       //年
12      
13      //LCD数据接口
14      input      pixel_xpos, //像素点横坐标
15      input      pixel_ypos, //像素点纵坐标
16      outputregpixel_data//像素点数据
17);
18
19//parameter define
20localparam CHAR_POS_X_1= 11'd1;//第1行字符区域起始点横坐标
21localparam CHAR_POS_Y_1= 11'd1;//第1行字符区域起始点纵坐标
22localparam CHAR_POS_X_2= 11'd17; //第2行字符区域起始点横坐标
23localparam CHAR_POS_Y_2= 11'd17; //第2行字符区域起始点纵坐标
24localparam CHAR_WIDTH_1= 11'd80; //第1行字符区域的宽度,第1行共10个字符(加空格)
25localparam CHAR_WIDTH_2= 11'd64; //第2行字符区域的宽度,第2行共8个字符(加空格)
26localparam CHAR_HEIGHT   = 11'd16; //单个字符的高度
27localparam WHITE= 24'hffffff;    //背景色,白色
28localparam BLACK= 24'h000000;    //字符颜色,黑色
29
30//reg define
31regchar ;      //字符数组
32
33//*****************************************************
34//**                  main code
35//*****************************************************
36
37//字符数组初始值,用于存储字模数据(由取模软件生成,单个数字字体大小:16*16)
38always @(posedge lcd_pclk ) begin
39      char <= 128'h00000018244242424242424224180000 ;// "0"
40      char <= 128'h000000107010101010101010107C0000 ;// "1"
41      char <= 128'h0000003C4242420404081020427E0000 ;// "2"
42      char <= 128'h0000003C424204180402024244380000 ;// "3"
43      char <= 128'h000000040C14242444447E04041E0000 ;// "4"
44      char <= 128'h0000007E404040586402024244380000 ;// "5"
45      char <= 128'h0000001C244040586442424224180000 ;// "6"
46      char <= 128'h0000007E444408081010101010100000 ;// "7"
47      char <= 128'h0000003C4242422418244242423C0000 ;// "8"
48      char <= 128'h0000001824424242261A020224380000 ;// "9"
49end
50
51//不同的区域绘制不同的像素数据
52always @(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 [ (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 [ (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 ] [ (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 ] [ (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 ] [ (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 ] [ (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 ] [ (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 ] [ (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 ] [ (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 ] [ (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 ] [ (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 ] [ (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 ] [ (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 ] [ (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上,字符颜色为黑色,背景色为白色。
1.5下载验证
       首先将FPC排线一端与RGB LCD模块上的J1接口连接,另一端与领航者开发板上的RGB TFTLCD接口连接。然后将下载器一端连电脑,另一端与开发板上的JTAG端口连接,最后连接电源线并打开电源开关。
       接下来我们下载程序,验证RGB LCD字符和图片显示的功能。下载完成后观察RGB LCD液晶屏上显示出日期和时间,并且时间在不断的计时,如下图所示,说明RGB TFT-LCD字符和图片显示程序下载验证成功。

图 7.5.13.1 实验结果
页: [1]
查看完整版本: 《领航者ZYNQ之FPGA开发指南_V2》第二十四章 RTC实时时钟LCD显示