AVRによるLCメーター


はじめに

コイルを自作する時にインダクタンスを測りたいことなどがあるので,LCメーターを作成しました.
検索エンジンでLCメーターを検索すると,ここで紹介されているのと同じ回路がよく使われているようでした.無調整で比較的高い精度が得られるようなので,今回はこの回路を使ったLCメーターを作りました.

回路

回路図 回路図  配線図 配線図

PICの代わりにAVR(AT90S2313)を使っていますが,それ以外はオリジナルと同一の回路です.
この回路で測定精度に影響を及ぼす最も重要な部品はキャリブレーション用の1000pFのコンデンサです.日本橋のデジットで誤差1%のポリプロピレンコンデンサを売っていたのでそれを利用しました.
今回使用したリードリレー(デジットで売っていたNECのURA-N1というもの)はLED並に消費電流が小さい(1V, 14mA)ので,AVRのポートで直接駆動しています.
測定結果をRS-232Cで外部にも出力してパソコン等で利用できるようにしています.

ファームウェア

参考にしたオリジナルの回路ではPICを使っていますが,今回はAVRを使うことにしたのでファームウェアを一から作る必要があります.
オリジナルのLCメーターの詳細な動作は作ったことがないので分かりませんが,測定の原理が詳しく説明されているので,それを元にC言語でプログラムを書きました.

測定の原理

このLCメータの仕組みとしては,発振回路に測定対象をつないだ場合の発振周波数の変化を測定することにより,測定対象のキャパシンタンスやインダクタンスを計算するようになっています.
その際に,基準となるキャリブレーション用の容量(Ccal)が既知のコンデンサを1つ使用します.
未知の値としては,発振回路中のコンデンサのキャパシタンスC,発振回路中のコイルのインダクタンスL,被測定対象のコンデンサのキャパシンタンスCu(またはコイルのインダクタンスLu),の3つが存在することになります.
一方で既知の値としては,発振回路に何もつながない状態での発振周波数F1,発振回路にキャリブレーション用の容量が既知の精密なコンデンサを接続した場合の発振周波数F2,発振回路に被測定対象のコンデンサ(またはコイル)を接続した場合の発振周波数F3,があります.
次のように方程式を考えて解くことで,未知の値を求めることができます.

数式1   数式2

このように発振回路中のコンデンサやコイルの値が不明でも,周波数の測定だけにより無調整で被測定対象の値を求めることができます.
F2とF3は必ずF1よりも小さくなるので,計測可能な周波数は最大でF1でよく,また(F1-F2)や(F1-F3)の値は常に非負となります.
上記の式は元のページで解説されているものですが,実際には下記のように式変形して計算しました.

数式3 数式4

このように式変形した一つの理由は,桁落ちによる計算誤差の発生を避けるためです.
後述のとおり今回は浮動小数点演算を行うため,値の近い数同士で減算を行うと,桁落ちと呼ばれる誤差が発生する可能性がありますが,整数で減算を行えばそのような誤差は生じません.
また,このようにすると単に浮動小数点での減算ルーチンを用意しなくてすむという理由もあります.
上記の式変形に加えて,今回使った浮動小数点演算のルーチンでは指数部は負の値はとらないようにしたので,あらかじめ測定可能な最小の値の逆数をかけておいて計算するようにしています(1.0×10^-16の値まで扱いたかったので1.0×10^16をかけた値をプログラム中では使用しています).

浮動小数点演算

通常AVRのような非力なマイコンで乗算や除算を行う場合は,固定小数点演算を行うことが多いと思います.
たいていは扱う数値の範囲が決まっているので,固定小数点演算で用は足りてしまいます.
しかしながら今回のLCメーターでは,扱う数値の桁の範囲が広く,固定小数点演算で場合分けして扱うとむしろ大変そうなので,浮動小数点演算を使うことにしました.オリジナルのLCメーターのファームウェアでも,浮動小数点演算を採用していると書かれています.
avr-gccを使うのでfloat型やdouble型で実数が扱えますが,生成されるコードサイズが大きかったので自前で浮動小数点演算用のルーチンを用意しました.
有効桁は10進で4桁あればいいので仮数部は16ビットとし,指数部は8ビットとしました.仮数部が16ビットだと,乗算,除算は32ビットの演算ができればいいので,C言語で用意されているunsigned long型で計算できます.16ビットより大きいと,64ビット以上の演算をサポートしていないCコンパイラでは自前で計算を行う必要があり面倒です.
関数での受け渡しなどの扱いやすさを考慮して,32ビット整数型の上位から9~16ビット目を指数部,下位16ビットを仮数部として使うことにしました.なお今回のアプリケーションでは必要なかったので,仮数部も指数部も非負の値だけ扱えるようにしました.
加算と減算は必要ないので乗算と除算ルーチンだけ作りました.また,乗数と除数は16ビット整数型としています.実際のコードは次のとおりです:

/*
  浮動小数点演算(乗算)
  内部表現は,下位24ビットのうち上位8ビットが指数(uint8_t)残り16ビットが仮数(uint16_t)
*/
static uint32_t fp_mul(uint32_t v0, uint16_t v1) {
  uint8_t e;
  e = (uint8_t)(v0 >> 16);
  v0 &= 0x0000ffff;
  v0 *= v1;
  while (v0 >= 65536) {
    v0 >>= 1;
    e++;
  }
  v0 |= (uint32_t)e << 16;
  return v0;
}
/*
  浮動小数点演算(除算)
  内部表現は,下位24ビットのうち上位8ビットが指数(uint8_t)残り16ビットが仮数(uint16_t)
*/
static uint32_t fp_div(uint32_t v0, uint16_t v1) {
  uint8_t e, ee;
  ee = (uint8_t)(v0 >> 16);
  e = ee - 16;
  v0 <<= 16;
  v0 /= v1;
  while (v0 >= 65536) {
    v0 >>= 1;
    e++;
  }
  if (e > ee) return 0;	/* underflow */
  v0 |= (uint32_t)e << 16;
  return v0;
}

AVRでの正確な周波数の測定

このLCメーターでは,発振周波数からインダクタンスやキャパシンタンスを計算するので,正確な周波数の測定が重要となります.
AVRで周波数メーターを作ることはよく行われていますが,厳密に周波数を測定するのは結構難しい問題です(そこまでの厳密さが必要かどうかはおいておくとして).
簡単な方法としては,タイマーとカウンタを使用して,カウンタにより入力パルスをカウントしてタイマーにより一定の時間が来たら割り込みをかけて単位時間あたりのパルスを数えるということができます.
しかしこの場合,カウントを開始するタイミングとタイマーで計測を開始するタイミングのズレや,割り込みが発生した後に割り込みルーチンへジャンプしてそこでカウント値を取得するまでのオーバーヘッドのクロック数を考慮しないといけません.C言語を使う場合など,そのオーバーヘッドがどの程度であるかはコンパイラに依存するので正確に知るのは困難です.またアセンブラを使ったとしても,2クロック以上要する命令(分岐命令など)を実行中に割り込みが起こると命令が終わるまで割り込みが遅れてしまうので,厳密な単位時間当たりのパルス数を計測することはできません.

ここでは,次のような方法で厳密に周波数のカウントを行うようにしました.
まず,タイマー用のカウンタはフリーランさせておいて(クリアやセット等は行わない),一定の時間毎に割り込みが起こるようにします.割り込みが起こるまではsleep命令で待機させておくことにより,複数クロック要する命令の実行中に割り込みが起こることを防止します.これにより,(割り込み呼び出し時に余計な条件分岐処理などしていなければ)割り込み処理ルーチンのある地点は,常に同じ周期(タイマーの周期)で実行されることになります.
なお,sei命令の直後の命令は(割り込みが発生せず)必ず実行されるので,sei命令の直後にsleep命令を実行すれば確実にタイマー割り込みによってsleepから復帰できます.
割り込みルーチン(ファームウェア中のTIMER0_OVF0_vectという関数)では,単純にその時点でのカウンタの値を保存しておきます.前回の割り込み発生時に保存した値と,今回の値の差をとれば,タイマーの計測開始とカウンタの計測開始の時間のズレなどは気にせずに,単位時間あたりのパルス数を得ることができます.

今回使用したAT90S2313には8ビットと16ビットのタイマー/カウンタが1つずつありますが,8ビットの方をタイマーとして,16ビットの方をカウンタとして使いました.測定する周波数は最大でも600kHz程度以下で測定周期は0.1秒なので,カウンタは16ビットあれば足ります.タイマーは8ビットですが,必要な回数だけループを回して0.1秒の計測を行います.
カウンタに8ビットを,タイマーに16ビットを使おうとすると,タイマーでは直接0.1秒を計測できますが,カウンタはオーバーフロー時の処理が必要になります.しかし,上述のようにsleep命令を使うとポーリングによりオーバーフローを監視することはできず,またカウンタのオーバーフロー割り込みを使って処理を行うとタイマー割り込みと重なる可能性があり,正確なタイマー割り込みのタイミングを保障できなくなりますので,その選択はできません.

今回作成したような測定器では測定間隔はあまり短くても意味がなく,1秒間に数回測定結果を更新できればよいので,周波数の測定は4回測った結果を平均するようにしています.

avr-gccのバージョンとコードサイズ

AVRのファームを書く場合,それほどプログラムの規模は大きくなく,またRAM等のリソースが限られていたりクロックのタイミングを考慮したいことが多いので,以前はほとんどアセンブラで書いていました.
しかしファームの作成にかかる手間が大きいので,最近は生産性を考えてC言語(avr-gcc)を使うようにしたいと考えるようになり,今回もCでファームを書きました.Cで書くと,特にデバッグ用のコードもすぐに書き足すことができて開発効率がよいです.
ただし,今回使用したAT90S2313はフラッシュROMが2KBと小さいため,コードサイズを小さくする必要があります.
このファームを書いた当初は,どうしても100バイトほどROMサイズをオーバーしてしまい,これ以上小さくすることができませんでした.
その時こちらのページ,でavr-gccはバージョンによって生成されるコードサイズが結構異なり,古いバージョンの方がコードサイズを小さくできる場合があると知り,試しにそれまで使っていたWinAVR-20081205ではなくWinAVR-20060421のavr-gccを使ってみたところ,コードサイズが2048バイト以下に収まりました(1952バイト).また,前者のavr-gccではコードサイズで最適化する"-Os"オプションがうまく働かず関数のインライン展開などを自動で行ってコードサイズを増やしてしまうのでこれまで"-O1"オプションでコンパイルしていましたが,後者では"-Os"が問題なく働きました.

完成

ケースに組み込んで完成しました.
RS-232Cの出力はステレオミニジャックを利用しています.

完成1 完成1  完成2 完成2

使い方は,コンデンサの容量を計測する場合はまず測定端子に何もつながないオープンな状態でゼロリセットのプッシュスイッチを押します.次に,測定端子に測定対象のコンデンサを接続すると,その容量が表示されます.
コイルの場合は,まず測定端子を銅線でつなぐなどしてショートした状態でゼロリセットボタンを押し,その後測定対象をつなぎます.

また,ゼロリセットボタンを押したままで電源を入れると,測定した周波数を表示しつづけるモードになります.測定端子に何かつなげばその状態での発振周波数が表示されますし,このモードでゼロリセットボタンを押すとキャリブレーション用の1000pFのコンデンサを接続した状態になるのでそのときの発振周波数を表示することができます.
この回路では1000pFのコンデンサと82uHのコイルで発振回路を構成しているので,何もつながない状態で周波数は556kHz程度になるはずですが,実際は593kHzとなりました.コンデンサ(誤差5%のポリプロピレンコンデンサ)が正確だと仮定すると,使用したマイクロインダクタのインダクタンスが実際は72uH程度だったと考えられます.

基準となる正確な測定器を持っていないのでこのLCメーターの正確さを確かめることはできませんが,手持ちのコンデンサをいくつか測定した限りでは比較的正確な値を示しているようなので,今後活用していきたいと思います.


[戻る]
2009-12-28 ページ更新(配線図でD1の向きが逆になっていたのを修正)
2009-02-21 ページ作成
(2009-02 製作)
T. Nakagawa