Arduinoで実験 (キャラクタ液晶)

2013/04/23


◆ キャラクタ液晶を使う

LEDをチカチカさせたり、7セグで数字の表示でも表現方法に限界があり、誰が見ても意味の分かるもの物と言えば、やっぱり文字の表示でしょう。

本稿ではキャラクタ液晶ディスプレイ(以下「CLCD」という。)に絞ってまとめてゆきます。

CLCDは文字表示に特化した液晶ディスプレイで、CLCDに内蔵されたコントローラへの命令で文字を表示します。

コントローラでは、日立の HD44780 というLCDコントローラが実質、業界標準となっているようでコマンド互換でいろんなコントローラが制御出来てしまいます。 つまり、液晶ディスプレイごとのプログラムを作らなくても動作します。

コントローラへは DB0~DB7 のシリアルバスでデータを設定し、Enable 端子をラッチすることで命令を確定します。

RS はデータが制御コマンドなのかデータであるのかを指定し、 R/W データを読み取るのか書き出すのかを指定します。 CLCDでは使用するデータ量が多くないのでデバイスからデータを読み込む事はほとんどないので R/W は LOW (GND)に接続して、接続ピンを節約するのが一般的です。 データシートで 読み込み時は HIGH となっている場合は、電源に接続しておきます。

HD44780互換とうたわれていてもコマンド互換の場合が多く、ピン配置までが互換であるとは限らず、必ずデータシートで確認しなければなりません。 時には拡張コマンドとして独自の機能を持つものもあります。

今回実験で使用する CLCD は秋月で購入できる 超小型キャラクタディスプレイモジュール(SD1602)を使用します。 このモジュールは超小型と歌う割には小さくなく、程よい大きさ(特に横が Arduino UNO と近い長さ)なので愛用者も多いようです。

ピン 名称 内容 接続先
1 Vss GND GND
2 Vdd 電源 +5V
3 Vo コントラスト  
4 RS レジスタ選択 2
5 R/W 読み取り/書き込み
3
6 Enable イネーブル 4
7 DB0 データバス:本実験では 4ビットモードを使用するので
DB4~DB7のみを使用します。
未使用
8 DB1
9 DB2
10 DB3
11 DB4 8
12 DB5 9
13 DB6 10
14 DB70 11
15 BL-A (+) バックライト、同シリーズでも個のピンが逆に配置されている物もあるので注意が必要です。  
16 BL-K (-) GND

実験に使用したもの

種別 定格・番 個数 備考
マイコン Arduino UNO 1  
キャラクタ液晶 SD1602H 1  
抵抗 100Ω 1 バックライト用
抵抗 3.3kΩ 1 コントラスト用

コントラストは温度や使用環境で変わってくるので、半固定抵抗を用いて調節できる方が望ましい。 他のサイトで色々見つかるので本サイトでは触れないことにする。

上記のピン配置は特に意味もなく、 Fretzing で書きやすかっただけです。


◆ ライブラリを使用してみる

Arduino IDE のライブラリには既に LiquidCrystal.h があり HD44780 互換 CLCD の制御が簡単に行えます。

早速、「HELLO WORLD」を表示してみます。

//
// 05-01 キャラクタ液晶ディスプレイの表示実験スケッチ
//

// ピン配置
#define pin_RS 8
#define pin_RW 9
#define pin_E 10
#define pin_D4 2
#define pin_D5 3
#define pin_D6 4
#define pin_D7 5

// ライブラリの初期化
#include <LiquidCrystal.h>
LiquidCrystal myLCD(pin_RS, pin_RW, pin_E, pin_D4, pin_D5, pin_D6, pin_D7);

void setup() {
  myLCD.begin(2, 16); // 表示範囲の設定

  myLCD.clear(); // 画面の消去
  myLCD.print("HELLO"); // 文字列の表示

  myLCD.setCursor(5, 1); // カーソル位置の指定
  myLCD.print("WORLD");
}

void loop() {
}

ライブラリの使用方法は特に解説しないので、カーソルの表示、点滅等の命令あるので Arduino 日本語リファレンス を参考にしてください。


◆ ライブラリを使用せずに制御してみる。

ライブラリを使用して開発効率を上げる方が理に適ってはいるのだがやっぱり自分の手で制御してみたいと思うのも当然だ。 今後、さまざまなデバイスを制御していきたいと思うのなら通っておいた方がよい道である。

Arduino の各ポートに接続された線では HIGH か LOW のどちらかしか判別できない。 この線が 4本あれば、4ビットのデータとして扱え 16までの数値を判別できる。 この信号が DB4~DB7 だ。 さらに、データであるか命令であるかを判別する RS 信号を加え 5ビットの制御信号がある。 R/W 信号は書き込み専用となるので LOW のみとなる(アクティブ・ロウ)。

4ビットのデータバスと RS 信号を設定したら、CLCDのコントローラに「今の信号を読み取れ!!」と指示を出す。 これがイネーブル信号です。 パソコンでエンターキーを押すような感じだと思ってください。

コントローラは LOW から HIGH になった時(立ち上がり)、又は HIGH から LOW になった時(立下り)いずれかで時点の信号を読み取り、命令/データとして処理します。

データバスが 8本あり、本来 8ビットの命令/データを転送するところを、上位 4ビットと下位 4ビットの 2回に分けることができる。 これが 4ビットモードで、限られたマイコンのポートを節約するための機能であり、倍の転送時間がかかるが、CLCDで扱うデータ量はグラフィック液晶に比べ格段に少ないので 処理速度を気にする必要が無く、デメリットよりもメリットの方がはるかに多い。

他にビジーフラグというものがあり、マイコンからの命令を受けたコントローラが、今は忙しいから命令を受け付けることができない旨を通知するフラグで、マイコン側はコントローラが命令の受信可能な状態がどうかを調べて命令を送信する必要がある。 が、実際データシート上でこの命令なら○○ms 位かかるというのが解るので、それ以上の余裕をもった時間待機しておけば、ほぼ問題なく動作する。

とにかく、キャラクタ液晶では速度が遅くなるといっても、人間が体感できるような速度ではないので、速度よりも処理の簡素化やポートの節約を優先した方がよい。

では、実際の処理をひとつずつプログラムしてゆきます。 最初に 各ポートに命令/データをセットして送信する関数とイネーブル信号を発行する関数を作成します。

//
// 05-02 命令/データ書き込み関数とイネーブル信号発行関数
//

// イネーブル信号発行関数
void eLatch(void) {
  digitalWrite(pin_E, LOW);  // イネーブル信号を一瞬だけ発行
  delayMicroseconds(1);
  digitalWrite(pin_E, HIGH);
  delayMicroseconds(1);
  digitalWrite(pin_E, LOW);
  delayMicroseconds(100);
}

// 命令/データ書き込み関数
void writeBUS(boolean rs, byte b) {
  //  rs = 0 で命令/ rs = 1 でデータ
  digitalWrite(pin_RS, rs);

  //  b 8ビットの上位 4ビットをポートにセット
  (b & 0b10000000)? digitalWrite(pin_D7, HIGH) : digitalWrite(pin_D7, LOW);  //  DB7 をセット
  (b & 0b01000000)? digitalWrite(pin_D6, HIGH) : digitalWrite(pin_D6, LOW);  //  DB6 をセット
  (b & 0b00100000)? digitalWrite(pin_D5, HIGH) : digitalWrite(pin_D5, LOW);  //  DB5 をセット
  (b & 0b00010000)? digitalWrite(pin_D4, HIGH) : digitalWrite(pin_D4, LOW);  //  DB4 をセット
  //上記の表記方法を書き換えると下の様になる
  // if(b | 0x10) {
  //   digitalWrite(pin_D4, HIGH);
  // } else {
  //   digitalWrite(pin_D4, LOW);
  // }  

  eLatch();  // イネーブルを発行

  //  b 8ビットの下位 4ビットをポートにセット
  (b & 0b00001000)? digitalWrite(pin_D7, HIGH) : digitalWrite(pin_D7, LOW);  //  DB7 をセット
  (b & 0b00000100)? digitalWrite(pin_D6, HIGH) : digitalWrite(pin_D6, LOW);  //  DB6 をセット
  (b & 0b00000010)? digitalWrite(pin_D5, HIGH) : digitalWrite(pin_D5, LOW);  //  DB5 をセット
  (b & 0b00000001)? digitalWrite(pin_D4, HIGH) : digitalWrite(pin_D4, LOW);  //  DB4 をセット

  eLatch();  // イネーブルを発行

}

上記の関数を作成してもまだ確認出来ないので、CLCD の初期化関数を作成する。

初期化の方法はデータシートに記載されているので、初期化フローを確認しておく。

//
// 05-03 キャラクタ液晶の初期化
//

// 初期化関数
void initLCD(void) {
  delay(50);
  digitalWrite(pin_RS, LOW);  // 各ポートを一応 LOW にしておく
  digitalWrite(pin_RW, LOW);
  digitalWrite(pin_E,  LOW);


// 0x03 を3回送信して一旦8ビットモードにする
  writeBUS(0, 0x03);
  delay(5);
  writeBUS(0, 0x03);
  delay(5);
  writeBUS(0, 0x03);
  delay(5);

// 0x20 を送信して4ビットモードにする
  writeBUS(0, 0x20);

// ファンクション・セット (Function Set)
//   bit5 = 1
//   bit4 = DL  モードセレクト     DL=0 4ビットモード、DL=1 8ビットモード
//   bit3 = N   ディスプレイの行数 N=0 1行、N=1 2行
//   bit2 = F   フォントサイズ     F=0 5x8ドット、F=1 5x11ドット
  writeBUS(0, 0x28);  // 0b 00101000

// ディスプレイ on/off コントロール (Display on/off control)
//   bit3 = 1
//   bit2 = D ディスプレイ  D=0 オフ、D=1 オン
//   bit1 = C カーソル      C=0 非表示、C=1 表示
//   bit0 = B カーソル点滅  B=0 なし、B=1 あり
  writeBUS(0, 0x0c);  // 0b 00001100

// ディスプレイ消去 (Clear display)
//   bit0 = 1
  writeBUS(0, 0x01);  // 0b 00000001
  delay(2);

// エントリーモード (Entry mode)
//   bit2 = 1
//   bit1 = I/D カーソルインクリメント I/D=0 しない、I/D=1 する
//   bit0 = S   移動   S=0 しない、S=1 する
  writeBUS(0, 0x06);  // 0b 00000110
}

以上で初期化までの関数を作成しました。 次は作成した関数を動作させてみます。 loop() 以降に上記の関数を追加してください。

//
// 05-04 作成した関数の動作実験スケッチ
//

#define pin_RS 8
#define pin_RW 9
#define pin_E 10
#define pin_D4 2
#define pin_D5 3
#define pin_D6 4
#define pin_D7 5

void setup() {
// ポートの設定
  pinMode(pin_RS, OUTPUT);
  pinMode(pin_RW, OUTPUT);
  pinMode(pin_E,  OUTPUT);
  pinMode(pin_D4, OUTPUT);
  pinMode(pin_D5, OUTPUT);
  pinMode(pin_D6, OUTPUT);
  pinMode(pin_D7, OUTPUT);

  initLCD();

  writeBUS(1, 'H');  // データを書き込み
  writeBUS(1, 'e');
  writeBUS(1, 'l');
  writeBUS(1, 'l');
  writeBUS(1, 'o');
}

void loop() {
}

// 以下に上記の関数を追加する。
// void eLatch()
// void writeBUS()
// void initLCD()

◆ キャラクタ液晶用の関数を作成する。

CLCDを使ううえでいくつかの関数を作成してきます。

カーソルのオン/オフと点滅のオン/オフを個別の関数にするには現在の設定状況を知る必要があるので CLCD から読み込むかグローバル変数で状態の値を保持する必要があります。 正直どちらも面倒なのでカーソルと点滅はひとつの関数で同時に設定します。

//
// 05-05 追加の関数群
//

// 画面の消去とカーソル位置を初期位置へ戻す
void lcdCLR(void) {
  writeBUS(0, 0x02);  // カーソル位置をホームへ戻す
  writeBUS(0, 0x01);  // 画面消去
  delay(2);
}

// カーソル位置を指定
void lcdLOCATE(byte col, byte raw) {
  byte addr = 0x80;  // カーソル指定の場合 bit7=1
  (col == 1)? addr += col + 0x40 : addr += col;
  writeBUS(0, addr);
}

// 文字列の表示
void lcdPRINT(char *c) {
  while(*c) {
    writeBUS(1, *c++);
  }
}

// ディスプレイコントロールの指定
void lcdDISP(boolean d, boolean c, boolean b) {
  byte dat = 0x08;
  if (d) dat += 0x04;
  if (c) dat += 0x02;
  if (b) dat += 0x01;
  writeBUS(0, dat);
  delay(2);
}

// ファンクションセットの指定
void lcdFUNC(boolean n, boolean f) {
  byte dat = 0x20;
  if (n) dat += 0x08;
  if (f) dat += 0x04;
  writeBUS(0, dat);
}

// エントリーモードの設定
void lcdENTRY(boolean id, boolean s) {
  byte dat = 0x04;
  if (id) dat += 0x02;
  if (s)  dat += 0x01;
  writeBUS(0, dat);
}


// ディスプレイシフト命令
void lcdSHIFT(boolean sc, boolean rl) {
  byte dat = 0x10;
  if (sc) dat += 0x08;
  if (rl) dat += 0x04;
  writeBUS(0, dat);
}

上記命令を使用したスケッチを作ります。 今までのスケッチに上記関数を追加して、次の loop() を書き換えます。

//
// 05-06 確認用スケッチ
//

void loop() {
// 基本の表示
  lcdCLR();
  lcdPRINT("TEST");
  delay(3000);

// ディスプレイの表示/非表示
  lcdDISP(0,0,0);
  delay(3000);
  lcdDISP(1,0,0);
  delay(3000);

// カーソルの表示/非表示と点滅
  lcdDISP(1,1,0);
  delay(3000);
  lcdDISP(1,1,1);
  delay(3000);
  lcdDISP(1,1,0);
  delay(3000);

// ディスプレイシフト(カーソルのみ)
  lcdCLR();
  lcdPRINT("TEST");
  delay(1000);
  for (int i=0; i<5; i++) {
    lcdSHIFT(0,1);
    delay(1000);
  }

// ディスプレイシフト(画面追従)
  lcdCLR();
  lcdPRINT("TEST");
  delay(1000);
  for (int i=0; i<5; i++) {
    lcdSHIFT(1,1);
    delay(1000);
  }

// 1行モードとラージフォント
  lcdCLR();
  lcdFUNC(0, 1);
  for (int i=0; i<20; i++) {
    writeBUS(1, i + 0x41);
    delay(1000);
  }
  lcdFUNC(1, 0);

}

エントリーモードの画面追従ビットはカーソルの自動インクリメントでもあるので使用する場合は特に気をつけないと、想定した動作にならない事があります。


◆ ユーザー定義文字の作成

実験で使用した CLCD ではアスキーコード表のコントロールコード部分 0x00 ~ 0x07 の7文字分をユーザー定義文字として使用することができます。

登録にはユーザー定義文字のアドレスと文字データが必要です。

CGRAM アドレスは bit3 ~ bit5 で指定するので 8倍かけます。

画面の一文字は横5×縦8で構成されているので横の5ドットを1バイトとし、8バイトのデータを登録することにより1文字を登録します。

「月」のデータを作成すると以下のようになります。

4 3 2 1 0 BIN HEX
□■■■■ 0 1 1 1 1 00001111 0F
□■□□■ 0 1 0 0 1 00001001 09
□■■■■ 0 1 1 1 1 00001111 0F
□■□□■ 0 1 0 0 1 00001001 09
□■■■■ 0 1 1 1 1 00001111 0F
□■□□■ 0 1 0 0 1 00001001 09
■□□□■ 1 0 0 0 1 00010001 11
□□□□□ 0 0 0 0 0 00000000 00
//
// 05-07 ユーザー定義文字の実験スケッチ
//

void loop() {
  lcdCLR();
  setCG(0,0x0f,0x09,0x0f,0x09,0x0f,0x09,0x11,0x00);
  writeBUS(0);

  while(1);
}

void setCG(byte num, byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7, byte b8) {
  num = num * 8 + 0x40;
  writeBUS(0, num);
  writeBUS(1, b1);
  writeBUS(1, b2);
  writeBUS(1, b3);
  writeBUS(1, b4);
  writeBUS(1, b5);
  writeBUS(1, b6);
  writeBUS(1, b7);
  writeBUS(1, b8);

  writeBUS(0, 0x02);  // カーソルがCGRAMを指しているので一度カーソル位置をホームへ戻す
}