zzuxzt

基于FPGA的LCD1602动态显示---Verilog实现

1
阅读(22610)


一.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*70x31:设置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