見出し画像

Arduino UNO R4をI2Cのスレーブモードで動作させるときの注意点

ラズパイやArduinoでセンサーなどを使うときにI2C通信がよく使われています。この場合、ラズパイやArduinoがマスターで、センサーがスレーブになって、マスターからスレーブのデータを書き込んだり、データを読み出したりします。

センサーなどの代わりにArduinoをスレーブとして動かして、マスターのラズパイやArduinoとI2Cで通信することもできるようになっています。

Arduino UNO R4でも、これまで同様I2Cのスレーブとして動作させるのですが、注意点があります。

それは、UNO R4ではWireライブラリonReceive()onRequest()で設定するコールバック関数の中ではSerialライブラリの関数を使ってはいけないという点です。


例えば、下記は、ArduinoIDEで、開発デバイスをArduino UNO R3など開発デバイスとしたときに出てくるWire(I2C)のサンプルスケッチ(slave_receiver.ino)です。これはArduinoをI2Cのスレーブとして動作させ、マスターからのデータを受信する例です。

#include <Wire.h>

void setup() {
  Wire.begin(8);                // join I2C bus with address #8
  Wire.onReceive(receiveEvent); // register event
  Serial.begin(9600);           // start serial for output
}

void loop() {
  delay(100);
}

void receiveEvent(int howMany) {
  while (1 < Wire.available()) { // loop through all but the last
    char c = Wire.read(); // receive byte as a character
    Serial.print(c);         // print the character
  }
  int x = Wire.read();    // receive byte as an integer
  Serial.println(x);         // print the integer
}

receiveEvent()がI2C通信でマスターからデータの書き込みがあったときに呼び出されるコールバック関数です。

このプログラムは、UNO R4より前のArduinoでは動作するのですが、UNO R4ではコールバック関数の実行中にハングアップしてしまいます。 

UNO R4で動かすには、コールバック関数内のSerialライブラリの関数の呼び出し部分を削除する必要があります。

ただ、このサンプルの場合、Serialの出力分を削ってしまうと、マスターから書き込みを実行しても何が起っているのか分からなくなってしまうので、下記のように、データを受信したらシリアル出力に出力する部分をコールバック関数の外にするなど必要でしょう。

#include <Wire.h>

unsigned char Reg;
int val;
bool recv_flg = false;

void setup() {
  Wire.begin(8);                // join I2C bus with address #8
  Wire.onReceive(receiveEvent); // register event
  Serial.begin(9600);           // start serial for output
}

void loop() {
  delay(100);
  if(recv_flg){
    Serial.print("Reg = "); Serial.print(Reg);
    Serial.print(", val = "); Serial.println(val);
    recv_flg = false;
  }
}

void receiveEvent(int howMany) {
  while (1 < Wire.available()) { // loop through all but the last
    char c = Wire.read(); // receive byte as a character
//    Serial.print(c);         // print the character
    Reg = c;
  }
  int x = Wire.read();    // receive byte as an integer
//  Serial.println(x);         // print the integer
  val = x;
  recv_flg = true;
}

では実際に、マスターをラズパイ5、スレーブをArduino UNO R4としてI2C通信してみましょう。

ラズパイのI2C端子とArduinoのI2C端子をつなぎます。ラズパイとArduinoでは電圧レベルが異なるので、レベル変換を間に入れています。

レベル変換には秋月電子のレベル変換モジュールを使いました。I2Cのレベル変換は色々あると思うのでなんでも良いと思います。
UNO R4には内部でプルアップが無いとのことなので、Arduino側には10kオームのプルアップ抵抗を入れました。

ラズパイ5とArduino UNO R4をI2C通信させる場合の配線例

ちなみにまだ試していませんが、Arduino UNO R4 WifiにはQwiicのコネクタがあり、そこに出ているI2Cの電圧レベルは3.3Vなので、Qwiicのコネクタでありば直接ラズパイとつなぐことも可能かと思います。その場合は、ArduinoのサンプルのWireをWire1にする必要があるかと思われます。

この配線とは別に、ArduinoはUSBケーブルでラズパイやPCに接続し、ArduinoIDEでサンプルプログラムをコンパイルして書き込みます。

Arduinoでサンプルが動いている状態で、ラズパイ側からi2cdetectを実行すれば、0x08アドレスが表示されるはずです。

 $ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         08 -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- UU -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --                   

試しに、ラズパイ側から、0x08のアドレスに下記のようにデータ(レジスタ0x01に値99)を書き込んでみましょう。

% i2cset -y 1 0x08 0x01 99

Arduinoのシリアル出力(ArduinoIDEのコンソール出力)に下記のように表示されればOKです。

Reg = 1, val = 99

ラズパイ側のI2Cスレーブへの書き込み方としては、pythonなら下記のような感じ。

import smbus
bus = subus.SMBus(1)
bus.write_byte_data(0x08, 0x01, [99])
# bus.write_i2c_block_data(0x08, 0x01, [99])でも同じです。

C/C++言語のプログラムなら下記のような感じです(エラー処理はすべて削除しています)。

#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>

int main(int argc, char *argv[])
{
    int fd = open("/dev/i2c-1", O_RDWR);
    ioctl(fd, I2C_SLAVE, 0x08);
    unsigned char reg[2] = {0x01, 99};
    write(fd, &reg, 2);
    close(fd);

    return 0;
}

コールバック関数のSerial出力が原因ということになかなか気がつくことができず、ずいぶんとはまってしまいました。ネットを検索しても見つからなかったので、常識的な話なのでしょうか?原因は定かではありませんが多重割り込みとかの関係かもしれません。

プログラムの開発中にコールバック関数で受信したデータや送信データを見てみたいという気持ちがありますが、Uno R4ではそこは我慢しなければならないようです。割り込みハンドラ(コールバック関数)の中では、余計な処理は入れずにさっさと処理をして終わるようにということでしょうか。


Fritzingで配線図を描くにあたり、Arduino UNO R4、raspberry Pi 5B,、秋月電子のレベル変換モジュールの各パーツは下記を使わせて頂きました。


いいなと思ったら応援しよう!