一.LCD1602简要介绍
LCD1602,根据名称可以知道,就是能显示2行,每行16个字符的液晶,只能显示字母,数字和符号等字符,不能显示汉字,图片。如下图:
市面上卖的的LCD1602操作基本上都是相同的,只是带不带背光之分。其控制芯片都是HD44780及其兼容芯片,所以控制接口都是一样,控制时序可以说是68并口时序。1LCD602控制线主要有4根:
(1)RS:数据/指令选择端,当RS=0,写指令;当RS=1,写数据。
(2)RW:读/写选择端,当RW=0,写指令/数据;当RW=1,读状态/数据。
(3)EN:使能端,下降沿使指令/数据生效。
(4)Data[7:0]:8根并行数据口。
外引接口:
操作时序图:
控制指令:
指令方面只讲解一下显示模式设置指令0x38,0x31的区别。其实模式设置指令就是上图中的指令6:0x38:设置8位格式,2行,5*7;0x31:设置8位格式,2行,5*7。为什么要介绍0x31呢,一般单片机驱动LCD1602都是0x38的?
由于一般的LCD1602都是VDD=5V驱动的,而有些FPGA开发板上的LCD1602接口是由3.3V供电的。也就是VDD=3.3V,这样就会引起供电不足的问题,所以经过试验得到,当VDD=3.3V时,显示模式设置指令写入0x38时,LCD1602显示很暗,看不到;进而改为0x31时,只显示1行,LCD1602就正常显示了。这个要引起注意,下面我的代码就是只显示1行的。
其他指令详解请查看数据手册。
二.FPGA驱动LCD1602思路
FPGA驱动LCD1602,其实就是通过同步状态机模拟单片机驱动LCD1602,由并行模拟单步执行,状态过程就是先初始化LCD1602,然后写地址,最后写入显示数据。
1.首先,我们要明白LCD1602是慢速器件。如果直接用FPGA外接的几十兆时钟直接驱动肯定是不行的,所以要对FPGA时钟进行分频驱动,或者计数延时使能驱动。
这里我采用的计数延时使能驱动,代码中通过计数器定时得出lcd_clk_en信号线驱动。要注意的是不同厂家生产的LCD1602的时序延时都不同,但大多数都是纳秒级的,这里我采用的是间隔500ns使能驱动,最好延时长一些比较可靠,这个可以自己尝试修正。
2.LCD1602的初始化过程需要明白。大家估计都用单片机驱动过LCD1602,这里FPGA驱动LCD1602的初始化过程也是一样的。主要是以下4条指令的配置:
(1)显示模式设置Mode_Set:8’h38
(2)显示开/关及光标设置Cursor_Set:8’h0c
(3)显示地址设置Address_Set:8’h06
(4)清屏设置Clear_Set:8'h01
这里需要注意是写指令,所以RS=0,并且写完指令后,EN下降沿使能。
3.初始化完成后,还需要写入地址,第一行初始地址:8’h80;第二行初始地址:8’h80+8”h40=8’hc0。这里RS=0,并且写完地址后,EN下降沿使能。
4.写入地址后,就可以显示字符啦。但需要注意LCD1602写入设置地址指令8’h06后,地址是随每写入一个数据后,默认自加一的。这个一定要明白,不然作动态显示时,就会出现问题。一定要把握我们的数据是要显示在哪个位置,而LCD1602写入地址是会默认地址指针加一的。这里RS=1,并且写完数据后,EN下降沿使能。
5.由于我们要动态显示,所以数据要刷新。这里由于我们采用的是同步状态机模拟LCD1602的控制时序,所以在显示完最后的数据后,状态要跳回写入地址状态,以便进行动态刷新。这个很重要,不只是保证刷新,更是保证地址没有偏移。
以上就是大致的思路步骤了,大家可以结合下面的代码进行分析消化,只有你完全弄清LCD1602的控制时序,指令要点,才能完全把LCD1602玩弄于手掌之中,不然似懂非懂,终究会有无法理解的问题出现。
三.Verilog代码
本来想把LCD1602_Driver封装成个模块的,然后直接向其写入地址,数据即可显示的,后来由于能力问题,发现不怎么好写,就没有封装成模块了。不过下面动态显示的代码还是可以给大家一个参考的,虽然代码时序不是很严谨。
下面代码功能主要是完成一个0-99s的计数器,动态刷新。主要是在LCD1602上动态显示:“Cnt:00”。
+ 查看代码
/***********************************************************************
************************* name:LCD1602_Driver *************************
************************* author:made by zzuxzt **************************
************************* time:2014.5.18 ********************************
***********************************************************************/
module lcd1602_driver(input clk, //50M
input rst_n,
output lcd_p, //Backlight Source +
output lcd_n, //Backlight Source -
output reg lcd_rs, //0:write order; 1:write data
output lcd_rw, //0:write data; 1:read data
output reg lcd_en, //negedge
output reg [7:0] lcd_data);
//--------------------lcd1602 order----------------------------
parameter Mode_Set = 8'h31,
Cursor_Set = 8'h0c,
Address_Set = 8'h06,
Clear_Set = 8'h01;
/****************************LCD1602 Display Data****************************/
wire [7:0] data0,data1; //counter data
wire [7:0] addr; //write address
//---------------------------------1s counter-----------------------------------
reg [31:0] cnt1;
reg [7:0] data_r0,data_r1;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
cnt1 <= 1'b0;
data_r0 <= 1'b0;
data_r1 <= 1'b0;
end
else if(cnt1==32'd50000000)
begin
if(data_r0==8'd9)
begin
data_r0 <= 1'b0;
if(data_r1==8'd9)
data_r1 <= 1'b0;
else
data_r1 <= data_r1 + 1'b1;
end
else
data_r0 <= data_r0 + 1'b1;
cnt1 <= 1'b0;
end
else
cnt1 <= cnt1 + 1'b1;
end
assign data0 = 8'h30 + data_r0 ;
assign data1 = 8'h30 + data_r1 ;
//-------------------address------------------
assign addr = 8'h80;
/****************************LCD1602 Driver****************************/
//-----------------------lcd1602 clk_en---------------------
reg [31:0] cnt;
reg lcd_clk_en;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
cnt <= 1'b0;
lcd_clk_en <= 1'b0;
end
else if(cnt == 32'h24999) //500us
begin
lcd_clk_en <= 1'b1;
cnt <= 1'b0;
end
else
begin
cnt <= cnt + 1'b1;
lcd_clk_en <= 1'b0;
end
end
//-----------------------lcd1602 display state-------------------------------------------
reg [4:0] state;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
state <= 1'b0;
lcd_rs <= 1'b0;
lcd_en <= 1'b0;
lcd_data <= 1'b0;
end
else if(lcd_clk_en)
begin
case(state)
//-------------------init_state---------------------
5'd0: begin
lcd_rs <= 1'b0;
lcd_en <= 1'b1;
lcd_data <= Mode_Set;
state <= state + 1'd1;
end
5'd1: begin
lcd_en <= 1'b0;
state <= state + 1'd1;
end
5'd2: begin
lcd_rs <= 1'b0;
lcd_en <= 1'b1;
lcd_data <= Cursor_Set;
state <= state + 1'd1;
end
5'd3: begin
lcd_en <= 1'b0;
state <= state + 1'd1;
end
5'd4: begin
lcd_rs <= 1'b0;
lcd_en <= 1'b1;
lcd_data <= Address_Set;
state <= state + 1'd1;
end
5'd5: begin
lcd_en <= 1'b0;
state <= state + 1'd1;
end
5'd6: begin
lcd_rs <= 1'b0;
lcd_en <= 1'b1;
lcd_data <= Clear_Set;
state <= state + 1'd1;
end
5'd7: begin
lcd_en <= 1'b0;
state <= state + 1'd1;
end
//--------------------work state--------------------
5'd8: begin
lcd_rs <= 1'b0;
lcd_en <= 1'b1;
lcd_data <= addr; //write addr
state <= state + 1'd1;
end
5'd9: begin
lcd_en <= 1'b0;
state <= state + 1'd1;
end
5'd10: begin
lcd_rs <= 1'b1;
lcd_en <= 1'b1;
lcd_data <= "C"; //write data
state <= state + 1'd1;
end
5'd11: begin
lcd_en <= 1'b0;
state <= state + 1'd1;
end
5'd12: begin
lcd_rs <= 1'b1;
lcd_en <= 1'b1;
lcd_data <= "n"; //write data
state <= state + 1'd1;
end
5'd13: begin
lcd_en <= 1'b0;
state <= state + 1'd1;
end
5'd14: begin
lcd_rs <= 1'b1;
lcd_en <= 1'b1;
lcd_data <= "t"; //write data
state <= state + 1'd1;
end
5'd15: begin
lcd_en <= 1'b0;
state <= state + 1'd1;
end
5'd16: begin
lcd_rs <= 1'b1;
lcd_en <= 1'b1;
lcd_data <= ":"; //write data
state <= state + 1'd1;
end
5'd17: begin
lcd_en <= 1'b0;
state <= state + 1'd1;
end
5'd18: begin
lcd_rs <= 1'b1;
lcd_en <= 1'b1;
lcd_data <= data1; //write data: tens digit
state <= state + 1'd1;
end
5'd19: begin
lcd_en <= 1'b0;
state <= state + 1'd1;
end
5'd20: begin
lcd_rs <= 1'b1;
lcd_en <= 1'b1;
lcd_data <= data0; //write data: single digit
state <= state + 1'd1;
end
5'd21: begin
lcd_en <= 1'b0;
state <= 5'd8;
end
default: state <= 5'bxxxxx;
endcase
end
end
assign lcd_rw = 1'b0; //only write
//------------------backlight driver----------------
assign lcd_n = 1'b0;
assign lcd_p = 1'b1;
endmodule