XIAO RP2040 を I2C slave にして M5STACK のポート拡張に使う

いろいろ前提条件の説明が必要な記事になりそうです。

（おことわり）slaveという表現は言い換えが進んでいるようですが，ここではそのままとします。

M5STACKはESP32なので，ADCの使い勝手が悪いです。ずばり ADC unit 等も販売されていて，私も便利に使っていますが，なにせ１ポートしかないのがつらいです。

また，とくにCore2とかはいわゆるM-BUSがカバーされていて，こちらも使い勝手が良くないです。M5STACKシリーズなら最低装備しているgrove，つまりI2Cを利用してうまく改善できれば，と考えました。

I2Cで使える専用ADC ICとかも販売されていますが，せっかくならI2C slaveになるマイコンを接続すれば，ポート拡張にもつかえるのでは？

というわけで試作してみました。

# 完成イメージ

```
M5STACK grove A (I2C) ------ (I2C slave) XIAO RP2040 (GPIO)
                                                     (ADC) etc...
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533274/9101ea0b-644e-b8e3-54e0-bf7d2760c63a.png)

M5STACKシリーズのgroveは5Vが出力されていますので，これをXIAOの5V端子に接続してXIAOを起動します。

```
                                USB
                          +-------------+
             (ADC0) | P26 |             |  5V --- grove A [3]
             (ADC1) | P27 |             | GND --- grove A [4]
             (ADC2) | P28 |             | 3V3
             (ADC3) | P29 |             |  P3
grove A [2] --- SDA |  P6 |             |  P4
grove A [1] --- SCL |  P7 |             |  P2
                       P0 |             |  P1
                          +-------------+
```
ポート番号はXIAO RP2040の表記ではなくて，RP2040の表記です。（理由は下記）

M5STACK BASIC で MicroPython では以下のような感じで使います。

```main.py
from machine import Pin, I2C
i2c=I2C(1, scl=Pin(22), sda=Pin(21)) # setup for grove A

print('scan',i2c.scan()) # 接続されているI2Cデバイスのアドレスをリスト表示

i2c.writeto(8,bytes([25,1])) # P25を１にする （青LEDをOFF）

i2c.writeto(8,bytes([3])) # P3をINPUTモードにして読む
print(i2c.readfrom(8,1)) # 結果はバイト列で得られる ( 0 or 1 )

i2c.writeto(8,bytes([26,3])) # P26をアナログ入力として読む
print(int.from_bytes(i2c.readfrom(8,2), 'little')) # ２バイト読んで整数に変換。12ビット
```
# XIAO RP2040側 スケッチ

```main.cpp
#include <Wire.h>

#define LED_B 25
#define LED_R 17
#define LED_G 16

#define I2C_address 8

byte a0,a1;
byte b0,b1;

/*
  1st byte: port number
  2nd byte: 0 ... OUTPUT 0 as GPIO OUPUT
            1 ... OUTPUT 1 as GPIO OUPUT
            2 ... set to GPIO INPUT PULLUP
            3 ... read as analog
            none ... set to GPIO INPUT
*/

void dataReceive(int Number) {
  Serial.println("onReceive");
  if(Wire1.available()) {
    a0 = Wire1.read();
    if(Wire1.available()) {
      a1 = Wire1.read();
      switch (a1) {
      case 0:
	pinMode(a0, OUTPUT);
	digitalWrite(a0,a1);
	break;
      case 1:
	pinMode(a0, OUTPUT);
	digitalWrite(a0,a1);
	break;
      case 2:
	pinMode(a0, INPUT_PULLUP);
	b0=digitalRead(a0);
	b1=0;
	break;
      case 3:
	unsigned int a=analogRead(a0);
	b0=a & 0xff;
	b1=a>>8;
	break;
      }
    } else {
      pinMode(a0, INPUT);
      b0=digitalRead(a0);
      b1=0;    
    }  
  }
}

void dataSend() {
  Serial.println("onRequest");
  Wire1.write(b0);
  Wire1.write(b1);
}

void setup() {
  pinMode(LED_B, OUTPUT);
  pinMode(LED_R, OUTPUT);
  pinMode(LED_G, OUTPUT);
  for(int i=0; i<5; i++) {
    digitalWrite(LED_B, 0); digitalWrite(LED_R, 1); digitalWrite(LED_G, 1);
    delay(200);
    digitalWrite(LED_B, 1); digitalWrite(LED_R, 0); digitalWrite(LED_G, 1);
    delay(200);
    digitalWrite(LED_B, 1); digitalWrite(LED_R, 1); digitalWrite(LED_G, 0);
    delay(200);
  }
  // delay(1000);
  Serial.begin(9600);
  // while(!Serial);
  // delay(1000);
  Serial.println("Booted");

  Wire1.setSDA(6);
  Wire1.setSCL(7);
  Wire1.begin(I2C_address);
  Wire1.onReceive(dataReceive);
  Wire1.onRequest(dataSend);
  Serial.println("Complete I2C setup");

  analogReadResolution(12);

  digitalWrite(LED_B, 1); digitalWrite(LED_R, 1); digitalWrite(LED_G, 1);*
}

void loop() {
  
}
```

- markdownの色付けのためにcppにしていますが，実際はinoでarduinoとしてコンパイルしました。
- **重要注意 `earlephilhower` のコアで，XIAO RP2040ではなくラズパイPICOとしてコンパイルします。**

```bash
$ arduino-cli compile --fqbn rp2040:rp2040:rpipico I2C_slave
```
- `onReceive`のハンドラで，ポート設定と，GPIO入力またはADCであれば，値も取得してみました。本来なら`loop(){}`の中で処理させるべきなのかもしれませんが，とりあえずうまく動いているようでした。

# 補足 注意事項

`earlephilhower` には XIAO RP2040 の定義もあるのですが，それでは `onRequest` でフリーズしてしまうようでした。Seeedが用意したコアも動きませんでした。そこでやむを得ずラズパイPICOとしてコンパイルすることで，うまく動くようになりました。

参考:
- https://coconala.com/blogs/219189/205870

ATSAMD の Seeed XIAO は，SeeedのコアでI2C slaveがうまく動きました。RP2040版を使ったのは個人的趣向からです。