Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

8. I2C スレーブ

前章でI2Cはマスターからスレーブを操作するプロトコルと説明しました。 スレーブデバイスを作れれば、他のMCUからこのMCUを操作することができます。

筆者は、自作キーボード用のジョイスティックポインターデバイスを作るために、CH32V003をI2Cスレーブデバイスとして使いました。 Groveポートを用意すれば、M5Stack等からも操作できるようになります。

本節ではI2Cスレーブデバイスの作成方法を説明します。

I2CスレーブはI2Cマスタと同じ機能を使って実装します。 I2Cマスタとは排他的に使うことになります。

8.1. I2C通信のメッセージボックス実装

I2Cでは、メッセージボックスと呼ばれる、マスターからスレーブに対して、レジスタアドレスを指定して書き込み、読み込みを行う通信方法が良くとられます。

例えば、16バイトのレジスタ配列に対して、レジスタアドレス 0x04 から4バイト、 0x20、0x21、0x22、0x23 と書き込む場合、以下のように通信されます。

  1. マスターからスレーブに、送信を開始を通知
  2. マスターからスレーブに、レジスタアドレスとして、0x04を送信
  3. マスターからスレーブに、レジスタアドレス 0x04 への書き込みとして、0x20 を送信
  4. マスターからスレーブに、レジスタアドレス 0x05 への書き込みとして、0x21 を送信
  5. マスターからスレーブに、レジスタアドレス 0x06 への書き込みとして、0x22 を送信
  6. マスターからスレーブに、レジスタアドレス 0x07 への書き込みとして、0x23 を送信

例えば、レジスタアドレス 0x04 から4バイトスレーブから読み込む場合には以下のように通信されます。

  1. マスターからスレーブに、送信を開始を通知
  2. マスターからスレーブに、レジスタアドレスとして、0x04を送信
  3. マスターからスレーブに、受信の開始を通知
  4. スレーブからマスターに、レジスタアドレス 0x04 の読み込みとして、0x20を送信
  5. スレーブからマスターに、レジスタアドレス 0x05 の読み込みとして、0x21を送信
  6. スレーブからマスターに、レジスタアドレス 0x06 の読み込みとして、0x22を送信
  7. スレーブからマスターに、レジスタアドレス 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スレーブデバイスの実装が完了しました。