ESP32 の I2C は壮絶にバグってる気がする

 ESP32 に MCP23017 を2つ繋いで、某ゲーム機のカートリッジ筐体内にある 16MB の ROMイメージを吸い上げる作業をクリスマスイブに着手し、翌日のクリスマスには完成させてブログ更新・・・って算段だったものが大幅に遅延しちゃってます。
 それが終わったら年賀状のつもりだったので、未だに年賀状が全くの未着手・・・


 年賀状タスクの優先度を上げるべきか悩むところですが、今更手遅れですよね。。


 さて本題ですが、最終的に成功したら記事にまとめようと思ってますけど、先に書いたとおり MCP23017 を2つ I2C で接続して32ビット分を準備し、某カートリッジの 24ビット+α を制御するというものなんですが、予想外に大はまり。
 んで、ここ3日ほど悩みまくった最終結論は


 ESP32 の I2C は壮絶にバグってる


という仮説です。
 Adafruit_MCP23017Fork して手直し してるんですが、例えばですけど

uint8_t Adafruit_MCP23017::readGPIO(uint8_t b) {
	Wire.beginTransmission(MCP23017_ADDRESS | i2caddr);
	if (b == 0)
		wiresend(MCP23017_GPIOA);
	else
		wiresend(MCP23017_GPIOB);

	Wire.endTransmission();
	Wire.requestFrom(MCP23017_ADDRESS | i2caddr, 1);
	return wirerecv();
}

みたいなものを readGPIOAB に倣って作るじゃないですか。


 んで、一見するとうまく動くんですが、いざ 16MB 分を回すと駄目なんですよ。

  • Wire.endTransmission() がエラー5を返す場合がある(いったん 5 を返し始めたらずっと 5 のまま)
  • Wire.requestFrom(MCP23017_ADDRESS | i2caddr, 1) が 1 を返さない場合がある


 某カートリッジの AD0〜AD15 がアドレス指定とデータ入力を兼ねているため、レジスタ設定→アドレス指定→レジスタ設定→データ入力 というコンボで回しているので、余計に発生頻度が上がるという事情もありますが、話は単に確率の話だけで、毎分測定の温度センサーでも同じことが起きえます。


 もう投げ出しそうになっていたところで、本家フォーラムで外人の指摘を見つけました。
 I2C/Wire/TwoWire can lock up or cease working in some conditions


 ESP8266 でも再現できるそうで、かなり根の深いバグ(エラッタ?)な感じです。
 今年の GW あたりから議論されていて reset する以外に抜本的な対策がないような雰囲気。
 それって解決じゃないだろ、と。

uint8_t Adafruit_MCP23017::readGPIO(uint8_t b) {
	for(;;)
	{
		for(;;)
		{
			Wire.beginTransmission(MCP23017_ADDRESS | i2caddr);
			if (b == 0)
				wiresend(MCP23017_GPIOA);
			else {
				wiresend(MCP23017_GPIOB);
			}
			if(!Wire.endTransmission())
				break;
			Wire.reset();
		}
		if(Wire.requestFrom(MCP23017_ADDRESS | i2caddr, 1) >= 1)
			break;
	}
	return wirerecv();
}

 途中で配線が外れたなどでエラーが回復しないと無限ループに突入するので、あまりよくない書き方ですが、Wire.endTransmission()、Wire.requestFrom() ともに、省略しがちなエラー判定をきちんと行わないとヤバいです。


 エラーが出たら reset() という対処方法は根本的な解決じゃないんですけど仕方ありません。
 取り急ぎ報告まで〜


(補足)
 きっと I2C が遅いせいだと思いますが、吸い出し速度は 5〜6秒/KB という鈍亀状態。
 検証に要する時間が数時間から十数時間というオーダーでして、当然にして放置させておくんですが、失敗していたときの絶望感と言ったら半端ありません。
 学習のための教材としてはいいけど、実際に完成しても実用性はないかもね


(最終報告)
 検証用に、とある筋から入手した正規の ROMイメージと比較してみたところ、219126バイト目で最初の不一致(これは1バイト)が発生し、その後は数十KBおきに1つ2つパラパラと発生、後半になるにつれてエラー率が向上という顛末でした。
 「219126バイト目」って部分は試行するたびに変わります。。。


 8388KB中の前半8013KBに限っては正誤率 99% を超えてるんですけど、頑張っても 100% には出来そうにありません。

  • そもそも I2C 自体にはエラーチェックの機構がない
  • それに加えて ESP32 の I2C 実装には致命的な問題が潜んでいる

 とりあえず今回の作戦は断念ですねー


(追記)2018/05/01
 I2C接続な気圧センサー LPS25H/LPS331AP でも I2C のエラー対策を行っていない素の状態だとフリーズする現象に見舞われたので、Fork して対策しました。
 上で書いた件と同様に、途中で配線が外れたなどでエラーが回復しないと無限ループに突入するので感心した書き方ではないですが、回避の例としてご参考ください。
ESP8266/ESP32のI2Cバグ対応 · wakwak-koba/lps-arduino