2017-05-14 18:03 | カテゴリ:PIC応用回路


アマゾンで電子部品をチェックしていたら、激安のLCD表示器を見つけ、早速注文しました。
ついでに、i2c通信でLCDを制御できる基盤(Ardiuno用と思われる)も同時に発注しました。
LCDは700円ほど、i2c基盤は140円ほどでした。
あの国から海を渡って3週間で到着しました。

(LCDi2c_01)
LCD_i2c01.jpg

このLCDをi2cで通信、制御する所までトライしてみようと思います。


続きは、、、、

LCDの仕様は?>

このLCDは、以前に秋月電子さんで購入したものとそっくりです。二度使用したことがありました。
端子の信号も同じなので、問題ないと思います。

(LCD_i2c02)
LCD_i2c02.jpg

LCDディスプレイ基盤そのものは、8ビットパラレル、もしくは4ビットパラレル(二度送信)信号を与えつつ、制御信号(3種)をON/OFFすることで、データをLCDに与える仕組みです。
4ビットパラレル仕様で動作させるにしても、4+3の7本の信号線(とGNDと5V)が必要です。
しかし、i2cで接続すれば、7本の信号線が2本になりますので、かなりすっきり、便利に使えます。

(LCD_i2c03)
LCD_i2c03.jpg

早速、以前使用していた、PICのi2cプログラムで、初期化コマンドや表示コマンドを与えてみましたが、全く動作しません。
バックライトはちゃんと青白く点灯し、コントラスト調整(半固定VR)もできますが、表示がダメです。

Ardiunoなら、既成のプログラム関数がネットで簡単に入手でき、それで、動作したという報告がなされています。

こちらは、相変わらずPICですので、一からプログラムを組まねばなりません。
仕方がないので、まずは、i2cのインターフェスICの仕様と接続をチェックしてみました。

(LCDi2c_04)
LCD_i2c04.jpg

テスターで端子を当たって、このICとLCDの端子がどのように接続されているかを調べました。
配線は、この様な接続になっています。


 i2cコントローラの出力のポート番号  LCDの端子名
             P0       RS
             P1       RW
             P2       E
             P3      (バックライトLEDのON/OFF)
             P4       D4
             P5       D5
             P6       D6
             P7       D7


ここで疑問が。
このICは、PICの様なプログラムされた機能ではなく、i2cで受信した8ビットデータを8ビットのパラレルポートに出力し、そこに接続されたLCDを制御する仕組みです。
また、ポートには、LCDの制御信号と4ビットパラレルのデータラインが接続されています。
ならば、単に8ビットのコマンドやデータを送ってもだめじゃん??
同時に、制御信号も併せてコントロールしなければ、単に8ビットのコマンドやデータをi2cで送っても動作するわけがありません。

ということは、制御信号と8ビットのデータを4ビットのデータに分割し、それを制御信号を変化させつつ与える様な制御をしなければならないということです。
これは、i2cを使用しないで、4ビットパラレル接続した場合と同じです。いや、i2c通信で信号を制御するために、それ以上に手間です。
かなりソフトを真剣に考えないと(作り変えないと)動作するとは思えません。

こりゃ、とんでもないものを買ってしまいました。(笑)


<基本で進めます>

嘆いてばかりいてもらちが明かないので、どう対応するか構想を考えました。

1)MCC(コードコンフィグレータ)は便利なので、i2c関係も含めてこれを使用する。

2)この中の、I2CMasterWrite()関数を使用すれば、所定のバイトを送り出す事が出来そう。

というスタイルで行くことにしました。

まず、MCCで出力されるi2cの関数の中に、

I2C_MasterWrite(&pData, length, SLV_adrs, &status);

があります。ここで、

  &pData  = i2cで送信するデータを格納した配列(unsigned char)のアドレスを示す&を付けて。
  length  = 送信する上記データのバイト数(個数)
  SLV_adrs = i2cコントローラのスレーブアドレス(今回の場合は、0x27でした)
  &status = i2c通信のステータスが返される変数。


この関数の動作ですが、所定のパラメータをセットしてコールすれば送信されるというと、ちょっと異なります。
これがつまづく原因だったのですが。
この関数がコールされると、送信データをバッファーにセットして、待ち行列に追加します。
タイマー割り込みで所定の条件で、バッファーにデータが残っていれば、それを送信し、完了をまって、次のデータを送信します。
これをバッファーが空になるまで続けます。

ポイントは、送信データをバッファーにセットして、そのデータが送信される前に(割り込み発生前に)そのデータを更新してしまうと、先のデータが送信されないことになります。
i2c通信の過程は、先日導入したロジアナを使ってどのようなデータがやり取りされているか?
ACKがちゃんと返されているかを確認できました。

(LCD_i2c05)
LCD_i2c05.jpg

私は、送信データを配列にセットし、その後この関数をコールして、すぐさま、内容を変更し、再度関数をコールしていました。
理由は、データを送信する際に、LCDを制御する信号(RS,RW、E)もデータを維持しつつ、変化させてLCDに読み込ませる為に、すぐに次のデータを送る必要があるからです。

はじめのうちは、この関数の処理の遅れがわからず、送りたいデータが送られたり、送られなかったたりと、不思議に動作をしたので、とても悩みました。

MPLABXのフォーラムに質問したら、上記の様なアドバイスがあり、修正して、うまく動作する様になりました。

(LCD_i2c06)
LCD_i2c06.jpg


<ビットデータとバイトデータを共有するには>

LCDを制御するデータは、上位4ビットに、送るデータの半分の4ビット分のデータを入れ、残りの4ビットに、LCD制御信号とバックライトON/OFFの制御をする信号を与えます。
つまり、ビット単位の制御を毎回しなければなりません。
その後、まとめて送信するバッファーに送るわけです。
ビット単位の操作とバイト単位の操作を同じ変数に行う必要があります。

この様な時に便利なのが、「共有体」という定義です。

例えば、

//-----------------
// 共有構造体定義
//-----------------

typedef union { // 共有タイプの構造体宣言
unsigned char i2c_send_byte; //バイトアクセスの際の変数名

struct {
unsigned RS : 1; //下位ビット
unsigned RW : 1;
unsigned Eflg : 1;
unsigned BL : 1;
unsigned data : 4; //上位ビット
};
} i2c_send_type; //構造体(共有体)のタイプ名称定義
i2c_send_type i2c_block; //構造体のタイプを変数名にアサイン


//================================================
// 上記構造体アクセス例
//================================================
//
//
// i2c_block.data = 1; //ビットデータにアクセス
// i2c_block.RS = 1; //ビットデータにアクセス
// i2c_block.i2c_send_byte = 10; //バイト単位でアクセス
//
//-----------------------------------------------------

この様に定義することで、ビット単位のアクセスも、まとめてバイト単位のアクセスもできます。


<実際のデータ送出例>

LCDは4ビットパラレルの動作です。
i2cにしても、PICからLCDまでの通信がi2cになるだけで、送る信号は、パラレルと同じです。

//-----------------------------------
// 4bitコマンドを送信(初期化時に必要)
//-----------------------------------

void CMD_put4(unsigned char cmd4) {

cmd4 = cmd4 & 0x0F;
i2c_block.i2c_send_byte = 0; //初期クリア
i2c_block.data = cmd4; //コマンドをDataにセット
i2c_block.RS = 0; //コマンドはRS=0
i2c_block.RW = 0; //書き込みはRW=0
i2c_block.Eflg = 0; //E信号は0
i2c_block.BL = 1; //LEDバックライトON

pCMD[0] = i2c_block.i2c_send_byte;

i2c_block.Eflg = 1; //E信号は1
pCMD[1] = i2c_block.i2c_send_byte;

i2c_block.Eflg = 0; //E信号は0
pCMD[2] = i2c_block.i2c_send_byte;

I2C_MasterWrite(&pCMD, 3, SLV_adrs, &status);//i2c関数を呼び出す
__delay_us(100);
}

//-----------------------------------
// 4bitデータを送信
//-----------------------------------

void Data_put4(unsigned char dt4) {
dt4 = dt4 & 0x0F;
i2c_block.i2c_send_byte = 0; //初期クリア
i2c_block.data = dt4; //コマンドをDataにセット
i2c_block.RS = 1; //データはRS=1
i2c_block.RW = 0; //書き込みはRW=0
i2c_block.Eflg = 0; //E信号は0
i2c_block.BL = 1; //LEDバックライトON

pData[0] = i2c_block.i2c_send_byte;

i2c_block.Eflg = 1; //E信号は1
pData[1] = i2c_block.i2c_send_byte;

i2c_block.Eflg = 0; //E信号は0
pData[2] = i2c_block.i2c_send_byte;

I2C_MasterWrite(&pData, 3, SLV_adrs, &status);
__delay_us(100);
}

ということで、格安LCD(20文字x4行)に、専用のi2c通信モジュールを付けて、PICで動作させることが出来ました!

最終プログラムは、こちらです。(Google Drive)

MPLABXのプロジェクト(圧縮)

https://drive.google.com/file/d/0B4yxpgEEEsTVYUNVTEpzeDBzWDA/view?usp=sharing



このあとは、先日アマゾンで注文しておいた、「超音波距離センサーモジュール」を繋いで、動作確認をしようと思います。
はたして動作するのでしょうか?
【ジャンル】:趣味・実用 【テーマ】:電子工作
コメント(4)

管理者のみに表示する