1. LCD1602 display principle
1. Pin function
Its internal functional block diagram is shown in the figure below:
Generally speaking, LCD1602 has 16 pins. LCD1602 of various manufacturers may be slightly different, but basically the same. The functions of the 16 pins are as follows:
Pin number | Pin name | Voltage level | Features |
1 | VSS | 0V | Power ground |
2 | VDD | +5V | Power positive |
3 | V0 | 0V | Voltage bias |
4 | RS | H/L | Command/data |
5 | R/W | H/L | Read/write |
6 | E | H/L | Enable |
7~14 | DB0 ~ DB7 | H/L | Data port |
15 | LEAD | +5V | Backlight positive |
16 | LEDK | 0V | Backlight negative |
Explanation of this table:
1. Connect VSS to the power ground.
2. Connect VDD to +5V.
3. VO is the bias signal of the liquid crystal display, which can be connected to a 10K 3296 precision potentiometer. Or RM065/RM063 blue and white adjustable resistors of the same resistance. See below.
4. RS is the command/data selection pin, which is connected to an I/O of the microcontroller. When RS is low, the command is selected; when RS is high, the data is selected.
5. RW is the read/write selection pin, which is connected to an I/O of the microcontroller. When RW is low, write commands or data to LCD1602; when RW is high, read status or data from LCD1602. If you do not need to perform a read operation, you can directly connect it to VSS.
6. E, the enable pin for executing commands, is connected to an I/O of the microcontroller.
7. D0—D7, parallel data input/output pins, which can be connected to any 8 I/O ports of P0—P3 of the microcontroller. If it is connected to port P0, port P0 should be connected to a 4.7K-10K pull-up resistor. If it is a 4-wire parallel drive, only 4 I/O ports are required.
8. A backlight anode, you can connect a 10-47 ohm current limiting resistor to VDD.
9. The negative pole of K backlight, connect to VSS. See the figure below.
2. Basic operation
The basic operations of LCD1602 are divided into four types:
1. Read status: input RS=0, RW=1, E=high pulse. Output: D0—D7 are status words.
2. Read data: input RS=1, RW=1, E=high pulse. Output: D0-D7 are data.
3. Write command: input RS=0, RW=0, E=high pulse. Output: None.
4. Write data: input RS=1, RW=0, E=high pulse. Output: None.
Read operation timing diagram and constraints:
Timing diagram of write operation:
Timing time parameters:
3. DDRAM, CGROM and CGRAM
DDRAM (Display Data RAM) is the display data RAM, used to register the character code to be displayed. A total of 80 bytes, the corresponding relationship between the address and the screen is as follows:
DDRAM is equivalent to the computer's video memory. In order to display characters on the screen, we send the character code to the video memory so that the character can be displayed on the screen. Similarly, the LCD1602 has a total of 80 bytes of video memory, namely DDRAM. But the display screen of LCD1602 is only 16×2. Therefore, not all character codes written into DDRAM can be displayed on the screen. Only the characters written in the range shown in the above figure can be displayed, and they are written outside the range. The characters cannot be displayed. In this way, we can use the following "cursor or display movement command" in the program to slowly move the character to the visible display range, and see the movement effect of the character.
As mentioned earlier, in order to display characters on the LCD screen, the character code is sent to DDRAM. For example, if you want to display the character'A' in the upper left corner of the screen, then write the character code 41H of the character'A' into the 00H address of the DDRAM. As for how to write, there will be instructions later. So why write the character code into DDRAM so that the character of this code can be displayed in the corresponding position? We know that LCD1602 is a kind of character dot matrix display. In order to display the font of a character, there must be the font data of this character. What is the font data of a character? Just look at the figure below to understand.
The left side of the above figure is the font data of the character'A', and the right side is the data on the left side with "○" representing 0 and "■" representing 1. Thus displaying the character "A". As can be seen from the figure below, the upper 4 bits of the character'A' are 0100, and the lower 4 bits are 0001. Together, they are 01000001b, which is 41H. It happens to be consistent with the ASCII code of the character, which gives us a lot of convenience. We can use the syntax P2='A' on the PC. After compilation, it happens to be the character code of this character.
The font memory is solidified on the LCD1602 module, which is CGROM and CGRAM. HD44780 has built-in fonts of 192 commonly used characters, which are stored in the character generator CGROM (Character Generator ROM). There are also 8 user-defined character generation RAMs. , Called CGRAM (Character Generator RAM). The following figure (Figure 12) illustrates the correspondence between CGROM and CGRAM and characters. From the names of ROM and RAM, we can also know that ROM is already solidified in the LCD1602 module and can only be read; while RAM is readable and writable. In other words, if you only need to display the characters that already exist in the CGROM on the screen, you only need to write its character code in the DDRAM; but if you want to display the characters that are not in the CGROM, such as the symbol of the Celsius temperature scale , Then only define it in CGRAM, and then write the character code of this custom character in DDRAM. Unlike the solidified characters in CGROM, there are no characters in CGRAM, so to write a character that does not exist in CGROM in DDRAM, it must be defined in CGRAM before use. After the program exits, the characters defined in CGRAM no longer exist, and must be redefined when used next time.
The above figure illustrates the font and cursor position of 5×8 dot matrix and 5×10 dot matrix characters. Let's talk about the 5×8 dot matrix first, it has 8 rows and 5 columns. Then it takes 8 bytes to define such a character, and the first 3 bits of each byte are not used. For example, define the symbol {0x10,0x06,0x09,0x08,0x08,0x09,0x06,0x00} for the Celsius temperature scale.
The above figure illustrates the instruction to set the CGRAM address. From the format of this instruction, we can see that it has a total of 6 bits aaaaaa, which can represent a total of 64 addresses, that is, 64 bytes. A 5×8 dot matrix character occupies a total of 8 bytes, so a total of 8 characters can be customized for these 64 bytes. In other words, DB5DB4DB3 in the 6-bit address in the above figure is used to represent 8 custom characters, and DB2DB1DB0 is used to represent 8 bytes of each character. The 8 custom characters (0-7) represented by DB5DB4DB3 are the character codes to be written into DDRAM. We know that only 8 custom characters can be defined in CGRAM, that is, there are only 8 character codes from 0 to 7, but there are a total of 16 character codes in the following table (××××0000b--×× ××1111b). In fact, as shown in the figure, it can only represent 8 custom characters (××××0000b=××××1000b, ××××0001b=××××1001b...and so on). In other words, the character code 0 and character code 8 written into the DDRAM are the same custom character. Each character of the 5×10 dot matrix occupies a total of 16 bytes of space, so only 4 such custom characters can be defined in CGRAM.
So how to customize characters in CGRAM? In the above introduction, we know that there is a set CGRAM address instruction, which is similar to the write DDRAM instruction, just set the font data of a certain custom character, and then set the CGRAM address according to the method described above, and write this in turn The font data is fine. We will explain it in the following example.
4. LCD1602 instructions
(1). Working mode setting instruction
×: Don't care, that is to say, this bit can be 0 or 1, generally 0.
DL: Set the number of data interface bits.
DL=1: 8-bit data interface (D7—D0).
DL=0: 4-bit data interface (D7—D4).
N=0: One line display.
N=1: Two-line display.
F=0: 5×8 dot matrix characters.
F=1: 5×10 dot matrix characters.
Note: Because it is a write command word, both RS and RW are 0. LCD1602 can only be driven in parallel mode, not serial mode. And the parallel mode can choose 8-bit data interface or 4-bit data interface. Here we choose 8-bit data interface (D7-D0). Our setting is 8-bit data interface, two-line display, 5×8 dot matrix, that is, 0b00111000, which is 0x38. (Note: the effect of NF being 10 or 11 is the same, both are two-line 5×8 dot matrix. Because it cannot be displayed in two-line 5×10 dot matrix, in other words, it is the same to use 0x38 or 0x3c here of).
(2). Display switch control instructions
D=1: display on, D=0: display off.
C=1: cursor is displayed, C=0: cursor is not displayed.
B=1: the cursor blinks, B=0: the cursor does not blink.
Note: The setting here is that the display is on, the cursor is not displayed, the cursor is not blinking, and the setting word is 0x0c.
(3). Enter mode setting instruction
I/D=1: The cursor moves to the right after writing new data.
I/D=0: The cursor moves to the left after writing new data.
S=1: Display movement.
S=0: Display does not move.
Note: The setting here is 0x06.
(4). Cursor or display movement instruction
Description: This command is very useful when you need to move the entire screen, and can realize the scrolling display effect of the screen. This command is not used during initialization.
(5). Clear screen command
Description: Clear the screen display content. The cursor returns to the upper left corner of the screen. It will take some time to execute this command.
(6). Cursor home instruction
Note: The cursor returns to the upper left corner of the screen, it does not change the content displayed on the screen.
(7). Set CGRAM address instruction
Description: This command has been introduced above. The usage is explained in the following example.
(8). Set DDRAM address command
Description: This command is used to set the DDRAM address. Before reading and writing to DDRAM, first set the DDRAM address, and then read and write. As we said before, DDRAM is the display memory of LCD1602. If we want to display on it, we must write the characters to be displayed into DDRAM. Similarly, if we want to know what characters are on a certain address of DDRAM, we must first set the DDRAM address, and then read it to the microcontroller.
(9). Read busy signal and address counter AC
Description: This command is used to read the status of LCD1602. For the microcontroller, LCD1602 is a slow device. When the microcontroller sends an instruction to it, it will execute the instruction. At this time, if the single-chip microcomputer sends the next instruction again, because the LCD1602 is slow, and the previous instruction has not been executed yet, it will not accept the new instruction, resulting in the loss of the new instruction. Therefore, this read busy instruction can be used to determine whether the LCD1602 is busy and whether it can receive instructions from the microcontroller. When BF=1, it means that the LCD1602 is busy and cannot accept instructions from the microcontroller; when BF=0, it means that the LCD1602 is idle and can receive instructions from the microcontroller. RS=0, which means it is a command; RW=1, which means it is a read. There is another by-product of this instruction: that is, the value of the address counter AC can be obtained. LCD1602 maintains an address counter AC to record the position of the next read and write CGRAM or DDRAM. It should be emphasized that: I did not execute this instruction successfully. Many netizens seem to do the same. Fortunately, we have another way, which is to delay. By looking at the execution time of each instruction, and after some experiments, the delay of the instruction can be determined. In this way, the next instruction can be executed after the previous instruction is executed.
(10). Write data to CGRAM or DDRAM instruction
Explanation: RS=1, data; RW=0, write. When the instruction is executed, first set the data to be written on DB7—DB0, and then execute the write command.
(11). Read data command from CGRAM or DDRAM
Explanation: RS=1, data; RW=1, read. Set the address of CGRAM or DDRAM first, and then execute the read command. The data is read into DB7-DB0.
5. LCD1602 general initial change steps (must be initialized in FPGA)
- Delay 15mS
- Write instruction 38H (do not detect busy signal)
- Delay 15mS
- Write instruction 38H (do not detect busy signal)
- Delay 15mS
- Write instruction 38H (do not detect busy signal)
- Every subsequent write instruction, read/write data operation needs to detect the busy signal
- Write instruction 38H: display mode setting
- Write command 08H: display off
- Write command 01H: display clear screen
- Write instruction 06H: display cursor movement setting
- Write command 0CH: display on and cursor setting
The two sequences of 11 and 12 cannot be interchanged. I can’t debug it if I changed them while writing. It is normal to swap the two sequences in the back left.
Two, FPGA implementation
Realization function: display two lines on the LCD display, the first line displays: Pan-Hong-Feng; the second line displays: LCD1602-Test
The code is as follows: (code is a bit messy, too lazy to comment)
// Company :
// Engineer :
// -----------------------------------------------------------------------------
// https://blog.csdn.net/qq_33231534 PHF's CSDN blog
// -----------------------------------------------------------------------------
// Create Date : 2020-09-07 15:48:40
// Revise Data : 2020-09-08 09:53:32
// File Name : lcd.v
// Target Devices : XC7Z015-CLG485-2
// Tool Versions : Vivado 2019.2
// Revision : V1.1
// Editor : sublime text3, tab size (4)
// Description : LCD1602 driver
module lcd(
input clk ,
input rst_n ,
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 ,
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 ;
assign lcd_rw = 1'b0;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 17'd0;
end
else begin
if (cnt==17'd100_000 - 1) begin
cnt <= 17'd0;
end
else begin
cnt <= cnt + 1'b1;
end
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
lcd_en <= 0;
end
else if (cnt==17'd50_000 - 1) begin
lcd_en <= 1;
end
else if (cnt==17'd100_000 - 1) begin
lcd_en <= 0;
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
char_cnt <= 0;
end
else if (state_c==WRITE && cnt==17'd50_000 - 1) begin
if (char_cnt==5'd24) 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 = "P";
5'd1: data_display = "a";
5'd2: data_display = "n";
5'd3: data_display = "-";
5'd4: data_display = "H";
5'd5: data_display = "o";
5'd6: data_display = "n";
5'd7: data_display = "g";
5'd8: data_display = "-";
5'd9: data_display = "F";
5'd10: data_display = "e";
5'd11: data_display = "n";
5'd12: data_display = "g";
5'd13: data_display = "L";
5'd14: data_display = "C";
5'd15: data_display = "D";
5'd16: data_display = "1";
5'd17: data_display = "6";
5'd18: data_display = "0";
5'd19: data_display = "2";
5'd20: data_display = "-";
5'd21: data_display = "T";
5'd22: data_display = "e";
5'd23: data_display = "s";
5'd24: data_display = "t";
default:data_display = "P";
endcase
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state_c <= IDLE;
end
else if(cnt==17'd50_000 - 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'd750000) begin
flag <= 1;
end
end
always @(*) 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'd12) begin
state_n = ROW2_ADDR;
end
else if (char_cnt==5'd24) 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
default:state_n = IDLE;
endcase
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'hc0; lcd_rs <= 0;end
stop :begin lcd_data <= 8'h38; lcd_rs <= 0;end
default:;
endcase
end
end
endmodule
Test code:
`timescale 1ns/1ns
module lcd_tb (); /* this is automatically generated */
reg rst_n;
reg clk;
wire lcd_rs;
wire lcd_rw;
wire lcd_en;
wire [7:0] lcd_data;
lcd inst_lcd
(
.clk (clk),
.rst_n (rst_n),
.lcd_rs (lcd_rs),
.lcd_rw (lcd_rw),
.lcd_en (lcd_en),
.lcd_data (lcd_data)
);
initial clk = 0;
always #10 clk = ~clk;
initial begin
#1;
rst_n = 0;
#200;
rst_n = 1;
#200;
#100000000;
$stop;
end
endmodule
Simulation waveform:
Physical map: