今回はパラレル接続のキャラクタLCD の使い方を紹介します。
タイトルに 1602 とついていますが、キャラクタLCD の制御IC は大抵同じものなので、文字数の違うものだったり、メーカーの違うものであっても同じように操作出来ると思います。
I2C 接続の LCD についてはこちらをご覧ください。
使用パーツ
- LCD 1602A
- PIC16F1827
今回使用する LCD はジャンクで購入したものなので正確な型番等は不明ですが、基板裏には QAPASS 1602A と表記されています。ピンアサインは表に書いてあるので問題なく使用出来ました。
今回使用したものとは違いますが、参考として秋月電子で販売されている LCD のリンクを貼っておきます。LCDキャラクタディスプレイモジュール L1682D1J000 (16×2行 白色バックライト)
こちらのページにあるデータシートを参考にしています。
接続方法
まずは各ピンの説明です。
VDD, VSS | 電源ピン:+5V と GND に接続 |
V0 | コントラスト調整:可変抵抗を接続 |
RS | Register Select:コマンドかデータを指定。マイコンに接続 |
RW | Read Write:読取は H 、書込は L 。読取の必要がない場合は GND に接続 |
E | Enable signal:マイコンに接続 |
D0 ~ D3 | データ:8bitモードのときのみ使用 |
D4 ~ D7 | データ:8bitモードと 4bitモードのどちらでも使用 |
A, K | バックライト電源:抵抗を介して電源に接続 |
LCD とマイコンの接続は 8bitモードと 4bitモードの 2種類がありますが、今回は 4bitモードで行います。
4bitモードでは 1byte のデータを2回に分けて送信する必要がありますが、マイコンのピンを4本節約できるメリットの方が大きいです。
回路図は次のようになります。
通信方法
データシートにあるタイミングチャートを見てみましょう。
注目すべき時点は、橙色の枠で囲んだ E が立ち下がる時点です。このタイミングで LCD にデータが送信されます。
すなわち、データの送信は
- RS をコマンドであれば H に、データであれば L に設定
- RW を L に設定(常に GND に接続していれば大丈夫)
- D4~D7 に送信するデータを設定(4bitモードの場合)
- 以上の状態で E を立ち上げてから立ち下げる
というステップで行うことができます。
以下がプログラム例です。(このページの最後にライブラリとしてまとめたものを載せています)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
void lcd_Send4(char c){ E = 1; PORTA = (PORTA & 0xF0) | (c & 0x0F); //RA0-3 に送信する4bitを設定 //2022/9/5追記 PICの種類によってRA3は入力専用の場合もあるので注意 NOP(); //クロック周波数が高い場合に必要な遅延 E = 0; //E立ち下げで送信 } void lcd_Send8(char c){ lcd_Send4(c >> 4); //上位4bit ビットマスクはlcd_Send4() の中で lcd_Send4(c); //下位4bit } void lcd_INST(char command){ RS = 0; lcd_Send8(command); __delay_us(40); } void lcd_DATA(char data){ RS = 1; lcd_Send8(data); __delay_us(40); } |
4bitモードでは 1byte のデータを送信するには上位4bit と下位4bit に分けて送信する必要があるので、上記のようにまず 4bit を送信する関数を作ってそれを2回呼び出しています。
RSについては、コマンドとデータでそれぞれ別の関数を用意してその中で設定しています。40us の遅延については後述のコマンド表に書いてある標準で必要な遅延時間です。
コマンド
まずコマンドの一覧表と初期化例を載せます。
コマンドの詳しい内容はデータシートを参照してください。
これらを基に初期化関数とカーソル位置設定関数を用意しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
void lcd_Init(){ RS = 0; E = 0; __delay_ms(45); lcd_Send4(0x03); __delay_ms(5); lcd_Send4(0x03); __delay_us(100); lcd_Send4(0x03); //ここまでで確実に8bitモードになる lcd_Send4(0x02); //4bitモードに設定 __delay_us(40); lcd_INST(0x28); //ファンクションセット : 4bit/ 2lines/ 5*7 lcd_INST(0x08); //表示OFF/ カーソルOFF/ ブリンクOFF lcd_INST(0x01); //ディスプレイクリア __delay_ms(2); lcd_INST(0x06); //エントリーモード : カーソル右移動/ 表示シフトなし lcd_INST(0x0C); //表示ON } void lcd_SetCursor(char row, char column){ const char lcd_DDRAM[4] = {0x00, 0x40, 0x14, 0x54}; //各行の先頭アドレス char adrs = lcd_DDRAM[row] + column; //指定位置のアドレス lcd_INST(0x80 + adrs); } |
データシートの初期化例ではディスプレイの表示を OFF にしたままなので、初期化関数の最後に表示を ON にさせています。
カーソル位置指定関数は4行の LCD にも対応できるようにしておきました。
CGRAM の使い方
LCD には英数字と半角カナのフォントが内蔵されていますが、CGRAM (Character Generation RAM) を使用すると独自のフォントを使用することができます。
CGRAM は右図のように8文字分用意されていて、この RAM にフォント(5×8)のデータを書き込むことで、任意の文字や記号を表示することができます。
文字コードと CGRAMアドレス、CGRAMデータの関係は下図のようになっています。
CGRAMアドレスの bit3~bit5 が文字コードの下位3bit を表し、bit0~bit2 がその文字内での位置を表します。(bit6,7 が無いことは前述のコマンド一覧の「CGRAMアドレスセット」を参照してください)
よって、1文字分のフォントを CGRAM に設定するためには、
- 指定の文字コードに対応する CGRAMアドレス(下位3bit は 000)をセット
- フォントのデータを送信(上から8回)
すればよいことになります。次の関数を用意しました。
1 2 3 4 5 6 |
void lcd_DefineCharacter(char adrs, char* pattern){ lcd_INST(0x40 | (adrs << 3)); //CGRAMアドレス (adrs: 0-7) for(char i=0; i<8; i++) lcd_DATA(pattern[i]); lcd_INST(0x80); //書き込み先をDDRAMに戻す } |
1文字表示と printf() による文字列表示
初期化が完了したら、文字データを送信することで現在のDDRAMアドレス(カーソル位置)にそのデータが書き込まれ、LCD に文字を表示することができます。
例えば、「A」と表示させたい場合は先程用意した関数を使用して
とすればよいです。
文字列を表示させたい場合、ポインタを引数にしてこの関数を繰り返し実行する関数を用意すればよいのですが、printf()関数が使用出来れば変換指定子なども利用できるのでとても便利です。
PIC で標準出力関数の printf()を使用するには、低レベル出力関数の putch() 関数を自分で設定する必要があります。
今回は出力先を LCD にするので、
1 2 3 |
void putch(char c){ lcd_DATA(c); } |
と記述します。これだけで printf()関数が使用可能になります。CGRAM の文字を使用するには、変換指定子の %c を使用します。
ちなみにこれは出力先を USART にしたりすることも可能です。
サンプルプログラム
LCD の1行目に”HelloWorld リケデン”と表示し、2行目には変換指定子を利用した変数の表示と CGRAM を利用した”℃”を表示させてみました。
1秒毎に1℃増えるという意味不明なプログラムですが気にしない。
上手くいかない場合はまずコントラストをいじってみると良いと思います。コントラストのピンに繋げた可変抵抗を回して下図のように薄すぎ/濃すぎでないか確かめてください。
以下プログラムと回路図(再掲)です。
1 2 3 4 5 6 7 |
void lcd_Send4(char c); void lcd_Send8(char c); void lcd_INST(char command); void lcd_DATA(char data); void lcd_Init(); void lcd_SetCursor(char row,char column); void lcd_DefineCharacter(char adrs, char* pattern); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
#define E RA6 #define RS RA7 #define _XTAL_FREQ 500000 #include <xc.h> void lcd_Send4(char c){ E = 1; PORTA = (PORTA & 0xF0) | (c & 0x0F); //RA0-3 に送信する4bitを設定 //2022/9/5追記 PICの種類によってRA3は入力専用の場合もあるので注意 NOP(); //クロック周波数が高い場合に必要な遅延 E = 0; //E立ち下げで送信 } void lcd_Send8(char c){ lcd_Send4(c >> 4); //上位4bit ビットマスクはlcd_Send4() の中で lcd_Send4(c); //下位4bit } void lcd_INST(char command){ RS = 0; lcd_Send8(command); __delay_us(40); } void lcd_DATA(char data){ RS = 1; lcd_Send8(data); __delay_us(40); } void lcd_Init(){ RS = 0; E = 0; __delay_ms(45); lcd_Send4(0x03); __delay_ms(5); lcd_Send4(0x03); __delay_us(100); lcd_Send4(0x03); //ここまでで確実に8bitモードになる lcd_Send4(0x02); //4bitモードに設定 __delay_us(40); lcd_INST(0x28); //ファンクションセット : 4bit/ 2lines/ 5*7 lcd_INST(0x08); //表示OFF/ カーソルOFF/ ブリンクOFF lcd_INST(0x01); //ディスプレイクリア __delay_ms(2); lcd_INST(0x06); //エントリーモード : カーソル右移動/ 表示シフトなし lcd_INST(0x0C); //表示ON } void lcd_SetCursor(char row, char column){ //行、列は0番目から const char lcd_DDRAM[4] = {0x00, 0x40, 0x14, 0x54}; //各行の先頭アドレス char adrs = lcd_DDRAM[row] + column; //指定位置のアドレス lcd_INST(0x80 + adrs); } void lcd_DefineCharacter(char adrs, char* pattern){ lcd_INST(0x40 | (adrs << 3)); //CGRAMアドレス (adrs: 0-7) for(char i=0; i<8; i++) lcd_DATA(pattern[i]); lcd_INST(0x80); //書き込み先をDDRAMに戻す } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
#pragma config FOSC = INTOSC // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin) #pragma config WDTE = OFF // Watchdog Timer Enable (WDT disabled) #pragma config PWRTE = OFF // Power-up Timer Enable (PWRT disabled) #pragma config MCLRE = ON // MCLR Pin Function Select (MCLR/VPP pin function is MCLR) #pragma config CP = OFF // Flash Program Memory Code Protection (Program memory code protection is disabled) #pragma config CPD = OFF // Data Memory Code Protection (Data memory code protection is disabled) #pragma config BOREN = ON // Brown-out Reset Enable (Brown-out Reset enabled) #pragma config CLKOUTEN = OFF // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin) #pragma config IESO = ON // Internal/External Switchover (Internal/External Switchover mode is enabled) #pragma config FCMEN = ON // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled) #pragma config WRT = OFF // Flash Memory Self-Write Protection (Write protection off) #pragma config PLLEN = ON // PLL Enable (4x PLL enabled) #pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset) #pragma config BORV = LO // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.) #pragma config LVP = OFF // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming) #include <xc.h> #include <stdio.h> #include "lcd.h" #define _XTAL_FREQ 500000 #define DEGREE 0 const char degree[8] = { //℃ 0b10000, 0b00110, 0b01001, 0b01000, 0b01000, 0b01001, 0b00110, 0b00000 }; void putch(char c){ lcd_DATA(c); } void Init(){ TRISA = 0; ANSELA = 0; TRISB = 0; ANSELB = 0; } void main(void) { Init(); lcd_Init(); lcd_DefineCharacter(DEGREE, degree); //アドレスDEGREE(0)に配列degreeのフォントデータ書込み printf("HelloWorld リケデン"); char t = 0; while(1){ lcd_SetCursor(1,0); printf("Temp : %3d%c", t, DEGREE); //lcd_DATA(DEGREE); //2022/9/5修正 不要のためコメントアウト t++; __delay_ms(1000); } } |
今回の記事は以上となります。最後までご覧いただきありがとうございました。
ちなみにこちらの記事ではちゃんと温度を測定しています。
整理されていてとても綺麗なプログラムありがとうございます。参考にさせて頂きました。
PIC16F1709にほとんどそのまま移植できたのですが、RA3が入力専用と言う事を忘れていて大分苦労しました。
貴プログラムの
printf(“Temp : %3d%c”, t, DEGREE);
lcd_DATA(DEGREE);
は「℃」が2度プリントされてしまうのですね。
コメントありがとうございます。
コメントを参考に記事(プログラム)を一部編集させていただきました。
当該サイトでLCDに表示が出ることを確認できました。
PicKit3を接続してプログラム書き込みでLCDに表示がでるのですが、電源のみに接続すると
何も表示されませんでした。
プログラムは、書き込まれているのに、何故電源のみでは表示(プログラム開始)されないのでしょうか。
余っているピンでLチカをさせてみるなど、PICが動いていることは確認していますか?
PICが動いている場合、電源に接続した状態でPICをリセットしてもLCDは起動しないでしょうか?
リセットで起動するなら、電源投入時の電圧が不安定などの原因が考えられます。
その場合LCD初期化前にPICをしばらくスリープさせると解決するかもしれません。
返信遅くなりすいません。
PIC側のGND配線に間違いがありました。
失礼しました。