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.
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.
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.
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:
3 Pins notwendig.
Genauigkeit vergleichbar mit einfacherer Lösung.
War einfach zu faul.
Der Vorteil dieser Schaltung liegt allerdings darin, dass damit direkt Spannungen gemessen werden können.
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.
In dieser Betriebsart wird der Wandler bei Bedarf vom Programm angestossen für jeweils eine Messung.
In dieser Betriebsart erfasst der Wandler permanent die anliegende Spannung und schreibt diese in das ADC Data Register.
Der ADC verfügt über eigene Register, welche hier aufgelistet werden:
ADCSR |
ADC Control and Status Register.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ADCL |
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
|
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.
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.
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.
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.
|