Raspberry PiにI2CインターフェースのLCDモジュールを接続する(6)バス・リピータを使用
■I2Cバスのドライブ能力
●AQM1602などのI2Cバスのドライブ能力
オシロスコープの画面は、Raspberry Pi model BのI2CのSCL(青)、SDA(黄)に接続したときの信号線です。SCLはマスタのRaspberry Piが出力しています。SDAは最初の0x7C(アドレス0x3EとR/W=0)の8ビットのマスタからの信号です。その後の9番目のSCLに合わせてスレーブからのACK信号が戻ります。
しかし、本来LOWの0.9V以下のレベルになるはずですが、1.21Vまでしか下がりません。次の0x80に続くマスタからの送信データに対しても同様の1.21Vの、LOWになり切れない信号が出力されています。このときの電源電圧は3.3Vです。
Raspberry PiのI2Cバスの端子には、1.8kΩのプルアップ抵抗が内蔵されています。3.3Vの電源に接続された1.8kΩでプルアップされたSDAバスの電圧が、1.21Vまで下がっています。
AQMのI2Cのドライバに流れ込んだ電流は次のようになります。
i =(3.3V-1.21V)/ 1.8 kΩ = 2.09V / 1.8 kΩ = 1.16 mA |
データシートではAQMのドライブ能力は、LOWレベルの場合1mAの電流を吸い込むことができ、0.8Vまでは出力電圧を下げられます。電流が1mAを超えると、出力電圧も増加し、LOWレベルを確保するための電流を流すことができません。そのためPCA9515Aのバス・リピータを使用しました。
●バス・リピータPCA9515AでACKを確実にLOWにする
PCA9515Aは「SCL1/SDA1」と「SCL0/SDA0」の双方向で信号のやり取りができます。電源電圧は3.3Vですが、SDA、SCL、EN(active-high enable)の各入出力端子は、5.5V入力に耐えられるように設計されています。
http://www.tij.co.jp/product/jp/PCA9515A
そのため、電源以外の各端子は5V電源回路と接続できます。
SCL、SDAの出力はオープン・ドレインです。HIGHの出力にするときは、出力をOFFにすることで電源にプルアップした抵抗でバスをHIGHにします。LOWの出力のときは出力端子には電流が流れ込み、プルアップ抵抗はその電流によるプルアップ抵抗の電圧降下でバスの電圧レベルがLOWになります。SCL、SDA端子は、プルアップ抵抗に6mAの電流まで流すことができます。
Vccの電源電圧は3.3Vの電源に接続し、5Vには接続しません。それ以外はSCL1を3.3V回路、SCL0側を5V回路としても両方とも利用できます。
●EN端子は内部でプルアップされている
EN端子はHIGHでバッファの機能は働き、この端子をLOWにすると両バス間のバスを分離します。常時この機能を利用する場合は、この端子は未接続でかまいません。
●ピッチ変換基板へのはんだ付け
PCA9515はSOICパッケージに入っていてピン間は1.265mmと狭いです。ブレッドボードで利用できるように2.54mmへのピッチ変換基板へ取り付けます。PCA9515のはんだ付けは、次の点に注意し慎重に作業を行えば問題なく完了します。ICのピン、基板のはんだ付けする面に次に示すフラックスを塗ってからはんだ付けします。
フラックスは、白光 FS-200を使用しました。
はんだをつけすぎた場合は、つけすぎたはんだに「はんだ吸取線」を当ててはんだゴテで温めると、余分なはんだが吸い取られます。はんだ付けした様子はこちらを参考にしてください。はんだ吸収線に塗布されているフラックスは空気中に放置しておくとゆっくりと気化してしまい、はんだを吸い取りにくくなります。長期間使わないときは、ケースからケーブルを長めに出さないようにするか、密封袋に入れておくようにします。
●I2cDeviceの作成
テスト・プログラムのキーとなる部分について動作を確認します。I2cDeviceの利用方法は下記を参照ください。
「Raspberry PiにI2CインターフェースのLCDモジュールを接続する(4)I2cDeviceクラス」
TMP102で温度を読むためのオブジェクトI2CTMP102の作成と同様に行います。
InitI2c()では、TMP102で温度読み取るオブジェクトI2CTMP102、LCDモジュールへデータの書き込みを行うオブジェクトI2CLCDを作成し、タイマを設定し、タイマをスタートします。この中で、最初に一度だけ行うLCDモジュールの初期化のための関数Initlcd()を呼び出します。
private async void InitI2c() { string aqs = I2cDevice.GetDeviceSelector(); var dis = await DeviceInformation.FindAllAsync(aqs); |
ここまでの処理の結果のdisは、TMP102、LCDモジュールのオブジェクト作成に利用しています。
var settings = new I2cConnectionSettings(TMP102_I2C_ADDR); settings.BusSpeed = I2cBusSpeed.FastMode; I2CTMP102 =await I2cDevice.FromIdAsync(dis[0].Id, settings); |
ここから、LCDモジュールへの書き込みのためのオブジェクトを作成します。TMP102ではI2Cのスレーブ・アドレスを変数で設定しましたが、LCDモジュールのオブジェクト作成ではアドレスを0x3Eと直接数値で設定しました。
ここでは、I2Cのバス・スピードは100kHzのStandard Modeで設定しました。AQMシリーズのLCDモジュールは400kHzのFast Modeにも対応しています。400kHzの速度を利用する場合は、Standard ModeをFast Modeに変更します。
var settings2 = new I2cConnectionSettings(0x3E); settings2.BusSpeed = I2cBusSpeed.StandardMode; I2CLCD = await I2cDevice.FromIdAsync(dis[0].Id, settings2); Initlcd(); i2cprint("stra12345"); |
Initlcd()でLCDモジュールの初期化処理を実行します。i2cprint("stra12345");はテストのためのもので、実際に稼働が確認されたら削除します。この後はタイマの設定と開始です。
timer = new DispatcherTimer(); timer.Interval = TimeSpan.FromMilliseconds(500); timer.Tick += Timer_Tick; timer.Start(); } |
●LCDモジュールの具体的な処理
I2Cバス経由でのデータの書き込みは、最初に書き込みデータをバイト配列のwritebufにセットします。書き込みのオブジェクトWriteメソッドを用いて、次のように「コマンド書き込み関数」と「表示データ書き込み関数」を用意しました。
●コマンド書き込み関数i2cwritecmd(cmd)
コマンド書き込み関数は、LCDモジュールへの1バイトのコマンドcmdを、引数として受け取ります。コマンドは、画面クリアやカーソルの点滅などたくさんあります。writebufのは2バイトで構成されています。0x00はAQM1602のデータシートに書かれている値です。
- 1バイト目 コマンドの書き込みを示す0x00
- 2バイト目 コマンドcmd
I2CLCD.Write(writebuf);で実際の書き込みを行います。
private void i2cwritecmd(byte cmd) { byte[] writebuf = new byte[2] { 0x00, cmd }; I2CLCD.Write(writebuf); } |
●表示データ書き込み関数
1バイトの表示データを引数とした表示データ書き込み関数を用意しました。writebufは2バイトで構成されています。
- 1バイト目 表示データを示す0x40
- 2バイト目 表示したい文字データ
コマンドと同じI2CLCD.Write(writebuf);で実際の書き込みを行います。
private void i2cwritedata(byte data) { byte[] writebuf = new byte[2] { 0x40, data }; I2CLCD.Write(writebuf); } |
●文字列オブジェクトの表示関数
文字列を用意してLCDにまとめて表示する関数です。内部では1文字表示データ書き込み関数を使っています。
void i2cprint(String pdata) { byte[] pd1 = Encoding.ASCII.GetBytes(pdata); |
セットされた文字列オブジェクトのデータをバイト型データとして取り出し、バイト型の配列にセットしています。
byte[] pd0 = new byte[] {0x40 }; |
表示データを示す0x40をpd0配列にセットします。pd0とpd1配列の両方の長さをもったバイト配列writebufを作ります。
byte[] writebuf = new byte[pd0.Length + pd1.Length]; |
pdo、pd1をwritebufにコピーしています。
pd0.CopyTo(writebuf, 0); pd1.CopyTo(writebuf, pd0.Length); |
writebufのデータを書き出します。
I2CLCD.Write(writebuf); } |
●LCDモジュールの初期化関数
LCDモジュール内部では、コマンドを受け取っても内部の処理時間がかかるので、その時間を待って次のコマンドを送ります。
LCDモジュールへの書き込み後、LCDモジュールの処理の完了を待つためにTask.Delay(n)を用いています。nはms単位の待ち時間です。コマンドは、データシートに書かれた順番で指定しています。
private void Initlcd() { Task.Delay(145); i2cwritecmd(0x38); Task.Delay(2); i2cwritecmd(0x39); |
ここから拡張コマンドの処理です。
Task.Delay(2); i2cwritecmd(0x10); Task.Delay(2); i2cwritecmd(0x78); Task.Delay(2); i2cwritecmd(0x55); Task.Delay(2); i2cwritecmd(0x6C); Task.Delay(300); |
通常のコマンドの処理に戻ります。
i2cwritecmd(0x38); |
次回、TMP102の測定データをLCDモジュールとウィンドウに表示してみます。
(2016/11/1 V1.0)
<神崎康宏>