基于FPGA的LCD1602动态显示---Verilog实现
1赞
一.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