2021年4月10日土曜日

Arduinoでi2cスレーブデバイスを作ってみる

 Arduino同士でI2Cのマスタとスレーブとして通信するとき、よくシリアル通信みたいな使い方を見かけるんだけど、I2C接続のセンサとかみたいにI2Cのレジスタを指定して読み書きできないか試してみた。

いろんなサンプルを見ているとI2C通信って一番最初にレジスタのアドレスを送って、その後に書き込みたいデータを送っているような感じ。読み込みの際も最初は読み込みたい先頭のアドレスを送っている感じ?

というわけでレジスタの範囲を指定して読み書きできるようなスケッチを作ってみた。

#include <Arduino.h>
#include <Wire.h>

#define SLAVE_ADDRESS 0x38

#define DEBUG
//#define DISABLE_INTERNAL_PULLUP

//使用するレジスタ範囲指定
#define I2C_REG_ROW 3
#define I2C_REG_COL 4
uint8_t I2C_REG[I2C_REG_ROW][I2C_REG_COL];

//読み書きするレジスタアドレス
uint8_t REG_SELECTED;

//i2c受信イベント
void receiveEvent(int _length) {
  //1byte目を読み込む(レジスタアドレス指定)
  REG_SELECTED = Wire.read();
  uint8_t startRow = REG_SELECTED >> 4;
  uint8_t startCol = (REG_SELECTED & 0x0F);
  
  #ifdef DEBUG
  Serial.print("REG:");
  Serial.println(REG_SELECTED, HEX);
  #endif
  
  while (Wire.available() > 0) {
    if(startRow < I2C_REG_ROW && startCol < I2C_REG_COL){
      I2C_REG[startRow][startCol] = Wire.read();
      #ifdef DEBUG
      Serial.print("Write:");
      Serial.println(I2C_REG[startRow][startCol], HEX);
      #endif
    }else{
      Wire.read();
    }
    ++startCol;
  }
}


void requestEvent() {
  uint8_t startRow = REG_SELECTED >> 4;
  uint8_t startCol = (REG_SELECTED & 0x0F);
  if(startRow < I2C_REG_ROW && startCol < I2C_REG_COL){
    Wire.write(&I2C_REG[startRow][startCol], I2C_REG_COL - startCol);
  }
}


void setup() {
  //SlaveでWireライブラリを初期化
  Wire.begin(SLAVE_ADDRESS);
  //イベント設定
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent);
  
  //内蔵プルアップ無効にする場合
  #ifdef DISABLE_INTERNAL_PULLUP
  pinMode(SDA, INPUT);
  pinMode(SCL, INPUT);
  #endif
  
  #ifdef DEBUG
  Serial.begin(115200);
  #endif
}

void loop() {
  
}

とりあえずArduino UNOとかのAtmega 328pデバイスで動くはず。

全部のレジスタ使いたいわけじゃなかったので使用できるレジスタの範囲を2次元配列でI2C_REG_ROWとI2C_REG_COLで指定する形にしてみた。もっときれいな実装方法あるかもしれないけど…
レジスタの範囲指定は0x00起点で何行、何列みたいな指定。
素人プログラムなのでおかしいところとか上手く動かないかもしれないところがあるかもしれない。

動作確認はRaspberry Pi 3BとArduino Microで行った。
ラズパイは3.3V、Arduinoは5Vなのでレベル変換するか、Arduino側の内蔵プルアップを無効にして(#define DISABLE_INTERNAL_PULLUPのコメントアウトを外す)Raspberry PiのI2Cに直結しても行けるかも?(無保証)
今回はレベルコンバータのLVCNV-I2Cを使用してテストを行った。A側にArduino、B側にラズパイをつなぐ。

ラズパイで"i2cdetect -y 1"をしてみる。

するとこんな感じでArduinoが認識された。Arduino側にはFFが送られてきていた。
i2cdetectは全アドレスにFFを送ってデバイスとしての応答があるかどうかを見ている?

次はi2cdumpしてみる

スケッチで指定したレジスタ範囲以外も全部0が送られている。
特に何も送らなければ0として認識するのかな。
つぎはi2csetで0x10から4Byte分適当なデータを書き込んでからdumpしてみる。
ちゃんと書き込んだのが読めてる。ちなみにレジスタ範囲指定外に書き込んでも無視されるはず。

とりあえず必要な機能が揃ったのでこれでいいかなー
レジスタをloop内とかで書き換えてもちゃんとi2c経由で読めるのかは未確認だけど…
これでI2C接続のモータドライバとか作ってラズパイで遊んだりできるかも。センサとかつければセンサの値を処理してからラズパイに渡したりもできるし。
昔使っていたI2Cのモータドライバより高性能なものが作れそう。

ちなみにESP32は今のところ標準ライブラリではi2cスレーブになれないらしいので、外部ライブラリを使う必要あるみたい。

0 件のコメント:

コメントを投稿