ADCのしくみ: 逐次比較
AVRは基本的にはディジタルICです。ではどうして、入力端子に印加されたアナログ電圧がわかるのでしょうか? 実は正確な電圧は計れません。しかし巧みに判断を繰り返すことで、ある程度の範囲までは絞り込めます。AVRのADCで使われている方式は「逐次比較(successive approximation)」と呼ばれます。
逐次比較方式では、まず、AREF端子に印加されている基準電位(reference voltage)の半分の電位(=AREF/2)を内蔵10ビットDACで作り出します。そうして、入力されたアナログ電圧とこのAREF/2とを比較します。入力電圧のほうが高い場合は、ADCは1を書き出し、内蔵DACの出力電位がAREFとAREF/2との中間電位にセットされます。入力電圧のほうが低い場合は、ADCは0を書き出し、内蔵DACの出力電位がAREF/2とGND電位との中間電位にセットされます。
AVRのADCは、この比較を10回繰り返して10ビットの結果を得ます。比較を繰り返すたびにDACの出力電位を変えて、電圧範囲を半分ずつに絞り込んでゆくわけです。最上位ビットが1であれば入力電圧はAREF/2よりも高いことがわかります。最上位ビットから1、0と続いていれば、入力電圧はAREF/2よりも高いがAREF × (3/4)以下であることがわかります。以下同様です。ビットごとに、入力電圧の存在している電圧範囲が半分ずつに絞り込まれ、10ビットの最後まで来れば、1024 (=210)分割されたどの電圧範囲に入力電圧が存在しているのかがわかります。得られた10ビットの二進数値がその電圧範囲を正確に指します。
逐次比較方式では、入力電圧を取り込んで一定に保っておく必要があります。その手段としてADCのまさに入口にサンプルホールド(sample and hold)回路があります。サンプルホールド回路は、入力端子にキャパシタを接続して入力電圧までキャパシタを充電するための回路です。入力電圧まで充電されるとキャパシタの接続が外されて、キャパシタから出力されるほぼ一定に近い電圧が逐次比較に使われます。そのため、たとい入力電圧の変化が激しくても、サンプリングの始まった時点で取り込まれた電圧が保っておけます。
では、ICユーザーとプログラマーは一体何をすればいいのでしょうか。ADCを使うということは要するに、まずADCのクロック速度(詳しくはあとで説明します)を設定してから、ADCレジスタの"start conversion"ビットをセットしてサンプルの取得開始をADCに命令するということです。AD変換が終われば"start conversion"ビットはクリアされます。AD変換の開始から終了まで何もせずにただ"start conversion"ビットが変化するかどうかを見ているだけでもいいし、割り込みサービスルーチン(ISR)を設定しておいてAD変換の終了をきっかけに割り込みをかけることもできます。ADCは満足できる程度には速いため、比較的シンプルなブロッキングウェイト(blocking-wait)方式がよく使われます。逆にCPUとADCの速度をぎりぎりまで引き出そうとするときはISR方式のほうがいいでしょう。本書ではその両方のサンプルコードを示します。
―――――――――――――――――――――――――――――――
訳註: 逐次比較をPythonで再現してみる。単に定義どおりに二進数に変換しているだけのことであった。
import numpy as np def adc(analogVolt, refVolt, numOfBits): upperVolt = refVolt lowerVolt = 0.0 digitalVal = 0 for i in range(numOfBits)[::-1]: # 下の処理をビット数だけ繰り返す: midVolt = (upperVolt + lowerVolt) / 2.0 # 或る電圧範囲(upperVolt~lowerVolt)の中間電位(midVolt)を求めて、 if analogVolt > midVolt: # その中間電位よりも入力電圧が高ければ、 digitalVal |= (1 << i) # 1を書き出して、 lowerVolt = midVolt # 電圧範囲をさらに半分に絞り込むが、 else: # 中間電位よりも入力電圧が高くなければ、 upperVolt = midVolt # 1を書き出さずに電圧範囲をさらに半分に絞り込む。 return digitalVal if __name__ == "__main__": refVolt = 3.3 numOfBits = 4 delta = 0.1 analogVolts = np.arange(0, refVolt + delta, delta) for analogVolt in analogVolts: digitalVal = adc(analogVolt, refVolt, numOfBits) binVal = bin(digitalVal) hexVal = hex(digitalVal) print("%.1f(V), AD=%d, %s, %s" % (analogVolt, digitalVal, binVal, hexVal))