前言
LCD1602作为基础液晶屏,是许多应用工程师绕不过的器件。藉由对LCD1602的学习,我们能了解到液晶屏的工作原理,对今后其他液晶屏的学习有着良好的铺垫作用。
一、LCD1602
LCD1602是指显示内容为16x2,即可以显示两行,每个字符由5x7或5x11等点阵字符位组成,每位之间有一个点距的间隔,每行之间也有间隔,起着字符间距和行间距的作用。
市面上字符液晶大多是基于HD44780液晶芯片的,控制原理完全相同。我们只需要掌握好HD44780的控制程序,便能很方便地应用于市面上大部分的字符型液晶。
首先先看下LCD1602的引脚图,了解下每个引脚的作用。下图为AlteraDE2开发板的LCD1602模块原理图。
从图中可知,LCD总共有14个引脚,每个引脚的功能在下方表格中列出。
Signal Name | Description |
LCD_DATA[0] | 数据口0 |
LCD_DATA[1] | 数据口1 |
LCD_DATA[2] | 数据口2 |
LCD_DATA[3] | 数据口3 |
LCD_DATA[4] | 数据口4 |
LCD_DATA[5] | 数据口5 |
LCD_DATA[6] | 数据口6 |
LCD_DATA[7] | 数据口7 |
LCD_RW | 读写选择端,0为写,1为读 |
LCD_EN | LCD使能端 |
LCD_RS | 指令/数据选择端,0为指令,1为数据 |
LCD_ON | LCD电源开关 |
LCD_BLON | LCD背光开关 |
通过写入指令或数据,控制内部的驱动芯片,从而实现我们所需的操作,这便是LCD的使用思路,那对于内部的控制器来说,总共有11条控制指令,如下表所示:
指令1:清显示。指令码01H,光标复位到地址00H位置,显示清空。
指令2:光标复位。光标返回到地址00H。
指令3:光标和显示位置设置。设定每次写入1位数据后光标移位方向并且设定一次写入一个字符是否移动。I/D控制光标移动方向,高电平右移,低电平左移;S控制屏幕上所有文字是否左移或右移,高电平表示有效,低电平表示无效。
指令4:显示开关控制。D:控制整体的显示开与关,高电平表示开显示,低电平表示关显示,但DDRAM中的数据依然保留;C:控制光标的开关,高电平表示开启光标,低电平表示无光标;B:控制光标是否闪烁,高电平闪烁,低电平不闪烁。
指令5:光标或显示移位。当S/C=0时,R/L控制显示内容和光标一起左移或右移,0为左移,1为右移;当S/C=1时,光标不移动,R/L控制显示内容左移或右移,0为左移,1为右移。
指令6:功能设定。设定数据总线位数、显示的行数和字形。DL=1时数据总线为8位,DL=0时数据总线是4位;N=0时显示一行,N=1时显示两行;F=0时为5*8点阵或字符,F=1时为5*11点阵或字符。
指令7:设定CGRAM地址。设定下一个要存入数据的CGRAM地址。在CGRAM中已经内置了192个字符,但留有8个字符空间允许用户自定义字符,编码表如下所示,最左边的红框即是自定义字符区域,其中0000_0000与0000_1000表示同一个字符,所以看似是十六个,实际仍是八个。
字符需要八行五列来描写(一行作为行间距),因此如果想好写入一个自定义字符,需要连续操作八次。示意图如下
指令8:设置DDRAM地址。DDRAM即为显存,往显存写入什么内容,屏幕便会显示什么,DDRAM的地址与显示屏对照关系如下,00H到0FH为第一行,40H到4FH为第二行,剩余的DDRAM空间不显示,但可以用指令来移位显示。
指令9:读忙信号或AC地址。如果DF=1忙碌,无法接受数据或指令;BF=0可以接收数据、指令。也可以读取AC地址。
指令10:向DDRAM或CGRAM写入数据。
指令11:从DDRAM或者CGRAM读数据。
二、完整代码
module LCD(switch_num,key_detemine,key_displace,ClOCK_50,KEY,LCD_RW,LCD_EN,LCD_RS,LCD_DATA,LCD_ON,LCD_BLON,key_detemine_feedback,key_displace_feedback,number_feedback);input [3:0] switch_num;
input key_detemine,key_displace;
input ClOCK_50;
input [3:0]KEY;
output LCD_RW, LCD_EN, LCD_RS, LCD_ON, LCD_BLON,key_detemine_feedback,key_displace_feedback;
output [7:0]LCD_DATA;
output [3:0] number_feedback;assign LCD_RW = rw;
assign LCD_EN = CLK_LCD;
assign LCD_RS = rs;
assign LCD_ON = 1;
assign LCD_BLON = 1;
assign LCD_DATA = data;parameter space = 8'b0010_0000;parameter CAPITAL_A = 8'b0100_0001;
parameter CAPITAL_B = 8'b0100_0010;
parameter CAPITAL_C = 8'b0100_0011;
parameter CAPITAL_D = 8'b0100_0100;
parameter CAPITAL_E = 8'b0100_0101;
parameter CAPITAL_F = 8'b0100_0110;
parameter CAPITAL_G = 8'b0100_0111;
parameter CAPITAL_H = 8'b0100_1000;
parameter CAPITAL_I = 8'b0100_1001;
parameter CAPITAL_J = 8'b0100_1010;
parameter CAPITAL_K = 8'b0100_1011;
parameter CAPITAL_L = 8'b0100_1100;
parameter CAPITAL_M = 8'b0100_1101;
parameter CAPITAL_N = 8'b0100_1110;
parameter CAPITAL_O = 8'b0100_1111;
parameter CAPITAL_P = 8'b0101_0001;
parameter CAPITAL_Q = 8'b0101_0001;
parameter CAPITAL_R = 8'b0101_0010;
parameter CAPITAL_S = 8'b0101_0011;
parameter CAPITAL_T = 8'b0101_0100;
parameter CAPITAL_U = 8'b0101_0101;
parameter CAPITAL_V = 8'b0101_0110;
parameter CAPITAL_W = 8'b0101_0111;
parameter CAPITAL_X = 8'b0101_1000;
parameter CAPITAL_Y = 8'b0101_1001;
parameter CAPITAL_Z = 8'b0101_1010;parameter LOWERCASE_a = 8'b0110_0001;
parameter LOWERCASE_b = 8'b0110_0010;
parameter LOWERCASE_c = 8'b0110_0011;
parameter LOWERCASE_d = 8'b0110_0100;
parameter LOWERCASE_e = 8'b0110_0101;
parameter LOWERCASE_f = 8'b0110_0110;
parameter LOWERCASE_g = 8'b0110_0111;
parameter LOWERCASE_h = 8'b0110_1000;
parameter LOWERCASE_i = 8'b0110_1001;
parameter LOWERCASE_j = 8'b0110_1010;
parameter LOWERCASE_k = 8'b0110_1011;
parameter LOWERCASE_l = 8'b0110_1100;
parameter LOWERCASE_m = 8'b0110_1101;
parameter LOWERCASE_n = 8'b0110_1110;
parameter LOWERCASE_o = 8'b0110_1111;
parameter LOWERCASE_p = 8'b0111_0000;
parameter LOWERCASE_q = 8'b0111_0001;
parameter LOWERCASE_r = 8'b0111_0010;
parameter LOWERCASE_s = 8'b0111_0011;
parameter LOWERCASE_t = 8'b0111_0100;
parameter LOWERCASE_u = 8'b0111_0101;
parameter LOWERCASE_v = 8'b0111_0110;
parameter LOWERCASE_w = 8'b0111_0111;
parameter LOWERCASE_x = 8'b0111_1000;
parameter LOWERCASE_y = 8'b0111_1001;
parameter LOWERCASE_z = 8'b0111_1010;parameter NUMBER_0 = 8'b0011_0000;
parameter NUMBER_1 = 8'b0011_0001;
parameter NUMBER_2 = 8'b0011_0010;
parameter NUMBER_3 = 8'b0011_0011;
parameter NUMBER_4 = 8'b0011_0100;
parameter NUMBER_5 = 8'b0011_0101;
parameter NUMBER_6 = 8'b0011_0110;
parameter NUMBER_7 = 8'b0011_0111;
parameter NUMBER_8 = 8'b0011_1000;
parameter NUMBER_9 = 8'b0011_1001;parameter CHARACTER_single_quotes = 8'b0101_1110;parameter s_idle = 4'd0;
parameter s_clear = 4'd1;
parameter s_cursor = 4'd2;
parameter s_inputmode = 4'd3;
parameter s_switchmode = 4'd4;
parameter s_shiftmode = 4'd5;
parameter s_setfunction = 4'd6;
parameter s_setgeneraddr = 4'd7;
parameter s_setdataaddr1 = 4'd8;
parameter s_readbasy = 4'd9;
parameter s_writecgram1 = 4'd10;
parameter s_readram = 4'd11;
parameter s_setdataaddr2 = 4'd12;
parameter s_writecgram2 = 4'd13;parameter IDLE = 8'bzzzz_zzzz;
parameter CLEAR = 8'b0000_0001;
parameter CURSOR = 8'b0000_0010;
parameter INPUTMODE = 8'b0000_0110;
parameter SWITCHMODE = 8'b0000_1111;
parameter SHIFTMODE = 8'b0001_1100;
parameter SETFUNCTION = 8'b0011_1100;
parameter SETGENERADDR = 8'b0100_0000;
parameter SETDATAADDR = 8'b1000_0000; wire clk, rst;
assign clk = CLK_LCD;
assign rst = !KEY[1];reg [3:0]state;
reg rw, rs;
reg [7:0]data;
reg [7:0]addr;always @(posedge clk or posedge rst) beginif (rst) begin// resetstate <= s_idle;data <= 8'b0;rw <= 1'b0;rs <= 1'b0;addr <= 8'b1000_0000;endelse begincase (state)s_idle :beginstate <= s_clear;data <= IDLE;rw <= rw;rs <= rs;ends_clear :beginstate <= s_inputmode;data <= CLEAR;rw <= 1'b0;rs <= 1'b0;ends_inputmode :beginstate <= s_switchmode;data <= INPUTMODE;rw <= 1'b0;rs <= 1'b0;ends_switchmode :beginstate <= s_shiftmode;data <= SWITCHMODE;rw <= 1'b0;rs <= 1'b0;ends_shiftmode :beginstate <= s_setfunction;data <= SHIFTMODE;rw <= 1'b0;rs <= 1'b0;ends_setfunction :beginstate <= s_setdataaddr1;data <= SETFUNCTION;rw <= 1'b0;rs <= 1'b0;ends_setdataaddr1:beginstate <= s_writecgram1;data <= addr;rw <= 1'b0;rs <= 1'b0;ends_writecgram1 :beginif (addr == 8'b1000_1111) beginstate <= s_setdataaddr2;data <= lcd_data(addr,num_5,num_4,num_3,num_2,num_1,num_0);rw <= 1'b0;rs <= 1'b1;addr <= 8'b1100_0000;endelse beginstate <= s_writecgram1;data <= lcd_data(addr,num_5,num_4,num_3,num_2,num_1,num_0);rw <= 1'b0;rs <= 1'b1;addr <= addr + 1'b1;end ends_setdataaddr2:beginstate <= s_writecgram2;data <= addr;rw <= 1'b0;rs <= 1'b0;ends_writecgram2 :beginif (addr == 8'b1100_1111) beginstate <= s_setdataaddr1;data <= lcd_data(addr,num_5,num_4,num_3,num_2,num_1,num_0);rw <= 1'b0;rs <= 1'b1;addr <= 8'b1000_0000;endelse beginstate <= s_writecgram2;data <= lcd_data(addr,num_5,num_4,num_3,num_2,num_1,num_0);rw <= 1'b0;rs <= 1'b1;addr <= addr + 1'b1;end enddefault :beginstate <= state;data <= data;rw <= rw;rs <= rs;endendcaseend
endfunction [7:0]lcd_data;input [7:0]lcd_addr;input [3:0] num_5,num_4,num_3,num_2,num_1,num_0;begincase(lcd_addr)8'h80 :lcd_data = CAPITAL_S;8'h81 :lcd_data = LOWERCASE_t;8'h82 :lcd_data = LOWERCASE_e;8'h83 :lcd_data = LOWERCASE_p;8'h84 :lcd_data = CAPITAL_L;8'h85 :lcd_data = LOWERCASE_e;8'h86 :lcd_data = LOWERCASE_n;8'h87 :lcd_data = LOWERCASE_g;8'h88 :lcd_data = LOWERCASE_t;8'h89 :lcd_data = LOWERCASE_h;8'h8b :lcd_data = LOWERCASE_i;8'h8c :lcd_data = LOWERCASE_s;8'hc2 :lcd_data = NUMBER_2;8'hc3 :lcd_data = NUMBER_4;8'hc4 :lcd_data = CHARACTER_single_quotes;8'hc5 :lcd_data = LOWERCASE_h;8'hc6 :lcd_data = number(num_5);8'hc7 :lcd_data = number(num_4);8'hc8 :lcd_data = number(num_3);8'hc9 :lcd_data = number(num_2);8'hca :lcd_data = number(num_1);8'hcb :lcd_data = number(num_0);default:lcd_data = space;endcaseend
endfunctionfunction [7:0]number;
input [3:0] num;begin case(num)0 : number = NUMBER_0;1 : number = NUMBER_1;2:number = NUMBER_2;3:number = NUMBER_3;4:number = NUMBER_4;5:number = NUMBER_5;6:number = NUMBER_6;7:number = NUMBER_7;8:number = NUMBER_8;9:number = NUMBER_9;10:number = LOWERCASE_a;11:number = LOWERCASE_b;12:number = LOWERCASE_c;13:number = LOWERCASE_d;14:number = LOWERCASE_e;15:number = LOWERCASE_f;default:;endcase
end
endfunctionreg[2:0] flag=0;
reg [3:0] num_5,num_4=1,num_3,num_2,num_1,num_0;
always @(posedge key_detemine)
begincase (flag)3'b000:num_0<=switch_num;3'b001:num_1<=switch_num;3'b010:num_2<=switch_num;3'b011:num_3<=switch_num;3'b100:num_4<=switch_num;3'b101:num_5<=switch_num;default:;endcase
end
always @(posedge key_displace)
beginif(flag>=3'b101)flag<=0;else flag<=flag+1;
endparameter CLK_LCD_times = 19'd030000;
reg [22:0]cnt;
reg CLK_LCD;
reg CLK_500Hz;always@(posedge ClOCK_50 or posedge rst) beginif(rst) begincnt <= 19'd0;CLK_500Hz <= 1'b0;endelse if(cnt == CLK_LCD_times) begincnt <= 19'd0;CLK_LCD <= ~CLK_LCD;endelse begin cnt <= cnt+1'b1;end
endassign number_feedback=switch_num;assign key_detemine_feedback = key_detemine ;
assign key_displace_feedback = key_displace;
endmodule
三、实现思路
对LCD的驱动采用状态机的方式编写,状态转移图如下:
我采用的是一直刷新显示的思路,因此不方便观察光标,也没法精准控制光标,读者可以尝试修改状态转移图,使得输出显示一次之后回落到空闲状态,然后等待各种信号控制。