最近、埋め込みプログラミングにハマっているKosugi_kunです。
今回はM5StickC Plusを使ってI2C通信をしてみたいと思います。
この後に説明しますが、今回書いていくプログラムをスレーブ側のプログラムになります。
スレーブってなんだよと思っているかも知れませんがこれからそれも含めて説明していきたいと思います。
M5StickC Plusは各ネット通販から購入できます。
I2Cとは
I2C(Inter-Integrated Circuit)はフィリップ社が開発したシンプルなシリアル通信規格です。
通信速度は標準で100kbps 低速モードで10kbps 高速モードで400kbpsです。
新しい規格では3.4mbpsで通信できるらしいです。
2本の通信線を使用してデータのやり取りを行います。
それぞれの通信線の名称と役割は下記の通りです。
SCL(シリアルクロック):マスタとスレーブでデータ送信のタイミングを合わせるために使う線です。SDA(Serial Data):通信で送受信する内容はこの線を通ってきます。
I2Cを使用するために必要な線はこの2本だけです。すごくシンプルですね。
次にマスタとスレーブの役割です。
マスタ
スレーブを束ねるI2Cのネットワーク内のボス的な存在です。
主な役割は下記の通りです。
- 他のデバイスとの通信の確立
- クロックを発信して通信タイミングを調整
- スレーブに対して書き込み、読み込みのリクエスト
スレーブ
スレーブはマスタの下についてマスタ側の命令を実装する子分的役割です。
主な機能は下記の通りです。
- マスタの指示にしたがいデータの受信、送信を行う。
- マスタが自身を識別するためのIDを持つ
スレーブはマスタの指示がないと通信を開始できないためスレーブがデータを送信したくてもマスタが要求しない限り送信できません。
このようにI2Cではマスタが中心になってスレーブデバイスを制御する形になります。
基本的に回路を作る時はマスタが1台、スレーブが2台以上の回路になります。
2本の通信線で2台以上のデバイスを操作できるのでマイコンの入出力端子の節約になります。
コーディング
ではプログラムを作成していきます。
まずはスレーブアドレスを決めましょう。その時に同じI2Cのネットワークに接続する他のデバイスとアドレスが同じにならないようにしましょう。
#define I2C_SLAVE_ADDR 8 // I2Cのスレーブアドレス
今回はスレーブアドレスは8にしました。
次は通信に使用するピンを決めます。今回はGPIOの0と26を使用します。
#define I2C_SDA_PIN 0 // I2CのSDAピン
#define I2C_SCL_PIN 26 // I2CのSCLピン
このように定数を設定したらWireライブラリを使用してI2Cの機能を初期化していきます。
#include <M5StickCPlus.h> // M5StickCの機能
#include <Wire.h> // I2C通信の機能
void setup(){
M5.begin(true, true, true); // LCD, Power, Serial
Wire.setPins(I2C_SDA_PIN,I2C_SCL_PIN); // SDAとSCLのピンを指定
Wire.begin(I2C_SLAVE_ADDR); // スレーブモードでI2Cを開始
}
これでM5をスレーブモードで起動することができます。
ですがこのままでは、データの送受信ができません。なのでこれからデータの受信(receive)と送信(request)の処理を書いていきます。
下記のプログラムは1バイト目に通信の種類、2バイト目以降にデータを受け取るようになっています。
データの受け取り処理は用途に合わせて書き換えてください。
このプログラムはあくまで一例です。
void receiveEvent(int howMany){
if (Wire.available()) { // データがある場合
const byte head = Wire.read(); // 1byte目のデータを読み込む
switch(head){
case 0x01:
{
unit16_t freq = Wire.read() << 8; // 2byte目を読み込んで8bit左シフト
freq |= Wire.read();
uint32_t duration = Wire.read() << 24;
duration |= Wire.read() << 16;
duration |= Wire.read() << 8;
duration |= Wire.read();
M5.Beep.tone(freq, duration); // ブザーを鳴らす
}
break;
case 0x02:
{
// headが0x02だった時の処理
}
break;
default:
break;
}
}
Wire.flush(); // バッファをクリア
}
このような感じのプログラムを作成してみました。
マスタ側は1バイト目に0x01,2~3バイト目に周波数,4~7バイト目に時間(ms)のデータを載せることでビープ音を鳴らすことができます。
実際にビープ音を鳴らすには初期化処理が必要ですが今回はI2Cの話なので紹介はしません。
次にマスタからデータ送信のリクエストがあった時の処理です。
プログラムの内容はマスタからリクエストがあった際に取得した自身のロール角、ピッチ角、ヨー角を送信します。
M5のロール角、ピッチ角、ヨー角を取得するプログラムは今回は紹介しません。
void requestEvent() {
float roll, pitch, yaw; // ロール角、ピッチ角、ヨー角
getRPYData(&roll, &pitch, &yaw); // 姿勢データの取得
//データ送信準備
Wire.flush();
Wire.write(0x55); // ヘッダーを送信
// ロール角、ピッチ角、ヨー角を送信
byte byteArray[sizeof(float)]; // float型のデータを格納するためのバイト配列
memcpy(byteArray, &roll, sizeof(float)); // ロール角をバイト配列にコピー
Wire.write(byteArray, sizeof(float)); // ロール角を送信
memcpy(byteArray, &pitch, sizeof(float)); // ピッチ角をバイト配列にコピー
Wire.write(byteArray, sizeof(float)); // ピッチ角を送信
memcpy(byteArray, &yaw, sizeof(float)); // ヨー角をバイト配列にコピー
Wire.write(byteArray, sizeof(float)); // ヨー角を送信
}
これでデータ送信の処理は完成です。
このプログラムは1byte目に0x55を送信しています。理由としては、マスター側が受信したデータが正しいか確認するためのものです。時々意図しないデータを受信することがあるのでそれを弾くために独自に値を送信しています。
最後にWireライブラリに作成したプログラムを実行するように設定します。
setup関数に下記のプログラムを追記します。
Wire.onRequest(requestEvent); // マスターからのデータ要求時のイベント
Wire.onReceive(receiveEvent); // マスターからのデータ受信時のイベント
これでI2Cのスレーブ側のプログラムは完成です。
他の方の説明ではメッセージの送受信が多かったので違ったプログラムを作成してみました。
I2Cで様々なデータの送受信、スレーブの操作を行いたい方はぜひ参考にしてみてください。
私はこのようなプログラムを作成してマスタ側からスレーブのM5Stickに様々なことをさせるプログラムを作成しました。
M5StickC Plusの購入リンクを載せておきますのでぜひ埋め込みプログラミングを始めてみてください。
コメント