« バグとの戦い | トップページ | 失敗しないプリント基板のパターン露光 »

2008年12月 7日 (日)

アセンブラのバグなのか?

【事後フォロー記事】
所長です、こんばんわ

下記の記事ですが、内容に誤りがある為、訂正します。
私が「バグバグ」と騒いでいた原因は、「関数のリエントラント(再入可能)性」の問題であり、コンパイラのバグでは無く、私のプログラミング知識不足からくる、プログラムのバグでした。
すっぽんのクリさん(コメント参照)からのアドバイス通りです。
m( __ __ )m

「リエントラント性」とは、分かり易く言うと、
割り込む前に使っていた関数(変数)が、割り込みから復帰した後に、何事も無かったかのように、正常に復帰するかどうか?
といった感じでしょうか・・・

わかりやすい例を挙げて説明します。

【悪い例】
#int_timer1
void  timer1_isr(void)
{
int t;
   disable_interrupts(GLOBAL);
   output_high(ST_PLUG);
   delay_us(100);               // wait 100usec (at 8MHz)
   output_low(ST_PLUG);
   set_timer1(set_interval_p);
   enable_interrupts(GLOBAL);
}

void main()
{
 中略
  delay_us(val);
 中略
}

何が悪いかと言うと、main()の中でdelay_us(x)関数が使われていますが、割り込みルーチンの中でも、同じdelay_us(x)関数を使っています。

(クリさんの言葉を借りれば・・・)「内部で静的な変数を使っている場合」両方のdelay_us(x)関数は、同じ変数を使っている可能性があります。言葉の通りだとすれば、間違った使い方だということがわかると思います。

要するに、さっきまで使っていたdelay_us(x)関数内部の変数が、使っている途中で、他の場所から使われてしまうわけです。
これでは、期待通りのプログラムにはなりません。

【良い例】
#int_timer1
void  timer1_isr(void)
{
int t;
   disable_interrupts(GLOBAL);
   output_high(ST_PLUG);
   for (t=0; t<24; t++) { }           // wait 100usec (at 8MHz)
   output_low(ST_PLUG);
   set_timer1(set_interval_p);
   enable_interrupts(GLOBAL);
}

void main()
{
 中略
  delay_us(val);
 中略
}

割り込みの部分で、 for 文を使って、delay_us(x)と同じ働きをするプログラムを作りました。

結果は、期待通りの動作となり、私が使っているCCSCのコンパイラでは、delay_us(x)は、「リエントラント性」が保障されていない事がわかりました。

割り込み関数を使う上で、
次の点に注意してプログラムを作ることにしました。
 ①割り込み中に、多重割り込みを防止するために、GIEピットをクリアする。
 ②割り込み関数は、極力簡素にする。
 ③リエントラント性を考慮する。

以上、事後フォローでした。

なお、回路は出来上がり、点火するところまでは、こぎつけました。
しかし、使っていたモーターから煙が噴いたので、また後戻り中です。
Speed400モーターは、起動時にあまり負荷が大きいと、20A(7.2Vで)も流れます。おそらく、タービンの回転がおかしかったときに、モーター内部の巻線が焼損したか、コミュテーター、ブラシ周辺が焼損したと思われます。

モーターは、高速回転タイプではなく、普通の380モーターでしばらくがんばります。

では・・・

------------------- 元記事 ---------------------
おおぉぉ~!
やっとプログラムのバグが直った!

バグはたくさんあるのですが、中でもどうしようも無かったのが、TimerとRAポートの割り込み制御でした。
割り込みを切っているはずなのに、切れてない。
どういうことなのか生成された(10年ぶりに)アセンブラを覗いてみると・・・
思ったようにコンパイルされていない!
コンパイルされたコードを見ると、何を意図しているのかよーわからん。

そこで、CCSCコンパイラで、インラインアセンブラを組んでみた。
(インラインアセンブラとは、C言語の記述の中に、無理やりアセンブラを組み込む方法)
CCSCのインラインアセンブラ記述はこんな感じ・・・

#asm ~ #endasm までの間がそのまま組み込まれます。

【実例】
#int_timer1
void  timer1_isr(void)                   // for plug heater interval
{
//   disable_interrupts(int_RA);    ←コメントアウトした元コード
#asm
      BCF    0x0B,3                   ←直接書いたアセンブラコード
#endasm
      set_adc_channel(0);              // set A/D channel
      delay_us(20);
      real_current = read_adc();       // plug current check by 10bit
      output_high(ST_PLUG);
      delay_us(200);                      // Make plug heat pulse
      output_low(ST_PLUG);      
      enable_interrupts(int_RA);
      set_timer1(set_interval);
}

これがコンパイラーのバグだとすると、ちょっと複雑な事をしようとしたときは、結局アセンブラがわかんないとやっぱダメなんだなーと思った。

CCSCには他にもバグっぽいところがいくつかある。
これらのコードを使うときは、バグに気をつけよう。
delay_ms(x)
delay_us(x)
delay_cycles(x)
disable_interrupts(x)

その他にも、割り込みにからんだコードを使うときは、なるべく他の割り込みを一時停止するのが良いです。
例えば・・・
・PWMのdutyを書き換えるとき等は、書き換える前に他の割り込みを止める。
・A/Dコンバーター(変換終了)や、EEPROM(書き込み終了)なんかも割り込みが絡んでるので注意。

CCS社(日本の代理店殿へも)にちょっと一言
Webで見るとCCSCのバグって結構ある見たいね。
有料でバグだらけの癖にアップデート期限があるなんてせこいぞ!
本国(アメリカ)で買うと12ヶ月、日本で買うと30日だけ?
どうにかなんないのかねぇ

では・・・

|

« バグとの戦い | トップページ | 失敗しないプリント基板のパターン露光 »

コメント

 こんばんわ。お久しぶりです。
PICは取り扱ったことがないので、以下、はずしてるかも知れませんが…。

>#int_timer1
>void timer1_isr(void) // for plug heater interval

…以下は、timer1の割り込みサービス・ルーチンかと思いますが、一般にCPUは割り込みが掛かると、ノン・マスカラブル以外の割り込みを禁止にします。PICもご多分に漏れず、割り込みが入るとINTCONレジスタのGIEビットをクリアして、割り込みが多重に入るのを防ぎます。

http://akizukidenshi.com/pdf/microchip/pic12f683.pdf

85ページの左下。

When an interrupt is serviced:
• The GIE is cleared to disable any further interrupt.
• The return address is pushed onto the stack.
• The PC is loaded with 0004h.

アセンブラコード

>BCF 0x0B,3

は、同じくINTCONのGPIEをクリアしますが、上のデータシートの86ページ、FIGURE 12-7によれば、GPIEをどう弄ろうが、GIEがクリアされている限り、他に影響を及ぼしません。
 なお、PICは割り込みサービス・ルーチンの先頭で、WとSTATUSレジスタを退避するために、これらをデータ領域の適当な部分にコピーしています(これに必要なコードは、#int_ マクロにより追加されている)が、多重割り込みを使う場合は、さらにセーブ領域からの退避を行う必要があります。ですので、PICで多重割り込みは、あまりお勧めできません。
 また、サービス・ルーチン中で、

> set_adc_channel(0);  // set A/D channel
> delay_us(20);
> real_current = read_adc(); // plug current check by 10bit
> output_high(ST_PLUG);
> delay_us(200); // Make plug heat pulse
> output_low(ST_PLUG);
> enable_interrupts(int_RA);
> set_timer1(set_interval);

と、かなり多くの関数を呼び出していますが、これらの関数を、サービス・ルーチン外でも使っている場合は、これらの関数のリエントラント(再入可能)性が保証されている必要があります。PICはデータ領域が狭く、多分 delay_ あたりは、内部で静的な変数を使っていると思いますので、リエントラントじゃないんじゃないかと。まあ、使ったことがないので断言は出来ませんが。

>且つ、FETがあっちっちなので、FETは3個並列に!

 TO220パッケージ単体の熱抵抗は、一般に50~65℃/Wはあるので、放熱器なしで消費できる電力は精々2W 程度です。ラジコンでは省略されがちな傾向があるようですが、チョッとした放熱板を付ければ、熱抵抗をかなり下げることができますのでお試しあれ。

投稿: くり | 2008年12月13日 (土) 午前 12時19分

こんばんわ
スッポンのくりさん久しぶりですねぇ

最初は、GIEをクリアしていたんですが、いろいろやっているうちに、他の割り込みも無い事から、GPIEだけになっています。
でも、考えてみたら、レジスタの退避は1回分しかしていないので、多重退避の処理をしない限り、割り込み中は、GIEをクリアするしかないですね。
再入可能性ですかぁ
あんまり考えたことなかったけど、その通りですね。
Delay関数はあちこちで使っているので、まったく保証されていないようです。
もう少し、CCSコンパイラを信じてやってみるとします・・・(笑)

FET(Renesas H7N0307)ですが、実は1.38℃/Wという優れものです。しかし、内部抵抗とプラグの抵抗を合わせても300mΩ程度しかないので、充電直後のNiHM6celで8Vも加えてしまうと27Aとなり、直流だとおよそ200Wまで達することになります。
ざっくりそのまま計算すると、200x1.38+常温=300度
プラグが加熱されると、更に抵抗が下がる為DCだと、300度ではすまなくなります。
加熱はDuty10~20%程度のPWMなので、全然大丈夫かと思うと、焦げ臭くなるくらい厚くなる事から、加熱時のプラグの抵抗が予想外に下がっているのかもしれません。
常温のプラグ抵抗をベースに安全動作領域を見ると、問題ないことから、抵抗はかなり下がっているようです。
(抵抗が1/3になると600W超!)
という事で、実物の実験結果から、3個になりました。今度、熱伝対でも付けて計ってみますかね・・・
このFETってフランジがソース(GND)ではなく、ドレイン(電源側)なので、あんまり放熱板を付ける気がしないんです・・・
放熱板は絶縁が必要なので、使う人は要注意。
実はこの前作った回路では、フランジをAGNDに接続したので、ぐるりのパターンを削る羽目にあいました。仕様書は良く読むべし。(読むのは嫌いなんですけどね)

投稿: 所長 | 2008年12月14日 (日) 午前 01時43分

こんばんわ

>最初は、GIEをクリアしていたんですが、いろいろやっているうちに、他の割り込みも無い事から、GPIEだけになっています。
でも、考えてみたら、レジスタの退避は1回分しかしていないので、多重退避の処理をしない限り、割り込み中は、GIEをクリアするしかないですね。

うーん、ちょっと勘違いをなさっていると思いますが、割り込み時には、PICのハードウェアが自動的にGIEをクリアします。したがって、プログラムが割り込みサービス・ルーチン内に到った時には、既にすべてのマスカラブルな割り込みは、禁止状態です。割り込みを禁止するために、プログラマが手動でGIEをクリアする必要はありません。また、RETFIEを実行すると、ハードウェアにより自動的にGIEがセットされます。


>FET(Renesas H7N0307)ですが、実は1.38℃/Wという優れものです。しかし、内部抵抗とプラグの抵抗を合わせても300mΩ程度しかないので、

 これも、ちょっと勘違いと思いますが、1.38℃/Wというのは、Channel to case thermal impedance、即ち、半導体からパッケージへの熱抵抗であり、パッケージから周りの空気への熱抵抗(case to atmosphere thermal impedance)は含まれていません。

http://www.chinacpec.com/admin/edit/UploadFile/2006524135935388.pdf

また、H7N0307のON抵抗はID = 30A, VGS = 4.5Vの条件で最悪11.5mΩとのことですから、全体で27A流れたとしても、H7N0307の自己損失は約8.4Wです。まあ、duty20%のPWMとしても、放熱板なしでは(熱抵抗50℃/Wとすると)、やっぱりヤバイですが。
絶縁の問題は、昔は雲母板とか使ったのですが、最近は、性能の高いシリコン・ラバーとか、パソコン・パーツ屋でも手に入ります。まあ、ルネサスがサッサとプラスチック・モールドのパッケージを出してくれれば問題はないのですが…。

投稿: くり | 2008年12月15日 (月) 午前 12時23分

すっぽんのくりさん
ご指摘ありがとうございます。

その1 割り込み後のGIEクリアについて
確かに、その手の後閑先生のHPを見ると、割り込み後は、自動的にGIEがクリアされると書いてありますね。
私は、コンパイラが生成したアセンブラを見て、クリアされていないと思っていましたが、見方が間違っているのか、それともマシン語に変換する際に、自動生成しているわけですね。(後者だとバグがあってもブラックボックスなので、分かりませんが・・・)


その2 FETの熱抵抗について
確かに、1.38℃/Wは「Channel to case」でした。「case to atmosphere」は常識なので、仕様書にあえて書いてないのでしょうね。
いずれにしても、大事なのは接合温度の定格ですから、「Channel to case」を考慮すればよいですね。
もう一度計算してみたいと思います。

では

投稿: 所長 | 2008年12月15日 (月) 午前 09時27分

コメントを書く



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


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



« バグとの戦い | トップページ | 失敗しないプリント基板のパターン露光 »