基于FPGA的LCD1602显示屏驱动

news/2024/11/14 19:38:11/

一、LCD1602显示原理

1、引脚功能

其内部功能框图如下图所示:

一般来说,LCD1602有16条引脚,各个厂家的LCD1602可能略有不同,但基本上一样,其16个引脚功能如下:

LCD1602引脚功能
引脚号引脚名电压等级功能
1VSS0V电源地
2VDD+5V电源正极
3V00V电压偏置
4RSH/L命令/数据
5R/WH/L读/写
6EH/L使能
7~14DB0~DB7H/L数据端口
15LEDA+5V背光正极
16LEDK0V背光负极

对这个表的说明:

1.    VSS接电源地。

2.    VDD接+5V。

3.    VO是液晶显示的偏压信号,可接10K的3296精密电位器。或同样阻值的RM065/RM063蓝白可调电阻。见下图。

4.    RS是命令/数据选择引脚,接单片机的一个I/O,当RS为低电平时,选择命令;当RS为高电平时,选择数据。

5.    RW是读/写选择引脚,接单片机的一个I/O,当RW为低电平时,向LCD1602写入命令或数据;当RW为高电平时,从LCD1602读取状态或数据。如果不需要进行读取操作,可以直接将其接VSS。

6.    E,执行命令的使能引脚,接单片机的一个I/O。

7.    D0—D7,并行数据输入/输出引脚,可接单片机的P0—P3任意的8个I/O口。如果接P0口,P0口应该接4.7K—10K的上拉电阻。如果是4线并行驱动,只须接4个I/O口。

8.    A背光正极,可接一个10—47欧的限流电阻到VDD。

9.    K背光负极,接VSS。见下图所示。

2、基本操作

LCD1602的基本操作分为四种:

1.    读状态:输入RS=0,RW=1,E=高脉冲。输出:D0—D7为状态字。

2.    读数据:输入RS=1,RW=1,E=高脉冲。输出:D0—D7为数据。

3.    写命令:输入RS=0,RW=0,E=高脉冲。输出:无。

4.    写数据:输入RS=1,RW=0,E=高脉冲。输出:无。

读操作时序图和约束:

 写操作时序图:

时序时间参数:

3、DDRAM、CGROM和CGRAM

DDRAM(Display Data RAM)就是显示数据RAM,用来寄存待显示的字符代码。共80个字节,其地址和屏幕的对应关系如下:

DDRAM相当于计算机的显存,我们为了在屏幕上显示字符,就把字符代码送入显存,这样该字符就可以显示在屏幕上了。同样LCD1602共有80个字节的显存,即DDRAM。但LCD1602的显示屏幕只有16×2大小,因此,并不是所有写入DDRAM的字符代码都能在屏幕上显示出来,只有写在上图所示范围内的字符才可以显示出来,写在范围外的字符不能显示出来。这样,我们在程序中可以利用下面的“光标或显示移动指令”使字符慢慢移动到可见的显示范围内,看到字符的移动效果。

前面说了,为了在液晶屏幕上显示字符,就把字符代码送入DDRAM。例如,如果想在屏幕左上角显示字符‘A’,那么就把字符‘A’的字符代码41H写入DDRAM的00H地址处即可。至于怎么写入,后面会有说明。那么为什么把字符代码写入DDRAM,就可以在相应位置显示这个代码的字符呢?我们知道,LCD1602是一种字符点阵显示器,为了显示一种字符的字形,必须要有这个字符的字模数据,什么叫字符的字模数据,看看下面的这个图就明白了。

上图的左边就是字符‘A’的字模数据,右边就是将左边数据用“○”代表0,用“■”代表1。从而显示出‘A’这个字形。从下面的图可以看出,字符‘A’的高4位是0100,低4位是0001,合在一起就是01000001b,即41H。它恰好与该字符的ASCII码一致,这样就给了我们很大的方便,我们可以在PC上使用P2=‘A’这样的语法。编译后,正好是这个字符的字符代码。

在LCD1602模块上固化了字模存储器,就是CGROM和CGRAM,HD44780内置了192个常用字符的字模,存于字符产生器CGROM(Character Generator ROM)中,另外还有8个允许用户自定义的字符产生RAM,称为CGRAM(Character Generator RAM)。下图(如图12)说明了CGROM和CGRAM与字符的对应关系。从ROM和RAM的名字我们也可以知道,ROM是早已固化在LCD1602模块中的,只能读取;而RAM是可读写的。也就是说,如果只需要在屏幕上显示已存在于CGROM中的字符,那么只须在DDRAM中写入它的字符代码就可以了;但如果要显示CGROM中没有的字符,比如摄氏温标的符号,那么就只有先在CGRAM中定义,然后再在DDRAM中写入这个自定义字符的字符代码即可。和CGROM中固化的字符不同,CGRAM中本身没有字符,所以要在DDRAM中写入某个CGROM不存在的字符,必须在CGRAM中先定义后使用。程序退出后CGRAM中定义的字符也不复存在,下次使用时,必须重新定义。

上面这个图说明的是5×8点阵和5×10点阵字符的字形和光标的位置。先来说5×8点阵,它有8行5列。那么定义这样一个字符需要8个字节,每个字节的前3个位没有被使用。例如,定义摄氏温标的符号{0x10,0x06,0x09,0x08,0x08,0x09,0x06,0x00}。

上面这个图说明的是设置CGRAM地址指令。从这个指令的格式中我们可以看出,它共有aaaaaa这6位,一共可以表示64个地址,即64个字节。一个5×8点阵字符共占用8个字节,那么这64个字节一共可以自定义8个字符。也就是说,上面这个图的6位地址中的DB5DB4DB3用来表示8个自定义的字符,DB2DB1DB0用来表示每个字符的8个字节。这DB5DB4DB3所表示的8个自定义字符(0--7)就是要写入DDRAM中的字符代码。我们知道,在CGRAM中只能定义8个自定义字符,也就是只有0—7这8个字符代码,但在下面的这个表中一共有16个字符代码(××××0000b--××××1111b)。实际上,如图所示,它只能表示8个自定义字符 (××××0000b=××××1000b, ××××0001b=××××1001b……依次类推)。也就是说,写入DDRAM中的字符代码0和字符代码8是同一个自定义字符。 5×10点阵每个字符共占用16个字节的空间,所以CGRAM中只能定义4个这样的自定义字符。

那么如何在CGRAM中自定义字符呢?在上面的介绍中,我们知道有一个设置CGRAM地址指令,同写DDRAM指令相似,只须设置好某个自定义字符的字模数据,然后按照上面介绍的方法,设置好CGRAM地址,依次写入这个字模数据即可。我们在后面的例子中再进行说明。

4、LCD1602指令

(1).工作方式设置指令

×:不关心,也就是说这个位是0或1都可以,一般取0。

DL:设置数据接口位数。

DL=1:8位数据接口(D7—D0)。

DL=0:4位数据接口(D7—D4)。

N=0:一行显示。

N=1:两行显示。

F=0:5×8点阵字符。

F=1:5×10点阵字符。

说明:因为是写指令字,所以RS和RW都是0。LCD1602只能用并行方式驱动,不能用串行方式驱动。而并行方式又可以选择8位数据接口或4位数据接口。这里我们选择8位数据接口(D7—D0)。我们的设置是8位数据接口,两行显示,5×8点阵,即0b00111000也就是0x38。(注意:NF是10或11的效果是一样的,都是两行5×8点阵。因为它不能以两行5×10点阵方式进行显示,换句话说,这里用0x38或0x3c是一样的)。

(2).显示开关控制指令

D=1:显示开,D=0:显示关。

C=1:光标显示,C=0:光标不显示。

B=1:光标闪烁,B=0:光标不闪烁。

说明:这里的设置是显示开,不显示光标,光标不闪烁,设置字为0x0c。

(3).进入模式设置指令

I/D=1:写入新数据后光标右移。

I/D=0:写入新数据后光标左移。

S=1:显示移动。

S=0:显示不移动。

说明:这里的设置是0x06。

(4).光标或显示移动指令

说明:在需要进行整屏移动时,这个指令非常有用,可以实现屏幕的滚动显示效果。初始化时不使用这个指令。

(5).清屏指令

说明:清除屏幕显示内容。光标返回屏幕左上角。执行这个指令时需要一定时间。

(6).光标归位指令

说明:光标返回屏幕左上角,它不改变屏幕显示内容。

(7).设置CGRAM地址指令

说明:这个指令在上面已经介绍过。用法在后面例子中说明。

(8).设置DDRAM地址指令

说明:这个指令用于设置DDRAM地址。在对DDRAM进行读写之前,首先要设置DDRAM地址,然后才能进行读写。前面我们说过,DDRAM就是LCD1602的显示存储器。我们要在它上面进行显示,就要把要显示的字符写入DDRAM。同样,我们想知道DDRAM某个地址上有什么字符,也要先设置DDRAM地址,然后将它读出到单片机。

(9).读忙信号和地址计数器AC

说明:这个指令用来读取LCD1602状态。对于单片机来说,LCD1602属于慢速设备。当单片机向其发送一个指令后,它将去执行这个指令。这时如果单片机再次发送下一条指令,由于LCD1602速度较慢,前一条指令还未执行完毕,它将不接受这新的指令,导致新的指令丢失。因此这条读忙指令可以用来判断LCD1602是否忙,能否接收单片机发来的指令。当BF=1,表示LCD1602正忙,不能接受单片机的指令;当BF=0,表示LCD1602空闲,可以接收单片机的指令。RS=0,表示是指令;RW=1,表示是读取。这条指令还有一个副产品:即可以得到地址记数器AC的值(address counter)。LCD1602维护了一个地址计数器AC,用来记录下一次读写CGRAM或DDRAM的位置。需要强调的是:这条指令我一次也没有执行成功。很多网友似乎也是这样。好在我们有另外的办法,也就是延时。通过查看每条指令的执行时间,再经过一些试验,可以确定指令的延时。这样就可以在上一条指令执行完毕后再执行下一条指令了。

(10).写数据到CGRAM或DDRAM指令

说明:RS=1,数据;RW=0,写。指令执行时,要在DB7—DB0上先设置好要写入的数据,然后执行写命令。

(11).从CGRAM或DDRAM读数据指令

说明:RS=1,数据;RW=1,读。先设置好CGRAM或DDRAM的地址,然后执行读取命令。数据就被读入后DB7—DB0。

5、LCD1602一般初始换步骤(在FPGA里边必须初始化)

  1. 延时15mS
  2. 写指令38H(不检测忙信号)
  3. 延时15mS
  4. 写指令38H(不检测忙信号)
  5. 延时15mS
  6. 写指令38H(不检测忙信号)
  7. 以后每次写指令、读/写数据操作均需要检测忙信号
  8. 写指令38H:显示模式设置
  9. 写指令08H:显示关闭
  10. 写指令01H:显示清屏
  11. 写指令06H:显示光标移动设置
  12. 写指令0CH:显示开及光标设置

11和12 两个顺序不能互换,我在写的时候调换了怎么也调试不出来,左后将两个顺序互换过来就正常了。

 

二、FPGA实现

实现功能:在LCD显示屏上显示两行,第一行显示:Pan-Hong-Feng;第二行显示:LCD1602-Test

代码如下:(代码有点乱,懒得注释了)


// Company  : 
// Engineer : 
// -----------------------------------------------------------------------------
// https://blog.csdn.net/qq_33231534    PHF's CSDN blog
// -----------------------------------------------------------------------------
// Create Date    : 2020-09-07 15:48:40
// Revise Data    : 2020-09-08 09:53:32
// File Name      : lcd.v
// Target Devices : XC7Z015-CLG485-2
// Tool Versions  : Vivado 2019.2
// Revision       : V1.1
// Editor         : sublime text3, tab size (4)
// Description    : LCD1602 drivermodule lcd(input				clk			,input				rst_n		,output	reg			lcd_rs		,output	wire		lcd_rw		,output	reg			lcd_en		,output	reg	[7:0]	lcd_data	);reg	[17:0]	cnt				;reg	[3:0]	state_c			;reg	[3:0]	state_n			;reg	[4:0]	char_cnt		;reg	[7:0]	data_display	;localparamIDLE			= 4'd0	,INIT 			= 4'd1	,S0				= 4'd2	,S1				= 4'd3	,S2				= 4'd4	,S3				= 4'd5	,ROW1_ADDR		= 4'd6	,WRITE			= 4'd7	,ROW2_ADDR		= 4'd8	,stop			= 4'd9	;assign lcd_rw = 1'b0;always @(posedge clk or negedge rst_n) beginif (!rst_n) begincnt <= 17'd0;endelse beginif (cnt==17'd100_000 - 1) begincnt <= 17'd0;endelse begincnt <= cnt + 1'b1;endendendalways @(posedge clk or negedge rst_n) beginif (!rst_n) beginlcd_en <= 0;endelse if (cnt==17'd50_000 - 1) beginlcd_en <= 1;endelse if (cnt==17'd100_000 - 1) beginlcd_en <= 0;endendalways @(posedge clk or negedge rst_n) beginif (!rst_n) beginchar_cnt <= 0;endelse if (state_c==WRITE && cnt==17'd50_000 - 1) beginif (char_cnt==5'd24) beginchar_cnt <= 5'd0;endelse beginchar_cnt <= char_cnt + 1'b1;endendendalways @(*) begincase(char_cnt)5'd0: data_display   = "P";5'd1: data_display   = "a";5'd2: data_display   = "n";5'd3: data_display   = "-";5'd4: data_display   = "H";5'd5: data_display   = "o";5'd6: data_display   = "n";5'd7: data_display   = "g";5'd8: data_display   = "-";5'd9: data_display   = "F";5'd10: data_display  = "e";5'd11: data_display  = "n";5'd12: data_display  = "g";5'd13: data_display  = "L";5'd14: data_display  = "C";5'd15: data_display  = "D";5'd16: data_display  = "1";5'd17: data_display  = "6";5'd18: data_display  = "0";5'd19: data_display  = "2";5'd20: data_display  = "-";5'd21: data_display  = "T";5'd22: data_display  = "e";5'd23: data_display  = "s";5'd24: data_display  = "t";default:data_display = "P";endcaseendalways @(posedge clk or negedge rst_n) beginif (!rst_n) beginstate_c <= IDLE;endelse if(cnt==17'd50_000 - 1) beginstate_c <= state_n;endendreg	[19:0]	cnt_15ms;reg		flag	;always@(posedge clk or negedge rst_n)beginif (!rst_n) begincnt_15ms <= 0;endelse if (state_c == IDLE) begincnt_15ms <= cnt_15ms + 1'b1;endendalways@(posedge clk or negedge rst_n)beginif (!rst_n) beginflag <= 0;endelse if (state_c==IDLE && cnt_15ms==20'd750000) beginflag <= 1;endendalways @(*) begincase(state_c)IDLE		:beginif (flag) beginstate_n = INIT;endelse beginstate_n = state_c;endendINIT 	:beginstate_n = S0;endS0  	:beginstate_n = S1;endS1  	:beginstate_n = S2;endS2  	:beginstate_n = S3;endS3  	:beginstate_n = ROW1_ADDR;endROW1_ADDR:beginstate_n = WRITE;endWRITE		:beginif (char_cnt==5'd12) beginstate_n = ROW2_ADDR;endelse if (char_cnt==5'd24) beginstate_n = stop;endelse beginstate_n = state_c;endendROW2_ADDR:beginstate_n = WRITE;endstop		:beginstate_n = stop;enddefault:state_n = IDLE;endcaseendalways @(posedge clk or negedge rst_n) beginif (!rst_n) beginlcd_data <= 8'd0;endelse begincase(state_c)IDLE		:begin lcd_data <= 8'h38; lcd_rs <= 0;endINIT 		:begin lcd_data <= 8'h38; lcd_rs <= 0;endS0			:begin lcd_data <= 8'h08; lcd_rs <= 0;endS1			:begin lcd_data <= 8'h01; lcd_rs <= 0;endS2			:begin lcd_data <= 8'h06; lcd_rs <= 0;endS3			:begin lcd_data <= 8'h0c; lcd_rs <= 0;endROW1_ADDR	:begin lcd_data <= 8'h80; lcd_rs <= 0;endWRITE		:begin lcd_data <= data_display; lcd_rs <= 1;endROW2_ADDR	:begin lcd_data <= 8'hc0; lcd_rs <= 0;endstop		:begin lcd_data <= 8'h38; lcd_rs <= 0;enddefault:;endcaseendend
endmodule

测试代码:


`timescale 1ns/1nsmodule lcd_tb (); /* this is automatically generated */reg rst_n;reg clk;wire       lcd_rs;wire       lcd_rw;wire       lcd_en;wire [7:0] lcd_data;lcd inst_lcd(.clk      (clk),.rst_n    (rst_n),.lcd_rs   (lcd_rs),.lcd_rw   (lcd_rw),.lcd_en   (lcd_en),.lcd_data (lcd_data));initial clk = 0;always #10 clk = ~clk;initial begin#1;rst_n = 0;#200;rst_n = 1;#200;#100000000;$stop;endendmodule

 仿真波形图:

实物图:

 


http://www.ppmy.cn/news/228001.html

相关文章

S7-200 SMART 老版本固件更新

S7-200 SMART 老版本固件更新 手里有一块S7-200 SMART PLC SR20&#xff0c;不知道什么年头的产品&#xff0c;最近翻出来想让其发挥一下余热。于是上 西门子S7 200 SMART 官网 下载 STEP 7-MicroWIN SMART&#xff0c;默认就是最新版本2.7, PC Access SMART 2.3&#xff0c;一…

xlrd

1、导入模块 import xlrd 2、打开Excel文件读取数据 data xlrd.open_workbook(excelFile.xls) 3、使用技巧 #获取一个工作表 table data.sheets()[0] #通过索引顺序获取 table data.sheet_by_index(0) #通过索引顺序获取 table data.sheet_by_name(uSheet1)#通过名…

XeLaTeX: 支持现代字体 LaTeX 编译器

XeLaTeX: 支持现代字体 LaTeX 编译器 原  文&#xff1a;XeLaTeX 译  者&#xff1a;Xovee 翻译时间&#xff1a;2023年2月21日 文章目录 XeLaTeX: 支持现代字体 LaTeX 编译器介绍基础&#xff1a;Times New Roman为不同的LaTeX模块设置不同的字体Overleaf中的字体安装在O…

(22.12.20)matlab2022+yalmip+cplex安装教程,win11 x64

前言 Hi,你好&#xff01;最近刚刚更换新的电脑设备&#xff0c;安装软件时尽量选择最新版本&#xff0c;但也遇到了大大小小的安装问题&#xff0c;这里把踩到的坑一并总结出来&#xff0c;给出一份还算合理的MATLAByalmipCPLEX安装教程&#xff08;win11&#xff09;。 MAT…

(已解决)网卡驱动Intel(R) Wi-Fi 6 AX200 160MHz,设备无法启动(代码10)

电脑由于重装系统后双显示屏无法正常工作&#xff0c;于是卸载了原先驱动&#xff0c;重启后发现无法连接wifi了&#xff0c; win键X打开设备管理器查看网络适配器&#xff0c;发现Intel(R) Wi-Fi 6 AX200 160MHz前有黄标&#xff0c;无法启动&#xff08;现已修复&#xff09…

Numpy---创建多维数组、创建正态分布、创建均匀分布

1. 创建一个随机整数的多维数组 np.random.randint(low, highNone, sizeNone, dtypel) 参数说明: low : 最小值 highNone: 最大值 highNone时&#xff0c;生成的数值在【0, low&#xff09;区间内 如果使用high这个值&#xff0c;则生成的数值在【low, high&#xff09;区…

控制算法工程师的主要职责描述(合集)

控制算法工程师的主要职责描述1 职责 1、负责公司MW级机组的控制策略参数整定&#xff0c;编写外部控制器 2、通过控制算法的优化实现风电机组轻量化设计 3、负责公司先进的风电机组智能控制算法的控制&#xff0c;风电机组的前馈控制&#xff0c;风电机组载荷在线预估&#xf…

【罗技鼠标650L蓝牙删除后怎么重新连接】

按住鼠标下面的圆形按钮3到5秒即可