12.GNUnilinkに挑戦 (3/3ページ) マニア向け情報

 GNUnilinkマニア向けです。 リセット頻発・不安定問題と、私が行なった対策について説明します。 とりあえずドングルを 作るor使う だけなら、読む必要は全くありません。 Simonのコードでいろいろ苦労している (楽しんでいる??)方は世の中にはそう多くはいないとは思いますが、全くの皆無でもないと思っています。 そういう方々へのお役に立てれば幸いです。 では、気合を入れて読んで下さい。

PART1. 安定性向上編

0. イントロ: "リセット発生!!"

一般的にリセットは以下の場合に発生するようです。

   1)それまで接続されていたチェンジャやドングルが取り外された場合。
   2)チェンジャやドングル側からの組み込み要求があった場合。
   3)チェンジャやドングルの故障などによる誤動作・無応答。
   4)Reset線がHighになったことをヘッドユニットが検出した場合。

 このうち、1)と2)はCDチェンジャの接続や取り外しの際に発生するもので、正常な動作です。 また、GOLFのヘッドユニット(MDデッキMDX-5V101R)がハードリセットを出す事はありませんし、スレーブがハードリセットを出す事もないでしょうから、4)は対象外と考えて良さそうです。

 3)ですが、さらに分類すると、

   3-1)ヘッドユニットからのコマンドにチェンジャやドングルが応答しない場合。
   3-2)ヘッドユニットからの定期的なKeepAlive確認要求(Ping)にチェンジャやドングルが応えない場合。
   3-3)チェンジャやドングルがSONYバス上のデータを乱している場合。

などが考えられます。
 なお、ここでのPingというのは、マスタ(ヘッドユニット)が、配下装置が正常に動いているかを監視するための方法で、定期的に各装置に状態問い合せすることを指します。


 これ(上記)は実際にリセットされた瞬間のSONYバスのパケットモニタです。 リセット事象はこれだけではありませんが、このパターンが一番多く発生しました。 マスタからPing要求が連続して2回きています(下から3行目と2行目)が、ドングルが無応答なので、マスタがリセット要求(re-initialize bus)を指示しています。 丁度、上の3-2)にあたるものです。
  今回のドングルでは、3-1)の事象も発生しましたが、これはモニタツールを使って観測すれば比較的簡単にとることができる性質の単純なバグでした。 また、3-3)については発生しませんでした。 ということで、解決すべき問題の事象は、3-2)ということになりました。 以降、3-2)の解決に向けて試行錯誤で進めてきた対策について、時系列的に説明します。


1. PICクロック20MHz化のヒントは、モニタから。

 まずは、問題の現象を掴もうということでパケットモニタを作り始めたんですが、たまたまこれが期待通りに動いてくれなかったことから一連の改良がスタートしました。
 試験用環境(MDX-61の制御部)ではパケットが全て受信できるのにMDX-5V101Rをモニタすると、どうも様子が変です。 半分近くのパケットがモニタできていません。 機器による違いかとも思いましたが、実際にはモニタがパケットを受け損なっているようです。 暫くその原因について悩んでいましたが、RichieのUnilink loggerの説明に、

   "GNUnilinkではRS232C側のボーレートを115Kbps以上にして使う事"

 と書かかれていたことを思い出しました。 そこで、ボーレートを115Kbpsにしてみました。 115Kbpsにするにはクロックを20MHzにする必要があります。 すると今度は上手く受信できます。

 モニタ改善の理由には以下の2つが考えられます。

  A)モタモタしていたモニタ結果のPC側シリアル出力が早くなって、そのぶんPICが遅れずに受信できるようになった。
  B)クロック高速化でPICの処理能力が上がった。

 このうち、A)の要因が大きいとは思いますが、B)の改善も見逃せません。 そこでもしやと思い、ドングルも20MHz化してみました。(それまでのドングルは10MHzクロック)  すると、リセット発生が減りました。 若干安定しました。
 試験用のヘッドユニットにドングル1台だけのシンプルな構成の場合と、ソースセレクタを介して複数のチェンジャが付いた実際の環境とではバスを流れるパケット量が違うので、クロックの高速化で処理能力を上げることはそれなりに意味がありそうです。


【ここまでの総合品質】: 改善があったとはいえ、MDX-5V101Rではマダマダです。

【おまけ】: モニタツールは劇的に良くなりました......。


2. 良く判らないから、パクリの波形整形

 次にSONYバス上に電気的な乱れがないかを疑うことにしました。 オリジナルのSimonの回路図には、終端や保護のための抵抗やダイオードがありませんが、XA-107,XA-300,MDX-61などの実際の製品の回路図を見ると付いています。 具体的にはこんな感じです。

  ●Clock,BusOn,Data,Resetの各バス線には、ツェナーダイオード(6.2V)が接続されている。
  ●さらにClock線には、ショットキーバリアダイオードが接続されている。
  ●BusOnには、100から1000pF程度のコンデンサが付いている。
  ●マスタ側のBusOn出力はオープンコレクタ。チェンジャ側には終端抵抗を付けたほうが良いかも。

 (本当は、ここにサービスマニュアルの回路図を貼りたいんですが、SONYの著作物なので貼れません。 自力で検索してください。)

 ということで、例の回路図のデザインにしてみました。なお、BusOn以外のところに付けた抵抗は、 CMOSの誤動作防止用です。

【効果】
 もともとPICには保護用のダイオードが内蔵されていて、しかもシュミットトリガ動作なので、ノイズに強くなっています。 また、対向するソースセレクタやヘッドユニットにも同じような部品が付いている可能性大なので、ドングル側で対策する必要はないかもしれません。 実際にこれらの対策が効いているかは不明です。 まぁここは保険的な意味合いも兼ね、とりあえず付けてみることにしました。 ツェナーダイオードは気休めです。

【ここまでの総合品質】: MDX-5V101Rではマダマダです。


3. 「先生、トイレ!!」(GNUunilinkのバス監視のバグ?)  (実は、ここがこの解説の "肝" です。)

 先の、3-2)のリセット事象の件に話題を戻します。  なぜPingに応えていないのかですが、直接的には、

  (T)コマンド受信の一時的な失敗
  (U)コマンド応答送信の一時的な失敗
  (V)ドングル自体の完全な機能停止

 のいずれかです。
 パケットモニタを見る限り、Pingに対する応答(Pong)は抜け落ちていますが、デバッグ用コード等を使ってさらに調査をすると、その後もドングルは動いていることが確認されました。 ですので(V)ではなさそうです。 また、(U)のPingへの応答処理自体は非常にシンプルで、ここがバグっているとは到底考えられません。 となると、(T)の事象、つまり、何らかの理由でPing要求を一時的に受信できなくなった、考えるのが妥当です。

 さらに観測を続けると、このPing無応答の事象は、スレーブ側からのメッセージ通知 (チェンジャの場合の例としては、"LOAD"の表示や演奏時間、ディスク番号やトラック番号などの種々の表示) の最中に発生しているように見えます。 どうも、バス使用権要求処理から、スレーブ側メッセージ送信までの処理のどこかがパケット受信処理と競合しているようです。

 そこで、パケットを受信できないケースがあるのか該当する部分のソースコードを調べてみました。

 すると、ありました。
 スレーブ装置がバスの使用権をマスタ(ヘッドユニット)から許可してもらうための一連のバス調停操作の一部(IssueSlaveBreak)が、若干、雑に実装されています。 オリジナルのドングルでは、

  ●ブレーク信号送出前のバス空き監視処理中にはパケットの受信が出来ない。

実装になっているのです。
この手順、比較的判りやすい例を挙げると、学校の授業での先生と生徒の質問&応答の手順に似ています。 こんな感じです。

----理想的な動作----
  (A)先生は生徒が手を上げなくともいつでも指名・質問ができる。生徒はこれに即応する。(同期的応答)
  (B)生徒は先生や他の生徒の発言が止んだら、手を上げて、そこで先生に指名されれば発言できる。(非同期通知)
  (C)生徒は発言したいことがあっても、手を上げるまでは、先生や他の生徒の発言に耳を傾けている。(受信+監視)

上記の動作が理想なのですが、悲しいことにGNUunilinkでは、上記のうちの(C)ができないのです。 つまり、

   ・マスタからのデータの受信か、
   ・バスが空くことの監視か


どちらかしかできない
ようになっています。

  (D)発言しようと思い立ったら、先生や他の奴の言っていることを聞かずに、先生の発言が止むことだけを待っている。
    (先生が話している内容はまるで頭に入っていない...)

というトボケタ動作をします。そこで、先のPing要求(まずは1回目)を無視してしまうわけです。
 さらに悪いことに、

  (E)上記の(D)は、自分が発言出来るまでずぅ〜っと続く。

というオマケ付きです。(信念が強いというか、ワガママというか...)  その結果、2回目のPing要求も無視してしまうことになり、ヘッドユニットから死んでいる(または取り外されている)とみなされて、再構成のためにリセット要求が発生します。まるで、

  「先生、トイレ!!」

って言いたい子供のようで、直前に先生が話してたことが全く耳に入っていません。(心境は良く判りますが...)


 さらに呆れたのはSONYバスのPingの作りです。
Pingは約0.6秒間隔でスレーブ装置に向けて送信されると言われています。(実際には1秒ぐらいです) ですのでPing要求のリトライは約0.6秒後かと思ったら、上記のリセット発生事象にもあるように間髪入れずに速攻できました。 単なる通常のコマンド無応答時のリトライと全く同じです。(おいおい...)


 もしリトライが0.6秒後だとすれば、上記(D)のトボケタ動作でさらに(E)にハマッても次のPing要求が来るまでに十分な時間があるのでスレーブからのメッセージ通知が完了しているはず、と予想していたのですが、実際にはマスタから間髪入れずに Pingリトライされるので(E)のおかげで2回目のPingにも応答できなくてリセットされてしまいます。(泣)



【対処方法】
 そもそも2つの事を同時に処理できないのは、PICの宿命かもしれません。(D),(E)を "GNUunilinkのバグ" と言い切るのもやや酷かもしれません。事実、MicのAVRのコードも同じ作りになっています。 何とか並列処理のプログラムを書けないだろうかと (C)の実装を考えてみましたが、無謀なので諦めました。

 (--- 実は、後から、この無謀と思われていた解決方法が判明します。(7項に示します。) ですが、この時点ではまだその方法に気が付いていませんでした。----)

 ということで(C)の対処は断念しましたが、上記の(D),(E)への対処なら何とか出来そうなのでやってみました。

 (D)の対策: --- 思い立ってもすぐには手を挙げない
    通知すべきイベントが発生しても、直ぐにバス監視の動作に移らずに、PingEnd(全装置へのPingが一通り完了した後)
    の直後に行なうようにする。
   (すると、マスタからのPingコマンドと衝突する可能性が減るので競合し難くなります。) (簡単にいうと、上の図の”(暇)”と書いたところで手を挙げるっていうことです。 詳しく知りたい方は、 respond.asm 内の Respond_Hello の箇所を、第1版とSimonのコードとで比較してみて下さい。)

【効果】:  非常に効果がありました。 平均して30分ぐらいはリセットなしで動き続けました。 正直、これで完成!!  とも思いました。 ですが、ぬか喜びでした。 リセット発生は完全には無くなりません。  (他のスレーブからの非同期通知と競合する可能性は残っていますから....)

 この鬼門とも言える非同期通知は、毎秒行なわれる演奏時間カウントアップの通知処理で最も多く使われています。 これが約30分間は正常に動くということは、発生頻度としては、"1800回に1回" 程度、といった勘定です。 (などと自慢しても、所詮、平均30分でリセット発生ってことには変わりありません。)

【ここまでの総合品質】: 幾ら発生頻度が低くとも、リセットが発生するようでは使い物になりません。


 (E)の対策: --- 「バトンタッチ方式」
    監視中に、データパケットが流れていることが確認されたら、監視動作を止めて、すぐに受信動作に移る。
    また、やり直しのために予約のフラグを立てて、次のPingEndの後にやり直す。
     (具体的な対処箇所は、functions.asm の IssueSlaveBreak です。)

【効果】: これも非常に効果がありました。 もう実機環境ではリセットは発生しません。

 それでも、やや厳しい条件を想定し、演奏時間カウントアップのイベントの発生頻度を10倍以上に増やしたり、 MDX-61のリモコンからコマンドを頻繁に入力した場合には、ごくまれにリセットされることがあります。 個人で使うなら問題なさそうですが、公開するにはあともう少しです。
 それから、(D)の対策の影響で、演奏時間カウントアップがギコチないというか、1秒刻みでなくて、 0.5秒刻みだったり1.5秒刻みだったりとバラツキがあります。

【ここまでの総合品質】: 安定性の点では合格といえますが、出来の良し悪しと言う点では、合格点ギリギリといったところです。

 結局、この(E)の対処では、Pingへの2回連続無応答によるリセットは回避することができているものの、 1つめのパケットを落とすという問題は解決出来ていません。 ということで、この対処の副作用も含めて、やや不満の残る結果となっています。

4. 各種タイマ値のチューニング

 かなり安定してきましたが、格別打つ手もない、煮詰まった状況になってきました。 仕方がないので、次に各種タイマ値のチューニングを試してみることにしました。 受信処理/送信処理には幾つかのWait時間が設定されていますので、これを0.5ミリ秒単位で大小に変化させて最適値を探す試験を延々と繰り返しました。

 結果的に、Simonのオリジナルの設定値とは若干違う値になりました。(それぞれ幾分小さい値になっています。)
本当にベストなセッティングなのは不明です。 安全側領域に入ると、0.5ミリ秒程度の変化では違いが明確には現れなくなってしまいます。 (ということで、このチューニングは単に自己満足なだけかもしれません。)

【効果】:  想定外の過負荷条件下で若干改善された程度です。実機では違いが判りません。(勿論悪くはなっていません。)

【ここまでの総合品質】: 特に変化なし、といったところです。


5. 再び問題(C)がぁ....「スリープ問題」 (機能制限で逃げまくり?)

 (D),(E)の対策でゴマカせると思っていた本格対処の難しい(C)の問題ですが、Ping無応答によるリセットは回避できるようになったものの、実際に使ってみると問題になることが3点ほど発生しました。

  (C-1) ドングルから他のソースに音源が切り替わったことを通知するイベントを受信しそこなう。
    見た目には、新しい音源に切り替わっていて特に問題はなさそうですが、
    ドングルはPlay状態のまま放置されてしまうという事象が発生しました。
     (なお、ヘッドユニットのほうが賢いためか、実害はありません。)

  (C-2) スリープの事前通知を受信しそこなう。
    システムがスリープする際にはマスタ(ヘッドユニット)からスリープの事前通知が送信されます。
    その後、マスタは全ての装置がIdle状態になるのをひたすら待ち続けるわけですが、このとき、
    万一、ドングルがこの通知を受信しそこない、Play状態のままでいると、マスタはその後もひたすら
    待ち続けるために、いつまで経ってもシステム全体がスリープ出来ない、というエンドレスな状況に陥ります。
    ヘッドユニットの表示上は電源OFFですが、実際は電源ON状態というヤバイ状態になります。


上記のパケットモニタは、スリープが成功した場合の例を示しています。


  (C-3) (E)の対策自体が失敗する。
    (E)の対策にあった、予約のフラグを立てる「バトンタッチ方式」は、マスタからのバス使用許可の
    問合せのコマンドに応える必要がありました。 ですが、擬似的にイベントの発生頻度を増やした
    超過負荷の条件下では、このコマンド自体をも受信しそこなうことがあります。
    すると、バトンリレーが切れてしまい継続しなくなるという問題が発生しました。


【対処方法】(実際は"対処"というよりも"機能制限"ですね。)
 超対処療法的ですが、(C-2)と(C-3)については対策のコードを実装しました。(恥ずかしいので説明は省略します。)
 このうち(C-2)の対策については、BUS_MAX(ソースセレクタXA-30C)が複数回送信するパケットを期待して実装しています。
ですのでXA-30Cが無い場合は(C-2)の対処ができません。 仕方がないので、XA-30Cが無い場合は演奏時間のカウントアップを行なわないという、回避策としました。

【効果】:  "それなり"、といったところです。

【ここまでの総合品質】:  機能制限という対処の良し悪しは別として、安定性は一層向上しました。


6. 最後の仕上げ? 割込みマスク見直し

 今回のドングルではPCとの通信機能を使っていないのですが、割込み処理自体は別の2つの目的で使っています。 1つは、演奏時間表示のためのタイマ割り込み、もう一つはスリープ状態からの復帰用のINT割り込みです。 このうちのタイマ割り込みについては、パケットの送受信処理のクリティカルな瞬間での割込みを抑止するための制御が必要です。

 実は、この処理については、Simonのオリジナルのコードをそのまま流用していましたが、パケットの送信処理の部分をよーくみると、何だか怪しい処理があることに気が付きました。(function.asmの"SendByte"という処理です。) 1バイトのデータを送信するのに、1ビット毎の送信を8回ループする処理があるのでですが、ここでの割込みのDisable処理がループ内の先頭部分にあり、一方、Enable処理がループを脱出したところにありました。 まぁ、とやかく言うほど致命的ではなさそうですが、7回無駄にDisableしている分だけステップ数的にも不利ですし、ビット操作を短いループでグリグリ回すのも何か怪しい感じがします。

【効果】:  ということでループ突入前でDisableするように修正してみたところ、なぁんと(C-3)の問題が改善されました。
  (なぜ、改善されたのか、合理的な説明はできませんが...)  なお、(C-3)の対処療法コードはとりあえず残しています。

【ここまでの総合品質】:  不満は残りますが、リリースOKと判断しました。
   (ドングル第1版のプログラムには、ここまでの対処が盛り込まれています。)


7. これで本当に完成。(C)の対処

 3項の問題(C)がそもそもの諸悪の根源なわけですが、これまでどう修正すればいいのか判らずにいました。
 (その間に、ドングル第2版のバスOUT機能やNextDisc機能を作ってってたりしたわけですが...)
 とはいえ、やはり気になります。
 ということで、パケットを1つ落としてしまう(E)の問題の対処方法について、再度見直しをすることにしました。

 この(E)の対処は、DataBitがONになるかを監視して、もしこれにヒットすると、マスタフェーズがIdleではない、 (つまり何かのデータがヘッドユニットから送信されてきた)と判断して、IssueSlaveBreak処理をアボートさせ、リトライのためにIssueSlaveBreak予約のフラグを立てた後に、受信処理の先頭(ReceiveBreak)に飛ぶ、というものでした。

 実は、これには以下の大きな2つの誤りがありました。 (勿論、何もしないよりは(E)の対処は非常にマシな処理なわけですが..)

 (1)監視にヒットしたパケットは、ReceiveBreakの前処理のwait処理のためにパケットの受信が手遅れになり、
  1パケット分はどうしても捨てられてしまっていた。
 (これで受信漏れが発生)

 (2)DataBitはマスタフェーズのIdle監視としては意味はあるが、本来、データの有無を監視するには、
  DataBitではなくてClockBitを監視すべきだった。


 ということで、以下のような対処をしました。

【対処方法】
  ・監視対象をDataBitから、ClockBitに変更する。
  ・IssureSlaveBreakでの監視にヒットしたら、予約のフラグを立てた後に、受信処理の先頭(ReceiveBreak)
   にではなくて、ReceiveCommand にジャンプさせる。

【効果】: 非常に効果がありました。
 監視にヒットして受信処理に移行する際にパケットを落とす事がなくなりました。 これによって、上記の (C)の問題が解決しました。
 ((C-1),(C-2),(C-3)のそれぞれについて改善を確認しています。 なお、(C-3)の5項での対処は残したままになっています。)

 また、これにあわせて、演奏時間のカウントアップも即時起動に戻しました。 これでカウントアップもスムースになりました。

【ここまでの総合品質】: 安定化対策としてはこれで完成です。(多分。)


 現時点(2004年7月時点)での安定化対策については以上です。 第2版にはここまでの対処が盛り込まれています。

---<お断り>---
 なお、ここに紹介していませんが、第1版と第2版のコードにはSimonのコードにない独自の実装も沢山追加されています。
 しかも、かなり右往左往した結果、コードには対処療法の跡が残ったままになっています。 そんなこんなで、ただでもヘタなコードがさらに読みにくくなっています。 それらについては何とぞご容赦下さい。

 品質向上編は以上です。



PART2. Misc編

8. 試験用ボードとモニタソフト

 大変ご紹介が遅れました。 試験用ボードの回路図とパケットモニタのソフトを公開します。
 モニタは、リセットやBusOnの信号に関係なく、パケットを受け続けます。
回路図とモニタソフトはこれです。(回路図のgifファイルも一緒に入っています)  この試験用ボードの下側のPICにこのプログラムを入れて使ってください。(公開終了しました)

 使い方は簡単で、RS232CのシリアルケーブルでPCと接続し、ハイパーターミナルなどのターミナルソフトを起動すれば、その画面上で各パケットをダンプすることが出来ます。(ボーレートは 115Kbpsです。)  さらに、Richie の Unilink loggerにここでダンプしたファイルを入力すれば、可読できるように変換してくれますので、SONYバス上でのパケットのやり取りの様子を知ること出来ます。

9. 参考情報:MDX-5V101RではResetが使われていない?

 SONYバスのReset線のところをひたすら観測しても、MDX-5V101RではReset信号を確認することが一度もできませんでした。 ハードリセット"が使われていないように見えます。 勿論、SONYバス上でReset線に強制的に+5Vを与えるとリセットが掛かりますので、ハードリセットの機能自体は残っているようです。

 "使われていない"、という表現は正しくなくて、"電源投入時のみリセット信号が送出される" というほうが適切だろうと思います。 ここでの電源とはACCのことではなく、常時通電の12Vのことです。 なので滅多にお目にかかれないのだと考えています。

 通常、ヘッドユニットに付いているリセットボタンは、CDチェンジャ等の接続変更をした場合にシステム再構成の契機として使いますが、各装置がPowerOnリセットの機能を備えてさえいれば、ヘッドユニットからのハードリセット指示が無くとも、その後に続くソフトリセット(re-initialize bus)で初期化できます。(勿論、スレーブの各装置が暴走していないというのが前提です...)  このような理由と、部品点数を減らそうという意図から、SimonのGNUnilinkハードにあったハードリセットの回路が、私のドングルには実装されていません。 この実装の正否ついては、現在も継続して評価中です。


10. 消費電流測定結果

 厳密な測定方法ではありませんが、第2版ハード(バスOUTつきドングル)での消費電流は以下のようになりました。
【測定方法】:
   ・動作時は、+12V電源側に直列にテスタを入れて測定.
   ・スリープ時は、小さい抵抗を直列にいれて、その間の電圧をデジタルテスタで測定し、電流に換算.

  A: 省エネ型三端子レギュレータ未使用の場合 B: 省エネ型三端子レギュレータ使用の場合
動作時(Idle時) 10mA以下 8mA以下
動作時(Play時) 30mA以下 27mA以下
スリープ時 約4mA 約0.1mA

 なお、ここには載せていませんが、第1版ハードはリレーなしの第2版ハードと考えればよいので、 Play時の消費電流は上記のIdle時の値とほぼ同じになります。 実測でもほぼ同じ結果でした。 表中に "以下"とあるのは、LEDの点灯/消灯による差のことで、LEDが消灯している瞬間はそれぞれこの約3mA減となります。

 省エネ型三端子レギュレータ(SII製 S-81350)の効果は、スリープ時に顕著です。 PICスリープ時の超低消費電力モードの恩恵を上手く使うことができます。 (ちなみに、PIC単体でのスリープ時の消費電流は、9μA(@5V)でした。PIC恐るべし。)
 省エネ型三端子レギュレータでない場合のスリープ時の消費電流が比較的大きい感じがしますが、例えば、60AHのバッテリーを空っぽにさせるには 4mA ですと約2年弱かかる計算になりますので、絶対的な消費電流としてみた場合、これは小さな値だと言えます。 車載で使うのであればこの違いはおそらく全く問題にならないでしょう。

 むしろ設計で気を使うべき点は リレーの部品選定だと思っています。 三端子レギュレータの定格最大電流は 100mAといわれていますが、定常的に流せるのはこの半分まででしょう。 ドライブ用のトランジスタへの負荷も極力抑えたいですし、炎天下でも誤動作しないようにするには、発熱は少ないに越したことはありません。 今回ご紹介したリレー以外のものを使う際には、電流の実測や手で触っての発熱の確認など問題ないことを注意深くチェックすることをお勧めします。


11. 課題など

 体裁の良い箱に詰め込むとか、タイトルのスクロール表示などのオメカシなど、すべき事はがまだまだ沢山あるのですが完全にサボっています。 いつご報告できるか判りません。 とはいえ、何とか役に立つところまでは仕上げたいと思っておりますので、コメントや有益な情報があれば有り難いです。


(このページは、掲載している方法および結果を保証するものではありません)