Arduinoで用意されている標準ライブラリのLCDドライバ、LiquidCrystalを改造してC-51849NFJ-SLW-ADNを制御できるようにします。
なお、この記事は、1年ほど前にCQ出版社のブログに掲載したものを再編集したものです。
●改造の手順
オリジナルのソースファイルをコピーしてそれを修正し、拡張した関数を追加して、別ライブラリとして登録します。
●ドライバの仕組み
このドライバは、画面の上半分40文字2行(エリア0)と下半分40文字2行(エリア1)を別々のものと考え、操作する前にどちらかに切り換えて別々に操作しようというものです。従って、カーソル操作などは40文字×2行のそれぞれのエリア内で考えます。
改造するのは、Eストローブの出力個所です。また、切り換え用のpublic関数を別に用意して、それでエリア0、エリア1を切り換えます。
画面の消去など一括で操作したい場合など、エリア0、エリア1同時に操作したいこともありますので、そういった使い方もできるようにしてあります。
●改造済みドライバ
先に改造済みのドライバを掲載しておきます。展開したフォルダを丸ごと”libraries”フォルダへコピーし、Arduino-IDEを再起動してください。次回使用するサンプル・スケッチは”examples”に入っています。
zipファイル Lcd404
以下、改造の手順を示しますが、手っ取り早く試したい人は次回の記事に進んでも構いません。
●LiquidCrystalの改造
オリジナルはそのまま残しておいて、コピーを改造するとこにします。見づらくなりますが、手を加えたところを明確にするために、オリジナルのコードはできるだけ残すようにしています。
まず、”libraries”フォルダにある”LiquidCrystal”フォルダを丸ごとコピーして”Lcd404″とリネームします。404は40文字×4行という意味です。名称は任意ですが、LiquidCrystal2とかでは、長ったらしく、意味もわかりにくいので、全然違う名前にしました。
なお、exampleフォルダにあるサンプル・スケッチは、そのままでは使えず、あとあと紛らわしいので削除し、新規に作ったものをそこへ保存することにします。
コピーしたら、まずファイル名をリネームします。
“LiquidCrystal.cpp” → “Lcd404.cpp”
“LiquidCrystal.h” → “Lcd404.h”
次に”Lcd404.cpp”をテキスト・エディタで開いて、冒頭のコードを次のように変更します。
// #include "LiquidCrystal.h" // 削除 #include "Lcd404.h" // 追加
同様に”Lcd404.h”の冒頭のコードを変更します。
//#ifndef LiquidCrystal_h // 削除 //#define LiquidCrystal_h // 削除 #ifndef Lcd404_h // 追加 #define Lcd404_h // 追加
なお、オブジェクト名はLiquidCrystalのままにしてあります。
init()関数を修正します。「40文字x4行対応」とコメントが付いている行が修正したところです。
void LiquidCrystal::init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t enable, uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) { _rs_pin = rs; _rw_pin = rw; // E1 _enable_pin = enable; // E2 _data_pins[0] = d0; _data_pins[1] = d1; _data_pins[2] = d2; _data_pins[3] = d3; _data_pins[4] = d4; _data_pins[5] = d5; _data_pins[6] = d6; _data_pins[7] = d7; pinMode(_rs_pin, OUTPUT); // we can save 1 pin by not using RW. Indicate by passing 255 instead of pin# // if (_rw_pin != 255) { del 40文字x4行対応 pinMode(_rw_pin, OUTPUT); // } del 40文字x4行対応 pinMode(_enable_pin, OUTPUT); if (fourbitmode) _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS; else _displayfunction = LCD_8BITMODE | LCD_1LINE | LCD_5x8DOTS; // begin(16, 1); // 40文字x4行対応 begin(40, 2); // 40文字x4行対応 area_select = 0; // デフォルト設定エリア0 40文字x4行対応 }
それから、4ビット・モードの処理などは使わなくなりますが、動作に影響がないものはそのまま残してあります。
●エリア切り換え関数の追加
エリア切り換え用に”selectArea()”というpublic関数を用意しました。この関数は、private変数の”area_select”を書き換えるものです。
エリア0の場合はarea_select = 0、エリア1の場合はarea_select = 1、両エリア同時の場合はarea_select = 2とします。実際はarea_selectが0でも1でもない場合は、2と同じ扱いにしています。
setCursor()でのカラム、ラインの指定が0オリジンなので、この関数のエリア指定もそれにあわせて0オリジンにしています。
// --------------------------------------------- // エリアセレクト // 0=Area0 / 1=Area1 / 2以上=Area0,1 // --------------------------------------------- void LiquidCrystal::selectArea(uint8_t sw) { area_select = sw; // エリア選択変数に設定 }
●カーソル設定関数の追加
この関数は標準で用意されているsetCursor()をオーバロードするものとして用意しました。第一、第二引数は標準のsetCursor()と同じですが、第三引数はエリア番号が指定できるようになっています。これを使うと、カーソル位置指定時に一緒にエリア番号も指定できます。
※この関数はオリジナルのsetCursor()を書き換えるのではなく、新規に別関数として追加してく ださい(オリジナルも残す)。
// --------------------------------------------- // エリア設定付きカーソル位置設定 // --------------------------------------------- void LiquidCrystal::setCursor(uint8_t col, uint8_t row, uint8_t area) { selectArea(area); // エリア設定 setCursor(col, row); // カーソル位置設定 }
●ストローブ出力関数の修正
area_select変数の値により、パルスの出力先を変更します。これが一番重要な関数です。コードは次のようにしました。enableはE2ストローブ、rwはE1ストローブとして扱っています。
// --------------------------------------------- // ストローブ出力(改) // 0:Area0/1=Area1/2以上=Area0&1 // 注意 R/WピンをE1として扱っている // --------------------------------------------- void LiquidCrystal::pulseEnable(void) { digitalWrite(_rw_pin, LOW); // E1 digitalWrite(_enable_pin, LOW); // E2 delayMicroseconds(1); if(area_select == 0) { // エリア0 digitalWrite(_rw_pin, HIGH); // E1 } else if(area_select == 1) { // エリア1 digitalWrite(_enable_pin, HIGH); // E2 } else { // エリア0と1両方 digitalWrite(_rw_pin, HIGH); // E1 digitalWrite(_enable_pin, HIGH); // E2 } delayMicroseconds(1); // enable pulse must be >450ns digitalWrite(_rw_pin, LOW); // E1 digitalWrite(_enable_pin, LOW); // E2 delayMicroseconds(100); // commands need > 37us to settle }
設定値にかかわらず、前後にE1、E2両方をLOWレベルに設定するようにしてあります。ディレイdelayMicroseconds()はセットアップ・タイムやパルス幅を確保するための遅延処理です。
●そのほかの修正
コンストラクタが四つありますが、データ8ビット、RS,E,RWのセット以外は使えませんので、コメントにして削除しています。また、本来のr/wポートのレベル切り換え処理は不要ですので、削除します。3か所ありますが、削除個所はスケッチのコードで確認してください。
clear()関数は、両エリア同時にクリアするようにしようかとも思ったのですが、かえって、片側ずつクリアするほうが使い道があると考えて、そのままにしてあります。初期表示のように、両方のエリアを同時にクリアしたい場合は、クリア直前で両エリアを選択してください。
●クラス定義の変更
“Lcd404.h”のLiquidCrystalクラスの定義で、パブリック関数やプライベート変数を追加したり、使わない(使えない)関数を削除しています。
コードの変更個所は次のようになります。「40文字x4行対応」とコメントしてある行が追加したところです。また、最初の方で”/*”と”*/”でくくってある2箇所が削除部分です。
コンストラクタが四つ定義されていますが、不要なものはコメントにして削除しました。なお、R/W信号を片方のE信号として扱います。ソースでは名称を修正せずにそのまま使っていますので注意してください。
“enable”をE2、”rw”をE1として扱っています。該当個所にコメントを付けていますが、ポートの初期化や操作時に注意してください。
※クラス名や”rw”、”enable”のシンボル名が実体と異なるのが気になる場合はテキストエディタの機能で十分注意した上で一括置換してください。その場合は、インスタンス作成時も変更したクラス名を使う必要があります。本来はクラス名を”LiquidCrystal”から”Lcd404″に変更すべきなのですが。
class LiquidCrystal : public Print { public: /* del 40文字x4行対応 LiquidCrystal(uint8_t rs, uint8_t enable, uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7); */ LiquidCrystal(uint8_t rs, uint8_t rw, uint8_t enable, uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7); /* del 40文字x4行対応 LiquidCrystal(uint8_t rs, uint8_t rw, uint8_t enable, uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3); LiquidCrystal(uint8_t rs, uint8_t enable, uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3); */ void init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t enable, uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7); void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS); void clear(); void home(); void noDisplay(); void display(); void noBlink(); void blink(); void noCursor(); void cursor(); void scrollDisplayLeft(); void scrollDisplayRight(); void leftToRight(); void rightToLeft(); void autoscroll(); void noAutoscroll(); void createChar(uint8_t, uint8_t[]); void setCursor(uint8_t, uint8_t); void setCursor(uint8_t, uint8_t, uint8_t area); // 40文字x4行対応 virtual size_t write(uint8_t); void command(uint8_t); void selectArea(uint8_t sw); // 40文字x4行対応 using Print::write; private: void send(uint8_t, uint8_t); void write4bits(uint8_t); void write8bits(uint8_t); void pulseEnable(); uint8_t _rs_pin; // LOW: command. HIGH: character. uint8_t _rw_pin; // LOW: write to LCD. HIGH: read from LCD. uint8_t _enable_pin; // activated by a HIGH pulse. uint8_t _data_pins[8]; uint8_t _displayfunction; uint8_t _displaycontrol; uint8_t _displaymode; uint8_t _initialized; uint8_t _numlines,_currline; uint8_t area_select; // 40文字x4行対応 };
●print()について
もっとも重要なprint()の本体はLiquidCrystalクラスには含まれていません。これは、LiquidCrystalが”Print”という親クラスを継承していて、親クラスのほうにprint()の実体があるためです。
class LiquidCrystal : public Print {
というのが、LiquidCrystalが親クラスPrintを継承するという定義です。
LiquidCrystalでprint()がコールされると、親クラスのprint()が実行されます。親クラスのprint()は文字を出力する際にLiquidCrystalの”write()”をコールします。LCDへ実際に文字を出力するのは、LiquidCrystalのwrite()です。
virtual size_t write(uint8_t); using Print::write;
とあるのは、親クラスPrintがLiquidCrystalのwrite()を使うための手続きです。本来、Printにもwrite()があるのですが、カスタマイズしたwrite()に置き換えることにより、親クラスの機能を利用しつつ、子クラス固有の処理を実行させることができます。
このように子クラスが親クラスの関数を差し替えて使うことをオーバライドといいます。
●使い方
エリア設定と初期化以外は通常のLiquidCrystalと同じです。インスタンス生成時にピン番号が指定できますが、8ビット・モードしか指定できないため、フォーマットは、
LiquidCrystal lcd(LCD_RS, LCD_E1, LCD_E2, LCD_D0, LCD_D1, LCD_D2, LCD_D3,LCD_D4, LCD_D5, LCD_D6, LCD_D7);
という形に限られます。なおR/W端子はGNDへ接続してWriteに固定にしてください。
あとは、適当なタイミングでlcd.selectArea()をコールしてエリアを指定したのち、通常の方法で文字を表示するなどの操作が可能です。
次回は具体的な使用例やTips的なことを簡単に説明します。
参考文献
書籍案内 → Arduino実験キットで楽ちんマイコン開発