OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 4523|回复: 0

[XILINX] 《领航者ZYNQ之FPGA开发指南 V2.0》第二十四章 RTC实时时钟LCD显示实验

[复制链接]

1118

主题

1129

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4671
金钱
4671
注册时间
2019-5-8
在线时间
1224 小时
发表于 2021-11-19 10:45:06 | 显示全部楼层 |阅读模式
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 QQ群.png

原子哥.jpg

微信公众号.png





第二十四章 RTC实时时钟LCD显示实验






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


24.1PCF8563简介
PCF8563是PHILIPS公司推出的一款工业级多功能时钟/日历芯片,具有报警功能、定时器功能、时钟输出功能以及中断输出功能,能完成各种复杂的定时服务。其内部功能模块的框图如下图所示:
第二十四章 RTC实时时钟LCD显示实验265.png
图 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)
表1.png
当电源电压低于PCF8563器件的最低供电电压时,VL为“1”,表明内部完整的时钟周期信号不能被保证,可能导致时钟/日历数据不准确。
BCD编码的秒数值如下表所示:
表 24.1.2 秒数值的BCD编码
第二十四章 RTC实时时钟LCD显示实验1168.png
寄存器的地址为03h,说明如下表所示:
表 24.1.3 分钟寄存器描述(地址03h)
表2.png
小时寄存器的地址为04h,说明如下表所示:
表 24.1.4 小时寄存器描述(地址04h)
表3.png
天寄存器的地址为05h,说明如下表所示:
表 24.1.5 天寄存器描述(地址05h)
当年计数器的值是闰年时,PCF8563自动给二月增加一个值,使其成为29天。
月/世纪寄存器的地址为07h,说明如下表所示:
表 24.1.6 月/世纪寄存器(地址07h)
表4.png
表 24.1.7 月份表
表5.png
年寄存器的地址为08h,说明如下表所示:
表 24.1.8 寄存器(地址08h)
表6.png
24.2实验任务
本节的实验任务是通过领航者Zynq开发板上的PCF8563实时时钟芯片,在RGB LCD液晶屏上来显示时间。
24.3硬件设计
领航者开发板上PCF8563接口部分的原理图如下图所示。
第二十四章 RTC实时时钟LCD显示实验2230.png
图 7.5.13.1 PCF8563接口原理图
PCF8563作为I2C接口的从器件与EEPROM等模块统一挂接在领航者开发板上的IIC总线上。 OSCI、OSCO与外部32.768KHz的晶振相连,为芯片提供驱动时钟;SCL和SDA分别是I2C总线的串行时钟接口和串行数据接口。
由于本实验中的管脚较多,这里仅给出XDC约束语句,XDC约束语句如下
  1. set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
  2. set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
  3.   
  4. set_property -dict {PACKAGE_PIN E18 IOSTANDARD LVCMOS33} [get_ports iic_scl]
  5. set_property -dict {PACKAGE_PIN F17 IOSTANDARD LVCMOS33} [get_ports iic_sda]

  6. set_property -dict {PACKAGE_PIN W18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[0]}]
  7. set_property -dict {PACKAGE_PIN W19 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[1]}]
  8. set_property -dict {PACKAGE_PIN R16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[2]}]
  9. set_property -dict {PACKAGE_PIN R17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[3]}]
  10. set_property -dict {PACKAGE_PIN W20 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[4]}]
  11. set_property -dict {PACKAGE_PIN V20 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[5]}]
  12. set_property -dict {PACKAGE_PIN P18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[6]}]
  13. set_property -dict {PACKAGE_PIN N17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[7]}]
  14. set_property -dict {PACKAGE_PIN V17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[8]}]
  15. set_property -dict {PACKAGE_PIN V18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[9]}]
  16. set_property -dict {PACKAGE_PIN T17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[10]}]
  17. set_property -dict {PACKAGE_PIN R18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[11]}]
  18. set_property -dict {PACKAGE_PIN Y18 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[12]}]
  19. set_property -dict {PACKAGE_PIN Y19 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[13]}]
  20. set_property -dict {PACKAGE_PIN P15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[14]}]
  21. set_property -dict {PACKAGE_PIN P16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[15]}]
  22. set_property -dict {PACKAGE_PIN V16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[16]}]
  23. set_property -dict {PACKAGE_PIN W16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[17]}]
  24. set_property -dict {PACKAGE_PIN T14 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[18]}]
  25. set_property -dict {PACKAGE_PIN T15 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[19]}]
  26. set_property -dict {PACKAGE_PIN Y17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[20]}]
  27. set_property -dict {PACKAGE_PIN Y16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[21]}]
  28. set_property -dict {PACKAGE_PIN T16 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[22]}]
  29. set_property -dict {PACKAGE_PIN U17 IOSTANDARD LVCMOS33} [get_ports {lcd_rgb[23]}]

  30. set_property -dict {PACKAGE_PIN N18 IOSTANDARD LVCMOS33} [get_ports lcd_hs]
  31. set_property -dict {PACKAGE_PIN T20 IOSTANDARD LVCMOS33} [get_ports lcd_vs]
  32. set_property -dict {PACKAGE_PIN U20 IOSTANDARD LVCMOS33} [get_ports lcd_de]
  33. set_property -dict {PACKAGE_PIN M20 IOSTANDARD LVCMOS33} [get_ports lcd_bl]
  34. set_property -dict {PACKAGE_PIN P19 IOSTANDARD LVCMOS33} [get_ports lcd_clk]
  35. set_property -dict {PACKAGE_PIN L17 IOSTANDARD LVCMOS33} [get_ports lcd_rst]
复制代码


24.4程序设计
根据实验任务,我们可以大致规划出系统的控制流程:ZYNQ首先通过I2C总线向PCF8563写入初始时间值,然后不断地读取时间数据,并将读到的时间数据显示到LCD上。由此画出系统的功能框图如下所示:
第二十四章 RTC实时时钟LCD显示实验5338.png
图 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所示:
第二十四章 RTC实时时钟LCD显示实验5656.png
图 7.5.13.2 顶层模块原理图
PCF8563实时时钟控制模块(pcf8563_ctrl)通过与IIC驱动模块(iic_dri)进行通信来实现对PCF8563实时时钟数据的读取;PCF8563实时时钟控制模块(pcf8563_ctrl)再将从IIC读取的时间数据送给LCD字符显示模块(lcd_disp_char),以进行显示。
顶层模块的代码如下:
  1. 1   module rtc_lcd(
  2. 2       input                sys_clk,     //系统时钟
  3. 3       input                sys_rst_n,   //系统复位
  4. 4   
  5. 5       //RGB LCD接口
  6. 6       output               lcd_de,      //LCD 数据使能信号
  7. 7       output               lcd_hs,      //LCD 行同步信号
  8. 8       output               lcd_vs,      //LCD 场同步信号
  9. 9       output               lcd_bl,      //LCD 背光控制信号
  10. 10      output               lcd_clk,     //LCD 像素时钟
  11. 11      inout        [23:0]  lcd_rgb,     //LCD RGB888颜色数据
  12. 12      
  13. 13      //RTC实时时钟
  14. 14      output               iic_scl,     //RTC的时钟线scl
  15. 15      inout                iic_sda      //RTC的数据线sda   
  16. 16      );                                                      
  17. 17  
  18. 18  //parameter define
  19. 19  parameter    SLAVE_ADDR = 7'b101_0001   ; //器件地址(SLAVE_ADDR)
  20. 20  parameter    BIT_CTRL   = 1'b0          ; //字地址位控制参数(16b/8b)
  21. 21  parameter    CLK_FREQ   = 26'd50_000_000; //i2c_dri模块的驱动时钟频率(CLK_FREQ)
  22. 22  parameter    I2C_FREQ   = 18'd250_000   ; //I2C的SCL时钟频率
  23. 23  parameter    TIME_INIT  = 48'h19_01_01_09_30_00;//初始时间
  24. 24  
  25. 25  //wire define
  26. 26  wire          dri_clk   ;   //I2C操作时钟
  27. 27  wire          i2c_exec  ;   //I2C触发控制
  28. 28  wire  [15:0]  i2c_addr  ;   //I2C操作地址
  29. 29  wire  [ 7:0]  i2c_data_w;   //I2C写入的数据
  30. 30  wire          i2c_done  ;   //I2C操作结束标志
  31. 31  wire          i2c_ack   ;   //I2C应答标志 0:应答 1:未应答
  32. 32  wire          i2c_rh_wl ;   //I2C读写控制
  33. 33  wire  [ 7:0]  i2c_data_r;   //I2C读出的数据
  34. 34  
  35. 35  wire    [7:0]  sec      ;   //秒
  36. 36  wire    [7:0]  min      ;   //分
  37. 37  wire    [7:0]  hour     ;   //时
  38. 38  wire    [7:0]  day      ;   //日
  39. 39  wire    [7:0]  mon      ;   //月
  40. 40  wire    [7:0]  year     ;   //年
  41. 41  
  42. 42  //*****************************************************
  43. 43  //**                    main code
  44. 44  //*****************************************************
  45. 45  
  46. 46  //i2c驱动模块
  47. 47  i2c_dri #(
  48. 48      .SLAVE_ADDR  (SLAVE_ADDR),  //EEPROM从机地址
  49. 49      .CLK_FREQ    (CLK_FREQ  ),  //模块输入的时钟频率
  50. 50      .I2C_FREQ    (I2C_FREQ  )   //IIC_SCL的时钟频率
  51. 51  ) u_i2c_dri(
  52. 52      .clk         (sys_clk   ),  
  53. 53      .rst_n       (sys_rst_n ),  
  54. 54      //i2c interface
  55. 55      .i2c_exec    (i2c_exec  ),
  56. 56      .bit_ctrl    (BIT_CTRL  ),
  57. 57      .i2c_rh_wl   (i2c_rh_wl ),
  58. 58      .i2c_addr    (i2c_addr  ),
  59. 59      .i2c_data_w  (i2c_data_w),
  60. 60      .i2c_data_r  (i2c_data_r),
  61. 61      .i2c_done    (i2c_done  ),
  62. 62      .i2c_ack     (i2c_ack   ),
  63. 63      .scl         (iic_scl   ),
  64. 64      .sda         (iic_sda   ),
  65. 65      //user interface
  66. 66      .dri_clk     (dri_clk   )  
  67. 67  );
  68. 68  
  69. 69  //PCF8563控制模块
  70. 70  pcf8563_ctrl #(
  71. 71      .TIME_INIT (TIME_INIT)
  72. 72     )u_pcf8563_ctrl(
  73. 73      .clk         (dri_clk   ),
  74. 74      .rst_n       (sys_rst_n ),
  75. 75      //IIC
  76. 76      .i2c_rh_wl   (i2c_rh_wl ),
  77. 77      .i2c_exec    (i2c_exec  ),
  78. 78      .i2c_addr    (i2c_addr  ),
  79. 79      .i2c_data_w  (i2c_data_w),
  80. 80      .i2c_data_r  (i2c_data_r),
  81. 81      .i2c_done    (i2c_done  ),
  82. 82      //时间和日期
  83. 83      .sec         (sec       ),
  84. 84      .min         (min       ),
  85. 85      .hour        (hour      ),
  86. 86      .day         (day       ),
  87. 87      .mon         (mon       ),
  88. 88      .year        (year      )
  89. 89      );
  90. 90  
  91. 91  //LCD字符显示模块
  92. 92  lcd_disp_char u_lcd_disp_char(
  93. 93      .sys_clk     (sys_clk   ),
  94. 94      .sys_rst_n   (sys_rst_n ),
  95. 95      //时间和日期
  96. 96      .sec         (sec       ),
  97. 97      .min         (min       ),
  98. 98      .hour        (hour      ),
  99. 99      .day         (day       ),
  100. 100     .mon         (mon       ),
  101. 101     .year        (year      ),
  102. 102     //RGB LCD接口
  103. 103     .lcd_de      (lcd_de    ),
  104. 104     .lcd_hs      (lcd_hs    ),
  105. 105     .lcd_vs      (lcd_vs    ),
  106. 106     .lcd_bl      (lcd_bl    ),
  107. 107     .lcd_clk     (lcd_clk   ),
  108. 108     .lcd_rgb     (lcd_rgb   )
  109. 109     );
  110. 110
  111. 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. 1   module pcf8563_ctrl #(
  2. 2       // 初始时间设置,从高到低为年到秒,各占8bit
  3. 3       parameter  TIME_INIT = 48'h19_10_26_09_30_00)(
  4. 4       input                 clk       , //时钟信号
  5. 5       input                 rst_n     , //复位信号
  6. 6   
  7. 7       //i2c interface
  8. 8       output   reg          i2c_rh_wl , //I2C读写控制信号
  9. 9       output   reg          i2c_exec  , //I2C触发执行信号
  10. 10      output   reg  [15:0]  i2c_addr  , //I2C器件内地址
  11. 11      output   reg  [7:0]   i2c_data_w, //I2C要写的数据
  12. 12      input         [7:0]   i2c_data_r, //I2C读出的数据
  13. 13      input                 i2c_done  , //I2C一次操作完成
  14. 14  
  15. 15      //PCF8563T的秒、分、时、日、月、年数据
  16. 16      output   reg   [7:0]  sec,        //秒
  17. 17      output   reg   [7:0]  min,        //分
  18. 18      output   reg   [7:0]  hour,       //时
  19. 19      output   reg   [7:0]  day,        //日
  20. 20      output   reg   [7:0]  mon,        //月
  21. 21      output   reg   [7:0]  year        //年
  22. 22  );
  23. 23  
  24. 24  //reg define
  25. 25  reg   [3:0]     flow_cnt  ;            // 状态流控制
  26. 26  reg   [12:0]    wait_cnt  ;            // 计数等待
  27. 27  
  28. 28  //*****************************************************
  29. 29  //**                    main code
  30. 30  //*****************************************************
  31. 31  
  32. 32  //先向PCF8563中写入初始化日期和时间,再从中读出日期和时间
  33. 33  always @(posedge clk or negedge rst_n) begin
  34. 34      if(!rst_n) begin
  35. 35          sec        <= 8'h0;
  36. 36          min        <= 8'h0;
  37. 37          hour       <= 8'h0;
  38. 38          day        <= 8'h0;
  39. 39          mon        <= 8'h0;
  40. 40          year       <= 8'h0;
  41. 41          i2c_exec   <= 1'b0;
  42. 42          i2c_rh_wl  <= 1'b0;
  43. 43          i2c_addr   <= 8'd0;
  44. 44          i2c_data_w <= 8'd0;
  45. 45          flow_cnt   <= 4'd0;
  46. 46          wait_cnt   <= 13'd0;
  47. 47      end
  48. 48      else begin
  49. 49          i2c_exec <= 1'b0;
  50. 50          case(flow_cnt)
  51. 51              //上电初始化
  52. 52              4'd0: begin
  53. 53                  if(wait_cnt == 13'd8000) begin
  54. 54                      wait_cnt<= 12'd0;
  55. 55                      flow_cnt<= flow_cnt + 1'b1;
  56. 56                  end
  57. 57                  else
  58. 58                      wait_cnt<= wait_cnt + 1'b1;
  59. 59              end
  60. 60              //写读秒
  61. 61              4'd1: begin
  62. 62                  i2c_exec  <= 1'b1;
  63. 63                  i2c_addr  <= 8'h02;
  64. 64                  flow_cnt  <= flow_cnt + 1'b1;
  65. 65                  i2c_data_w<= TIME_INIT[7:0];
  66. 66              end
  67. 67              4'd2: begin
  68. 68                  if(i2c_done == 1'b1) begin
  69. 69                      sec     <= i2c_data_r[6:0];
  70. 70                      flow_cnt<= flow_cnt + 1'b1;
  71. 71                  end
  72. 72              end
  73. 73              //写读分
  74. 74              4'd3: begin
  75. 75                  i2c_exec  <= 1'b1;
  76. 76                  i2c_addr  <= 8'h03;
  77. 77                  flow_cnt  <= flow_cnt + 1'b1;
  78. 78                  i2c_data_w<= TIME_INIT[15:8];
  79. 79              end
  80. 80              4'd4: begin
  81. 81                  if(i2c_done == 1'b1) begin
  82. 82                      min     <= i2c_data_r[6:0];
  83. 83                      flow_cnt<= flow_cnt + 1'b1;
  84. 84                  end
  85. 85              end
  86. 86              //写读时
  87. 87              4'd5: begin
  88. 88                  i2c_exec  <= 1'b1;
  89. 89                  i2c_addr  <= 8'h04;
  90. 90                  flow_cnt  <= flow_cnt + 1'b1;
  91. 91                  i2c_data_w<= TIME_INIT[23:16];
  92. 92              end
  93. 93              4'd6: begin
  94. 94                  if(i2c_done == 1'b1) begin
  95. 95                      hour    <= i2c_data_r[5:0];
  96. 96                      flow_cnt<= flow_cnt + 1'b1;
  97. 97                  end
  98. 98              end
  99. 99              //写读天
  100. 100             4'd7: begin
  101. 101                 i2c_exec  <= 1'b1;
  102. 102                 i2c_addr  <= 8'h05;
  103. 103                 flow_cnt  <= flow_cnt + 1'b1;
  104. 104                 i2c_data_w<= TIME_INIT[31:24];
  105. 105             end
  106. 106             4'd8: begin
  107. 107                 if(i2c_done == 1'b1) begin
  108. 108                     day     <= i2c_data_r[5:0];
  109. 109                     flow_cnt<= flow_cnt + 1'b1;
  110. 110                 end
  111. 111             end
  112. 112             //写读月
  113. 113             4'd9: begin
  114. 114                 i2c_exec  <= 1'b1;
  115. 115                 i2c_addr  <= 8'h07;
  116. 116                 flow_cnt  <= flow_cnt + 1'b1;
  117. 117                 i2c_data_w<= TIME_INIT[39:32];
  118. 118             end
  119. 119             4'd10: begin
  120. 120                 if(i2c_done == 1'b1) begin
  121. 121                     mon     <= i2c_data_r[4:0];
  122. 122                     flow_cnt<= flow_cnt + 1'b1;
  123. 123                 end
  124. 124             end
  125. 125             //写读年
  126. 126             4'd11: begin
  127. 127                 i2c_exec  <= 1'b1;
  128. 128                 i2c_addr  <= 8'h08;
  129. 129                 flow_cnt  <= flow_cnt + 1'b1;
  130. 130                 i2c_data_w<= TIME_INIT[47:40];
  131. 131             end
  132. 132             4'd12: begin
  133. 133                 if(i2c_done == 1'b1) begin
  134. 134                     year     <= i2c_data_r;
  135. 135                     i2c_rh_wl<= 1'b1;
  136. 136                     flow_cnt <= 4'd1;
  137. 137                 end
  138. 138             end
  139. 139             default: flow_cnt <= 4'd0;
  140. 140         endcase
  141. 141     end
  142. 142 end
  143. 143
  144. 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. 1   module lcd_display(
  2. 2       input                lcd_pclk ,
  3. 3       input                rst_n ,
  4. 4      
  5. 5       //日历数据
  6. 6       input         [7:0]  sec,        //秒
  7. 7       input         [7:0]  min,        //分
  8. 8       input         [7:0]  hour,       //时
  9. 9       input         [7:0]  day,        //日
  10. 10      input         [7:0]  mon,        //月
  11. 11      input         [7:0]  year,       //年
  12. 12      
  13. 13      //LCD数据接口
  14. 14      input        [10:0]  pixel_xpos, //像素点横坐标
  15. 15      input        [10:0]  pixel_ypos, //像素点纵坐标
  16. 16      output  reg  [23:0]  pixel_data  //像素点数据
  17. 17  );
  18. 18  
  19. 19  //parameter define
  20. 20  localparam CHAR_POS_X_1  = 11'd1;  //第1行字符区域起始点横坐标
  21. 21  localparam CHAR_POS_Y_1  = 11'd1;  //第1行字符区域起始点纵坐标
  22. 22  localparam CHAR_POS_X_2  = 11'd17; //第2行字符区域起始点横坐标
  23. 23  localparam CHAR_POS_Y_2  = 11'd17; //第2行字符区域起始点纵坐标
  24. 24  localparam CHAR_WIDTH_1  = 11'd80; //第1行字符区域的宽度,第1行共10个字符(加空格)
  25. 25  localparam CHAR_WIDTH_2  = 11'd64; //第2行字符区域的宽度,第2行共8个字符(加空格)
  26. 26  localparam CHAR_HEIGHT   = 11'd16; //单个字符的高度
  27. 27  localparam WHITE  = 24'hffffff;    //背景色,白色
  28. 28  localparam BLACK  = 24'h000000;    //字符颜色,黑色
  29. 29  
  30. 30  //reg define
  31. 31  reg  [127:0]  char  [9:0] ;        //字符数组
  32. 32  
  33. 33  //*****************************************************
  34. 34  //**                    main code
  35. 35  //*****************************************************
  36. 36  
  37. 37  //字符数组初始值,用于存储字模数据(由取模软件生成,单个数字字体大小:16*16)
  38. 38  always @(posedge lcd_pclk ) begin
  39. 39      char[0] <= 128'h00000018244242424242424224180000 ;  // "0"
  40. 40      char[1] <= 128'h000000107010101010101010107C0000 ;  // "1"
  41. 41      char[2] <= 128'h0000003C4242420404081020427E0000 ;  // "2"
  42. 42      char[3] <= 128'h0000003C424204180402024244380000 ;  // "3"
  43. 43      char[4] <= 128'h000000040C14242444447E04041E0000 ;  // "4"
  44. 44      char[5] <= 128'h0000007E404040586402024244380000 ;  // "5"
  45. 45      char[6] <= 128'h0000001C244040586442424224180000 ;  // "6"
  46. 46      char[7] <= 128'h0000007E444408081010101010100000 ;  // "7"
  47. 47      char[8] <= 128'h0000003C4242422418244242423C0000 ;  // "8"
  48. 48      char[9] <= 128'h0000001824424242261A020224380000 ;  // "9"
  49. 49  end
  50. 50  
  51. 51  //不同的区域绘制不同的像素数据
  52. 52  always @(posedge lcd_pclk or negedge rst_n ) begin
  53. 53      if (!rst_n)  begin
  54. 54          pixel_data <= BLACK;
  55. 55      end
  56. 56      
  57. 57      //在第一行显示年的千位 固定值"2"
  58. 58      else if(     (pixel_xpos >= CHAR_POS_X_1)                    
  59. 59                && (pixel_xpos <  CHAR_POS_X_1 + CHAR_WIDTH_1/10*1)
  60. 60                && (pixel_ypos >= CHAR_POS_Y_1)                    
  61. 61                && (pixel_ypos <  CHAR_POS_Y_1 + CHAR_HEIGHT)  ) begin
  62. 62          if(char [2] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
  63. 63                        - ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )  
  64. 64              pixel_data <= BLACK;         //显示字符为黑色
  65. 65          else
  66. 66              pixel_data <= WHITE;        //显示字符区域背景为白色
  67. 67      end
  68. 68      
  69. 69      //在第一行显示年的百位 固定值"0"
  70. 70      else if(     (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*1)
  71. 71                && (pixel_xpos <  CHAR_POS_X_1 + CHAR_WIDTH_1/10*2)
  72. 72                && (pixel_ypos >= CHAR_POS_Y_1)                  
  73. 73                && (pixel_ypos <  CHAR_POS_Y_1 + CHAR_HEIGHT)  ) begin
  74. 74          if(char [0] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
  75. 75                        - ((pixel_xpos-CHAR_POS_X_1)%8) -1 ]  )
  76. 76              pixel_data <= BLACK;
  77. 77          else
  78. 78              pixel_data <= WHITE;
  79. 79      end
  80. 80      
  81. 81      //在第一行显示年的十位
  82. 82      else if(     (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*2)
  83. 83                && (pixel_xpos <  CHAR_POS_X_1 + CHAR_WIDTH_1/10*3)
  84. 84                && (pixel_ypos >= CHAR_POS_Y_1)                  
  85. 85                && (pixel_ypos <  CHAR_POS_Y_1 + CHAR_HEIGHT)  ) begin
  86. 86          if(char [year[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
  87. 87                                - ((pixel_xpos-CHAR_POS_X_1)%8) -1 ]  )
  88. 88              pixel_data <= BLACK;
  89. 89          else
  90. 90              pixel_data <= WHITE;
  91. 91      end
  92. 92      
  93. 93      //在第一行显示年的个位
  94. 94      else if(     (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*3)
  95. 95                && (pixel_xpos <  CHAR_POS_X_1 + CHAR_WIDTH_1/10*4)
  96. 96                && (pixel_ypos >= CHAR_POS_Y_1)                  
  97. 97                && (pixel_ypos <  CHAR_POS_Y_1 + CHAR_HEIGHT)  ) begin
  98. 98          if(char [year[3:0]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
  99. 99                                - ((pixel_xpos-CHAR_POS_X_1)%8) -1 ]  )
  100. 100             pixel_data <= BLACK;
  101. 101         else
  102. 102             pixel_data <= WHITE;
  103. 103     end
  104. 104     
  105. 105     //在第一行显示空格
  106. 106     else if(     (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*4)
  107. 107               && (pixel_xpos <  CHAR_POS_X_1 + CHAR_WIDTH_1/10*5)
  108. 108               && (pixel_ypos >= CHAR_POS_Y_1)                  
  109. 109               && (pixel_ypos <  CHAR_POS_Y_1 + CHAR_HEIGHT)  ) begin
  110. 110         pixel_data <= WHITE;
  111. 111     end
  112. 112     
  113. 113     //在第一行显示月的十位
  114. 114     else if(     (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*5)
  115. 115               && (pixel_xpos <  CHAR_POS_X_1 + CHAR_WIDTH_1/10*6)
  116. 116               && (pixel_ypos >= CHAR_POS_Y_1)                  
  117. 117               && (pixel_ypos <  CHAR_POS_Y_1 + CHAR_HEIGHT)) begin
  118. 118         if(char [mon[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
  119. 119                              - ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
  120. 120             pixel_data <= BLACK;
  121. 121         else
  122. 122             pixel_data <= WHITE;
  123. 123     end
  124. 124     
  125. 125     //在第一行显示月的个位
  126. 126     else if(     (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*6)
  127. 127               && (pixel_xpos <  CHAR_POS_X_1 + CHAR_WIDTH_1/10*7)
  128. 128               && (pixel_ypos >= CHAR_POS_Y_1)                  
  129. 129               && (pixel_ypos <  CHAR_POS_Y_1 + CHAR_HEIGHT)  ) begin
  130. 130         if(char [mon[3:0]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
  131. 131                              - ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
  132. 132             pixel_data <= BLACK;
  133. 133         else
  134. 134             pixel_data <= WHITE;
  135. 135     end
  136. 136     
  137. 137     //在第一行显示空格
  138. 138     else if(     (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*7)
  139. 139               && (pixel_xpos <  CHAR_POS_X_1 + CHAR_WIDTH_1/10*8)
  140. 140               && (pixel_ypos >= CHAR_POS_Y_1)                  
  141. 141               && (pixel_ypos <  CHAR_POS_Y_1 + CHAR_HEIGHT)  ) begin
  142. 142         pixel_data <= WHITE;
  143. 143     end
  144. 144     
  145. 145     //在第一行显示日的十位
  146. 146     else if(     (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*8)
  147. 147               && (pixel_xpos <  CHAR_POS_X_1 + CHAR_WIDTH_1/10*9)
  148. 148               && (pixel_ypos >= CHAR_POS_Y_1)                  
  149. 149               && (pixel_ypos <  CHAR_POS_Y_1 + CHAR_HEIGHT)  ) begin
  150. 150         if(char [day[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
  151. 151                              - ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
  152. 152             pixel_data <= BLACK;
  153. 153         else
  154. 154             pixel_data <= WHITE;
  155. 155     end
  156. 156     
  157. 157     //在第一行显示日的个位
  158. 158     else if(     (pixel_xpos >= CHAR_POS_X_1 + CHAR_WIDTH_1/10*9)
  159. 159               && (pixel_xpos <  CHAR_POS_X_1 + CHAR_WIDTH_1)
  160. 160               && (pixel_ypos >= CHAR_POS_Y_1)                  
  161. 161               && (pixel_ypos <  CHAR_POS_Y_1 + CHAR_HEIGHT)  ) begin
  162. 162         if(char [day[3:0]] [ (CHAR_HEIGHT+CHAR_POS_Y_1 - pixel_ypos)*8
  163. 163                              - ((pixel_xpos-CHAR_POS_X_1)%8) -1 ] )
  164. 164             pixel_data <= BLACK;
  165. 165         else
  166. 166             pixel_data <= WHITE;
  167. 167     end
  168. 168     
  169. 169     //在第二行显示时的十位
  170. 170     else if(     (pixel_xpos >= CHAR_POS_X_2)                  
  171. 171               && (pixel_xpos <  CHAR_POS_X_2 + CHAR_WIDTH_2/8*1)
  172. 172               && (pixel_ypos >= CHAR_POS_Y_2)                  
  173. 173               && (pixel_ypos <  CHAR_POS_Y_2 + CHAR_HEIGHT)  ) begin
  174. 174         if(char [hour[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8
  175. 175                               - ((pixel_xpos-CHAR_POS_X_2)%8) -1 ] )
  176. 176             pixel_data <= BLACK;
  177. 177         else
  178. 178             pixel_data <= WHITE;
  179. 179     end
  180. 180     
  181. 181     //在第二行显示时的个位
  182. 182     else if(     (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*1)
  183. 183               && (pixel_xpos <  CHAR_POS_X_2 + CHAR_WIDTH_2/8*2)
  184. 184               && (pixel_ypos >= CHAR_POS_Y_2)                  
  185. 185               && (pixel_ypos <  CHAR_POS_Y_2 + CHAR_HEIGHT)  ) begin
  186. 186         if(char [hour[3:0]] [ (CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8
  187. 187                               - ((pixel_xpos-CHAR_POS_X_2)%8) -1 ] )
  188. 188             pixel_data <= BLACK;
  189. 189         else
  190. 190             pixel_data <= WHITE;
  191. 191     end
  192. 192     
  193. 193     //在第二行显示空格
  194. 194     else if(     (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*2)
  195. 195               && (pixel_xpos <  CHAR_POS_X_2 + CHAR_WIDTH_2/8*3)
  196. 196               && (pixel_ypos >= CHAR_POS_Y_2)                  
  197. 197               && (pixel_ypos <  CHAR_POS_Y_2 + CHAR_HEIGHT)  ) begin
  198. 198         pixel_data <= WHITE;
  199. 199     end
  200. 200     
  201. 201     //在第二行显示分的十位
  202. 202     else if(     (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*3)
  203. 203               && (pixel_xpos <  CHAR_POS_X_2 + CHAR_WIDTH_2/8*4)
  204. 204               && (pixel_ypos >= CHAR_POS_Y_2)                  
  205. 205               && (pixel_ypos <  CHAR_POS_Y_2 + CHAR_HEIGHT)  ) begin
  206. 206         if(char [min[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8
  207. 207                              - ((pixel_xpos-CHAR_POS_X_2)%8) -1 ] )
  208. 208             pixel_data <= BLACK;
  209. 209         else
  210. 210             pixel_data <= WHITE;
  211. 211     end
  212. 212     
  213. 213     //在第二行显示分的个位
  214. 214     else if(     (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*4)
  215. 215               && (pixel_xpos <  CHAR_POS_X_2 + CHAR_WIDTH_2/8*5)
  216. 216               && (pixel_ypos >= CHAR_POS_Y_2)                  
  217. 217               && (pixel_ypos <  CHAR_POS_Y_2 + CHAR_HEIGHT)  ) begin
  218. 218         if(char [min[3:0]] [ (CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8
  219. 219                              - ((pixel_xpos-CHAR_POS_X_2)%8) -1 ] )
  220. 220             pixel_data <= BLACK;
  221. 221         else
  222. 222             pixel_data <= WHITE;
  223. 223     end
  224. 224     
  225. 225     //在第二行显示空格
  226. 226     else if(     (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*5)
  227. 227               && (pixel_xpos <  CHAR_POS_X_2 + CHAR_WIDTH_2/8*6)
  228. 228               && (pixel_ypos >= CHAR_POS_Y_2)                  
  229. 229               && (pixel_ypos <  CHAR_POS_Y_2 + CHAR_HEIGHT)  ) begin
  230. 230         pixel_data <= WHITE;
  231. 231     end
  232. 232     
  233. 233     //在第二行显示秒的十位
  234. 234     else if(     (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*6)
  235. 235               && (pixel_xpos <  CHAR_POS_X_2 + CHAR_WIDTH_2/8*7)
  236. 236               && (pixel_ypos >= CHAR_POS_Y_2)                  
  237. 237               && (pixel_ypos <  CHAR_POS_Y_2 + CHAR_HEIGHT)  ) begin
  238. 238         if(char [sec[7:4]] [ (CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8
  239. 239                               - ((pixel_xpos-CHAR_POS_X_2)%8) -1 ] )
  240. 240             pixel_data <= BLACK;
  241. 241         else
  242. 242             pixel_data <= WHITE;
  243. 243     end
  244. 244     
  245. 245     //在第二行显示秒的个位   
  246. 246     else if(     (pixel_xpos >= CHAR_POS_X_2 + CHAR_WIDTH_2/8*7)
  247. 247               && (pixel_xpos <  CHAR_POS_X_2 + CHAR_WIDTH_2)
  248. 248               && (pixel_ypos >= CHAR_POS_Y_2)                  
  249. 249               && (pixel_ypos <  CHAR_POS_Y_2 + CHAR_HEIGHT)  ) begin
  250. 250         if(char [sec[3:0]] [ (CHAR_HEIGHT+CHAR_POS_Y_2 - pixel_ypos)*8
  251. 251                              - ((pixel_xpos-CHAR_POS_X_2)%8) -1 ] )
  252. 252             pixel_data <= BLACK;
  253. 253         else
  254. 254             pixel_data <= WHITE;
  255. 255     end
  256. 256     
  257. 257     else begin
  258. 258         pixel_data <= WHITE;    //屏幕背景为白色
  259. 259     end
  260. 260 end
  261. 261
  262. 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”的字模数据,由取模软件生成,先将软件设置成字符模式,取模软件的设置如下:
第二十四章 RTC实时时钟LCD显示实验27725.png
图 7.5.13.3 字符软件设置
这里将点阵设置为16,即一个数字的字符用一行来表示。
生成字模的界面如下:
第二十四章 RTC实时时钟LCD显示实验27826.png
图 7.5.13.4 生成字模的软件设置
程序中第51行至260行代码根据输入的日期、时间和字符区域的坐标显示在LCD上,字符颜色为黑色,背景色为白色。
24.5下载验证
首先将FPC排线一端与RGB LCD模块上的J1接口连接,另一端与领航者开发板上的RGB TFTLCD接口连接。然后将下载器一端连电脑,另一端与开发板上的JTAG端口连接,最后连接电源线并打开电源开关。
接下来我们下载程序,验证RGB LCD字符和图片显示的功能。下载完成后观察RGB LCD液晶屏上显示出日期和时间,并且时间在不断的计时,如下图所示,说明RGB TFT-LCD字符和图片显示程序下载验证成功。
第二十四章 RTC实时时钟LCD显示实验28160.png
图 7.5.13.1 实验结果

正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-23 19:24

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表