DUEのI2C通信を試してみたいと思います。
DUEのボードコンポーネントを確認した時、librariesディレクトリの中にWireライブラリの姿が見えましたので、UNOと同じ様に使用できるものと推定できます。また、DUEにはI2Cユニットが2ユニット搭載されていて、Wire、Wire1の2つのインスタンスが使用できるはずですので、その辺りの検証もしてみます。
まず、DUEの回路図を確認しておきましょう。
DUEの回路図は、Arduino HPの「RESOURCES ⇒ PRODUCTS ⇒ ARDUINO DUE ⇒ DOCUMENTATION ⇒ SCHEMATIC」からダウンロードすることが出来ます。
DUEのMCU、SAM3X8EのI2C端子は、以下の様に記載されています。
回路図表記 | Arduino | Wireライブラリ | |||||
ピン番号 | ポート | 名称 | ピン番号 | ポートに割り当てられた機能 | インスタンス | ||
9 | PA17 | SDA1 | D70 | PA17(TW0_SDA/SCK0) | Wire1 | ||
70 | PA18 | SCL1 | D71 | PA18(TW0_SCK/A20/WKUP9) | |||
86 | PB12 | SDA0-3 | D20 | PB12(TW1_SDA/PWMH0/ADC8) | Wire | ||
87 | PB13 | SCL0-3 | D21 | PB13(TW1_SCK/PWMH1/ADC9) |
ちょっと混乱しますが、SAM3X8EのI2Cチャンネル0が回路図ではSCL1,SDA1と、表記されていて、SAM3X8EのI2Cチャンネル1が回路図ではSCL0,SDA0となっています。てんこしゃんこ、です。尚、回路図の表記0/1が、そのままWireライブラリのインスタンス(Wire/Wire1)に対応しています。
DUEでのI2C端子の配置ですが、以下図の様になっています。
DUEの回路図に戻って、I2C端子を追いかけて行くと、SCL0-3,SDA0-3の端子には、ボード上にプルアップ抵抗が付いていました(下図)。
※Arduino DUE回路図から抜粋して引用
SCL1,SDA1には付いていませんでした。従って:
本来I2Cバスライン上には、必ずプルアップ抵抗が必要なのですが、I2Cマスタのボード上にプルアップ抵抗が付いていなかった場合、外付けにプルアップする必要があって面倒です。と言って、複数のモジュールが同じことを考えて皆プルアップしていると、合成抵抗がどんどん小さくなって、ちょっと不安です。消費電流も大きくなります。
そのため、プルアップ付きのWireとプルアップ無しのWire1をユーザが使い分けられるような仕様なのか?いずれにせよ、上の通りとなっています。Wire1を使う場合には、外付けにプルアップ抵抗が必要ですので、ご注意ください。
※UNOのWireライブラリでは、Wire.begin()した時に、I2C端子のI/OポートにINPUT_PULLUP属性を付けて、ATmega328の内蔵プルアップ抵抗を使っていました。UNOではソフトウェアでINPUT_PULLUPを無効化できたのですが、DUEのWire側プルアップ抵抗はボード上に実装されているので無効化できません。
「#3-2 Arduino間I2C通信を試す」にて、UNOとMicro間でI2C通信を試した時のプログラム(test_i2c.ino)を使います。まず、単純にターゲットをDUEに変更してコンパイルしたところ、ノーエラーでコンパイルできてしまいました。素晴らしい\(1^o^)/。
UNOと同じ作法で、UNOと同じソースコードが使えるようです。ちゃんと期待通りに動作すれば、今までの経験で培ってきたノウハウでDUEのI2Cを使うことが出来ます。次節の実験で確かめてみましょう。
その前に、DUEのWire1インスタンスを選択可能な様に以下の様な#defineを追加してソースを改定しておきました。
#define WIRE Wire // DUE: select Wire or Wire1
デフォルトでは、Wireインスタンスを使用するようになっていますので、DUEでWire1インスタンスを使って通信したい場合には、上の#defineにWire1を指定してコンパイル&書き込んでください。
修正版のプログラムをリンクしておきます。
実験用プログラム:test_i2c.ino
いつもの様にオリジナルが差し替えてあります。
コンパイル&書き込むと、シリアルモニタからコマンドを受け付けるようになっています。コマンドについてはこちらで説明してあります。
※蛇足ですが、このプログラム(test_i2c.ino)は、STM32ではコンパイルが通りません。STM32の場合、I2Cをスレーブで使う場合には、"Wire_slave.h"を#includeする必要があるためです(作法が異なる)。
実験してみましょう。
まずは、DUEをI2Cマスタに設定し、I2Cスレーブに設定したUNOとの通信を試してみます。DUE、UNO共に同じプログラム(test_i2c.ino)をコンパイル&書き込んでおいてください。共に「#define WIRE Wire」としておきます。
以下の様に接続します。
Arduino DUE | 接続 | L/S | 接続 | Arduino UNO |
D4 | GND | |||
3.3V | → | LV | ||
D20(SDA) | ⇔ | LV1 | ||
D21(SCL) | ⇔ | LV2 | ||
GND | ⇔ | GND | ⇔ | GND |
HV | ← | 5V | ||
HV1 | ⇔ | A4(SDA) | ||
HV2 | ⇔ | A5(SCL) | ||
× | D4 |
DUEのD4=GNDは、DUEをI2Cマスタとして使うための設定です。D4はINPUT_PULLUPに設定されているので、普通にdigitalRead()すると、HIGHが読めます。以下コードの様にHIGHが読めたArduinoはI2Cスレーブ、LOWが読めたArduinoはI2Cマスタとして機能するようにプログラムされています。
const int pin_MTSV=4; // master / slave select
// initialize
pinMode (pin_MTSV, INPUT_PULLUP);
mst0_slv1=digitalRead (pin_MTSV);
DUEのD4端子をGNDに落とすことでLOWが読み出され、DUEがI2Cマスタとなります。以下実験では、I2Cマスタに設定した側のシリアルモニタを使います。UNOのD4は未接続(開放)としておきます。
なお、DUE(3.3V定格)とUNO(5V定格)でレベルが合いませんので、以下の様な双方向タイプのL/Sを間に挟む必要があります。
以下、実験中の様子です。
実験サンプル:【 】内はDUEシリアルモニタからの送信データ
【snd,hello, uno. i am due】
OK: 2030[us] sent: hello, uno. i am due
【snd】コマンドは、I2Cマスタ(DUE)からI2Cスレーブ(UNO)へデータ(文字列)を送信するコマンドです。I2Cスレーブは、データを受け取ると一旦内部バッファに蓄えています。
【rcv】
OK: 3264[us] recv: hello, uno. i am due・・・・・・・・・・・・・・・・
【rcv】コマンドは、I2Cスレーブ(UNO)からデータ(文字列)を受信するコマンドです。I2Cスレーブ(UNO)は、直前の【snd】コマンドで受け取ったデータをオウム返ししてきます。
受信データの後ろに付いたごみについては、こちらで解説してあります。想定通りの動作で問題ありません。
以上で、DUEをI2Cマスタとして使い、Wireインスタンスを使った通信が確認できました。続いてWire1インスタンスの動作も確認しておきます。
DUEの方だけ、「#define WIRE Wire1」に変更して、プログラム(test_i2c.ino)をコンパイル&書き込みます。更に、DUEの接続を変更します(↓)。
Arduino DUE | 接続 | L/S | 接続 | Arduino UNO |
D4 | GND | |||
3.3V | → | LV | ||
D70(SDA) | ⇔ | LV1 | ||
D71(SCL) | ⇔ | LV2 | ||
GND | ⇔ | GND | ⇔ | GND |
HV | ← | 5V | ||
HV1 | ⇔ | A4(SDA) | ||
HV2 | ⇔ | A5(SCL) | ||
× | D4 |
変更箇所をピンク色に着色しておきました。その他の接続は同じです。Wire1側にはオンボードのプルアップ抵抗が付いていないので、外付けにプルアップ抵抗が必要なのですが、今回は、双方向タイプのL/Sを接続しているため、自動的にプルアップされています。
実験サンプル:【 】内はDUEシリアルモニタからの送信データ
【snd,hello, uno. i am due-Wire1】
OK: 2606[us] sent: hello, uno. i am due-Wire1
【rcv】
OK: 3374[us] recv: hello, uno. i am due-Wire1・・・・・・・・・・・・・・・・
正しく通信できています。動作確認終了、正常動作を確認しました。
手持ちのモジュールだと、LCDサブシステム、I/Oエクスパンダ(PCF8574)、DA変換器(MCP4725)、9軸加速度・ジャイロ・磁気センサ(MPU9250)等がI2C接続ですので、DUEからも制御が出来そうです。
次は、UNOをI2Cマスタとして使い、DUEがI2Cスレーブとして動作することを確認しておきます。
DUEのプログラム(test_i2c.ino)を「#define WIRE Wire」に戻して、コンパイル&書き込んでください。UNOの方は、前節の実験のまま変更有りません。
以下の通りに接続します。
Arduino DUE | 接続 | L/S | 接続 | Arduino UNO |
D4 | × | |||
3.3V | → | LV | ||
D20(SDA) | ⇔ | LV1 | ||
D21(SCL) | ⇔ | LV2 | ||
GND | ⇔ | GND | ⇔ | GND |
HV | ← | 5V | ||
HV1 | ⇔ | A4(SDA) | ||
HV2 | ⇔ | A5(SCL) | ||
GND | D4 |
変更箇所をピンク色に着色しておきました。GNDに落としてあったDUEのD4端子は、GNDとの接続を切断しておきます。今度はDUEのD4端子からHIGHが読み出されるため、DUEがI2Cスレーブとなり、UNOがI2Cマスタとなります。以下実験では、I2CマスタであるUNOのシリアルモニタを使います。
実験サンプル:【 】内はUNOシリアルモニタからの送信データ
【snd,hello, due. i am uno】
OK: 2132[us] sent: hello, due. i am uno
【rcv】
OK: 3204[us] recv: hello, due. i am uno・・・・・・・・・・・・・・・・
スレーブとしての動作も、UNOの時と全く同じ作法でプログラミングできて、正常に動作できることが確認できました。
外部モジュールの制御にも使用可能ですが、I2Cスレーブにもなれるので、今回の様にMCU間の通信にも使用可能です。MCU間通信だと非同期シリアル(UART)とどっちを使うか迷う所ではありますが、I2Cはバス接続なので1本のバスに複数のマスタやスレーブを接続できるのが最大の強みです(UARTだと一対一通信に限られる)。何にせよ、UNOと同じ作法で使えるのはGood👍でした。