PFGA-LCD图片字符显示

1. LCD显示原理

       液晶屏显示画面时,屏幕从上到下逐行扫描,扫描完成后液晶屏就呈现一帧画面。然后屏幕回到初始位置进行下一次扫描。为了同步液晶屏的显示过程和液晶控制器,控制器会产生一系列的定时信号。当电子枪换行进行扫描时,控制器会发出一个水平同步信号,简称HSync;而当一帧画面绘制完成后,电子枪回复到原位,准备画下一帧前,控制器会发出一个垂直同步信号,简称 VSync。控制器通常以固定频率进行刷新,这个刷新率就是 VSync 信号产生的频率。

  • 水平同步信号 HSYNC(行同步信号)

水平同步信号用于表示液晶屏一行像素数据的传输结束,每传输完成液晶屏的一行像素数据时,HSYNC 会发生电平跳变,如分辨率为 800x480 的显示屏,传输一帧图像 HSYNC 的电平会跳变 480 次

  • ​ 垂直同步信号 VSYNC(场同步信号)

​垂直同步信号 VSYNC 用于表示液晶屏一帧像素数据的传输结束,每次传输完成一帧像素数据时,VSYNC 会发生电平跳变。其中 “帧” 是图像的单位,一副图像成为一帧,在液晶中,一帧指一个完整屏幕液晶像素点。常常用 “帧/秒” 来表示液晶屏的刷新特性,即液晶屏每秒钟可以显示多少帧图像,也通常成为 “fps”,如液晶屏以 60 帧每秒的速率运行时,VSYNC 每秒电平跳变 60 次,也成为 60 fps

  • ​ 数据使能信号 DE

​ 数据使能信号 DE 用于表示数据的有效性,当 DE 信号线为高电平时,RGB 信号线表示数据有效

RGB LCD时间参数

RGB LCD行显示时序

  1. HSYNC信号有效时,表示一行数据的开始。
  2. HSPW表示HSYNC信号的脉冲宽度为(HSPW+1)个VCLK信号周期,即(HSPW+1)个像素,这(HSPW+1)个像素的数据无效。
  3. HSYNC信号脉冲之后,还要经过(HBP+1)个VCLK信号周期,有效的像素数据才出现。所以,在HSYNC有效之后,总共要经过(HSPW+1+HBP+1)个无效的像素,它对应左边框,第一个有效的像素才出现。
  4. 随后即连续发出(HOZVAL+1)个像素的有效数据。
  5. 最后是(HFP+1)个无效的像素,它对应右边框,完整的一行结束,紧接着就是下一行的数据了。

RGB LCD帧显示时序

  1. VSYNC信号有效时,表示一帧数据的开始。
  2. VSPW表示VSYNC信号的脉冲宽度为(VSPW+1)个HSYNC信号周期,即(VSPW+1)行,这(VSPW+1)行的数据无效。
  3. VSYNC信号脉冲之后,还要经过(VBP+1)个HSYNC信号周期,有效的行数据才出现。所以,在VSYNC信号有效后要经过(VSPW+1+VBP+1)个无效的行,第一个有效行才出现,对应上边框。
  4. 随后即连续发出(LINEVAL+1)行的有效数据。
  5. 最后是(VFPD+1)个无效的行,它对应下边框,完整的一帧结束,紧接着就是下一帧数据了。

RGB LCD屏幕时序参数 

2. 字符图像

2.1 字符矩阵

        字符的大小决定了字符显示区域内像素点的数目,而字符的样式(字体、颜色等)则决定了各像素点的颜色值。因此,在显示字符之前,我们需要先指定字符的大小、样式,然后获取该字符的点阵,这个 过程我们称之为“提取字模 ,或简称 取模
        我们一般使用 0 1 的组合来描述字符的点阵排列:点阵中每个像素点用一位( 1 bit )数据来表示,其中用于表征字符的像素点用数字 1 来表示,其他的像素点作为背景用数字 0 来表示。字模的提取可通过字符取模软件来实现,这里不过多赘述。

我将四个字视为一个整体来取字模,需要注意的是图左下角每行显示数据是以字节(Byte)为单位的,而一个字节的数据为 8 bit,即可以表示一行点阵中的 8 个像素点。由于图 中的点阵每行为 128 个像素点,所以需要 16 Byte 的数据来表示一行,因此将“每行显示数据点阵处设置为 16。 数据以十六进制显示,每行有 16 个 Byte,对应每行四个汉字共 128 个像素点;共有32 行,对应每个汉字的高度为 32

##天道酬勤字模
00000000000000000000000000000000;
00000000000000000000000000000000;
00000000000001000000001002080400;
000000800C0301C00002301C030C0700;
000001C0060181807FFF3218020C8600;
07FFFFE00301C300036031983FFFC600;
000300000300C20003603118020C0600;
000300000300841803603118020C0600;
00030000003FFFFC03603118020C0600;
00030000000030001FFE311803FC0608;
00030000000030001364311802687FFC;
00030000010420401364B19800610608;
000300207F87FFE01364B9580FFF8608;
00030070030600C01364BD780C610608;
3FFFFFF8030600C01264B5780C610608;
000340000307FFC01265B5380C610608;
00024000030600C01265A1180C610618;
00064000030600C0143C21180FFF0618;
00062000030600C0140421180C610418;
000620000307FFC01804211800610418;
000C1000030600C0100421181FFF8C18;
000C1800030600C01FFC611800600C18;
00180800030600C01004611800610818;
00300C000307FFC0100441180FFF9818;
00300600048600C01004411800601818;
00600300184600801004C11800603018;
00C003C0302000001FFC81180061A030;
010001F0301C00001007811801FE43F0;
0600007E000FFFFC100100183F0080F0;
0800003000007FF00002001810010060;
30000000000000000004001000060000;
00000000000000000000000000000000;
提取字模完成后,我们需要在 LCD 显示模块中将获取的点阵数据映射到液晶屏中心 32*128 个的像素 点的字符显示区域,从而实现字符的显示。

2.2  图像矩阵

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

在 Vivado 软件中,RAM ROM 都是由 BMG IP 核(Block Memory Generator)配置生成的,ROM的配置过程和 RAM 类似在“Port A Options”选项卡中,设置 ROM 读端口的位宽和深度,因为我们的像素数据是“RGB888” 格式,所以端口位宽要设置成 24 位;使用“Notepad++”编辑器打开.coe 初始化文件,可以看到存储的数 据共有 10000 个数据,所以端口深度设置成 10000。与 RAM IP 核一样,我们同样不使用流水线寄存器。 “Port A Options”选项卡的设置如下图所示:

最重要的一部就是添加我们的.coe文件 

 最后点击 OK,完成对 ROM 的配置

3. 代码实现

3.1 IOBuffer

vivado中,连接的管脚的信号一般都会自动添加OBUF或IBUF。但是对于inout类型的接口,不会主动添加IOBUF,因为in/out切换需要控制信号,需要用户自己分配好。IOBUF这个原语在Xilinx的原语手册有说明,主要作为三态端口使用,作用是把FPGA内部三态信号与外部的双向信号连接。

当信号T为1时,IOBUF作为输入,即外部信号输入到FPGA;T为0时IOBUF作为输出。实际使用时,方向引脚对应T,代码模块输出对应IOBUF的I,代码块输入对应IOBUF的O。需要注意代码块的引脚方向信号必须是1为输入,0为输出,IOBUF的IO脚直接连FPGA引脚。

assign语句实现

module PIOBUF
 2     (
 3         inout        IO  ,
 4         input        T   ,
 5         input        I   ,    
 6         output       O     
 7     );
 8 
 9     assign O      = IO;        
10     assign IO     = ~T ? I : 1'bz; 
11     
12 endmodule
module PIOBUF
    (
        inout        IO  ,
        input        T   ,
        input        I   ,
        output       O
    );

    assign O      = T     ? IO : I;
    assign IO     = ~T    ? I  : 1'bz;

endmodule

以上便是最常见的两种三态门写法,很多双向口都是使用该方法。实际上这两种方式是一样的,assign语句是线型,所以当T为0时,直接把I的值赋给O与先把值赋给IO,IO再赋给O,结果是一样的。

在该LCD显示中,由于 lcd_rgb 24 位的双向引脚,所以这里对双向引脚的方向做一个切换。当 lcd_de 信号为高电平时,此时输出的像素数据有效,将 lcd_rgb 的引脚方向切换成输出,并将 LCD 驱动模块输出的 lcd_rgb_o(像素数据)连接至 lcd_rgb 引脚; 当 lcd_de 信号为低电平时,此时输出的像素数据无效,将 lcd_rgb 的引脚方向切换成输入。代码中将高阻状态“Z”赋值给 lcd_rgb 的引脚,表示此时 lcd_rgb 的引脚电平由外围电路决定,此时可以读取 lcd_rgb 的引脚电平,从而获取到 LCD 屏的 ID,获得了LCD的ID之后就便于LCD的时钟分频模块计算LCD的驱动时钟

//像素数据方向切换
32 assign lcd_rgb = lcd_de ? lcd_rgb_o : {24{1'bz}};
33 assign lcd_rgb_i = lcd_rgb;

原语实现

// IOBUF: Single-ended Bi-directional Buffer
 2 // All devices
 3 // Xilinx HDL Language Template, version 2020.1
 4 IOBUF #(
 5 .DRIVE(12), // Specify the output drive strength
 6 .IBUF_LOW_PWR("TRUE"), // Low Power - "TRUE", High Performance = "FALSE"
 7 .IOSTANDARD("DEFAULT"), // Specify the I/O standard
 8 .SLEW("SLOW") // Specify the output slew rate
 9 ) IOBUF_inst (
10 .O(O), // Buffer output
11 .IO(IO), // Buffer inout port (connect directly to top-level port)
12 .I(I), // Buffer input
13 .T(T) // 3-state enable input, high=input, low=output
14 );
15 // End of IOBUF_inst instantiation

根据Xilinx的IOBUF原语T=0时,I与O信号是连接的特性,可以实现一些接口的小妙用。比如对于音频接口I2S,如果RX与TX作为两个模块,在FPGA作为时钟WCLK,BCLK输出,同时使用数据输出输入时,那么WCLK,BCLK分别使用IOBUF连接可以简单的解决既能发送数据,也能采样接收的数据。也可以在GPIO使用,三态口接IOBUF,输出高低电平时可以通过读取输入得到实际输出状态。

3.2 字符图片显示

首先需要定义寄存器将我们的128*32位的字符矩阵寄存,即一行共128bit,共32行

 //给字符数组赋值,显示汉字“天道酬勤”,每个汉字大小为 32*32
43 always @(posedge lcd_pclk) begin
44 char[0 ] <= 128'h00000000000000000000000000000000;
45 char[1 ] <= 128'h00000000000000000000000000000000;
46 char[2 ] <= 128'h00000000000100000000002000000000;
47 char[3 ] <= 128'h000000100001800002000070000000C0;
48 char[4 ] <= 128'h000000380001800003FFFFF803FFFFE0;
49 char[5 ] <= 128'h07FFFFFC0001800003006000000001E0;
50 char[6 ] <= 128'h0000C000000180600300600000000300;
51 char[7 ] <= 128'h0000C0000001FFF00300C00000000600;
52 char[8 ] <= 128'h0000C000000180000310804000001800;
53 char[9 ] <= 128'h0000C00000018000031FFFE000003000;
54 char[10] <= 128'h0000C00000018000031800400001C000;
55 char[11] <= 128'h0000C00000018000031800400001C000;
char[12] <= 128'h00C0C000018181800318004000018000;
57 char[13] <= 128'h00C0C00001FFFFC0031FFFC000018010;
58 char[14] <= 128'h00C0C060018001800318004000018038;
59 char[15] <= 128'h00C0FFF001800180031800403FFFFFFC;
60 char[16] <= 128'h00C0C000018001800318004000018000;
61 char[17] <= 128'h00C0C000018001800218004000018000;
62 char[18] <= 128'h00C0C00001800180021FFFC000018000;
63 char[19] <= 128'h00C0C000018001800210304000018000;
64 char[20] <= 128'h00C0C00001FFFF800200300000018000;
65 char[21] <= 128'h00C0C000018001800606300000018000;
66 char[22] <= 128'h00C0C000018001000607370000018000;
67 char[23] <= 128'h00C0C00000000000060E31C000018000;
68 char[24] <= 128'h00C0C000001000400418307000018000;
69 char[25] <= 128'h00C0C000020830600430303800018000;
70 char[26] <= 128'h00C0C010020C18300860301800018000;
71 char[27] <= 128'h00C0C038060E18180883700800018000;
72 char[28] <= 128'h3FFFFFFC0C0618181100F008003F8000;
73 char[29] <= 128'h000000001C0408182000600000070000;
74 char[30] <= 128'h00000000000000000000000000020000;
75 char[31] <= 128'h00000000000000000000000000000000;
76 end

然后通过计数器来显示对应点的LCD点亮或熄灭状态

//为 LCD 不同显示区域绘制图片、字符和背景色
79 always @(posedge lcd_pclk or negedge rst_n) begin
80 if (!rst_n)
81 pixel_data <= BACK_COLOR;
82 else if( (pixel_xpos >= PIC_X_START) && (pixel_xpos < PIC_X_START + PIC_WIDTH)
83 && (pixel_ypos >= PIC_Y_START) && (pixel_ypos < PIC_Y_START + PIC_HEIGHT) )
84 pixel_data <= rom_rd_data ; //显示图片
85 else if((pixel_xpos >= CHAR_X_START) && (pixel_xpos < CHAR_X_START + CHAR_WIDTH)
86 && (pixel_ypos >= CHAR_Y_START) && (pixel_ypos < CHAR_Y_START + CHAR_HEIGHT))
begin
87 if(char[y_cnt][CHAR_WIDTH -1'b1 - x_cnt])
88 pixel_data <= CHAR_COLOR; //显示字符
89 else
90 pixel_data <= BACK_COLOR; //显示字符区域的背景色
91 end
92 else
93 pixel_data <= BACK_COLOR; //屏幕背景色
94 end

其中代码第82~83行:利用行场坐标来限制图像的输出范围。

( pixel_xpos >= PIC_X_START ) && ( pixel_xpos < PIC_X_START + PIC_WIDTH )
&& ( pixel_ypos >= PIC_Y_START ) && ( pixel_ypos < PIC_Y_START + PIC_HEIGHT )
当满足该范围(真),则读取rom中的值并显示图片
pixel_data <= rom_rd_data ; //显示图片
同理,对于字符而言,当行场坐标落在范围内 if ( char [ y_cnt ][ CHAR_WIDTH - 1'b1 - x_cnt ]) 时,则显示字符,其中 y_cnt和CHAR_WIDTH - 1'b1 - x_cnt分别表示了char字符矩阵的行列坐标,当该坐标点对应的bit为1(真)那么显示字符
pixel_data <= CHAR_COLOR ; //显示字符
为bit0(假)那么显示背光
pixel_data <= BACK_COLOR ; //屏幕背景色
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值