字符型LCD模块价格便宜且便于和单片机或者FPGA连接。下面是一个1X16的字符型LCD模块。
为了控制LCD模块,一共需要11个引脚,其中包括8根数据线和3跟控制信号。这3个控制信号分别为:
E:使能信号或者说是LCD片选信号,高有效
R/W:读/写信号,0:写,1:读
RS:寄存器选择,0:选择命令寄存器,1:选择数据寄存器
大多数的字符型LCD模块都是基于HD44780或者兼容芯片的,从这里可以得到更为详细的资料。
7位设计
下面用我们FPGA板来驱动这个LCD模块,下面是硬件连接框图。
Pluto板从PC机的串行口接收数据,完成串并转换后发送到LCD模块。串并转换模块的设计与串行接口项目中的一样,所以这里只需要将其实例化即可。
module LCDmodule(clk, RxD, LCD_RS, LCD_RW, LCD_E, LCD_DataBus);
input clk, RxD;
output LCD_RS, LCD_RW, LCD_E;
output [7:0] LCD_DataBus;
wire RxD_data_ready;
wire [7:0] RxD_data;
async_receiver deserialer(.clk(clk), .RxD(RxD), .RxD_data_ready(RxD_data_ready), .RxD_data(RxD_data));
每次从串行口接收到1个字节的数据,RxD_data_ready变为有效,且持续一个时钟周期。
PC机通过串行口,以8位的模式发送数据,所以理论上我们需要从PC机接收9位数据才能驱动8位数据和LCD模块的RS线。现在,我们使用接收到的数据的最高位(bit 7)来驱动RS,并且只发送7位数据到数据总线上。
assign LCD_RS = RxD_data[7];
assign LCD_DataBus = {1b0, RxD_data[6:0]}; ? // 只发送7位数据到数据到LCD模块, 最高位补0以凑足8位
assign LCD_RW = 0;
因为我们不需要从LCD模块读取数据,所以R/W引脚直接连接到地。
下一个问题是E的有效信号需要持续220nS,这对与使用25MHz(每周期40nS)时钟信号的FPGA来说是比较长的时间了。所以E至少需要连续驱动5.5个时钟周期,这里我们驱动其7个时钟周期。
reg [2:0] count;
always @(posedge clk) if(RxD_data_ready | (count!=0)) count <= count + 1;
E信号由寄存器产生以避免毛刺。
reg LCD_E;
always @(posedge clk) LCD_E <= (count!=0);
产生的波形如下所示 :
完整的HDL代码在这里下载。
软件
我们需要初始化LCD模块并向其发送一些数据,使之显示出来。
下面是初始化LCD模块并在其上显示hello的C语言代码。
void main()
{
OpenComm();
// 初始化LCD模块
WriteCommByte(0x38); // 设置成8位模式
WriteCommByte(0x0F); // 显示光标
WriteCommByte(0x01); // 清屏,此操作将需要1.64ms,所以延时
Sleep(2);
// 显示 hello
WriteCommByte(h + 0x80);
WriteCommByte(e + 0x80);
WriteCommByte(l + 0x80);
WriteCommByte(l + 0x80);
WriteCommByte(o + 0x80);
CloseComm();
}
8位设计
前面的设计的主要弊端在于我们实际上只使用了7位数据总线,从而导致LCD模块的设置 DD RAM 地址的命令不能执行。
一个比较简单的解决办法是使用“标志字符”。这里我选择0x00,因为他没有被HD44780的命令使用。
新的协议如下所示 :
发送命令字节之前,先发送该“标志字符”。
发送数据字节时,直接发送,不需要在其前增加“标志字符”。
新的C语言代码如下所示:
void main()
{
OpenComm();
// 初始化
WriteCommByte(0x00); WriteCommByte(0x38); // 设置成8位模式
WriteCommByte(0x00); WriteCommByte(0x0F); // 显示光标
WriteCommByte(0x00); WriteCommByte(0x01); // 清屏,此操作将需要1.64ms,所以延时
Sleep(2);
WriteCommByte(h);
WriteCommByte(e);
WriteCommByte(l);
WriteCommByte(l);
WriteCommByte(o);
WriteCommByte(0x00); WriteCommByte(0xC0); // 继续显示下半部分
WriteCommByte(e);
WriteCommByte(v);
WriteCommByte(e);
WriteCommByte(r);
WriteCommByte(y);
WriteCommByte(o);
WriteCommByte(n);
WriteCommByte(e);
CloseComm();
}
新的HDL代码如下所示:
module LCDmodule(clk, RxD, LCD_RS, LCD_RW, LCD_E, LCD_DataBus);
input clk, RxD;
output LCD_RS, LCD_RW, LCD_E;
output [7:0] LCD_DataBus;
wire RxD_data_ready;
wire [7:0] RxD_data;
async_receiver deserialer(.clk(clk), .RxD(RxD), .RxD_data_ready(RxD_data_ready), .RxD_data(RxD_data));
assign LCD_RW = 0;
assign LCD_DataBus = RxD_data;
wire Received_Escape = RxD_data_ready & (RxD_data==0);
wire Received_Data = RxD_data_ready & (RxD_data!=0);
reg [2:0] count;
always @(posedge clk) if(Received_Data | (count!=0)) count <= count + 1;
// LCD_E 有效6个时钟周期, 所以输入25MHz的时钟时, 结果为 6x40ns=240ns
reg LCD_E;
always @(posedge clk)
if(LCD_E==0)
LCD_E <= Received_Data;
else
LCD_E <= (count!=6);
reg LCD_instruction;
always @(posedge clk)
if(LCD_instruction==0)
LCD_instruction <= Received_Escape;
else
LCD_instruction <= (count!=7);
assign LCD_RS = ~LCD_instruction;
endmodule
HD44780的数据手册中表示“E”变低后,“RS”需要继续有效10个纳秒。在这里你需要记得:E只驱动了6个时钟周期,并且 LCD_instruction 标志在第七个时钟后复位,留了25ns的时间余量。
用户1373959 2009-8-8 20:29