LECTURE 4 A/D 変換でボリューム(可変抵抗)値を読む

○ はじめに

突然ですが、マイコンで遊んでいると光センサや赤外線センサ、加速度センサといった装置を取り付けて周囲の状況を収集してみたくなりませんか?(ならない人も、そういう気持ちになったつもりになって読んでくださいね…)

そういうセンサが提供してくれるデータは、大抵の場合アナログ値です。それに対してマイコンで直接扱える値は 0/1 のデジタル値です。よってマイコンで各種センサを使おうとすると、アナログ値を何とかしてデジタル値に変換しないといけません。これを実現してくれるのが A/D 変換器です。

ここでは ATMega8535 が備えている A/D 変換機能を使い、ボリューム(可変抵抗)を回した量を読み込んでみたいと思います。

○ A/D 変換について

A/D 変換器(ADC) は Analogue(Analog) to Digital Converter を意味していて、アナログ値をデジタル値に変換するための回路です。逆にデジタル値をアナログ値に変換する D/A 変換器 (Digital to Analogue Converter) も当然世の中には有りますが、ATMega8535 が備えているのは A/D 変換器のみです。ATmega8535 の A/D 変換器の主な仕様は以下の様になります。

  • 10 ビットの分解能
  • 変換時間 65 - 260us
  • 8 チャネルの単独アナログ入力
  • x10、x200 のゲインを伴う差動入力
  • アナログ入力電圧範囲 0 - Vcc に対応
  • 変換基準電圧として外部電圧以外に内部の 2.56V 電圧を使用可能
  • 連続/逐次変換動作に対応
  • A/D 変換完了割り込みをサポート
  • スリープ動作中の変換によるノイズ低減

PORTA の 8 ピン(PA0 - PA7)をアナログ入力端子として使用します(PDIP パッケージの場合は 40 - 33 番ピン)。これらのピンは、データシート等では ADC0 - ADC7 として記載していることが多い様ですが、avr-libc では本名称は定義されていない様です。

本ページのサンプルプログラムでは、この A/D 変換機能を利用してボリュームの開度を読み込んでみます。

↓赤枠内がボリュームです。
ボリューム

○ 関連レジスタ

A/D 変換関連のレジスタを以下に紹介します。マクロについては、avr-libc で使用できる定義名を掲載しています。また、日本語のレジスタ名とビット名は、英語の定義名称を適当に訳してます。

  • A/D 変換チャネル選択レジスタ (ADMUX)

    ビット番号 7 6 5 4 3 2 1 0
    マクロ REFS1 REFS0 ADLAR MUX4 MUX3 MUX2 MUX1 MUX0
    初期値 0 0 0 0 0 0 0 0

    各ビットの説明を以下に書きます。

    ビット名 内容
    REFS1-0(基準電圧選択) A/D 変換器の基準電圧を選択します。

    REFS1 REFS0 基準電圧
    0 0 AREF に接続した外部基準電圧
    0 1 AVCC
    1 0 予約
    1 1 内部 2.56V 基準電圧

    ADLAR(A/D 変換結果左揃え) 後述する A/D 変換データレジスタへの変換結果の格納形式を指定します。
    MUX4-0(アナログ入力選択) A/D 変換器に接続するアナログ入力の組み合わせを指定します。

    MUX4-0 単独入力 差動入力
    非反転入力 反転入力 ゲイン
    00000 PINA0 - - -
    00001 PINA1 - - -
    00010 PINA2 - - -
    00011 PINA3 - - -
    00100 PINA4 - - -
    00101 PINA5 - - -
    00110 PINA6 - - -
    00111 PINA7 - - -
    01000 - PINA0 PINA0 x10
    01001 - PINA1 PINA0 x10
    01010(PDIP では保証外らしい) - PINA0 PINA0 x200
    01011(PDIP では保証外らしい) - PINA1 PINA0 x200
    01100 - PINA2 PINA2 x10
    01101 - PINA3 PINA2 x10
    01110(PDIP では保証外らしい) - PINA2 PINA2 x200
    01111(PDIP では保証外らしい) - PINA3 PINA2 x200
    10000 - PINA0 PINA1 x1
    10001 - PINA1 PINA1 x1
    10010 - PINA2 PINA1 x1
    10011 - PINA3 PINA1 x1
    10100 - PINA4 PINA1 x1
    10101 - PINA5 PINA1 x1
    10110 - PINA6 PINA1 x1
    10111 - PINA7 PINA1 x1
    11000 - PINA0 PINA2 x1
    11001 - PINA1 PINA2 x1
    11010 - PINA2 PINA2 x1
    11011 - PINA3 PINA2 x1
    11100 - PINA4 PINA2 x1
    11101 - PINA5 PINA2 x1
    11110 1.23V - - -
    11111 GND - - -


  • A/D 変換制御/状態レジスタ A (ADCSRA)

    ビット番号 7 6 5 4 3 2 1 0
    マクロ ADEN ADSC ADATE ADIF ADIE ADPS2 ADPS1 ADPS0
    初期値 0 0 0 0 0 0 0 0

    各ビットの説明を以下に書きます。

    ビット名 内容
    ADEN(A/D 変換許可) 1 を設定すると A/D 変換器が有効になります。0 を設定すると、A/D 変換器の電源が切れます。
    ADSC(A/D 変換開始) ADATE が 0 の場合(自動変換無効)に、ADSC に 1 を設定することで A/D 変換が開始されます。ADATE が 1 の場合(自動変換有効)にも、初回実行のために ADSC に 1 を設定する必要があります。
    ADATE(A/D 変換自動起動許可) 1 を設定することで A/D 変換が自動起動されます。自動変換の実行は、後述の SFIOR レジスタの ADTS2-0 ビットで指定した信号の立ち上がりエッジで開始されます。
    ADIF(A/D 変換割り込みフラグ) A/D 変換完了時に 1 が設定されます。本フラグをクリアするためには、1 を書き込む必要があることに注意してください。なお、A/D 変換完了割り込みが有効な場合は、割り込みハンドラ実行後に自動的にクリアされます。
    ADIE(A/D 変換完了割り込み許可) 1 を設定すると、A/D 変換完了割り込みが有効になります。
    ADPS2-0(A/D 変換クロック選択) A/D 変換に使用するクロックのシステムクロックに対する分周比を、以下の組み合わせで設定します。

    ADPS2 ADPS1 ADPS0 分周比
    0 0 0 CK/2
    0 0 1 CK/2
    0 1 0 CK/4
    0 1 1 CK/8
    1 0 0 CK/16
    1 0 1 CK/32
    1 1 0 CK/64
    1 1 1 CK/128


  • A/D 変換データレジスタ (ADCH、ADCS)(ADC として 2バイトアクセス可能)

    A/D 変換結果を格納するレジスタです。ADMUX レジスタの ADLAR ビットの値によって 格納形式が変わります。また、ADCL / ADCH の順に読む必要があります。 なお、ADCL の値が必要無い場合は ADCH のみ読みこんでも構いません。
    ADC として 2 バイトアクセスする場合は、 アクセス時に割り込みが起きない様にしてください。

    10 ビットのため、最大値は 0x3ff = 基準電圧 - 1 LSB となります。

    --- ADCH (ADLAR=0) ---

    ビット番号 7 6 5 4 3 2 1 0
    マクロ 未使用 未使用 未使用 未使用 未使用 未使用 - -
    初期値 0 0 0 0 0 0 0 0

    --- ADCL (ADLAR=0) ---

    ビット番号 7 6 5 4 3 2 1 0
    マクロ - - - - - - - -
    初期値 0 0 0 0 0 0 0 0

    ADLAR=0 の場合は ADCL の全ビットが変換結果の下位 8 ビット、ADCH の下位 2 ビットが変換結果の上位 2 ビットとなります。

    --- ADCH (ADLAR=1) ---

    ビット番号 7 6 5 4 3 2 1 0
    マクロ - - - - - - - -
    初期値 0 0 0 0 0 0 0 0

    --- ADCL (ADLAR=1) ---

    ビット番号 7 6 5 4 3 2 1 0
    マクロ - - 未使用 未使用 未使用 未使用 未使用 未使用
    初期値 0 0 0 0 0 0 0 0

    ADLAR=1 の場合は ADCH の全ビットが変換結果の上位 8 ビット、ADCH の上位 2 ビットが変換結果の下位 2 ビットとなります。


  • 特殊I/O機能レジスタ (SFIOR)
    ※ACME / PUD / PSR2 / PSR10 マクロで表されるビットは、A/D 変換とは関係無いビットです。

    ビット番号 7 6 5 4 3 2 1 0
    マクロ ADTS2 ADTS1 ADTS0 予約 ACME PUD PSR2 PSR10
    初期値 0 0 0 0 0 0 0 0

    各ビットの説明を以下に書きます。

    ビット名 内容
    ADTS2-0(A/D 変換自動起動元) ADCSRA レジスタの ADATE ビットを 1 に設定(自動変換有効)した場合に、毎回の A/D 変換を起動する信号を選択します。

    ADTS2-0 信号元
    000 連続変換動作(変換完了後、すぐに次の変換開始。ADSC は 1 のまま)
    001 アナログ比較器
    010 外部割り込み要求0
    011 タイマ/カウンタ0比較一致
    100 タイマ/カウンタ0オーバーフロー
    101 タイマ/カウンタ1比較B一致
    110 タイマ/カウンタ1オーバーフロー
    111 タイマ/カウンタ1捕獲要求

○ 回路図

LECTURE 1 ATmega8535 および使用環境について に掲載してある全体回路図に対して、ATMega8535 の PORTA/PA0 およびボリューム(可変抵抗)部分を赤枠で囲んだ図を こちら に置いておきます。

PORTA の PA0 ピンに 50KΩ のボリュームを接続しています。同時に PA0 を 10KΩ の抵抗によってプルアップしています。これにより、ボリュームの抵抗値が 0 KΩ の場合は PA0 は 0V となり、最大値の 50KΩ の場合に Vcc x 50K/(50K + 10K) = 約 4.17V となります(VCC=5V の場合)。

AVCC(アナログ電源供給部)は VCC (5V) に接続しています。また、AREF(外部アナログ変換基準電圧)を VCC 接続しているため、A/D 変換結果の最大値 0x3ff に 1LSB 足した値が VCC(5V) に対応します。よってボリュームを完全に閉じた状態から開いた状態まで回すと、A/D 変換結果は最少 0 から最大 0x3ff x 4.17V / 5V = 約 853.182(0x355) 程度になると予想できます。

○ サンプルプログラムと説明

A/D 変換を使ってボリューム値を読むサンプルプログラムを 04volume.tar.gz からダウンロードできます。はじめに のページから、全てのサンプルプログラムを 固めた tar ファイルをダウンロードすることもできます。

中には

  • Makefile
  • adc.h
  • adc.c
  • util.h
  • util.c
  • usart.h
  • usart.c
  • main-int.c
  • main-noint.c

が格納されています。usart.h/usart.c については LECTURE 3 USART によるシリアル通信 の内容と同じで Makefile もほとんど変わらないので、それ以外について以下に載せます。なお、以下に掲載しているソースコードには修正 BSD ライセンス条項を含めていませんが、ソースコード配布は修正 BSD ライセンスに従います(はじめに のページの免責事項を参照してください)。

これまでにヒューズビットの設定を行ったことが一度も無い場合は、まず最初に make fuse を実行してください。fuse ターゲットについては、LECTURE 2 LED を点滅させる のサンプルプログラムの項を参照してください。

make 実行後、割り込みを使用したプログラムをアップロードする場合は make uisp-int を実行してください。また、割り込みを使用していないプログラムをアップロードする場合は make uisp-noint を実行してください。実装方法が異なるだけで動作内容は同じです。

本サンプルプログラムは 500ms に 1 回の割合で、ボリューム値を A/D 変換機能で読み込んだ後に、値を "PINA0 input = ..." という形式の文字列に加工してシリアル通信で送信します。A/D 変換結果部分は 10進数表示です。文字列を受信するためには、実験用回路と PC のシリアルポート(D-SUB 9ピン)をストレートケーブルで接続し、Windows であればハイパーターミナル等の通信ソフトで以下のパラメータを設定してフロー制御無しで接続してください。

  • 非同期動作
  • ボーレート 9600 bps
  • パリティ無し
  • ストップビット 1ビット
  • 8 ビットデータ転送

"PINA0 input = ..." という形式の文字列が、ターミナルに連続的に表示されると思います。この間にボリュームを回して可変抵抗値を変化させると、"PINA0 input = ..." の ... の部分に表示される数字が変化します。

↓通信ソフトにはこの様に表示されます。
ボリューム値の読み込み結果表示画面

  • adc.h
        1    #ifndef ___ADC_H_INCLUDED_
        2    #define ___ADC_H_INCLUDED_
        3    
        4    #include <avr/io.h>
        5    #include <inttypes.h>
        6    
        7    
        8    /* A/D変換最大値 */
        9    #define ADC_MAX     0x3ff
       10    
       11    /* A/D変換クロック選択用マクロ */
       12    #define ADC_CK_2    (_BV(ADPS0))
       13    #define ADC_CK_4    (_BV(ADPS1))
       14    #define ADC_CK_8    (_BV(ADPS0) | _BV(ADPS1))
       15    #define ADC_CK_16   (_BV(ADPS2))
       16    #define ADC_CK_32   (_BV(ADPS0) | _BV(ADPS2))
       17    #define ADC_CK_64   (_BV(ADPS1) | _BV(ADPS2))
       18    #define ADC_CK_128  (_BV(ADPS0) | _BV(ADPS1) | _BV(ADPS2))
       19    
       20    /* 割り込み使用/非使用選択マクロ */
       21    #define ADC_NO_INT  0
       22    #define ADC_USE_INT 1
       23    
       24    
       25    #define adc_set_ch(ch) { ADMUX = (ch) & (_BV(MUX2) | _BV(MUX1) | _BV(MUX0)); }
       26    #define adc_wait()     loop_until_bit_is_set(ADCSRA, ADIF)
       27    #define adc_get()      ADC
       28    #define adc_start()    { ADCSRA |= _BV(ADIF); ADCSRA |= _BV(ADSC); }
       29    
       30    
       31    void adc_set_interrupt(void (*func)(void));
       32    void adc_init(uint8_t clock, int use_interrupt);
       33    
       34    
       35    #endif
       36    
       37    /* End of adc.h */
    
    
    • 11 〜 18 行目

      A/D 変換用のクロック指定を楽にできる様にマクロを定義しています。ADCSRA レジスタの ADPS2-0 ビットに対応するビットを _BV() マクロで設定しています。本マクロは 後述の adc_init() 関数実行時に指定します。

    • 25 〜 28 行目

      記述が楽になる様に、あるいは分りやすくなる様にいくつかのマクロを定義しています。

      adc_set_ch() マクロに PINA0 - PINA7(PORTA0/PA0 - PORTA7/PA7 でも良い) のいずれかを指定することで、A/D 変換器に接続するアナログ入力を選択します。 adc_set_ch() では ADMUX レジスタの MUX2-0 に指定値を設定します。 そのため、本プログラムでサポートするアナログ入力は、8 チャネルの単独入力のみです。

      adc_wait() マクロは、後述の adc_start() による A/D 変換開始後に、変換が終了するまでブロックします。 実際には、A/D 変換完了時には ADCSRA レジスタの ADIF ビットが 1 となるため、 avr-libc の loop_until_bit_is_set(sfr, bit) マクロを使って ADIF が 1 となるのを監視します。

      adc_get() マクロは、単に ADC レジスタ(A/D 変換データレジスタ)の値を返します。 本マクロ使用時には割り込みが起きない様にしてください。 本プログラムでは ADMUX レジスタの ADLAR ビットは常に 0 であるため、16 ビット ADC レジスタの下位 10 ビットが A/D 変換結果となります。ただし、上位 6 ビットは初期値 0 のはずですので、そのまま使っています。

      adc_start() マクロによって、ADCSRA レジスタの ADSC ビットを 1 にすることで A/D 変換を開始します。開始前に、変換完了監視に使う ADIF ビットを明示的にクリアしています。 なお、ADIF ビットは 1 を書き込むことでクリアされるため、_BV(ADIF) を設定しています。

  • adc.c
        1    #include <avr/interrupt.h>
        2    #include <avr/io.h>
        3    #include <inttypes.h>
        4    
        5    #include "adc.h"
        6    
        7    
        8    static void (*adc_interrupt_handler)(void);
        9    
       10    
       11    /*
       12     * A/D変換完了時割り込みハンドラ
       13     * adc_init(???,ADC_USE_INT) とした場合に有効となる。
       14     */
       15    ISR(ADC_vect)
       16    {
       17            adc_interrupt_handler();
       18    }
       19    
       20    
       21    /*
       22     * A/D変換完了時割り込みハンドラ内で実行する
       23     * ユーザ関数を設定する。
       24     */
       25    void
       26    adc_set_interrupt(void (*func)(void))
       27    {
       28            adc_interrupt_handler = func;
       29    }
       30    
       31    
       32    /*
       33     * A/D変換器の初期化を行う。
       34     * clock は adc.h で定義しているクロック選択用マクロを指定する。
       35     *
       36     * 変換完了時割り込みを使用する場合、本関数実行後に 
       37     * sei() を実行することで有効となる。
       38     */
       39    void
       40    adc_init(uint8_t clock, int use_interrupt)
       41    {
       42            clock &= (_BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0));
       43    
       44            if (use_interrupt) {
       45                    /* A/D変換許可|変換完了時割り込み|クロック設定 */
       46                    ADCSRA = (_BV(ADEN) | _BV(ADIE) | clock);
       47            }
       48            else {
       49                    /* A/D変換許可|クロック設定 */
       50                    ADCSRA = (_BV(ADEN) | clock);
       51            }
       52    }
       53    
       54    /* End of adc.c */
    
    
    • 11 〜 18 行目

      A/D 変換完了割り込みを有効にした場合に使用する、割り込みハンドラを記述しています。 LECTURE 3 USART によるシリアル通信 にて説明した様に、 avr-libc を使う場合、割り込みハンドラは ISR(割り込み種別を表すマクロ){ /* 実処理 */} の様に記述します。ATmega8535 の A/D 変換完了を表すマクロは avr/interrupt.h にて ADC_vect と定義されています。

      実際には、後述する adc_set_interrupt() で登録されたユーザ関数を実行する様に実装しています。

    • 21 〜 29 行目 (adc_set_interrupt())

      前述の A/D 変換完了割り込みハンドラ内で実際に実行する、ユーザ関数を登録します。

    • 32 〜 52 行目 (adc_init())

      A/D 変換器の初期化を行います。引数の clock には、adc.h で定義している ADC_CK_X マクロを指定します。もしも他の値を設定された場合に、ADCSRA レジスタの無関係なビット を書き変えてしまうことを防ぐために、ADPS2-0 に対応したビット以外をクリアします。

      その後 A/D 変換完了割り込みが不要な場合は、ADCSRA レジスタの ADEN ビットを 1 に設定して A/D 変換器を有効にすると共に A/D 変換用のクロックを設定します。 A/D 変換完了割り込みが必要な場合は、さらに ADIE ビットを 1 に設定して割り込みを有効にします。

  • util.h
        1    #ifndef ___UTIL_H_INCLUDED_
        2    #define ___UTIL_H_INCLUDED_
        3    
        4    #include <avr/io.h>
        5    
        6    
        7    #define pullup() {\
        8            PORTA = 0xff; \
        9            PORTB = 0xff; \
       10            PORTC = 0xff; \
       11            PORTD = 0xff; \
       12    }
       13    
       14    void delay_250ms(void);
       15    void delay_500ms(void);
       16    int  itoa(char *buf, int target, int base, int width, char pad);
       17    
       18    
       19    #endif
       20    
       21    /* End of util.h */
    
    
  • util.c
        1    #include <util/delay.h>
        2    #include "util.h"
        3    
        4    
        5    void
        6    delay_250ms(void)
        7    {
        8            int i;
        9    
       10            for (i = 0; i < 25; i ++) {
       11                    _delay_ms(10);
       12            }
       13    }
       14    
       15    
       16    void 
       17    delay_500ms(void)
       18    {
       19            int i;
       20    
       21            for (i = 0; i < 50; i ++) {
       22                    _delay_ms(10);
       23            }
       24    }
       25    
       26    
       27    #define INT64_MAX_DIGIT 20
       28    
       29    int 
       30    itoa(char *buf, int target, int base, int width, char pad)
       31    {
       32            char tmpbuf[INT64_MAX_DIGIT + 1];
       33            char *tmppos = tmpbuf;
       34            char *pos = buf;
       35            char *left;
       36            char *right;
       37            char swap;
       38            char ch;
       39            int  length = 0;
       40            int  i;
       41    
       42            do {
       43                    *(tmppos ++) = "0123456789abcdef"[target % base];
       44                    length ++;
       45            }
       46            while (target /= base);
       47    
       48            *tmppos = '\0';
       49            left = tmpbuf;
       50            right = tmppos - 1;
       51    
       52            while (left < right) {
       53                    swap = *left;
       54                    *(left ++) = *right;
       55                    *(right --) = swap;
       56            }
       57    
       58            for (i = 0; i < width - length; i ++) {
       59                    *(pos ++) = pad;
       60            }
       61    
       62            tmppos = tmpbuf;
       63    
       64            while ((ch = *(tmppos ++))) {
       65                    *(pos ++) = ch;
       66            }
       67    
       68            *pos = '\0';
       69            return (width > length) ? width : length;
       70    }
       71    
       72    /* End of util.c */
    
    
    • 16 〜 24 行目 (delay_500ms())

      500 ms 待機するための delay_500ms() を、avr-libc の _delay_ms() を使って新たに用意しました。

    • 27 〜 70 行目 (itoa())

      数値を指定して文字列に変換する自作の関数です。指定して文字列長になるまで、 指定した文字を左側から詰めてくれます。例えば itoa(buf, 10, 10, 5, '*') とすると、 変換の結果得られる文字列は "***10" となります。

      int
      itoa(char *buf, int target, int base, int width, char pad);
      
      buf    : 変換結果の文字列を格納するためのバッファ
      target : 変換対象の数値
      base   : 基数
      width  : 変換結果の文字列の文字長
      pad    : width に達するまで文字列の左側から埋める文字
      
      

      avr-libc の文字列操作関数を使いたい所ですが、前にも書きました様に、リンクすると ATmega8535 のフラッシュメモリ容量 8KB を軽く超えてしまうため必要最小限の処理を 用意すべく自作しています…

  • main-int.c(割り込み使用時の main)
        1    #include <avr/interrupt.h>
        2    #include <avr/io.h>
        3    #include <avr/sleep.h>
        4    #include <inttypes.h>
        5    
        6    #include "adc.h"
        7    #include "usart.h"
        8    #include "util.h"
        9    
       10    
       11    static void adc_interrupt(void);
       12    
       13    
       14    int
       15    main(void)
       16    {
       17            pullup();
       18            PORTA &= ~_BV(PINA0); /* PINA0 プルアップ無効 */
       19    
       20            /* (8MHz/64) = 125KHz */
       21            adc_init(ADC_CK_64, ADC_USE_INT);
       22            adc_set_interrupt(adc_interrupt);
       23            adc_set_ch(PINA0);
       24            usart_init(USART_NO_INT);
       25    
       26            sei();
       27    
       28            while (1) {
       29                    sleep_mode();
       30                    delay_500ms();
       31            }
       32    
       33            return 0; /* 警告を避けるため */
       34    }
       35    
       36    
       37    static void
       38    adc_interrupt(void)
       39    {
       40            char abuf[5];
       41            char *pos = abuf;
       42            char ch;
       43            char buf[32] = "PINA0 input =     \r\n";
       44            int index = 14;
       45    
       46            uint16_t data = adc_get();
       47    
       48            itoa(abuf, data, 10, 4, '0');
       49    
       50            while ((ch = *(pos ++))) {
       51                    buf[index ++] = ch;
       52            }
       53    
       54            usart_tx_string(buf);
       55    }
       56    
       57    /* End of main-int.c */
    
    

    本ファイルは、A/D 変換完了割り込みを使う場合の main() を記述しています。

    • 14 〜 34 行目 (main())

      • pullup() (pullup.h、全入力ポートの内部プルアップを有効化)
      • PINA0 ピンは入力として使用するので、プルアップを無効化
      • adc_init() (adc.h、A/D 変換器初期化)
      • adc_set_interrupt() (adc.h、A/D 変換完了割り込み時に実行するユーザ関数の登録)
      • adc_set_ch() (adc.h、PINA0 をアナログ入力として設定)
      • usart_init() (usart.h、USART 初期化)

      を行った後、avr/interrupt.h で定義されている sei() を実行してシステムの割り込みを有効化しています。 これにより A/D 変換完了の度に、後述する adc_interrupt() が実行されます。

      以後 28 〜 31 行目の無限ループにて

      • avr-libc の sleep_mode() によるアイドル動作へ移行
      • 割り込み等による復帰後、delay_500ms() によって 500ms 待つ

      を繰り返しています。

      さて、本ファイルでは A/D 変換を開始するコードがどこにも有りません。 ここでは、まだ説明していないノイズ低減機能を使って A/D 変換を実行しています。 ノイズ低減機能は、CPU の停止中に A/D 変換を行うことで変換結果に乗ってくるノイズを減らす機能です。 以下の様にしてノイズ低減機能による A/D 変換を実行します。

      • ADCSRA レジスタの ADEN ビットを 1 として A/D 変換器を有効にする
      • ADCSRA レジスタの ADSC ビットが 0 であることを確認する
      • ADCSRA レジスタの ADATE ビットが 0 であることを確認する(自動変換無効)
      • ADCSRA レジスタの ADIE ビットを 1 として A/D 変換完了割り込みを有効にする
      • アイドル動作に移行する(CPU 停止後、A/D 変換が開始される)
      • A/D 変換完了によって A/D 変換完了割り込みが発生し CPU が起動する

      上記を踏まえると、本ファイルの main() は sleep_mode() によって A/D 変換を開始し、 後述の adc_interrupt() で割り込み処理を行い、500ms 待って再度 sleep_mode() により A/D 変換を開始するという流れになっていることが分ります。

    • 37 〜 55 行目 (adc_interrupt())

      A/D 変換完了割り込み時に実行されるユーザ関数です。

      まず adc_get() で A/D 変換結果を取得します。その後、得られた A/D 変換結果の数値を itoa() にて 10 進数 4 文字長の文字列に変換しています。その際、4 文字に満たない分は '0' で埋められます。そして "PINA0 input = ..." という文字列を作成した後、usart_tx_string() によって USART 送信バッファに文字列を設定し、割り込み処理を終了します。

  • main-noint.c(割り込み未使用時の main)
        1    #include <avr/io.h>
        2    #include <inttypes.h>
        3    
        4    #include "adc.h"
        5    #include "usart.h"
        6    #include "util.h"
        7    
        8    
        9    #define INDEX_TO_INSERT 14
       10    
       11    
       12    int
       13    main(void)
       14    {
       15            char abuf[5];
       16            char *pos;
       17            char ch;
       18            char buf[32] = "PINA0 input =     \r\n";
       19            int index;
       20            uint16_t data;
       21    
       22            pullup();
       23            PORTA &= ~_BV(PINA0); /* PINA0 プルアップ無効 */
       24    
       25            /* (8MHz/64) = 125KHz */
       26            adc_init(ADC_CK_64, ADC_NO_INT);
       27            adc_set_ch(PINA0);
       28            usart_init(USART_NO_INT);
       29    
       30            while (1){
       31                    adc_start();
       32                    adc_wait();
       33                    data = adc_get();
       34    
       35                    itoa(abuf, data, 10, 4, '0');
       36                    index = INDEX_TO_INSERT;
       37                    pos = abuf;
       38    
       39                    while ((ch = *(pos ++))) {
       40                            buf[index ++] = ch;
       41                    }
       42    
       43                    usart_tx_string(buf);
       44                    delay_500ms();
       45            }
       46    
       47            return 0; /* 警告を避けるため */
       48    }
       49    
       50    /* End of main-noint.c */
    
    

    本ファイルは、A/D 変換完了割り込みを使わない場合の main() を記述しています。

    • 31 〜 32 行目

      • pullup() (pullup.h、全入力ポートの内部プルアップを有効化)
      • PINA0 ピンは入力として使用するので、プルアップを無効化
      • adc_init() (adc.h、A/D 変換器初期化)
      • adc_set_ch() (adc.h、PINA0 をアナログ入力として設定)
      • usart_init() (usart.h、USART 初期化)

      を行った後、while ループに入ります。ループ内ではまず

      adc_start();
      adc_wait();
      
      

      を実行して、A/D 変換開始後、変換が終了するまで待ちます。adc_wait() は変換が終了するまでブロックします。

      変換終了後 adc_get() によって変換結果を取得し、文字列加工の後に usart_tx_string() によって USART 送信バッファに文字列を設定する所は、A/D 変換完了割り込み使用時の main() と同じです。

      こちらの main() ではその後 delay_500ms() を実行して、while() ループによる次の A/D 変換開始まで 500ms 待っています。

以上で解説は終わりです。A/D 変換機能が使える様になれば、色々なセンサ入力を AVR で採りこんで使用することが出来ます。ぜひ楽しんでみてください!