目录
LCD1602显示原理
将LCD显示屏与FPGA连接之后,需要做的第一件事就是进行LCD驱动(也就是LCD初始化),之后往LCD里写一些字符,调试LCD是否可以正常使用
这里用的是LCD1602如下图:一共2行,一行16个显示块,其地址和屏幕的对应关系如下:
如果想在屏幕左上角显示字符‘A’,那么就把字符‘A’的字符代码41H写入DDRAM的00H地址处即可
但如果要显示CGROM中没有的字符,比如摄氏温标的符号,那么就只有先在CGRAM中定义,然后再在DDRAM中写入这个自定义字符的字符代码即可。具体参考这篇:基于FPGA的LCD1602显示屏驱动_panhongfeng111的博客-CSDN博客_基于fpga的lcd显示
LCD1602接口
对这里面比较重要的几个接口进行说明:
(1)RS 命令/数据选择引脚当RS为低电平时,选择命令;当RS为高电平时,选择数据。
(2)RW 读/写选择引脚,当RW为低电平时,向LCD1602写入命令或数据;当RW为高电平时,从LCD1602读取状态或数据。如果不需要进行读取操作,可以直接将其接VSS。
(3) E 执行命令的使能引脚。
(4)D0—D7 并行数据输入/输出引脚
LCD1602操作时序
(1)读操作时序
读状态:输入RS=0,RW=1,E=高脉冲。输出:D0—D7为状态字。
读数据:输入RS=1,RW=1,E=高脉冲。输出:D0—D7为数据。
(2)写操作时序
写命令:输入RS=0,RW=0,E=高脉冲。输出:无。
写数据:输入RS=1,RW=0,E=高脉冲。输出:无。
LCD1602初始化
根据数据手册,LCD的初始化需要完成下面12步:
1 延时15ms
2 写指令38H(不检测忙信号)
3 延时5ms
4 写指令38H(不检测忙信号)
5 延时5ms
6 写指令38H(不检测忙信号)
7 以后每次写指令、读/写数据操作均需要检测忙信号
8 写指令38H:显示模式设置
9 写指令08H:显示关闭
10 写指令01H:显示清屏
11 写指令06H:显示光标移动设置
12 写指令0CH:显示开及光标设置
这里面有1次15ms延时,和2次5ms延时,写指令是38H,我们可以把1~8步合在一起,延时25ms,写指令38H,那么初始化就可以简化为5步:
LCD1602读写数据
初始化完成之后一般往LCD里写入一些字符串,验证一下是否可以正常显示
写的时候,要先指定地址,如果在第一行写入,首地址是00H,再加上DB7的1,即80H
如果在第二行写入,首地址是40H,再加上DB7的1,即C0H
因此在初始化完成之后,加入addr1 write1 addr2 write2 四个状态
把写数据和初始化放在一起,完整状态机如下:
module lcd(
input clk ,
input rst ,
input delay_en , //延时完成
output reg lcd_rs ,//状态or数据选择
output reg lcd_rw ,//读or写选择
output reg lcd_en ,//使能信号
output reg [7:0] lcd_data //输出LCD指令
);
reg [7:0] data_display ;//显示数据
reg [5:0] data_cnt; //数据计数器
reg [3:0] state; //状态
parameter IDLE =4'd0;
parameter S0 =4'd1;
parameter S1 =4'd2;
parameter S2 =4'd3;
parameter S3 =4'd4;
parameter S4 =4'd5;
parameter Addr1=4'd6;
parameter WR1 =4'd7;
parameter Addr2=4'd8;
parameter WR2 =4'd9;
parameter stop =4'd10;
always @(*) begin//根据数据计数器输出显示数据
case(data_cnt)
5'd0: data_display = "H";
5'd1: data_display = "E";
5'd2: data_display = "L";
5'd3: data_display = "L";
5'd4: data_display = "O";
5'd5: data_display = "W";
5'd6: data_display = "-";
5'd7: data_display = "W";
5'd8: data_display = "O";
5'd9: data_display = "R";
5'd10: data_display = "L";
5'd11: data_display = "D";
5'd12: data_display = "1";
5'd13: data_display = "6";
5'd14: data_display = "0";
5'd15: data_display = "2";
5'd16: data_display = "h";
5'd17: data_display = "e";
5'd18: data_display = "l";
5'd19: data_display = "l";
5'd20: data_display = "o";
5'd21: data_display = "w";
5'd22: data_display = "h";
5'd23: data_display = "e";
5'd24: data_display = "l";
5'd26: data_display = "l";
5'd27: data_display = "o";
5'd28: data_display = "w";
5'd29: data_display = "-";
5'd30: data_display = "-";
5'd31: data_display = "-";
default:data_display = "-";
endcase
end
always@(posedge clk)begin
if(rst)begin
lcd_rs <=1'd0;
lcd_rw <=1'd0;
lcd_en <=1'd0;
lcd_data<=8'd0;
data_cnt<=6'd0;
state <=IDLE;
end
else begin
case(state)
IDLE :begin
if(delay_en)begin
state <=S0;
end
else begin
state <=IDLE;
end
end
S0 :begin
lcd_rs <=1'd0;
lcd_rw <=1'd0;
lcd_en <=1'd1;
lcd_data<=8'h38;
state <=S1;
end
S1 :begin
lcd_rs <=1'd0;
lcd_rw <=1'd0;
lcd_en <=1'd1;
lcd_data<=8'h08;
state <=S2;
end
S2 :begin
lcd_rs <=1'd0;
lcd_rw <=1'd0;
lcd_en <=1'd1;
lcd_data<=8'h01;
state <=S3;
end
S3 :begin
lcd_rs <=1'd0;
lcd_rw <=1'd0;
lcd_en <=1'd1;
lcd_data<=8'h06;
state <=S4;
end
S4 :begin
lcd_rs <=1'd0;
lcd_rw <=1'd0;
lcd_en <=1'd1;
lcd_data<=8'h0C;
state <=Addr1;
end
Addr1:begin
lcd_rs <=1'd0;
lcd_rw <=1'd0;
lcd_en <=1'd1;
lcd_data<=8'h80;//第一行地址
state <=WR1;
end
WR1 :begin
lcd_rs <=1'd1;
lcd_rw <=1'd0;
lcd_en <=1'd1;
if(data_cnt==6'd15)begin//第一行写完
state <=Addr2;
data_cnt<=6'd0;
end
else begin
data_cnt<=data_cnt+6'd1;
state <=WR1;
lcd_data<=data_display;//显示第一行数据
end
end
Addr2:begin
lcd_rs <=1'd0;
lcd_rw <=1'd0;
lcd_en <=1'd1;
lcd_data<=8'hC0;//第二行地址
state <=WR2;
end
WR2 :begin
lcd_rs <=1'd1;
lcd_rw <=1'd0;
lcd_en <=1'd1;
if(data_cnt==6'd15)begin//第二行写完
state <=stop;
data_cnt<=6'd0;
end
else begin
data_cnt<=data_cnt+6'd1;
lcd_data<=data_display;//显示第二行数据
state <=WR2;
end
end
stop :begin
lcd_rs <=1'd0;
lcd_rw <=1'd0;
lcd_en <=1'd0;
lcd_data<=8'h38;
state <=IDLE;
end
endcase
end
end
endmodule
仿真结果如下: