I2Cで通信を続けていると、エラーが発生してACK/NAKがおかしくなったり、STOP Conditionが送れなかったりする事象が時々発生する。特に、STOP Conditionが送れなくなると通信を終了することができず、詰んでしまう。そこで、どうしてこのような状態が発生するのか、どうやったら復帰できるのかを解説したい。

 上の図は、一般的な送信データ例を示したものである。Master側はSCLに同期してSDAを操作してデータを送信する。ここで重要なことは、SCLがHighの間にSDAをHighからLowに引き下げるのがSTART Condition、SCLがHighの間にSDAをLowからHighに引き上げるのがSTOP Condition、データの送受信はSCLがLowの間にSDAを変更して行う、ということである。SCLがHighの間にSDAを動かすとStartとStopになってしまうので、データ送受信はSCLがLowの間に切り替え、SCLの立上りでデータを取り込む、ということである。

 さて、時々エラーが起きてしまう。その原因は、SCLのエラーに起因する。クロックが間違って挿入されればSlaveのデータポインタはMasterより進んでしまう。クロックが欠落してしまえばSlaveのデータポインタはMasterより遅れてしまう。このような現象が起きると、MasterとSlave間のデータポインタはずれてしまい、正しい挙動ができなくなる。

 上の図はクロック挿入を示したものである。Slaveはクロックに同期してゴミデータを受信し、MasterがD0を送るタイミングでACKを返そうとする。そして、MasterがACK受信を期待しているタイミングではSlaveは何も送らない。従って、Busを誰も使っていないため、Pull-up信号であるHighを受信し、NAKと判定してしまう。

 上の図はクロック欠落を示したものである。SlaveはクロックがないのでMasterが送ろうとしたD5を取り込まない。MasterはD0を送った後にACKを期待するが、この時点でのSlaveはD0を取り込もうとしているので受信状態となり、Busを誰も使わない。従ってBusはHigh状態になる。つまり、NAKとなる。

 こういった問題が発生し、同期が狂ってしまうと復帰が難しい。リセットするしかない。それにはSTOP Conditionを送ればよいのだが、Stopを送るためにはBusがHighになっていないといけない。しかし、同期が狂っていて、SlaveがBusを占有し、SlaveがLowを出しているとStopを送れない。それを解消するために、ダミークロックを挿入する。では、いくつ入れるか?これは同期ずれ数によって変わる。そのため、SCLとSDAを汎用Portに切り替え、SCLをHigh、Lowと切り替えていく。そして、LowにしたタイミングでSDAがHighになった時がチャンス!この時にSDAをLowにして、直後にSCLをHigh、SDAをHighと変化させる。つまり、STOP Conditionを送るのである。送れるのは、SDAがHighになっている時だけなので、このチャンスを逃さないように(大したことないけど)。このように、ダミークロックを送りつつSDAを監視し、SDAがHighになったらStopを送出することで通信をリセットできる。Stopを送出後、再度ペリフェラル設定をやり直して通信したところ、普通に通信できた。つまり、復帰できたのである(データ送信途中で意図的にStopを送って通信をリセットできることを確認済)。本件はマイコンがSTマイクロ製(STM8L151C8T6)、SlaveデバイスがTI(BQ76940)とLinear(LTC2991)のデバイスで検証した。他のマイコンとICの組み合わせで動作するかはわからないが、参考になれば幸いである。