OpenEdv-开源电子网

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

[XILINX] 【正点原子FPGA连载】第二十二章 RGB-LCD字符和图片显示实验--摘自【正点原子】超越者之FPGA开发指南

[复制链接]

1424

主题

1440

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
6074
金钱
6074
注册时间
2019-5-8
在线时间
1739 小时
跳转到指定楼层
楼主
发表于 2021-1-18 11:56:58 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 正点原子运营 于 2021-1-19 16:55 编辑

1)实验平台:正点原子超越者FPGA开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=631660290421
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-chaoyuezhe.html
4)本章实例源码下载: 15_lcd_rgb_char.rar (3.74 MB, 下载次数: 2)

5)正点原子FPGA交流群:905624739点击加入:
6)关注正点原子公众号,获取最新资料更新



第二十二章 RGB-LCD字符和图片显示实验


我们在“RGB-LCD彩条显示实验”中成功的在正点原子的RGB-LCD液晶屏模块上显示出了彩条。本章我们在“RGB-LCD彩条显示实验”的基础上稍作修改,在LCD上完成“正点原子logo图片”和汉字“正点原子”的显示。

本章包括以下几个部分:

1.1   简介

1.2   实验任务

1.3   硬件设计

1.4   程序设计

1.5   下载验证


一.1 简介

我们在“RGB-LCD彩条显示实验”中对正点原子的RGB-LCD液晶屏模块作了详细的介绍,包括数据输入时序、同步方式、以及分辨率等。如果大家对这部分内容不是很熟悉的话,请参考“RGB-LCD彩条显示实验”中的简介部分。
一.2 实验任务

本节的实验任务是通过超越者开发板上的RGB-LCD接口,在正点原子的RGB-LCD液晶屏的左上角位置显示图片以及4个汉字“正点原子”。其中每个汉字的大小为32*32,图片的大小为100*100。
一.3 硬件设计

RGB-LCD接口部分的硬件设计原理及本实验中各端口信号的管脚分配与“RGB-LCD彩条显示实验”完全相同,请参考“RGB-LCD彩条显示实验”中的硬件设计部分。
一.4 程序设计

下图是根据本章实验任务画出的系统框图。可以看出,其中本次实验的系统框图与“RGB-LCD彩条显示实验”基本一致,我们只需要修改LCD显示模块就可以实现在LCD液晶屏上显示字符和图片的功能。另外,由于图片的像素数据较多,因此我们在LCD显示模块中例化了一个ROM,用来存储图片数据。



图 22.4.1 字符和图片显示实验系统框图


字符(包括汉字、字母和符号等)的本质都是点阵,在LCD屏幕上体现为字符显示区域内像素点的集合。字符的大小决定了字符显示区域内像素点的数目,而字符的样式(字体、颜色等)则决定了各像素点的颜色值。因此,在显示字符之前,我们需要先指定字符的大小、样式,然后获取该字符的点阵,这个过程我们称之为“提取字模”,或简称“取模”。

我们一般使用0和1的组合来描述字符的点阵排列:点阵中每个像素点用一位(1 bit)数据来表示,其中用于表征字符的像素点用数字1来表示,其他的像素点作为背景用数字0来表示,如图 22.4.2所示。采用这种方式描述的字符是不含有颜色特征的,只能区分点阵中的字符和背景。



图 22.4.2 汉字“正”及其点阵描述

字模的提取可通过字符取模软件来实现,在这里我们使用取模软件“PCtoLCD2002”来获取汉字“正点原子”的字模。首先在开发板所随附的资料盘(A盘)中“6_软件资料/1_软件/PCtoLCD2002完美版”目录下找到“PCtoLCD2002”并双击打开,如下图所示:



图 22.4.3 取模软件PCtoLCD2002


打开之后会发现软件中的字体、字宽和字高都是无法设置的,这个时候点击菜单栏的“模式”,选择“字符模式”,如下图所示。



图 22.4.4 切换到字符模式


切换到字符模式后,就可以设置字体、字宽和字高了。字宽和字高的值越高,显示在LCD屏上的字符就越大,但是代码也需要做相应的修改。这里将字体选择默认的“宋体”,字宽和字高设置成“32”,然后在下方文本框中输入汉字“正点原子”,如下图所示:



图 22.4.5 字符设置

由于PCtoLCD2002会给每个字符生成一个独立的字模,如果此时点击文本框右侧的“生成字模”按钮,我们将会得到四个32*32的字模。然而为了方便在LCD上显示,我们将四个汉字看作一个整体,从而获得一个字宽为128,字高为32的“大字模”。为了达到这个目的,我们首先将图 22.4.5中四个汉字的点阵保存为.BMP格式的图片。在菜单栏中点击“文件”并选择“另存为”,在保存界面中指定文件存储路径,并选择保存类型为“BMP图像文件”,然后输入文件名“正点原子_bmp”,最后点击“保存”。本次我们在工程路径下新建一个“doc”文件夹,将生成的BMP图片保存在doc文件夹下。如图 22.4.6和图 22.4.7所示。



图 22.4.6 点击“文件”并另存为图像




图 22.4.7 BMP格式图片保存界面


我们在“画图”中打开刚刚保存的BMP格式的图片如下所示:



图 22.4.8 保存的BMP格式图片


接下来我们将取模软件PCtoLCD2002切换至图形模式,在菜单栏中点击“模式”,选择“图形模式”。



图 22.4.9 切换至图形模式


然后在菜单栏中点击“文件”并选择“打开”,指定图 22.4.7中存放BMP格式图片的路径并打开图片“正点原子_bmp”,图片打开后如下所示。



图 22.4.10 PCtoLCD2002图形模式


请大家注意比较图 22.4.10与图 22.4.5的差异,在上图中,四个汉字“正点原子”被看作一个整体,而不再是四个独立的字符。实际上,这四个汉字也确实是作为一个整体以BMP图片的形式导入到取模软件中的。

在生成字模之前,我们需要先设置字模的格式。在菜单栏中点击“选项”,并在弹出的配置界面中按照下图进行配置,配置完成后点击确定。



图 22.4.11 字模格式配置界面


在配置界面中,当鼠标悬浮在各配置选项上时,软件会自动提示当前配置的含义。需要注意的是图 22.4.11左下角“每行显示数据”是以字节(Byte)为单位的,而一个字节的数据为8个bit,即可以表示一行点阵中的8个像素点。由于图 22.4.10中的点阵每行为128个像素点,所以需要16个Byte的数据来表示一行,因此将“每行显示数据—点阵”处设置为16。

配置字模选项完成后,点击“生成字模”,即可得到汉字“正点原子”所对应的点阵数据,如下图所示:


图 22.4.12 生成字模


最后点击保存字模,命名成“正点原子_字模”,可将生成的点阵数据保存在txt格式的文本文档中,如图 22.4.13所示。数据以十六进制显示,每行有16个Byte,对应每行四个汉字共128个像素点;共有32行,对应每个汉字的高度为32。


图 22.4.13 “正点原子”字模


提取字模完成后,我们需要在LCD显示模块中将获取的点阵数据映射到液晶屏中心32*128个的像素点的字符显示区域,从而实现字符的显示。

到这里提取字模的过程就已经完成了,接下来我们介绍图片像素数据的获取方法。

LCD显示模块中的ROM是通过例化IP核来实现的只读存储器,它使用FPGA的片上存储资源,即BRAM。由于FPGA的片上存储资源有限,所以ROM中存储的图片大小也受到限制,本次实验采用的图片分辨率为100*100。超越者开发板上的RGB-LCD接口采用RGB888数据格式,即每个像素点的颜色用24bit的数据来表示,因此大小为100*100*24bit = 240000bit=234.375Kbit≈0.23Mbit。我们超越者开发板使用的FPGA芯片的BRAM存储容量为0.576Mbit,能够满足本次实验中的图片存储需求。

ROM作为只读存储器,在调用IP核时需要指定初始化文件,在这里就是写入存储器中的图片数据,各种格式的图片(bmp、jpg等)在Xilinx开发软件中都是以COE文件或者HEX文件的形式导入到ROM中的。COE文件格式较为简单,因此本次实验选取COE的文件格式。COE是一种Xilinx工具能识别的文件格式,在文件的开头定义了存储数据的进制和初始化的数据,存储的数据最后一行以“;”结束,其余的以“,”结束。例如一个存储数据为16进制,深度为5的COE文件内容如下图所示:



图 22.4.14 COE文件格式


图中第一行定义存储的数据为16进制,第二行初始化的数值向量,分别对应不同存储单元的数据,存储地址从0开始,依次累加,最后一个存储地址的数据以“;”结束。

当需要存储的数据量较小时,如果我们知道数据的内容,那么就可以仿照图 22.4.14的格式手动编写COE文件。但是由于图片的数据量较大,并且我们无法直接看出各个像素点对应的颜色数据,因此需要借助工具来实现图片到COE文件的转换。在这里我们使用正点原子提供的工具“PicToLCD”来实现这一转换过程,该工具位于开发板所随附的资料中“6_软件资料/1_软件/PicToLCD”目录下。

我们在Windows自带的“画图”工具中将正点原子的LOGO图片大小调整100*100,并利用工具PicToLCD转换得到COE文件“ZDYZ.coe”。

双击运行“PicToLCD.exe”,点击“加载图片”并在弹出的界面中选择需要转换的图片(注意:待转换图片分辨率的大小必须是100*100,否则代码中访问ROM的最大地址需要修改)。图片加载成功后工具会在图片属性中指示出图片的文件名和大小;接下来选择图片转换的数据格式为RGB888;文件类型选择“COE”;最后点击“一键转换” 按钮,在弹出的界面中选择COE文件的存放路径并输入文件名。PicToLCD转换过程中的软件界面如图 22.4.15所示:




图 22.4.15 PicToLCD转换界面


最终转换得到的COE文件部分截图如下所示:



图 22.4.16 转换得到的COE文件


在ISE软件中,RAM和ROM都是由BMG IP核(Block Memory Generator)配置生成的,ROM的配置过程和RAM类似,首先右击工程在弹出的窗口中选择“New Source”选项,如下图所示:



图 22.4.17 “New Source”选项


之后进入到如下图所示的窗口。



图 22.4.18 建立“ROM IP”核界面


首先选择“IP(CORE Generator & Architecture Wizard)”,在“File name”一栏写入ROM IP核的名称,这里我们命名为“blk_mem_gen”,然后选择ROM IP核的存放路径,这里我们存放在“lcd_rgb_char\prj\lcd_rgb_char\ipcore_dir”路径下,最后点击“Next”,进入下图所示界面:



图 22.4.19 选择“ROM IP”核界面


选择如图所示的“Block Memory Generator”,点击“Next”,进入下图所示界面:



图 22.4.20 创建“ROM IP”核完成界面


点击“Finish”,进入ROM IP配置界面,如下图所示:



图 22.4.21 “ROM IP”核配置界面1


从上图中我们可以看出,想要配置一个ROM IP核,我们只需要完成6个步骤。首先我们先来介绍一下第一个页面,在第一个页面中,我们可以看到一个标签,它是“Interface Type”(接口类型)栏:

Native:表示生成一个没有AXI4结构的Block Memory Generator(BMG)模块。

AXI4:表示生成一个含有AXI4结构的Block Memory Generator(BMG)模块,该模块和其他含有AXI4结构的IP核模块有同样的接口标准。

这里我们选择“Native”,点击“Next”,进入下图所示界面:



图 22.4.22 “ROM IP”核配置界面2


在这个页面中,我们首先需要选择存储器的类型为“Single Port ROM”。然后还有2个可供修改的标签“Addressing Option”(寻址选项)和“Algorithm”(算法)。关于“Addressing Options”栏,我们选择取消是否使能32bit的地址长度。在“Algorithm”栏,有以下三种选项:

Minimum Area:表示使用最少的块RAM原语。

Low Power:表示为了单次读/写操作使用最少的块RAM原语。

Fixed Primitives:使用自定义的单端口块RAM原语。

然后点击“next”进入第三个页面,如下图所示:



图 22.4.23 “ROM IP”核配置界面3


在这个页面中,设置ROM读端口的位宽和深度,因为我们的像素数据是“RGB888”格式,所以端口位宽要设置成24位;使用“Notepad++”编辑器打开.coe初始化文件,可以看到存储的数据共有10000个数据,所以端口深度设置成10000。设置“Operating Mode”栏为“Read First”,“Enable”栏为“Use ENA Pin”。然后点击“next”进入第四个页面,如下图所示:



图 22.4.24 “ROM IP”核配置界面4


在这个页面中,我们选中“Load Init File”栏,设置“Coe File”栏的路径,加载我们刚刚使用“PicToLCD”软件生成的“.coe” 文件。然后其余的页面我们使用默认设置即可。然后点击“next”进入第五个页面,如下图所示:



图 22.4.25 “ROM IP”核配置界面5


这里保持默认设置,直接点击“next”进入第六个页面,如下图所示:



图 22.4.26 “ROM IP”核配置界面6


这里保持默认设置,最后点击“Generate”,完成对ROM的配置。

RGB-LCD彩条显示实验分为顶层模块(lcd_rgb_char)、ID读取模块(rd_id)、时钟分频模块(clk_div)、LCD驱动模块(lcd _driver)以及LCD显示模块(lcd_display),其中ID读取模块(rd_id)、时钟分频模块(clk_div)以及LCD驱动模块(lcd _driver)均与“RGB-LCD彩条显示实验”完全相同,本次实验只对LCD显示模块(lcd_display)作了修改。因此,这里我们重点讲解lcd_display模块,其他部分大家可以参考“RGB-LCD彩条显示实验”。

LCD显示模块的代码如下:

  1. <font size="4">1   module lcd_display(

  2. 2       input             lcd_pclk,     //时钟

  3. 3       input             rst_n,        //复位,低电平有效

  4. 4                                       

  5. 5       input      [10:0] pixel_xpos,   //像素点横坐标

  6. 6       input      [10:0] pixel_ypos,   //像素点纵坐标   

  7. 7       output reg [23:0] pixel_data    //像素点数据,

  8. 8   );                                   

  9. 9                                       

  10. 10  //parameter define                  

  11. 11  localparam PIC_X_START = 11'd1;      //图片起始点横坐标

  12. 12  localparam PIC_Y_START = 11'd1;      //图片起始点纵坐标

  13. 13  localparam PIC_WIDTH   = 11'd100;    //图片宽度

  14. 14  localparam PIC_HEIGHT  = 11'd100;    //图片高度

  15. 15                        

  16. 16  localparam CHAR_X_START= 11'd1;      //字符起始点横坐标

  17. 17  localparam CHAR_Y_START= 11'd110;    //字符起始点纵坐标

  18. 18  localparam CHAR_WIDTH  = 11'd128;    //字符宽度,4个字符:32*4

  19. 19  localparam CHAR_HEIGHT = 11'd32;     //字符高度

  20. 20                        

  21. 21  localparam BACK_COLOR  = 24'hE0FFFF; //背景色,浅蓝色

  22. 22  localparam CHAR_COLOR  = 24'hff0000; //字符颜色,红色

  23. 23  

  24. 24  //reg define

  25. 25  reg   [127:0] char[31:0];  //字符数组

  26. 26  reg   [13:0]  rom_addr  ;  //ROM地址

  27. 27  

  28. 28  //wire define   

  29. 29  wire  [10:0]  x_cnt;       //横坐标计数器

  30. 30  wire  [10:0]  y_cnt;       //纵坐标计数器

  31. 31  wire          rom_rd_en ;  //ROM读使能信号

  32. 32  wire  [23:0]  rom_rd_data ;//ROM数据

  33. 33  

  34. 34  //*****************************************************

  35. 35  //**                    main code

  36. 36  //*****************************************************

  37. 37  

  38. 38  assign  x_cnt = pixel_xpos - CHAR_X_START; //像素点相对于字符区域起始点水平坐标

  39. 39  assign  y_cnt = pixel_ypos - CHAR_Y_START; //像素点相对于字符区域起始点垂直坐标

  40. 40  assign  rom_rd_en = 1'b1;                  //读使能拉高,即一直读ROM数据

  41. 41  

  42. 42  //给字符数组赋值,显示汉字“正点原子”,每个汉字大小为32*32

  43. 43  always @(posedge lcd_pclk) begin

  44. 44      char[0 ]  <= 128'h00000000000000000000000000000000;

  45. 45      char[1 ]  <= 128'h00000000000000000000000000000000;

  46. 46      char[2 ]  <= 128'h00000000000100000000002000000000;

  47. 47      char[3 ]  <= 128'h000000100001800002000070000000C0;

  48. 48      char[4 ]  <= 128'h000000380001800003FFFFF803FFFFE0;

  49. 49      char[5 ]  <= 128'h07FFFFFC0001800003006000000001E0;

  50. 50      char[6 ]  <= 128'h0000C000000180600300600000000300;

  51. 51      char[7 ]  <= 128'h0000C0000001FFF00300C00000000600;

  52. 52      char[8 ]  <= 128'h0000C000000180000310804000001800;

  53. 53      char[9 ]  <= 128'h0000C00000018000031FFFE000003000;

  54. 54      char[10]  <= 128'h0000C00000018000031800400001C000;

  55. 55      char[11]  <= 128'h0000C00000018000031800400001C000;

  56. 56      char[12]  <= 128'h00C0C000018181800318004000018000;

  57. 57      char[13]  <= 128'h00C0C00001FFFFC0031FFFC000018010;

  58. 58      char[14]  <= 128'h00C0C060018001800318004000018038;

  59. 59      char[15]  <= 128'h00C0FFF001800180031800403FFFFFFC;

  60. 60      char[16]  <= 128'h00C0C000018001800318004000018000;

  61. 61      char[17]  <= 128'h00C0C000018001800218004000018000;

  62. 62      char[18]  <= 128'h00C0C00001800180021FFFC000018000;

  63. 63      char[19]  <= 128'h00C0C000018001800210304000018000;

  64. 64      char[20]  <= 128'h00C0C00001FFFF800200300000018000;

  65. 65      char[21]  <= 128'h00C0C000018001800606300000018000;

  66. 66      char[22]  <= 128'h00C0C000018001000607370000018000;

  67. 67      char[23]  <= 128'h00C0C00000000000060E31C000018000;

  68. 68      char[24]  <= 128'h00C0C000001000400418307000018000;

  69. 69      char[25]  <= 128'h00C0C000020830600430303800018000;

  70. 70      char[26]  <= 128'h00C0C010020C18300860301800018000;

  71. 71      char[27]  <= 128'h00C0C038060E18180883700800018000;

  72. 72      char[28]  <= 128'h3FFFFFFC0C0618181100F008003F8000;

  73. 73      char[29]  <= 128'h000000001C0408182000600000070000;

  74. 74      char[30]  <= 128'h00000000000000000000000000020000;

  75. 75      char[31]  <= 128'h00000000000000000000000000000000;

  76. 76  end

  77. 77  

  78. 78  //为LCD不同显示区域绘制图片、字符和背景色

  79. 79  always @(posedge lcd_pclk or negedge rst_n) begin

  80. 80      if (!rst_n)

  81. 81          pixel_data <= BACK_COLOR;

  82. 82      else if( (pixel_xpos >= PIC_X_START) && (pixel_xpos < PIC_X_START + PIC_WIDTH)

  83. 83            && (pixel_ypos >= PIC_Y_START) && (pixel_ypos < PIC_Y_START + PIC_HEIGHT) )

  84. 84          pixel_data <= rom_rd_data ;  //显示图片

  85. 85      else if((pixel_xpos >= CHAR_X_START) && (pixel_xpos < CHAR_X_START + CHAR_WIDTH)

  86. 86           && (pixel_ypos >= CHAR_Y_START) && (pixel_ypos < CHAR_Y_START + CHAR_HEIGHT)) begin

  87. 87          if(char[y_cnt][CHAR_WIDTH -1'b1 - x_cnt])

  88. 88              pixel_data <= CHAR_COLOR;    //显示字符

  89. 89          else

  90. 90              pixel_data <= BACK_COLOR;    //显示字符区域的背景色

  91. 91      end

  92. 92      else

  93. 93          pixel_data <= BACK_COLOR;        //屏幕背景色

  94. 94  end

  95. 95  

  96. 96  //根据当前扫描点的横纵坐标为ROM地址赋值

  97. 97  always @(posedge lcd_pclk or negedge rst_n) begin

  98. 98      if(!rst_n)

  99. 99          rom_addr <= 14'd0;

  100. 100     //当横纵坐标位于图片显示区域时,累加ROM地址   

  101. 101     else if((pixel_ypos >= PIC_Y_START) && (pixel_ypos < PIC_Y_START + PIC_HEIGHT)

  102. 102         && (pixel_xpos >= PIC_X_START) && (pixel_xpos < PIC_X_START + PIC_WIDTH))

  103. 103         rom_addr <= rom_addr + 1'b1;

  104. 104     //当横纵坐标位于图片区域最后一个像素点时,ROM地址清零   

  105. 105     else if((pixel_ypos >= PIC_Y_START + PIC_HEIGHT))

  106. 106         rom_addr <= 14'd0;

  107. 107 end

  108. 108

  109. 109 //ROM:存储图片

  110. 110 blk_mem_gen u_blk_mem_gen (

  111. 111   .clka(lcd_pclk), // input clka

  112. 112   .ena(rom_rd_en), // input ena

  113. 113   .wea(wea), // input [3 : 0] wea

  114. 114   .addra(rom_addr), // input [31 : 0] addra

  115. 115   .dina(dina), // input [31 : 0] dina

  116. 116   .douta(rom_rd_data) // output [31 : 0] douta

  117. 117 );

  118. 118

  119. 119 endmodule
  120. </font>
复制代码

程序中第10行至22行定义了一系列的参数,方便大家修改图片的位置,字符位置和字符颜色等。

程序中第25行定义了一个大小为32*128bit的二维数组char,用于存储取模得到的点阵数据。二维数组char共32行,每一行有128位数据,在程序的第42至76行完成了对该二维数组的赋值。赋值后数组中每一行数据从高位到低位分别对应点阵中该行从左向右的每一个像素点。

程序中第78行至94行完成了字符和图片的显示,根据当前像素点扫描的坐标,为pixel_data赋值字符颜色、背景色或者图片数据(从ROM中读出的数据)。屏幕上字符显示区域内的像素点与字符数组char中的点阵数据一一映射。当点阵数据为1时,将像素点颜色赋值为红色,用来显示字符;当点阵数据为0时,将像素点颜色赋值为浅蓝色,用来作为字符显示区域的背景。屏幕上除字符和图片显示区域之外的其他区域内的像素点均赋值为浅蓝色。

程序中第96行至107行根据当前的扫描坐标为ROM地址赋值。需要说明的是,我们将ROM的读使能信号固定为高电平,即一直读ROM,而ROM中的数据是由ROM地址来进行控制。

图 22.4.27为LCD显示模块的仿真波形图,其中rom_rd_en固定为高电平,当像素点横坐标(pixel_xpos)和像素点纵坐标(pixel_ypos)位于图片显示区域时,读ROM地址依次累加,从而依次获取ROM中的数据。



图 22.4.27  LCD显示模块仿真图

一.5 下载验证

首先将FPC排线一端与RGB-LCD连接,另一端与超越者开发板上的RGB-LCD接口连接。然后将下载器一端连电脑,另一端与开发板上的JTAG端口连接,最后连接电源线并打开电源开关。接下来我们下载程序,验证RGB-LCD字符和图片显示的功能。下载完成后观察RGB-LCD液晶屏上显示的图案如下图所示,说明RGB-LCD字符和图片显示程序下载验证成功。



图 22.5.1 RGB-LCD字符和图片显示










回复

使用道具 举报

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

本版积分规则



关闭

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

正点原子公众号

如发现本坛存在违规或侵权内容, 请点击这里发送邮件举报 (或致电020-38271790)。请提供侵权说明和联系方式。我们将及时审核依法处理,感谢配合。

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

GMT+8, 2026-10-21 08:58

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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