赞
踩
昨天刚结束FPGA的课程设计,做的题目是用Verilog HDL编写LCD1602字符显示程序,并在开发板DE2-115上进行演示,实现的功能是显示移动字符和滚动字符,并通过一个开关来控制模式的切换。此次课程设计参考了网站上许多前辈大佬的文章,在他们的基础上进行修改。但发现许多的文章仅仅介绍了如何显示静态字符,而没有介绍滚动字符显示如何编写,遂由此写下这篇博客,希望对有需要的人有所帮助。
基于Verilog HDL 液晶显示控制器的设计
(1)掌握LCD1602字符型液晶显示器工作和时序原理;
(2)在LCD1602液晶显示器上面实现静态字符;
(3)扩展功能:在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驱动(含代码)
完整指令集如下
根据数据手册,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)的解释。
此设计初始化需要发送6条指令,每条指令可以定义为1个状态用于赋值,或者整个初始化过程可以定义为1个状态再用循环来写入指令,完成后写入第一行的地址0X80(此时RS仍为0)。之后开始写入数据RS拉高等于1,不断写入再判断是否写到第一行末尾字符(这个可以自行设定),如果到了我们再写入第二行的地址0XC4(为了显示效果,我们从第二行中间位置开始显示),写完之后我们进入stop状态,也即显示静态字符。动态字符的显示与静态字符的显示有些许差别,需要再添加一个状态我们命名为dongtai(写入0X18指令),每进入一次该状态整屏字符就会往左平移一次,之后跳回stop状态,如果此时接着跳回dongtai状态则会因为两次平移间隔的时间太短,人眼难以观察,显示的效果不好。因此在滚动字符模式下,当我们进入stop状态,开始计时400ms(大于400ms也行,可以自己调节),计时结束后再跳入dongtai状态,这样两次平移之间的间隔就为400ms,人眼可以观察清楚。
(1)
(2)
(3)
移动字符模式(mode=1),由仿真图可以知道,lcd_rs在①阶段完成了LCD的初始化配置,并在最后写入第一行首个字符的地址,在②阶段写入所要显示的字符。当第一行显示完成时,给它写入第二行首个字符的地址,待所有字符写入完成,在③阶段写入指令进入stop(0x38)状态并维持一段时间再跳入dongtai(0x18)状态,使字符整屏移动,两个状态相互跳转实现字符移动效果。
静态字符模式(mode=0),静态字符模式与移动字符模式①②阶段相同,只是在③阶段一直维持stop状态,即一直写入0x38指令。
此次设计使用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方式
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文件进行仿真时,将注释了仿真的语句取消注释,将该语句的上一行注释掉。
功能演示
第一次写博客难免有地方没有考虑仔细,欢迎在评论区进行讨论,如有错误也欢迎指正。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。