前言
昨天刚结束FPGA的课程设计,做的题目是用Verilog HDL编写LCD1602字符显示程序,并在开发板DE2-115上进行演示,实现的功能是显示移动字符和滚动字符,并通过一个开关来控制模式的切换。此次课程设计参考了网站上许多前辈大佬的文章,在他们的基础上进行修改。但发现许多的文章仅仅介绍了如何显示静态字符,而没有介绍滚动字符显示如何编写,遂由此写下这篇博客,希望对有需要的人有所帮助。
一、设计任务
基于Verilog HDL 液晶显示控制器的设计
(1)掌握LCD1602字符型液晶显示器工作和时序原理;
(2)在LCD1602液晶显示器上面实现静态字符;
(3)扩展功能:在LCD1602液晶显示器实现动态字符。
二、综合设计部分
1.设计原理及方案
(1)LCD1602介绍
LCD1602(Liquid Crystal Display)液晶显示屏是一种字符型液晶显示模块,可以显示ASCII码的标准字符和其它的一些内置特殊字符,还可以有8个自定义字符,显示容量:16×2个字符,每个字符为5*7点阵。
①引脚
该设计只需要向LCD写入指令或者字符数据,故RW引脚可以直接置低电平。注:在DE2-115中使用的LCD模块并不含背光单元,故而LCD_BLON信号在用户工程中的设定是无效的,因此模块代码中不添加LCD_BLON输出(此提示可以在DE2-115数据手册LCD1602模块中看到)。
②操作时序
该设计只需要写操作,故此处只介绍LCD1602的写操作时序。RW=0,RS=0/1(控制写入的是字符数据还是指令)
首先将要写入的数据DB0-DB7准备好,再拉高E并维持一段时间(大于150ns),最后将E拉低,DB0-DB7的数据写入LCD。通过控制RS等于0或者1来实现指令/字符数据的写入。
注:这个地方有个问题,E脉冲宽度给150ns根本就不行,最后参考单片机中的教程,整个周期给了2ms,E脉冲宽度给了1ms,最终实现了。(此条提示参考:基于FPGA的LCD1602驱动设计(含有代码))
③指令集
LCD1602有非常多的指令,此处只介绍本设计所用到的几条指令。
0X38:开显示
0X08:显示关,不显示光标,光标不闪烁
0X0C:显示开,不显示光标,光标不闪烁
0X01:清除屏幕显示内容,光标返回屏幕左上角
0X06:写入数据光标右移,不显示移动
0X18:整屏字符左移
0X80:第一行首个字符的位置
0XC4:第二行中间字符的位置
注:字符位置的设置可参考基于FPGA的LCD1602驱动(含代码)
完整指令集如下
(2)LCD1602驱动流程
①LCD初始化
根据数据手册,LCD的初始化需要完成下面7步:
1 写指令38H
2 延时15ms
3 写指令38H(检测忙)
4 写指令08H(检测忙)
5 写指令01H(检测忙)
6 写指令06H(检测忙)
7 写指令0CH(检测忙)
因LCD1602相较于单片机来说是慢速设备,所以在发送下一条指令时需要检测LCD是否处理完上一条指令,也就是检测BF位(可以发送完整指令集中的9.读BF及AC值),而根据上图lcd指令执行时间可以得知每条指令的执行时间为us级,因此我们此处可以将每条指令维持时间给到2ms(详见下面代码部分),就不需要检测BF位了。
注:此设计通过延时来避免读BF位的操作是根据这篇博客基于FPGA的LCD1602显示屏驱动中4、LCD1602指令(9)的解释。
②LCD写数据
此设计初始化需要发送6条指令,每条指令可以定义为1个状态用于赋值,或者整个初始化过程可以定义为1个状态再用循环来写入指令,完成后写入第一行的地址0X80(此时RS仍为0)。之后开始写入数据RS拉高等于1,不断写入再判断是否写到第一行末尾字符(这个可以自行设定),如果到了我们再写入第二行的地址0XC4(为了显示效果,我们从第二行中间位置开始显示),写完之后我们进入stop状态,也即显示静态字符。动态字符的显示与静态字符的显示有些许差别,需要再添加一个状态我们命名为dongtai(写入0X18指令),每进入一次该状态整屏字符就会往左平移一次,之后跳回stop状态,如果此时接着跳回dongtai状态则会因为两次平移间隔的时间太短,人眼难以观察,显示的效果不好。因此在滚动字符模式下,当我们进入stop状态,开始计时400ms(大于400ms也行,可以自己调节),计时结束后再跳入dongtai状态,这样两次平移之间的间隔就为400ms,人眼可以观察清楚。
(1)
(2)
(3)
2.仿真结果及分析
移动字符模式(mode=1),由仿真图可以知道,lcd_rs在①阶段完成了LCD的初始化配置,并在最后写入第一行首个字符的地址,在②阶段写入所要显示的字符。当第一行显示完成时,给它写入第二行首个字符的地址,待所有字符写入完成,在③阶段写入指令进入stop(0x38)状态并维持一段时间再跳入dongtai(0x18)状态,使字符整屏移动,两个状态相互跳转实现字符移动效果。
静态字符模式(mode=0),静态字符模式与移动字符模式①②阶段相同,只是在③阶段一直维持stop状态,即一直写入0x38指令。
3.硬件调试
此次设计使用DE2-115开发板进行验证
(1)器件选择
具体器件名可看开发板芯片
(2)引脚绑定
板子上50MHZ时钟可通过PIN_Y2输入
根据DE2-115使用手册进行LCD引脚配置,模式控制信号(此处我们与下图最后一排开关从左往右数第二个开关进行绑定)和刷新信号(此处我们与下图最后一排开关从左往右数第一个开关进行绑定)可自定义引脚。
(3)连接电脑
白线为USB线,黑线为电源线。
(4)正确导入管脚分配表,无错误后,重新综合整个工程
(5)综合完毕,无错误后,连接好开发板,开启电源,准备下载。
(6)配置完成后下载
注意,下载时要将板子上的开关SW19拨到RUN。
(7)固化文件下载
上述烧录的程序为非固化文件,每次重新上电后需要重新烧录,因此我们可以将文件转换为固化文件,再将其烧录,避免程序掉电丢失。
一般会显示文件超过了最大容量,因此在这里将文件压缩
成功生成固化文件(.jic)
下载固化文件,首先将原先的文件删除掉,再点击添加文件、Start,烧录完成之后重新上电,即可观察到相应现象。
如果想要擦除文件,将原先的√取消掉,再勾选Erase,最后点击Start即可。
烧录程序和固化文件下载可以参考这两篇博客
Quartus Prime硬件实验开发(DE2-115板)实验一CPU指令运算器设计
【工具教程】FPGA程序掉电保存,jic模式烧写EPCS,代替AS方式
4.完整代码
module lcd1602(
input clk,//50MHZ
input rst_n,
input wire mode,//模式设置,静态字符(0),移动字符(1)
output wire lcd_on,//开lcd
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;//显示字符
localparam
IDLE = 4'd0,//设置显示模式并延时15ms
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,
dongtai = 4'd10;//移位
assign lcd_rw = 1'b0;//写操作
assign lcd_on = 1'b1;//lcd上电
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 18'd0;
end
else begin
if (cnt==18'd100_000 - 1) begin//2ms清零
// if (cnt==17'd4 - 1) begin//仿真
cnt <= 18'd0;
end
else begin
cnt <= cnt + 1'b1;
end
end
end
always @(posedge clk or negedge rst_n) begin//lcd_en周期为2ms,高电平1ms
if (!rst_n) begin
lcd_en <= 0;
end
else if (cnt==18'd50_000 - 1) begin
// else if (cnt==17'd2 - 1) begin//仿真
lcd_en <= 1;
end
else if (cnt==18'd100_000 - 1) begin
// else if (cnt==17'd4 - 1) begin//仿真
lcd_en <= 0;
end
end
always @(posedge clk or negedge rst_n) begin//cnt=100_000时清零,所以每写一个字符间隔2ms
if (!rst_n) begin
char_cnt <= 0;
end
else if (state_c==WRITE && cnt==18'd50_000 - 1) begin
// else if (state_c==WRITE && cnt==17'd2 - 1) begin//仿真
if (char_cnt==5'd22) begin
char_cnt <= 5'd0;
end
else begin
char_cnt <= char_cnt + 1'b1;
end
end
end
always @(*) begin
case(char_cnt)
5'd0: data_display = "H";
5'd1: data_display = "A";
5'd2: data_display = "P";
5'd3: data_display = "P";
5'd4: data_display = "Y";
5'd5: data_display = "-";
5'd6: data_display = "N";
5'd7: data_display = "E";
5'd8: data_display = "W";
5'd9: data_display = "-";
5'd10: data_display = "Y";
5'd11: data_display = "E";
5'd12: data_display = "A";
5'd13: data_display = "R";
5'd14: data_display = "!";
5'd15: data_display = "-";
5'd16: data_display = "L";
5'd17: data_display = "C";
5'd18: data_display = "D";
5'd19: data_display = "1";
5'd20: data_display = "6";
5'd21: data_display = "0";
5'd22: data_display = "2";
default:data_display = "H";
endcase
end
always @(posedge clk or negedge rst_n) begin//状态持续时间2ms,保证每个状态下发送的指令被lcd接收
if (!rst_n) begin
state_c <= IDLE;
end
else if(cnt==18'd50_000 - 1) begin//因cnt到100_000被清零,所以次态赋给现态时刻为50_000-150_000-250_000,也就是每2ms更新一次状态
// else if(cnt==17'd2 - 1) begin//仿真
state_c <= state_n;
end
end
reg [19:0] cnt_15ms;//上电延时等待
reg flag;
always@(posedge clk or negedge rst_n)begin
if (!rst_n) begin
cnt_15ms <= 0;
end
else if (state_c == IDLE) begin
cnt_15ms <= cnt_15ms + 1'b1;
end
end
always@(posedge clk or negedge rst_n)begin//判断延时是否完成
if (!rst_n) begin
flag <= 0;
end
else if (state_c==IDLE && cnt_15ms==20'd750_000) begin//15ms
// else if (state_c==IDLE && cnt_15ms==20'd3-1) begin//仿真
flag <= 1;
end
end
reg [24:0] cnt_400ms;//每发送一条移动指令间隔时间
reg flag_1;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_400ms <= 25'd0;
end
else begin
if(state_c==stop) begin
if(cnt_400ms==25'd20_000_000 - 1)begin
// if(cnt_400ms==25'd6 - 1)begin//仿真
cnt_400ms <= 25'd0;
end
else begin
cnt_400ms <= cnt_400ms + 1'b1;
end
end
end
end
always@(posedge clk or negedge rst_n)begin//判断延时是否完成
if (!rst_n) begin
flag_1 <= 0;
end
else if (state_c==stop && cnt_400ms==25'd20_000_000 - 1) begin//开发板主频50MHZ,也即每20ns记一次,计20_000_000次总共就400ms。
// else if (state_c==stop && cnt_400ms==25'd6 - 1) begin//仿真
flag_1 <= 1;
end
else if (state_c==dongtai)begin
flag_1 <= 0;
end
end
always @(*) begin//状态转换及模式选择
if(mode==0)begin
case(state_c)
IDLE :
begin
if (flag) begin
state_n = INIT;
end
else begin
state_n = state_c;
end
end
INIT :
begin
state_n = S0;
end
S0 :
begin
state_n = S1;
end
S1 :
begin
state_n = S2;
end
S2 :
begin
state_n = S3;
end
S3 :
begin
state_n = ROW1_ADDR;
end
ROW1_ADDR:
begin
state_n = WRITE;
end
WRITE :
begin
if (char_cnt==5'd15) begin//第一行最后一个字符
state_n = ROW2_ADDR;
end
else if (char_cnt==5'd22) begin//第二行最后一个字符
state_n = stop;
end
else begin
state_n = state_c;
end
end
ROW2_ADDR:
begin
state_n = WRITE;
end
stop :
begin
state_n = stop;
end
endcase
end
else if(mode==1) begin
case(state_c)
IDLE :
begin
if (flag) begin
state_n = INIT;
end
else begin
state_n = state_c;
end
end
INIT :
begin
state_n = S0;
end
S0 :
begin
state_n = S1;
end
S1 :
begin
state_n = S2;
end
S2 :
begin
state_n = S3;
end
S3 :
begin
state_n = ROW1_ADDR;
end
ROW1_ADDR:
begin
state_n = WRITE;
end
WRITE :
begin
if (char_cnt==5'd15) begin
state_n = ROW2_ADDR;
end
else if (char_cnt==5'd22) begin
state_n = stop;
end
else begin
state_n = state_c;
end
end
ROW2_ADDR:
begin
state_n = WRITE;
end
stop :
begin
if (flag_1) begin
state_n = dongtai;
end
else begin
state_n = state_c;
end
end
dongtai :begin
state_n = stop;
end
endcase
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
lcd_data <= 8'd0;
end
else begin
case(state_c)
IDLE :begin lcd_data <= 8'h38; lcd_rs <= 0;end
INIT :begin lcd_data <= 8'h38; lcd_rs <= 0;end
S0 :begin lcd_data <= 8'h08; lcd_rs <= 0;end
S1 :begin lcd_data <= 8'h01; lcd_rs <= 0;end
S2 :begin lcd_data <= 8'h06; lcd_rs <= 0;end
S3 :begin lcd_data <= 8'h0c; lcd_rs <= 0;end
ROW1_ADDR :begin lcd_data <= 8'h80; lcd_rs <= 0;end
WRITE :begin lcd_data <= data_display; lcd_rs <= 1;end
ROW2_ADDR :begin lcd_data <= 8'hc4; lcd_rs <= 0;end//第二行中间显示
stop :begin lcd_data <= 8'h38; lcd_rs <= 0;end
dongtai :begin lcd_data <= 8'h18; lcd_rs <= 0;end//移位
default:;
endcase
end
end
endmodule
注:新建VWF文件进行仿真时,将注释了仿真的语句取消注释,将该语句的上一行注释掉。
三、功能演示
功能演示文章来源:https://www.toymoban.com/news/detail-789325.html
总结
第一次写博客难免有地方没有考虑仔细,欢迎在评论区进行讨论,如有错误也欢迎指正。文章来源地址https://www.toymoban.com/news/detail-789325.html
到了这里,关于基于Verilog HDL LCD1602显示器的设计的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!