字符型LCD模块价格便宜且便于和单片机或者FPGA连接。下面是一个1X16的字符型LCD模块。
为了控制LCD模块,一共需要11个引脚,其中包括8根数据线和3跟控制信号。这3个控制信号分别为:
大多数的字符型LCD模块都是基于HD44780或者兼容芯片的,从这里可以得到更为详细的资料。
下面用我们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 = {1'b0, 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();
}
完整的代码在这里下载。
点击这里查看更多的关于HD44780的指令集。
前面的设计的主要弊端在于我们实际上只使用了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的时间余量。
下面该轮到您们去实践了。
链接
更多的来自http://www.hantronix.com/关于LCD的应用资料
一份很好的LCD接口参考资料