AVR-Tutorial: ADC
Was macht der ADC?
Wenn es darum geht, Spannungen zu messen, wird der Analog-Digital-Wandler (kurz: A/D-Wandler) oder englisch Analog Digital Converter (ADC) benutzt. Er konvertiert eine elektrische Spannung in eine Digitalzahl. Prinzipiell wird dabei die Messspannung mit einer Referenzspannung verglichen. Die Zahl drückt daher das Verhältnis der Messspannung zu dieser Referenzspannung aus. Sie kann in gewohnter Weise von einem Mikrocontroller weiterverarbeitet werden.
Elektronische Grundlagen
Die ADC-Versorgungsspannung (AVCC) darf maximal um ±0,3 V von der Versorgung des Digitalteils (VCC) abweichen, jedoch nicht 5,5 V überschreiten. Die externe Referenzspannung VREF darf nicht kleiner als die im Datenblatt unter ADC Characteristics als VREFmin angegebene Spannung (z. B. ATmega8: 2 V, ATmega644P: 1 V) und nicht größer als AVCC sein. Die Spannungen an den Wandlereingängen müssen im Intervall GND ≤ VIN ≤ VREF liegen.
Im Extremfall bedeutet dies: Sei VCC = 5,5 V, folgt AVCCmax = VREFmax = VINmax = 5,5 V.
Der Eingangswiderstand des ADC liegt in der Größenordnung von einigen Megaohm, so dass der ADC die Signalquelle praktisch nicht belastet. Desweiteren enthält der Mikrocontroller eine sogenannte Sample-&-Hold-Schaltung. Dies ist wichtig, wenn sich während des Wandlungsvorgangs die Eingangsspannung verändert, da die AD-Wandlung eine bestimmte Zeit dauert. Die Sample-&-Hold-Stufe speichert zum Beginn der Wandlung die anliegende Spannung und hält sie während des Wandlungsvorgangs konstant.
Beschaltung des ADC-Eingangs
Um den ADC im Folgenden zu testen, wird eine einfache Schaltung an den PC0-Pin des ATmega8 angeschlossen. Dies ist der ADC-Kanal 0. Bei anderen AVR-Typen liegt der entsprechende Eingang auf einem anderen Pin, hier ist ein Blick ins Datenblatt angesagt.
Der Wert des Potentiometers ist dank des hohen Eingangswiderstandes des ADC ziemlich unkritisch. Es kann jedes Potentiometer von 1 kΩ bis 1 MΩ benutzt werden.
Wenn andere Messgrößen gemessen werden sollen, so bedient man sich oft und gern des Prinzips des Spannungsteilers. Der Sensor ist ein veränderlicher Widerstand. Zusammen mit einem zweiten, konstanten Widerstand bekannter Größe wird ein Spannungsteiler aufgebaut. Aus der Variation der durch den variablen Spannungsteiler entstehenden Spannung kann auf den Messwert zurückgerechnet werden.
Vcc ----------+ Vcc ---------+ | | --- Sensor, | | der seinen Widerstand | | in Abhängigkeit der --- Messgröße ändert | | +------- PC0 +-------- PC0 | | Sensor, --- der seinen Widerstand | | in Abhängigkeit der | | Messgröße ändert --- | | GND ---------+ GND --------+
Die Größe des zweiten Widerstandes im Spannungsteiler richtet sich nach dem Wertebereich, in welchem der Sensor seinen Wert ändert. Als Daumenregel kann man sagen, dass der Widerstand so groß sein sollte wie der Widerstand des Sensors in der Mitte des Messbereichs.
Beispiel: Wenn ein Temperatursensor seinen Widerstand im Bereich 0…100 °C von 2 kΩ auf 5 kΩ ändert, sollte der zweite Widerstand eine Größe von etwa (2+5)/2 kΩ = 3,5 kΩ haben.
Aber egal wie immer man das auch macht, der entscheidende Punkt besteht darin, dass man seine Messgröße in eine veränderliche Spannung „übersetzt“ und mit dem ADC des ATmega8 die Höhe dieser Spannung misst. Aus der Höhe der Spannung kann dann wieder in der Umkehrung auf die Messgröße zurückgerechnet werden.
Referenzspannung AREF
Der ADC benötigt für seine Arbeit eine Referenzspannung. Dabei gibt es zwei Möglichkeiten:
- interne Referenzspannung
- externe Referenzspannung
Bei der Umstellung der Referenzspannung sind Wartezeiten zu beachten, bis die ADC-Hardware einsatzfähig ist (siehe Datenblatt (PDF; 6,3 MB) und diesen Forumsthread).
Interne Referenzspannung
Mittels Konfigurationsregister können beim ATmega8 verschiedene Referenzspannungen eingestellt werden. Dies umfasst die Versorgungsspannung AVcc sowie eine vom AVR bereitgestellte Spannung von 2,56 V (bzw. bei den neueren AVRs 1,1 V, wie z. B. beim ATtiny13, ATmega48, -88, -168, …). In beiden Fällen wird an den AREF-Pin des Prozessors ein Kondensator von 100 nF als Minimalbeschaltung nach Masse angeschlossen, um die Spannung zu puffern/glätten. Es ist jedoch zu beachten, dass die interne Referenzspannung ca. ±10 % vom Nominalwert abweichen kann, vgl. dazu das Datenblatt Abschnitt ADC Characteristics VINT (z. B. ATmega8: 2,3…2,9 V, ATmega324P: 2,33…2,79 V bzw. 1,0…1,2 V, „Values are guidelines only.“). Die typische Abweichung der internen Referenzspannung vom Sollwert bei einigen AVR-Controllern wird in dieser Testschaltung exemplarisch untersucht.
Externe Referenzspannung
Wird eine externe Referenz verwendet, so wird diese an AREF angeschlossen. Aber aufgepasst! Wenn eine Referenz in Höhe der Versorgungsspannung benutzt werden soll, so ist es besser, dies über die interne Referenz zu tun. Außer bei anderen Spannungen als 5 V bzw. 2,56 V gibt es eigentlich keinen Grund, an AREF eine Spannungsquelle anzuschließen. In Standardanwendungen fährt man immer besser, wenn die interne Referenzspannung mit einem Kondensator an AREF benutzt wird. Die 10-µH-Spule L1 kann man meist auch durch einen 47-Ω-Widerstand ersetzen.
Ein paar ADC-Grundlagen
Der im AVR eingebaute ADC ist ein 10-Bit-ADC, d. h. er liefert Messwerte im Bereich 0 bis 1023. Liegt am Eingangskanal 0 V an, so liefert der ADC einen Wert von 0. Hat die Spannung am Eingangskanal die Referenzspannung erreicht (stimmt nicht ganz), so liefert der ADC einen Wert von 1023. Unterschreitet oder überschreitet die zu messende Spannung diese Grenzen, so liefert der ADC 0 bzw. 1023. Wird die Auflösung von 10 Bit nicht benötigt, so ist es möglich, die Ausgabe durch ein Konfigurationsregister so einzuschränken, dass ein leichter Zugriff auf die 8 höchstwertigen Bits möglich ist.
Wie bei vielen analogen Schaltungen unterliegt auch der ADC einem Rauschen. Das bedeutet, dass man nicht davon ausgehen sollte, dass der ADC bei konstanter Eingangsspannung auch immer denselben konstanten Wert ausgibt. Ein „Zittern“ der niederwertigsten zwei Bits ist durchaus nicht ungewöhnlich. Besonders hervorgehoben werden soll an dieser Stelle nochmals die Qualität der Referenzspannung. Diese Qualität geht in erheblichem Maße in die Qualität der Wandlerergebnisse ein. Die Beschaltung von AREF mit einem Kondensator ist die absolut notwendige Mindestbeschaltung, um eine einigermaßen akzeptable Referenzspannung zu erhalten. Reicht dies nicht aus, so kann die Qualität einer Messung durch Oversampling erhöht werden. Dazu werden mehrere Messungen gemacht und deren Mittelwert gebildet.
Oft interessiert auch der absolute Spannungspegel nicht. Im Beschaltungsbeispiel oben ist man normalerweise nicht direkt an der am Poti entstehenden Spannung interessiert. Vielmehr ist diese Spannung nur ein Mittel zum Zweck, um die Stellung des Potis zu bestimmen. In solchen Fällen kann die Poti-Beschaltung wie folgt abgewandelt werden:
Hier wird AREF (bei interner Referenz) als vom Controller gelieferte Spannung benutzt und vom Spannungsteiler bearbeitet wieder an den Controller zur Messung zurückgegeben. Dies hat den Vorteil, dass der Spannungsteiler automatisch Spannungen bis zur Höhe der Referenzspannung ausgibt, ohne dass eine externe Spannung mit AREF abgeglichen werden müsste. Selbst Schwankungen in AREF wirken sich hier nicht mehr aus, da ja das Verhältnis der Spannungsteilerspannung zu AREF immer konstant bleibt (ratiometrische Messung). Und im Grunde bestimmt der ADC ja nur dieses Verhältnis. Wird diese Variante gewählt, so muss berücksichtigt werden, dass die Ausgangsspannung an AREF nicht allzusehr belastet wird. Der Spannungsteiler muss einen Gesamtwiderstand von deutlich über 10 kΩ besitzen. Werte von 100 kΩ oder höher sind anzustreben. Verwendet man AVCC anstatt AREF und schaltet auch die Referenzspannung auf AVCC um, ist die Belastung durch das Poti unkritisch, weil hier die Stromversorgung direkt zur Speisung verwendet wird.
Ist hingegen die absolute Spannung von Interesse, so muss man darauf achten, dass ein ADC in digitalen Bereichen arbeitet (Quantisierung). An einem einfacheren Beispiel soll demonstriert werden, was damit gemeint ist.
Angenommen, der ADC würde nur 5 Stufen auflösen können und AREF sei 5 V:
Volt Wert vom ADC
0 -+ | 0 1 -+ | 1 2 -+ | 2 3 -+ | 3 4 -+ | 4 5 -+
Ein ADC-Wert von 0 bedeutet also keineswegs, dass die zu messende Spannung exakt den Wert 0 hat. Es bedeutet lediglich, dass die Messspannung irgendwo im Bereich von 0 V bis 1 V liegt. Sinngemäß bedeutet daher auch das Auftreten des Maximalwertes nicht, dass die Spannung exakt AREF beträgt, sondern lediglich, dass die Messspannung sich irgendwo im Bereich der letzten Stufe (also von 4 V bis 5 V) bewegt.
Umrechnung des ADC-Wertes in eine Spannung
Die Größe eines „Bereiches“ bestimmt sich also zu
[math]\displaystyle{ \text{Bereichsbreite} = \frac{\text{Referenzspannung}}{\text{Maximalwert}+1} }[/math].
Der Messwert vom ADC rechnet sich dann wie folgt in eine Spannung um:
[math]\displaystyle{ \text{Spannung} = \text{ADCwert} \cdot \frac{\text{Referenzspannung}}{\text{Maximalwert}} }[/math]
Wird der ADC also mit 10 Bit an 5 V betrieben, so lauten die Umrechnungen:
[math]\displaystyle{ \text{Bereichsbreite} = \frac{5\,\text{V}}{1024} = 0{,}004883\,\text{V} = 4{,}883\,\text{mV} }[/math]
[math]\displaystyle{ \text{Spannung} = \text{ADCwert} \cdot 4{,}883\,\text{mV} }[/math]
Wenn man genau hinsieht, stellt man fest, dass sowohl die Referenzspannung als auch der Maximalwert Konstanten sind. D. h. der Quotient aus Referenzspannung und Maximalwert ist konstant. Somit muss nicht immer eine Addition und Division ausgeführt werden, sondern nur eine Multiplikation! Das spart viel Aufwand und Rechenzeit! Dabei kann sinnvollerweise Festkommaarithmetik zum Einsatz kommen.
Kalibrierung
Hat man eine externe, genaue Referenzspannung zur Hand, dann kann ein Korrekturfaktor berechnet werden, mit dem die Werte des ADCs im Nachhinein korrigiert werden können. Dies geschieht normalerweise über eine sogenannte Gain-Offset-Korrektur an einer Geraden oder einer Parabel. In erster Näherung kann man auch die interne Referenzspannung um das Inverse des ermittelten Korrekturwertes verstellen, um einen genaueren bereits digitalisierten Wert zu bekommen.
Die Steuerregister des ADC
ADMUX
REFS1 | REFS0 | ADLAR | MUX3 | MUX2 | MUX1 | MUX0 |
---|
- Referenzspannung: REFS1, REFS0
REFS1 | REFS0 | Referenz |
---|---|---|
0 | 0 | externe Referenz |
0 | 1 | interne Referenz: Avcc |
1 | 0 | wird beim Mega8 nicht benutzt |
1 | 1 | interne Referenz: 2,56 Volt |
- Ausrichtung: ADLAR
ADLAR | Auswirkung |
---|---|
0 | Das Ergebnis wird in den Registern ADCH/ADCL rechtsbündig ausgerichtet. Die 8 niederwertigsten Bits des Ergebnisses werden in ADCL abgelegt. Die verbleibenden 2 höchstwertigen Bits des Ergebnisses werden im Register ADCH in den Bits 0 und 1 abgelegt. |
1 | Das Ergebnis wird in den Registern ADCH/ADCL linksbündig ausgerichtet. Die 8 höchstwertigen Bits des Ergebnisses werden in ADCH abgelegt. Die verbleibenden 2 niederwertigen Bits werden im Register ADCL in den Bits 6 und 7 abgelegt. |
- Kanalwahl: MUX3, MUX2, MUX1, MUX0
MUX3 | MUX2 | MUX1 | MUX0 | Kanal |
---|---|---|---|---|
0 | 0 | 0 | 0 | Kanal 0, Pin PC0 |
0 | 0 | 0 | 1 | Kanal 1, Pin PC1 |
0 | 0 | 1 | 0 | Kanal 2, Pin PC2 |
0 | 0 | 1 | 1 | Kanal 3, Pin PC3 |
0 | 1 | 0 | 0 | Kanal 4, Pin PC4 |
0 | 1 | 0 | 1 | Kanal 5, Pin PC5 |
0 | 1 | 1 | 0 | Kanal 6 (*) |
0 | 1 | 1 | 1 | Kanal 7 (*) |
1 | 1 | 1 | 0 | 1,23 V, Vbg |
1 | 1 | 1 | 1 | 0 V, GND |
(*) Beim ATmega8 nur in der Gehäusebauform TQFP und MLF verfügbar, nicht in PDIP
ADCSRA
ADEN | ADSC | ADFR | ADIF | ADIE | ADPS2 | ADPS1 | ADPS0 |
---|
- ADEN – ADC Enable
- Mittels ADEN wird der ADC ein- und ausgeschaltet. Eine 1 an dieser Bitposition schaltet den ADC ein.
- ADSC – ADC Start Conversion
- Wird eine 1 an diese Bitposition geschrieben, so beginnt der ADC mit der Wandlung. Das Bit bleibt auf 1, solange die Wandlung im Gange ist. Wenn die Wandlung beendet ist, wird dieses Bit von der ADC-Hardware wieder auf 0 gesetzt.
- ADFR – ADC Free Running
- Wird eine 1 an ADFR geschrieben, so wird der ADC im Free-Running-Modus betrieben. Dabei startet der ADC nach dem Abschluss einer Messung automatisch die nächste Messung. Die erste Messung wird ganz normal über das Setzen des ADSC-Bits gestartet.
- ADIF – ADC Interrupt Flag
- Wenn eine Messung abgeschlossen ist, wird das ADIF-Bit gesetzt. Ist zusätzlich noch das ADIE-Bit gesetzt, so wird ein Interrupt ausgelöst und der entsprechende Interrupt-Handler angesprungen.
- ADIE – ADC Interrupt Enable
- Wird eine 1 an ADIE geschrieben, so löst der ADC nach Beendigung einer Messung einen Interrupt aus.
- ADPS2, ADPS1, ADPS0 – ADC Prescaler
- Mit dem Prescaler kann die ADC-Frequenz gewählt werden. Laut Datenblatt sollte diese für die optimale Auflösung zwischen 50 kHz und 200 kHz liegen. Ist die Wandlerfrequenz langsamer eingestellt, kann es passieren, dass die eingebaute Sample-&-Hold-Schaltung die Eingangsspannung nicht lange genug konstant halten kann. Ist die Frequenz aber zu schnell eingestellt, dann kann es passieren, dass sich die Sample-&-Hold-Schaltung nicht schnell genug an die Eingangsspannung anpassen kann.
ADPS2 | ADPS1 | ADPS0 | Vorteiler |
---|---|---|---|
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 |
- Beispiel
- 8 MHz Prozessortakt: 8.000.000 Hz / 200.000 Hz = 40
Da mit 200 kHz gerechnet wurde (maximale Frequenz), nimmt man den nächsthöheren Wert, also 64.
8.000.000 Hz / 64 = 125.000 Hz = 125 kHz
So erhält man bei 8 MHz einen Prescaler von 64 und eine Frequenz von 125 kHz.
Die Ergebnisregister ADCL und ADCH
Da das Ergebnis des ADC ein 10-Bit-Wert ist, passt dieser Wert naturgemäß nicht in ein einzelnes Register, das ja bekanntlich nur 8 Bit breit ist. Daher wird das Ergebnis in zwei Register ADCL und ADCH abgelegt. Standardmäßig (d. h. ADLAR = 0) werden von den 10 Ergebnisbits die niederwertigsten 8 im Register ADCL abgelegt und die noch fehlenden 2 Bits im Register ADCH an den niederwertigsten Bitpositionen gespeichert.
ADCH ADCL +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+ | | | | | | | | | | | | | | | | | | +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+ 9 8 7 6 5 4 3 2 1 0
Ist keine 10-Bit-Genauigkeit gefragt, kann diese Zuordnung aber auch geändert werden: Durch Setzen des ADLAR-Bits im ADMUX-Register wird die Ausgabe geändert zu:
ADCH ADCL +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+ | | | | | | | | | | | | | | | | | | +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+ 9 8 7 6 5 4 3 2 1 0
Auf diese Weise kann das ADC-Ergebnis direkt als 8-Bit-Zahl weiterverarbeitet werden: Die 8 höchstwertigen Bits stehen bereits verarbeitungsfertig im Register ADCH zur Verfügung.
Beim Auslesen der ADC-Register ist zu beachten: Immer zuerst ADCL und erst dann ADCH auslesen. Beim Zugriff auf ADCL wird das ADCH-Register gegenüber Veränderungen vom ADC gesperrt. Erst beim nächsten Auslesen des ADCH-Registers wird diese Sperre wieder aufgehoben. Dadurch ist sichergestellt, daß die Inhalte von ADCL und ADCH immer aus demselben Wandlungsergebnis stammen, selbst wenn der ADC im Hintergrund selbsttätig weiterwandelt. Das ADCH-Register muss ausgelesen werden!
Beispiele
Ausgabe als ADC-Wert
Das folgende Programm liest in einer Schleife ständig den ADC aus und verschickt das Ergebnis im Klartext (ASCII) über die UART. Zur Verringerung des unvermeidlichen Rauschens werden 256 Messwerte herangezogen und deren Mittelwert als endgültiges Messergebnis gewertet. Dazu werden die einzelnen Messungen in den Registern temp2, temp3 und temp4 als 24-Bit-Zahl aufaddiert. Die Division durch 256 erfolgt dann ganz einfach dadurch, dass das Register temp2 verworfen wird und die Register temp3 und temp4 als 16-Bit-Zahl aufgefasst werden. Eine Besonderheit ist noch, dass je nach dem Wert in temp2 die 16-Bit-Zahl in temp3 und temp4 noch aufgerundet wird: Enthält temp2 einen Wert größer als 127, dann wird zur 16-Bit-Zahl in temp3/temp4 noch 1 dazu addiert.
In diesem Programm findet man oft die Konstruktion
subi temp3, low(-1) ; Addieren von 1
sbci temp4, high(-1) ; Addieren des Carry
Dabei handelt es sich um einen kleinen Trick. Um eine Konstante zu einem Register direkt addieren zu können bräuchte man einen Befehl à la addi
(Add Immediate, Addiere Konstante), den der AVR aber nicht hat. Ebenso gibt es kein adci
(Add with Carry Immediate, Addiere Konstante mit Carry-Flag). Man müsste also erst eine Konstante in ein Register laden und addieren. Das kostet aber Programmspeicher, Rechenzeit und man muss ein Register zusätzlich frei haben.
; 16-Bit-Addition mit Konstante, ohne Cleverness
ldi temp5, low(1)
add temp3, temp5 ; Addieren von 1
ldi temp5, high(1)
adc temp3, temp5 ; Addieren des Carry
Hier greift man einfach zu dem Trick, dass eine Addition gleich der Subtraktion des negativen Werts ist. Also „addiere +1“ ist gleich „subtrahiere −1“. Dafür hat der AVR zwei Befehle, subi
(Subtract Immediate, Subtrahiere Konstante) und sbci
(Subtract Immediate with Carry, Subtrahiere Konstante mit Carry-Flag).
Das folgende Programm ist für den ATmega8 geschrieben. Für moderne Nachfolgetypen wie den ATmega88 muss der Code angepasst werden (siehe diesen Forumsthread und die Application Note AVR094: Replacing ATmega8 by ATmega88 (PDF; 74 KB)).
.include "m8def.inc"
.def temp1 = r16 ; allgemeines temp Register, zur kurzfristigen Verwendung
.def temp2 = r17 ; Register für 24 Bit Addition, Lowest Byte
.def temp3 = r18 ; Register für 24 Bit Addition, Middle Byte
.def temp4 = r19 ; Register für 24 Bit Addition, Highest Byte
.def adlow = r20 ; Ergebnis vom ADC / Mittelwert der 256 Messungen
.def adhigh = r21 ; Ergebnis vom ADC / Mittelwert der 256 Messungen
.def messungen = r22 ; Schleifenzähler für die Messungen
.def ztausend = r23 ; Zehntausenderstelle des ADC Wertes
.def tausend = r24 ; Tausenderstelle des ADC Wertes
.def hundert = r25 ; Hunderterstelle des ADC Wertes
.def zehner = r26 ; Zehnerstelle des ADC Wertes
.def zeichen = r27 ; Zeichen zur Ausgabe auf den UART
.equ F_CPU = 4000000 ; Systemtakt in Hz
.equ BAUD = 9600 ; Baudrate
; Berechnungen
.equ UBRR_VAL = ((F_CPU+BAUD*8)/(BAUD*16)-1) ; clever runden
.equ BAUD_REAL = (F_CPU/(16*(UBRR_VAL+1))) ; Reale Baudrate
.equ BAUD_ERROR = ((BAUD_REAL*1000)/BAUD-1000) ; Fehler in Promille
.if ((BAUD_ERROR>10) || (BAUD_ERROR<-10)) ; max. +/-10 Promille Fehler
.error "Systematischer Fehler der Baudrate grösser 1 Prozent und damit zu hoch!"
.endif
; hier geht das Programm los
ldi temp1, LOW(RAMEND) ; Stackpointer initialisieren
out SPL, temp1
ldi temp1, HIGH(RAMEND)
out SPH, temp1
;UART Initalisierung
ldi temp1, LOW(UBRR_VAL) ; Baudrate einstellen
out UBRRL, temp1
ldi temp1, HIGH(UBRR_VAL)
out UBRRH, temp1
sbi UCSRB, TXEN ; TX einschalten
; ADC initialisieren: ADC0, Vcc als Referenz, Single Conversion,
; Vorteiler 32 -> ADC-Frequenz = 125 kHz @ F_OSC = 4 MHz,
; bei anderen Taktfrequenzen die ADPS-Werte ggf. anpassen!
ldi temp1, (1<<REFS0) ; Kanal 0, interne Referenzspannung 5V
out ADMUX, temp1
ldi temp1, (1<<ADEN) | (1<<ADPS2) | (1<<ADPS0)
out ADCSRA, temp1
Main:
clr temp1
clr temp2
clr temp3
clr temp4
ldi messungen, 0 ; 256 Schleifendurchläufe
; neuen ADC-Wert lesen (Schleife - 256 mal)
sample_adc:
sbi ADCSRA, ADSC ; den ADC starten
wait_adc:
sbic ADCSRA, ADSC ; wenn der ADC fertig ist, wird dieses Bit gelöscht
rjmp wait_adc
; ADC einlesen:
in adlow, ADCL ; immer zuerst das Low-Byte lesen
in adhigh, ADCH ; danach das mittlerweile gesperrte High-Byte
; alle 256 ADC-Werte addieren
; dazu wird mit den Registern temp4, temp3 und temp2 ein
; 24-Bit breites Akkumulationsregister gebildet, in dem
; die 10-Bit-Werte aus adhigh, adlow aufsummiert werden
add temp2, adlow ; addieren
adc temp3, adhigh ; addieren über Carry
adc temp4, temp1 ; addieren über Carry, temp1 enthält 0
dec messungen ; Schleifenzähler MINUS 1
brne sample_adc ; wenn noch keine 256 ADC Werte -> nächsten Wert einlesen
; Aus den 256 Werten den Mittelwert berechnen.
; Mathematisch eine Division durch 256.
; Da aber 2^8 = 256 ist, kann das einfach durch das Weglassen des niederwertigsten Bytes
; erreicht werden.
;
; Allerdings wird der Wert noch gerundet:
cpi temp2, 128 ; "Kommastelle" kleiner als 128 ?
brlo no_round ; ist kleiner ==> Sprung
; Aufrunden
subi temp3, low(-1) ; addieren von 1
sbci temp4, high(-1) ; addieren des Carry
no_round:
; Ergebnis nach adlow und adhigh kopieren,
; damit die temp-Register frei werden
mov adlow, temp3
mov adhigh, temp4
; in ASCII umwandeln
; Division durch mehrfache Subtraktion
ldi ztausend, '0'-1 ; Ziffernzähler direkt als ASCII-Code
; bzgl. '0'-1 siehe http://www.mikrocontroller.net/topic/198681
Z_ztausend:
inc ztausend
subi adlow, low(10000) ; -10,000
sbci adhigh, high(10000) ; 16 Bit
brcc Z_ztausend
subi adlow, low(-10000) ; nach Unterlauf wieder einmal addieren
sbci adhigh, high(-10000); +10,000
ldi tausend, '0'-1 ; Ziffernzähler direkt als ASCII-Code
Z_tausend:
inc tausend
subi adlow, low(1000) ; -1,000
sbci adhigh, high(1000) ; 16 Bit
brcc Z_tausend
subi adlow, low(-1000) ; nach Unterlauf wieder einmal addieren
sbci adhigh, high(-1000) ; +1,000
ldi hundert, '0'-1 ; Ziffernzähler direkt als ASCII-Code
Z_hundert:
inc hundert
subi adlow, low(100) ; -100
sbci adhigh, high(100) ; 16 Bit
brcc Z_hundert
subi adlow, low(-100) ; nach Unterlauf wieder einmal addieren
sbci adhigh, high(-100) ; +100
ldi zehner, '0'-1 ; Ziffernzähler direkt als ASCII-Code
Z_zehner:
inc zehner
subi adlow, low(10) ; -10
sbci adhigh, high(10) ; 16 Bit
brcc Z_zehner
subi adlow, low(-10) ; nach Unterlauf wieder einmal addieren
sbci adhigh, high(-10) ; +10
subi adlow, -'0' ; adlow enthält die Einer, Umwandlung in ASCII
; an UART senden
mov zeichen, ztausend ; Zehntausenderstelle ausgeben
rcall transmit
mov zeichen, tausend ; Tausenderstelle ausgeben
rcall transmit
mov zeichen, hundert ; Hunderterstelle ausgeben
rcall transmit
mov zeichen, zehner ; Zehnerstelle ausgeben
rcall transmit
mov zeichen, adlow ; Einerstelle ausgeben
rcall transmit
ldi zeichen, 13 ; CR, Carriage Return (Wagenrücklauf)
rcall transmit
ldi zeichen, 10 ; LF, Line Feed (Neue Zeile)
rcall transmit
rjmp Main
transmit:
sbis UCSRA, UDRE ; Warten, bis UDR bereit ist ...
rjmp transmit
out UDR, zeichen ; und Zeichen ausgeben
ret
Ausgabe als Spannungswert
Das zweite Beispiel ist schon um einiges größer. Hier wird der gemittelte ADC-Wert in eine Spannung umgerechnet. Dazu wird Festkommaarithmetik verwendet. Die Daten sind in diesem Fall:
Referenzspannung : 5 V alte Auflösung : 5 V / 1024 = 4,8828125 mV neue Auflösung : 1 mV
→ Faktor = 4,8828125 mV / 1 mV = 4,8828125
Der Faktor wird dreimal mit 10 multipliziert und das Ergebnis auf 4883 gerundet. Die neue Auflösung wird dreimal durch 10 dividiert und beträgt 1 µV. Der relative Fehler beträgt
[math]\displaystyle{ F_r = \frac{4883}{4882{,}8125}-1 = 0{,}00384\,\% = \frac{1}{26042} }[/math].
Dieser Fehler ist absolut vernachlässigbar. Nach der Multiplikation des ADC-Wertes mit 4883 liegt die gemessene Spannung in der Einheit Mikrovolt (µV) vor. Vorsicht! Das ist nicht die reale Auflösung und Genauigkeit, nur rein mathematisch bedingt. Für maximale Genauigkeit sollte man die Versorgungsspannung AVCC, welche hier gleichzeitig als Referenzspannung dient, exakt messen, die Rechnung nachvollziehen und den Wert im Quelltext eintragen. Damit führt man eine einfache Einpunktkalibrierung durch.
Da das Programm schon um einiges größer und komplexer ist, wurde es im Vergleich zur Vorgängerversion geändert. Die Multiplikation sowie die Umwandlung der Zahl in einen ASCII-String sind als Unterprogramme geschrieben, dadurch erhält man wesentlich mehr Überblick im Hauptprogramm und die Wiederverwendung in anderen Programmen vereinfacht sich. Außerdem wird der String im RAM gespeichert und nicht mehr in CPU-Registern. Die Berechnung der einzelnen Ziffern erfolgt über eine Schleife, das ist kompakter und übersichtlicher.
.include "m8def.inc"
.def z0 = r1 ; Zahl für Integer -> ASCII Umwandlung
.def z1 = r2
.def z2 = r3
.def z3 = r4
.def temp1 = r16 ; allgemeines Register, zur kurzfristigen Verwendung
.def temp2 = r17 ; Register für 24 Bit Addition, niederwertigstes Byte (LSB)
.def temp3 = r18 ; Register für 24 Bit Addition, mittlerers Byte
.def temp4 = r19 ; Register für 24 Bit Addition, höchstwertigstes Byte (MSB)
.def adlow = r20 ; Ergebnis vom ADC-Mittelwert der 256 Messungen
.def adhigh = r21 ; Ergebnis vom ADC-Mittelwert der 256 Messungen
.def messungen = r22 ; Schleifenzähler für die Messungen
.def zeichen = r23 ; Zeichen zur Ausgabe auf den UART
.def temp5 = r24
.def temp6 = r25
; Faktor für Umrechung des ADC-Wertes in Spannung
; = (Referenzspannung / 1024 ) * 100000
; = 5V / 1024 * 1.000.000
.equ Faktor = 4883
.equ F_CPU = 4000000 ; Systemtakt in Hz
.equ BAUD = 9600 ; Baudrate
; Berechnungen
.equ UBRR_VAL = ((F_CPU+BAUD*8)/(BAUD*16)-1) ; clever runden
.equ BAUD_REAL = (F_CPU/(16*(UBRR_VAL+1))) ; Reale Baudrate
.equ BAUD_ERROR = ((BAUD_REAL*1000)/BAUD-1000) ; Fehler in Promille
.if ((BAUD_ERROR>10) || (BAUD_ERROR<-10)) ; max. +/-10 Promille Fehler
.error "Systematischer Fehler der Baudrate grösser 1 Prozent und damit zu hoch!"
.endif
; RAM
.dseg
.org 0x60
Puffer: .byte 10
; hier geht das Programm los
.cseg
.org 0
ldi temp1, LOW(RAMEND) ; Stackpointer initialisieren
out SPL, temp1
ldi temp1, HIGH(RAMEND)
out SPH, temp1
;UART Initalisierung
ldi temp1, LOW(UBRR_VAL) ; Baudrate einstellen
out UBRRL, temp1
ldi temp1, HIGH(UBRR_VAL)
out UBRRH, temp1
sbi UCSRB, TXEN ; TX einschalten
; ADC initialisieren: Kanal 0, int. Ref. AVCC, Single Conversion,
; Vorteiler 32 -> ADC-Frequenz = 125 kHz @ F_OSC = 4 MHz,
; bei anderen Taktfrequenzen die ADPS-Werte ggf. anpassen!
ldi temp1, (1<<REFS0) ; ADC0, interne Ref. Vcc
out ADMUX, temp1
ldi temp1, (1<<ADEN) | (1<<ADPS2) | (1<<ADPS0) ; f/32
out ADCSRA, temp1
Hauptschleife:
clr temp1
clr temp2
clr temp3
clr temp4
ldi messungen, 0 ; 256 Schleifendurchläufe
; neuen ADC-Wert lesen (Schleife - 256 mal)
adc_messung:
sbi ADCSRA, ADSC ; den ADC starten
adc_warten:
sbic ADCSRA, ADSC ; wenn der ADC fertig ist, wird dieses Bit gelöscht
rjmp adc_warten
; ADC einlesen:
in adlow, ADCL ; immer zuerst LOW Byte lesen
in adhigh, ADCH ; danach das mittlerweile gesperrte High Byte
; alle 256 ADC-Werte addieren
; dazu wird mit den Registern temp4, temp3 und temp2 ein
; 24-Bit breites Akkumulationsregister gebildet, in dem
; die 10 Bit Werte aus adhigh, adlow aufsummiert werden
add temp2, adlow ; addieren
adc temp3, adhigh ; addieren über Carry
adc temp4, temp1 ; addieren über Carry, temp1 enthält 0
dec messungen ; Schleifenzähler MINUS 1
brne adc_messung ; wenn noch keine 256 ADC Werte -> nächsten Wert einlesen
; Aus den 256 Werten den Mittelwert berechnen
; Bei 256 Werten ist das ganz einfach: Das niederwertigste Byte
; (im Register temp2) fällt einfach weg
;
; allerdings wird der Wert noch gerundet
cpi temp2,128 ; "Kommastelle" kleiner als 128 ?
brlo nicht_runden ; ist kleiner ==> Sprung
; Aufrunden
subi temp3, low(-1) ; addieren von 1
sbci temp4, high(-1) ; addieren des Carry
nicht_runden:
; Ergebnis nach adlow und adhigh kopieren
; damit die temp Register frei werden
mov adlow, temp3
mov adhigh, temp4
; in Spannung umrechnen
ldi temp5, low(Faktor)
ldi temp6, high(Faktor)
rcall mul_16x16
; in ASCII umwandeln
ldi XL, low(Puffer)
ldi XH, high(Puffer)
rcall Int_to_ASCII
; an UART senden
ldi ZL, low(Puffer+3)
ldi ZH, high(Puffer+3)
ldi temp1, 1
rcall sende_zeichen ; eine Vorkommastelle ausgeben
ldi zeichen, ',' ; Komma ausgeben
rcall sende_einzelzeichen
ldi temp1, 3 ; drei Nachkommastellen ausgeben
rcall sende_zeichen
ldi zeichen, 'V' ; Volt-Zeichen ausgeben
rcall sende_einzelzeichen
ldi zeichen, 13 ; Carriage Return (Steuerzeichen)
rcall sende_einzelzeichen
ldi zeichen, 10 ; New Line (Steuerzeichen)
rcall sende_einzelzeichen ; CR+NL bewirkt Zeilenumbruch
rjmp Hauptschleife
; Ende des Hauptprogramms
; Unterprogramme
; ein Zeichen per UART senden
sende_einzelzeichen:
sbis UCSRA,UDRE ; Warten, bis UDR bereit ist ...
rjmp sende_einzelzeichen
out UDR, zeichen ; und Zeichen ausgeben
ret
; mehrere Zeichen ausgeben, welche durch Z adressiert werden
; Anzahl in temp1
sende_zeichen:
sbis UCSRA,UDRE ; Warten, bis UDR bereit ist ...
rjmp sende_zeichen
ld zeichen, Z+ ; Zeichen laden
out UDR, zeichen ; und Zeichen ausgeben
dec temp1
brne sende_zeichen
ret
; 32-Bit-Zahl in ASCII umwandeln
; Zahl liegt in temp1..4
; Ergebnis ist ein 10stelliger ASCII-String, welcher im SRAM abgelegt wird
; Adressierung über X-Pointer
; mehrfache Subtraktion wird als Ersatz für eine Division durchgeführt.
Int_to_ASCII:
push ZL ; Register sichern
push ZH
push temp5
push temp6
ldi ZL,low(Tabelle*2) ; Zeiger auf Tabelle
ldi ZH,high(Tabelle*2)
ldi temp5, 10 ; Schleifenzähler
Int_to_ASCII_schleife:
ldi temp6, -1+'0' ; Ziffernzähler zählt direkt im ASCII Code
lpm z0,Z+ ; Nächste Zahl laden
lpm z1,Z+
lpm z2,Z+
lpm z3,Z+
Int_to_ASCII_ziffer:
inc temp6 ; Ziffer erhöhen
sub temp1, z0 ; Zahl subrahieren
sbc temp2, z1 ; 32 Bit
sbc temp3, z2
sbc temp4, z3
brge Int_to_ASCII_ziffer ; noch kein Unterlauf, nochmal
add temp1, z0 ; Unterlauf, eimal wieder addieren
adc temp2, z1 ; 32 Bit
adc temp3, z2
adc temp4, z3
st X+,temp6 ; Ziffer speichern
dec temp5
brne Int_to_ASCII_schleife ; noch eine Ziffer?
pop temp6
pop temp5
pop ZH
pop ZL ; Register wieder herstellen
ret
; Tabelle mit Zahlen für die Berechung der Ziffern
; 1 Milliarde bis 1
Tabelle:
.dd 1000000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1
; 16 Bit Wert in Spannung umrechnen
;
; = 16Bitx16Bit=32 Bit Multiplikation
; = vier 8x8 Bit Multiplikationen
;
; adlow/adhigh * temp5/temp6
mul_16x16:
push zeichen
clr temp1 ; 32 Bit Akku löschen
clr temp2
clr temp3
clr temp4
clr zeichen ; Null, für Carry-Addition
mul adlow, temp5 ; erste Multiplikation
add temp1, r0 ; und akkumulieren
adc temp2, r1
mul adhigh, temp5 ; zweite Multiplikation
add temp2, r0 ; und gewichtet akkumlieren
adc temp3, r1
mul adlow, temp6 ; dritte Multiplikation
add temp2, r0 ; und gewichtet akkumlieren
adc temp3, r1
adc temp4, zeichen ; carry addieren
mul adhigh, temp6 ; vierte Multiplikation
add temp3, r0 ; und gewichtet akkumlieren
adc temp4, r1
pop zeichen
ret
Für alle, die es besonders eilig haben, gibt es hier eine geschwindigkeitsoptimierte Version der Integer-in-ASCII-Umwandlung. Zunächst wird keine Schleife verwendet, sondern alle Stufen der Schleife direkt hingeschrieben. Das braucht zwar mehr Programmspeicher, ist aber schneller. Außerdem wird abwechselnd subtrahiert und addiert, dadurch entfällt das immer wieder notwendige Addieren nach dem Unterlauf. Zu guter Letzt werden die Berechnungen nur mit der minimal notwendigen Wortbreite durchgeführt. Am Anfang mit 32 Bit, dann nur noch mit 16 bzw. 8 Bit.
; 32-Bit-Zahl in ASCII umwandeln
; geschwindigkeitsoptimierte Version
; Zahl liegt in temp1..4
; Ergebnis ist ein 10stelliger ASCII String, welcher im SRAM abgelegt wird
; Adressierung über X-Pointer
Int_to_ASCII:
ldi temp5, -1 + '0'
_a1ser:
inc temp5
subi temp1,BYTE1(1000000000) ; - 1.000.000.000
sbci temp2,BYTE2(1000000000)
sbci temp3,BYTE3(1000000000)
sbci temp4,BYTE4(1000000000)
brcc _a1ser
st X+,temp5 ; im Puffer speichern
ldi temp5, 10 + '0'
_a2ser:
dec temp5
subi temp1,BYTE1(-100000000) ; + 100.000.000
sbci temp2,BYTE2(-100000000)
sbci temp3,BYTE3(-100000000)
sbci temp4,BYTE4(-100000000)
brcs _a2ser
st X+,temp5 ; im Puffer speichern
ldi temp5, -1 + '0'
_a3ser:
inc temp5
subi temp1,low(10000000) ; - 10.000.000
sbci temp2,high(10000000)
sbci temp3,BYTE3(10000000)
sbci temp4,BYTE4(10000000)
brcc _a3ser
st X+,temp5 ; im Puffer speichern
ldi temp5, 10 + '0'
_a4ser:
dec temp5
subi temp1,low(-1000000) ; + 1.000.000
sbci temp2,high(-1000000)
sbci temp3,BYTE3(-1000000)
sbci temp4,BYTE4(-1000000)
brcs _a4ser
st X+,temp5 ; im Puffer speichern
ldi temp5, -1 + '0'
_a5ser:
inc temp5
subi temp1,low(100000) ; -100.000
sbci temp2,high(100000)
sbci temp3,BYTE3(100000)
brcc _a5ser
st X+,temp5 ; im Puffer speichern
ldi temp5, 10 + '0'
_a6ser:
dec temp5
subi temp1,low(-10000) ; +10,000
sbci temp2,high(-10000)
sbci temp3,BYTE3(-10000)
brcs _a6ser
st X+,temp5 ; im Puffer speichern
ldi temp5, -1 + '0'
_a7ser:
inc temp5
subi temp1,low(1000) ; -1000
sbci temp2,high(1000)
brcc _a7ser
st X+,temp5 ; im Puffer speichern
ldi temp5, 10 + '0'
_a8ser:
dec temp5
subi temp1,low(-100) ; +100
sbci temp2,high(-100)
brcs _a8ser
st X+,temp5 ; im Puffer speichern
ldi temp5, -1 + '0'
_a9ser:
inc temp5
subi temp1, 10 ; -10
brcc _a9ser
st X+,temp5 ; im Puffer speichern
ldi temp5, 10 + '0'
_a10ser:
dec temp5
subi temp1, -1 ; +1
brcs _a10ser
st X+,temp5 ; im Puffer speichern
ret
Literatur
- Datenblatt des verwendeten Controllers
- Application Note AVR127: Understanding ADC Parameters (PDF; 777 KB)
- Application Note AVR121: Enhancing ADC resolution by oversampling (PDF; 117 KB)
- Application Note AVR120: Characterization and Calibration of the ADC on an AVR (PDF; 257 KB)
- Application Note AVR126: ADC of megaAVR® in Single-Ended Mode (PDF; 233 KB)
- Kristian Saxrud Bekken, Microchip: Differential and Single-Ended ADC (PDF; 161 KB)