« Mega168のUART | トップページ | やっぱりボクが悪かった »

2008年8月24日 (日)

Tiny用のソフトI2Cスレーブ

今度はボクは悪くない?(1/17/08 11:30)
 2日間悩ませたトラブルがやっと解消した。USIインタフェースを使ったTWI(I2C)スレーブの通信である。Atmel社のアプリケーションノートを参考に、スレーブ側の通信ルーチンをこのあいだから作っていた。LCDに表示する通信だから割込みを使わなくても良いのだが、汎用性を考えるとやはり割り込みは使いたい。目茶目茶読みにくいAtmelのソースコード(変数名がやたらに長く日本人には視認性最悪、技巧に走って分岐条件が複雑、丁寧な記述とそっけないハードコーディングが混在)を1週間かけて解読し、動きを理解したところで書き始める。

 最初、割込み部分を最小にしようと考えたが、処理がメイン側と、割込み側で分離され、ソースが読みにくくなる。まあ、商用製品じゃないんだからあとのメンテナンスにこだわることはないのだけれど、メインに持ち込むと、割り込みをとりこぼしデータが化けたりする可能性は高くなる。スレーブ側は、シフトレジスタで送受したあと、クロック線をLowに引いてマスターを待たせることができるらしいが、いきなりこれを実装するのは無茶だ。ということで、意気込んでは見たが、ソースは結局Atmelの構造と同じになってしまった。初めての本格的な、割込み駆動のソフトである。

 細かいミスや、Atmelソースのバグ(スレーブアドレスの末尾1ビットを消すのにシフトしている。このコード、テストをしていないのかも)を修正し、思ったより順調にデータが受信され得意になっていたのが15日夜。ところがこの先がいけない。一旦データを受信したら、それ以降、全く動作をせず、リセットしないと元に戻らない。最初はどこかでループしているんだろうと気楽に考えていたが、どうもそうではない。

 割込み駆動だと、ループしていても割込みだけは受け付けるので、どこが問題かわかりにくい。あちこちにテストステートメントを挿入してみるが、どこも正常である。しかも最初は何の問題もなくデータを受信してUARTを経由して端末に表示されるのに、次からは割り込みを受け付けたという文字が表示されるだけで、がんとして先に進まない。

 ロジアナを引っ張り出してシーケンスを見るが、これは想定どおりの結果である。最初は良くても、次はマスターの呼びかけに全く答えていない。ところがハングした後も別のスレーブ(RTC)の通信は最初ははずすが、2回目以降はちゃんと受け付ける。そして自分のところでないとはねている。こいつ、選り好みをしてやがるのか。そんなことどうしてわかるんだと、親のMega168の送信シーケンスを見るが全く同じ。

 データシートのUSIの説明を読み返す。割込みルーチンにUARTの送信ステートメントを恐る恐る入れてシーケンスを確認する。制御レジスタの中身を表示する。問題ない。そりゃそうだ、最初はちゃんと動くんだから。次の日。見るともなく見ていたkuman氏のページで、シフトレジスタはUSISRという制御レジスタのあるビットに1を書くことで始めて動く(最初は単にステータスを表示するビットだと誤解)という説明で、目から鱗が落ちあわてて修正するが、これも同じ。まあ、これは、今まで不審な割り込みが記録されていた現象の原因と思われるが、何しろ1回きりで次が動かない。これでは先に進めない。

 今朝から寝床の中で、1回目と2回目で状態がどう変わったかという視点で考えているとひらめいたものがある。 I2C通信のところは2回目のときに、全てを最初の状態にもどしている。これで動かないのはもうプログラムではなく、ハードの問題になる。

 しかし、それ以外に違うところがひとつあった。それは、通信の乱れをリセットするために開始条件割込みが起きると1秒タイマーをスタートさせているところである。タイムアウトが起きると無条件にインタフェースを初期化して次にそなえるというしかけだ。タイマーは一旦スタートさせると止められないので、そのあとは割り込みをマスクし、開始条件のたびにタイマーを初期化している。タイマとUSIインタフェースは全く別のハードだから、「こんなの関係ない」のだけれど、もうこれ以外に調べるところがない。

 ほとんど期待もしないで、「そんな馬鹿な」とか言いつつ、タイマースタートのステップを削り動かしてみた。これが、何と、通ったのである! 始めは目を疑った。いや、ちゃんと送られたデータを表示する。何回やっても正常に動作する。一体どういうことだ。これは。タイマーの割込みマスクがUSIにまで影響するのか。

 データをバッファサイズ以上に送るとおかしくなったりするが、とりあえずはスレーブ受信はうまくいった。やれやれ。しかし、いまだに信じられない。よくよく調べてみたらやっぱりこちらの原因だということになるかもしれないが、今度ばかりは俺のせいじゃない。しかし、2日間、よく頑張った。日頃、言っているトラブルシューティングのコツ「原因解明は外へ、外へ」を証明するデバッグであったことは間違いない。

LCD(液晶ディスプレイ)まで到着
(1/19/08)
 I2Cスレーブの泥沼から這い出てからは、早かった。タイムアウトはあとで考えることにして、いよいよI2Cの当面の目的、LCD表示に向かう。このあとは、Tiny26を手探りで動かさなければならないので、慎重に準備をする。プロジェクトを別に起し、そこへ今までのコードを足しこんで行く。まあ、ピンの位置を間違えたり(RTCの場所を動かしてピンをずらすのを忘れる)、UARTとI2Cの読み込み変数を変えるのを忘れて、データが出ているのに動かないので頭を抱えたり、折角、I2Cのピンのセットアップが済んでいるのに、ハードコーディングしたLCDの初期化ルーチンがそこを壊したりなどのミスはあったけれど、ロジアナを出したり、エラーステートメントをてんこ盛りにしてデバッグする手間もなく、泊り込みの調査部会のあとの4~5時間で、待望の他のMCUからのデータがLCDに表示されるのを確認した。嬉しくて思わず拍手をした。A2161043

 お正月(3日)以来の作業である。あとはこれをTiny26に移す工程が残っているが、そう難しい工程ではない。完全に山を越した。USIインタフェースを使ったソフトTWI(I2C)はこれで、マスター、スレーブとも一応完成である。それにしても、Mega168のインストールからずいぶん時間がかかった。まあ、AVR経験3ヶ月にしてはよくやったと思う。Atmelのソースを参考にはしているが、バグを直してあげたし、おあいこだろう。 次の開発目的は、やっぱりEtherNetなのか。USBspiも作っておきたい。

そうは問屋がおろさない(1/22/08)
 もう峠を越した、次は、EtherNetだと気楽に書いたのは3日前だったが、何のことはない。また完全にはまっている。まず、LCDに出す最大メッセージ(32バイト)の出力の最後データが欠けるバグに頭を抱えた。ソフトウエアの開発で一番間違えやすい、臨界点のバグである。アマチュアのプログラムだからこだわることはないのだけれど、精神衛生上良くない。移植する前に完全にしておこうとリストを何度も見返すが、これが悪いところが見当たらないのである。

 C言語は配列が0から始まり、文字列と配列のデータの長さが違う、データ形式も違うなど、文字列の取り扱いはとても混乱する。一字でもはみだすと他のデータを壊して原因不明の障害の元になるので何度も紙に書いて確認するが、間違っていない。はずしかけたテストステートメントをまた突っ込んで洗いざらいデータを出してみると、ちゃんとデータを準備しておりコード上の間違いはなさそうである。どうもI2Cがデータをバッファに溜めこむタイミングと最後のリターン文字をキーにLCDに出力しはじめる時期がぶつかって最初の文字を取りこぼしているような感じである。

 I2Cの速度を落とせば、どうなるかとクロックを落としてみたら、余計データをとりこぼすようになって慌てて元に戻す。非同期処理のバグ取りは本当に難しい。プログラミングの真髄はRobustness(堅牢さ)で、こういうバグは是非取っておきたいのだが、これは本格的なICEなどのデバッガーでないと無理だ。万策尽きてもう寝ようと夜遅く風呂に入っていて湯船の中でふと思いついた。そうだ。何も今の非同期の構造にこだわることはない。LCDにデータを出すだけなのだから、データをすべて受け取り終わるまで待っていれば良いのだ。UARTと一緒に動かしていたから、今の構造になっているが、ベタに待っていれば良い。どうせミリセカンドの世界である。

 次の日、早速プログラムを簡単にし、動かしてみる。何の問題もなく最後の文字まで出力される。やれやれ始めからこうしておけば良かったのである。勢い込んで、いよいよ最終ステップのTiny26への移植作業にとりかかる。てんこ盛りになったテストステートメントや、デバッグでお世話になったUART、それに今は使えないタイマー(これは今度の構造でも前と同じ現象になった)の部分を取り外し、26に合わせてポートの場所を換え(26のUSIインタフェースはISPピンと重なる)てプログラムを作る。サイズも1.5キロバイトまで下がった。念のため861のまま動作確認する。問題なく動く。

 引き出しにしまってあったTiny26を取り出し、コンパイラーをTiny26指定にしてバイナリを書き込む。端末から文字を入力する。出た!と喜んだのもつかの間、後半にゴミデータがついてくる。えー、861とこの部分は何の変更もないはずなのにどうして? 試しに、861に戻すと問題なく表示される。USIインタフェースのデータシートを確認するが、変わったところは何もない。最後の最後で足をすくわれた感じである。

 奇妙な現象である。WinAVRのコンパイラーオプションを変えてみると、字化けする場所が変わってくる。???である。最大の最適化では3文字目から、コンパイラオプションのO1では10文字目からゴミデータとなる。データが多いと1文字だけであとはすべて00データになる。マスターのストップコンディションを発行する位置を変えるだけで字化けどころか文字そのものがでてこなかったりする(これは861でも起きた)。

 よりによってデバッグ用のUARTをはずしてからの現象である。何か馬鹿にされているような気分だが仕方がない。LCDにデータを出してみるが、LCDは表示が限られるし、低速なので無闇に入れるとおかしくなってしまう。ロジアナもこういうときは役に立たない。データ表示と同じ結果が確認されるだけである。色々調べた結果、原因は大体つかめたが、その解消方法が見当たらない。

 要するにI2Cでは、マスターからの送信の終わりをスレーブが知ることができない。ストップコンディションの発行が唯一の手がかりなのだが、861と違って、26はそのあいだもUSIインタフェースが動き続けデータとして送り込んでいるのが原因であることまではわかった。しかし、これをどう止めればよいのか方法がわからないのである。

 逆に何故861ではうまく行っていたのだろう。USIのシフトレジスタはSCL(クロック線)の立ち上がり、立下りでドライブされデータを受け取って行くがロジアナで見る限り、SCLは動いておらず、シフトレジスタは動かない。従ってデータは入ってこないはずである。26ではこれが動いているようだ。

 ところが不思議なことに、多量のデータを送ると、最初のうちからデータが乱れ始めることがわかった。そうなるとデータの最後を空打ちしてゴミを出していると言う仮説はあやしい。もっと他の原因がありそうだ。データを受け取り始めたときにこのストリームがどれだけ続くかMCUにわかるわけがない。それが大量のときに限って最初から乱れるのは人間的だが、機械ではありえない。なぞは深まるばかりである。こうして今日も深夜を迎える。

間に合わなかった(1/23/08)
 大量に送るときは最初からおかしくなるという、いかにも人間臭い反応について、一晩考えていた。データが送られて来たときに先のことは当然、知る由もない。人間でも無理である。今朝目覚めてみると、2年ぶりの雪だった。起きるかどうか逡巡しているとき突然ひらめいたものがある。そうだ、プログラムの構造は、データ読み込みを一旦全部やってから、表示に移るしかけになっている。ここで間違えれば、昨日のようなことが起きてもおかしくない。そうか、I2Cのやりとりばかりに気を取られてこの部分に気がつかなかった。

 朝食もそこそこに、地下に降りてPCに電源を入れる。コンパイラで状態が変わるというのが臭い。大量データを読んだときのみ、データを汚している可能性がある。バッファの位置を変えてコンパイルしてみた。勢い込んでバイナリをMCUに書き込む。ファーム書き込みの画面に見慣れないメッセージが出た。また、電源を入れるのを忘れたか。いや、ちゃんと電源が入っている。メッセージを良く見ると、Parity Errorの表示。

 去年の10月に買ったTiny26の書き込み回数の寿命がつきたことを示すメッセージである。間に合わなかった。最初に買ったMCUだ。目茶目茶な書き込み回数である。10000回を保証するとあるが、とうとうこの回数を超えてしまったようだ。ファームの書き込み制限があるので、早く、このMCUをLCDのドライバーにしようと、これまでやってきたのだが、タッチの差で間に合わなかったのである。

 プログラムさえ入ればマイコンとしては何の問題もなく動くはずである。ちゃんとした余生を送らせてやりたかった。そのために場所を提供しようと苦心してきたが、残念ながら何の役にも立たない石になってしまった。もう少し早くこのプロジェクトを始めるべきだったと悔やまれる。合掌。

そそっかしいにもほどがある(1/27/08)
 いやいや、生来のあわてものの性格はこの年になっても変わらない、困ったものだ。ParityErrorですっかりショックを受け、Tiny26をもう少しで生きながら埋葬するところだった。あれ以来、ちょっとした虚脱状態に陥り、気を取り直そうと、秋月でTiny2313を4個も買ってきてしまった。何しろ、秋月では2313がひとつ¥100なのである。可愛がっていたペットが死んで、さびしさの余りすぐ別のペットを買い込んでくる心境でおかしくなる。この石はADCがついていないが、生意気にもUARTを備えており、こうした通信系のドライバーにはかえって都合が良い。

 調査の報告書の執筆が思いのほかはかどったので時間が出来た。早速、元のブレッドボードのLCD回路はそのまま残し、新ブレッドボードの空いている上段に2313の場所を作って、26のLCDドライバーをそっくり配線しなおした。ISPピンをつけ、avrsp -rで2313の接続を確認してから、プログラムを2313用に修正し、ファームを書き込む。ところがこれがまたエラーになるのである。何い、そんな馬鹿な。よく見ると、ISPとピンを共有するI2Cのケーブルが接続されている。これをはずすと、ちゃんとファームは書き込まれた。

 待てよ。前のTiny26でもI2Cの配線はしたまま書き込んでいた。ひょっとすると、これがこのあいだのParityErrorの原因だったのかもしれない。急いで、しまってあったTiny26 を取り出し、前のブレッドボードに挿して、I2Cケーブルをはずして書き込む。何と、問題なく書き込めるではないか。でも前と同じ状況なのだろうなと、Megaを立ち上げて表示テストをしてみる。何と、何と、これがエラーが出ないのである。2行分32文字を全く問題なく表示する。

 何が原因かはわからない。しかし、今はちゃんと動く。わけがわからない。書き込みが不十分でエラーが出ていたとしか考えられない。Tiny861では問題がなかったのは、I2Cポートの場所が861では独立していたからである。いやいやとんだ勘違いであった。しかし、わかって良かった。もう少しで使える石をゴミ扱いするところだった。考えてみれば、買ってから3ヶ月、1万回が限度だとすれば、毎日100回以上ファーム書き込みをしないとこの回数に達しない。いくら夢中でやったとしても、これはいくら何でも多すぎる。おかしいとは思っていた。

遂に犯人をつかまえた(1/28/08)
 Tiny2313でもはまってしまった。はじめ順調にLCDに32文字を表示するので安心していたら、どうもおかしい。少しづつ入力していくと2行目に文字が出てこない。一気に32文字送るとちゃんと最後まで表示する。???である。プログラムは前の26用なので、デバッグ用のUARTが入っていない。しかも、ISPピンはI2CにかぶっていてソフトUARTに使えない。2313は専用のUARTを持っているが配線もソフトもまだ準備していない。

 LCDに表示させようとしたが、テストする対象にテストステートメントを表示させることには所詮限度がある。ソースコードは全く変えていないのにこの不審な動き。わけがわからない。まあ、Tiny26 が生き返ったから、こだわることはない(LCDドライバーにする予定はない)のだが、気に入らない。あれこれ考えた末、こいつのUARTを動かして徹底分析することにする。俺も懲りない男だ。深夜2時、配線を終えてこの日は寝た。

 翌28日、仕事から帰ってきて再びPCに向かう。Mega168のUARTルーチンを拝借して2313のUARTを作る。USB-UARTアダプタはMegaのを移す。こういうときのブレッドボードは実に便利である。これがいちいち半田付けをしていたら気の遠くなる作業だったことだろう。

 ところが送信は一発で動いたのだが、受信が反応しない。割り込みを使わないベタの受信でデバッグするところもないコードなのに動かない。暫くして原因がわかった。メインループのsleepから目覚めるのにダミーの割込みルーチンをコピーし忘れていたのが原因。これも初期化のときに、割込みを生かす設定になっていたのを「おかしい、これは間違いだ」とか言いながら割込みをdisableにしている。このときに気づくべきなのだが、思い込みというのはこういうものだ。大事故は大抵これが原因で起きる。

 さて、UARTから続々と状況が出力されて原因はすぐわかった。表示カラムがとんでもない値で動いている。カラム設定のコマンドで一旦正しくなるが、行換えで狂う。これでは2行目に行かないはずである。表示カラムはグローバル変数で、初期化のときに必ず0にしている。メインと処理関数のところで違うものを指しているか、データを汚している。試しに、変数の定義を、処理関数のところからメインの方に移してみる。

 直った!前のTiny26では問題なく動いていたのは何なのだ。間違いなく、gccのリンカーあたりのバグである。やっと犯人を一人つかまえた。ひょっとすると以前ソフトUARTではまっていた、カウンターが変なところでリセットされるのもこのあたりが原因だったのかもしれない。単なるコソドロだと思っていたら、一連の連続強盗の犯人だったようなものだ。

 いずれにしても、バグが解明されたあとの気分は何物にも替えがたい。安月給で昇進も期待できない刑事が犯人探しに夢中になる気持ちが理解できる。これで心おきなくニセコでスキーが楽しめる。

ここに後述するバグを取ったUSIインタフェースを使ったソフトI2Cスレーブのコードをおきます。なお、この前後の不具合はすべて、SRAM変数とスタック領域が被ったことが原因と思われます。そのつもりでドタバタ劇を見てください。
「I2LCD26.lzh」をダウンロード

|

« Mega168のUART | トップページ | やっぱりボクが悪かった »

AVR」カテゴリの記事

コメント

コメントを書く



(ウェブ上には掲載しません)


コメントは記事投稿者が公開するまで表示されません。



トラックバック

この記事のトラックバックURL:
http://app.f.cocolog-nifty.com/t/trackback/1089557/23221794

この記事へのトラックバック一覧です: Tiny用のソフトI2Cスレーブ:

« Mega168のUART | トップページ | やっぱりボクが悪かった »