Zugriff auf Ports

Alle Ports der AVR-Controller werden über Register gesteuert. Dazu sind jedem Port 3 Register zugeordnet:

DDRx   

Datenrichtungsregister für Port x (x entspricht A, B, C oder D).

PORTx   

Datenregister für Port x (x entspricht A, B, C oder D).
Dieses Register wird verwendet, um die Ausgänge eines Ports anzusteuern.

Wird ein Port als Eingang geschaltet, so können mit diesem Register die internen Pull-Up Widerstände aktiviert oder deaktiviert werden (1 = aktiv).

PINx Eingangadresse für Port x (x entspricht A, B, C oder D).
Dies ist kein eigentliches Register, sondern definiert lediglich eine Adresse, in welcher der aktuelle Zustand der Eingangspins eines Ports vom Controller abgelegt werden.
Nichtsdestotrotz erfolgt der Zugriff auf den Zustand der Pins genau so, wie wenn PINx ein normales Register wäre.
Die Adresse kann nur gelesen und nicht beschrieben werden.

Datenrichtung bestimmen

Zuerst muss die Datenrichtung der verwendeten Pins bestimmt werden.
Um dies zu erreichen wird das Datenrichtungsregister des entsprechenden Ports beschrieben.

outp (<bits>, <port>);

Für jeden Pin, der als Ausgang verwendet werden soll, muss dabei das entsprechende Bit auf dem Port gesetzt werden. Soll der Pin als Eingang verwendet werden muss das entsprechende Bit gelöscht sein.

Wollen wir also beispielsweise Pin 0 bis 4 von Port B als Ausgänge definieren so schreiben wir folgende Zeile:

outp (0x1F, DDRB);

// Binär 00011111 = Hexadezimal 1F

Die Pins 5 bis 7 werden als Eingänge geschaltet.

Ganze Ports

Um einen ganzen Port als Ausgang zu definieren, kann der folgende Befehl verwendet werden:

outp (0xFF, DDRB);

Im Beispiel wird der Port B als Ganzes als Ausgang geschaltet.

Digitale Signale

Am einfachsten ist es, digitale Signale mit dem Microcontroller zu erfassen bzw. auszugeben.

Ausgänge

Wir wollen nun einen als Ausgang definierten Pin auf Logisch 1 setzen.
Dazu schreiben wir den entsprechenden Wert in das Portregister des entsprechenden Ports.

Mit dem Befehl

outp (0x04, PORTB);

wird also der Ausgang an Pin 2 gesetzt (Beachte, dass die Bits immer von 0 an gezählt werden, das niederwertigste Bit ist also Bit 0 und nicht etwa Bit 1).

Man beachte bitte, dass bei der Verwendung der outp-Funktion immer alle Pins gleichzeitig angegeben werden. Man sollte also zuerst den aktuellen Wert des Ports einlesen und das Bit des gewünschten Ports in diesen Wert einfliessen lassen.

Es gibt jedoch in der AVRGCC-Bibliothek Funktionen, welche dies selbständig machen.
Die Funktionen lassen sich wie folgt verwenden:

sbi (PORTB, 2);    // Setzt Pin 2 auf Logisch 1 (EIN)
cbi (PORTB, 2);    // Setzt Pin 2 auf Logisch 0 (AUS)

Eingänge (Wie kommen Signale in den µC)

Die digitalen Eingangssignale können auf verschiedene Arten zu unserer Logik gelangen.

Signalkopplung

Am einfachsten ist es , wenn die Signale direkt aus einer anderen digitalen Schaltung übernommen werden können. Hat der Ausgang der entsprechenden Schaltung TTL-Pegel dann können wir sogar direkt den Ausgang der Schaltung mit einem Eingangspin von unserem Controller verbinden.
Hat der Ausgang der anderen Schaltung keinen TTL-Pegel so müssen wird den Pegel über entsprechende Hardware, z.B. einen Optokoppler, anpassen.
Die Masse der beiden Schaltungen muss selbstverständlich miteinander verbunden werden.

Der Software selber ist es natürlich letztendlich egal, wie das Signal eingespeist wird. Wir können ja ohnehin lediglich prüfen, ob an einem Pin unserer Controllers eine logische 1 (Vcc) oder eine logische 0 (Masse) anliegt.

Die Abfrage der Zustände der Portpins erfolgt über den Befehl

inp (<port>)

Wobei es hier sehr wichtig ist, als Portadresse nicht etwa das Portregister PORTx zu verwenden, sondern die Porteingangsadresse PINx. Dies ist ein oft gemachter Fehler!
Wollen wir also die aktuellen Signalzustände von Port D abfragen und in einer Variable namens bPortD abspeichern so schreiben wir dazu folgende Befehlszeile:

bPortD = inp (PIND);

Tasten und Schalter

Der Anschluss mechanischer Kontakte an den Microcontroller gestaltet sich ebenfalls ganz einfach, wobei wir zwei unterschiedliche Methoden unterscheiden müssen (Active Low und Active High):

Active Low

Active High

Bei dieser Methode wird der Kontakt zwischen den Eingangspin des Controllers und Masse geschaltet.
Damit bei offenem Schalter der Controller kein undefiniertes Signal bekommt wird zwischen die Versorgungsspannung und den Eingangspin ein sogenannter Pull-Up Widerstand geschaltet. Dieser dient dazu, den Pegel bei geöffnetem Schalter auf logisch 1 zu ziehen.
Der Widerstandswert des Pull-Up Widerstands ist an sich nicht kritisch. Es muss jedoch beachtet werden, dass über den Widerstand ein Strom in den Eingang fliesst, also sollte er nicht zu klein gewählt werden um den Controller nicht zu zerstören. Wird er allerdings zu hoch gewählt ist die Wirkung eventuell nicht gegeben. Als üblicher Wert haben sich 10 Kiloohm eingebürgert.

Die AVR's haben sogar an den meisten Pins softwaremässig zuschaltbare interne Pull-Up Widerstände, welche wir natürlich auch verwenden können.

Hier wird der Kontakt zwischen die Versorgungsspannung und Masse geschaltet.
Damit bei offener Schalterstellung kein undefiniertes Signal am Controller ansteht wird zwischen den Eingangspin und die Masse ein Pull-Down Widerstand geschaltet. Dieser dient dazu, den Pegel bei geöffneter Schalterstellung auf logisch 0 zu halten,

Pull-Up Widerstände aktivieren

Die internen Pull-Up Widerstände von Vcc zu den einzelnen Portpins werden über das Register PORTx aktiviert bzw. deaktiviert, wenn ein Pin als Eingang geschaltet ist.
Wird der Wert des entsprechenden Portpins auf 1 gesetzt so ist der Pull-Up Widerstand aktiviert. Bei einem Wert von 0 ist der Pull-Up Widerstand nicht aktiv.
Man sollte jeweils entweder den internen oder einen externen Pull-Up Widerstand verwenden, aber nicht beide zusammen.

outp (0x00, DDRD);        // Port D als Eingang schalten
outp (0x00, PORTD);    // Interne Pull-Up Widerstände aus

Im Beispiel wird der gesamte Port D als Eingang geschaltet und alle Pull-Up Widerstände deaktiviert.

Tastenentprellung

Nun haben alle mechanischen Kontakte, sei es von Schaltern, Tastern oder auch von Relais, die unangenehme Eigenschaft zu prellen. Dies bedeutet, dass beim Schliessen des Kontaktes derselbe nicht direkt Kontakt herstellt, sondern mehrfach ein- und ausschaltet bis zum endgültigen Herstellen des Kontaktes.
Soll nun mit einem schnellen Microcontroller gezählt werden, wie oft ein solcher Kontakt geschaltet wird, dann haben wir ein Problem, weil das Prellen als mehrfache Impulse gezählt wird.
Diesem Phänomen muss beim Schreiben des Programms unbedingt Rechnung getragen werden.

Analog

Leider können wir mit unseren Controllern keine analogen Werte direkt ausgeben oder einlesen. Dazu müssen wir Umwege gehen, doch dies wird in einem späteren Kapitel behandelt.

16-Bit Portregister (ADC, ICR1, OCR1, TCNT1)

Einige der Portregister in den AVR-Controllern sind 16 Bit breit, Man spricht dann von einem Wort. Für den Zugriff auf diese Register stellt die Funktionsbibliothek zwei spezielle Befehle zur Verfügung:

__inw (<port>);

Liest den aktuellen Wert eines 16-Bit Portregisters ein.

__outw (<wert>, <port>);

Schreibt einen Wert in ein 16-Bit Portregister.