トップ 新規 編集 差分 一覧 ソース 検索 ヘルプ PDF RSS ログイン

Getting Started Notes - ADC

最終更新時間:2007年08月26日 13時12分53秒

Analog/Digital Converterの使い方(修正中)

  • (コメント2005/02/09) ADCに繋ぐ回路の出力インピーダンスは概ね10kΩ以下推奨って、どこかに書いてあったと思います。
  • (2005/02/12)データシートにありますね。うーん、このドキュメントは本来ソフトウェア(avrgcc)のtutorialですので、回路のことをどこまで書くかは悩んでしまいます。ADCは回路について書くべきことが多そうですから。その辺のことは[サンプル回路・サンプルプログラム]あたりがよくないですか?なにか書いてくれるとうれしいです。
  • AVRStudio では変換開始から変換終了時の割り込み発生イベントなどは動く (ただ、タイマイベント自動開始モードや連続変換モードは最初の一回分しか動かない?) が、変換データそのものは常に 0 になっている。

 概要

 AVRにはアナログ信号の処理のために、A/Dコンバータ(以後ADC)が内蔵されています。このADCは10bit分解能でアナログ信号を捉えることが出来ます。このADCは逐次比較型動作を行います。

 複数のアナログ信号を扱えるようにするため、ADCの前にアナログマルチプレクサが置かれています。このマルチプレキサはポートAに接続されており、これによってPORTAの8つのpinそれぞれのアナログ信号を処理することが出来ます。一度に変換できる信号は8つの信号のうちの1つだけです。

 ADCは10bit(基準電圧が5Vでも分解能は5mV)でアナログ電圧を測るので、ノイズ低減が大事。データシートの「アナログノイズ低減技術」の項を参照ください。

 ADC関連レジスタ(mega8の場合)

ADCL,ADCH(ADCW) A/Dデータレジスタ

AD変換の結果を返すレジスタ。avrgccではADCWというペアレジスタとして16bit値を取得できる。データシートにはペアレジスタとしての名称をADCDとも書いてある。通常は下位10bitに変換結果が入る(変換結果下位8bit=ADCL、上位2bit=ADCHの下位2bit)。ADMUXのADLARビットがセットされていると上位10bitに変換結果が入る。レジスタアクセスの同時性確保のため、ADCLが読まれるとADCHが読まれるまでADCWの更新がブロックされる。そのため、その間にAD変換が終了するとその結果は失われる。

ADMUX:入力源と基準電圧など

bit7-6 REFS1-REFS0 基準電圧選択

00=AREFピン電圧 01=AVcc電圧 10=予約 11=内部1.1V基準電圧

  • mega8 では内部基準電圧は 2.56V。デバイスによって微妙に異なる。データシート参照。
  • 00 以外を選ぶ場合、AREF ピンに内部基準電圧が出力される。AREF が例えば AVCC に繋がっていないかどうか注意。
  • AT90S系にはREFSビットはなく、基準電圧はAVcc(Vcc)固定です。
bit5 ADLAR ADC Left Adjust Result ADC左揃え選択

ADLAR=1だと、ADRWペアレジスタの上位10bitに変換結果が入る。ADLAR=0だと下位10bit。AT90S系にはこのビットはありません。

  • 8bit以下の精度でいい場合はADLARをセットすればADCHだけを取得すればいいので便利.

ADCHはADCL読み出し時バッファされるらしいのでちょっと不安ですけど、ADCLを読まないならADCHだけ読み出すことは問題はない模様です。

bit3-0 MUX3-MUX0 ADチャンネル選択

8個の信号のうちどれをADCに接続するかを決定する。
MUX3-MUX0=0〜7の場合、アナログ入力チャンネルを8つのピンから1つ選択。
その他に1110=基準電圧1.1V、1111=GNDが定義されている(AT90S系ではMUX3はないのでこの2つはない)。データシート参照。

  • なお、mega8 の基準電圧は 1.23V (1.1V でも 2.56V でもない!)。

ADCSRA (AT90S系では ADCSR )

bit7 ADEN ADC許可

  ここがセットされるとADC機能が有効になる

bit6 ADSC ADC開始
  セットされると単独変換および連続変換の最初の変換を開始させる。
bit5 ADFR 連続/単独変換動作選択 (mega88ではADATE)
  セットされると連続変換モード
bit4 ADIF AD変換完了割込要求フラグ
  AD変換が終了した時点でこのビットが立つ。ADIE参照。
bit3 ADIE AD変換完了割込許可
  このビットとSREGの全割込許可がセットされていれば、AD変換が終了した時点(ADIFがセットされた時点)で割り込みを発生させる。

bit2-0 ADPS2-ADPS0 AD変換クロック選択

  ADCにはADC独自の50〜200kHz(*)の入力クロックfadcを必要とします。この範囲内に入る程度のクロックをシステムクロックを分周して供給します。分周比はレジスタADCSRA(*)のbit ADPS0〜2で設定されます。以下にADPS2-0の値と分周比、対応するシステム周波数の値を記します。現在のシステム周波数から適切な分周比とADPSの値を選んでください。

ADPS2〜0 ADCクロック  システム周波数(MHz)
 000   CK/  2    0.1〜0.4
 001   CK/  2    0.1〜0.4
 010   CK/  4    0.2〜0.8
 011   CK/  8    0.4〜1.6
 100   CK/ 16    0.8〜3.2
 101   CK/ 32    1.6〜6.4
 110   CK/ 64    3.2〜12.8
 111   CK/128    6.4〜25.6
  • クロック周波数fosc=4MHzなら、上表よりfadを50〜200kHzにするには、1/32または1/64が適切となるので、ADPSは0b101または0b110を設定する。
  • ADCの動作自体は 1MHzまで保証されている。ただ変換誤差は 3-4.5LSB に悪化する。
  • 初回変換は25ADCクロック、2回目以降の変換なら13ADCクロックを要するので、ADCの頻度はfadcが50-200kHzの範囲内なら初回500〜125μsec、2回目以降260〜65μsecとなります。

ADCSRB (mega48/88/168など)

bit6 ACME アナログコンパレータマルチプレクサ許可

アナログコンパレータの項参照。mega8ではSFIORレジスタにあるもの。

bit2-0 ADTS2-ADTS0 AD変換自動起動要因選択

連続起動モードではADCの終了により次のADCを開始していたが、ADC開始を他の割り込みなどによって行わせることが出来る。後述する変換自動起動モード参照。

DIDR0 (mega48/88/168など)

bit5-0 ADC5D-ADC0D デジタル入力禁止

対応する ADCn ピンのデジタル入力バッファをオフにする。この時、PINx の当該ビットは常にゼロになる。

 単独変換モード:

概要

 おおざっぱに言って、ADCの操作方法は以下のようになります。

ADCの設定
    ↓
ADC起動(ADCSRのADSCビットに1を書き込む)
   同時にADIFビットにも1を書き込むこと(フラグをクリアする)
    ↓
ADCSRのADIFが立ち上がるのを待つ(割り込みで検知してもいい)
    ↓
変換データを取り出す

ADIFによる変換終了確認

/* アナログ入力ch0からのADC単独変換モード。
   変換結果 lo_valはportB(LED)に出力されます。
   10/00 Leitner Harald / arranged by akibow */
#include <avr/io.h>
#define ADC_ENABLE (_BV(ADEN)|_BV(ADIF)|0b110)
//  ADC8MHzでfad=125kHz、ADIFフラグのクリア
#define ADC_START  (ADC_ENABLE|_BV(ADSC))
//  ADC開始。ADC設定関連を消さないようにADC_ENABLEも書き込む

int main( void )
{
    DDRB = 0xFF;        // PORTBを出力に
    //ADCノイズ低減モードスリープ(AT90Sシリーズの場合はアイドルモードで)
    ADCSR = ADC_ENABLE;        // ADC有効
    ADMUX = 0;          
    // Analog input 0を選択。ADCを有効にしてから操作した方がいいらしい
    // DIDR0 = _BV(ADC0D); //  mega48/88 等の場合、対応ピンのデジタル入力を禁止しておく

    ADCSR = ADC_START;        //変換開始
    loop_until_bit_is_set(ADCSR,ADIF);  //変換終了時ADIFがセットされる
    ADCSR = ADC_ENABLE; //ADIFのクリア.この場合はなくてもいいけど
    PORTB = ~(ADCW>>2); //値を取り出す。8bit化してPORTBに出力
    for (;;);
}
  • まず、大まかなADC設定をします。ADCSRレジスタに設定を書き込みます。
  • その後、どのポートからアナログ電圧を読み込むか、基準電圧をどうするかなどを設定するために、ADMUXの設定をおこないます。ここでは、ADCSRにより単独変換モードが選択され、ADC入力としてch0(PORTA-PA0)が選択されています。
  • 次に、ADCSRレジスタのADSCビットを書き込み、変換を始動します。このとき、ADIFレジスタを書き込みあらかじめこのフラグをクリアすること、その他のビットを書き込みせっかくおこなった設定が消えないように注意してください。
  • 最初の変換が終了した後、ADCSRレジスタのADIFビットが1になります。これをチェックして変換終了を知り、変換値をADCH/ADCLから読み出します。ADC変換値(10bit)を2bit右シフトして、上位8bit値を得た後PORTBにつないだLED列で表示させています。
    • ここではペアレジスタを16bitレジスタのように扱えるavrgccの機能を利用して、adc_val=ADCW; で読み出しています。これは、実際には以下のような動作をまとめているものです。
L=ADCL; /* このとき同時にADCHもバッファに保存される */
H=ADCH; /* ADCHではなく、先に保存したバッファの値が読み出される */
adc_val = H<<8 | L;
    • ADCL/ADCHを別々に読み出すときは、同時性を確保するために下位バイトを先に読み出さなければなりません。AVRの機能として、下位バイトを読み出した時点で上位バイトも読み出されバッファに保存されます。次に上位バイトを読み出すと、その時点での上位バイト値ではなく、下位バイトを読み出したときに保存しておいた上位バイトの値が読み出されます。これによって、上位下位の値は同じ瞬間の値であることが保証されます。ADCWなどのペアレジスタの操作では、この順序は適切に考慮されます。

スリープの併用

/* アナログ入力ch0からのADC単独変換モード。
   変換結果 lo_valはportB(LED)に出力されます。
   10/00 Leitner Harald / arranged by akibow */
#include <avr/io.h>
#include <avr/interrupt.h>

#define ADC_ENABLE (_BV(ADEN)|_BV(ADIF)|_BV(ADIE)|0b110)
//  ADC8MHzでfad=125kHz、ADIFフラグのクリア
#define ADC_START  (ADC_ENABLE|_BV(ADSC))
//  ADC開始。ADC設定関連を消さないようにADC_ENABLEも書き込む

EMPTY_INTERRUPT(SIG_ADC); //空っぽの割り込み。SLEEPから起きるためだけに利用

int main( void )
{
    DDRB = 0xFF;        // PORTBを出力に
    //ADCノイズ低減モードスリープ(AT90Sシリーズの場合はアイドルモードで)
    MCUCR=_BV(SE)|_BV(SM0);
    ADCSR = ADC_ENABLE;        // ADC有効
    ADMUX = 0;                 // Analog input 0を選択
    // DIDR0 = _BV(ADC0D);     //  mega48/88 等の場合、対応ピンのデジタル入力を禁止しておく。 */
    sei();

    ADCSR = ADCSR|_BV(ADSC);        //変換開始
    asm("sleep"::);  //変換が終わるまで寝ておく
    PORTB = ~(ADCW>>2); //Sleepから目覚めたら値を取り出す
    for (;;);
}

変換中スリープに入るようにすると、CPUの動作ノイズを最低限に抑えられるため、ややノイズに有利になります。

 連続変換モード

概要

ADCの設定(ADC有効、連続変換モード、分周比設定、ADIFクリア)
    ↓
最初のADC起動(ADCSRのADSCビットに1を書き込む)
   同時にADIFビットにも1を書き込むこと(フラグをクリアする)
    ↓
ADCSRのADIFが立ち上がるのを待つ(割り込みで検知してもいい)
    ↓
ADIFクリアし、変換データを取り出す
    ↓
次の変換終了を待つ(ADIFポーリングまたは割り込み待ち)

ADIFポーリング版

/* アナログ入力ch0からのADC連続変換モード。
変換結果 lo_valはportB(LED)に出力されます。
7/00 Leitner Harald , 01/05 modified by anonymous@2ch.net */

#include <avr/io.h>
#include <avr/interrupt.h>

uint8_t lo_val, hi_val;

#define ADC_ENABLE (_BV(ADEN)|_BV(ADIF)|_BV(ADFR)|0b110)
// ADC有効、連続変換モード,クロックCK/64(8MHz時125kHz)
#define ADC_START  (ADC_ENABLE|_BV(ADSC))

int main( void )
{
    DDRB = 0xFF;        /* PORTBを出力に */
    MCUCR=_BV(SE)|_BV(SM0);
    //ADCノイズ低減モードスリープ(AT90Sシリーズの場合はアイドルモードで)
    
    ADCSR = ADC_ENABLE;
    ADMUX = 0;                /* Analog input 0を選択*/
    
    ADCSR = ADC_START;
    for (;;) 
    {
        loop_until_bit_is_set(ADCSR,ADIF);  //変換終了時ADIFがセットされる
        ADCSR = ADC_ENABLE; //ADIFのクリア
        adc_val = ADCW;  //ADCL,ADCHを読み込み、16bit値として得る
        PORTB = ~(adc_val>>2); //上位8bit値を出力
    }
}

このプログラムは同様の動作を行う。連続変換モードでは(自動的に変換を繰り返し、そのたびにSIG_ADC割り込みがかかるため)変換値は永久に更新され続けます。

割り込み使用

/* アナログ入力ch0からのADC連続変換モード。
変換結果 lo_valはportB(LED)に出力されます。
7/00 Leitner Harald , 01/05 modified by anonymous@2ch.net */

#include <avr/io.h>
#include <avr/interrupt.h>
uint8_t lo_val, hi_val;

#define ADC_ENABLE (_BV(ADEN)|_BV(ADIF)|_BV(ADIE)|_BV(ADFR)|0b110)
// ADC有効、連続変換モード,クロックCK/64(8MHz時125kHz)
#define ADC_START  (ADC_ENABLE|_BV(ADSC))

ISR(ADC_vect)
{
    uint16_t adc_val;
    adc_val = ADCW;  //ADCL,ADCHを読み込み、16bit値として得る
    PORTB = ~(adc_val>>2); //上位8bit値を出力
}

int main( void )
{
    DDRB = 0xFF;        /* PORTBを出力に */
    MCUCR=_BV(SE)|_BV(SM0);
    //ADCノイズ低減モードスリープ(AT90Sシリーズの場合はアイドルモードで)
    
    ADCSR = ADC_ENABLE;
    ADMUX = 0;                /* Analog input 0を選択*/
    ADCSR = ADC_START;
    sei();
    for (;;) asm("sleep"::);
}

割り込みルーチン内で処理しない方法

#include <avr/io.h>
#include <avr/interrupt.h>

uint8_t lo_val, hi_val;

#define ADC_ENABLE (_BV(ADEN)|_BV(ADIF)|_BV(ADIE)|_BV(ADFR)|0b110)
// ADC有効、連続変換モード,クロックCK/64(8MHz時125kHz)
#define ADC_START  (ADC_ENABLE|_BV(ADSC))

EMPTY_INTERRUPT(SIG_ADC);

int main( void )
{
    DDRB = 0xFF;        /* PORTBを出力に */
    MCUCR=_BV(SE)|_BV(SM0);
    //ADCノイズ低減モードスリープ(AT90Sシリーズの場合はアイドルモードで)
    
    ADCSR = ADC_ENABLE;
    ADMUX = 0;                /* Analog input 0を選択*/
    ADCSR = ADC_START;
    sei();
    for (;;) 
    {
        loop_until_bit_is_set(ADCSR,ADIF);  //変換終了時ADIFがセットされる
        adc_val = ADCW;  //ADCL,ADCHを読み込み、16bit値として得る
        PORTB = ~(adc_val>>2); //上位8bit値を出力
    }
}

=======ここまで========

  • いずれの例も AVRStudio では最初の一回分しか ADC は動作しない。AVRStudio のバグ?
  • ほんとだ。これは検証してなかったわスマソ。ソースのバグじゃないよな?
  • ADPS設定を忘れていた。とりあえず0b110(CK/64,8MHzで125kHz)をいれといた。でもシミュレートできないね。Sleep命令を除去してもだめ〜、そのうち実機確認します。(いつになるやら)

 変換自動起動モード:

mega48/88/168 や tiny13 などで新しくサポートされたモード。以下の割り込み要求フラグが立った時、変換が開始される。

レジスタ ADCSRB bit2〜0
ADTS2 ADTS1 ADTS0    起動元
   0     0     0     連続変換動作
   0     0     1     コンパレータ
   0     1     0     外部割り込み 0
   0     1     1     タイマ/カウンタ0 比較 A 一致
   1     0     0     タイマ/カウンタ0 オーバーフロー
   1     0     1     タイマ/カウンタ1 比較 B 一致
   1     1     0     タイマ/カウンタ1 オーバーフロー
   1     1     1     タイマ/カウンタ1 キャプチャ要求

上記の表のとおり、従来の連続変換モードは自動起動モードの一種という扱いになった。連続変換動作とは、つまり ADC 変換完了割り込みフラグによって変換開始するモードという意味になる。

  • 上記の各々の割り込みルーチン内で ADC を起動するのとちがい、全割り込み許可フラグの影響を受けないため、上記イベントから ADC 変換開始までの時間が厳密に一定なのが特徴。
  • ただ、当の割り込みを発生させないなら、割り込み要求フラグを自前でクリアしなければならない。
  • このモードでは A/D変換をスリープ中に行わせるには一工夫要る。後述。
  • 以下のソースは ATmega48, 12MHz での例。
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>


// f = 12MHz を 1/8 したクロックをタイマに供給、
// 設定値 150 でフリーラン。
// 12MHz / (8 x 150) = 10kHz(100us)周期で A マッチ発生。

void init_tick0(void) {

  TIMSK0 = 0;           // タイマ関連全割り込み禁止。
  TCCR0B = 0;           // タイマ0停止。
  TCCR0A = 0b10;        // CTC動作、OC0A(PD6),OC0B(PD5)出力ピン不使用。
  OCR0A  = 150 - 1;     // TOP値。
  TCNT0  = 0;           // カウンタ0 をリセット。

  TCCR0B = 0b010;       // CTC動作、内部クロック 8 分周動作、カウント開始。
}

void init_PortD(void)
{
    PORTD = 0b00000000;   // 0 初期化。
    DDRD  = 0b11111111;   // PORTD を出力へ。
}

void init_adc(void)
{

    ADMUX  = 0b01000000;  // AVCC基準、ADC0ピン入力、右揃え。
    DIDR0  = _BV(ADC0D);  // ADC0 ピンのデジタル入力停止。 
    ADCSRA =  _BV(ADATE) | _BV(ADIE) | _BV(ADIF) | 0b110;
                          // 変換自動起動モード、変換完了割込許可、ADCクロック f/64 = 187.5kHz.

    ADCSRB = 0b00000011;  // タイマ0,比較A一致で自動起動。
                          // 初回変換に 25adccyc (1600cyc)
                          // 次回以降   13.5adccyc(864cyc; 約72.0us)

    ADCSRA |= _BV(ADEN);  // ADC部起動。変換開始フラグ(ADSC)は必要ない。
}

int main(void)
{
    init_PortD();
    init_adc();
    init_tick0();
    // SMCR=_BV(SE);
    sei();
    for (;;){
      //  asm("sleep"::); // 変換自動起動モードではこういう形で sleep を使うことはできない。
    }
}
ISR(ADC_vect)

{
    uint16_t adc_val = ADCW;
    PORTD = adc_val >> 2;
    TIFR0 = _BV(OCF0A);   // タイマ割込自体は必要ないかわり割込フラグのクリアが必要になる。
}
  • この例ではタイマ 0 を必要とするため、A/D 変換ノイズ低減動作に入れないのは当然だが、アイドル動作でも正しく動かないように見える。アイドル動作やノイズ低減動作に入ると、自動起動モードでなく連続変換モードとして動作する?
  • AVRStudio では ADC は ADSC でのみ起動するらしい ... ので、変換自動起動モードはまったく動作しないみたいです。上の例も実機で確認。つーか、このバグの検証で昨日今日とADCの項にごちゃごちゃ書きこみが増えてたんだったり(汗)
  • sleep を使う場合、データシートの「電力管理とスリープ動作」のアイドル動作、A/D変換ノイズ低減動作の項にあるように、 sleep すると同時に A/D変換が始まってしまう。通常 ADC完了割り込みからの復帰と同時に sleep に入るので、ここで A/D変換がすぐに始まることになって、連続変換のように見える。
    • すんません、そこ何度も見たのになぜか見落としてました(sleepでAD変換開始)
  • タイマ割り込みのほうはどう扱われるかというと、フラグが立った時点では A/D変換の真最中なので、データシートの「A/D変換器」の変換の開始の項にあるように、これは単に無視されることになる。
  • もし sleep を使うなら、sleep する瞬間には既に A/D変換が始まっているようにタイミングを調整するしかないかな。ADC完了割り込みが起きてから、タイマ割り込みフラグが立つまで寝ずに起きていればいいと思う。
    • こんな感じ?
set_sleep_mode(SLEEP_MODE_ADC);
for(;;) {
    //必要ならここでなにか処理をする
    while (bit_is_clear(TIFR0,OCF0A)); //抜けると同時に変換開始
    TIFR = _BV(OCF0A);
    sleep_mode();  //ADC動作中なので改めてADC変換が始まることはない
    //ただし、このスリープ期間はTimer0動かないのでその分考慮・・・
}

もしくは

EMPTY_INTERRUPT(SIG_OUTPUT_COMPARE0A);
for(;;) {
    //必要ならここでなにか処理をする
    set_sleep_mode(SLEEP_MODE_IDLE);
    ADCSRA &= ~_BV(ADIE); //sleep突入で起こるADCで生じるADC割り込みでは起きない
    sleep_mode();  //COMPARE0Aで起こされるまで寝ておく
    ADCSRA |= _BV(ADIF); //無駄変換による割り込みフラグoff
    ADCSRA |= _BV(ADIE); //今度はADC割り込みで起きる。
    set_sleep_mode(SLEEP_MODE_ADC);
    sleep_mode();  //ADC動作中なので改めてADC変換が始まることはない
    //ただし、このスリープ期間はTimer0動かないのでその分考慮・・・
}
    • ただし、ADC時間>OCR0A設定時間であること。
    • 実際の変換周期=ADC時間+OCR0A設定時間+α