« 2017年7月23日 - 2017年7月29日 | トップページ

2017年8月27日 - 2017年9月2日の1件の記事

2017年9月 2日 (土)

ソフトI2Cはクロックストレッチまで手を出してあえなく沈没

 I2Cのスレーブライブラリの改訂が泥沼に入り込んだまま動けない。アナログの超音波距離計測センサーHC-SR04(以下SR04)を、電源3.3V、I2C化したブレークアウト基板を作ったところまでは良かったが、そのあと、計測を高速で動かそうとして、ソフトI2Cスレーブのプログラミングに大苦労し、気が付いたら一か月が経っていた。

 そもそも、このTiny85を使ったソフトI2Cのライブラリーは、もう2年も前に開発したもので、ソースコードまで公開している。今度の開発でいくつかの不具合が見つかったので、改訂版を出せばそれですむ話なのだが、せっかく再公開するのだから、もう少し機能を増やしておこうと欲を出したのが間違いの始まりだった。Dsc01170

 ソースコードをレビューしているうち、最初の版は、I2Cのマスター読み込みのストップコンディション検知が未実装だったことがわかった。ブログ記事では作ったことになっているが、ソースコードを読んでみると実装されていない。

 スレーブからマスターへの送信(マスター読み込み)の終了は、最後のデータバイトにマスターがNACK応答を返せば止まるので、ストップコンディションは不要と言えば不要である。しかし出来ていると書いてしまった手前、放置するわけにもいかなくなった。それにマスター書き込みの方は、リピートスタートまで(もちろんストップも)実装したのに、何となく落ち着きが悪い。

 そこで、これを追加しようと気楽にコードを加えたのだが、これがうまくいかない。出来上がったプログラムはどうやってもストップコンディションを受け付けないのである。マスターはストップコンディションを出しているはずなのにロジアナで見ると波形が出ていない。謎である。

 脱線につぐ脱線というのが、がた老AVR研究所の通例ではあるが、今回はどうもいけない。迷路に入りすぎだ。I2Cのスレーブ側のプログラムは、すべて割り込み駆動で、SR04を制御するバックグラウンドルーチンとは非同期で同時に動く。デバッグは一筋縄では行かない。

 こういう細かいことに興味のない読者の方々には申し訳ないが、実は所長は、こうした箱庭のような8ビットマイコンのマルチタスク的プログラミングが好きで、つい深入りしてしまう。ロジックアナライザーで全体の様子が手に取るように把握できるようになったので余計やめられなくなっている。

 以下は、この泥沼のようなプログラム開発の一か月の顛末である。しかも今回は撤退に次ぐ撤退の苦しい開発となった。まあ、こういうときもあると自分を慰めている。

懸案のI2C通信と計測の同時処理はあきらめる(8/6/2017)
 前回記事で、SR04のエコーと、I2C通信の共通変数(タイマーキャリーカウンター)を分離して、正しいデータが出たと喜んだのもつかの間、少し待ち時間を短くしていくとまたデータが不正確になってきた。どうも距離の計測値が低すぎるし、安定した値がでてこない。

 ソースコードをもう一度入念に調べる。すると、共用していないはずのタイマールーチンに続々とおかしな使い方が見えてきた。要するに被っていたのはキャリー変数だけではなかったのである。

 まず、肝心のタイマーのおおもとのレジスターTCNT0をI2Cが始まるたびに0に戻していた。最大でも0.26ms(8ビットのオーバーフロー)、音速にして5センチ余りの誤差は、今度の用途(人感センサー)を考えると、そう大きな問題ではないが正確でなくなることには間違いない。Ws000029 さらに、I2Cの通信が終わるたびに、タイマーのオーバーフロー割り込みを止めてしまっていることが判明した。タイマーそのものは動いていても、8ビットタイマーのキャリーが増えていかないのは致命的だ。これが、ときどき距離が不正確になる犯人であった。

 この一週間、デバッグに奔走していた。調べれば調べるほど、エコー時間計測とI2C通信の非同期処理は不可能であることがわかってきた。要するにタイマーが一つしかないところで、タイムアウトと計測を一緒にやろうというのが無理筋だったのである。

 とにかく、こんなにリソースを共用していたらマルチタスクもあったものではない。計測期間中にI2C通信を行うソフトの開発は中止することにした。今回の無駄足は疲労感が強い。もっと早く気が付くべき基本のところを忘れて、深入りしてしまったからだ。 

 当初の方式、センサーのエコーを少し長めに待ち(SR04の最大エコー30ms程度)、データを読み込む方式に戻した。これでも100ms以内で連続して計測が出来る。まあ、人感センサーならこの程度で十分だろう。

マスター読み込みのストップコンディション検知にチャレンジ(8/16/2017)
 エコー期間中にI2Cを動かして計測間隔を短縮しようという目論見は完全にはずれた。意気込んでプログラミングをしようと振り上げた拳(こぶし)の行き先がない。この高ぶった気分を鎮めるために、何か手を動かさないと納まりがつかなくなってしまった。

 そこで、未実装の「マスター読み込み」シーケンスのストップコンディションの検知機能を開発することにした。しかし、これも難儀したのである。しかけは、「マスター書き込み」と同じように最初のデータビットのクロック(SCL)立ち上がり区間で、データライン(SDA)を監視して、ストップコンディションを判定する。

 「マスター書き込み」では、スタートコンディション(これはリピートスタート)もストップコンディションも順調に検知し、動作も確認している。簡単に動くだろうと気楽にコーディングしたのだが、全く動かない。かえって、全体の動作が不安定になってしまった。Dsc01195

 いつものロジックアナライザーにプローブ点を増やして解析する。波形を見ると、マスターがストップコンディションを実行しているのにSDAが上がっていない。つまりストップコンディションが発生していない状態であることが分かった。謎である。

 メモに何枚もタイミングチャートを描いて調べていく。ひとつづつの動きを詳細に確認していくと、驚くべき事実が浮かび上がってきた。「マスター書き込み」なら容易に出来る処理は、「マスター読み込み」では不可能ではないかということである。

ソフトでストップコンディションは作れない(8/20/2017)
 誤解を招かないように補足すれば、ソフトウエアでは実現できないということである。ハードウエアならSCLがHIGHのときのSDAの動きを監視する機構を加えれば良いので問題はない。しかし、ソフトでは同時処理が出来ないので、「マスター読み込み」ではSCLクロックが来る前に、スレーブは次に送るべきビット値を用意してSDAに乗せておかなければならない。

 もし、その値がLOW(0)のときは、ストップコンディションを阻止してしまい、スレーブ側ではそれを検知できないということになる。そもそも、「マスター読み込み」はスレーブ側がSDAラインを上下させてデータをマスターに送る。スレーブがSDAを下げてしまうと、マスターはこれをHIGHにすることが出来ない。

 元から不可能な機能を作ろうと、もがいていたことになる。ack/nackビットを受け取って次のデータの第一ビットの間中SDAラインをスレーブは占有してしまうので、スタート/ストップコンディションをマスターが出せないのである。「マスター書き込み」では、SDAラインはマスターが制御するので問題ない。

 とにかく、「マスター読み込み」のとき、ソフトでスレーブ側のストップコンディション検知は実装できないことが明らかになった。この2週間余りの作業は全く無意味なものになった。お馬鹿としか言いようがない。エコー期間のI2C騒動と、このストップコンディション開発を撤退するにあたり、反省をこめて何が悪かったのかまとめてみる。

教訓:

  •  一度に複数の機能をデバッグしてはいけない。どれが原因か特定するのが難しい(欲張るな)。プログラムをなめてかかって、いくつかの機能をまとめてコーディングしていた。原因が見つけにくいうえに、単独でバグをつぶしていくより、結果として余計な時間がかかっている。
  • 基本的な機能から確認していかないと何をやっているのかわからなくなる(急がば回れ)原理を理解せずに、小手先のコーディングが多すぎた。タイミングチャートをいくら詳しく描いて、コードを工夫しても、I2Cの基本を忘れていたら何にもならない。
  • 思い込みは可能な限り見直す(これは定数だから変わらないと思い込むな)わかっているはずなのに、この思い込みというやつは魔物である。すべてを疑うという視点をなくさないようにと気を配っていても、つい先のことに気を取られて基本部分を忘れがちになる。

 やれやれ、現役時代、さんざん後輩に言い聞かせてきた原則ばかりである。このブログにも偉そうに何度も同じようなことを書いてきた。それがこのざまである。お恥ずかしい。まさしくプログラムは考えたようには動かない、書いたようにしか動かない、である。

 この記事はあとから書いているので、まだ冷静な話になっているが、デバッグの最中は暗中模索、ロジアナの前で悪戦苦闘であった。もうやめようかと何度も思った。Tiny85のファーム書き換えはSR04を外す必要があり、ビルドを繰り返しているうち、ブレッドボードの接触不良で動作が不安定になったりする。散々である。

 ただ、ロジアナの威力は今さらながらすごいと感動した。プローブの数を増やしていけば、色々な所の可視化が可能になる。今回も、マスター側からもプローブを出せることに気づいたのは大きかった。これによって、マスター側が正常に動いていることを確認でき、自分の愚かな思い込みに気づくことが出来たからだ。

超音波センサーユニットの配線図とライブラリソースコードの再公開(8/19/2017)
 とにかく開発は一段落した。ライブラリーも安定して、これまでのように時々、0値が連続したり、異常値が出たりすることはなくなった。思い当たる不具合は、これまでのところ全部つぶした。

 最後までわからなかった偶に起きるデータ異常値は、ロジアナの波形を精密に調べなおして、SCL割り込みがタイマー割り込み(オーバーフロー)によって遅れ、その結果、SCLクロックが外れているのを発見した。I2cslaveoverhead

 タイミングによっては、これがスタート/ストップコンディションとみなされ、そのあとのデータが欠落する。Tiny85のCPUクロックは8MHzで、内蔵CRクロックなのでこれ以上は上げられない。SCL割り込みのオーバーヘッドは8μs内外で、クロックが40Khz(半サイクル12.5μs)のI2Cでは、ギリギリである。

 I2Cはこれ以上遅くしたくないので、タイムアウト用のタイマーの割り込みを起こさないようにするしかない。プリスケールを8から64に上げ、メッセージが16文字以内なら割り込みが起きないようにした。テストの結果、20文字以上送っても、数値の乱れはなくなった(必ず起きるわけではない)。

 お約束のソースコードと配線図を掲載する。I2Cライブラリーそのものは上記のようにすべて改善に失敗したが、マスター側とSR04制御部には若干の機能を追加してある。
 以下に例によってZIPで固めたソースライブラリ、readme.txt、配線図ファイル(.bs3)を置きます。 
   「I2CslaveSet_2.zip」をダウンロード

 コマンドの解説など詳しくはreadme.txtに説明した。以前の公開したライブラリーはうまく動かないところもあるので、この版のライブラリを使われることをお勧めする(当該ブログ記事にも注記)。

I2CスレーブライブラリーとSR04測定ルーチンの改善点(8/21/2017)
 テスト用の親機のTiny861とのペアで追加した機能と、修正した不具合の部分を以下にまとめておく。

I2cslave_85_2

追加した機能:
(1)    連続距離測定
SR04の距離測定が0.2秒間隔で開始され、UARTコンソールに測定値が1行づつ表示される。人感センサーなどで機器を動かしながら最適位置を見つけるのに便利。リターンキーを押すことで測定は中止される。

(2)    スレーブ側からの独自データ送出
サンプルとして、スレーブ側のソフトのバージョン番号をフラッシュに定義し、それを表示させる。I2C化するディバイスのレジスター値などを表示するのに使える。

(3)    バッファーデータの16進データ表示
スレーブの持つ送受信バッファー(36バイト)を全部表示する。本来はデバッグ用。

修正した不具合
(1)    マスター読み込みでのスレーブ側の不正データ送信のバグ修正
タイマー割り込みのオーバーヘッドで、送ったデータが欠落したり、文字化けが起きていたのを修正。

(2)    スレーブ側の連続データ送出でのバグ修正
マスターから送ったデータの戻しでバッファーサイズ分を全部表示できなかったのを修正。

 いやいや、長い間かかったけれど、何とか納まった。ウェブで見る限り、AVRマイコンでソフトI2Cスレーブの開発例は殆どない。あっても、すべてUSIというAVR独自のハードインターフェースを使ったもので、この開発のようにGPIOだけで作った例はひとつもない(自分で探した限り)。

 ちょっと鼻が高いのだが、これは完全な自己満足の世界である。ということで、さらに挑戦しようと、欲を出して恥の上塗りをすることになった。それが、次に紹介するクロックストレッチの実装である。まあ、笑わずに聞いてください。

クロックストレッチ実装の野望、もろくも砕ける(8/23/2017)
 I2Cは、原則、すべての通信の主導権をマスターが握っており、マスターの出すクロック(SCL)に従って複数のスレーブが動作する。これに対し、クロックストレッチとは、I2C通信でスレーブが、マスターを待たせる唯一の手段である。

 スレーブの処理速度が遅く、マスターの要求に応えられないときなどに使う。SCLラインをいずれかのスレーブがLOW(0)に落とすことによって、マスターはSCLの発行を止め、スレーブが解放(HIGH)にすることで再開する。
実装は簡単に見える。マスター側で、クロックライン(SCL)を監視していて、誰か(スレーブの誰か)がSCLをLOW(0)に落とせば(いわゆるワイアードNOR)、それがHIGHになるまで通信を止める(クロックパルスを延期する)。

 スレーブ側はもっと簡単である。必要な時にSCLをLOWにしてしまえば、すべての交信はストップする。また、HIGHに戻せば、マスター側が待っているから再開する(はず)。鼻歌交じりで、双方をコーディングし、勇んでテストに入った。これが地獄の始まりとは露知らず。

 テストの前は、クロックストレッチが実際に行われているかの実測のため、タイマーを再整備したりして余裕だったのだが、実際には全く動かなかった。スレーブでクロックストレッチをかけた途端、すべての反応は止まる。ハングアップである。

 ロジアナで様子を見る。通信シーケンスは、スレーブがクロックストレッチをかけたとみられるところから、見事にSCLがLOWになったまま、最後まで動かない。あれ、おかしい。マスターが待つのはともかく、スレーブがどうして引きすられる?

 スレーブには1秒タイムアウトが設けてあって、I2Cの通信が1秒以内に終わらないと通信を終了させて初期状態にもどるようになっているはずだ。

 あっあっあー、だめだ。スレーブ側の割り込みプログラムのなかで、SCLがHIGHになるのを待っている。これではバックグラウンドのコマンドでいくらSCLを元に戻そうにも、永遠にバックグラウンド側に戻ってこない。いわゆるデッドロックである。

 悪あがきで、多重割り込みでスレーブ側に短いタイムアウトを設け、別のところで待つようなしかけも考えた。avr-libcには多重割り込みを認めるマクロはあるが(リニアPCMプレーヤーで使用)、今度の場合は、バックグラウンドに戻っても、スレーブの割り込みプログラムに帰れないので意味がない。

 クロックストレッチをスレーブ側が要求するとき、あらかじめ割り込みルーチンの進入を止めておくという姑息な方法もあるが、マスター側の通信要求がいつ発生するか分からないときに、スレーブを止めるのは著しく可用性を失う。

 要するに、ハードでのみ実現できる機能で、そもそもソフトでやろうというのが無謀だったのだ。クロックストレッチという誰も実現していない機能を実現してやろうという野望は、もろくも消えた。

 やれやれ、三度にわたる敗退である。暫くは立ち上がれそうにない。ただ、この電子工作は、定年後の重要なライフワークのひとつになっており、もう今さら止めるわけにはいかない。

 今、考えている次のテーマは、ESP32で、課題になっていたSPIFFS(フラッシュメモリを使ったディスクシステム)だ。フォーラムなどを見ていると、ESP-IDFという、Arduinoとは違うプラットホームで動き始めたようだ。これを試すのも悪くないかと思っている。

| | コメント (2) | トラックバック (0)

« 2017年7月23日 - 2017年7月29日 | トップページ