PSoC容量計
チュートリアルではPWMでLEDの点滅を行ったのだが、これではまだ、今ひとつPSoCの美味なるところが見えないようでもある。やはりもうちょっとPSoCらしい雰囲気のあるアプリケーションはないかということで、試しに容量計を作ってみよう。
容量の測定方法はいろいろ考えられるが、ここではコンデンサの充放電にかかる時間を測定することで実現してみることにした。
写真は公称0.1μFのコンデンサを測定してみたもの。有効数字無視の表示をしている。こんなとんでもない精度は無いけれども、秋月の容量測定機能付きのマルチメータの指示が113.2nF(0.1132μF)とか言うので、それなりに合ってる・・と思う。

※言うまでもないけれど、抵抗の誤差、内部の24MHzの誤差などの影響を受けるので、ここまで一致したのは「たまたま」の筈。
 それでも運が良ければ無調整でもこの位になることはあるってことで。(追記:2004年9月8日)


プロジェクト一式はここをクリック
回路図

てなわけでこれが回路図であるなんじゃこりゃ?ということになるだろう。右は表示用の液晶パネル(秋月で買った16文字×2行もの)。肝心の測定計は左のP0.1とP0.1のところである。抵抗はCRの時定数を決める基準抵抗で、プログラム内の値と合わせておく。100Kや1Kなどと置き換えてやればレンジ切り替えになる。


測定のしくみ
下の図が今回製作する容量計のキモの部分。その下は動作波形。
Counter16(16ビットカウンタ)で矩形波をこしらえて、これでRを通してコンデンサを銃放電する。抵抗の値が分かっていれば、二つの電圧間を通過する時間からコンデンサの容量を算出できることになる。
理屈の上では充電方向、放電方向のいずれでも良いようなものだが、充電方向はPSoCの電源電圧に依存してしまうので少々うっとおしい。放電方向ならGNDの電圧は電源電圧に依存しないからお手軽ということで、放電方向で測定した。
判定用の電圧はPSoC内部のそれなり高精度な基準電圧である、BandGap電圧(1.3V)を利用するのがお約束。
BandGap+/-BandGap
として、VREFHI(BandGap+BandGap=2.6V)からAGND(BandGap=1.3V)に至るまでの時間を測定してやろうという魂胆である。

※時定数が長くなると、コンパレータの出力がドタバタする筈なので、本当はヒステリシスコンパレータにしたほうがいい・・とは思う。


P0.0の出力が'L'(0Vと仮定)、時刻tにおけるコンデンサの端子間電圧Vc、抵抗に流れる電流をIとすると、
Q=CV(C:コンデンサの容量)
より、Δt(秒)後の電圧変化ΔVcは
ΔVc=-ΔQ/C=-IΔt/C=-(Vc/R)Δt/C
ΔVc=-Vc/(RC)Δt
1/Vc dVc=-1/(RC)dt
∴log(Vc)=-1/(RC)t+C0(C0は定数)
t=0の時にVc=Vvrefhiとすれば
C0=log(Vvrefhi)
Vc=Vagndになるまでの時間で計測すると
log(Vagnd)=-1/(RC)t+log(Vvrefhi)
C=t/(R*(log(Vvrefhi)-log(Vagnd)))= t/(R*log(Vvrefhi/Vagnd))
カウンタの差分値をN、カウンタの動作クロックをF(Hz)
とすればt=N/Fより

C=N/(F*R*log(Vvrefhi / Vagnd)) (あってるかな?)

内部ブロック図

※:Counter16/Timer16_1/Timer16_2のクロックは12MHz(SysClk/2)

もうちょっと細かく書いた内部ブロックがこれ。別に大した物でもなくて、要するにコンパレータとタイマーのペアを二つ用意して、スレッショルドを超えたところでカウント値をキャプチャしてやれば良いという仕掛け。ここでポイントになるのは、カウンタと、二つのタイマの周期が全部同じであること。タイマーのスタートタイミングはCPUで一つずつかけるのでずれがあるけども周期が同じなので、この差は「いつまでも一緒」になるのがポイントになる。
今回必要なのは二つのタイマの間の差分だけなので、カウンタとの間のずれは問題にならないけれども、タイマの方はもろに時間測定に関わるところなので無視するわけにはいかない。そこで先ほどの「ズレはずっと一緒」というのがポイントになる。
Timer16_2のキャプチャ入力をTImer16_1と同じにして同時にキャプチャしたときの差分をオフセット分として保存しておいて、計測モードで計測された差分からこのオフセット分を引いてやると、ズレ分がキャンセルできるという理屈である。
キャプチャした値の読み込みは放電完了して充電に映るタイミングで行う。次の放電が始まるまでの時間(1/12MHz*65536/2=2.73ms)以内に応答すれば良いので結構楽。


プログラム
細々したところはソースを見て頂こう。ポイントはCounter16_1INT.asmの中で

  ljmp _isr_Counter16_1

の一行を入れておくこと。
一方、Cプログラムの側では

  #pragma interrupt_handler isr_Counter16_1

としておくと、isr_Counter16_1()関数が割込処理用の関数になり、レジスタの待避/復帰などのコードが生成される。

  void isr_Counter16_1()
  {
    if (count16int == 0) {
      count16int = 1;
      t1val = Timer16_1_wReadCompareValue();
      t2val = Timer16_2_wReadCompareValue();
    }
  }

てなぐ
あいで、それぞれのカウント値が得られる。フラグを見ているのは、タスク側が値を拾ったかどうかを見て、処理中に書き換えてしまうことがないようにするため。
あとは上でチョロッと健闘した怪しい式に放り込むと値が出ておめでとうである。