Analog-Digital Umwandlung

http://www.dingeldein-online.de/basteln/avr.html

In den meisten AVR Mikrocontrollern ist auch ein AD-Konverter ADC enthalten. Dieser kann mit 8- oder 10-Bit-Auflösung Spannungen, die an mehreren Kanälen (Pins) anliegen, konvertieren. Er kann einmalig konvertieren oder auch fortlaufend.

Für das Testen dieses Features startet man mit einer Funktion, die die eigentliche AD-Konversion durchführt. Ich habe dazu die Funktion ReadChannel, wie bei www.mikrocontroller.net beschrieben (http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Der_interne_ADC_im_AVR) verwendet. Die Werte für ADPS2, ADPS1 und ADPS0 müssen aus der Taktfrequenz der CPU berechnet werden. Die Formel ist ebenfalls unter obiger URL zu finden.

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

/* Funktion übernommen aus http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Der_interne_ADC_im_AVR */
uint16_t ReadChannel(uint8_t channel )
{
        uint8_t i;
        uint16_t result;

        ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1);    // Frequenzvorteiler setzen auf 64 und ADC aktivieren

        ADMUX = channel; // Kanal waehlen
        ADMUX |= _BV(REFS1) | _BV(REFS0); // interne Referenzspannung nutzen

        /* nach Aktivieren des ADC wird ein "Dummy-Readout" empfohlen, man liest
             also einen Wert und verwirft diesen, um den ADC "warmlaufen zu lassen" */
        ADCSRA |= _BV(ADSC);              // eine ADC-Wandlung
        while ( ADCSRA & _BV(ADSC) ) {
                ;     // auf Abschluss der Konvertierung warten
        }
        result = ADCW;  // ADCW muss einmal gelesen werden,
        // sonst wird Ergebnis der nächsten Wandlung
        // nicht übernommen.

        /* Eigentliche Messung - Mittelwert aus 4 aufeinanderfolgenden Wandlungen */
        result = 0;
        for( i=0; i<4; i++ )
        {
                ADCSRA |= _BV(ADSC);            // eine Wandlung "single conversion"
                while ( ADCSRA & _BV(ADSC) ) {
                        ;   // auf Abschluss der Konvertierung warten
                }
                result += ADCW;             // Wandlungsergebnisse aufaddieren
        }
        ADCSRA &= ~_BV(ADEN);             // ADC deaktivieren (2)
        result /= 4;                     // Summe durch vier teilen = arithm. Mittelwert
        return result;
}

Im Hauptprogramm:

#define VREF 2.56 /* value of (interna) reference voltage)
*/

/*
 ** function prototypes
 */
uint16_t ReadChannel(uint8_t channel);

int main(void)
{
        int value;
        char buf[32];
        float f;

        DDRC &=~ (1 << PC3);        /* Pin PC3 input */

        DI();
        /* now enable interrupt, since UART library is interrupt controlled */
        sei();
        DO("after init usart\n\r");

        while (1) {
                value = ReadChannel(3); // channel 3 is PC3
                f = value;
                f = (f*VREF)/1024.0; // 256 for 8 bits, 1024 for 10 bits; VREF=2.56 for internal ref.
                sprintf( buf, "Measuring %.3f Volt (raw value = %d)\r", (double)(f), value  );
                DO(buf);
                dowait();
        }
}

Beim Linken muss eine zusätzliche printf-Bibliothek, die auch Floats ausgeben kann, hinzugebunden werden (libprintf_flt). Diese Bibliothek wird normalerweise nicht eingebunden, weil sie deutlich größere Executables produziert.




ADC (Analog Digital Converter)

Eine Zusammenfassung aus http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial



Einige AVR-Typen haben bereits einen mehrkanaligen Analog-Digital-Konverter eingebaut. AVRs enthalten zur Zeit Wandler mit maximaler Auflösung von 10-Bit.

Der interne ADC im AVR



Betriebsarten

Die Register des ADC



ADCSRA   

ADC Control and Status Register A.

In diesem Register stellen wir ein, wie wir den ADC verwenden möchten.
Das Register ist wie folgt aufgebaut:

Bit

7

6

5

4

3

2

1

0

Name

ADEN

ADSC

ADFR

ADIF

ADIE

ADPS2

ADPS1

ADPS0

R/W

R/W

R/W

R/W

R/W

R/W

R/W

R/W

R/W

Initialwert

0

0

0

0

0

0

0

0

ADEN (ADC Enable)

Dieses Bit muss gesetzt werden, um den ADC überhaupt zu aktivieren. Wenn das Bit nicht gesetzt ist, können die Pins wie normale I/O-Pins verwendet werden.

ADSC (ADC Start Conversion)

Mit diesem Bit wird ein Messvorgang gestartet. In der frei laufenden Betriebsart muss das Bit gesetzt werden, um die kontinuierliche Messung zu aktivieren.
Wenn das Bit nach dem Setzen des ADEN-Bits zum ersten Mal gesetzt wird, führt der Controller zuerst eine zusätzliche Wandlung und erst dann die eigentliche Wandlung aus. Diese zusätzliche Wandlung wird zu Initialisierungszwecken durchgeführt.
Das Bit bleibt nun so lange auf 1, bis die Umwandlung abgeschlossen ist, im Initialisierungsfall entsprechend bis die zweite Umwandlung erfolgt ist und geht danach auf 0.

ADFR (ADC Free Run select)

Mit diesem Bit wird die Betriebsart eingestellt.
Ist das Bit auf 1 gesetzt arbeitet der ADC im "Free Running"-Modus. Dabei wird das Datenregister permanent aktualisiert.


ADIF (ADC Interrupt Flag)

Dieses Bit wird vom ADC gesetzt, sobald eine Umwandlung erfolgt ist und das ADC Data Register aktualisiert wurde. Das Bit wird bei lesendem Zugriff auf ADC(L,H) automatisch (d.h. durch die Hardware) gelöscht.
Wenn das ADIE Bit sowie das I-Bit im AVR Statusregister gesetzt ist, wird der ADC Interrupt ausgelöst und die Interrupt-Behandlungsroutine aufgerufen.
Das Bit wird automatisch gelöscht, wenn die Interrupt-Behandlungsroutine aufgerufen wird. Es kann jedoch auch gelöscht werden, indem eine logische 1 in das Register geschrieben wird.

ADIE (ADC Interrupt Enable)

Wenn dieses Bit gesetzt ist und ebenso das I-Bit im Statusregister SREG, dann wird der ADC-Interrupt aktiviert.

ADPS2...ADPS0 (ADC Prescaler Select Bits)

Diese Bits bestimmen den Teilungsfaktor zwischen der Taktfrequenz und dem Eingangstakt des ADC.
Der ADC benötigt einen eigenen Takt, welchen er sich selber aus der CPU-Taktfreqenz erzeugt. Der ADC-Takt sollte zwischen 50 und 200kHz sein.
Der Vorteiler muss also so eingestellt werden, dass die CPU-Taktfrequenz dividiert durch den Teilungsfaktor einen Wert zwischen 50-200kHz ergibt.
Bei einer CPU-Taktfrequenz von 4MHz beispielsweise rechnen wir
Somit kann hier der Teilungsfaktor 32 oder 64 verwendet werden. Im Interesse der schnelleren Wandlungszeit werden wir hier den Faktor 32 einstellen.

ADPS2

ADPS1

ADPS0

Teilungsfaktor

0

0

0

2

0

0

1

2

0

1

0

4

0

1

1

8

1

0

0

16

1

0

1

32

1

1

0

64

1

1

1

128

ADCL

ADCH

ADC Data Register

Wenn eine Umwandlung abgeschlossen ist, befindet sich der gemessene Wert in diesen beiden Registern. Von ADCH werden nur die beiden niederwertigsten Bits verwendet. Es müssen immer beide Register ausgelesen werden, und zwar immer in der Reihenfolge: ADCL, ADCH. Der effektive Messwert ergibt sich dann zu:

x = ADCL;       // mit uint16_t x
x += (ADCH<<8);
						// in zwei Zeilen (LSB/MSB-Reihenfolge
						und
                //
						C-Operatorpriorität sichergestellt) 
						

oder

x = ADCW; // je nach AVR auch x =
						ADC (siehe avr/ioxxx.h) 
						

ADMUX  

ADC Multiplexer Select Register

Mit diesem Register wird der zu messende Kanal ausgewählt.
Das Register ist wie folgt aufgebaut:

Bit

7

6

5

4

3

2

1

0

Name

REFS1

REFS0

ADLAR

MUX4

MUX3

MUX2

MUX1

MUX0

R/W

R/W

R/W

R/W

R/W

R/W

R/W

R/W

R/W

Initialwert

0

0

0

0

0

0

0

0


MUX4...MUX0

Mit diesen 5 Bits wird der zu messende Kanal bestimmt. Wenn das Register beschrieben wird, während eine Wandlung läuft, so wird zuerst die aktuelle Umwandlung auf dem bisherigen Kanal beendet.



REFS1...REFS0 (ReferenceSelection Bits)

Mit diesen Bits kann die Referenzspannung eingestellt werden. Bei der Umstellung sind Wartezeiten zu beachten, bis die ADC-Hardware einsatzfähig ist (Datenblatt):

REFS1

REFS0

Referenzspanung

0

0

Externes AREF

0

1

AVCC als Referenz

1

0

Reserviert

1

1

Interne 2,56 Volt


ADLAR (ADC Left Adjust Result)

Das ADLAR Bit verändert das Aussehen des Ergebnisses der AD-Wandlung. Bei einer logischen 1 wird das Ergebnis linksbündig ausgegeben, bei einer 0 rechtsbündig. Eine Änderung in diesem Bit beeinflusst das Ergebnis sofort, ganz egal ob bereits eine Wandlung läuft. Linksbündig ausrichten hat den Vorteil, dass man – wenn man auf die 2 LSB verzichtet – nur das HIGH-Byte auszulesen braucht.

Nutzung des ADC

Um den ADC zu aktivieren, müssen wir das ADEN-Bit im ADSCRA-Register setzen.



16 Bit Zugriff

Beim ADC besteht das Problem darin, dass zwischen den Zugriffen auf die beiden Teilregister eine Wandlung beendet werden kann und der ADC ein neues Ergebnis in ADCL und ADCH schreiben will, wodurch High- und Low-Byte nicht zusammenpassen.

Beim ADC-Datenregister ADCH/ADCL ist die Synchronisierung so gelöst. Hier wird beim Lesezugriff auf das Low-Byte ADCL beide Teilregister für Zugriffe seitens des ADC so lange gesperrt, bis das High-Byte ADCH ausgelesen wurde. Dadurch kann der ADC nach einem Zugriff auf ADCL keinen neuen Wert in ADCH/ADCL ablegen, bis ADCH gelesen wurde. Ergebnisse von Wandlungen, die zwischen einem Zugriff auf ADCL und ADCH beendet werden, gehen verloren!

Nach einem Zugriff auf ADCL muss grundsätzlich ADCH gelesen werden!

Sowohl bei den Timern als auch beim ADC werden vom C-Compiler 16-Bit Pseudo-Register zur Verfügung gestellt (z. B. ADCH/ADCL → ADC bzw. ADCW), bei deren Verwendung der Compiler automatisch die richtige Zugriffsreihenfolge regelt. In C-Programmen sollten grundsätzlich diese 16-Bit-Register verwendet werden! Sollte trotzdem ein Zugriff auf ein Teilregister erforderlich sein, sind obige Angaben zu berücksichtigen.

Beispiel

Ein kleines Beispiel für den "single conversion"-Mode bei einem ATmega und Nutzung der internen Referenzspannung. Das Ergebnis der Routine ist der ADC-Wert, also 0 für 0-Volt und 1023 für V_ref-Volt.

In der praktischen Anwendung wird man zum Programmstart den ADC erst einmal grundlegend konfigurieren und dann auf verschiedenen Kanälen messen. Diese beiden Dinge sollte man meist trennen, denn das Einschalten des ADC und vor allem der Referenzspannung dauert ein paar Dutzend Mikrosekunden. Außerdem ist das erste Ergebnis nach dem Einschalten ungültig und muss verworfen werden.

/* ADC initialisieren */
void ADC_Init(void)
{
 
  uint16_t result;
 
  //  ADMUX = (0<<REFS1) |
(1<<REFS0);      // AVcc als Referenz benutzen
  ADMUX = (1<<REFS1)
| (1<<REFS0);
     // interne Referenzspannung nutzen
  ADCSRA = (1<<ADPS1)
| (1<<ADPS0);
    // Frequenzvorteiler
  ADCSRA |= (1<<ADEN);
                 // ADC aktivieren
 
  /* nach Aktivieren des ADC wird ein
"Dummy-Readout" empfohlen, man liest
     also einen Wert und verwirft
diesen, um den ADC "warmlaufen zu lassen" */
 
  ADCSRA |= (1<<ADSC);
               // eine ADC-Wandlung 
  while (ADCSRA
& (1<<ADSC)
) {}     
// auf Abschluss der Konvertierung warten
  /* ADCW muss einmal gelesen werden,
sonst wird Ergebnis der nächsten
     Wandlung nicht übernommen. */
  result = ADCW;
}
 
/* ADC Einzelmessung */
uint16_t ADC_Read( uint8_t channel
)
{
  // Kanal waehlen, ohne andere Bits zu
beeinflußen
  ADMUX = (ADMUX & ~(0x1F))
| (channel & 0x1F);
  ADCSRA |= (1<<ADSC);
           // eine Wandlung "single
conversion"
  while (ADCSRA
& (1<<ADSC)
) {}  //
auf Abschluss der Konvertierung warten
  return ADCW;                   
// ADC auslesen und zurückgeben
}
 
/* ADC Mehrfachmessung mit
Mittelwertbbildung */
uint16_t ADC_Read_Avg( uint8_t
channel, uint8_t average )
{
  uint32_t result = 0;
 
  for (uint8_t
i = 0; i < average; ++i )
    result += ADC_Read( channel );
 
  return (uint16_t)(
result / average );
}
 

/* Beispielaufrufe: */
 
int main()
{
  uint16_t adcval;
  ADC_Init();
 
  while(
1 ) {
    adcval = ADC_Read(0);
 // Kanal 0
    // ...
 
    adcval = ADC_Read_Avg(2,
4);  //
Kanal 2, Mittelwert aus 4 Messungen
    // ...
  }
}