FPGA学习笔记(四):正点原子领航者开发板LCD字符图片显示实战指南
前言
今天学习正点原子领航者开发板LCD字符图片显示模块,以下是记录的笔记和重写的代码。
1:LCD字符图片显示
char[y] [x] :代表第y行,第x列的像素点,可以通过工具来将图片和文字转化成矩阵数组。
reg [15:0] char [15:0] :创建了一个16行x16列的数组。
2: 看波形——写代码
代码分析:
1:像素点相对于字符区域起始点水平坐标:
这里x_cnt和y_cnt的设置不是唯一的,是与字符的寄存器行列相关的,所以下面寄存器变了,他也会改变。
assign x_cnt = pixel_xpos + 1'b1 - CHAR_X_START; //像素点相对于字符区域起始点水平坐标
assign y_cnt = pixel_ypos - CHAR_Y_START; //像素点相对于字符区域起始点垂直坐标
assign rom_rd_en = 1'b1; //读使能拉高,即一直读ROM数据
2:为LCD不同显示区域绘制图片、字符和背景色:这段代码非常重要,既显示图片,又显示字符。
图片:首先分析x轴的像素点pixel_xpos(行像素点)图片是在10~109的范围内,pixel_xpos对应110的位置不能赋值,根据时序图中的rom_data和pixel_data就可以看出来,要输出的pixel_data比rom_data要晚一个周期,所以pixel_data整体上对应行像素点pixel_xpos的位置晚一个周期(应该把D1的值赋给pixel_xpos的10),所以这里的x起始位必须减一,即早一个周期。其次是y的像素点pixel_xpos(场像素点)图片是在10~109的范围内,但是他的周期持续非常长时间,所以保持不变就可以了。
字符:x起始位必须减一的原因同上,具体看时序图中的pixel_data和char两行。不一样的是这里使用的是寄存器,不是rom,所以逐个分析char[y_cnt][CHAR_WIDTH -1'b1 – x_cnt]里的内容。先看纵坐标y的范围是(0~31),而y_cnt = pixel_ypos – CHAR_Y_START中,字符里pixel_ypos的为(120~151),CHAR_Y_START为120,所以他符合行的范围。其次,看横坐标x的范围pixel_xpos是(10~137),[CHAR_WIDTH -1'b1 – x_cnt]中x_cnt = pixel_xpos + 1'b1 – CHAR_X_START,将x_cnt代入,即可得到CHAR_WIDTH-pixel_xpos+CHAR_X_START-2,CHAR_WIDTH为128,CHAR_X_START为10,所以这里得到[CHAR_WIDTH -1'b1 – x_cnt]的范围是(126~-1),符合时序图char信号的内容。因为char信号必须比pixel_xpos信号要早一个周期,因为这里用的是always时序赋值,会晚一个周期。而纵坐标y,行持续时间足够长不需要减一,具体时序图char信号的内容。
always @(posedge lcd_pclk or negedge rst_n) begin
if (!rst_n)
pixel_data <= BACK_COLOR;
else if( (pixel_xpos >= PIC_X_START - 1'b1) && (pixel_xpos < PIC_X_START + PIC_WIDTH - 1'b1)
&& (pixel_ypos >= PIC_Y_START) && (pixel_ypos < PIC_Y_START + PIC_HEIGHT) )
pixel_data <= rom_rd_data ; //显示图片
else if((pixel_xpos >= CHAR_X_START - 1'b1) && (pixel_xpos < CHAR_X_START + CHAR_WIDTH - 1'b1)
&& (pixel_ypos >= CHAR_Y_START) && (pixel_ypos < CHAR_Y_START + CHAR_HEIGHT)) begin
if(char[y_cnt][CHAR_WIDTH -1'b1 - x_cnt])
pixel_data <= CHAR_COLOR; //显示字符
else
pixel_data <= BACK_COLOR; //显示字符区域的背景色
end
else
pixel_data <= BACK_COLOR; //屏幕背景色
end
3:根据当前扫描点的横纵坐标为ROM地址赋值
因为行持续时间很长,所以这里pixel_ypos不用进行减一的操作。而pixel_xpos的时间要减2则是因为always赋值语句会晚一个时钟周期为其赋值,所以得提前一个周期,所以减去1。其次是因为时序图中,rom——rom_data晚一个周期,所以也得提前一个周期,也就是rom为1在时序图中对应pixel_xpos的是9而非10,如果是对应pixel_xpos10就不用减这个1了。
always @(posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)
rom_addr <= 14'd0;
//当横纵坐标位于图片显示区域时,累加ROM地址
else if((pixel_ypos >= PIC_Y_START) && (pixel_ypos < PIC_Y_START + PIC_HEIGHT)
&& (pixel_xpos >= PIC_X_START - 2'd2) && (pixel_xpos < PIC_X_START + PIC_WIDTH - 2'd2))
rom_addr <= rom_addr + 1'b1;
//当横纵坐标位于图片区域最后一个像素点时,ROM地址清零
else if((pixel_ypos >= PIC_Y_START + PIC_HEIGHT))
rom_addr <= 14'd0;
end
module lcd_display(
input lcd_pclk, //时钟
input sys_rst_n, //复位,低电平有效
input [10:0] pixel_xpos, //像素点横坐标
input [10:0] pixel_ypos, //像素点纵坐标
output reg [23:0] pixel_data //像素点数据,
);
//parameter define
localparam PIC_X_START = 11'd10; //图片起始点横坐标
localparam PIC_Y_START = 11'd10; //图片起始点纵坐标
localparam PIC_WIDTH = 11'd100; //图片宽度
localparam PIC_HEIGHT = 11'd100; //图片高度
localparam CHAR_X_START= 11'd10; //字符起始点横坐标
localparam CHAR_Y_START= 11'd120; //字符起始点纵坐标
localparam CHAR_WIDTH = 11'd128; //字符宽度,4个字符:32*4
localparam CHAR_HEIGHT = 11'd32; //字符高度
localparam BACK_COLOR = 24'hE0FFFF; //背景色,浅蓝色
localparam CHAR_COLOR = 24'hff0000; //字符颜色,红色
//reg define
reg [127:0] char[31:0]; //字符数组
reg [13:0] rom_addr ; //ROM地址
//wire define
wire [10:0] x_cnt; //横坐标计数器
wire [10:0] y_cnt; //纵坐标计数器
wire rom_rd_en ; //ROM读使能信号
wire [23:0] rom_rd_data ;//ROM数据
assign x_cnt = pixel_xpos + 1'b1 - CHAR_X_START; //像素点相对于字符区域起始点水平坐标
assign y_cnt = pixel_ypos - CHAR_Y_START; //像素点相对于字符区域起始点垂直坐标
assign rom_rd_en = 1'b1; //读使能拉高,即一直读ROM数据
always @(posedge lcd_pclk) begin
char[0 ] <= 128'h00000000000000000000000000000000;
char[1 ] <= 128'h00000000000000000000000000000000;
char[2 ] <= 128'h00000000000100000000002000000000;
char[3 ] <= 128'h000000100001800002000070000000C0;
char[4 ] <= 128'h000000380001800003FFFFF803FFFFE0;
char[5 ] <= 128'h07FFFFFC0001800003006000000001E0;
char[6 ] <= 128'h0000C000000180600300600000000300;
char[7 ] <= 128'h0000C0000001FFF00300C00000000600;
char[8 ] <= 128'h0000C000000180000310804000001800;
char[9 ] <= 128'h0000C00000018000031FFFE000003000;
char[10] <= 128'h0000C00000018000031800400001C000;
char[11] <= 128'h0000C00000018000031800400001C000;
char[12] <= 128'h00C0C000018181800318004000018000;
char[13] <= 128'h00C0C00001FFFFC0031FFFC000018010;
char[14] <= 128'h00C0C060018001800318004000018038;
char[15] <= 128'h00C0FFF001800180031800403FFFFFFC;
char[16] <= 128'h00C0C000018001800318004000018000;
char[17] <= 128'h00C0C000018001800218004000018000;
char[18] <= 128'h00C0C00001800180021FFFC000018000;
char[19] <= 128'h00C0C000018001800210304000018000;
char[20] <= 128'h00C0C00001FFFF800200300000018000;
char[21] <= 128'h00C0C000018001800606300000018000;
char[22] <= 128'h00C0C000018001000607370000018000;
char[23] <= 128'h00C0C00000000000060E31C000018000;
char[24] <= 128'h00C0C000001000400418307000018000;
char[25] <= 128'h00C0C000020830600430303800018000;
char[26] <= 128'h00C0C010020C18300860301800018000;
char[27] <= 128'h00C0C038060E18180883700800018000;
char[28] <= 128'h3FFFFFFC0C0618181100F008003F8000;
char[29] <= 128'h000000001C0408182000600000070000;
char[30] <= 128'h00000000000000000000000000020000;
char[31] <= 128'h00000000000000000000000000000000;
end
//为LCD不同显示区域绘制图片、字符和背景色
always @(posedge lcd_pclk or negedge sys_rst_n) begin
if(!sys_rst_n)
pixel_data <= BACK_COLOR;
else if((pixel_xpos >= PIC_X_START - 1'b1) && (pixel_xpos < PIC_X_START + PIC_WIDTH - 1'b1)
&& (pixel_ypos >= PIC_Y_START) && (pixel_ypos < PIC_Y_START + PIC_HEIGHT))
pixel_data <= rom_rd_data; //图片
else if((pixel_xpos >= CHAR_X_START - 1'b1) && (pixel_xpos < CHAR_X_START + CHAR_WIDTH - 1'b1)
&& (pixel_ypos >= CHAR_Y_START) && (pixel_ypos < CHAR_Y_START + CHAR_HEIGHT))begin
if(char[y_cnt][CHAR_WIDTH - 1'b1 - x_cnt]) //文字
pixel_data <= CHAR_COLOR; //字符颜色,红色
else
pixel_data <= BACK_COLOR; //背景色,浅蓝色
end
else
pixel_data <= BACK_COLOR;
end
//根据当前扫描点的横纵坐标为ROM地址赋值
always @(posedge lcd_pclk or negedge sys_rst_n) begin
if(!sys_rst_n)
rom_addr <= 14'd0;
else if((pixel_ypos >= PIC_Y_START) && (pixel_ypos < PIC_Y_START + PIC_HEIGHT)
&& (pixel_xpos >= PIC_X_START - 2'd2) && (pixel_xpos < PIC_X_START + PIC_WIDTH - 2'd2))
rom_addr <= rom_addr + 1'b1;
else if(pixel_ypos >= PIC_Y_START + PIC_HEIGHT)
rom_addr <= 14'd0;
else
rom_addr <= rom_addr;
end
blk_mem_gen_0 blk_mem_gen_0 (
.clka (lcd_pclk), // input wire clkaendmodule
.ena (rom_rd_en), // input wire ena
.addra (rom_addr), // input wire [13 : 0] addra
.douta (rom_rd_data) // output wire [23 : 0] douta
);
endmodule
参考内容
作者:学无止境_hjy