- Arduino2台でデータの送受信を行いたいけれども、どうすればよいのだろう?
- CAN通信を使用すれば送受信ができるらしいけど、外部モジュールを買わないといけないし、プログラムも複雑そう
- I2C通信なら追加で外部モジュールを買わなくても送受信ができるとのことなので、I2C通信のやり方を教えてほしい!
今回はそんな疑問にお答えします。
以前Arduino2台を使用したCAN通信でのデータ送受信方法を解説しましたが、CAN通信はまずMCP2515という外部モジュールの購入が必要でです。配線も6本近く繋ぐ必要があり、何よりもプログラムがそれなりに複雑なので、初心者にはいささかハードルの高い代物であります。
その点I2C通信であれば外部モジュールの購入も不要であり、配線も通信線2本+GND1本の合計3本の接続で済んでしまうというシンプル仕様。CANに比べればデータ送受信の速度が遅いという欠点がありますが、初心者が行う電子工作ではそこまで大容量のデータを高速で送受信する必要はないかと思うので、I2Cで十分です。
今回はサンプルプログラムを掲載しつつ、I2C通信について解説していきます。
-
-
CANを使ってArduino2台で複数データの送受信を行う方法
Arduinoを使用したCAN通信で複数データの送受信はどうやって行うのだろう? 一度に複数のデータが送られてきたときに、受信側はどうやって受け取る? 単発データならまだしも、複数データだと取りこぼし ...
続きを見る
I2C通信の仕様について
- データを送信するマスターと、受信するスレーブに分かれる
- 複数機器を同時に接続することができるので、スレーブは "スレーブアドレス" を設定する必要がある
- 設定できるスレーブアドレスの範囲は0x08~0x77
- マスターはスレーブアドレスを指定してデータの送信を行う
- 同じスレーブの機器が複数存在するとデータの送受信が正しく行えない
- 一度に送受信できるデータは32byteまで
- CANとは異なりIDの設定は必須ではない
- intやlongのような2byte以上のデータも、1byte毎に分解して送信される
今回送信するデータ
ID(16進数) | データ型 | 変数名 | 値 |
0x00 | int | intData | 1234 |
0x01 | float | floatData | 56.78 |
0x02 | char | charData | 'A' |
前述の通り、I2C通信はCANとは異なりIDの設定が必須ではないため、こちら側で独自に設定をする必要があります。
IDがないとどんなデータ型の値が送られてきたか分からないので、今回のサンプルプログラムでは先頭1byteをIDとして使用することにします。
配線方法
送信側(マスター) | 受信側(スレーブ) |
SDA | SDA |
SCL | SCL |
GND | GND |
I2C通信で使用する関数一覧(流し読みしてください)
1. 初期化関連
関数 | 用途 | 説明 |
---|---|---|
Wire.begin() |
マスター初期化 | Arduino をマスターとして I2C 通信を開始する |
Wire.begin(address) |
スレーブ初期化 | Arduino をスレーブとして初期化し、指定したアドレスで待ち受ける |
2. マスター送信関連
関数 | 用途 | 説明 |
---|---|---|
Wire.beginTransmission(address) |
送信開始 | 指定したスレーブアドレスへの送信を開始する |
Wire.write(value) |
データ送信 | 1バイトまたはバイト配列を送信する(Wire.write((byte*)&var, sizeof(var)) で複数バイト送信可能) |
Wire.endTransmission() |
送信終了 | データ送信を終了し、スレーブに転送する(戻り値は送信結果コード) |
3. マスター受信関連
関数 | 用途 | 説明 |
---|---|---|
Wire.requestFrom(address, quantity) |
データ要求 | 指定スレーブから指定バイト数のデータを受信する |
Wire.read() |
受信データ読み出し | 受信バッファから1バイトを読み出す |
Wire.readBytes(buffer, length) |
バイト配列で受信 | 受信バッファから複数バイトをまとめて読み出す |
4. スレーブ受信・送信関連
関数 | 用途 | 説明 |
---|---|---|
Wire.onReceive(function) |
受信イベント登録 | スレーブとしてデータを受信したときに呼び出す関数を登録 |
Wire.onRequest(function) |
リクエストイベント登録 | スレーブとしてマスターからデータ要求があったときに呼び出す関数を登録 |
5. その他ユーティリティ
関数 | 用途 | 説明 |
---|---|---|
Wire.available() |
受信データ数確認 | 受信バッファに残っているバイト数を取得 |
Wire.setClock(frequency) |
通信速度設定 | I2Cクロック周波数を設定(例:Wire.setClock(400000) で 400kHz) |
送信側コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#include <Wire.h> // I2C通信ライブラリを読み込む #define SLAVE_ADDR 0x08 // 通信相手のスレーブI2Cアドレスを定義 // 送信用の変数(今回の要件) int intData = 1234; // ID 0x00 で送る int 値 float floatData = 56.78f; // ID 0x01 で送る float 値 char charData = 'A'; // ID 0x02 で送る char 値 void setup() { Wire.begin(); // マスターとしてI2Cを開始 Serial.begin(9600); // デバッグ用シリアルを開始 } void loop() { // ---- ID 0x00: int を送信 ---- Wire.beginTransmission(SLAVE_ADDR); // スレーブへの送信を開始 Wire.write(0x00); // 先頭1バイトにID(0x00)を送る Wire.write((byte*)&intData, sizeof(intData));// intの生バイト列をそのまま送る Wire.endTransmission(); // 送信を確定してバスに流す // →送信が完了するとバッファのデータはすべてクリアされる delay(400); // 少し待機(見やすさのため) // ---- ID 0x01: float を送信 ---- Wire.beginTransmission(SLAVE_ADDR); // 新たな送信を開始 Wire.write(0x01); // 先頭1バイトにID(0x01)を送る Wire.write((byte*)&floatData, sizeof(floatData)); // floatの4バイトを送る Wire.endTransmission(); // 送信を確定 delay(400); // 少し待機 // ---- ID 0x02: char を送信 ---- Wire.beginTransmission(SLAVE_ADDR); // 新たな送信を開始 Wire.write(0x02); // 先頭1バイトにID(0x02)を送る Wire.write((byte*)&charData, sizeof(charData)); // charの1バイトを送る Wire.endTransmission(); // 送信を確定 delay(800); // 一巡の区切りとして待機 } |
受信側コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
#include <Wire.h> // I2C通信ライブラリを読み込む void setup() { Wire.begin(0x08); // スレーブとしてI2Cを開始(自アドレス0x08) Wire.onReceive(receiveEvent); // 受信割り込みハンドラを登録 Serial.begin(9600); // デバッグ用シリアルを開始 } void loop() { delay(50); // メインループは特に処理なしで待機 } // 受信イベント(マスターからデータを受けたとき自動で呼ばれる) // →受信イベントが呼ばれる直前でバッファの古いデータはすべて削除され、新しいデータが格納される void receiveEvent(int numBytes) { if (numBytes < 1) return; // 受信バイト数が0以下なら何もしない byte id = Wire.read(); // Wire.read()でバッファから1バイト分の読み取り可能 // 先頭1バイトはID値が入っているので、IDとして取得 switch (id) { // IDで受信データの型と処理を分岐 case 0x00: { // 0x00 は int を受信 int val; // 受け取り用のint変数 Wire.readBytes((char*)&val, sizeof(val)); // 受信バッファからintサイズ分を読み込む Serial.print("ID 0x00 intData = "); // ラベルを出力 Serial.println(val); // 値を出力 break; // 分岐を抜ける } case 0x01: { // 0x01 は float を受信 float val; // 受け取り用のfloat変数 Wire.readBytes((char*)&val, sizeof(val)); // 受信バッファからfloatサイズ分を読み込む Serial.print("ID 0x01 floatData = "); // ラベルを出力 Serial.println(val); // 値を出力 break; // 分岐を抜ける } case 0x02: { // 0x02 は char を受信 char val; // 受け取り用のchar変数 Wire.readBytes((char*)&val, sizeof(val)); // 受信バッファから1バイト読み込む Serial.print("ID 0x02 charData = "); // ラベルを出力 Serial.println(val); // 文字として出力 break; // 分岐を抜ける } default: { // 想定外のIDを受けた場合 Serial.print("Unknown ID: 0x"); // 不明IDの通知 Serial.println(id, HEX); // 16進でIDを表示 // 必要なら残りの受信データを読み捨てる処理を追加する break; // 分岐を抜ける } } } |
I2Cに関しては特に難しいロジックもなく、CANに比べればシンプルなコードになっていますね。
もし電子工作で簡易的にデータの送受信が必要になった場合、ぜひI2C通信を使ってみてください!