1602为常见的液晶显示设备,本实验所用的规格为16x2字符型驱动,直观图如下所示。
上图为本人已完成的LCD液晶驱动显示效果(可以显示任意字符串),由两行组成,可以显示英文字符,部分日语,也可以显示自定义的图像,Spartan-3E所用的1602 (Sitronix ST7066U graphics controller)与FPGA的接口如下所示:
信号说明:
其中SF_D为4位的数据接口位,LCD_E为液晶显示或者不显示控制位,LCD_RS为区分控制或数据信号标志位,当LCD_RS = ‘0’时,表示SF_D为控制信号,LCD_RS=’1’此时SF_D为数据位,LCD_RW为读写控制位,因为不需要从LCD液晶屏中读取数据所以可以简单的再复位操作时,将其清零即可。
可以看出,此1602的数据位为4位,故要写入8位命令时需要进行两次写入操作,写入数据的时序图如下所示,
红线所画为两个字节数据写入所需的最小时差(这个需要特别注意)
根据Spartan-3E的GUIDE,可以知道1602工作的基本流程主要分为上电初始化、写命令、设置读写地址、传递数据三大过程,笔者统计了下这三大步骤中的等待时间最小值和最大值,其中最小值为40us,最大值为15ms,故笔者采取了将50MHZ的时钟分频为10KHZ,即时钟周期为100us的方法来简化设计。
在Spartan-3E的用户手册上详细讲解了如何对此液晶进行操作,笔者做了简单的摘要:
一) 上电初始化(目的是建立宽度为4 bit的数据接口)
1) 等待15ms或者更长时间
2) 写命令字0x3,保持LCD_E高电平12个周期(时钟频率为50MHZ时)
3) 等待4.1ms或者更长时间
4) 写命令字0x3,保持LCD_E高电平12个周期
5) 等待100us或者更长时间
6) 写命令字0x3,保持LCD_E高电平12个周期
7) 等待40us或者更长时间
8) 写命令字0x2,保持LCD_E高电平12个周期
9) 等待40us或者更长时间
二) 写命令
1) 发送功能设置命令(Function Set),0x28
2) 发送端口模式设置命令(Entry Mode Set),0x06,自动增加地址指针
3) 发送Display ON/OFF命令,0x0C,打开显示开关并禁止光标闪烁
4) 发送Clear Display命令,等待1.64ms或者更长时间
三) 设置读写地址和传递数据
指定起始地址,并给出一个或者多个数据,这一步是显示的关键,必须仔细设置控制位,笔者就是因为这个错误,白白花费了大半天的时间,具体方法是在写任何数据之前,发送Set DD RAM Address 命令,(写入7位的DD RAM地址,注意最高位为1)然后再写入数据。
简单介绍我所设计的LCD液晶IP核的思路,首先生成100us的时钟,然后根据初始化顺序,编写状态机。最终设计效果是在1602液晶屏上可以静态显示16*2的任意字符串—而这只需修改两个参数即可轻松实现。下面是部分代码展示。下图为代码说明。
图1所示为写16个字符的代码实现,图2所示为将普通的字符串转换为1602可显示的编码值的模块,图3所示为生成clk_100us的代码,
最终效果图是在屏幕上显示两行字符串:
参考代码:
module lcd1602(clk,rst,LCD_E,LCD_RW,LCD_RS,LCD_D);
input clk,rst;
output LCD_E,LCD_RW,LCD_RS;
output [7:0] LCD_D;
reg LCD_E,LCD_RW,LCD_RS;
reg [7:0] LCD_D;
reg [9:0] state;
reg [5:0] address;
parameter IDLE =10'b0000000000;
parameter CLEAR =10'b0000000001; //清屏
parameter RETURNCURSOR =10'b0000000010; //归home位
parameter SETMODE =10'b0000000111; //输入方式设置,读写数据后ram地址增/减1;画面动/不动
parameter SWITCHMODE =10'b0000001111; //显示状态设置,显示开/关;光标开/关;闪烁开/关
parameter SHIFT =10'b0000011100; //光标画面滚动 画面/光标平移一位;左/右平移一位
parameter SETFUNCTION =10'b0000111100;
//工作方式设置 1:8/1:4位数据接口;两行/一行显示;5x10/5x7点阵
parameter SETCGRAM =10'b0001000000; //设置CGRAM
parameter SETDDRAM1 =10'b0010000001; //设置DDRAM
parameter SETDDRAM2 =10'b0010000010; //设置DDRAM
parameter READFLAG =10'b0100000000; //读状态
parameter WRITERAM1 =10'b1000000001; //写RAM
parameter WRITERAM2 =10'b1000000010; //写RAM
parameter READRAM =10'b1100000000; //读RAM
void function(e,t){for(var n=t.getElementsByTagName("img"),a=+new Date,i=[],o=function(){this.removeEventListener&&this.removeEventListener("load",o,!1),i.push({img:this,time:+new Date})},s=0;s< n.length;s++)!function(){var e=n[s];e.addEventListener?!e.complete&&e.addEventListener("load",o,!1):e.attachEvent&&e.attachEvent("onreadystatechange",function(){"complete"==e.readyState&&o.call(e,o)})}();alog("speed.set",{fsItems:i,fs:a})}(window,document);
parameter cur_inc =1; parameter cur_dec =0; parameter cur_shift =1; parameter cur_noshift =0; parameter open_display =1; parameter open_cur =0; parameter blank_cur =0; parameter shift_display =1; parameter shift_cur =0; parameter right_shift =1; parameter left_shift =0; parameter LCD_Dwidth8 =1; parameter LCD_Dwidth4 =0; parameter twoline =1; parameter oneline =0; parameter font5x10 =1; parameter font5x7 =0;
/******************************************************************/ function [7:0] ddram; //写入需要的字符数据 input [5:0] n; begin case(n) 0:ddram=8'h48;//H 1:ddram=8'h65;//e 2:ddram=8'h6c;//l 3:ddram=8'h6c;//l 4:ddram=8'h6f;//o 5:ddram=8'h21;//! 6:ddram=8'h21;//! 7:ddram=8'hA0;//space 8:ddram=8'h7E;//-> 9:ddram=8'hA0;//space 10:ddram=8'h5A;//Z 11:ddram=8'h52;//R 12:ddram=8'h74;//t 13:ddram=8'h65;//e 14:ddram=8'h63;//c 15:ddram=8'h68;//h 16:ddram=8'h77;//w 17:ddram=8'h77;//w 18:ddram=8'h77;//w
19:ddram=8'h2E;//.
var cpro_psid ="u2572954"; var cpro_pswidth =966; var cpro_psheight =120;
20:ddram=8'h5A;//Z 21:ddram=8'h52;//R 22:ddram=8'hB0;//R 23:ddram=8'h74;//t 24:ddram=8'h65;//e 25:ddram=8'h63;//c 26:ddram=8'h68;//h 27:ddram=8'h2E;//. 28:ddram=8'h6E;//n 29:ddram=8'h65;//e 30:ddram=8'h74;//t 31:ddram=8'hA0;//space default: ddram=8'hxx; endcase end endfunction
/******************************************************************/ //分频模块 reg [16:0] clkcnt; reg clkdiv; always @ (posedge clk) if(!rst) clkcnt<=17'b0_0000_0000_0000_0000; else begin if(clkcnt<17'b0_1001_1100_0100_0000) //16'b1001_1100_0100_0000 begin clkcnt<=clkcnt+1; clkdiv<=0; end else if(clkcnt==17'b1_0011_1000_0111_1111) clkcnt<=17'b0_0000_0000_0000_0000; else begin clkcnt<=clkcnt+1; clkdiv<=1; end end