LCD1602 display driver based on FPGA

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:

LCD1602 pin function
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)

  1. Delay 15mS
  2. Write instruction 38H (do not detect busy signal)
  3. Delay 15mS
  4. Write instruction 38H (do not detect busy signal)
  5. Delay 15mS
  6. Write instruction 38H (do not detect busy signal)
  7. Every subsequent write instruction, read/write data operation needs to detect the busy signal
  8. Write instruction 38H: display mode setting
  9. Write command 08H: display off
  10. Write command 01H: display clear screen
  11. Write instruction 06H: display cursor movement setting
  12. 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:

 

Guess you like

Origin blog.csdn.net/qq_33231534/article/details/108484995