作成日時:2015年06月08日 23時30分26秒
更新日時:2020年05月19日 08時45分59秒
この記事は9年ほど前に投稿されました。内容が古くなっている可能性がありますので更新日時にご注意ください。
前回まで、LEDや7セグLEDなどの制御をしてきましたが、やはり表示できる情報に限りが有るので、せめて簡単な文字くらいは表示したいものですね。しかし、数字以上のものとなるとLEDでは表示できません。Raspberry PiにはHDMIやコンポジット端子、ディスプレイ出力があるので、GUIを構築してやるというのも手ですが、そこまでは必要なく例えば日付や金額に毛が生えた程度の簡単な表示がしたい場合があります。
そんな時に活躍するのがキャラクタ液晶というアイテムです。
今回はそれを使ってみようと思います。
必要なもの
- 3.3Vで動作可能なHD44780互換のキャラクタ液晶
今回購入したのは16文字x2行の青背景白文字のもの。型式はTC1602E-13A - 半固定抵抗(コントラスト調整用 入手した液晶の仕様に合うもの)
キャラクタ液晶について
まず、液晶というものにはキャラクタ液晶とグラフィック液晶というものがあります。
例えばグラフィック液晶というのは、今目の前にある液晶ディスプレイみたいな、ドットが沢山並んでいて、そのドットに画像データを送る事によって文字や図形などを表示させている液晶のことです。
そしてキャラクタ液晶というのは、文字コードを液晶に転送してその文字コードに対応する文字を液晶に表示するタイプの液晶です。
キャラクタ液晶は基本的に英数とカナに数種類の記号しか表示できませんが、操作が簡単なので文字しか表示しなくてもいいものに多く使われています。例えばFAXとかプリンタみたいなものや、駐車場の精算機などに使われています。
キャラクタ液晶の多くは日立製のHD44780という制御ICか、その互換品が搭載されています。
このICはドットマトリクスのLCDコントローラでLCDドライバやバッファ、キャラクタジェネレーターなんかをまとめたICであり、80年代前半に日立が作りまして、バッファが80x8bitあって最大で80文字(40文字x2行)の表示が可能であり、基本的に1行8文字、16文字、40文字の製品が多く、1~4行表示できる製品があります。最小1行x8文字から最大4行x40文字というものが一般的で、4行x40文字はバッファが足りないので2つのICを使っているようです。
価格は1つ500円前後~数千円のものが多いようです。
表示できる文字
ICの仕様上、200種類前後の文字を表示できるものが多いみたいです。
表示方法はデータラインからパラレルでアドレスデータを送信するという方法で、基本的にこのアドレスはASCIIコードと殆ど同じ(一部オリジナル文字あり)で、8bitの表示エリアの残りの文字はカナデータが入っているモノが多いようです。(オリジナルのHD44780 日本語仕様がアスキー+カナ+αなのでそれに準拠しているものが多い。)
ちなみに私が入手した液晶ユニットのデータシートによると、文字コードは以下の通りでした。
勘のいい人は直ぐに気付いたと思いますが、JISコードの冒頭の1バイト領域とほぼ同じですね。
それに有ると便利な 千万円や矢印となっています。そういえばこの液晶チルダ(~)が矢印に置き換わっているんですね。
配線
製品によって何処のピンがどれに対応するかが違いますので、どう繋ぐかは液晶の仕様書とにらめっこしてください。なお、この液晶には8bitモードと4bitモードがあります。4bitモードの方が使うポートが4つも少ないので今回は4bitモード使用することにします。
Raspberry Pi 2のIO表ですが、実はずっとGPIO.xと表記されたポート以外は使えないと思い込んでいました。しかし、よく見ると他のポートにもきちんと番号が割り当てられていましてた。
今回は制御信号が3本、データが4本、LCD電源制御とバックライト電源制御(ラズパイの電源と連動させるなら不要)の計9本のIOピンにGND端子の合計10本必要になります。今回はCPU側を連続で3・5・7・9.11・13・15・19・21ピン(GPIO8・9・7・0・2・3・12・13 9ピンがGND)とLCDの電源として37・39ピン(GPIO25・GND)を使用しました。(ピンを離したのは液晶を安定させるため……と言う事にしておく。 ←本当はピンソケットをカットするとき間違えたから仕方なく)
キャラクタ液晶はパーツ屋で買った場合、このように簡単なデータシートと一緒に販売されているものが多いようですね。
本体。今回購入した液晶は、左側にデータ用のピンが、右側にバックライト用のピンがでていました。
こいつを
こうして
こうじゃ。
裏側。プラスティックメモリー見ながらやったらこんなgdgdした感じになってしまった。
ちなみに、ラズパイのGPIOピンの定格電流は50mAで、表示用の最大消費電力が25mA、バックライトが定格15mAなので、バックライトと電源を別のIOポートで制御するとかなりの余裕があるし、この製品はバックライトと本体電源を単一化できるので、単一化した場合は合計最大40mA、一応20%のマージンがあるのでGPIOポートから直で電源を取っているが、本当ならこの電源は別ラインから取るべきかと。もし何か製品を作る場合はその辺はしっかり設計してね。
液晶の制御方法
この液晶は以下の通りにコマンドを送信することで初期化します。
1.電源挿入
↓40ms待つ
2.8bitモードのコマンド送信
↓4.1ms待つ
3.再度8bitコマンドを送信
↓100μs待つ
4.8bitコマンド再送信
↓
5.4bitモードに設定
↓
6.行数とフォントを設定
↓
7.液晶の表示をオフにする
↓
8.表示データクリア
↓
9.カーソルと表示シフトの設定
↓
10.液晶表示オン
なお、4bitモード設定以降は、コマンドを4bitずつにわけて送信します。
よってコマンド送信は以下の処理をすることになります。
1.RS・R/W・E信号を0にする
↓60ns待つ
2.データ信号を0にする
↓40μs待つ
3.RS・R/W信号を送る
↓60ns待つ
4.E信号を送る
↓60ns待つ
5.上位4bit(本来のDB7~4で送るデータ)を送る
↓各コマンドの実行時間分待つ(処理時間を気にしないなら一番長いコマンドの実行時間待てばOK)
6.E信号を0にする
↓
7.1~4を繰り返す
↓
8.下位4bit(本来DB3~0で送る予定のデータ)を転送する
↓
9.E信号を0にする
つまりは、制御信号送信+4bit送信を2回繰り返すことになります。
よって実際にコーディングする場合は4bitを送信するコマンドを作り、そのコマンドを2回連続で実行する方がコードがスッキリすると思います。
コーディング
試しに液晶ディスプレイに数字の「0」を表示してみたいと思います。
まずは「stdio.h」「wiringPi.h」「string.h」をインクルードしてください。
(string.hは今回使わないのですがこのあと必要なので先に指定しておきます。)
次にデータ用4本、制御用3本、電源用2本の計9本のGPIOを指定します。
ここまでは前回の要領で行えます。私は以下の通りに設定しました。
このポートを全てOUTPUTで初期化します。
//LCDのピンを指定
// 4bit Mode
#define LCD_D7 13 // ここから
#define LCD_D6 12
#define LCD_D5 3
#define LCD_D4 2 // ここまでがデータ用
#define LCD_EN 0 // リード/ライト イネーブル信号
#define LCD_RW 7 // リード/ライト 選択信号
#define LCD_RS 9 // レジスタ選択信号
#define LCD_BK 8 // LCDのバックライト電源オンオフ用
#define LCD_ON 25 // LCDの電源オンオフ用
次にデータ転送用の命令を作ります。
int LCD_SET(int rs,int rw,int db7,int db6,int db5,int db4){
// 一旦全ポートに0を書き込む
digitalWrite(LCD_RW,0);
digitalWrite(LCD_RS,0);
digitalWrite(LCD_EN,0);
nanosleep(60);
digitalWrite(LCD_D7,0);
digitalWrite(LCD_D6,0);
digitalWrite(LCD_D5,0);
digitalWrite(LCD_D4,0);
usleep(40);
// データ転送
digitalWrite(LCD_RW,rw);
digitalWrite(LCD_RS,rs);
nanosleep(60);
digitalWrite(LCD_EN,1);
nanosleep(60);
digitalWrite(LCD_D7,db7);
digitalWrite(LCD_D6,db6);
digitalWrite(LCD_D5,db5);
digitalWrite(LCD_D4,db4);
digitalWrite(LCD_EN,0);
// 表示クリア、初期位置復帰命令の実行時間が
// 1.52msと長いので、RSとR/Wの数値が0の命令は
// 待ち時間を長く設定する。
if((rw+rs)==0)usleep(1520);
if((rw+rs)!=0)usleep(40);
}
液晶のタイミング仕様を参考に、このようにコーディングしました。(nanosleepでコンパイルエラーが発生する場合は、usleep()でμ秒単位で指定してください。 例:60ns=0.06μs)
この命令を使うときは以下のように転送します。
LCD_SET(RS信号,R/W切り替え,DB7,DB6,DB5,DB4);
このコマンドを実行することで、液晶に制御信号と4bitのデータが転送されます。
このLCDの制御は基本的に制御信号3bit+データ8bitなので、実際にはこのコマンドを2回実行する必要があります。
LCD_SET(RS信号,R/W切り替え,DB7,DB6,DB5,DB4);
LCD_SET(RS信号,R/W切り替え,DB3,DB2,DB1,DB0);
こうすることで、8bitのデータを送信することが出来ます。
最初の初期化作業の8bitモードに設定してから4bitモードにするまでが1回分で事足りる以外は全て2回コマンドを実行する必要があります。
次に作成したデータ転送コマンドを使いLCDの初期化作業に入ります。
int LCD_RES(void){
digitalWrite(LCD_ON,0); // LCD電源オフ
digitalWrite(LCD_BK,0); // LCDバックライトオフ
delay(1);
digitalWrite(LCD_ON,1); // LCD電源オン
digitalWrite(LCD_BK,1); // LCDバックライトオン
delay(40);
LCD_SET(0,0,0,0,1,1); // 8bitモードに設定
delay(4.1);
LCD_SET(0,0,0,0,1,1); // 8bitモードに設定
usleep(100);
LCD_SET(0,0,0,0,1,1); // 8bitモードに設定
LCD_SET(0,0,0,0,1,0); // 4bitモードに設定
LCD_SET(0,0,0,0,1,0);LCD_SET(0,0,1,0,0,0); // 2行 普通フォントに設定
LCD_SET(0,0,0,0,0,0);LCD_SET(0,0,1,0,0,0); // 液晶表示OFF
LCD_SET(0,0,0,0,0,0);LCD_SET(0,0,0,0,0,1); // データクリア
LCD_SET(0,0,0,0,0,0);LCD_SET(0,0,0,1,1,0); // カーソル右移動 左シフトに設定
LCD_SET(0,0,0,0,0,0);LCD_SET(0,0,1,1,0,0); // 液晶表示ON
}
基本的に一度リセットしてしまえば電源を切るまで初期化の必要はありませんが、メンテナンス性向上のため、別のコマンドにわけることにします。
はじめに液晶自体をリセットするために、一旦電源をオフにして1ms待ち、再度電源を投入し、40ms(メーカー指定値)待ったあと、初期化作業に入ります。
以上で基本的なコード作成は完了しました。
この段階でのソースコードはこんな感じになっていると思います。
#include <stdio.h>
#include <wiringPi.h>
#include <string.h>
//LCDのピンを指定
// 4bit Mode
#define LCD_D7 13 // ここから
#define LCD_D6 12
#define LCD_D5 3
#define LCD_D4 2 // ここまでがデータ用
#define LCD_EN 0 // リード/ライト イネーブル信号
#define LCD_RW 7 // リード/ライト 選択信号
#define LCD_RS 9 // レジスタ選択信号
#define LCD_BK 8 // LCDのバックライト電源オンオフ用
#define LCD_ON 25 // LCDの電源オンオフ用
int main(void){
// WiringPi初期化
wiringPiSetup();
// GPIOポートの設定
pinMode(LCD_D7,OUTPUT);
pinMode(LCD_D6,OUTPUT);
pinMode(LCD_D5,OUTPUT);
pinMode(LCD_D4,OUTPUT);
pinMode(LCD_EN,OUTPUT);
pinMode(LCD_RS,OUTPUT);
pinMode(LCD_RW,OUTPUT);
pinMode(LCD_ON,OUTPUT);
pinMode(LCD_BK,OUTPUT);
// 液晶リセット
LCD_RES();
}
int LCD_RES(void){
digitalWrite(LCD_ON,0); // LCD電源オフ
digitalWrite(LCD_BK,0); // LCDバックライトオフ
delay(1);
digitalWrite(LCD_ON,1); // LCD電源オン
digitalWrite(LCD_BK,1); // LCDバックライトオン
delay(40);
LCD_SET(0,0,0,0,1,1); // 8bitモードに設定
delay(4.1);
LCD_SET(0,0,0,0,1,1); // 8bitモードに設定
usleep(100);
LCD_SET(0,0,0,0,1,1); // 8bitモードに設定
LCD_SET(0,0,0,0,1,0); // 4bitモードに設定
LCD_SET(0,0,0,0,1,0);LCD_SET(0,0,1,0,0,0); // 2行 普通フォントに設定
LCD_SET(0,0,0,0,0,0);LCD_SET(0,0,1,0,0,0); // 液晶表示OFF
LCD_SET(0,0,0,0,0,0);LCD_SET(0,0,0,0,0,1); // データクリア
LCD_SET(0,0,0,0,0,0);LCD_SET(0,0,0,1,1,0); // カーソル右移動 左シフトに設定
LCD_SET(0,0,0,0,0,0);LCD_SET(0,0,1,1,0,0); // 液晶表示ON
}
int LCD_SET(int rs,int rw,int db7,int db6,int db5,int db4){
// 一旦全ポートに0を書き込む
digitalWrite(LCD_RW,0);
digitalWrite(LCD_RS,0);
digitalWrite(LCD_EN,0);
nanosleep(60);
digitalWrite(LCD_D7,0);
digitalWrite(LCD_D6,0);
digitalWrite(LCD_D5,0);
digitalWrite(LCD_D4,0);
usleep(40);
// データ転送
digitalWrite(LCD_RW,rw);
digitalWrite(LCD_RS,rs);
nanosleep(60);
digitalWrite(LCD_EN,1);
nanosleep(60);
digitalWrite(LCD_D7,db7);
digitalWrite(LCD_D6,db6);
digitalWrite(LCD_D5,db5);
digitalWrite(LCD_D4,db4);
digitalWrite(LCD_EN,0);
// 表示クリア、初期位置復帰命令の実行時間が
// 1.52msと長いので、RSとR/Wの数値が0の命令は
// 待ち時間を長く設定する。
if((rw+rs)==0)usleep(1520);
if((rw+rs)!=0)usleep(40);
}
このように何も表示されない状態になっていると思います。
それでは「0」を転送してみましょう。
先ほどのコードにある液晶リセットコマンド(LCD_RES)のあとに以下のコマンドを転送します。
// データ表示クリア
LCD_SET(0,0,0,0,0,0);LCD_SET(0,0,0,0,0,1);
これで液晶に何もない状態が出来ます。(上のリセットコマンドに含まれていますが、念のため再度クリアします。)
次にデータを転送します。コード表によると「0」の文字コードは「0011 0000」のようなので、以下の通りに送信します。
LCD_SET(1,0,0,0,1,1);LCD_SET(1,0,0,0,0,0);
さっきから転送コマンドを1行に書いていますが、好みにより2行にしても構いません。
これをコンパイルして実行するとこのような表示になるはずです。
このように比較的簡単に液晶に文字を表示させることが出来ました。
このあと他の文字を表示したり、2行目に改行したりしますが。長くなりましたのでとりあえず今回はここまでにします。以下に「0」を表示した時のソースコードを貼り付けておきます。
#include <stdio.h>
#include <wiringPi.h>
#include <string.h>
//LCDのピンを指定
// 4bit Mode
#define LCD_D7 13 // ここから
#define LCD_D6 12
#define LCD_D5 3
#define LCD_D4 2 // ここまでがデータ用
#define LCD_EN 0 // リード/ライト イネーブル信号
#define LCD_RW 7 // リード/ライト 選択信号
#define LCD_RS 9 // レジスタ選択信号
#define LCD_BK 8 // LCDのバックライト電源オンオフ用
#define LCD_ON 25 // LCDの電源オンオフ用
int main(void){
// WiringPi初期化
wiringPiSetup();
// GPIOポートの設定
pinMode(LCD_D7,OUTPUT);
pinMode(LCD_D6,OUTPUT);
pinMode(LCD_D5,OUTPUT);
pinMode(LCD_D4,OUTPUT);
pinMode(LCD_EN,OUTPUT);
pinMode(LCD_RS,OUTPUT);
pinMode(LCD_RW,OUTPUT);
pinMode(LCD_ON,OUTPUT);
pinMode(LCD_BK,OUTPUT);
// 液晶リセット
LCD_RES();
// データ表示クリア
LCD_SET(0,0,0,0,0,0);LCD_SET(0,0,0,0,0,1);
// 「0」の文字コードを転送
LCD_SET(1,0,0,0,1,1);LCD_SET(1,0,0,0,0,0);
}
int LCD_RES(void){
digitalWrite(LCD_ON,0); // LCD電源オフ
digitalWrite(LCD_BK,0); // LCDバックライトオフ
delay(1);
digitalWrite(LCD_ON,1); // LCD電源オン
digitalWrite(LCD_BK,1); // LCDバックライトオン
delay(40);
LCD_SET(0,0,0,0,1,1); // 8bitモードに設定
delay(4.1);
LCD_SET(0,0,0,0,1,1); // 8bitモードに設定
usleep(100);
LCD_SET(0,0,0,0,1,1); // 8bitモードに設定
LCD_SET(0,0,0,0,1,0); // 4bitモードに設定
LCD_SET(0,0,0,0,1,0);LCD_SET(0,0,1,0,0,0); // 2行 普通フォントに設定
LCD_SET(0,0,0,0,0,0);LCD_SET(0,0,1,0,0,0); // 液晶表示OFF
LCD_SET(0,0,0,0,0,0);LCD_SET(0,0,0,0,0,1); // データクリア
LCD_SET(0,0,0,0,0,0);LCD_SET(0,0,0,1,1,0); // カーソル右移動 左シフトに設定
LCD_SET(0,0,0,0,0,0);LCD_SET(0,0,1,1,0,0); // 液晶表示ON
}
int LCD_SET(int rs,int rw,int db7,int db6,int db5,int db4){
// 一旦全ポートに0を書き込む
digitalWrite(LCD_RW,0);
digitalWrite(LCD_RS,0);
digitalWrite(LCD_EN,0);
nanosleep(60);
digitalWrite(LCD_D7,0);
digitalWrite(LCD_D6,0);
digitalWrite(LCD_D5,0);
digitalWrite(LCD_D4,0);
usleep(40);
// データ転送
digitalWrite(LCD_RW,rw);
digitalWrite(LCD_RS,rs);
nanosleep(60);
digitalWrite(LCD_EN,1);
nanosleep(60);
digitalWrite(LCD_D7,db7);
digitalWrite(LCD_D6,db6);
digitalWrite(LCD_D5,db5);
digitalWrite(LCD_D4,db4);
digitalWrite(LCD_EN,0);
// 表示クリア、初期位置復帰命令の実行時間が
// 1.52msと長いので、RSとR/Wの数値が0の命令は
// 待ち時間を長く設定する。
if((rw+rs)==0)usleep(1520);
if((rw+rs)!=0)usleep(40);
}
データ転送部について
実際に液晶のデータ転送を行っている部分ですが、この記事では4bitを転送するコマンドを2回実行して転送していますが、以下のコードに変更することによってコマンドを1回実行するだけで良くなります。ただし、ごくたまに初期化失敗や表示が乱れる場合がありますので、確実性をとるなら4bit2回のほうが良さそうです。
int LCD_SET(int rs,int rw,int db7,int db6,int db5,int db4,int db3,int db2,int db1,int db0){
// 一旦全ポートに0を書き込む
digitalWrite(LCD_RW,0);
digitalWrite(LCD_RS,0);
digitalWrite(LCD_EN,0);
nanosleep(60);
digitalWrite(LCD_D7,0);
digitalWrite(LCD_D6,0);
digitalWrite(LCD_D5,0);
digitalWrite(LCD_D4,0);
usleep(40);
// データ転送
digitalWrite(LCD_RW,rw);
digitalWrite(LCD_RS,rs);
nanosleep(60);
digitalWrite(LCD_EN,1);
nanosleep(60);
digitalWrite(LCD_D7,db7);
digitalWrite(LCD_D6,db6);
digitalWrite(LCD_D5,db5);
digitalWrite(LCD_D4,db4);
digitalWrite(LCD_EN,0);
nanosleep(1000);
digitalWrite(LCD_EN,1);
nanosleep(60);
digitalWrite(LCD_D7,db3);
digitalWrite(LCD_D6,db2);
digitalWrite(LCD_D5,db1);
digitalWrite(LCD_D4,db0);
digitalWrite(LCD_EN,0);
// 表示クリア、初期位置復帰命令の実行時間が
// 1.52msと長いので、RSとR/Wの数値が0の命令は
// 待ち時間を長く設定する。
if((rw+rs)==0)usleep(1520);
if((rw+rs)!=0)usleep(40);
}
なお、コマンドはこうなります。
LCD_SET(RS信号,R/W切り替え,DB7,DB6,DB5,DB4);
LCD_SET(RS信号,R/W切り替え,DB3,DB2,DB1,DB0);
↓
LCD_SET(RS信号,R/W切り替え,DB7,DB6,DB5,DB4,DB3,DB2,DB1,DB0);
[…] 前回、キャラクタ液晶に文字を表示する作業をしてみました。 今回はそこからちょっと踏み込んでみましょう。 […]
[…] sakura87.net 2 usersRaspberry Pi 2でキャラクタ液晶の制御(1) – 桜の… […]
[…] →ラズベリーパイで動作させた記事が見つかっても、I2C接続ではない(例:Raspberry Pi 2でキャラクタ液晶の制御、Raspberry PiとキャラクタLCDで遊んでみた)。 […]
[…] 前回、キャラクタ液晶に文字を表示する作業をしてみました。 今回はそこからちょっと踏み込んでみましょう。 […]