差别
这里会显示出您选择的修订版和当前版本之间的差别。
后一修订版 | 前一修订版 | ||
文本lcd模块的控制 [2020/07/09 10:38] zili 创建 |
文本lcd模块的控制 [2020/07/09 13:13] (当前版本) zili |
||
---|---|---|---|
行 3: | 行 3: | ||
\\ | \\ | ||
- | 文本LCD模块便宜且易于使用微控制器或FPGA进行接口。 | + | 文本LCD模块便宜且易于使用微控制器或FPGA进行接口。\\ |
这是一个1行x 16个字符的模块: | 这是一个1行x 16个字符的模块: | ||
- | + | {{ ::lcd_fpgas_are_fun.jpg |}} | |
要控制LCD模块,您需要11个IO引脚来驱动8位数据总线和3个控制信号。3个控制信号是: | 要控制LCD模块,您需要11个IO引脚来驱动8位数据总线和3个控制信号。3个控制信号是: | ||
+ | * E:启用或“ LCD选择”。高活跃。 | ||
+ | * 读/写:读/写。0写入,1读取。 | ||
+ | * RS:寄存器选择,0表示命令字节,1表示数据字节。 | ||
+ | \\ | ||
+ | 大多数LCD模块都基于HD44780芯片或是兼容的。查阅[[https://en.wikipedia.org/wiki/Hitachi_HD44780_LCD_controller|Wikipedia]]以获取更多信息。 | ||
+ | \\ | ||
- | E:启用或“ LCD选择”。高活跃。 | + | \\ |
- | 读/写:读/写。0写入,1读取。 | + | ####7位设计 |
- | RS:寄存器选择,0表示命令字节,1表示数据字节。 | + | 让我们用FPGA板驱动LCD模块。\\ |
- | 大多数LCD模块都基于HD44780芯片或兼容的。查看Wikipedia以获取更多信息。 | + | |
- | + | ||
- | 7位设计 | + | |
- | 让我们从FPGA板驱动LCD模块。 | + | |
这是我们设计的框图: | 这是我们设计的框图: | ||
+ | {{ ::lcdmodule.gif |}} | ||
+ | [[http://www.knjn.com/FPGA-RS232.html|Pluto]]从PC串行端口接收数据,对其进行反序列化,然后将其发送到LCD模块。解串器与串行接口项目中的模块相同,因此此处仅对其进行实例化。\\ | ||
- | Pluto从PC串行端口接收数据,对其进行反序列化,然后将其发送到LCD模块。解串器与串行接口项目中的模块相同,因此此处仅对其进行实例化。 | + | <code verilog> |
+ | 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; | ||
- | LCD 模块模块(clk,RxD,LCD_RS,LCD_RW,LCD_E,LCD_DataBus); | + | wire RxD_data_ready; |
- | 输入 clk,RxD; | + | wire [7:0] RxD_data; |
- | 输出 LCD_RS,LCD_RW,LCD_E; | + | async_receiver deserializer(.clk(clk), .RxD(RxD), .RxD_data_ready(RxD_data_ready), .RxD_data(RxD_data)); |
- | 输出 [7:0] LCD_DataBus; | + | </code> |
- | 连线 RxD_data_ready; | + | 每当串行端口提供一个字节时,“ RxD_data_ready”将在一个时钟周期内处于活动状态。\\ |
- | 线 [7:0] RxD_data; | + | \\ |
- | async_receiver解串器(.clk(clk)、. RxD(RxD)、. RxD_data_ready(RxD_data_ready)、. RxD_data(RxD_data)); | + | PC通过串口以8位模式向我们发送数据。理想情况下,我们需要从PC接收9位,以便我们可以驱动8位数据总线和LCD模块的“ RS”线。现在,让我们使用接收到的数据的MSB(第7位)来驱动“ RS”,并将仅7位发送到数据总线。\\ |
- | 每当串行端口提供一个字节时,“ RxD_data_ready”将在一个时钟周期内处于活动状态。 | + | |
- | PC通过串口以8位模式向我们发送数据。理想情况下,我们需要从PC接收9位,以便我们可以驱动8位数据总线和LCD模块的“ RS”线。现在,让我们使用接收到的数据的MSB(第7位)来驱动“ RS”,并将仅7位发送到数据总线。 | + | <code verilog> |
+ | assign LCD_RS = RxD_data[7]; | ||
+ | assign LCD_DataBus = {1'b0, RxD_data[6:0]}; // sends only 7 bits to the module, padded with a '0' in front to make 8 bits | ||
- | 分配 LCD_RS = RxD_data [7]; | + | assign LCD_RW = 0; |
- | 分配 LCD_DataBus = {1'b0,RxD_data [6:0]}; //仅发送7位到模块,在前面加一个'0'使8位 | + | </code> |
- | 分配 LCD_RW = 0; | + | 我们从不读取LCD模块,所以R / W线是接地的。\\ |
- | 我们从不读取LCD模块,因此R / W线接地。 | + | \\ |
+ | 最后一个麻烦是“ E”信号需要长时间激活,即220ns。从FPGA的角度来看,这很长,因为我使用的是25MHz时钟(周期为40ns)。因此,“ E”至少需要驱动5.5个时钟。在这里,我们使用一个计数器对时钟进行计数,将其驱动7个时钟。\\ | ||
- | 最后一个麻烦是“ E”信号需要长时间激活,即220ns。从FPGA的角度来看,这很长,因为我使用的是25MHz时钟(周期为40ns)。因此,“ E”至少需要驱动5.5个时钟。在这里,我们使用一个计数器对时钟进行计数,将其驱动7个时钟。 | + | <code verilog> |
+ | reg [2:0] count; | ||
+ | always @(posedge clk) if(RxD_data_ready | (count!=0)) count <= count + 1; | ||
+ | </code> | ||
- | reg [2:0]计数;如果(RxD_data_ready |(count!= 0))count <= count + 1; | + | “ E”信号是通过寄存器创建的,因此可以保证无干扰。\\ |
- | 总是 @(posege clk) | + | |
- | “ E”信号是通过寄存器创建的,因此可以保证无干扰。 | + | |
+ | <code verilog> | ||
reg LCD_E; | reg LCD_E; | ||
- | 总是 @(posedge CLK)LCD_E <=(计数!= 0); | + | always @(posedge clk) LCD_E <= (count!=0); |
+ | </code> | ||
波形如下所示: | 波形如下所示: | ||
+ | {{ ::lcdmodule_waveform.gif |}} | ||
+ | HDL设计在[[https://www.fpga4fun.com/files/LCDmodule.zip|这里]]。\\ | ||
+ | \\ | ||
+ | ####软件方面 | ||
+ | 我们对LCD进行初始化并发送一些要显示的数据。 | ||
+ | {{ ::lcd_hello.jpg |}} | ||
+ | \\ | ||
+ | 以下是初始化LCD模块并显示“ hello”的C代码。 | ||
- | HDL设计在这里。 | + | <code verilog> |
- | + | void main() | |
- | 该软件 | + | |
- | 我们初始化LCD并发送一些要显示的数据。 | + | |
- | + | ||
- | + | ||
- | + | ||
- | 这是初始化LCD模块并显示“ hello”的C代码。 | + | |
- | + | ||
- | void main() | + | |
{ | { | ||
- | OpenComm(); | + | OpenComm(); |
- | //初始化LCD模块 | + | // initialize the LCD module |
- | WriteCommByte(0x38); // 8位模式下的“功能集” | + | WriteCommByte(0x38); // "Function Set" in 8 bits mode |
- | WriteCommByte(0x0F); //光标打开时“ Display ON” | + | WriteCommByte(0x0F); // "Display ON" with cursors ON |
- | WriteCommByte(0x01); //“ Clear Display”,最多可能需要1.64ms,因此延迟 | + | WriteCommByte(0x01); // "Clear Display", can take up to 1.64ms, so the delay |
- | Sleep(2); | + | Sleep(2); |
- | //显示“你好” | + | // display "hello" |
- | WriteCommByte('h'+ 0x80); | + | WriteCommByte('h' + 0x80); |
- | WriteCommByte('e'+ 0x80); | + | WriteCommByte('e' + 0x80); |
- | WriteCommByte('l'+ 0x80); | + | WriteCommByte('l' + 0x80); |
- | WriteCommByte('l'+ 0x80); | + | WriteCommByte('l' + 0x80); |
- | WriteCommByte('o'+ 0x80); | + | WriteCommByte('o' + 0x80); |
- | CloseComm(); | + | CloseComm(); |
} | } | ||
- | 完整的代码在这里。 | + | </code> |
+ | \\ | ||
+ | 完整的代码在[[https://www.fpga4fun.com/files/LCDmodule_com.zip|这里]]。\\ | ||
- | 要获取有关HD44780指令集的更多信息,请在此处检查。 | + | 要获取有关HD44780指令集的更多信息,[[http://www.doc.ic.ac.uk/~ih/doc/lcd/instruct.html|请在此处检查]]。\\ |
+ | \\ | ||
- | 8位设计 | + | ####8位设计 |
- | 主要缺点是较早的设计是我们仅向LCD数据总线发送7位。这是一个问题,因为无法再使用LCD模块的设置DD RAM地址命令。 | + | 主要缺点是较早的设计是我们仅向LCD数据总线发送7位。这是一个问题,因为无法再使用LCD模块的设置DD RAM地址命令。\\ |
- | 一种简单的解决方法是使用转义符。我们选择了字符0x00。 | + | 一种简单的解决方法是使用转义符。我们选择了字符0x00。\\ |
新协议如下: | 新协议如下: | ||
- | + | * 要发送命令字节,请在其前面加上0x00。 | |
- | 要发送命令字节,请在其前面加上0x00。 | + | * 要发送数据字节,只需发送它,不需要前缀。 |
- | 要发送数据字节,只需发送它,不需要前缀。 | + | \\ |
新的C代码是: | 新的C代码是: | ||
- | void main() | + | <code verilog> |
+ | void main() | ||
{ | { | ||
- | OpenComm(); | + | OpenComm(); |
- | //初始化LCD模块 | + | // initialize the LCD module |
- | WriteCommByte(0x00); WriteCommByte(0x38); // 8位模式下的“功能集” | + | WriteCommByte(0x00); WriteCommByte(0x38); // "Function Set" in 8 bits mode |
- | WriteCommByte(0x00); WriteCommByte(0x0F); //光标打开时“显示打开” | + | WriteCommByte(0x00); WriteCommByte(0x0F); // "Display ON" with cursors ON |
- | WriteCommByte(0x00); WriteCommByte(0x01); //“ Clear Display”,最多可能需要1.64ms,因此延迟 | + | WriteCommByte(0x00); WriteCommByte(0x01); // "Clear Display", can take up to 1.64ms, so the delay |
- | Sleep(2); | + | Sleep(2); |
- | WriteCommByte('h'); | + | WriteCommByte('h'); |
- | WriteCommByte('e'); | + | WriteCommByte('e'); |
- | WriteCommByte('l'); | + | WriteCommByte('l'); |
- | WriteCommByte('l'); | + | WriteCommByte('l'); |
- | WriteCommByte('o'); | + | WriteCommByte('o'); |
- | WriteCommByte(0x00); WriteCommByte(0xC0); //进入LCD | + | WriteCommByte(0x00); WriteCommByte(0xC0); // go on second half of LCD |
- | WriteCommByte('e')的后半部分; | + | WriteCommByte('e'); |
- | WriteCommByte('v'); | + | WriteCommByte('v'); |
- | WriteCommByte('e'); | + | WriteCommByte('e'); |
- | WriteCommByte('r'); | + | WriteCommByte('r'); |
- | WriteCommByte('y'); | + | WriteCommByte('y'); |
- | WriteCommByte('o'); | + | WriteCommByte('o'); |
- | WriteCommByte('n'); | + | WriteCommByte('n'); |
- | WriteCommByte('e'); | + | WriteCommByte('e'); |
- | CloseComm(); | + | CloseComm(); |
} | } | ||
+ | </code> | ||
+ | \\ | ||
新的HDL代码如下所示: | 新的HDL代码如下所示: | ||
- | LCD 模块模块(clk,RxD,LCD_RS,LCD_RW,LCD_E,LCD_DataBus); | + | <code verilog> |
- | 输入 clk,RxD; | + | module LCDmodule(clk, RxD, LCD_RS, LCD_RW, LCD_E, LCD_DataBus); |
- | 输出 LCD_RS,LCD_RW,LCD_E; | + | input clk, RxD; |
- | 输出 [7:0] LCD_DataBus; | + | output LCD_RS, LCD_RW, LCD_E; |
+ | output [7:0] LCD_DataBus; | ||
- | 连线 RxD_data_ready; | + | wire RxD_data_ready; |
- | 线 [7:0] RxD_data; | + | wire [7:0] RxD_data; |
- | async_receiver deserialer(.clk(clk)、. RxD(RxD)、. RxD_data_ready(RxD_data_ready)、. RxD_data(RxD_data)); | + | async_receiver deserialer(.clk(clk), .RxD(RxD), .RxD_data_ready(RxD_data_ready), .RxD_data(RxD_data)); |
- | 分配 LCD_RW = 0; | + | assign LCD_RW = 0; |
- | 分配 LCD_DataBus = RxD_data; | + | assign LCD_DataBus = RxD_data; |
- | 电线 Received_Escape = RxD_data_ready&(RxD_data == 0); | + | wire Received_Escape = RxD_data_ready & (RxD_data==0); |
- | 电线 Received_Data = RxD_data_ready&(RxD_data!= 0); | + | wire Received_Data = RxD_data_ready & (RxD_data!=0); |
- | reg [2:0]计数; | + | reg [2:0] count; |
- | 总是 @(posege clk)如果(Received_Data |(count!= 0))count <= count + 1; | + | always @(posedge clk) if(Received_Data | (count!=0)) count <= count + 1; |
- | //激活LCD_E为6个时钟,所以在25MHz的,这是6x40ns = 240ns | + | // activate LCD_E for 6 clocks, so at 25MHz, that's 6x40ns=240ns |
- | REG LCD_E; | + | reg LCD_E; |
- | 总是 @(posedge CLK) | + | always @(posedge clk) |
- | 如果(LCD_E == 0) | + | if(LCD_E==0) |
LCD_E <= Received_Data; | LCD_E <= Received_Data; | ||
- | 否则 | + | else |
- | LCD_E <=(count!= 6); | + | LCD_E <= (count!=6); |
reg LCD_instruction; | reg LCD_instruction; | ||
- | 总是 @(posedge CLK) | + | always @(posedge clk) |
- | 如果(LCD_instruction == 0) | + | if(LCD_instruction==0) |
LCD_instruction <= Received_Escape; | LCD_instruction <= Received_Escape; | ||
- | 否则 | + | else |
- | LCD_instruction <=(count!= 7); | + | LCD_instruction <= (count!=7); |
- | 分配 LCD_RS =〜LCD_instruction; | + | assign LCD_RS = ~LCD_instruction; |
- | 终端模块 | + | endmodule |
- | HD44780规范显示,“ E”变低后,“ RS”必须在10ns内有效。因此,您会注意到这里“ E”仅被驱动6个时钟,并且“ LCD_instruction”标志仅在时钟7之后被复位,以提供25ns的空间。 | + | </code> |
+ | \\ | ||
+ | HD44780规范显示,“ E”变低后,“ RS”必须在10ns内有效。因此,您会注意到这里“ E”仅被驱动6个时钟,并且“ LCD_instruction”标志仅在时钟7之后被复位,以提供25ns的空间。\\ | ||
+ | {{ ::lcdmodule_waveform2.gif |}} | ||
+ | \\ | ||
+ | \\ | ||
+ | **That's all folks!轮到您尝试了。** | ||
- | 那就是所有人!轮到您尝试了。 |