🌈以下是本人对LCD液晶显示模块的理解,是为了方便后续的学习,如有补充和错误,欢迎评论区留言!

1.实验设备及要求

实验设备:正点原子达芬奇开发板(Xilinx),LCD液晶显示屏。
实验要求:使用FPGA开发板在LCD液晶显示屏上显示4色彩条,要求兼容四种型号的LCD屏幕。
开发环境:Vivado_2020.2、Notepad++、Visio。


2.LCD液晶显示屏硬件知识汇总

2.1.LCD实物图及区域划分

图二中的VSYNC、HSYNC分别是场同步信号和行同步信号,当一帧图片显示完成,场同步信号拉低表示新的一帧图片显示的开始;当一行信号显示完成时,行同步信号拉低表示下一行数据显示的开始。VFP、VBP、HFP、HBP分别是场前延、场后延、行前延和行后延,这部分区域不显示有效信息,这部分所消耗的时间称为“消隐时间”。以下是以上信号的具体参数和消耗时间:
示例图片

    LCD(1024*600)刷新一帧的时间:
    = (VSPW + VBP + LINE + VFP) * (HSPW + HBP + HOZVAL + HFP)
    = (3 + 20 +600 +12) * (20 +140 +1024 +160)
    = 635 * 1344
    = 853440 ns
LCD显示一行的总时间 = (行后延时间 + 行前延时间 + 行同步信号维持时间 + 行有效数据时间),根据这个公式可知显示一行的时间就是1344ns,而分辨率为1024 * 600的LCD屏的总行数 = (600个有效行数 + 20行场前延 + 12行场后延),除此之外还有场同步信号的维持时间,它维持了3个场(帧)时序,即维持时间是3 * 1344 ns,故总时间就是853440 ns,这是一帧图像所需要的显示时间,如果我需要60HZ刷新率,即一秒钟60帧,则它的所需要的时钟约为51.2 Mhz

2.1.LCD模块引脚分布及功能

示例图片
引脚 作用 引脚 作用
LCD R[0:7] Red像素输入 RESET LCD复位
LCD G[0:7] Green像素输入 LCD BL LCD背光控制
LCD B[0:7] Blue像素输入 LCD DE LCD数据输入
LCD CLK LCD复位 LCD VSYNC LCD场同步信号
PIN 35~39 触摸相关 LCD HSYNC LCD行同步信号

注:R[7]、G[7]、B[7]的引脚为inout类型,FPGA通过读取这三个引脚的值,可以判断当前LCD的分辨率。


3.实验设计

3.1硬件模块的设计

  1. rd_id模块
    示例图片
管脚名称 引脚类型 作用
clk input 时钟
rst_n input 复位
lcd_rgb input 判断LCD分辨率
lcd_id output 输出LCD类型

rd_id模块作用:通过R[7]、G[7]、B[7]的信号判断当前的LCD屏id数据。

  1. clk_div模块
    图片5.png
管脚名称 引脚类型 作用
clk input 时钟
rst_n input 复位
lcd_id input 读取当前LCD类型
lcd_pclk output 根据LCD类型输出对应时钟

clk_div模块作用:根据rd_id模块的lcd_id参数,输出对应的时钟频率,为后续模块提供时钟信号。

  1. lcd_display模块
    图片7.png
管脚名称 引脚类型 作用
lcd_pclk input 时钟
rst_n input 复位
pixel_xpos input 当前像素点横坐标
pixel_ypos input 当前像素点纵坐标
h_disp input LCD屏水平分辨
v_disp input LCD屏垂直分辨
pixel_data output 输出像素数据

lcd_display模块作用:lcd_drive模块提供当前像素坐标以及LCD分辨率,根据以上信息,输出当前位置需要显示的颜色数据。

  1. lcd_drive模块
    图片6.png
管脚名称 引脚类型 作用
lcd_pclk input 时钟
rst_n input 复位
lcd_id input 读取当前LCD类型
pixel_data input 读取像素数据
pixel_xpos output 当前像素点横坐标
pixel_ypos output 当前像素点纵坐标
h_disp output LCD屏水平分辨
v_disp output LCD屏垂直分辨
lcd_de output LCD数据使能信号
lcd_hs output LCD行同步信号
lcd_vs output LCD场同步信号
lcd_bl output LCD背光控制信号
lcd_clk output LCD像素时钟
lcd_rst output LCD复位
lcd_rgb output LCD颜色数据

lcd_drive模块:读取LCD的id参数,并向lcd_drive模块中提供LCD的分辨率和当前像素位置坐标,根据内部的lcd_de信号,通过LCD显示颜色。

  1. 顶层模块框架
    图片8.png

3.2程序设计

以下源码由正点原子提供

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
module rd_id(
input clk , //时钟
input rst_n , //复位,低电平有效
input [23:0] lcd_rgb, //RGB LCD像素数据,用于读取ID
output reg [15:0] lcd_id //LCD屏ID
);

//reg define
reg rd_flag; //读ID标志

//*****************************************************
//** main code
//*****************************************************

//获取LCD ID M2:B7 M1:G7 M0:R7
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
rd_flag <= 1'b0;
lcd_id <= 16'd0;
end
else begin
if(rd_flag == 1'b0) begin
rd_flag <= 1'b1;
case({lcd_rgb[7],lcd_rgb[15],lcd_rgb[23]})
3'b000 : lcd_id <= 16'h4342; //4.3' RGB LCD RES:480x272
3'b001 : lcd_id <= 16'h7084; //7' RGB LCD RES:800x480
3'b010 : lcd_id <= 16'h7016; //7' RGB LCD RES:1024x600
3'b100 : lcd_id <= 16'h4384; //4.3' RGB LCD RES:800x480
3'b101 : lcd_id <= 16'h1018; //10' RGB LCD RES:1280x800
default : lcd_id <= 16'd0;
endcase
end
end
end
endmodule

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
module clk_div(
input clk, //50Mhz
input rst_n,
input [15:0] lcd_id,
output reg lcd_pclk
);

//reg define
reg clk_25m;
reg clk_12_5m;
reg div_4_cnt;

//*****************************************************
//** main code
//*****************************************************

//时钟2分频 输出25MHz时钟
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
clk_25m <= 1'b0;
else
clk_25m <= ~clk_25m;
end

//时钟4分频 输出12.5MHz时钟
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
div_4_cnt <= 1'b0;
clk_12_5m <= 1'b0;
end
else begin
div_4_cnt <= div_4_cnt + 1'b1;
if(div_4_cnt == 1'b1)
clk_12_5m <= ~clk_12_5m;
else
clk_12_5m <= clk_12_5m;
end
end

always @(*) begin
case(lcd_id)
16'h4342 : lcd_pclk = clk_12_5m;
16'h7084 : lcd_pclk = clk_25m;
16'h7016 : lcd_pclk = clk;
16'h4384 : lcd_pclk = clk_25m;
16'h1018 : lcd_pclk = clk;
default : lcd_pclk = 1'b0;
endcase
end

endmodule
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
module lcd_display(
input lcd_pclk, //时钟
input rst_n, //复位,低电平有效
input [10:0] pixel_xpos, //当前像素点横坐标
input [10:0] pixel_ypos, //当前像素点纵坐标
input [10:0] h_disp, //LCD屏水平分辨率
input [10:0] v_disp, //LCD屏垂直分辨率
output reg [23:0] pixel_data //像素数据
);

//parameter define
parameter WHITE = 24'hFFFFFF; //白色
parameter BLACK = 24'h000000; //黑色
parameter RED = 24'hFF0000; //红色
parameter GREEN = 24'h00FF00; //绿色
parameter BLUE = 24'h0000FF; //蓝色

//*****************************************************
//** main code
//*****************************************************

//根据当前像素点坐标指定当前像素点颜色数据,在屏幕上显示彩条
always @(posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)
pixel_data <= BLACK;
else begin
if((pixel_xpos >= 11'd0) && (pixel_xpos < h_disp/5*1))
pixel_data <= WHITE;
else if((pixel_xpos >= h_disp/5*1) && (pixel_xpos < h_disp/5*2))
pixel_data <= BLACK;
else if((pixel_xpos >= h_disp/5*2) && (pixel_xpos < h_disp/5*3))
pixel_data <= RED;
else if((pixel_xpos >= h_disp/5*3) && (pixel_xpos < h_disp/5*4))
pixel_data <= GREEN;
else
pixel_data <= BLUE;
end
end

endmodule

<!-- endtab -->

<!-- tab lcd_driver -->
module lcd_driver(
input lcd_pclk, //时钟
input rst_n, //复位,低电平有效
input [15:0] lcd_id, //LCD屏ID
input [23:0] pixel_data, //像素数据
output reg [10:0] pixel_xpos, //当前像素点横坐标
output reg [10:0] pixel_ypos, //当前像素点纵坐标
output reg [10:0] h_disp, //LCD屏水平分辨率
output reg [10:0] v_disp, //LCD屏垂直分辨率
output reg data_req, //数据请求信号
//RGB LCD接口
output reg lcd_de, //LCD 数据使能信号
output lcd_hs, //LCD 行同步信号
output lcd_vs, //LCD 场同步信号
output lcd_bl, //LCD 背光控制信号
output lcd_clk, //LCD 像素时钟
output lcd_rst, //LCD复位
output [23:0] lcd_rgb //LCD RGB888颜色数据
);

//parameter define
// 4.3' 480*272
parameter H_SYNC_4342 = 11'd41; //行同步
parameter H_BACK_4342 = 11'd2; //行显示后沿
parameter H_DISP_4342 = 11'd480; //行有效数据
parameter H_FRONT_4342 = 11'd2; //行显示前沿
parameter H_TOTAL_4342 = 11'd525; //行扫描周期

parameter V_SYNC_4342 = 11'd10; //场同步
parameter V_BACK_4342 = 11'd2; //场显示后沿
parameter V_DISP_4342 = 11'd272; //场有效数据
parameter V_FRONT_4342 = 11'd2; //场显示前沿
parameter V_TOTAL_4342 = 11'd286; //场扫描周期

// 7' 800*480
parameter H_SYNC_7084 = 11'd128; //行同步
parameter H_BACK_7084 = 11'd88; //行显示后沿
parameter H_DISP_7084 = 11'd800; //行有效数据
parameter H_FRONT_7084 = 11'd40; //行显示前沿
parameter H_TOTAL_7084 = 11'd1056; //行扫描周期

parameter V_SYNC_7084 = 11'd2; //场同步
parameter V_BACK_7084 = 11'd33; //场显示后沿
parameter V_DISP_7084 = 11'd480; //场有效数据
parameter V_FRONT_7084 = 11'd10; //场显示前沿
parameter V_TOTAL_7084 = 11'd525; //场扫描周期

// 7' 1024*600
parameter H_SYNC_7016 = 11'd20; //行同步
parameter H_BACK_7016 = 11'd140; //行显示后沿
parameter H_DISP_7016 = 11'd1024; //行有效数据
parameter H_FRONT_7016 = 11'd160; //行显示前沿
parameter H_TOTAL_7016 = 11'd1344; //行扫描周期

parameter V_SYNC_7016 = 11'd3; //场同步
parameter V_BACK_7016 = 11'd20; //场显示后沿
parameter V_DISP_7016 = 11'd600; //场有效数据
parameter V_FRONT_7016 = 11'd12; //场显示前沿
parameter V_TOTAL_7016 = 11'd635; //场扫描周期

// 10.1' 1280*800
parameter H_SYNC_1018 = 11'd10; //行同步
parameter H_BACK_1018 = 11'd80; //行显示后沿
parameter H_DISP_1018 = 11'd1280; //行有效数据
parameter H_FRONT_1018 = 11'd70; //行显示前沿
parameter H_TOTAL_1018 = 11'd1440; //行扫描周期

parameter V_SYNC_1018 = 11'd3; //场同步
parameter V_BACK_1018 = 11'd10; //场显示后沿
parameter V_DISP_1018 = 11'd800; //场有效数据
parameter V_FRONT_1018 = 11'd10; //场显示前沿
parameter V_TOTAL_1018 = 11'd823; //场扫描周期

// 4.3' 800*480
parameter H_SYNC_4384 = 11'd128; //行同步
parameter H_BACK_4384 = 11'd88; //行显示后沿
parameter H_DISP_4384 = 11'd800; //行有效数据
parameter H_FRONT_4384 = 11'd40; //行显示前沿
parameter H_TOTAL_4384 = 11'd1056; //行扫描周期

parameter V_SYNC_4384 = 11'd2; //场同步
parameter V_BACK_4384 = 11'd33; //场显示后沿
parameter V_DISP_4384 = 11'd480; //场有效数据
parameter V_FRONT_4384 = 11'd10; //场显示前沿
parameter V_TOTAL_4384 = 11'd525; //场扫描周期

//reg define
reg [10:0] h_sync ;
reg [10:0] h_back ;
reg [10:0] h_total;
reg [10:0] v_sync ;
reg [10:0] v_back ;
reg [10:0] v_total;
reg [10:0] h_cnt ;
reg [10:0] v_cnt ;

//*****************************************************
//** main code
//*****************************************************

//RGB LCD 采用DE模式时,行场同步信号需要拉高
assign lcd_hs = 1'b1; //LCD行同步信号
assign lcd_vs = 1'b1; //LCD场同步信号

assign lcd_bl = 1'b1; //LCD背光控制信号
assign lcd_clk = lcd_pclk; //LCD像素时钟
assign lcd_rst= 1'b1; //LCD复位

//RGB888数据输出
assign lcd_rgb = lcd_de ? pixel_data : 24'd0;

//像素点x坐标
always@ (posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)
pixel_xpos <= 11'd0;
else if(data_req)
pixel_xpos <= h_cnt + 2'd2 - h_sync - h_back ;
else
pixel_xpos <= 11'd0;
end

//像素点y坐标
always@ (posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)
pixel_ypos <= 11'd0;
else if(v_cnt >= (v_sync + v_back)&&v_cnt < (v_sync + v_back + v_disp))
pixel_ypos <= v_cnt + 1'b1 - (v_sync + v_back) ;
else
pixel_ypos <= 11'd0;
end

//行场时序参数
always @(*) begin
case(lcd_id)
16'h4342 : begin
h_sync = H_SYNC_4342;
h_back = H_BACK_4342;
h_disp = H_DISP_4342;
h_total = H_TOTAL_4342;
v_sync = V_SYNC_4342;
v_back = V_BACK_4342;
v_disp = V_DISP_4342;
v_total = V_TOTAL_4342;
end
16'h7084 : begin
h_sync = H_SYNC_7084;
h_back = H_BACK_7084;
h_disp = H_DISP_7084;
h_total = H_TOTAL_7084;
v_sync = V_SYNC_7084;
v_back = V_BACK_7084;
v_disp = V_DISP_7084;
v_total = V_TOTAL_7084;
end
16'h7016 : begin
h_sync = H_SYNC_7016;
h_back = H_BACK_7016;
h_disp = H_DISP_7016;
h_total = H_TOTAL_7016;
v_sync = V_SYNC_7016;
v_back = V_BACK_7016;
v_disp = V_DISP_7016;
v_total = V_TOTAL_7016;
end
16'h4384 : begin
h_sync = H_SYNC_4384;
h_back = H_BACK_4384;
h_disp = H_DISP_4384;
h_total = H_TOTAL_4384;
v_sync = V_SYNC_4384;
v_back = V_BACK_4384;
v_disp = V_DISP_4384;
v_total = V_TOTAL_4384;
end
16'h1018 : begin
h_sync = H_SYNC_1018;
h_back = H_BACK_1018;
h_disp = H_DISP_1018;
h_total = H_TOTAL_1018;
v_sync = V_SYNC_1018;
v_back = V_BACK_1018;
v_disp = V_DISP_1018;
v_total = V_TOTAL_1018;
end
default : begin
h_sync = H_SYNC_4342;
h_back = H_BACK_4342;
h_disp = H_DISP_4342;
h_total = H_TOTAL_4342;
v_sync = V_SYNC_4342;
v_back = V_BACK_4342;
v_disp = V_DISP_4342;
v_total = V_TOTAL_4342;
end
endcase
end

//数据使能信号
always@ (posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)
lcd_de <= 1'b0;
else
lcd_de <= data_req;
end

//请求像素点颜色数据输入
always@ (posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)
data_req<=1'b0;
else if((h_cnt >= h_sync + h_back - 2'd2) && (h_cnt < h_sync + h_back + h_disp - 2'd2)
&& (v_cnt >= v_sync + v_back) && (v_cnt < v_sync + v_back + v_disp))
data_req <= 1'b1;
else
data_req <= 1'b0;
end

//行计数器对像素时钟计数
always@ (posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)
h_cnt <= 11'd0;
else begin
if(h_cnt == h_total - 1'b1)
h_cnt <= 11'd0;
else
h_cnt <= h_cnt + 1'b1;
end
end

//场计数器对行计数
always@ (posedge lcd_pclk or negedge rst_n) begin
if(!rst_n)
v_cnt <= 11'd0;
else begin
if(h_cnt == h_total - 1'b1) begin
if(v_cnt == v_total - 1'b1)
v_cnt <= 11'd0;
else
v_cnt <= v_cnt + 1'b1;
end
end
end

endmodule
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
module lcd_rgb_colorbar(
input sys_clk, //系统时钟
input sys_rst_n, //系统复位

//RGB LCD接口
output lcd_de, //LCD 数据使能信号
output lcd_hs, //LCD 行同步信号
output lcd_vs, //LCD 场同步信号
output lcd_bl, //LCD 背光控制信号
output lcd_clk, //LCD 像素时钟
output lcd_rst, //LCD 复位
inout [23:0] lcd_rgb //LCD RGB888颜色数据
);

//wire define
wire [15:0] lcd_id ; //LCD屏ID
wire lcd_pclk ; //LCD像素时钟

wire [10:0] pixel_xpos; //当前像素点横坐标
wire [10:0] pixel_ypos; //当前像素点纵坐标
wire [10:0] h_disp ; //LCD屏水平分辨率
wire [10:0] v_disp ; //LCD屏垂直分辨率
wire [23:0] pixel_data; //像素数据
wire [23:0] lcd_rgb_o ; //输出的像素数据
wire [23:0] lcd_rgb_i ; //输入的像素数据

//*****************************************************
//** main code
//*****************************************************

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

//读LCD ID模块
rd_id u_rd_id(
.clk (sys_clk ),
.rst_n (sys_rst_n),
.lcd_rgb (lcd_rgb_i),
.lcd_id (lcd_id )
);

//时钟分频模块
clk_div u_clk_div(
.clk (sys_clk ),
.rst_n (sys_rst_n),
.lcd_id (lcd_id ),
.lcd_pclk (lcd_pclk )
);

//LCD显示模块
lcd_display u_lcd_display(
.lcd_pclk (lcd_pclk ),
.rst_n (sys_rst_n ),
.pixel_xpos (pixel_xpos),
.pixel_ypos (pixel_ypos),
.h_disp (h_disp ),
.v_disp (v_disp ),
.pixel_data (pixel_data)
);

//LCD驱动模块
lcd_driver u_lcd_driver(
.lcd_pclk (lcd_pclk ),
.rst_n (sys_rst_n ),
.lcd_id (lcd_id ),
.pixel_data (pixel_data),
.pixel_xpos (pixel_xpos),
.pixel_ypos (pixel_ypos),
.h_disp (h_disp ),
.v_disp (v_disp ),
.data_req ( ),

.lcd_de (lcd_de ),
.lcd_hs (lcd_hs ),
.lcd_vs (lcd_vs ),
.lcd_bl (lcd_bl ),
.lcd_clk (lcd_clk ),
.lcd_rst (lcd_rst ),
.lcd_rgb (lcd_rgb_o )
);

endmodule

4.实验结果展示

1709725784991.jpg

5.实验扩展(LCD显示字符和图片)

扩展实验任务要求:在之前实验基础上,将显示彩条变为显示图片和字符。

5.1代码和操作步骤

对代码的修改主要是针对lcd_display模块,这个模块主要作用是控制LCD显示的字符和显示的图片。其可修改代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
module lcd_display(
input lcd_pclk, //时钟
input 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'd216; //字符宽度,6个字符:36*6
localparam CHAR_HEIGHT = 11'd34; //字符高度

localparam BACK_COLOR = 24'hE0FFFF; //背景色,浅蓝色
localparam CHAR_COLOR = 24'hff0000; //字符颜色,红色

//reg define
reg [215:0] char[33: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数据

//*****************************************************
//** main code
//*****************************************************

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数据

//给字符数组赋值,显示汉字“正点原子”,每个汉字大小为32*32
always @(posedge lcd_pclk) begin
char[0 ] <= 216'h000000000000000000000000000000000000000000000000000000;
char[1 ] <= 216'h000000000000000000000000000000000000000000000000006000;
char[2 ] <= 216'h0000000000000C0000000700000000000000000000000000003800;
char[3 ] <= 216'h0000000000000E0000000380000000001800000380000000183000;
char[4 ] <= 216'h00000F0000000E00000001C000000000FE000001C00000031C7000;
char[5 ] <= 216'h0003FF8000000C00000000C00000003F0F000001C00000038C6000;
char[6 ] <= 216'h00FFFE0000000C0000000000000020181C000001800000018C4000;
char[7 ] <= 216'h007FF00000000DE00000001FE0003C00300000018000000184C000;
char[8 ] <= 216'h001CF00000003FE0000007FFF0001C03C000000180000000808300;
char[9 ] <= 216'h001EF0000001FE000007FE0000000C01800000018000000001FF80;
char[10] <= 216'h001CF000000018000003800000000000BE000001800000207F01E0;
char[11] <= 216'h001CF000000018060000103800000003CE0000018000003F8003E0;
char[12] <= 216'h001CFFF8000010FF0000181E0000003D8600000183E00060000300;
char[13] <= 216'h001FFFFC00003F03C0001C0F0000003186000001FFE000600F0400;
char[14] <= 216'h1FFFE03E000FC703C000380300001831A60003FFE00000E3F38000;
char[15] <= 216'h0FDCE03E007887018000703100007C13E60001F9800000C3838000;
char[16] <= 216'h0F1CE03C0060C6018000C0380000D81186000001800000C0060000;
char[17] <= 216'h071CF83C0020C40180018030000018118600000180000000040000;
char[18] <= 216'h071CFE3C002047C10002386000001031F600000380000000180600;
char[19] <= 216'h07387E3800207E0100000C6000001837E6000003400000000FFF80;
char[20] <= 216'h073800380021F801000006C0000018318600000360000003FE0180;
char[21] <= 216'h07F0007800201843000003C00000083186000006300001FF060000;
char[22] <= 216'h0380007800201FE300000180000008318600000638000000060000;
char[23] <= 216'h0383FE7800207E03000003E0000008213E00000E18000000060000;
char[24] <= 216'h03FFFFF00027D803000006700000FE211E00000C0C000000060000;
char[25] <= 216'h03F807F00020180300001C1C0001C1E10E0000180F000000060000;
char[26] <= 216'h038003F0002018030000380F0000007C0400003807800000060000;
char[27] <= 216'h000003E0002018370000E007C000000FE000006003E00000060000;
char[28] <= 216'h000001C00020181F00078003FE000003FFF000C003F800000E0000;
char[29] <= 216'h000001800020180F00000001FF000000FF80030001FF00007C0000;
char[30] <= 216'h0000000000000006000000002000000018000000000000003C0000;
char[31] <= 216'h000000000000000600000000000000000000000000000000180000;
char[32] <= 216'h000000000000000000000000000000000000000000000000000000;
char[33] <= 216'h000000000000000000000000000000000000000000000000000000;
end

//为LCD不同显示区域绘制图片、字符和背景色
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

//根据当前扫描点的横纵坐标为ROM地址赋值
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

下面以显示字符为例,演示如何修改上述代码实现显示不同的字符。

  1. 首先我们要确定我们想要显示的字符以及字符的字宽和字高。这里以正点原子为例,字高和字宽都为32。
  2. 我们需要借助一款软件(PCtoLCD),它可以将字符点阵转化为16进制代码。软件获取方式:软件下载
  3. 打开软件后,接下来如下图所示进行操作:
  1. 点击左上角文件->另存为类型为.bmp的图片文件。接着在此处打开该文件,这时可以注意到软件已经从字符模式变为图片模式。
  2. 点击下方生成字模。并复制字模,对verilog进行修改,即可实现在LCD上显示正点原子

5.2详细修改过程

这个修改过程其实是非常麻烦的,当时也是折磨了我很久我才想明白怎么回事。😭😭😭

  1. 显示的字符和Verilog程序的对应关系:
  • 字高: 它决定了char这个数组的维数。当你的字高设置为32时,那么变量就要定义为char[31:0]。
  • 字数和字宽:这两个参数共同决定了char这个变量的位数。例如字宽为32字数为4时,那么变量char位数就是4*32=128位,结合上面可以给出char变量的最终定义reg [127:0] char[31:0]。另外它也决定了CHAR_WIDTH的赋值。它的赋值结果为localparam CHAR_WIDTH = 11'd128;
  • 字符:它决定了char变量的赋值,不同的字符对应不同的字模。
  1. 显示的字符与PCtoLCD软件参数的设置之间的关系:

这里要注意的是点阵的显示单位是字节,也就是八位,所以在每一行要显示的位数一定是8的倍数,遵守这条规则的前提下,下面的设置就非常好理解了。

  • 字数和字宽:由于一行显示的位数一定要是8的倍数,当我们确认了字数和字宽,那么一行显示的位数就确定了,位数 = 字数*字宽。如果次数的位数不满足是8的倍数,我们可以通过增加字符之间的间隔让其满足上述条件。例如字数为6,字宽为34的西南交通大学,通过计算发现6 * 34 = 204,这并不是8的整数倍,想要确保字符的字宽仍为34,我们就需要在字符之间加入2个间隔。这个间隔的个数可以通过下面的公式计算得出。

假设字数为num,字宽为font_w,字间隔为font_empty,那么就会有如下等式:(font_w + font_empty)*num %8 = 0

  • 字数和字宽:同样的,这两个因素还决定了软件的 字模选项中->每行显示数据->点阵 中的值,在位数满足8的整数倍前提下,值 = (font_w + font_empty)*num /8