LCD1602液晶显示实验

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

实验报告
实验名称: [LCD1602液晶显示实验]姓名:
学号:
指导教师:
实验时间: [2013年6月15日]
信息与通信工程学院
LCD1602液晶显示实验
1.实验原理
1.1 基本原理
1.1.1 1602字符型LCD简介
字符型液晶显示模块是一种专门用于显示字母、数字、符号等点阵式LCD,目前常用16*1,16*2,20*2和40*2行等的模块。

1.1.2 1602LCD的基本参数及引脚功能
1602LCD分为带背光和不带背光两种,基控制器大部分为HD44780,带背光的比不带背光的厚,是否带背光在应用中并无差别,两者尺寸差别如下图1-2所示:
图1-2 1602LCD尺寸图
1.1602LCD主要技术参数:
显示容量: 16×2个字符
芯片工作电压: 4.5~5.5V
工作电流: 2.0mA(5.0V)
模块最佳工作电压: 5.0V
字符尺寸: 2.95×4.35(W×H)mm
2.引脚功能说明:
1602LCD采用标准的14脚(无背光)或16脚(带背光)接口,各引脚接口说明如表:
表1-3引脚接口说明表

符号引脚说明编号符号引脚说明

1 VSS 电源地9 D
2 数据
2 VDD 电源正极10 D
3 数据
3 VL 液晶显示偏压11 D
4 数据
4 RS 数据/命令选择12 D
5 数据
5 R/W 读/写选择13 D
6 数据
6 E 使能信号14 D
7 数据
7 D0 数据15 BLA 背光源正极
8 D1 数据16 BLK 背光源负极
1.1.3 1602LCD的指令说明及时序
1602液晶模块内部的控制器共有11条控制指令,如表1-4所示:
表1-4 控制命令表
序号指令RS R/W D7 D6 D5 D4 D3 D2 D1 D0
1 清显示0 0 0 0 0 0 0 0 0 1
2 光标返回0 0 0 0 0 0 0 0 1 *
3 置输入模式0 0 0 0 0 0 0 1 I/D S
4 显示开/关控制0 0 0 0 0 0 1 D C B
5 光标或字符移位0 0 0 0 0 1 S/C R/L * *
6 置功能0 0 0 0 1 DL N F * *
7 置字符发生存贮器
地址
0 0 0 1 字符发生存贮器地址
8 置数据存贮器地址0 0 1 显示数据存贮器地址
9 读忙标志或地址
0 1 BF 计数器地址
10 写数到CGRAM或
DDRAM)
1 0 要写的数据内容
11 从CGRAM或
DDRAM读数
1 1 读出的数据内容
1602液晶模块的读写操作、屏幕和光标的操作都是通过指令编程来实现的。

(说明:1为高电平、0为低电平)读写操作时序如图1-5和1-6所示:
图1-5 读操作时序
图1-6 写操作时序
1.1.4 1602LCD的RAM地址映射及标准字库表
液晶显示模块是一个慢显示器件,所以,在执行每条指令之前,一定要确认模块的忙标志为低电平,表示不忙,否则此指令失效。

要显示字符时,要先输入显示字符地址,也就是告诉模块在哪里显示字符,图1-7是1602的内部显示地址。

图1-7 1602LCD内部显示地址
例如,第二行第一个字符的地址是40H,那么是否直接写入40H就可以将光标定位在第二行第一个字符的位置呢?这样不行,因为写入显示地址时要求最高位D7恒定为高电平1,所以实际写入的数据应该是01000000B(40H)+10000000B(80H)=11000000B(C0H)。

在对液晶模块的初始化中,要先设置其显示模式,在液晶模块显示字符时光标是自动右移的,无需人工干预。

每次输入指令前都要判断液晶模块是否处于忙的状态。

1602液晶模块内部的字符发生存储器(CGROM)已经存储了160个不同的点阵字符图形,这些字符有:阿拉伯数字、英文字母的大小写、常用的符号、和日文假名等,每一个字符都有一个固定的代码,比如大写的英文字母“A”的代码是01000001B(41H),显示时模块把地址41H中的点阵字符图形显示出来,我们就能看到字母“A”
因为1602识别的是ASCII码,试验可以用ASCII码直接赋值,编程中还可以用字符型常量或变量赋值,如“A”。

1602通过D0~D7的8位数据端传输数据和指令。

1.1.5 1602LCD的一般初始化(复位)过程
延时15mS
写指令38H(不检测忙信号)
延时5mS
写指令38H(不检测忙信号)
延时5mS
写指令38H(不检测忙信号)
以后每次写指令、读/写数据操作均需要检测忙信号
写指令38H:显示模式设置
写指令08H:显示关闭
写指令01H:显示清屏
写指令06H:显示光标移动设置
写指令0CH:显示开及光标设置
1.2 任务1及其原理
任务1:更改lcd上显示的字符。

通过更改wire [127:0] row1_val,wire [127:0] row2_val的赋值即可,注意保证“”内的字符数包括空格共16位。

1.3 任务2及其原理
任务2:将“LCD1602驱动模块”文件与“LCD1602驱动测试模块”文件合并成为一个verilog文件。

首先,新建一个顶层VerilogHDL文件,将两个文件的输入,内容合并,调试新的模块,然后将管脚连接在一起,从而完成合并。

1.4 任务3及其原理
任务3:实现液晶屏的某一位完成0~9的循环变换,并且设置复位键,在循环过程中按下复位键循环可从0重新开始。

完成本次的实验想法是将“LCD1602驱动测试模块”文件中wire [127:0] row1_val,wire [127:0] row2_val变量进行改动,将row1_val原本赋予的字符替换为一个变量,设定新的时钟,即循环变换的间隔时间,再通过条件语句控制循环。

使能键的控制原理是通过对循环变换的条件进行操作实现的,即当按键使能时循环开始,反之,则从0开始。

2.实验流程图
0~9循环变换流程图
开始
定义输入输出变
量及类型
lcd 液晶屏初
始化
是否有按键按
下?
复位键是否启
动?
定义a=8'h30;i=0
将a 赋值给row1_val 低8位
是否是时钟输入
上升沿?
i<=8
a=a+1; i=i+1;
结束
显示定义字符







3.实验程序
3.1 任务1程序
程序更改部分:wire [127:0] row1_val = " liu wupeng "; wire [127:0] row2_val = " 2009081206 ";
3.2 任务2程序
将测试模块中的驱动部分,两个文件的对应命名只留其一,合并两文件,即以下部分
// 例化LCD1602驱动
lcd1602_drive u0(
.clk(CLOCK_50M),
.rst_n(Q_KEY),
.row1_val(row1_val),
.row2_val(row2_val),
.lcd_data(LCD1602_DATA),
.lcd_e(LCD1602_E),
.lcd_rs(LCD1602_RS),
.lcd_rw(LCD1602_RW));
完整程序请参见附件:1
3.3 任务3程序
改动模块:LCD1602驱动测试模块,加重处为修改、添加部分
module lcd1602_test(
input CLOCK_50M, // 板载时钟25MHz
input Q_KEY, // 板载按键RST
output [7:0] LCD1602_DATA, // LCD1602数据总线
output LCD1602_E, // LCD1602使能
output LCD1602_RS, // LCD1602指令数据选择
output LCD1602_RW, // LCD1602读写选择
output SEL0, // LCD1602读写选择
output SEL1, // LCD1602读写选择
output SEL2 // LCD1602读写选择
input key, //使能控制键
);
reg [127:0] row1_val; //将原本的wire型改为reg型,保证其可变wire [127:0] row2_val = " ";
//将第二行赋值都是空格,实验时显示为空
reg [7:0] a; //a变化控制reg [127:0] row1_val
reg [3:0] i;
reg [25:0] cnt; //分频用
reg en; //使能控制
assign SEL0 = 1'b0;
assign SEL1 = 1'b0;
assign SEL2 = 1'b1;
always @ (posedge CLOCK_50M,negedge Q_KEY)
if (!Q_KEY) cnt <= 0;
else cnt <= cnt + 1'b1;
wire lcd_clk = cnt[25]; //分频模块
always @(a)
begin
row1_val[7:0]<=a;
end //将a赋给 row1_val[7:0]的低8位
always @(negedge key)
begin
if (en==0) en<=1;
else
en<=0;
end //使能控制模块,下降沿有效
always @(posedge lcd_clk)
if (en)
if(i<=8)
begin
a=a+1;
i=i+1;
end
else
begin
i=0;
a=8'h30;
end
else
a=8'h30; //通过条件控制语句实现0~9的循环变换
// 例化LCD1602驱动
lcd1602_drive u0(
.clk(CLOCK_50M),
.rst_n(Q_KEY),
// LCD1602 Input Value
.row1_val(row1_val),
.row2_val(row2_val),
// LCD1602 Interface
.lcd_data(LCD1602_DATA),
.lcd_e(LCD1602_E),
.lcd_rs(LCD1602_RS),
.lcd_rw(LCD1602_RW)
);
endmodule
4.管脚分配
4.1 任务2管教分配
4.2 任务3管教分配
5.实验结果
5.1 任务1实验结果
修改程序后,液晶屏第一行显示" liu wupeng ",第二行显示" 2009081206 "。

5.2 任务2实验结果
文件合并后,显示内容仍与原始程序相同。

5.3 任务3实验结果
液晶屏第一行最后一位可实现0~9的循环变换,拨动复位键,计数从0重新开始。

6.实验心得
通过本次实验,我了解了LCD-1602的构成及使用方法,能够通过编程显示字符。

在完成课堂任务“实现某一位0~9循环变换”时,起初由于没有掌握LCD的工作原理,出现了问题,即0~9快速变化,无法控制其速度,经过对程序及实验原理的研究,最后完成了任务。

FPGA实验是锻炼编程能力、逻辑思维能力的一门很好的课程,我现在所欠缺的便是缜密的思维及查错的能力,还望在以后实验中提高。

通过这次实验,学到了很多东西,体会到了自己实现一个程序功能的小小喜悦,也认识到了自己只是FPGA道路上的新手,还有很多未知的知识需要学习。

真正学习的历程需要在课下多下功夫,希望通过这学期的学习自己能熟练掌握一些编程的技术,培养良好的思维模式。

附件:1
module hebing(
input clk, // 50MHz时钟
input rst_n, // 复位信号
input [127:0] row1_val, // 第一行字符
input [127:0] row2_val, // 第二行字符
// LCD1602 Interface
output SEL0, // LCD1602读写选择
output SEL1, // LCD1602读写选择
output SEL2, // LCD1602读写选择
// LCD1602 Interface
output reg [ 7:0] lcd_data, // 数据总线
output lcd_e, // 使能信号
output reg lcd_rs, // 指令、数据选择
output lcd_rw // 读、写选择
);
// 0 ~ (8*16-1) = 128
// 16bits -> 0123456789ABCDEF <-
wire [127:0] row1_val = " liu wupeng ";
wire [127:0] row2_val = " 2009081206 ";
assign SEL0 = 1'b0;
assign SEL1 = 1'b0;
assign SEL2 = 1'b1;
// +++++++++++++++++++++++++++++++++++++
// 分频模块开始
// +++++++++++++++++++++++++++++++++++++
reg [15:0] cnt; // 计数子
always @ (posedge clk, negedge rst_n)
if (!rst_n)
cnt <= 0;
else
cnt <= cnt + 1'b1;
// 500Khz ~ 1MHz 皆可
wire lcd_clk = cnt[15]; // (2^15 / 50M) = 1.31ms // -------------------------------------
// 分频模块结束
// -------------------------------------
// +++++++++++++++++++++++++++++++++++++
// LCD1602驱动模块开始
// +++++++++++++++++++++++++++++++++++++
// 格雷码编码:共40个状态
parameter IDLE = 8'h00;
// 写指令,初始化
parameter DISP_SET = 8'h01; // 显示模式设置
parameter DISP_OFF = 8'h03; // 显示关闭
parameter CLR_SCR = 8'h02; // 显示清屏
parameter CURSOR_SET1 = 8'h06; // 显示光标移动设置
parameter CURSOR_SET2 = 8'h07; // 显示开及光标设置
// 显示第一行
parameter ROW1_ADDR = 8'h05; // 写第1行起始地址
parameter ROW1_0 = 8'h04;
parameter ROW1_1 = 8'h0C;
parameter ROW1_2 = 8'h0D;
parameter ROW1_3 = 8'h0F;
parameter ROW1_4 = 8'h0E;
parameter ROW1_5 = 8'h0A;
parameter ROW1_6 = 8'h0B;
parameter ROW1_7 = 8'h09;
parameter ROW1_8 = 8'h08;
parameter ROW1_9 = 8'h18;
parameter ROW1_A = 8'h19;
parameter ROW1_B = 8'h1B;
parameter ROW1_C = 8'h1A;
parameter ROW1_D = 8'h1E;
parameter ROW1_E = 8'h1F;
parameter ROW1_F = 8'h1D;
// 显示第二行
parameter ROW2_ADDR = 8'h1C; // 写第2行起始地址
parameter ROW2_0 = 8'h14;
parameter ROW2_1 = 8'h15;
parameter ROW2_2 = 8'h17;
parameter ROW2_3 = 8'h16;
parameter ROW2_4 = 8'h12;
parameter ROW2_5 = 8'h13;
parameter ROW2_6 = 8'h11;
parameter ROW2_7 = 8'h10;
parameter ROW2_8 = 8'h30;
parameter ROW2_9 = 8'h31;
parameter ROW2_A = 8'h33;
parameter ROW2_B = 8'h32;
parameter ROW2_C = 8'h36;
parameter ROW2_D = 8'h37;
parameter ROW2_E = 8'h35;
parameter ROW2_F = 8'h34;
reg [5:0] current_state, next_state; // 现态、次态// FSM: always1
always @ (posedge lcd_clk, negedge rst_n)
if(!rst_n) current_state <= IDLE;
else current_state <= next_state;
// FSM: always2
always
begin
case(current_state)
IDLE : next_state = DISP_SET;
// 写指令,初始化
DISP_SET : next_state = DISP_OFF;
DISP_OFF : next_state = CLR_SCR;
CLR_SCR : next_state = CURSOR_SET1;
CURSOR_SET1 : next_state = CURSOR_SET2;
CURSOR_SET2 : next_state = ROW1_ADDR;
// 显示第一行
ROW1_ADDR : next_state = ROW1_0;
ROW1_0 : next_state = ROW1_1;
ROW1_1 : next_state = ROW1_2;
ROW1_2 : next_state = ROW1_3;
ROW1_3 : next_state = ROW1_4;
ROW1_4 : next_state = ROW1_5;
ROW1_5 : next_state = ROW1_6;
ROW1_6 : next_state = ROW1_7;
ROW1_7 : next_state = ROW1_8;
ROW1_8 : next_state = ROW1_9;
ROW1_9 : next_state = ROW1_A;
ROW1_A : next_state = ROW1_B;
ROW1_B : next_state = ROW1_C;
ROW1_C : next_state = ROW1_D;
ROW1_D : next_state = ROW1_E;
ROW1_E : next_state = ROW1_F;
ROW1_F : next_state = ROW2_ADDR; // 显示第二行
ROW2_ADDR : next_state = ROW2_0; ROW2_0 : next_state = ROW2_1;
ROW2_1 : next_state = ROW2_2;
ROW2_2 : next_state = ROW2_3;
ROW2_3 : next_state = ROW2_4;
ROW2_4 : next_state = ROW2_5;
ROW2_5 : next_state = ROW2_6;
ROW2_6 : next_state = ROW2_7;
ROW2_7 : next_state = ROW2_8;
ROW2_8 : next_state = ROW2_9;
ROW2_9 : next_state = ROW2_A;
ROW2_A : next_state = ROW2_B;
ROW2_B : next_state = ROW2_C;
ROW2_C : next_state = ROW2_D;
ROW2_D : next_state = ROW2_E;
ROW2_E : next_state = ROW2_F;
ROW2_F : next_state = ROW1_ADDR; //
default : next_state = IDLE ;
endcase
end
// FSM: always3
always @ (posedge lcd_clk, negedge rst_n) begin
if(!rst_n)
begin
lcd_rs <= 0;
lcd_data <= 8'hxx;
end
else
begin
// 写lcd_rs
case(next_state)
IDLE : lcd_rs <= 0;
// 写指令,初始化
DISP_SET : lcd_rs <= 0;
DISP_OFF : lcd_rs <= 0;
CLR_SCR : lcd_rs <= 0;
CURSOR_SET1 : lcd_rs <= 0;
CURSOR_SET2 : lcd_rs <= 0;
// 写数据,显示第一行
ROW1_ADDR : lcd_rs <= 0;
ROW1_0 : lcd_rs <= 1;
ROW1_1 : lcd_rs <= 1;
ROW1_2 : lcd_rs <= 1;
ROW1_3 : lcd_rs <= 1;
ROW1_4 : lcd_rs <= 1;
ROW1_5 : lcd_rs <= 1;
ROW1_6 : lcd_rs <= 1;
ROW1_7 : lcd_rs <= 1;
ROW1_8 : lcd_rs <= 1;
ROW1_9 : lcd_rs <= 1;
ROW1_A : lcd_rs <= 1;
ROW1_B : lcd_rs <= 1;
ROW1_C : lcd_rs <= 1;
ROW1_D : lcd_rs <= 1;
ROW1_E : lcd_rs <= 1;
ROW1_F : lcd_rs <= 1;
// 写数据,显示第二行
ROW2_ADDR : lcd_rs <= 0;
ROW2_0 : lcd_rs <= 1;
ROW2_1 : lcd_rs <= 1;
ROW2_2 : lcd_rs <= 1;
ROW2_3 : lcd_rs <= 1;
ROW2_4 : lcd_rs <= 1;
ROW2_5 : lcd_rs <= 1;
ROW2_6 : lcd_rs <= 1;
ROW2_7 : lcd_rs <= 1;
ROW2_8 : lcd_rs <= 1;
ROW2_9 : lcd_rs <= 1;
ROW2_A : lcd_rs <= 1;
ROW2_B : lcd_rs <= 1;
ROW2_C : lcd_rs <= 1;
ROW2_D : lcd_rs <= 1;
ROW2_E : lcd_rs <= 1;
ROW2_F : lcd_rs <= 1;
endcase
// 写lcd_data
case(next_state)
IDLE : lcd_data <= 8'hxx; // 写指令,初始化
DISP_SET : lcd_data <= 8'h38; DISP_OFF : lcd_data <= 8'h08; CLR_SCR : lcd_data <= 8'h01; CURSOR_SET1 : lcd_data <= 8'h06; CURSOR_SET2 : lcd_data <= 8'h0C;
// 写数据,显示第一行
ROW1_ADDR : lcd_data <= 8'h80;
ROW1_0 : lcd_data <= row1_val[127:120];
ROW1_1 : lcd_data <= row1_val[119:112];
ROW1_2 : lcd_data <= row1_val[111:104];
ROW1_3 : lcd_data <= row1_val[103: 96];
ROW1_4 : lcd_data <= row1_val[ 95: 88];
ROW1_5 : lcd_data <= row1_val[ 87: 80];
ROW1_6 : lcd_data <= row1_val[ 79: 72];
ROW1_7 : lcd_data <= row1_val[ 71: 64];
ROW1_8 : lcd_data <= row1_val[ 63: 56];
ROW1_9 : lcd_data <= row1_val[ 55: 48];
ROW1_A : lcd_data <= row1_val[ 47: 40];
ROW1_B : lcd_data <= row1_val[ 39: 32];
ROW1_C : lcd_data <= row1_val[ 31: 24];
ROW1_D : lcd_data <= row1_val[ 23: 16];
ROW1_E : lcd_data <= row1_val[ 15: 8];
ROW1_F : lcd_data <= row1_val[ 7: 0];
// 写数据,显示第二行
ROW2_ADDR : lcd_data <= 8'hC0;
ROW2_0 : lcd_data <= row2_val[127:120];
ROW2_1 : lcd_data <= row2_val[119:112];
ROW2_2 : lcd_data <= row2_val[111:104];
ROW2_3 : lcd_data <= row2_val[103: 96];
ROW2_4 : lcd_data <= row2_val[ 95: 88];
ROW2_5 : lcd_data <= row2_val[ 87: 80];
ROW2_6 : lcd_data <= row2_val[ 79: 72];
ROW2_7 : lcd_data <= row2_val[ 71: 64];
ROW2_8 : lcd_data <= row2_val[ 63: 56];
ROW2_9 : lcd_data <= row2_val[ 55: 48];
ROW2_A : lcd_data <= row2_val[ 47: 40];
ROW2_B : lcd_data <= row2_val[ 39: 32];
ROW2_C : lcd_data <= row2_val[ 31: 24];
ROW2_D : lcd_data <= row2_val[ 23: 16];
ROW2_E : lcd_data <= row2_val[ 15: 8];
ROW2_F : lcd_data <= row2_val[ 7: 0];
endcase
end
end
assign lcd_e = lcd_clk; // 数据在时钟高电平被锁存assign lcd_rw = 1'b0; // 只写
// -------------------------------------
// LCD1602驱动模块结束
// -------------------------------------
endmodule。

相关文档
最新文档