I2Cで制御できるLCD(液晶表示器)をArduino(CQカチャ WSN282)で使ってみました。なお、I2C制御ですので、I2Cマスタとして作動するものなら、Arduino以外のマイコンでも制御可能です。
最初はArduino標準のライブラリのWireで直接制御しますが、LiquidCrystal互換のArduino用ライブラリを用意しましたので、それも紹介します。
なお、この記事は、過日、CQ出版社のブログへ掲載したものを再編集したものです。
関連記事 「汎用キャラクタLCDのI2C化(1)」
●ACM1602NI
ArduinoでLCDを使うことは多いと思いますが、信号線が最低でも4+2本必要ですので、I/Oポートの少ないArduinoでは使い難いことがあります。そういうときにI2Cで制御できるLCDが使えれば、制御線は2本だけで済みますので用途が広がります。
今回紹介するDISPLAYTRONIC社のACM1602NI-FLW-FBW-M01は、I2Cコントローラとしてボード上にPICを搭載した、I2Cで制御できるLCDです。
このLCDは昇圧レギュレータを搭載しているため、3.3Vで駆動できますが、最大定格は5.5Vのため、5Vでも使用できます。
画面はよくある深緑色系の液晶ですが、バックライトが白色LEDのため、文字は見やすくなっています。なお、白抜きタイプと異なりバックライトなしでも視認できます。ただ、コントラスト調整がクリティカルで可視範囲が狭いため、ちょっと使い難いです。少し電圧を変えるだけで、まったく見えなくなったり、真っ黒になったりしますので、表示が出ないというときには電圧を調整してみてください。
バックライト電源は3.3Vが直結できますが、5V使用時は直列に抵抗器を入れて電流を制限する必要があります。
●ACM1602NIの制御方法
このデバイスはI2Cのスレーブとして働きます。スレーブ・アドレスは0×50に固定されています。
LCDの制御方法は、通常のLCDの8ビットモードと同様です。LCD制御コマンドは一般的なものと互換性があります。
通常のLCDの場合、RS信号を”H”レベルにしてデータを書き込むとLCDデータ、”L”レベルにして書き込むとLCD制御コマンドという扱いになりますが、ACM1602ではRS信号に1バイト割り当てて、2バイトのデータとして扱います。
具体的には、1バイト目を0×80にすると、LCDデータ(文字コードなど)、1バイト目を0×00にするとLCD制御コマンドという具合に、1バイト目の最上位ビットがRS信号に対応しています。
このオンボードPICの働きは、LCDを8ビット・モードに設定した後は、I2Cで送られてきたデータを単純にLCDのレジスタにセットするというシンプルなものです。そのため、行数や画面クリアなどの初期化処理は、I2Cマスタ(Arduinoなどのホスト)からコマンドを出すことで、実行させる必要があります。これは少々煩雑ですが、直接LCDを制御するのと同じことができます。
●Arduinoでの制御
マニュアルではPICで使う場合のサンプル・プログラムが掲載されていますが、Arduino版を作りましたので、ここで説明します。
まず、初期化が必要です。その後は必要に応じて、画面クリアやカーソル移動などのコマンドを送信し、文字データを送信すれば文字が表示されます。
LCDコマンド、LCDデータとも2バイトのデータ送信が必要ですが、関数化することにより、通常のLCD制御と同じ感覚で使えるようにしてあります。
I2C送信の際、マニュアルに掲載されているサンプル・プログラムではバイト送信の間隔にディレイを入れてありますが、Wireでは不要でした。作り方によってはディレイが必要になる場合があります。ほかのドライバを使う場合などで、通信がうまくいかないときは、50usぐらいインターバルをとるとうまくいくと思います。
●Arduinoとの接続
図、写真はCQカチャ(WSN282;Arduino互換機)と接続したときのものです。I2CのSCLとSDA信号はArduinoのアナログ入力A4、A5のポートを使います。従ってディジタル・ポートは一つも消費しませんが、アナログ・ポートが二つ減ることになります。電源以外は2本の信号線で接続できるため、非常にすっきりしています。
今回は、写真のようにLCDに7Pのピンヘッダをはんだ付けして、ブレッドボードに直接さして配線しました。5Vで駆動するため、バックライトのLEDの端子には直列に33Ωの抵抗器を入れてあります。少し暗くなりますが、そのまま3.3Vで使ってもちゃんと光ります 。
●LCDコマンド送信
LCDコマンド送信関数のコードを示します。Arduino添付のライブラリ”Wire”を使います。
1 2 3 4 5 6 7 8 9 | void writeCmd(byte cmd) { byte rs_flg; Wire.beginTransmission(0x50); // スタート・コンディション発行、コントロール・バイト送信 rs_flg = 0x00; // LCDコマンド指定 ...(1) Wire.write(rs_flg); // 第一バイト送信 (コマンド指定) ...(1) Wire.write(cmd); // 第二バイト送信 ( LCDコマンド) ...(2) Wire.endTransmission(); // ストップ・コンディション発行 } |
Wireの典型的な使い方です。(1)でLCDコマンドかLCDデータかのフラグ、(2)でコマンドを送信しています。
注意が必要なのは、(1)の部分です。一旦”rs_flg”という変数に0を代入してそれをwrite()に渡しています。 この部分は、直接 “Wire.write(0)”と書きたいところですが、実際はコンパイル・エラーになります。恐らく、0は文字列のNULLと認識して、文字列と判断されているようです。オーバロードの誤作動ですね。一旦変数にいれることで、バイト型ということを明確にしてコンパイル・エラーを回避しています。
●LCDデータ送信
種別が異なるだけで、コマンドのときとほぼ同じです。
1 2 3 4 5 6 | void writeData(uint8_t dat) { Wire.beginTransmission(0x50); Wire.write(0x80); // 第一バイト送信 (データ指定) Wire.write(dat); // 第二バイト送信 (LCDデータ) Wire.endTransmission(); } |
基本的には、writeCmd(), writeData()の二つの関数だけですべて制御できます。
●LCD初期化
前述のwriteCmd()を使ってLCDの初期化手順を実行します。
1 2 3 4 5 6 7 8 9 10 11 | void init( void ) { delay(15); writeCmd(0x01); delay(5); writeCmd(0x38); delay(5); writeCmd(0x0f); delay(5); writeCmd(0x06); delay(5); } |
インターバルを取りながらコマンドを書き込んでいます。このLCDは8ビット・モード固定のため、通常のLCDのように4ビット、8ビット切り換えのための手順が省略されています。画面クリアや行数設定などをここで行います。
●文字の表示
初期化終了以降は、writeData()で文字コードを1バイトずつ送ってやると、LCDへ文字が表示されます。たとえばこんな感じです。
1 2 3 4 5 | byte num = 0; for ( int i = 0; i < 16; i++) { writeData(0x30 + num); // ダイレクト LCDデータライト num = (num + 1) % 10; } |
このコードで数字の0~9が順番に16個表示されます。
●文字列の表示
次にNULL終端の文字列を表示する処理を説明します。LiquidCrystalでは”print()”
が用意されていますが、ひとまず、単純に文字列を表示する処理を作りました。書式指定したい場合は、”sprintf()”を使ってください。なお、ライブラリ化したあとは、”print()”がつかえるようになります。
1 2 3 4 5 6 7 8 9 10 | void string( char *str) { byte i; for (i = 0; i < 16; i++) { if (str[i] == 0x00) { break ; } else { writeData(str[i]); } } } |
NULLが出てくるまで単純にwriteData()の呼び出しを繰り返しているだけです。この関数はLiquidCrystalの”print(char *)”に相当します。
●画面のクリア
画面をクリアする関数を次に示します。コマンド・コードを送るだけですが、LCD側の処理に2ms程度時間がかかり、処理完了前にほかのコマンドやデータを送るとまずいので、ディレイを入れて時間稼ぎしています。
1 2 3 4 5 | void clear( void ) { writeCmd(0x01); // クリア・ディスプレイ・コマンド delay(2); // 動作終了待ち } |
●カーソル移動
カーソル移動は、表示するDDRAMの先頭アドレスを指定することで実現します。
画面構成は1行目1文字目がDDRAMアドレスの0、2行目1文字目が0×40から始まります。したがって、DDRAMアドレス = col + row * 0×40の関係になります。いずれも0オリジンです。実際はかけ算を使わずに配列にオフセット値を入れて加算しています。
1 2 3 4 5 6 7 8 9 | void setCursor(byte col, byte row) { byte row_offsets[] = { 0x00, 0x40 }; if ( row > 1 ) { row = 1; } writeCmd( 0x80 | (col + row_offsets[row]) ); } |
●それ以外の関数
“print()”、”begin()”、”string()”以外は、LiquidCrystalと互換性がありますので、LiquidCrystalの関数と同様に使えます。
●サンプル・スケッチ
実際のコードでは、すべての関数を共通関数としてクラスにまとめてあります。従って、通常のライブラリのようにインスタンス化が必要です。
共通関数のファイルは”wAcm1602Func.ino”と”wAcm1602Func.h”の二つです。多少面倒ですが、スケッチごとにメインのスケッチと同じフォルダへ、この二つのファイルをコピーして使ってください。
これらの関数群は、ライブラリ化する前の動作確認用という意味で作りましたので暫定的なものです。ライブラリ化すれば、このような手間は不要になります。
また、クラス化に伴い、関数の呼び出し方が少し変わります。”writeCmd()”は”lcd.writeCmd()”というように、インスタンス名(ここでは”lcd”)とピリオドが頭に付いた形になっていますので注意してください。
この方法は、ライブラリ化した際の呼び出し方と同じです。従って、ライブラリ化した際は、このサンプル・プログラムは少し変更するだけで使えます。
コード一式をフォルダにまとめてZIP圧縮したものを掲載しておきます。適当なフォルダに解凍してください。親フォルダを含めて日本語は使えませんので注意してください。また、文字コードがUTF-8ですのでテキスト・エディタで開くときは注意してください。
ダウンロード >>> wI2cLcdACM1602
サンプル・スケッチのコードを次に示します。
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 | #include <Wire.h> // I2C #include "wLcdFunc.h" // LCD共通関数 wLcdFunc lcd; // LCD共通関数のインスタンス char StrBuf[17]; // 文字列バッファ void setup( void ) { byte i; Wire.begin(); // I2C初期化 lcd.init(); // LCD初期化 lcd.clear(); // 全クリア lcd.noBlink(); // カーソル点滅なし lcd.noCursor(); // カーソル表示なし delay(1000); byte num = 0; for (i = 0; i < 16; i++) { lcd.writeData(0x30 + num); // ダイレクト LCDデータライト num = (num + 1) % 10; } lcd.setCursor(5, 1); // カーソル位置 for (i = 0; i < 10; i++) { lcd.writeData(0x30 + num); num = (num + 1) % 10; } delay(1000); // 表示OFF/ON lcd.noDisplay(); delay(1000); lcd.display(); delay(1000); // 文字列表示 lcd.clear(); // 全クリア lcd.string( "test1" ); // 文字列表示 lcd.setCursor(5, 1); // カーソル位置指定 lcd.string( "test2" ); // 文字列表示 delay(1000); // 数値文字列表示 byte val = 0x12; sprintf (StrBuf, " 0x%02X" , val); lcd.string(StrBuf); } void loop( void ) { } |
ぱっと見た目には、LiquidCrystalを使ったのと同じようになっているのがわかると思います。
それから、このスケッチ内で、begin()以外、Wireを直接使用する記述はありませんが、共通関数内で使用しているため、必ずWireをリンクしておく必要があります。
参考文献 ACM1602の記事があります。
書籍「Arduino実験キットで楽ちんマイコン開発」 → 書籍紹介ページ