ArduinoのI2C接続キャラクタLCDモジュール用の関数を作る(1)コントローラHD47780を操作
■コントローラはHD47780の互換がメイン
I2C接続キャラクタLCDモジュールは、I2CインターフェースでArduino(マイコン・ボード)からLCDモジュールの制御を行うコマンドと表示するデータを受け取り、指定された仕様で受け取ったデータを表示します。
そのため、ArduinoからはWireライブラリを利用して、コマンドやデータをこのI2C接続キャラクタLCDモジュールに送信します。
ArduinoのLiquidCrystalライブラリを利用してLCDモジュールに表示する場合は、コマンドを意識する必要はありませんでした。しかし、I2C接続のものはLiquidCrystal ライブラリが対応していません。そのため、I2C接続のWireライブラリを利用してLCDモジュールにコマンド、データを書き込む関数を用意し、その関数を組み合わせてLCDモジュールの表示方法の制御やデータの表示を行います。
●LCDモジュールを制御するコマンド
幸い、現在市販されているキャラクタLCDモジュールはHD47780とコマンドの互換性があるので、一度そのコマンドの体系をしっかり確認しておけば、後で困まりません。また、コマンドを利用すると、表示のシフトやカーソルのブリンクなど多様な応用が実現できます。
●コマンド/データの区分
LCD制御モジュールへ書き込むデータが、コマンドか表示データかの区分はRS端子の“H”/“L”で区別されます。端子のHIGH(データ・レジスタ)、LOW(コマンド・レジスタ)でコマンドの書き込みと表示データの書き込みの選択を行います。
本来HD47780には、コマンド・レジスタのビジー・フラグやカウンタ・アドレスを読み取ったり、DD-RAMやCG-RAMのデータを読み取る機能も用意されています。しかしArduinoのLiquidCrystalライブラリなどでもモジュールに書き込むだけで読み込みの機能は利用していません。I2C接続のLCDモジュールも、コマンドも表示データもともに書き込み処理しかしていません。
●ArduinoからLCDモジュールへのメッセージ
I2Cインターフェースを介して、ArduinoからLCDモジュールへ送られるメッセージは2バイトで構成されます。
- 1バイト目は2バイト目がコマンドかデータなのかを示す制御バイト
- 2バイト目はコマンドかもしくは表示データ
送信データは2バイトですが、実際に送信されるのはLCDモジュールのI2Cのスレーブ・アドレスが追加されるので3バイトの送信データです。
このデータを送信するプログラムは、WireライブラリのbeginTransmission(address)関数を利用します。スレーブのアドレスは、このbeginTransmission(address)の引数で示します。
そのあと、実際のデータはWire.write(value)関数で送信します。この関数を2回利用し2バイトの送信を行います。
int i2cwr(unsigned char dsel, unsigned char wrdata) { Wire.beginTransmission(lcd_address); Wire.write(dsel); Wire.write(wrdata); return Wire.endTransmission(); } |
この関数を利用して、引数を一つにしてコマンド書き込み、表示データの書き込みの二つの関数を用意します。
●コマンドの書き込み
コマンドcmdを書き込む関数は、次のようになります。
byte i2cwrcmd(unsigned char cmd) { return i2cwr(0x00, cmd); } |
●表示データの書き込み
表示データを書き込む関数は、次のようになります。
byte i2cwrdata(unsigned char wrdata) { return i2cwr(0x80, wrdata); } |
●表示のクリア
表示をクリアしてカーソルをホーム・ポジションである上段、左端にセットするコマンドは0x01です。表示をクリアする関数を次のように設定します。
void lcdclear() { i2cwrcmd(0x01); delay(1); } |
クリア・コマンドの実行には1ms以上かかる場合があります。そのため、delay(1)でコマンドの実行完了を待ちます。この待ち処理を入れないと、次に書き込んだコマンドが受け付けられる期待通りの処理が実行さません。
●LCDモジュールの初期化
LCDモジュールを最初に次の関数で初期化します。電源投入後、この初期化関数を実行することで、LCDモジュールに表示データを書き込むことができるようになります。
void init_lcd() { delay(145); i2cwrcmd(0x38); delay(1); i2cwrcmd(0x38); delay(1); i2cwrcmd(0x0C); delay(1); i2cwrcmd(0x01); delay(1); } |
●カーソルのセット
colに0からの値を、rowには1行目は0、2行目の場合は1をセットしてカーソルの位置を設定します。カーソルの位置はDD-RAMのアドレスで、この場合は0x00から0x7Fの値がアドレスの範囲となります。このうちデフォルトの状態で表示されるのは、1行目が0x00から0x0F、2行目は0x40から0x4FのアドレスのDD-RAMの内容です。
void lcdsetCu(unsigned char col, unsigned char row) { unsigned char ddad = col + row * 0x40 + 0x80; i2cwrcmd(ddad); |
●テスト・プログラム
この関数を実際に動かしてみます。うっかり忘れがちなのはWireライブラリの初期化を開始するWire.begin();を最初に実行することです。この処理を忘れ、何も変化せずその原因に気づかず少し戸惑いました。
初期化、クリア、カーソルのセット、データの表示などを行うテスト・プログラムを、次に示すように作りました。
次に、テスト・プログラムの全体を示します。
#include <Wire.h> unsigned char lcd_address = 0x50; int i2cwr(unsigned char dsel, unsigned char wrdata) { Wire.beginTransmission(lcd_address); Wire.write(dsel); Wire.write(wrdata); return Wire.endTransmission(); } byte i2cwrcmd(unsigned char cmd) { return i2cwr(0x00, cmd); } byte i2cwrdata(unsigned char wrdata) { return i2cwr(0x80, wrdata); } void lcdclear() { i2cwrcmd(0x01); delay(1); } void lcdsetCu(unsigned char col, unsigned char row) { unsigned char ddad = col + row * 0x40 + 0x80; i2cwrcmd(ddad); } void init_lcd() { delay(145); i2cwrcmd(0x38); delay(1); i2cwrcmd(0x38); delay(1); i2cwrcmd(0x0C); delay(1); i2cwrcmd(0x01); delay(1); } void setup() { Wire.begin(); // put your setup code here, to run once: init_lcd(); //delay(3); i2cwrdata(0x31); i2cwrdata(0x32); lcdsetCu(3, 1); i2cwrdata(0x41); i2cwrdata(0x42); delay(1500); } void loop() { // put your main code here, to run repeatedly: lcdclear(); i2cwrdata(0x41); delay(1000); i2cwrdata(0x42); i2cwrdata(0x43); delay(500); lcdsetCu(3, 1); i2cwrdata(0x31); i2cwrdata(0x32); i2cwrdata(0x33); delay(1000); } |
テスト・プログラムの実行結果を次に示します。クリア、文字表示、カーソルのセット、文字表示を繰り返しています。
(2016/7/4 V1.0)
<神崎康宏>
バックグラウンド
“H”/“L”;HighとLow。
ビジー・フラグ;問い合わせを受けた時点で、内部が動作しているので、新しい命令を受け付けないということを知らせる信号です。
DD-RAM;表示RAMです。このエリアにキャラクタを書くとLCDに表示されます。
CG RAM;ユーザがキャラクタ・パターンを定義できるエリアで、キャラクタ・ジェネレータ・パターンと重複しています。