Der UART, Teil 1

Wir werden in zukünftigen Übungen in die Situation kommen, dass wir Informationen vom Controller auswerten bzw. anzeigen müssen wobei es vielleicht dann nicht mehr reicht, ein paar Leuchtdioden anzusteuern.
Somit brauchen wir eine Verbindung vom AVR zu unserem PC, und dies am besten über die serielle Schnittstelle.

Allgemeines zum UART

Einige AVR-Controller haben einen vollduplexfähigen UART (Universal Asynchronous Receiver and Transmitter) schon eingebaut. Dies ist eine wirklich feine Sache, haben wir doch damit schon das nötige Werkzeug, um mit anderen Geräten, welche über eine serielle Schnittstelle verfügen (insbesondere mit unserem PC), zu kommunizieren.

Übrigens: Vollduplex heisst nichts anderes, als dass der Baustein gleichzeitig senden und empfangen kann.

Der UART wird über vier separate Register angesprochen:

UCR   

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

Bit 7 6 5 4 3 2 1 0
Name RXCIE TXCIE UDRIE RXEN TXEN CHR9 RXB8 TXB8
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
RXCIE RX Complete Interrupt Enable
Wenn dieses Bit gesetzt ist wird ein UART RX Complete Interrupt ausgelöst wenn ein Zeichen vom UART empfangen wurde. Das Global Enable Interrupt Flag muss selbstverständlich auch gesetzt sein.
TXCIE TX Complete Interrupt Enable
Wenn dieses Bit gesetzt ist wird ein UART TX Complete Interrupt ausgelöst wenn ein Zeichen vom UART gesendet wurde. Das Global Enable Interrupt Flag muss selbstverständlich auch gesetzt sein.
UDRIE UART Data Register Empty Interrupt Enable
Wenn dieses Bit gesetzt ist wird ein UART Datenregister Leer Interrupt ausgelöst, wenn der UART wieder bereit ist um ein neues zu sendendes Zeichen zu übernehmen. Das Global Enable Interrupt Flag muss selbstverständlich auch gesetzt sein.
RXEN Receiver Enable
Nur wenn dieses Bit gesetzt ist arbeitet der Empfänger des UART überhaupt. Wenn das Bit nicht gesetzt ist kann der entsprechende Pin des AVR als normaler I/O-Pin verwendet werden.
TXEN Transmitter Enable
Nur wenn dieses Bit gesetzt ist arbeitet der Sender des UART überhaupt. Wenn das Bit nicht gesetzt ist kann der entsprechende Pin des AVR als normaler I/O-Pin verwendet werden.
CHR9 9 Bit Characters
Wenn dieses Bit gesetzt ist können 9 Bit lange Zeichen übertragen und empfangen werden. Das 9. Bit kann bei Bedarf als zusätzliches Stopbit oder als Paritätsbit verwendet werden. Man spricht dann von einem 11-Bit Zeichenrahmen:
1 Startbit + 8 Datenbits + 1 Stopbit + 1 Paritätsbit = 11 Bits
RXB8 Receive Data Bit 8
Wenn das vorher erwähnte CHR9-Bit gesetzt ist, dann enthält dieses Bit das 9. Datenbit eines empfangenen Zeichens.
TXB8 Transmit Data Bit 8
Wenn das vorher erwähnte CHR9-Bit gesetzt ist, dann muss in dieses Bit das 9. Bit des zu sendenden Zeichens eingeschrieben werden bevor das eigentliche Datenbyte in das Datenregister geschrieben wird.

USR   

UART Status Register.
Hier teilt uns der UART mit, was er gerade so macht.
Bit 7 6 5 4 3 2 1 0
Name RXC TXC UDRE FE OR - - -
R/W R R/W R R R R R R
Initialwert 0 0 1 0 0 0 0 0
RXC UART Receive Complete
Dieses Bit wird vom AVR gesetzt, wenn ein empfangenes Zeichen vom Empfangs-Schieberegister in das Empfangs-Datenregister transferiert wurde.
Das Zeichen muss nun schnellstmöglich aus dem Datenregister ausgelesen werden. Falls dies nicht erfolgt bevor ein weiteres Zeichen komplett empfangen wurde wird eine Überlauf-Fehlersituation eintreffen. Mit dem Auslesen des Datenregisters wird das Bit automatisch gelöscht.
TXC UART Transmit Complete
Dieses Bit wird vom AVR gesetzt, wenn das im Sende-Schieberegister befindliche Zeichen vollständig ausgegeben wurde und kein weiteres Zeichen im Sendedatenregister ansteht. Dies bedeutet also, wenn die Kommunikation vollumfänglich abgeschlossen ist.
Dieses Bit ist wichtig bei Halbduplex-Verbindungen, wenn das Programm nach dem Senden von Daten auf Empfang schalten muss. Im Vollduplexbetrieb brauchen wir dieses Bit nicht zu beachten.
Das Bit nur dann automatisch gelöscht, wenn der entsprechende Interrupthandler aufgerufen wird, ansonsten müssen wir das Bit selber löschen.
UDRE UART Data Register Empty
Dieses Bit wird vom AVR gesetzt, wenn ein Zeichen vom Sendedatenregister in das Send-Schieberegister übernommen wurde und der UART nun wieder bereit ist, ein neues Zeichen zum Senden aufzunehmen.
Das Bit wird automatisch gelöscht, wenn ein Zeichen in das Sendedatenregister geschrieben wird.
FE Framing Error
Dieses Bit wird vom AVR gesetzt, wenn der UART einen Zeichenrahmenfehler detektiert, d.h. wenn das Stopbit eines empfangenen Zeichens 0 ist.
Das Bit wird automatisch gelöscht, wenn das Stopbit des empfangenen Zeichens 1 ist.
OR OverRun
Dieses Bit wird vom AVR gesetzt, wenn unser Programm das im Empfangsdatenregister bereit liegende Zeichen nicht abholt bevor das nachfolgende Zeichen komplett empfangen wurde.
Das nachfolgende Zeichen wird verworfen.
Das Bit wird automatisch gelöscht, wenn das empfangene Zeichen in das Empfangsdatenregister transferiert werden konnte.
UDR UART Data Register.
Hier werden Daten zwischen UART und CPU übertragen. Da der UART im Vollduplexbetrieb gleichzeitig empfangen und senden kann handelt es sich hier physikalisch um 2 Register, die aber über die gleiche I/O-Adresse angesprochen werden. Je nachdem, ob ein Lese- oder ein Schreibzugriff auf den UART erfolgt wird automatisch das richtige UDR angesprochen.
UBRR UART Baud Rate Register.
In diesem Register müssen wir dem UART mitteilen, wie schnell wir gerne kommunizieren möchten. Der Wert, der in dieses Register geschrieben werden muss, errechnet sich nach folgender Formel:

UBRR = Taktfrequenz / (Baudrate * 16) - 1

Es sind Baudraten bis zu 115200 Baud und höher möglich.

Die Hardware

Der UART basiert auf normalem TTL-Pegel mit 0V (LOW) und 5V (HIGH). Die Schnittstellenspezifikation für RS232 definiert jedoch -3V ... -12V (LOW) und +3 ... +12V (HIGH). Zudem muss der Signalaustausch zwischen AVR und Partnergerät invertiert werden. Für die Anpassung der Pegel und das Invertieren der Signale gibt es fertige Schnittstellenbausteine. Der bekannteste davon ist wohl der MAX232. Allerdings kostet der auch wieder Geld und benötigt zusätzlich immerhin 4 externe Elkos.

Die in den PC eingebauten Schnittstellen vertragen ohne Klagen auch den TTL-Pegel vom AVR. Allerdings müssen wir immer noch die Signale invertieren. Im einfachtesn Fall verwenden wir dazu jeweils einen einfachen NPN-Transistor und 2 Widerstände. Näheres dazu erfahrt ihr in den folgenden Übungen.

Senden mit dem UART

Wir wollen nun Daten mit dem UART auf die serielle Schnittstelle ausgeben.
Dazu müssen wir den UART zuerst mal initialisieren. Dazu setzen wir je nach gewünschter Funkstionsweise die benötigten Bits im UART Control Register.
Da wir vorerst nur senden möchten und (noch) keine Interrupts auswerten wollen gestaltet sich die Initialisierung wirklich sehr einfach, da wir lediglich das Transmitter Enable Bit setzen müssen:

outp ((1 << TXEN), UCR);

Nun müssen wir noch die Baudrate festlegen. Gemäss unserer Formel brauchen wir dazu die Taktfrequenz des angeschlossenen Oszillators bzw. Quarz in die Formel einzufügen und das Resultat der Berechnung in das Baudratenregister des UART einzuschreiben:

#define F_CPU 4000000       // Zum Beispiel 4Mhz-Quarz
#define UART_BAUD_RATE 9600 // Wir versuchen mal mit 9600 Baud

outp (F_CPU / (UART_BAUD_RATE * 16L) - 1, UBRR);

Um nun ein Zeichen auf die Schnittstelle auszugeben müssen wir dasselbe lediglich in das UART Daten Register schreiben.

outp ('x', UDR);    // Schreibt das Zeichen x auf die Schnittstelle

Schreiben einer Zeichenkette (String)

Wenn wir nun mehrere, aufeinanderfolgende Zeichen auf die Schnittstelle ausgeben wollen, dann müssen wir mit dem nächsten Zeichen warten, bis das vorhergehende jeweils aus dem Datenregister in das Sende-Schieberegister übernommen wurde.
Zu diesem Zweck können wir uns für's Erste eine einfache Funktion basteln:

void SendeString (char *szBuf)
{
    while (*szBuf++) {
        outp (*szBuf, UDR);
        loop_until_bit_is_set (USR, UDRE);
    }
}

Das ist jetzt insofern etwas kritisch, dass wir während dem Senden des Strings nicht mehr auf andere Ereignisse reagieren können. Dies wird aber ohnehin erst dann ein Thema, wenn wir mit unserem Programm gleichzeitig Senden und Empfangen wollen.
Wir werden zu einem späteren Zeitpunkt Lösung für dieses Problem finden.