さくらのジャンク箱ロゴ

Sakura87がお届けするPCや電子工作と写真のブログ @16周年

Arduino Unoでキャラクタ液晶制御

この記事は5年ほど前に投稿されました。内容が古くなっている可能性がありますので更新日時にご注意ください。

IMG_7116_20151218

IO関係は一通り制御出来たので次はキャラクタ液晶を制御してみたいと思います。(Raspberry Piで行ったとおりに行くなら次は1桁の7セグLEDですが、多分殆どあのコードママで動くので飛ばします。)

キャラクター液晶の説明とかそういうの

キャラクタ液晶(モジュール)とは、マイコンを内蔵していて文字コードをマイコンに転送することで文字(基本的には英数字とカナ)を表示することの出来る液晶のことで。マイコンを使って簡単に文字情報を表示することが出来ます。詳しくは前回Raspberry Piにて制御したものに書いてあるのでそれを見てください。

用意するもの

  • パラレル接続のキャラクタ液晶(コントローラがHD44780互換品・5V仕様)
    今回は秋月電子の定番品であるSC1602BSLBを使用。
  • 10kΩの可変抵抗器(液晶コントラスト調節用 若しくは5kΩ程度の抵抗)
    今回は手持ちの4.7kΩの抵抗器を液晶3番ピンとGNDの間に挟んで使用。
  • 100Ωの抵抗(バックライト点灯用 秋月で買ったらついてくる)

配線

秋月で買った液晶についてきたソケットを取り付けました。

IMG_7117_20151218

リード線を使って延長したり、液晶方向に取り付けるという手もありますが。
今後何かに組み込むことを考慮して裏側に取り付けました。

液晶の仕様書を確認して、以下の説明のように接続します。

液晶側 Arduino側 説明
名称 ピン番号※ ピン番号 役割
Vcc(+) 1 +5V 電源入力
Vss(-) 2 GND GND
Vo 3 抵抗→GND コントラスト調整
RS 4 3 レジスタ選択
R/W 5 GND 入出力切り替え
E 6 2 イネーブル信号
DB0 7 ※使用しません
DB1 8
DB2 9
DB3 10
DB4 11 4 データ用信号
DB5 12 5
DB6 13 6
DB7 14 7

コーディング

基本的には前回Raspberry Piでやったものとほとんど同じ方法で処理できると思います。

// IOピンの設定
#define RSPin 3 // レジスタ選択
#define EnPin 2 // イネーブル信号

// データバス(DB7-4の4bit制御)
#define DB7Pin 7
#define DB6Pin 6
#define DB5Pin 5
#define DB4Pin 4

// 液晶の文字数設定(Chr:1行の文字数 Lin:行数)
#define LcdChr 16
#define LcdLin 2

ピン番号と液晶の仕様を設定します。
今回は4bitモードで使用します。

// プロトタイプ宣言
short LcdCmd(unsigned char);  // 8bitコマンド用
short LcdCmd4(unsigned char); // 4bitコマンド用
short LcdData(unsigned char); // データ転送用(8bit)
int TextSend(int, char);      // テキスト転送用(文字コードの関係で英文のみ)
short LcdSend(unsigned char); // 実際にデータを転送する関数
void LcdInit(void);           // 液晶初期化命令

変数のプロトタイプを記述します。今回作成する命令は上記の6つで実際に制御に使用するものはLcdCmd/LcdCmd4/LcdData/TextSend/LcdInitの5つです。

次に実際に液晶にデータを転送する関数を作ります。
プロトタイプを宣言しているので関数の位置が前後しても問題無いです。

// 実際に液晶にデータを転送するための命令
// 上位4bitを転送する。下位ビットは無視される。
short LcdSend(unsigned char Data) {
	
  // 念のため一旦出力を0に。
  digitalWrite(DB7Pin, 0);
  digitalWrite(DB6Pin, 0);
  digitalWrite(DB5Pin, 0);
  digitalWrite(DB4Pin, 0);
  delayMicroseconds(1);
  
  // 上位4bitのみ転送
  digitalWrite(DB7Pin, (Data >> 7) & 0x01);
  digitalWrite(DB6Pin, (Data >> 6) & 0x01);
  digitalWrite(DB5Pin, (Data >> 5) & 0x01);
  digitalWrite(DB4Pin, (Data >> 4) & 0x01);

  // パルスを発生させ40μ秒待つ
  delayMicroseconds(1);
  digitalWrite(EnPin, 0);
  delayMicroseconds(1);
  digitalWrite(EnPin, 1);
  delayMicroseconds(1);
  digitalWrite(EnPin, 0);
  delayMicroseconds(40);

  return 0;

}

前回Raspberry Piでの制御で一旦データビットを0にしないとどうもうまく制御できなかったことがあったため、今回も最初に一度データビットをゼロにします。その後に転送されてきたデータの上位ビットを抜き出して転送。下位ビットは破棄します。転送後パルスを発生させて終わり。

次にコマンド用命令を作ります。4bit初期化時につかう命令と通常コマンド用の8bit命令を作ります。

// 4bitコマンド用転送命令
// RS信号を0に設定してデータを転送するだけ。
// コマンドは上位ビットのみ有効
// Ex. 例えば1001というコマンドなら0x90とする。
//   下位の0は無視される。
short LcdCmd4(unsigned char Data) {

  digitalWrite(RSPin, 0);
  delayMicroseconds(1);
  LcdSend(Data);

}

// 8bitコマンド用転送命令
// 最初に上位ビットを転送して、その後上詰めにして
// 下位ビットを転送している。
short LcdCmd(unsigned char Data) {
  digitalWrite(RSPin, 0);
  delayMicroseconds(1);
  LcdSend(Data);
  LcdSend(Data << 4);
  
  // コマンドが0x00~0x02までなら2ms待つ
  // Clear DisplayとCursor At Homeが処理時間が長い
  // ことへの対処。
  if(Data<0x03) delayMicroseconds(2000);
}

RSピンを0に設定して、4bit用命令の場合はそのまま渡されたデータを転送して終わりです。
8bitの場合は最初に4bitと同様に転送し、その後すぐに4bitシフトして転送します。

そして8bit命令の場合は画面クリアとカーソルホーム復帰の命令が処理時間が長いので追加で2000μ秒(2ms)待ちます。

次にデータ転送命令を作ります。

// データ転送用命令。
// 転送方法は先程のLcdCmdと同様。
short LcdData(unsigned char Data) {

  digitalWrite(RSPin, 1);
  delayMicroseconds(1);
  LcdSend(Data);
  LcdSend(Data << 4);

}

基本的には上のコマンド転送命令と同じです。RS信号を1にするだけです。

次にテキスト転送命令を作ります。

int TextSend(int Line, char *Data) {
  LcdCmd(0x40 + (0x40 * Line));
  while (*Data) LcdData(*Data++);

}

アドレス指定命令は0x80ですが、1行1文字目のアドレスが0x00、2行1文字目が0x40なので、それを利用して計算でアドレスを指定します。

その後に転送されたテキストデータを転送します。文字列(ASCIIZ)の末端はNULL(0x00)であることと、Whileの値が真の場合に処理を続ける。0は偽を表すという特性を利用したよくある手法を使って文字がなくなるまで繰り返してデータを転送します。はみ出し処理とか文字消しなどはしていないので転送時に適当に調整してください。

なお、今回のテキスト転送命令は、ArduinoIDEの文字コードがUTF-8固定なので使用できる文字は英数字のみとなっております。ただし、特に文字種の制限はしていませんので。文字コードを直接指定してあげればカタカナや用意されている記号を表示させること自体は可能です。

準備ができたので初期化命令を書きます。

// 液晶初期化命令
// 最初に1回だけ実行する
void LcdInit(void) {

  // 必要なピンを出力に設定
  pinMode(RSPin , OUTPUT);
  pinMode(EnPin , OUTPUT);
  
  pinMode(DB7Pin, OUTPUT);
  pinMode(DB6Pin, OUTPUT);
  pinMode(DB5Pin, OUTPUT);
  pinMode(DB4Pin, OUTPUT);

  delay(100); // 保険で100ms待つ

  // 4bitモードに設定する
  LcdCmd4(0x30);
  delay(5);
  LcdCmd4(0x30);
  delay(5);
  LcdCmd4(0x30);
  delayMicroseconds(150);
  LcdCmd4(0x20);
  // 4bit化ここまで

  // 液晶初期化
  LcdCmd(0x28);  // 4bitモード フォント 行数の設定
  LcdCmd(0x08);  // ディスプレイオフ
  LcdCmd(0x40);  // エントリーモードの設定
  LcdCmd(0x0c);  // ディスプレイオン カーソルなし
  LcdCmd(0x06);  // インクリメントモードに設定
  LcdCmd(0x01);  // 表示初期化
  LcdCmd(0x02);  // カーソル初期位置

}

IOピンを出力で初期化して、初期化コマンドを仕様書通りに転送するだけです。
今回は分けましたが、そこまで長いコードではないのでsetup関数の中に入れてしまってもいいかもしれません。

setup関数の中身

void setup() {
  // put your setup code here, to run once:
  delay(100); //電源投入後100ms待つ(保険)
  
  LcdInit();  // 初期化命令

  // 文字列転送
  TextSend(1, "ArduinoUno Rev.3");
  TextSend(2, "**Sakura87.net**");

}

電源投入後100ミリ秒待ち、初期化命令を呼び出したあと文字列転送命令で文字を表示しています。
文字列転送命令の内容を変えることで表示する文字を変えることが出来ます。

なお、今回はloop関数は使用していません。
コレを利用して液晶に例えば温度センサーを使って温度を表示する時などには使用します。

動作例

電源を入れた直後

IMG_7131_20151220

上記のプログラムを走らせた状態

IMG_7133_20151220

プログラム全体

// IOピンの設定
#define RSPin 3 // レジスタ選択
#define EnPin 2 // イネーブル信号

// データバス(DB7-4の4bit制御)
#define DB7Pin 7
#define DB6Pin 6
#define DB5Pin 5
#define DB4Pin 4

// 液晶の文字数設定(Chr:1行の文字数 Lin:行数)
#define LcdChr 16
#define LcdLin 2

// プロトタイプ宣言
short LcdCmd(unsigned char);  // 8bitコマンド用
short LcdCmd4(unsigned char); // 4bitコマンド用
short LcdData(unsigned char); // データ転送用(8bit)
int TextSend(int, char);      // テキスト転送用(文字コードの関係で英文のみ)
short LcdSend(unsigned char); // 実際にデータを転送する関数
void LcdInit(void);           // 液晶初期化命令


void setup() {
  // put your setup code here, to run once:
  delay(100); //電源投入後100ms待つ(保険)
  
  LcdInit();  // 初期化命令

  // 文字列転送
  TextSend(1, "ArduinoUno Rev.3");
  TextSend(2, "**Sakura87.net**");

}

// 今回ループは使用しない
void loop() {}


// 液晶初期化命令
// 最初に1回だけ実行する
void LcdInit(void) {

  // 必要なピンを出力に設定
  pinMode(RSPin , OUTPUT);
  pinMode(EnPin , OUTPUT);
  
  pinMode(DB7Pin, OUTPUT);
  pinMode(DB6Pin, OUTPUT);
  pinMode(DB5Pin, OUTPUT);
  pinMode(DB4Pin, OUTPUT);

  delay(100); // 保険で100ms待つ

  // 4bitモードに設定する
  LcdCmd4(0x30);
  delay(5);
  LcdCmd4(0x30);
  delay(5);
  LcdCmd4(0x30);
  delayMicroseconds(150);
  LcdCmd4(0x20);
  // 4bit化ここまで

  // 液晶初期化
  LcdCmd(0x28);  // 4bitモード フォント 行数の設定
  LcdCmd(0x08);  // ディスプレイオフ
  LcdCmd(0x40);  // エントリーモードの設定
  LcdCmd(0x0c);  // ディスプレイオン カーソルなし
  LcdCmd(0x06);  // インクリメントモードに設定
  LcdCmd(0x01);  // 表示初期化
  LcdCmd(0x02);  // カーソル初期位置

}

// 4bitコマンド用転送命令
// RS信号を0に設定してデータを転送するだけ。
// コマンドは上位ビットのみ有効
// Ex. 例えば1001というコマンドなら0x90とする。
//   下位の0は無視される。
short LcdCmd4(unsigned char Data) {

  digitalWrite(RSPin, 0);
  delayMicroseconds(1);
  LcdSend(Data);

}

// 8bitコマンド用転送命令
// 最初に上位ビットを転送して、その後上詰めにして
// 下位ビットを転送している。
short LcdCmd(unsigned char Data) {
  digitalWrite(RSPin, 0);
  delayMicroseconds(1);
  LcdSend(Data);
  LcdSend(Data << 4);
  
  // コマンドが0x00~0x02までなら2ms待つ
  // Clear DisplayとCursor At Homeが処理時間が長い
  // ことへの対処。
  if(Data<0x03) delayMicroseconds(2000);


}

// データ転送用命令。
// 転送方法は先程のLcdCmdと同様。
short LcdData(unsigned char Data) {

  digitalWrite(RSPin, 1);
  delayMicroseconds(1);
  LcdSend(Data);
  LcdSend(Data << 4);

}

// 実際に液晶にデータを転送するための命令
// 上位4bitを転送する。下位ビットは無視される。
short LcdSend(unsigned char Data) {
	
  // 念のため一旦出力を0に。
  digitalWrite(DB7Pin, 0);
  digitalWrite(DB6Pin, 0);
  digitalWrite(DB5Pin, 0);
  digitalWrite(DB4Pin, 0);
  delayMicroseconds(1);
  
  // 上位4bitのみ転送
  digitalWrite(DB7Pin, (Data >> 7) & 0x01);
  digitalWrite(DB6Pin, (Data >> 6) & 0x01);
  digitalWrite(DB5Pin, (Data >> 5) & 0x01);
  digitalWrite(DB4Pin, (Data >> 4) & 0x01);

  // パルスを発生させ40μ秒待つ
  delayMicroseconds(1);
  digitalWrite(EnPin, 0);
  delayMicroseconds(1);
  digitalWrite(EnPin, 1);
  delayMicroseconds(1);
  digitalWrite(EnPin, 0);
  delayMicroseconds(40);

  return 0;

}

// テキスト転送命令
// この命令を使用して文字列を表示できる
// ただし、ArduinoIDEの文字コードがUTF-8固定で
// この変数は特に変換していないので半角カナは使えない。
// よってアスキーコード前方127文字分のみ使用可能。

// Line→ 文字を出力する行数
// Data→ 出力される文字数

// 例:TextSend(1,"1234567890ABCDEF");
//     これで液晶の1行目に「1234567890ABCDEF」が表示されるはず。

// なお、文字数等の制限は基本的にしていないので。
// 何百文字でも入力可能だが。基本的には液晶の最大=入力最大
// 行数は最大2行まではこのプログラムでOK。
// 2行以上の液晶の場合、行数がちょっと複雑なのでコードの変更が
// 必要だと思われる。なお、4行の液晶でも2行目までの制御は
// 互換性があるものが多い。
int TextSend(int Line, char *Data) {
  LcdCmd(0x40 + (0x40 * Line));
  while (*Data) LcdData(*Data++);

}

終わりに

というわけで今回はArduinoでキャラクタ液晶を制御してみました。
なお、Arduinoの開発環境では標準で同種のキャラクタ液晶を制御するためのライブラリが用意されていますが、今回は基本的な命令を使って処理をしました。

このブログの「自分でデバイスを動かす方法を学ぶ」という方針を闇に掲げていますので。余程処理が複雑だったり、メーカーからライブラリが提供されていないかぎりは最初は自力で制御するようにしています。

そのほうがデバイスがどういう原理でどう動いているのかがよく理解できると思います。
やはり折角動かすからにはそのものがどういう動作をしているのかはきちんと理解して動かしたいですよね。

キャラクタ液晶はマイコンをつかう上でいろいろと重宝すると思いますのでこのアイテムの制御は覚えておきたいテクニックですね。

文字コードを直接指定して転送する方法

IMG_7137_20151220
(文字化けしている例)

コーディングの項で触れたとおり、今回は文字コードの関係上ソースに半角カナを直接書いても文字化けしてしまい表示させることは出来ません。しかし、実際には英数字のみでは不足で、せめてカナは表示したいところですね。そこでエスケープ文字(¥)を使って文字コードを直接指定するという方法をでカナを表示してみたいと思います。

例えば半角カナの「ア」は「0xB1」であり、これを指定するには「\xB1」と書きます。
という事で以下のコードを書くと

TextSend(1, "\xCE\xDD\xBC\xDE\xC2\xCA \xBE\xB2\xC3\xDD \xC5\xD8");

以下のように表示されます。(1行目)

IMG_7135_20151220
(正常に表示されている)

この方法を使えば文字コード表に乗っている全ての文字が表示出来ます。
ただし、入力した文字を表示できるわけではないのでちょっと不便ですね。

HD44780互換液晶(日本仕様)の文字コード表

hd44780code2

ちなみに一番最後の0xFFの文字は「■」ではなく全塗りつぶしです。

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

関連記事