庭のさくらんぼの木にイラガの毛虫が付いて、退治にたいへんな思いをした。イラガというのは繭が小さいウズラの卵みたいな見栄えをして、幼虫は緑でフラクタル型のとげとげがはえている蛾です。幼虫のとげは毒があり、皮膚の柔らかいところに触れると、海でクラゲに刺されたように、細かい針をたくさん打込まれたような痛みが生じ、ひどい目に遭います。。。。アイキャッチ画像はアゲハチョウの幼虫です。。。。という、話ではなくて・・・・
太陽光パネル用MPPT機能付DCDC変換プログラムのバグ取りを進めている。
極めて不可解な現象が数日前から出ている。利用しているPICは安物なので、AD変換の量子化精度は10ビットしかない。ビット精度を上げるために、複数回測定して平均をとることを、一般にオーバーサンプリングというのだそうだ。私の場合は、高周波ノイズの影響を抑制するために同じようなことを行っている。
これまでに、電圧測定のタイミングの取り方をいろいろと検討し、最終的には、mainの中で無限ループを回して常時測定する方法に落ちついた。
高周波ノイズを抑制しつつ測定間隔を短くするために、連続4回の測定値の移動平均という手法を用いている。精度向上には各測定値に重み付けをすべきだが、複雑な計算にはプログラムメモリが不足するので、単純な矩形窓を利用している。
動作状況を詳細に知るために、およそ1/4秒(262ms)周期でLCDモジュールに数字を表示しているが、測定している3ヶ所の電圧値が、不定期で瞬間的に3/4になるという症状が出た。3ヶ所の測定値全てで、かつ、それらが同期しておらず、ほぼランダムに瞬間的に値が小さくなるという極めて不可解な症状。
周期的、同期的であれば、論理的なミスが想定されるのですが、ランダムとなると想定が難しい。考えられる様々な原因を一つ一つ確認しても、いっこうに解明できず、どん詰り状態に陥ってしまった。
そんなときは、気分転換に限るのだが、何をしてても頭から離れず。。。。で、今朝、奥さまが仕事に出るのを駅まで車で送っている途中、はたと閃いた。
プログラムの構成を少し説明する。移動平均の演算はmainループの中で、以下のように一番古いデータを減算し、新しいデータを加算するという単純なロジック。
1 while(ch-CH_NUM){ // ADチャネルループ
2 sum[ch] -= advalue[count][ch]; // SUM_NUM前の測定値を減算
3 advalue[count][ch] = adconv(ch); // 変換結果を読込み
4 sum[ch] += advalue[count][ch]; // 新規測定値を加算
5 ch++;
6 }
一方、LCD表示は、タイマ割込みで262ms毎のタイミングで行っている。
上記の2行目の演算が終ってから4行目の演算が終る間に割込みが入ると、sum[ch]の値は1つ分少ない値となる。4点の移動平均をとっているので、合計が1回分少なく3回分(3/4)になるということ。確かに、論理的には、現象が説明できる。
しかし、長いmainループの中で、この瞬間に割込みが入る確率はどれほどのものか。1ループの時間を測定すると、約162μs。262msの表示間隔に1617回ループを回っており、メインループ約30ステップのうちの2ステップに割込みタイミングが当る確率は6%となり、そこそこの頻度で発生することが確認された。
回避策としては、2行目の前に割込みを禁止にし、4行目の後で割込み許可をすることで、この現象が改善されれば、この想定が正しいと言うこと。
奥さまを送り届けて、リサイクルごみをリサイクルセンタに届けてから、自宅に戻り、さっそくプログラム改修。
1 while(ch-CH_NUM){ // ADチャネルループ
2 if(GIE) {
3 GIE = 0; // 割込み禁止
4 gie_flag = 1;
5 }
7 sum[ch] -= advalue[count][ch]; // SUM_NUM前の測定値を減算
8 advalue[count][ch] = adconv(ch); // 変換結果を読込み
9 sum[ch] += advalue[count][ch]; // 新規測定値を加算
10 if(gie_flag) GIE = 1; // 割込み許可
11 ch++;
12 }
ビンゴ!!!
こういうときは、なかなか気持ち良いもの。プログラムコードに向い合っているときには、なかなかバグが取れないのだが、ちょっと離れた瞬間に閃くというのはよくあること。頭のリフレッシュが大切だと言うことを、改めて実感。
しかし、これだけではなく、まだまだバグ取りは続くのであった。。。。