FPGAベースのLCD1602ディスプレイドライバー

1. LCD1602ディスプレイの原理

1.ピン機能

内部の機能ブロック図を次の図に示します。

一般に、LCD1602には16ピンがあります。さまざまなメーカーのLCD1602は若干異なる場合がありますが、基本的には同じです。16ピンの機能は次のとおりです。

LCD1602ピン機能
ピン番号 ピン名 電圧レベル 特徴
1 VSS 0V パワーグラウンド
2 VDD + 5V 正の電力
V0 0V 電圧バイアス
4 RS H / L コマンド/データ
5 R / W H / L 読み書き
6 E H / L 有効にする
7〜14 DB0〜DB7 H / L データポート
15 + 5V 正のバックライト
16 LEDK 0V バックライトネガ

この表の説明:

1. VSSを電源グランドに接続します。

2. VDDを+ 5Vに接続します。

3. VOは、液晶ディスプレイのバイアス信号で、10K 3296精密ポテンショメーターに接続できます。または同じ抵抗のRM065 / RM063青と白の調整可能な抵抗器。下記参照。

4. RSはコマンド/データ選択ピンで、マイクロコントローラのI / Oに接続されており、RSがローの場合はコマンドが選択され、RSがハイの場合はデータが選択されます。

5. RWは読み取り/書き込み選択ピンであり、マイクロコントローラのI / Oに接続されており、RWがローの場合、コマンドまたはデータをLCD1602に書き込み、RWがハイの場合、ステータスまたはデータをLCD1602から読み取ります。読み取り操作を実行する必要がない場合は、VSSに直接接続できます。

6.コマンドを実行するためのイネーブルピンであるEは、マイクロコントローラーのI / Oに接続されています。

7. D0〜D7、パラレルデータ入力/出力ピン。マイクロコントローラーのP0〜P3の任意の8つのI / Oポートに接続できます。ポートP0に接続する場合、ポートP0は4.7K-10Kプルアップ抵抗に接続する必要があります。4線パラレルドライブの場合、必要なI / Oポートは4つだけです。

8.バックライトアノード。10〜47オームの電流制限抵抗をVDDに接続できます。

9. Kバックライトの負極、VSSに接続します。下の図を参照してください。

2.基本的な操作

LCD1602の基本的な操作は4つのタイプに分けられます:

1.ステータスの読み取り:入力RS = 0、RW = 1、E =高パルス。出力:D0〜D7はステータスワードです。

2.データの読み取り:入力RS = 1、RW = 1、E =高パルス。出力:D0〜D7はデータです。

3.書き込みコマンド:入力RS = 0、RW = 0、E =高パルス。出力:なし。

4.データの書き込み:入力RS = 1、RW = 0、E =高パルス。出力:なし。

動作タイミング図と制約を読む:

 書き込み操作のタイミング図:

タイミング時間パラメーター:

3. DDRAM、CGROMおよびCGRAM

DDRAM(表示データRAM)は、表示する文字コードを登録するための表示データRAMです。合計80バイト、アドレスと画面の対応関係は次のとおりです。

DDRAMはコンピュータのビデオメモリに相当し、画面に文字を表示するために、文字コードをビデオメモリに送り、画面に文字を表示できるようにします。同様に、LCD1602には合計80バイトのビデオメモリ、つまりDDRAMがあります。ただし、LCD1602の表示画面は16×2しかありませんので、DDRAMに書き込んだ文字コードをすべて画面に表示できるわけではなく、上図の範囲に書かれた文字のみ表示でき、範囲外に書かれています。文字は表示できません。このように、プログラムで次の「カーソルまたは表示移動コマンド」を使用して、表示されている表示範囲にキャラクターをゆっくりと移動し、キャラクターの移動効果を確認できます。

前述のように、LCD画面に文字を表示するために、文字コードがDDRAMに送信されます。たとえば、画面の左上隅に文字「A」を表示する場合は、DDRAMの00Hアドレスに文字「A」の文字コード41Hを書き込みます。書き方については、後ほど説明があります。では、なぜこのコードの文字を対応する位置に表示できるように、文字コードをDDRAMに書き込むのでしょうか。LCD1602は一種の文字ドットマトリックスディスプレイであることはわかっています。文字のフォントを表示するには、この文字のフォントデータが必要です。文字のフォントデータとは何ですか?下の図を見て理解してください。

上図左側が文字「A」のフォントデータ、右側が左側の「○」が0、「■」が1のデータです。したがって、文字「A」が表示されます。下の図からわかるように、文字「A」の上位4ビットは0100で、下位4ビットは0001です。合わせて01000001b、つまり41Hです。これはたまたま文字のASCIIコードと一致しているため、非常に便利ですPCでは構文P2 = 'A'を使用できます。コンパイル後、たまたまこのキャラクターのキャラクターコードになります。

フォントメモリは、LCDROM2モジュール、つまりCGROMとCGRAMに統合されています。HD44780には、192の一般的に使用される文字のフォントが内蔵されており、それらは文字ジェネレータCGROM(Character Generator ROM)に格納され、8つのユーザー定義文字生成RAMがあります。 、CGRAM(Character Generator RAM)と呼ばれます。次の図(図12)は、CGROMおよびCGRAMと文字の対応を示しています。ROMとRAMの名前から、ROMはLCD1602モジュールですでに固化されており、読み取りのみが可能で、RAMは読み取りと書き込みが可能であることもわかります。つまり、CGROMにすでに存在する文字だけを画面に表示する必要がある場合は、その文字コードをDDRAMに書き込むだけでよく、CGROMにない文字(摂氏温度スケールのシンボルなど)を表示したい場合、それからCGRAMでのみ定義し、DDRAMにこのカスタムキャラクターの文字コードを書き込みます。CGROMの立体化された文字とは異なり、CGRAMには文字がありません。したがって、CGROMに存在しない文字をDDRAMに書き込むには、使用する前にCGRAMで定義する必要があります。プログラムが終了すると、CGRAMで定義された文字は存在しなくなり、次回使用するときに再定義する必要があります。

上の図は、5×8ドットマトリックス文字と5×10ドットマトリックス文字のフォントとカーソル位置を示しています。まず、5×8のドットマトリックスについて説明します。8行5列です。次に、そのような文字を定義するのに8バイトかかり、各バイトの最初の3ビットは使用されません。たとえば、摂氏温度スケールに記号{0x10,0x06,0x09,0x08,0x08,0x09,0x06,0x00}を定義します。

上の図は、CGRAMアドレスを設定するための命令を示しています。この命令の形式から、合計6ビットのaaaaaaを持っていることがわかります。これは、合計64アドレス、つまり64バイトを表すことができます。5×8のドットマトリックス文字は合計8バイトを占めるため、合計8文字をこれらの64バイト用にカスタマイズできます。つまり、上図の6ビットアドレスのDB5DB4DB3は8つのカスタム文字を表すために使用され、DB2DB1DB0は各文字の8バイトを表すために使用されます。DB5DB4DB3で表される8つのカスタム文字(0〜7)は、DDRAMに書き込まれる文字コードです。CGRAMで定義できるカスタム文字は8つだけです。つまり、0から7までの文字コードは8つしかありませんが、次の表には合計16文字のコードがあります(××××0000b--×× ××1111b)。実際、図に示されているように、8つのカスタム文字しか表現できません(××××0000b =××××1000b、××××0001b =××××1001b ...など)。つまり、DDRAMに書き込まれる文字コード0と文字コード8は同じカスタム文字です。5×10ドットマトリックスの各文字は合計16バイトのスペースを占有するため、CGRAMで定義できるカスタム文字は4つだけです。

では、CGRAMのキャラクターをカスタマイズする方法は?上記の概要では、DDRAM書き込み命令と同様のCGRAMアドレス設定命令があり、特定のカスタム文字のフォントデータを設定してから、上記の方法に従ってCGRAMアドレスを設定し、これを順番に書き込むことがわかっています。フォントデータは大丈夫です。以下の例で説明します。

4. LCD1602の手順

(1)。作業モード設定手順

×:気にしない、つまり、このビットは0または1、通常は0にすることができます。

DL:データインターフェースのビット数を設定します。

DL = 1:8ビットデータインターフェイス(D7 — D0)。

DL = 0:4ビットデータインターフェイス(D7〜D4)。

N = 0:1行表示。

N = 1:2行表示。

F = 0:5×8ドットマトリックス文字。

F = 1:5×10ドットマトリックス文字。

注:これは書き込みコマンドワードであるため、RSとRWはどちらも0です。LCD1602は、シリアルモードではなく、パラレルモードでのみ駆動できます。また、パラレルモードでは、8ビットデータインターフェイスまたは4ビットデータインターフェイスを選択できます。ここでは、8ビットデータインターフェイス(D7〜D0)を選択します。私たちの設定は、8ビットデータインターフェイス、2行表示、5x8ドットマトリックス、つまり0x00111000、つまり0x38です。(注:NFが10または11であることの影響は同じで、どちらも2行5x8ドットマトリックスです。2行5x10ドットマトリックスでは表示できないため、つまり、ここでは0x38または0x3cを使用することは同じですの)。

(2)。スイッチ制御命令の表示

D = 1:表示オン、D = 0:表示オフ。

C = 1:カーソルを表示、C = 0:カーソルを表示しない。

B = 1:カーソルは点滅します。B= 0:カーソルは点滅しません。

注:ここでの設定は、ディスプレイがオン、カーソルが表示されない、カーソルが点滅しない、設定ワードが0x0cであるということです。

(3)。モード設定指示入力

I / D = 1:新しいデータを書き込んだ後、カーソルが右に移動します。

I / D = 0:新しいデータを書き込んだ後、カーソルは左に移動します。

S = 1:動きを表示します。

S = 0:ディスプレイは動かない。

注:ここでの設定は0x06です。

(4)。カーソルまたはディスプレイ移動命令

説明:このコマンドは、画面全体を移動する必要がある場合に非常に役立ち、画面のスクロール表示効果を実現できます。このコマンドは、初期化時には使用されません。

(5)。画面消去コマンド

説明:画面表示内容をクリアします。カーソルが画面の左上隅に戻ります。このコマンドの実行にはしばらく時間がかかります。

(6)。カーソルホーム命令

注:カーソルは画面の左上隅に戻ります。画面に表示される内容は変わりません。

(7)。CGRAMアドレス設定命令

説明:このコマンドは上記で導入されました。次の例で使用法を説明します。

(8)。DDRAMアドレス設定コマンド

説明:このコマンドは、DDRAMアドレスを設定するために使用されます。DDRAMの読み取りと書き込みを行う前に、最初にDDRAMアドレスを設定し、次に読み取りと書き込みを行います。前に述べたように、DDRAMはLCD1602のディスプレイメモリです。表示したい場合は、表示する文字をDDRAMに書き込む必要があります。同様に、DDRAMの特定のアドレスにある文字を知りたい場合は、最初にDDRAMアドレスを設定してから、それをマイクロコントローラーに読み込む必要があります。

(9)。ビジー信号とアドレスカウンターACを読み取る

説明:このコマンドは、LCD1602のステータスを読み取るために使用されます。マイクロコントローラーにとって、LCD1602は遅いデバイスです。マイクロコントローラが命令を送信すると、命令が実行されます。このとき、シングルチップマイコンが次の命令を再度送信すると、LCD1602が遅く、前の命令がまだ実行されていないため、新しい命令を受け付けず、新しい命令が失われます。したがって、この読み出しビジー命令を使用して、LCD1602がビジーであるかどうか、およびLCD1602がマイクロコントローラから命令を受信できるかどうかを判断できます。BF = 1の場合、LCD1602はビジーであり、マイクロコントローラーからの命令を受け入れることができないことを意味し、BF = 0の場合、LCD1602はアイドル状態であり、マイクロコントローラーからの命令を受信できることを意味します。RS = 0はコマンドであることを意味し、RW = 1は読み取りであることを意味します。この命令には別の副産物があります。つまり、アドレスカウンタACの値を取得できます。LCD1602は、次の読み取りおよび書き込みCGRAMまたはDDRAMの位置を記録するために、アドレスカウンターACを維持します。次の点を強調しておく必要があります。この命令は正常に実行されませんでした。多くのネチズンが同じことをしているようです。幸いなことに、私たちには別の方法があります。それは遅延することです。各命令の実行時間を見て、いくつかの実験を行った後、命令の遅延を特定できます。このようにして、前の命令が実行された後に次の命令を実行することができます。

(10)。CGRAMまたはDDRAM命令へのデータの書き込み

説明:RS = 1、データ、RW = 0、書き込み。命令を実行するときは、最初にDB7〜DB0に書き込むデータを設定してから、書き込みコマンドを実行します。

(11)。CGRAMまたはDDRAMからのデータコマンドの読み取り

説明:RS = 1、データ、RW = 1、読み取り。最初にCGRAMまたはDDRAMのアドレスを設定してから、読み取りコマンドを実行します。データはDB7-DB0に読み込まれます。

5. LCD1602の一般的な初期変更手順(FPGAで初期化する必要があります)

  1. 15ミリ秒の遅延
  2. 書き込み命令38H(ビジー信号を検出しない)
  3. 15ミリ秒の遅延
  4. 書き込み命令38H(ビジー信号を検出しない)
  5. 15ミリ秒の遅延
  6. 書き込み命令38H(ビジー信号を検出しない)
  7. 後続の書き込み命令ごとに、データの読み取り/書き込み操作でビジー信号を検出する必要があります
  8. 書き込み命令38H:表示モード設定
  9. 書き込みコマンド08H:表示オフ
  10. 書き込みコマンド01H:クリア画面を表示
  11. 書き込み命令06H:カーソル移動設定の表示
  12. 書き込みコマンド0CH:表示とカーソル設定

11と12の2つのシーケンスは交換できません。書き込み中に変更した場合、デバッグできません。通常、左奥の2つのシーケンスを入れ替えます。

 

2、FPGA実装

実現機能:LCDディスプレイに2行を表示し、最初の行はPan-Hong-Fengを表示し、2行目はLCD1602-Testを表示します。

コードは次のとおりです:(コードは少し面倒で、コメントするには面倒すぎる)


// 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

テストコード:


`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

 シミュレーション波形:

物理的な地図:

 

おすすめ

転載: blog.csdn.net/qq_33231534/article/details/108484995