Tiny85のI2Cスレーブライブラリーのソースコード公開
いま流行(はやり)の電子工作というのならArduinoなのだろうけど、こちらは今やイギリスの半導体メーカーに買収されてしまったAtmelの8ピンマイコンでソフトI2Cを自作している。こんな遊び方は、時代錯誤になりつつあると思うが、これが当人には結構面白く、ハマっている。
I2Cは必要ラインが少なくてすみ(2線。グランドを入れて3線)、バス接続が出来るのでピン数の少ないマイコンでは定番の通信インターフェースである。専用のハードではなく普通のI/Oピン(GPIO)を使ってソフトウエアでこのI2Cを実現したのものをソフトI2Cと呼んでいる。
ただ、GPIOによるI2Cインターフェースのソフトは、マスター側がほとんどで、スレーブ側の制作例は珍しい。マイコンがマスターになって、スレーブであるI2C機器を制御するというのが一般的だからだ。スレーブをソフトで作るというのは今度のように、I2Cのない周辺機器をI2C化するときくらいに限られる。
前記事までに、I2Cスレーブソフトは、目標のTiny85で一応動いたのだが、これは当面の目的である超音波距離測定ユニットHC-SR04を動かす部分の機能だけで、汎用的な用途にはまだ不安がある(連続データの送受信など)。
前回、ソースコードを公開しなかったのは、せっかくここまで動いたのだから、もう少し、色々なところで使えるように機能を拡大してからと考えたためである。 追加の開発は楽に出来ると思っていたのだが、実は結構てこずった。
ああでもない、こうでもない、と試行錯誤の結果、何とか安定して動くスレーブインターフェースの開発に成功した。もうソースコードを公開しても恥ずかしくないレベルに達したと思う。誰かが使ってくれると嬉しい。
I2Cスレーブはユーザーインターフェースが難しい(10/10/2015)
I2Cインターフェースのスレーブ側は基本的には完全な受け身で自らは何も動き出さない。マスター側のトリガーをひたすら待ち続ける。スレーブのメインプログラムは無限ループでマスターからの信号を待ち、通信が始まったら、マスターの出すクロックの指示をすべて守らなければならない。
こういうときは、マスターからの信号は割り込みで処理するのが一般的である。マスターからのデータはひとつも落とせないからである。このためにスレーブ側のメインプログラム(対象機器の制御、データの受け渡し)は、I2Cとのやりとりの合い間に動くバックグラウンドタスクとして、非同期で動かす必要がある。
こうした方法の定番というのを分かりやすく解説している参考書やウェブの記事を、これまで見たことがない。どういうやりかたで作るのが良いのかいつも悩む。このあたりのロジックや、アルゴリズムは、マルチタスクを制御するOS(オペレーティングシステム)のしかけそのものなので、市販の参考書はそれを意識して、たいてい仰々しい書き方になっている。実務的にとても理解しにくい(初めて読む人にはわけがわからないだろう)。
最近はOSをやさしく紹介する本がいくつか出ているが、実際のソースコードまで落としたものは少ないようだ。目的がOSを理解させることで、具体的にソフトを動かして何かの用を足すことを目的にしていないからである。
前記事までのソフトI2Cスレーブのメインプログラムは、あくまでもHC-SR04を動かすために特化しており、このままでは他の用途に使いにくいし、汎用的に使うにはまだ機能が不足している。ちょうど良い機会なので、他にも応用が出来るような構造にし、併せて解説も少し足し、これから同じようなことに挑戦される方の参考になることを狙うことにした(誰もいないか)。
現在のライブラリの形式は、I2Cインターフェースそのものはi2c_slave.cというソースファイル1本に集中させ(ヘッダーファイルは、i2c_slave.h)、アプリケーションに依存するコマンド解釈などの機器制御とデータの受け渡しは、メインプログラムi2c_SR04_85.cにまとめてある。
従って、違う機器(LEDや、液晶表示装置、温度センサーなど)をI2C化するときは、このメインプログラムだけを目的に合わせて変更すれば良いようになっている。前にも触れたように、このあたりは定式化したやりかたがない(もちろん、知らないだけという可能性もある)ので、がた老AVR研究所なりの方法論で話を進めることをご了承願いたい。
やり方は次の通りである。まず、使いそうな機能をいくつか想定して、実際にそれを実装する。このしくみを詳しく説明すれば、メインプログラムと、割り込み駆動のI2Cルーチンとの間の動きが明確になり、他への応用が容易になるのではないかという狙いである。
連続データの送受信が非同期で出来れば文句がないだろう(10/14/2015)
どんな機能が役立つのかは、このI2Cスレーブを使う具体的な例を考えて、それを基に決める。色々検討したが、やっぱり今度のように非I2C機器をI2C化することが一番多いのではないかという結論になった。
LCDなどの表示装置は既にI2C駆動のものも多いが、パラレル駆動のLCDや、LEDマトリックス、グラフィックLCDなどは、必要なI/Oピンを少なくできるのでI2C化するメリットは多い。ただ、これらの表示装置は、マスターからスレーブへの出力が殆どなので、I2Cとしてはそう難しくない。さらに、全体の処理速度を早くするために、多バイトのデータが一気に送れる機能があれば、もう文句はないだろう。
一方、EEPROMやRTC(リアルタイマークロック)などの入力ディバイスは、スレーブからマスターへのデータ転送が必要になる。こういうディバイスには、マスターからコマンドを受け、すぐにリピートスタートで求められたデータをスレーブからマスターへ送り出す機能も必要になるだろう(この機能はこれまでの記事の通り開発済み)。
こうしたことから、汎用的なモデル機能を以下のようにまとめた。
(1) UARTコンソールのように、マスターから一定の長さのテキストを送り、スレーブ側で蓄積し、コマンドでそのデータを送り返す機能。
(2) スレーブ側に何らかのデータを用意し、マスターからのコマンドに応じたデータ値を1シーケンスでマスター側に送り返す機能。
(3) マスターからのコマンドによって、スレーブ側に接続した機器を制御する機能。これは(2)のバリエーションで、今回は、超音波距離センサーなどの起動トリガーコマンドがそれにあたる。
連続データのマスター書き込み(送信)は簡単に片付いた(10/15/2015)
まず、(1)の擬似UART的な機能の送信の部分である。マスター側のコンソールから連続したデータ列をリターンキー(\r)を終端文字として一気にスレーブに送り、それをバッファーに一旦貯めた後、別のコマンドで一気にエコーバックさせる。
これまでの開発で完動したのは、HC-SR04での、マスターからの1バイト送信と2バイト受信だけなので、今度は、スレーブの受信リングバッファーがオーバーフローしないように、スレーブのメインプログラムと割り込みルーチンがマルチタスク的に動く必要がある。
メインプログラムで、割り込みルーチンのリングバッファーのポインターを常に調べ、割り込みルーチンでデータが入って、ポインターが進めば、すかさずデータを本体の配列に収容する。UARTなどでいつもやっている方式と同じである。
マスター側のドライバーにコマンドを新設し、;(セミコロン)のあとの文字列が一気に送られるようなコードを加える。コーディングは簡単にすんだ。テストである。よーし、スレーブ側のデバッグ用のコンソールに送ったデータが綺麗に並んだ。これは問題なく動いた。
次のマスター連続受信(スレーブ送出)が難しい(10/16/2015)
(1)のもう一つの機能、マスター連続読み込み(受信)である。マスターからのコマンドによって、配列などに貯められたデータを一気にスレーブ側から、マスターに送り返す。スレーブ側は待つだけで、駆動するのはマスター側のクロックであり、スレーブはそれに従ってデータを載せて行く。
ここで問題が発生した。書き込みのときは、コンソールのキーボードから打ちこまれた終端文字のリターンキー(\r)文字を見てマスターがストップコンディションを発行し、スレーブ側はこれを検知する機能があるので(前々記事参照)、問題なく通信が終了する。
しかし、マスター読み込みの終了は、最終文字のACK/NACKビットでマスターがNACKをセットし、スレーブに返すことになっている。マスターが、送られてきた最終文字(リターンキー)のACK/NACKビットに、NACKをセットすることは時間的に不可能なので、マスターはリターンキーであることを知ったあと、ストップコンディションを挿入することになる。
うーむ、困った。スレーブ側にはマスター読み込み時のストップコンディション検知機能がまだ実装されていない。固定量のデータならマスターが最終文字にNACKをつけるので正常に止められるが、このままでは、可変量の連続読み込みの送信終了は受け取れないことになる。どうしよう。
ここまで来て、引き下がるわけにはいかない。乗りかかった船である。マスター読み込みのステートマシンに、ストップコンディションを検知するロジックを追加した。コードそのものは、マスター書き込みのところと同じなので簡単に出来た。ただ、フラッシュサイズは3Kバイトを超えた。
しかし、これがどうやってもまともに動かないのだ。ストップコンディションを検知できなくて、すべての通信でタイムアウトしてしまう。ロジックアナライザーを再び引っ張り出して、ステートマシンをプローブして、原因がわかった。懸念していた通り、処理が次のクロック(SCL)パルスに食い込んでいる波形を発見した。クロックが8Mhzでは、今の50khzのI2Cが通らない。
結局、マスター読み込み(スレーブ送信)でのストップコンディション検出はあきらめる。となると何か回避策を考えなければならない。良い方法を思いついた。
リターンキーを受信したら、とにかくマスターはもう一文字受信し、この文字にマスターがNACKビットをつける。こうすると、スレーブに通信終了を知らせることが出来る。マスターはこの文字を捨てればよい。ちょっと姑息だけれどなかなか洒落た方法だ。
スレーブ側の、NACKによる終了はこれまで正常に動いている。上機嫌で自信満々テストに臨んだ。ところが、これでも通信は正常に終わらない。つねにタイムアウトしてしまう。ロジアナで見ても正常な波形だ。もう調べるところがない。完全に暗礁に乗り上げてしまった。
旧友会が2回(10/18/2015)
息抜きに、ここで、ちょっと電子工作以外の話題をご紹介する。同窓会の話題である。これまで、時々、話してきた小学校や高校の同窓会ではなく、珍しい古い友人からのお誘いが偶然に、続けて2つもあり、久しぶりに飲み会をやった。
ひとつは、40年前、地方に転勤した時に一緒に仕事をした会社仲間の旧友会で、もうひとつは、同じ会社ではなく、20年前に仕事で親しくなった取引先の友人の同窓会である。どちらも10年くらい前までは頻繁に会っていたが、このところご無沙汰だった。
それぞれ、出席者の殆どはもう現役を離れている。まず、会うなり「病気と、孫と、髪の毛の話は禁止」という宣言が出て大笑いした。良く見るとみんな相当な禿頭だ。まあ、それはともかく、昔話というのはいつになっても楽しいものだ。結論から言えば、どちらもとても楽しかった。
会社の旧友会には実は余り顔を出したことがない。現役の頃の上下関係をいつまでも引きずる人間が少しでもいると周囲の雰囲気が悪くなり気分よく話が出来なくなるからだ。今回は幹事が、人を選んでくれたようで全く違和感なく昔話を楽しむことが出来た。
こうした旧友会は、自分の人生を遠くから見るような解放感がある。この年になると、現役時代の熱い感情的な部分が削ぎ落とされて、自らを、良くも悪くも客観的にみている自分を発見する。今さら何が出来るわけでもない。達観した見方が出来るのだ。
2つの会合とも、久しぶりに酒が進み、帰りが大変だった。まあ、何とか人の迷惑にならずに帰宅できたが、この年で深酒はほどほどにしておいた方が良い。自戒、自戒である。
マスター連続受信(スレーブ送り出し)終了条件の謎(10/21/2015)
電子工作の話に戻ろう。いやあ、今回も解決までが長かったが、苦労の甲斐あって、これまで不安定な動きをしていたマスター読み込みは、完全にNO ERRORで動作するようになった。
今回の不具合は、前のバグのように、お馬鹿なミスが原因ではなくて、複雑な要因で起きていたバグだったが、少しづつ事実を積み上げ、思い付いた仮説を検証しながら、最終的にバグの原因を解明できたので、すこぶる機嫌が良い。迷宮入り寸前の難事件を見事に解決した名探偵の気分である。
マスターからの連続書き込みは簡単に成功したのに、マスターが連続して読み込む方は、どうも調子が悪い。マスター読み込みは、スレーブがデータを送った後、マスターからNACK/ACKビットが返ってくるので通信の終了はストップコンディションが出なくても通信終了になるはずなのに、現状は殆どの通信がタイムアウトになってしまう。
気に入らないのが、すべてがうまくいかないのではなく、時々正常に終わるときがあることだ。何かのタイミングで起きたり止まったりするというのが、一番厄介なトラブルである。ステートマシンにプローブ(SR04のトリガーピンを流用)を入れて動きをロジアナで確かめる。
確かに、NACK/ACKビットを調べるステップで、マスターはNACK(1)を返し、そこをステートマシンが通過していることは間違いない。それならタイムアウトフラグはここでクリアされ正常終了するはずだが、そうなっていない。
そうだとすると問題はそのあとだ。一連の通信が終わったあと、割り込みルーチンはステートマシンを初期状態に戻し、割り込みをクロック(SCL)からデータ変化(SDA)に替えて、次のスタートコンディションを待つ。このあたりがうまくいっていない可能性が高い。
しかし、マスターは、NACKを返した後、ストップコンディションを発行している。ロジアナにもはっきりした波形が出ている。たとえ、NACKでリセットできなくても、ここでストップコンディションによって割り込みルーチンは初期状態に戻り、リセットされるはずだ。それなのにタイムアウトになる。二重におかしい。
遂にマスター連続受信に成功。原因はNACK処理ではなかった(10/25/2015)
謎は深まるばかりだ。そこで、今度は、ステートマシンではなくて、スタート/ストップコンディションを受ける初期状態のデータ(SDA)の割り込みにプローブをかけてみた。本当にリセットされてこの割り込みを受けているか確認するためである。
確認したからと言って何か成算があるわけではないが物は試しである。半信半疑でロジアナを動かす。問題の通信終了のシーケンスを拡大する。おやあ、ACK/NACKビットを受けるステートマシンの直後に、スタート/ストップコンディションを受ける割り込みが2つも発生しているではないか。
先に触れたストップコンディションの時に発生するのは想定通りだが、その前の割り込みは何なのだ。あっあっあー、わかったぞ。マスターがストップコンディションを出すための準備に、SDAをLowにする準備の部分を拾ってスタートコンディションにしてしまっている!
NACKのリセットのあと初期状態に戻るのが早い割に、実際にSDAの割り込み処理に来るオーバーヘッドが長すぎ(17μsもある)、次のSCLが立ち上がってしまってスタートコンディションと誤認してしまったのだ。
しかも、一旦、スタートコンディションと認知してしまえば、割り込みはSDAからクロック(SCL)に移り、このあといくらストップコンディションが来ても無視される。時々動くのは、SCLの立ち上がりの前に割り込みルーチンに来るときがあるからだ。このときは問題なく動く。よーし、間違いない。すべては氷解した。
原因はわかった。しかし、SDAの割り込み遅れを早くすることは出来ない。そもそもは、ACK/NACKビットのSCLの立ち上がり中にリセットしてSDA割り込みを待ち受けること自体が違反だ。ここはSCLが下がって次のステージに入ってから、スタートストップコンディションを待ち受ける体制にするべきなのだ。
ACK/NACKビットのステートマシンのところに、SCLが次のブロックに行くまで待つプロセスを入れて、これまでのタイムアウトは完全に解消された。連続データは勿論、(2)のリピートスタート処理も全く問題なく動く。
いやあ素晴らしい。仮説に基づき、ロジアナで少しづつ事実を追いながら原因を突き止めた。「デバッグは外へ、外へ」という格言どおり、原因は意外なところに潜んでいたのである。
ソースコードの公開。readmeもつけた(10/30/2015)
紙数が増えてきたので、(2)や、(3)の機能の説明は、添付するソースコードのフォルダーの中にあるreadme.txtにまとめて書いたので、そこを参照頂きたい。回路図も念のため掲載しておく。
冒頭でも述べたが、このソースコードはSR04というセンサーユニットをI2Cで動かすための機能と、連続データの入出力などのテスト機能が入った、汎用的なモデル機能をデモするプログラムで余り実用的なものではない。
このソースを参考に、ご自分の用途にあったI2Cスレーブのプログラムを開発されたい。そういう意味では、余計な機能や冗長なコードがまだ沢山残っており、余り自慢できるものではないが、少しでも参考になればと恥を忍んで公開することにする。
以下に、2つのファームウエアのフォルダーとreadme.txtなどをzipでかためたファイルを置きます。同じファイル名でも、以前に公開したファイルと内容は全く異なっていますのでご注意ください。
上記のライブラリーは改訂されています。なるべくこれを使わず、新しい方をお使いください(新しいリンク先)
| 固定リンク
| コメント (0)
| トラックバック (0)
最近のコメント