I2C通信
基本操作
I2C(アイ・スクエアド・シー,アイ・アイ・シー,Inter-Integrated Circuit)はTWI(2線直列インターフェース)とも呼ばれる.
I2C通信では,1つのマスタデバイスと,1つ以上のスレーブデバイスがある.マイクロコントローラはマスタデバイスとしてもスレーブデバイスとしても設定できる.
SCL(クロック)信号は主にマスタによって制御される.データの転送はSDA(データ)線で行われる.マスタは各スレーブに対し,固有の7ビットアドレスを使って通信を行う.
| "START" | START信号を送信する |
| "STOP" | STOP信号を送信する |
| "READ" | I2Cデバイスから入力チャンネルに読み込む |
| "WRITE" | 出力チャンネルからI2Cデバイスに書き込む |
| "data" | data をI2Cデバイスに書き込む |
| "ACK" | ACK信号(肯定応答)を送信または受信する |
| "NACK" | NACK信号(否定応答)を送信または受信する |
"START"信号と"STOP"信号はマスタデバイスのみが送信する."READ"は入力チャンネルが,"WRITE"は出力チャンネルが使用し,data は入出力チャンネルのどちらでも使うことができる."ACK"信号と"NACK"信号はデータを受け取るデバイスによって送信され,データを送信するデバイスによって読み取られる.
マイクロコントローラが通信する必要のあるI2Cデバイスの指定は"I2C"オプションで"I2C"->{name1->{spec11,spec12,…},name2->{spec21,spec22,…},…}として与えられる.I2Cの namei は以下の値を取ることができる:
スレーブのI2Cデバイスについて,指定と共に"ClockFrequency"サブオプションを与えることができる.
| {"SDA pin","I2C","SlaveAddress"->addr,"Conversion"…} | データがアドレス addr のスレーブデバイスによって送受信され,マイクロコントローラはマスタデバイスとなる |
| {"SDA pin","I2C","Conversion"…} | データがマスタデバイスによって送受信され,マイクロコントローラはスレーブデバイスとなる |
入力チャンネルでは,サブオプション"Conversion"は入ってくるデータがどのように変換され,入力に割り当てられる必要があるのかを指定する.
出力チャンネルでは,サブオプション"Conversion"は出力がどのように変換され,送信される必要があるのかを指定する.
マイクロコントローラがスレーブモードで動作している場合,そのI2CアドレスはMicrocontrollerEmbedCode[sys,<|…,"I2CAddress"->"addr"…|>,…]のように,マイクロコントローラの詳細設定をすべて指定する第2引数で指定しなければならない.
例
ATmega168をマスタとして,Arduino Unoをスレーブとして使用し,また,スレーブのMPU-6050温度センサを使用する.マスタはスレーブMPU-6050から測定値を受け取り,それをスレーブのArduino Unoに送る.スレーブのUnoはデータをシリアルで送り,そのデータはデバイスのフレームワークが読むことができる.
I2Cの接続と,ATmega168の電圧調整器を以下に示す.
マスタはスレーブデバイスと通信を行うために,そのアドレスを知る必要がある.MPU-6050のデータシートから,アドレスは0x68であることが分かる.スレーブのUnoにはその他の有効な任意のアドレスが選べる.
mpuAddr = "0x68";
unoAddr = "0x10";マスタはまずMPU-6050を初期化する.すべてのサンプリング時点において,マスタは自信が送った2バイトを読み込む.
mpuSpecs = mpuAddr -> {{"START", "0x41", "START", "READ", "ACK", "READ", "STOP"}, "Initialization" -> {"START", "0x6B", "0x0", "STOP"}};at168Sys = StateSpaceModel[{{}, {}, {}, {{1, 0}, {0, 1}}}, SamplingPeriod -> 0.5, SystemsModelLabels -> None]マスタはその後これらのバイトを,クロック周波数が
MHzであるスレーブのUnoに書き込む必要がある.
unoSpecs = unoAddr -> {{"START", "WRITE", "WRITE", "STOP"}, "ClockFrequency" -> 16 10^6};ピン27はマスタATmega168のSDAピンであり,このピンを通してデータの送受信が行われる.入力バイトと出力バイトの変換は,単に通過するだけであるので,同一である.
at168IO[addr_] := Table["27" -> <|"Type" -> "I2C", "SlaveAddress" -> addr, "Conversion" -> fn|>, {fn, {#1&, #2&}}]Needs["MicrocontrollerKit`"]MicrocontrollerEmbedCode[at168Sys,
<|"Target" -> "ATmega168A_28PDIP", "Inputs" -> at168IO["0x68"], "Outputs" -> at168IO["0x10"]|>, <|"ExternalProgrammer" -> "usbasp"|>,
<|"I2C" -> {mpuSpecs, unoSpecs}|>]マスタはスレーブMPU-6050からの生のバイトをスレーブUnoに送る.スレーブUnoは変換計算を行う.
スレーブUnoをマスタからの2バイトを読み込むように設定する必要がある.
masterSpecs = "Master" -> {"READ", "ACK", "READ"};MPU-6050のデータシートには,生のバイトを実際の温度の値に変換する方法が指定されている.生のバイトは16ビットの値に変換され,さらに実際の値に変換される.
Needs["SymbolicC`"]fn = COperator[BitOr, {COperator[BitShiftLeft, {#1, 8}], #2}]&;unoSys = NonlinearStateSpaceModel[{{}, 36.53 + u / 340}, {}, u, SamplingPeriod -> 0.5]スレーブがI2Cデータを受信するように設定し,変換を行い,それをシリアルポートで送信する.マスタに呼び出されたときに応答できるようにスレーブのI2Cアドレスも指定しなければならない.
ℳ = MicrocontrollerEmbedCode[unoSys, <|"Target" -> "ArduinoUno", "Inputs" -> {"A4" -> <|"Type" -> "I2C", "Conversion" -> fn|>}, "Outputs" -> {"1" -> "Serial"}, "I2CAddress" -> unoAddr|>, "/dev/tty.usbmodem14111", <|"I2C" -> masterSpecs|>]最後に,スレーブからのシリアルデータを受信するようにデバイスフレームワークを設定する.
dv = DeviceOpen["Serial", "/dev/tty.usbmodem14111"]parseData[{ℳ["Serial"]["StartByte"], val0__}] := ToExpression[FromCharacterCode[{val0}]]
parseData[___] := Sequence[]y = {};
task = SessionSubmit@ScheduledTask[If[DeviceExecute[dv, "SerialReadyQ"], y = Join[y, {parseData@DeviceReadBuffer[dv, "ReadTerminator" -> ℳ["Serial"]["EndByte"]]}];
DeviceExecute[dv, "ReadFlush"];], 0.5];TaskRemove[task];
DeviceClose[dv];ListLinePlot[y, PlotRange -> All]