Arduino で I2C をやろうと思ったら、まず wire ライブラリを使うと思います。
そして wire ライブラリを使うと必ず、タイムアウトが無いことに苦しむと思います。
私が苦しんだのは Start Condition の無限ループでした。
wire ライブラリでは Master Read または Write を実行する際に Start condition が取れないと、Start condition が取れるまで待ちます。
いつまでもいつまでも待ちます。
そのため、うっかり Pull-up していない SCL/SDA に Arduino を繋いで I2C を実行すると Start condition なんて取れるわけがないので無限ループに入って Arduino がフリーズします。
小手先の対策として、I2C を実行する前に SCL/SDA の状態を確認して、いつまでもHIGH にならない場合はタイムアウトさせる処理を追加してみたりもしました。
int cnt_to = 0; while(1){ if(digitalRead(SDA) == HIGH && digitalRead(SCL) == HIGH){ break; } else{ delay(10); cnt_to++; if(cnt_to == 10){ break; } } } if(cnt_to == 10){ // タイムアウト処理 } else{ // I2C 実行 }
この対策でもほとんどの状況では無限ループを回避できていたのですが、ある特定の条件において SCL/SDA ともに HIGH を検出しながら Start condition を検出できないという謎な現象のぶつかったことから不満が爆発し、根本原因の解消に乗り出すことにしました。
最初は wire ライブラリの改変で何とかしようと思ったのですが、さすがライブラリだけあって私の理解を超えた記述の山です。それでも頑張って Start condition の無限ループの記述を見付けたのですが、私の知識では改変できませんでした。色々と書き換えたり書き加えたりしてみたものの、どうやっても思った通りの動作になってくれません。
結局、ライブラリの改変は諦めるに至ったのですが、その過程で wire ライブラリが Arduino の本体のマイコン ATMEGA328P のレジスタを叩いて I2C を制御する様子が見えました。
そこでネットからマイコンのデータシートを拾って読んでみたところ、レジスタを叩いて制御する方法は思ったほど難しくない・・・というか、wire ライブラリをどうにかするよりも遥かに簡単なことに気付き、それならもう自分でイチから作ってしまおうと考えたのが始まりです。
次の記事からは実際に作った関数とその中身を解説します。