+===========+ |P/ECE研究室| +===========+ P/ECE研究記録 2014年 ==================== * Tue Dec 30 17:15:42 JST 2014 Naoyuki Sawa - USBコントローラ(PDIUSBD12)の省電力設定 P/ECEカーネル1.20は、USBコントローラ(PDIUSBD12)の設定として、D12_NOLAZYCLOCKとD12_CLOCKRUNNINGのどちらのフラグも指定していません。 D12_NOLAZYCLOCKとD12_CLOCKRUNNINGのどちらのフラグも指定していない理由は、以下のように書かれています。 参照資料: http://aquaplus.jp/piece/dl/up118to120.txt >D12_NOLAZYCLOCKとD12_CLOCKRUNNINGは常に不要と思われます。 >PDIUSBD12のマニュアルでは少々説明不足なかんじですが、 > >D12_NOLAZYCLOCK: >USBケーブルはつながっているが、有効な通信が行われていない場合に、外部出力クロック速度を下げる。 >USBコントローラからクロック供給を得て動作するCPU等のための省電力サポート機能。 >このフラグを「指定すると」機能がOFFになり、出力クロックは常にフルスピードとなる。 > >D12_CLOCKRUNNING: >USBケーブルがつながっていないときに、USBC自体のクロックを止める。 >このフラグを「指定すると」機能がOFFになり、USBコントローラは常に動きつづける。 >USBコントローラが停止すると外部出力クロックも停止してしまうので、 >USBコントローラからのクロック供給を得て常に動きつづけたい(USBケーブルがつながっているか >どうかには関係なく)ようなCPUがある場合は、このフラグを指定する。 > >という意味だと思います。 結論から言うと、上記は間違いでした。どこが間違っているかと言うと、以下の二点です。 � �D12_NOLAZYCLOCK」と「D12_CLOCKRUNNING」の効果についての、理解が間違っています。 �⊂陛杜呂里燭瓩寮瀋蠅箸靴董◆�D12_CLOCKRUNNING」を指定しない事は正しいのですが、「D12_NOLAZYCLOCK」を指定しない事が間違っています。 ■� �D12_NOLAZYCLOCK」と「D12_CLOCKRUNNING」の効果 PDIUSBD12のデータシートによると、D12_NOLAZYCLOCKとD12_CLOCKRUNNINGの効果は、以下のように書かれています。 参照資料:「PDIUSBD12 USB interface device with parallel bus Rev. 08 - 20 December 2001 Product data」(PDIUSBD12.pdf) ┃□11.2.3 Set mode ┃┌──┬───────┬────────────────────────────────────────────────────┐ ┃│Bit │Symbol │Description │ ┃├──┼───────┼────────────────────────────────────────────────────┤ ┃│2 │CLOCK RUNNING │ A ‘1’ indicates that the internal clocks and PLL are always running even during Suspend state. │ ┃│ │ │ A ‘0’ indicates that the internal clock, crystal oscillator and PLL are stopped whenever not needed. │ ┃│ │ │ To meet the strict Suspend current requirement, this bit needs to be set to ‘0’. │ ┃│ │ │ The programmed value will not be changed by a bus reset. │ ┃├──┼───────┼────────────────────────────────────────────────────┤ ┃│1 │NO LAZYCLOCK │ A ‘1’ indicates that CLKOUT will not switch to LazyClock. │ ┃│ │ │ A ‘0’ indicates that the CLKOUT switches to LazyClock 1ms after the Suspend pin goes HIGH. │ ┃│ │ │ LazyClock frequency is 30 kHz ± 40%. │ ┃│ │ │ The programmed value will not be changed by a bus reset. │ ┃└──┴───────┴────────────────────────────────────────────────────┘ DLできるURL: http://www.gaw.ru/pdf/Philips/usb/PDIUSBD12.pdf (2014/12/30現在) 説明はこれだけで、他にD12_NOLAZYCLOCKとD12_CLOCKRUNNINGに関する説明やタイミング図も無いようです。 確かに、PDIUSBD12のデータシートは説明不足なかんじです。以下のような疑問が浮かびます。 ・D12_NOLAZYCLOCKが‘1’ならば、サスペンド中も外部出力クロックが低速クロックに切り替わりません。 ・D12_NOLAZYCLOCKが‘0’ならば、サスペンド中に外部出力クロックが低速クロックに切り替わります。 …切り替わらない(‘1’)場合に、高速クロックを出力し続けるのか、外部出力クロックが止まるのかが、はっきりとわかりません。 ・D12_CLOCKRUNNINGが‘1’ならば、サスペンド中も内部クロックが止まりません。 ・D12_CLOCKRUNNINGが‘0’ならば、サスペンド中は内部クロックが止まります。 …という事はわかるるのですが、内部クロックが止まった時に、外部出力クロックも止まるのかどうかが、はっきりとわかりません。 さて、先日ふとしたことから、これまで読んだ事の無かった、PDIUSBD12のFAQのドキュメントを見付けました。 そこには、以下のように書かれていました。 参照資料:「FAQ - PDIUSBD12 1 October 1998」(faq_pdiusbd12.pdf) ┃□3.5 What is the CLKOUT frequency during suspend? ┃The behaviour of the output clock is configured based on the Configuration Byte written using the Set Mode Command (0xF3). ┃┌───────────────┬────────────────────────────────────────┐ ┃│Configuration Byte │CLKOUT │ ┃├───────┬───────┤ │ ┃│No Lazy Clock │Clock Running │ │ ┃├───────┼───────┼────────────────────────────────────────┤ ┃│0 │0 │CLKOUT switches to Lazy Clock on suspend. │ ┃│ │ │The output frequency is 18KHz to 48 KHz. │ ┃│ │ │The PLL clock switched off to reduce current consumption. │ ┃├───────┼───────┼────────────────────────────────────────┤ ┃│1 │0 │The CLKOUT stops on suspend. │ ┃├───────┼───────┼────────────────────────────────────────┤ ┃│0 │1 │CLKOUT switches to Lazy Clock on suspend. │ ┃│ │ │The output frequency is 18KHz to 48 KHz. │ ┃│ │ │The PLL clock remains on. │ ┃├───────┼───────┼────────────────────────────────────────┤ ┃│1 │1 │The suspend state does not affect the CLKOUT frequency with this configuration. │ ┃└───────┴───────┴────────────────────────────────────────┘ DLできるURL: http://ricrdn0.sweb.cz/usb/faq_pdiusbd12.pdf (2014/12/30現在) なるほど、これならばよくわかります。 「D12_NOLAZYCLOCK」と「D12_CLOCKRUNNING」の効果について、正しくは以下の通りでした。 ・D12_NOLAZYCLOCKが‘1’で、D12_CLOCKRUNNINGが‘1’ならば、サスペンド中も外部出力クロックは高速クロックのままです。 ・D12_NOLAZYCLOCKが‘1’で、D12_CLOCKRUNNINGが‘0’ならば、サスペンド中は外部出力クロックが止まります。 ・D12_NOLAZYCLOCKが‘0’ならば、サスペンド中は外部出力クロックが低速クロックになります。 ・D12_CLOCKRUNNINGが‘1’ならば、サスペンド中も内部クロックが止まりません。 ・D12_CLOCKRUNNINGが‘0’ならば、サスペンド中は内部クロックが止まります。 ■�⊂陛杜呂里燭瓩寮瀋� もっとも省電力にするためには、サスペンド中は、内部クロックも外部出力クロックも止めることです。 そのためには、 ・「D12_NOLAZYCLOCK」は指定する。 ・「D12_CLOCKRUNNING」は指定しない。 とするのが正解でした。 現状のP/ECEカーネル1.20は、 ・「D12_NOLAZYCLOCK」は指定しない。 ・「D12_CLOCKRUNNING」も指定しない。 となっています。 P/ECEの回路では、PDIUSBD12の外部出力クロックを使用しないので、常に外部出力クロックが停止していても良いぐらいなのですが、 PDIUSBD12の設定にはそういう設定は無いので、次善の策として、サスペンド中に外部出力クロックを止めるのが望ましいです。 しかし、P/ECEカーネル1.20の設定では、スタンバイ中も外部出力クロックに無駄な低速クロックが出力されてしまっています。 結論としては、D12_NOLAZYCLOCKを指定すべきところ、P/ECEカーネル1.20はD12_NOLAZYCLOCKを指定していないので、無駄な電力を消費していました。 ただ、D12_NOLAZYCLOCKの指定が不足していることによる、無駄な消費電力はさほど大きくないと思います。 「Lazy Clock」と名付けてPDIUSBD12の'売り'の一つにするぐらいなので、低速クロックを外部出力するための消費電力は十分小さいと思うからです。 あと、記憶がおぼろげなのですが、当時、P/ECE掲示板で消費電流の計測をした時に、D12_NOLAZYCLOCKの有無では違いが無かったような気がします。 たぶん、D12_CLOCKRUNNINGの指定の有無による消費電力の違いがほとんどで、D12_NOLAZYCLOCKの有無による違いは小さいのではないでしょうか。 というわけで、現状のP/ECEカーネル1.20のままでも、省電力性能に重大な問題は無いと思います。が、やっぱり気になりますよね(^^; * Mon Dec 29 18:38:36 JST 2014 Naoyuki Sawa - P/ECEカーネル1.20のスタンバイ復帰バグ ■問題点 P/ECEカーネル1.20の変更点の一つとして、以下のように書かれています。 http://aquaplus.jp/piece/dl/up118to120.txt >・スタンバイ状態でUSBケーブルを接続すると、自動的にスタンバイ復帰するようにしました。 しかし実際に試してみると、確かに復帰する事も有るのですが、復帰しない事も結構有るのです。 スタンバイ状態でUSBケーブルを接続してもP/ECEの画面が消えたままで、PC側では「不明なデバイス」と認識してしまいます。 この問題には、だいぶん前から気付いていたのですが、これまで調査していませんでした。 普段、P/ECEに電池を入れずに使っていて、スタンバイ状態にする事も無いので、実用上ほとんど問題にならなかったからです。 今回あらためて、この問題を調査してたところ、原因が判りました。 ■原因 原因は、『スタンバイ復帰用の「ポート入力割り込み2」の割り込みプライオリティが設定されていないこと』でした。 具体的には、P/ECEカーネルのソースの下記の箇所です: \usr\PIECE\sysdev\pcekn\powerman.c 467行 【誤】 bp[0x270] |= 0x04; // ポート入力割り込み2 許可 【正】 bp[0x261] |= 0x07; // ポート入力割り込み2 プライオリティ ←←この処理が抜けていた!! bp[0x270] |= 0x04; // ポート入力割り込み2 許可 スタンバイ状態でUSBケーブルを接続すると、自動的にスタンバイ復帰する仕組みは、以下の通りです。 ポート入力2は、「P22 電源モニタ」端子に相当します。 USBケーブルを接続すると、「P22 電源モニタ」端子が変化して、割り込みが発生します。 割り込みが発生すると、スタンバイ復帰します。 要するに、割り込み発生によってスタンバイ復帰させようとしているのですが、 現在のP/ECEカーネル1.20の処理では、割り込みコントローラの割り込みプライオリティの設定が抜けていました。 割り込みコントローラの割り込みプライオリティレジスタは、イニシャルリセットで初期化されません。 \usr\PIECE\docs\datasheet\EPSON\s1c33209_221_222j.pdf 「S1C33209/221/222 テクニカルマニュアル」B-II-5-12「割り込みコントローラのI/Oメモリ」参照 割り込みプライオリティレジスタの値が、たまたま1〜7ならば、割り込みが発生して、正常にスタンバイ復帰します。 0ならば、割り込みプライオリティ0とは割り込み禁止と同じなので、割り込みが発生せず、スタンバイ復帰しません。 これが、上記の問題の原因でした。 ■対策 根本的な対策は、カーネルを書き換えることなのですが、それは難しいので、運用で回避する対策を考えてみます。 □対策方法�� PCから「ポート入力割り込み2」の割り込みプライオリティを設定します。 具体的には、PCにP/ECEを接続して、DOS窓で以下のようにタイプしてください: isd.exe m 40261 1 これだけでokです。40261というのは、「ポート入力割り込み2」の割り込みプライオリティレジスタのアドレスです。 上記のコマンドで、「ポート入力割り込み2」の割り込みプライオリティを1に設定したことになります。 以下のようにタイプすると、アドレス040261の値が01になっている事を確認できます。(確認は必須ではありません。) isd.exe d 40261 040261 01 04 76 00 01 06 13 77 46 65 01 62 25 01 01 02; ..v....wFe.b%... 〜 P/ECEをスタンバイさせて、USBケーブルを接続すると、正常にスタンバイ復帰します。 この効果は、P/ECEの電池が無くなるまで有効です。スタンバイする度に、コマンドを実行する必要はありません。 □対策方法�� 「ポート入力割り込み2」の割り込みプライオリティを設定する、アプリケーションを実行します。 都合の良いことに、P/ECEに付属しているアプリケーションの中に、そういうアプリケーションがあります。 \usr\PIECE\app\pex\testir.pex 赤外線通信のアプリケーションなのですが、これを実行すると、「ポート入力割り込み2」の割り込みプライオリティが7になります。 赤外線通信の割り込みと、スタンバイ復帰の割り込みは、同じ割り込みプライオリティレジスタを利用しているからです。 赤外線通信アプリケーションをP/ECEに入れておいて、実行してください。すぐに終了して構いません。 P/ECEをスタンバイさせて、USBケーブルを接続すると、正常にスタンバイ復帰します。 この効果は、P/ECEの電池が無くなるまで有効です。スタンバイする度に、アプリケーションを実行する必要はありません。 �,諒�が手軽ですが、P/ECE開発環境がインストールされたPCが必要であるという欠点があります。 �△論岾粟�通信アプリケーションを入れておくためP/ECEの容量を消費しますが、PCが無くてもできる対策です。 ■余談 ところで、'イニシャルリセットで初期化されません。'とは、要するに不定値なのですが、どれぐらいで不定値になるのでしょうか。 試してみたところ、以下のようになりました: ○電池を入れたままでP/ECEのリセットボタンを押しても、値は変化しません。 ○電池を抜いて10分放置して、ふただび電池を入れても、値は変化していませんでした。 ×電池を抜いて一時間放置して、ふただび電池を入れると、値は変化していました。  変化後の値はランダムで、必ずしも0になるとは限らないようです。0になる事が多いみたいですが、1になる事も有りました。 というわけで、前述の対策方法�´△鮃圓辰�P/ECEの電池が切れた場合も、10分以内に電池を交換すれば、対策の効果は持続すると思います。 * Fri Oct 24 21:26:13 JST 2014 Naoyuki Sawa - strtod()のバグ ■問題点 P/ECE開発環境に付属している、EPSON製Cライブラリの、「strtod()関数」には、バグがあります。 『変換に失敗したとき、走査の終了位置を示す文字へのポインタが設定されない』というバグです。 strtod()は、以下のように使うことがよくあると思います。 │ #include │ #include │ static const char str[] = "1.2 -3.4 5.6"; │ int main() { │ const char* ptr = str; │ char* endp; │ double d; │ for(;;) { │ d = strtod(ptr, &endp); │ if(endp == ptr) { break; } //変換に失敗したら、終了する。 │ printf("%g\n", d); │ ptr = endp; //今回の走査の終了位置から、次回の走査を行う。 │ } │ return 0; │ } strtod()関数は、変換に失敗したとき、渡されたptrと同じポインタを、endpに格納することになっています。 上記のプログラムは、ptrとendpが同じならば、変換できる文字列が無くなったと判断して、ループを終了しています。 ところが、P/ECEのstrtod()関数は、「変換に失敗したとき、endpが不定になる」というバグがあるようです。 厳密には、「変換に失敗したとき、endpに値が格納されません」。 strtod()を呼び出す時点でendpは不定値ですので、変換が失敗すると、endpは不定値のままとなります。 プログラムは、strtod()が変換に失敗したことを判断できずに、いつまでもループが終了せずに、暴走します。 さらに悪いことには、不定なendpの位置から次回の走査を行おうとして、無茶苦茶な数値を取得してしまいます。 ■原因 原因は、EPSON製Cライブラリの「strtod()関数」の、バグでした。 変換が失敗したとき、endpが指す変数にポインタを格納すべきところ、間違ったローカル関数にポインタを格納していました。 【誤】 │ if(iChgFlg == 0){ │ if(sStrTmpP != NULL){ │ sStrTmpP = sStrPtrP; │ } │ return (double)0.0; /* no conversion */ │ } ↓ 【正】 │ if(iChgFlg == 0){ │ if(sEndPtrP != NULL){ │ *sEndPtrP = (char*)sStrPtrP; │ } │ return (double)0.0; /* no conversion */ │ } ちなみに、EPSON製Cライブラリの「strtod()関数」には、上記の他にも、(重大性は低いものの)ケアレスミスぽい点があります。 ■対策 二種類の対策方法があります。 □対策� ,箸蠅△┐魂麋鬚垢訛从�方法 「strtod()関数」をあまり頻繁に使わない場合は、その都度対策するのが手っ取り早くて良いと思います。 変換が失敗したときにendpが変わらないのですから、strtod()を呼び出す前にptrと同じポンイタを入れておけば良いのです。 │ endp = ptr; //★EPSON製Cライブラリの「strtod()関数」のバグ対策★ │ d = strtod(ptr, &endp); │ if(endp == ptr) { break; } //変換に失敗したら、終了する。 □対策�◆〆�本的に修正する対策方法 「strtod()関数」を頻繁に使う場合は、その都度上記の対策を行うのは面倒だし、見落とすおそれがあるので、 EPSON製Cライブラリの「strtod()関数」を修正して、置き換える方が安全でしょう。 修正した「strtod.c」を、アプリケーションと一緒にビルドすれば、 「\usr\PIECE\lib\lib.lib」の中にある元の「strtod()関数」よりも優先されて、修正した「strtod()」関数がリンクされます。 修正した「strtod.c」はこちら: http://www.piece-me.org/piece-lab/stodbug/strtod.c ■ダウンロード バグ再現プログラムと、対策プログラムのサンプルは、こちらです。 ダウンロードはこちら: http://www.piece-me.org/piece-lab/stodbug/stodbug-20141024-src.zip