8. I2C スレーブ
前章でI2Cはマスターからスレーブを操作するプロトコルと説明しました。 スレーブデバイスを作れれば、他のMCUからこのMCUを操作することができます。
筆者は、自作キーボード用のジョイスティックポインターデバイスを作るために、CH32V003をI2Cスレーブデバイスとして使いました。 Groveポートを用意すれば、M5Stack等からも操作できるようになります。
本節ではI2Cスレーブデバイスの作成方法を説明します。
I2CスレーブはI2Cマスタと同じ機能を使って実装します。 I2Cマスタとは排他的に使うことになります。
8.1. I2C通信のメッセージボックス実装
I2Cでは、メッセージボックスと呼ばれる、マスターからスレーブに対して、レジスタアドレスを指定して書き込み、読み込みを行う通信方法が良くとられます。
例えば、16バイトのレジスタ配列に対して、レジスタアドレス 0x04 から4バイト、 0x20、0x21、0x22、0x23 と書き込む場合、以下のように通信されます。
- マスターからスレーブに、送信を開始を通知
- マスターからスレーブに、レジスタアドレスとして、0x04を送信
- マスターからスレーブに、レジスタアドレス 0x04 への書き込みとして、0x20 を送信
- マスターからスレーブに、レジスタアドレス 0x05 への書き込みとして、0x21 を送信
- マスターからスレーブに、レジスタアドレス 0x06 への書き込みとして、0x22 を送信
- マスターからスレーブに、レジスタアドレス 0x07 への書き込みとして、0x23 を送信
例えば、レジスタアドレス 0x04 から4バイトスレーブから読み込む場合には以下のように通信されます。
- マスターからスレーブに、送信を開始を通知
- マスターからスレーブに、レジスタアドレスとして、0x04を送信
- マスターからスレーブに、受信の開始を通知
- スレーブからマスターに、レジスタアドレス 0x04 の読み込みとして、0x20を送信
- スレーブからマスターに、レジスタアドレス 0x05 の読み込みとして、0x21を送信
- スレーブからマスターに、レジスタアドレス 0x06 の読み込みとして、0x22を送信
- スレーブからマスターに、レジスタアドレス 0x07 の読み込みとして、0x23を送信
このように、送信の最初のバイトをレジスタアドレスとして、次のバイトをデータとして送信することで、スレーブデバイスは、マスターからの要求に応じて、レジスタアドレスを指定して、データを読み書きすることができます。
今回解説するI2Cレジスタの実装はそれを実現するものです。
8.2. Arduino
まずArduinoでの実装を説明します。 サンプルコードは以下のリポジトリにあります。
https://github.com/74th/ch32v003-book-code/tree/main/i2c_slave-arduino_core_ch32
Arduinoでは、マスターからの送信、受信の要求を受け時に実行するコールバック関数を登録して実装できます。
読み書きに使う関数は、マスターの時と同じWire.write()
、Wire.read()
になります。
メッセージボックスの実装を送受信コールバックに実装すると以下のようになります。
// レジスタ
volatile uint8_t i2c_registers[0x30] = { 0x00 };
volatile uint8_t position = 0;
// マスターからスレーブへの送信(受信)
void on_receive(int length) {
for (int i = 0; i < length; i++) {
if (i == 0) {
// 最初のバイトはレジスタアドレスとする
position = Wire.read();
} else {
// 2バイト目以降はレジスタアドレスに書き込む
if (position + (i - 1) < sizeof(i2c_registers)) {
i2c_registers[position + (i - 1)] = Wire.read();
} else {
Wire.read();
}
}
}
}
// スレーブからマスターへの送信(送信)
void on_request() {
int i;
for (i = 0; i < 4; i++) {
// レジスタアドレスのデータを送信する
if (position + (i - 1) < sizeof(i2c_registers)) {
Wire.write(i2c_registers[position + i]);
} else {
Wire.write(0x00);
}
}
}
I2Cスレーブデバイスのアドレスを指定して、以下のようにセットアップします。
void setup() {
Serial.begin(115200);
Wire.onReceive(on_receive);
Wire.onRequest(on_request);
Wire.begin(I2C_ADDRESS);
}
これで、I2Cスレーブデバイスの実装が完了しました。