AVR ATmega88/168 ADC 7SEG電圧計

前回の7SEGテストボードを使ってADCをテストしてみました。
目的としては電源回路、鉛電池などの簡易な電圧モニタとしての応用を考えています。

adctest01

88adc-a01
テスト回路
7セグ回路はアノードコモン、ドライブTRなし。
リファレンス電圧はArefをAVCCにつないで3.3Vまたは5Vとします。
今回のテストではVCCから10kVRでPC0(ADC0)に接続しています。
複数チャンネルの入力は割り込みの関係から7セグの表示が乱れるため1チャンネルのみの入力としています。

adctest02
ミニブレッドボード用アダプタを作ってみました。

参考サイト
始める電子回路 A/D変換機能を使う
AVRWIKI Analog/Digital Converter

単独変換モード 88adc01.txt
メインループ内で単独変換モードを試してみます。
ADC関連の初期設定をmainの項に追記しています。
各レジスタはデータシートを参照します。

/*
*    ADC Test main 単独変換モード (Anode Common)
*    ATmega88V RC 8MHz
*    avrdude -p m88 -u -U lfuse:w:0xe2:m
*/
#define F_CPU 8000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>
const   uint8_t     SEG[] = {
//  0bABCDEFGP
    0b00000011,     // 0     0
    0b10011111,     // 1     1
    0b00100101,     // 2     2
    0b00001101,     // 3     3
    0b10011001,     // 4     4
    0b01001001,     // 5     5
    0b01000001,     // 6     6
    0b00011111,     // 7     7
    0b00000001,     // 8     8
    0b00001001,     // 9     9
    0b11111101,     // -    10
    0b11111110,     // dot  11
};
uint8_t digit,led_dig[4];
uint8_t c0,c1,c2,c3;
uint16_t a;
// ---------------------(main)------------------------
int main(void) {
    DDRB    = 0xFF;             // PORTB
    DDRC    = 0x00;             // PORTC
    DDRD    = 0xFF;             // PORTD
    PORTB   = 0xFF;             // PORTB DriveTR = 0x00
    PORTC   = 0xFE;             // PORTC pc0 Low
    PORTD   = 0xFF;             // PORTD

    ADCSRA  = (1 << ADEN)       // ADC許可 
            | (1 << ADSC)       // ADC Start
            | (1 << ADATE)      // ADC Auto Triger enable 1: disable 0:
            | (1 << ADIF)       // ADC Interrupt flag
            | (0 << ADIE)       // ADC割り込み enable 1: disable 0:
            | (0b110 << ADPS0); // ADC Clock 1/64 8M/64=125KHz
    ADCSRB  = (0b000 << ADTS0); // 連続変換動作(default)
    ADMUX   = (0b00 << REFS0)   // 00 AREF: 01 AVCC: 11 内部基準1.1V:
            | (0 << ADLAR)      // Right(default) 0: Left 1:
            | (0b0000 << MUX0); // 0000 PC0:
    DIDR0   = (1 << ADC0D);     // Digtal Input disable(pc0)

     while (1) {
// -- 変換結果を取得
    a = (ADC >> 0);   // 10bit 0: -- 1bit 9:
// -- 7SEG scan
    PORTD = led_dig[digit];
    DDRB = 1 << digit;
    digit = (digit + 1) % 4;
// -- 桁取得
    c0 = (a % 10); a /= 10;
    c1 = (a % 10); a /= 10;
    c2 = (a % 10); a /= 10;
    c3 = (a % 10); a /= 10;
// -- 表示(右詰め)
    led_dig[0] = SEG[c0];
    led_dig[1] = SEG[c1];
    led_dig[2] = SEG[c2];
    led_dig[3] = SEG[c3];
    }
}

コンパイル、書込

$ avr-gcc -mmcu=atmega88 -Wall -Os 88adc01.c
$ avr-objcopy -O ihex -R .eeprom a.out 88adc.hex
$ avrdude -c usbasp -p m88 -U flash:w:88adc.hex

adctest03
変換結果をADCで取得します。(右詰め)

ADC bit 0~       ADC bit 0~
0   10  1023      5   5   31
1    9   511      6   4   15
2    8   255      7   3    7
3    7   127      8   2    3
4    6    63      9   1    1

(ADC >> 9)を指定した場合0から1に変わる点はVCC(3.3V)で1.65Vになります。
ADCSRA
ADEN :1 ADC許可
ADSC :1 ADC開始
ADATE:1 有効にしないと初回の1回のみしか変換しません
ADIF :1 特に指定しなくても1CHのみならmcu側で良きに計らってくれるようです。
ADMUX
REFS0 00:AREF 基準電圧選択
ADLAR 0:右詰め 左詰めで使用する場合8bit以下の変換結果はADCHを読むだけでいいとありますが、右詰めの方がわかりやすいですね。左詰めの10bitのデータをADCで取得する場合は(ADC >> 6)となります。
MUX0 0000:PC0 A/Dチャンネル選択
DIDR0
ADC0D 1:PC0デジタル入力禁止

連続変換モード(割り込み) 88adc02.txt
割り込みで動作を確認してみます。(7SEGも割り込みで動作)

// ------ 省略 --------
// --------------------(Timer0)-------------------------
ISR(TIMER0_OVF_vect) {  // Timer0 オーバーフロー割り込み
    TCNT0  = 131;
    scan();
}
// --------------------(Timer1)-------------------------
ISR(TIMER1_OVF_vect) {   // Timer1 オーバーフロー割り込み
    TCNT1 = 53036;
}
// ---------------------(ADC)-------------------------
ISR(ADC_vect) {
// -- 変換結果を取得
    a = (ADC >> 0);   // 10bit 0: -- 1bit 9:
}
// ---------------------(main)------------------------
int main(void) {
    DDRB    = 0xFF;             // PORTB
    DDRC    = 0x00;             // PORTC
    DDRD    = 0xFF;             // PORTD
    PORTB   = 0x0F;             // PORTB DriveTR = 0x00
    PORTC   = 0xFE;             // PORTC pc0 Low
    PORTD   = 0xFF;             // PORTD

    TCCR0B  = (0b011 << CS00);  // Prescaler 1/64
    TCNT0   = 131;              // (1/8)x64x(256-131)=1ms
    TCCR1B  = (0b011 << CS10);  // Prescaler 1/64
    TCNT1   = 53036;            // (1/8)x64x(65536-53036)=100ms
    TIMSK0  = (1 << TOIE0);     // Timer0 Overflow
    TIMSK1  = (1 << TOIE1);     // Timer1 Overflow

    ADCSRA  = (1 << ADEN)       // ADC許可 
            | (0 << ADSC)       // ADC Start
            | (1 << ADATE)      // ADC Auto Triger enable 1: disable 0:
            | (1 << ADIF)       // ADC Interrupt flag
            | (1 << ADIE)       // ADC割り込み enable 1: disable 0:
            | (0b110 << ADPS0); // ADC Clock 1/64 8M/64=125KHz
    ADCSRB  = (0b000 << ADTS0); // 連続変換動作(default)
    ADMUX   = (0b00 << REFS0)   // 00 AREF: 01 AVCC: 11 内部基準1.1V:
            | (0 << ADLAR)      // Right(default) 0: Left 1:
            | (0b0000 << MUX0); // 0000 PC0:
    DIDR0   = (1 << ADC0D);     // Digtal Input disable(pc0)
    sei();                      // 割り込み許可
    set_sleep_mode(SLEEP_MODE_IDLE);

    while (1) {
    sleep_mode();
    }
}

ADCSRA
ADSC 0:割り込みで変換開始をしています。
ADCSRB
デフォルトは連続変換動作になっています。
Timer1(Overflow)に設定してみますが動作になんら変化が無いようにみえます。
以下の設定をしてみます。

ISR(ADC_vect) {
// -- 変換結果を取得
    a = (ADC >> 0);   // 10bit 0: -- 2bit 9:
    loop_until_bit_is_set(ADCSRA,ADIF); // 追記
}
// main
    ADCSRB  = (0b000 << ADTS0); // Timer1(Overflow) 変更

7SEGが100msごとに順に表示しています。(割り込みが効いています)
Timerからの割り込みでADC設定を変更するような使い方と思います。

以上、簡単なテストだけですが適切にPORT,ADC設定を行えばADCで1~10bitのデータを取得できました。(ADCはいろいろな起動元、割り込みが発生するので正確に動作を把握するのは難しいですね)

電圧値を表示する 88adc03.txt
取得したデータから電圧として表示するにはVref=1023として換算すればいいのですが変動の大きい電圧源の場合は更新期間が早すぎてモニタとしては都合が悪くなります。
今回は車のバッテリー電圧をモニタする用途で考えてみたいと思います。
88adc-a02
作成する回路
電源はこの回路用にレギュレーターを使用、発熱対策として抵抗(1W)を入れて調整をしています。
MAX入力は安全性と標準の抵抗値から21.5V
精度はそこそことします。

uint8_t cnt,dp;
uint16_t a,v;
uint32_t at;

// --------------------(Timer0)-------------------------

ISR(TIMER0_OVF_vect) {  // Timer0 オーバーフロー割り込み
    TCNT0  = 131;

    scan();
    cnt ++;
    at = at + a;
    if (cnt == 255) {
        at = at / 255;
        v = (at / 0.4758);  // 1023/2150 4桁の整数
        dp = 1;
// -- 桁取得
        c0 = (v % 10); v /= 10;
        c1 = (v % 10); v /= 10;
        c2 = (v % 10); v /= 10;
        c3 = (v % 10); v /= 10;
        cnt = 0;
        at = 0;
    }
}

分圧抵抗は取り付けないで7SEGテストボードのままで確認します。
決してAVRのピンに直接12V(5Vより高い電圧)を加えないようにします
adctest04
車のバッテリーは常に充放電を繰り返し電圧も約12V~15Vと変動しています。
Timer0で255回、変換結果を取得して平均します。
平均した値を約1秒で4回表示を更新しています。
VRを回すと0~2150まで変化すると思います。
VCCは3.3Vまたは5Vでも分圧比は変わらないのでほとんど同じ結果を返すはずです。
後は分圧抵抗の精度次第になるかと思います。

adctest05
作成した電圧計 3桁、小型の7セグ
時計が入っていたボディに押し込んでいます。
単に電圧計ならば既製品の方が見栄えもよく安いのですがADCで取得した値からなんらかのアクションを起こしたい場合はやはりAVRの出番となりそうです。例えばバッテリーの電圧が11.5Vに低下したらACアダプタに切り替えるなどいろいろ応用が考えられます。
ハード、ソフトの変更は必要ですが後はアイデア次第ですね。