Analoge Ein- und Ausgabe

Leider können wir mit unseren Controllern keine analogen Werte direkt verarbeiten. Dazu bedarf es jeweils einer Umwandlung des analogen Signals in einen digitalen Zahlenwert. Je nachdem, ob wir Daten einlesen oder ausgeben wollen reden wir dabei von DAC oder ADC.

ADC (Analog Digital Converter)

Der ADC wandelt analoge Signale in digitale Werte um welche vom Controller interpretiert werden können. Einige AVR-Typen haben bereits einen oder mehrere solcher ADC's eingebaut, bei den kleineren AVR's müssen wir uns anders aus der Affäre ziehen. Die Genauigkeit, mit welcher ein analoges Signal aufgelöst werden kann wird durch die Auflösung des ADC in Anzahl Bits angegeben, man hört bzw. liest jeweils von 8-Bit ADC oder 10-Bit ADC oder noch höher.
Ein ADC mit 8 Bit Auflösung kann somit das analoge Signal mit einer Genauigkeit von 1/255 des Maximalwertes darstellen. Wenn wir nun mal annehmen, wir hätten eine Spannung zwischen 0 und 5 Volt und eine Auflösung von 3 Bit, dann könnten die Werte 0V, 0.625V, 1.25, 1.875V, 2.5V, 3.125V, 3.75, 4.375,  5V daherkommen, siehe dazu folgende Tabelle:

Eingangsspannung am ADC Entsprechender Messwert
0...0.625V 0
0.625...1.25V 1
1.25...1.875V 2
1.875...2.5V 3
2.5...3.125V 4
3.125...3.75V 5
3.75...4.375V 6
4.375...5V 7
5V 8

Die Angaben sind natürlich nur ungefähr. Je höher nun die Auflösung des ADC ist, also je mehr Bits er hat, um so genauer kann der Wert erfasst werden.

Messen eines Widerstandes

Wir wollen hier einmal die wohl einfachste Methode zur Erfassung eines analogen Wertes realisieren und zwar das Messen eines veränderlichen Widerstandes wie z.B. eines Potentiometers.

Man stelle sich vor, wir schalten einen Kondensator in Reihe zu einem Widerstand zwischen die Versorgungsspannung und Masse und dazwischen nehmen wir das Signal ab und führen es auf einen der Pins an unserem Controller, genau so wie es in folgender Grafik dargestellt ist.

Wenn wir nun den Pin des AVR als Ausgang schalten und auf Logisch 1 (HIGH) legen, dann liegt an beiden Platten des Kondensators Vcc an und dieser wird entladen (Klingt komisch, mit Vcc entladen, ist aber so).
Nachdem nun der Kondensator genügend entladen ist schalten wir einfach den Pin als Eingang wodurch dieser hochohmig wird. Der Kondensator lädt sich jetzt über das Poti auf, dabei steigt der Spannungsabfall über dem Kondensator und derjenige über dem Poti sinkt. Fällt nun der Spannungsabfall über dem Poti unter die  Thresholdspannung des Eingangspins (2/5 Vcc, also ca. 2V), dann schaltet der Eingang von HIGH auf LOW um. Wenn wir nun messen (zählen), wie lange es dauert, bis der Kondensator so weit geladen ist, dann haben wir einen ungefähren Wert der Potentiometerstellung.
Der 220 Ohm Widerstand dient dem Schutz des Controllers. Wenn nämlich sonst die Potentiometerstellung auf Maximum steht (0 Ohm), dann würde in den Eingang des Controllers ein viel zu hoher Strom fliessen und der AVR würde in Rauch aufgehen.

Dies ist meines Wissens die einzige Schaltung zur Erfassung von Analogwerten, welche mit nur einem einzigen Pin auskommt.
Mit einem weiteren Eingangspin und ein wenig Software können wir auch eine Kalibrierung realisieren, um den Messwert in einen vernünftigen Bereich (z.B: 0...100 % oder so) umzurechnen.

Wer Lust hat, sich selber mal an ein solches Programm heranzuwagen, der sollte das jetzt tun. Für diejenigen, die es gern schnell mögen, hier das Beispielprogramm, welches den UART-Printf aus den vorangegangenen Kapiteln benötigt, inl. Makefile:

Poti.c Hauptprogramm.
Pot.c Separate Routine zur Ermittlung des Messwertes.
Pot.h Zugehörige Headerdatei.
UartPrintF.c Für die Debugausgabe auf den UART.
UartPrintF.h Zugehörige Headerdatei.
Makefile Makefile.

Nachdem das Programm auf den AVR geladen wurde muss dieser kalibriert werden. Dazu wird der Kalibrierungsschalter geschlossen und das Poti einige Male zwischen minimaler und maximaler Stellung hin und her gedreht. Dabei werden die jeweiligen Maximalwerte bestimmt. Wenn der Kalibrierschalter wieder geöffnet wird werden die Kalibrierungsdaten in's EEPROM des AVR geschrieben, damit die Prozedur nicht nach jedem Reset wiederholt werden muss.

Auf Pin 4 habe ich noch ein Triggersignal gelegt, welches auf HIGH geht wenn die Messung beginnt und auf LOW, wenn der Messvorgang beendet wird. Mit Hilfe dieses Signals kann der Vorgang wunderschön auf einem Oszillographen dargestellt werden.

ADC über Komparator

Es gibt einen weiteren Weg, eine analoge Spannung mit Hilfe des Komparators, welcher in fast jedem AVR integriert ist, zu messen. Siehe dazu auch die Application Note AVR400 von Atmel.

Dabei wird das zu messende Signal auf den invertierenden Eingang des Komparators geführt. Zusätzlich wird ein Referenzsignal an den nicht invertierenden Eingang des Komparators angeschlossen. Das Referenzsignal wird hier auch wieder über ein RC-Glied erzeugt, allerdings mit festen Werten für R und C.

Das Prinzip der Messung ist nun dem vorhergehenden recht ähnlich. Durch Anlegen eines LOW-Pegels an Pin 2 wird der Kondensator zuerst einmal entladen. Auch hier muss darauf geachtet werden, dass der Entladevorgang genügend lang dauert.
Nun wird Pin 2 auf HIGH gelegt. Der Kondensator wird geladen. Wenn die Spannung über dem Kondensator die am Eingangspin anliegende Spannung erreicht hat schaltet der Komparator durch. Die Zeit, welche benötigt wird, um den Kondensator zu laden kann nun auch wieder als Mass für die Spannung an Pin 1 herangezogen werden.

Ich habe es mir gespart, diese Schaltung auch aufzubauen und zwar aus mehreren Gründen:

  1. 3 Pins notwendig.

  2. Genauigkeit vergleichbar mit einfacherer Lösung.

  3. War einfach zu faul.

Der Vorteil dieser Schaltung liegt allerdings darin, dass damit direkt Spannungen gemessen werden können.

Der ADC im AVR

Wenn es einmal etwas genauer sein soll, dann müssen wir auf einen AVR mit eingebautem(n)  ADC-Wandler zurückgreifen. Wir wollen hier einmal den AT90S8535 besprechen, welcher über 8 ADC-Kanäle verfügt.

Die Umwandlung innerhalb des AVR basiert auf der Schrittweisen Näherung. Beim AVR müssen die Pins AGND und AVCC beschaltet werden mit 0V bzw. Vcc. Warum der das nicht intern macht ist mir eigentlich auch nicht so ganz klar. Zusätzlich muss eine externe Referenzspannung im Bereich von 2V bis Vcc an AREF angelegt werden. Die zu messende Spannung muss nun im Bereich zwischen AGND und AREF liegen.

Der ADC kann in zwei verschiedenen Betriebsarten verwendet werden.

Einfache Wandlung (Single Conversion)

In dieser Betriebsart wird der Wandler bei Bedarf vom Programm angestossen für jeweils eine Messung.

Frei laufend (Free Running)

In dieser Betriebsart erfasst der Wandler permanent die anliegende Spannung und schreibt diese in das ADC Data Register.

Die Register des ADC

Der ADC verfügt über eigene Register, welche hier aufgelistet werden:

ADCSR   

ADC Control and Status Register.
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
Initialwert 0 0 0 0 0 0 1 0
ADEN ADC Enable
Dieses Bit muss gesetzt werden, um den ADC überhaupt zu aktivieren. Wenn das Bit nicht gesetzt ist können die Pin's 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 Running Select
Mit diesem Bit wird die Betriebsart eingestellt.
Eine logische 1 aktiviert den frei laufenden Modus. Der ADC misst nun ständig den ausgewählten Kanal und schreibt den gemessenen Wert in das ADC Data Register.
ADIF ADC Interrupt Flag
Dieses Bit wird vom ADC gesetzt wenn eine Umwandlung erfolgt und das ADC Data Register aktualisiert ist.
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 ein logisches 1 in das Register geschrieben wird (So steht's in der AVR-Doku).
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
  TFmin=CLK/200kHz=4000000/200000=20
  TFmax=CLK/50kHz=4000000/50000=80
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 + ADCH * 256

ADMUX  

ADC Multiplexer Select Register
Mit diesem Register wird der zu messende Kanal ausgewählt. Beim 90S8535 kann jeder Pin von Port B als ADC-Eingang verwendet werden (=8 Kanäle).
Das Register ist wie folgt aufgebaut:

Bit 7 6 5 4 3 2 1 0
Name - - - - - MUX2 MUX1 MUX0
R/W R R R R R R/W R/W R/W
Initialwert 0 0 0 0 0 0 0 0
MUX2
...
MUX0
Mit diesem 3 Bits wird der zu messende Kanal bestimmt. Es wird einfach die entsprechende Pinnummer des Ports eingeschrieben.
Wenn das Register beschrieben wird, während dem eine Umwandlung läuft, so wird zuerst die aktuelle Umwandlung auf dem bisherigen Kanal beendet. Dies ist vor allem beim frei laufenden Betrieb zu berücksichtigen.

Meine Empfehlung ist deswegen klar diese, dass der frei laufende Betrieb nur bei einem einzelnen zu verwendenden Analogeingang verwendet werden sollte, wenn man sich Probleme bei der Umschalterei ersparen will.

Aktivieren des ADC

Um den ADC zu aktivieren müssen wir das ADEN-Bit im ADCSR-Register setzen. Im gleichen Schritt legen wir auch gleich die Betriebsart fest. Wenn wir frei laufend arbeiten wollen setzen wir zusätzlich das ADFR-Bit. Bei Bedarf wird auch gleich der Teilungsfaktor eingestellt.

// Teilungsfaktor auf 8 und ADC aktivieren
// Nicht frei laufend
outp ((1<<ADEN) & 3, ADCSR);

Um nun eine einzelne Messung, sagen wir mal an Pin 3, durchzuführen schalten wir den Kanal ein, starten die Wandlung und warten, bis der ADC die Beendigung meldet:

outp ((1<<PINB3), ADMUX);        // Kanal an Pin 3 auswählen
sbi (ADCSR, ADSC);
while (bit_is_set (ADCSR, ADSC)) /* Nur warten */;
x = __inw (ADCL);                // Wert auslesen

Da ich leider bisher noch kein Experimentierboard für den 8535 gebastelt habe kann ich euch auch keine erprobte Übung anbieten.

DAC (Digital Analog Converter)

Mit Hilfe eines DAC können wir nun auch Analogsignale ausgeben. Es gibt hier mehrere Verfahren. Wenn wir beim ADC die Möglichkeit haben, mit externen Komponenten zu operieren müssen wir bei der DAC-Wandlung mit dem auskommen, was der Controller selber zu bieten hat.

DAC über mehrere digitale Ausgänge

Wenn wir an den Ausgängen des Controllers ein entsprechendes Widerstandsnetzwerk aufbauen haben wir die Möglichkeit, durch die Ansteuerung der Ausgänge über den Widerständen einen Addierer aufzubauen, mit dessen Hilfe wir eine dem Zahlenwert proportionale Spannung erzeugen können. Das Schaltbild dazu kann etwa so aussehen:

Es sollten selbstverständlich möglichst genaue Widerstände verwendet werden, also nicht unbedingt solche mit einer Toleranz von 10% oder mehr. Weiterhin empfiehlt es sich, je nach Anwendung den Ausgangsstrom über einen Operationsverstärker zu verstärken.

PWM (Pulsweitenmodulation)

Wir kommen nun zu einem Thema, welches in aller Munde ist, aber viele Anwender verstehen nicht ganz, wie PWM eigentlich funktioniert.

Wie wir alle wissen ist ein Microcontroller ein rein digitales Bauteil. Definieren wir einen Pin als Ausgang, dann können wir diesen Ausgang entweder auf HIGH setzen worauf am Ausgang die Versorgungsspannung Vcc anliegt, oder aber wir setzen den Ausgang auf LOW wonach dann 0V am Ausgang liegt.
Was passiert aber nun, wenn wir periodisch mit einer festen Frequenz zwischen HIGH und LOW umschalten?
Richtig, wir erhalten eine Rechteckspannung, wie die folgende Abbildung zeigt:

Diese Rechteckspannung hat nun einen geometrischen Mittelwert, der je nach Pulsbreite kleiner oder grösser ist.

Wenn wir nun diese pulsierende Ausgangsspannung noch über ein RC-Glied filtern dann haben wir schon eine entsprechende Gleichspannung erzeugt.

Mit den AVR's können wir direkt PWM-Signale erzeugen. Dazu dient der 16-Bit Zähler, welcher im sogenannten PWM-Modus betrieben werden kann.

Hinweis: In den folgenden Überlegungen wird als Controller der 90S2313 vorausgesetzt. Die Theorie ist allerdings bei anderen AVR-Controllern vergleichbar, die Pinbelegung allerdings nicht unbedingt.

Um den PWM-Modus zu aktivieren müssen im Timer/Counter1 Control Register A TCCR1A die Pulsweiten-Modulatorbits PWM10 bzw. PWM11 entsprechend nachfolgender Tabelle gesetzt werden:

PWM11 PWM10 Bedeutung
0 0 PWM-Modus des Timers ist nicht aktiv.
0 1 8-Bit PWM.
1 0 9-Bit PWM.
1 1 10-Bit PWM.

Der Timer/Counter zählt nun permanent von 0 bis zur Obergrenze und wieder zurück, er wird also als sogenannter Auf-/Ab Zähler betrieben. Die Obergrenze hängt davon ab, ob wir mit 8, 9 oder 10-Bit PWM arbeiten wollen:

Auflösung Obergrenze Frequenz
8 255 fTC1 / 510
9 511 fTC1 / 1022
10 1023 fTC1 / 2046

Zusätzlich muss mit den Bits COM1A1 und COM1A0 desselben Registers die gewünschte Ausgabeart des Signals definiert werden:

COM1A1 COM1A0 Bedeutung
0 0 Keine Wirkung, Pin wird nicht geschaltet.
0 1 Keine Wirkung, Pin wird nicht geschaltet.
1 0 Nicht invertierende PWM.
Der Ausgangspin wird gelöscht beim Hochzählen und gesetzt beim Herunterzählen.
1 1 Invertierende PWM.
Der Ausgangspin wird gelöscht beim Herunterzählen und gesetzt beim Hochzählen.

Der entsprechende Befehl um beispielsweise den Timer/Counter als nicht invertierenden 10-Bit PWM zu verwenden heisst dann:

outp ((1<<PWM11)|(1<<PWM10)|(1<<COM1A1), TCCR1A);

Damit der Timer/Counter überhaupt läuft müssen wir im Control Register B TCCR1B noch den gewünschten Takt (Vorzähler) einstellen, und somit auch die Frequenz des PWM-Signals bestimmen.

CS12 CS11 CS10 Bedeutung
0 0 0 Stop. Der Timer/Counter wird gestoppt.
0 0 1 CK
0 1 0 CK / 8
0 1 1 CK / 64
1 0 0 CK / 256
1 0 1 CK / 1024
1 1 0 Externer Pin 1, negative Flanke
1 1 1 Externer Pin 1, positive Flanke

Also, um einen Takt von CK / 1024 zu generieren verwenden wir folgenden Befehl:

outp ((1<<CS12) | (1<<CS10), TCCR1B);

Jetzt muss nur noch der Vergleichswert festgelegt werden. Diesen schreiben wir in das 16-Bit Timer/Counter Output Compare Register OCR1A. Wir können dazu den von der Bibliothek zur Verfügung gestellten Befehl zum Schreiben von 16-Bit Registern verwenden, als Portadresse wird das Low-Byte des Ports verwendet.

__outw (xxx, OCR1AL);

Die folgende Grafik soll den Zusammenhang zwischen dem Vergleichswert und dem generierten PWM-Signal aufzeigen.

Ach ja, fast hätte ich's vergessen. Das generierte PWM-Signal wird am Output Compare Pin OC1 des Timers ausgegeben und leider können wir deshalb auch nur ein einzelnes PWM-Signal mit dieser Methode generieren.