AVR-Tutorial: UART: Unterschied zwischen den Versionen

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche
K (→‎Senden von Zeichen: 2 URLs aktualisiert)
 
(16 dazwischenliegende Versionen von 11 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
Wie viele andere Controller besitzen die meisten AVRs einen '''[[UART]]''' ('''U'''niversal '''A'''synchronous '''R'''eceiver and '''T'''ransmitter). Das ist eine serielle Schnittstelle, die meistens zur Datenübertragung zwischen Mikrocontroller und PC genutzt wird. Zur Übertragung werden zwei Pins am Controller benötigt: '''TXD''' und '''RXD'''. Über '''TXD''' ("Transmit Data") werden Daten gesendet, '''RXD''' ("Receive Data") dient zum Empfang von Daten.
Wie viele andere Controller besitzen die meisten AVRs einen '''[[UART]]''' (<span style="text-decoration:underline">U</span>niversal <span style="text-decoration:underline">A</span>synchronous <span style="text-decoration:underline">R</span>eceiver and <span style="text-decoration:underline">T</span>ransmitter). Das ist eine serielle Schnittstelle, die meistens zur Datenübertragung zwischen Mikrocontroller und PC genutzt wird. Dafür werden zwei Pins am Controller benötigt: '''TXD''' und '''RXD'''. Über TXD (''Transmit Data'') werden Daten gesendet, RXD (''Receive Data'') dient zum Empfang von Daten.


== Hardware ==
== Hardware ==


Um den UART des Mikrocontrollers zu verwenden, muss der Versuchsaufbau um folgende Bauteile erweitert werden:  
Um den UART des Mikrocontrollers zu verwenden, muss der Versuchsaufbau um folgende Bauteile erweitert werden:


[[Bild:AVR-RS232.png|framed|right|640px| UART/MAX232 Standardbeschaltung ]]
[[Bild:AVR-RS232.png|framed|right|640px|UART/MAX232-Standardbeschaltung]]


Auf dem Board vom [http://shop.mikrocontroller.net/ Shop] sind diese Bauteile bereits enthalten, man muss nur noch die Verbindungen zwischen MAX232 (IC2) und AVR herstellen wie im [http://shop.mikrocontroller.net/images/avrplat28-app.jpg Bild] zu sehen.
Auf vielen Evaluation-Boards sind diese Bauteile bereits enthalten, man muss ggf. nur noch die Verbindungen zwischen MAX232 und AVR herstellen<!--wie im [http://shop.mikrocontroller.net/images/avrplat28-app.jpg Bild] (toter Link) zu sehen-->.


* Der MAX232 ist ein [[Pegelwandler]], der die -12V/+12V Signale an der seriellen Schnittstelle des PCs zu den 5V/0V des AVRs kompatibel macht.
* Der MAX232 ist ein [[Pegelwandler]], der die −12/+12-Volt-Signale an der seriellen Schnittstelle des PCs zu den 5/0&nbsp;Volt des AVRs kompatibel macht.
* C1 ist ein kleiner Keramikkondensator, wie er immer wieder zur Entkopplung der Versorgungsspannungen an digitalen ICs verwendet wird.
* C1 ist ein kleiner Elektrolyt-, Tantal- oder Keramikkondensator, wie er immer wieder zur Entkopplung der Versorgungsspannungen an digitalen ICs verwendet wird.
* Die vier Kondensatoren C2..C5 sind Elektrolytkondensatoren (Elkos). Auf die richtige Polung achten! Minus ist der Strich auf dem Gehäuse. Der exakte Wert ist hier relativ unkritisch, in der Praxis sollte alles von ca. 1µF bis 47µF mit einer Spannungsfestigkeit von 16V und höher funktionieren.
* Die vier Kondensatoren C2…C5 sind Elektrolyt-, Tantal- oder Keramikkondensatoren (siehe Datenblatt der verwendeten MAX232-Version!). Falls Elkos oder Tantals verwendet werden, auf die richtige Polung achten! Der exakte Wert ist hier relativ unkritisch, in der Praxis sollte alles von ca. 1&nbsp;µF bis 47&nbsp;µF mit einer Spannungsfestigkeit von 16&nbsp;V und höher funktionieren.
* X1 ist ein weiblicher 9-poliger SUB-D-Verbinder.
* X1 ist ein weiblicher 9-poliger SUB-D-Verbinder.
* Die Verbindung zwischen PC und Mikrocontroller erfolgt über ein 9-poliges Modem-Kabel (also ein ''Verlängerungskabel'', '''kein [http://de.wikipedia.org/wiki/Nullmodem-Kabel Nullmodem]-Kabel!)''', das an den seriellen Port des PCs angeschlossen wird. Bei einem Modem-Kabel sind die Pins 2 und 3 des einen Kabelendes mit den Pins 2 und 3 des anderen Kabelendes durchverbunden. Bei einem Nullmodem-Kabel sind die Leitungen gekreuzt, sodass Pin 2 von der einen Seite mit Pin 3 auf der anderen Seite verbunden ist und umgekehrt.
* Die Verbindung zwischen PC und Mikrocontroller erfolgt über ein 9-poliges Modem-Kabel (also ein ''Verlängerungskabel'', '''kein [https://de.wikipedia.org/wiki/Nullmodem-Kabel Nullmodem-Kabel]!'''), das an den seriellen Port des PCs angeschlossen wird. Bei einem Modem-Kabel sind die Pins 2 und 3 des einen Kabelendes mit den Pins 2 und 3 des anderen Kabelendes durchverbunden. Bei einem Nullmodem-Kabel sind die Leitungen gekreuzt, sodass Pin 2 von der einen Seite mit Pin 3 auf der anderen Seite verbunden ist und umgekehrt.
* Als Faustregel kann man annehmen: Befinden sich an den beiden Enden des Kabels die gleiche Art von Anschlüssen (Männchen = Stecker; Weibchen = Buchse), dann benötigt man ein gekreuztes, also ein Nullmodem-Kabel. Am PC-Anschluss selbst befindet sich ein Stecker, also ein Männchen, sodaß am Kabel auf dieser Seite eine Buchse (also ein Weibchen) sitzen muss. Da am AVR laut obigem Schaltbild eine Buchse verbaut wird, muss daher an diesem Ende des Kabels ein Stecker sitzen. Das Kabel hat daher an einem Ende einen Stecker und am anderen Ende eine Buchse und ist daher ein normales Modem-Kabel ( = nicht gekreuzt).
* Als Faustregel kann man annehmen: Befinden sich an den beiden Enden des Kabels die gleiche Art von Anschlüssen (Männchen = Stecker; Weibchen = Buchse), dann benötigt man ein gekreuztes, also ein Nullmodem-Kabel. Am PC-Anschluss selbst befindet sich ein Stecker, also ein Männchen, sodaß am Kabel auf dieser Seite eine Buchse (also ein Weibchen) sitzen muss. Da am AVR laut obigem Schaltbild eine Buchse verbaut wird, muss daher an diesem Ende des Kabels ein Stecker sitzen. Das Kabel hat somit an einem Ende einen Stecker und am anderen Ende eine Buchse und ist also ein normales Modem-Kabel (= nicht gekreuzt).


[[Bild:USART_Kabel.gif|framed|center| Kabelbeschaltungen]]
[[Bild:USART_Kabel.gif|framed|center|Kabelbeschaltungen]]


== Software ==
== Software ==


=== UART konfigurieren? ===
=== UART konfigurieren ===


Als erstes muss die gewünschte Baudrate im Register '''UBRR''' festgelegt werden. Der in dieses Register zu schreibende Wert errechnet sich nach der folgenden Formel:  
Als erstes muss die gewünschte Baudrate im Register '''UBRR''' festgelegt werden. Der in dieses Register zu schreibende Wert errechnet sich nach der folgenden Formel:


<math>\text{UBRR} = \frac {\text{Taktfrequenz (in Hz)}} { 16 \cdot \text{Baudrate} } - 1</math>
<math>\text{UBRR} = \frac{\text{Taktfrequenz (in Hz)}} { 16 \cdot \text{Baudrate} } - 1</math>


Beim AT90S4433 kann man den Wert direkt in das Register '''UBRR''' laden, beim ATmega8 gibt es für '''UBRR''' zwei Register: '''UBRRL''' (Low-Byte) und '''UBRRH''' (High-Byte). Bei Baudraten über etwa 3900 Bit/s (gilt nur bei Verwendung eines Takts von 16 MHz) steht in '''UBRRH''' 0, da der berechnete Wert kleiner als 256 ist und somit in '''UBRRL''' alleine passt. Beachtet werden muss, dass das Register '''UBRRH''' vor dem Register '''UBRRL''' beschrieben werden muss. Der Schreibzugriff auf '''UBRRL''' löst das Neusetzen des internen Taktteilers aus.
Beim AT90S4433 kann man den Wert direkt in das Register UBRR laden, beim ATmega8 gibt es für UBRR zwei Register: '''UBRRL''' (Low-Byte) und '''UBRRH''' (High-Byte). Bei Baudraten über etwa 3.900&nbsp;Bit/s (gilt nur bei Verwendung eines Takts von 16&nbsp;MHz) steht in UBRRH eine 0, da der berechnete Wert kleiner als 256 ist und somit in UBRRL alleine passt. Beachtet werden muss, dass das Register UBRRH ''vor'' dem Register UBRRL beschrieben werden muss. Der Schreibzugriff auf UBRRL löst das Neusetzen des internen Taktteilers aus.


<div style="border: 1px solid gray; padding: 1ex">
<span style="color:red">'''Wichtiger Hinweis 1'''</span>


 
Es empfiehlt sich, statt der oben genannten Formel die '''Formel der Codebeispiele''' zu verwenden:
<span style="color:#ff0000;">'''WICHTIGER HINWEIS 1'''</span>
 
Es empfiehlt sich statt der oben genannten Formel, die Formel der Codebeispiele zu verwenden:


<math>\text{UBRR} = \frac{\text{Taktfrequenz (in Hz)} + (\text{Baudrate} \cdot 8)}{(\text{Baudrate} \cdot 16)}-1 \quad \left( = \frac{ \text{Taktfrequenz (in Hz)} }{ 16 \cdot \text{Baudrate} } -0.5 \right)</math>
<math>\text{UBRR} = \frac{\text{Taktfrequenz (in Hz)} + (\text{Baudrate} \cdot 8)}{(\text{Baudrate} \cdot 16)}-1 \quad \left( = \frac{ \text{Taktfrequenz (in Hz)} }{ 16 \cdot \text{Baudrate} } -0.5 \right)</math>


<b>Beispiel:</b> Bei einem ATMega mit 16MHz und 115200 Baud ist der Wert laut Datenblatt UBBRL=8. Rechnet man mit der Formel UBRRL=(F_CPU / (UART_BAUDRATE* 16L) - 1) ergibt sich ein Wert von 7,680555 und im UBRRL Register steht somit eine <b>7 statt einer 8</b>. Die Verwendung der Formel aus dem Codebeispiel ergibt 8.180555 und im UBRRL Register steht somit der richtige Wert - nämlich 8
'''Beispiel:''' Bei einem ATMega mit 16&nbsp;MHz und 115200&nbsp;Baud ist der Wert laut Datenblatt UBBRL=8. Rechnet man mit der Formel UBRRL=(F_CPU / (UART_BAUDRATE* 16L) - 1), ergibt sich ein Wert von 7,680555 und im UBRRL-Register steht somit eine '''7 statt einer 8'''. Die Verwendung der Formel aus dem Codebeispiel ergibt 8,180555 und im UBRRL-Register steht somit der richtige Wert nämlich 8.
 
</div>
----


<span style="color:#ff0000;">'''WICHTIGER HINWEIS 2'''</span>
<div style="border: 1px solid gray; padding: 1ex">
<span style="color:red">'''Wichtiger Hinweis 2'''</span>


Auf Grund permanent wiederkehrender Nachfrage sei hier '''AUSDRÜCKLICH''' darauf hingewiesen, dass bei Verwendung des UART im asynchronen Modus dringend ein Quarz oder Ouarzoszillator verwendet werden sollte! Der interne RC-Oszillator der AVRs ist recht ungenau! Damit kann es in Ausnahmefällen funktionieren, muss es aber nicht! Auch ist der interne Oszillator temperaturempfindlich. Damit hat man dann den schönen Effekt, dass eine UART-Schaltung die im Winter noch funktionierte, im Sommer den Dienst verweigert.
Aufgrund permanent wiederkehrender Nachfrage sei hier ausdrücklich darauf hingewiesen, dass bei Verwendung des UART im asynchronen Modus dringend ein '''Quarz oder Quarzoszillator''' verwendet werden sollte! Der interne RC-Oszillator der AVRs ist recht ungenau! Damit kann es in Ausnahmefällen funktionieren, muss es aber nicht! Auch ist der interne Oszillator temperaturempfindlich. Damit hat man dann den schönen Effekt, dass eine UART-Schaltung, die im Winter noch funktionierte, im Sommer den Dienst verweigert.


Außerdem muss bei der Berechnung von '''UBRR''' geprüft werden, ob mit der verwendeten Taktfrequenz die gewünschte Baudrate mit einem Fehler von <1% generiert werden kann. Das Datenblatt bietet hier sowohl die Formel als auch Tabellen unter der Überschrift des U(S)ART an.
Außerdem muss bei der Berechnung von UBRR geprüft werden, ob mit der verwendeten Taktfrequenz die gewünschte Baudrate mit einem Fehler von &lt;1 % generiert werden kann. Das Datenblatt bietet hier sowohl die Formel als auch Tabellen unter der Überschrift des U(S)ART an.


<math> \text{Fehler}_{\text{Baudrate}}[%] = \left( \frac{\text{UBRR}_{\text{gerundet}}+1}{\text{UBRR}_{\text{genau}}+1} -1 \right) \cdot 100</math>
<math>\frac{\text{Fehler}_{\text{Baudrate}}}{\text{%}} = \left( \frac{\text{UBRR}_{\text{gerundet}}+1}{\text{UBRR}_{\text{genau}}+1} -1 \right) \cdot 100</math>


Siehe auch [[Baudratenquarz]]
Siehe auch [[Baudratenquarz]].


Wer es ganz einfach haben will, nimmt die folgenden Macros. Die rechnen sogar den Fehler aus und brechen die Assemblierung ggf. ab. Das ist dann praktisch idiotensicher.
Wer es ganz einfach haben will, nimmt die folgenden Macros. Die rechnen sogar den Fehler aus und brechen die Assemblierung ggf. ab. Das ist dann praktisch idiotensicher.


<avrasm>
<syntaxhighlight lang="asm">
.equ F_CPU = 4000000                            ; Systemtakt in Hz
.equ F_CPU = 4000000                            ; Systemtakt in Hz
.equ BAUD  = 9600                              ; Baudrate
.equ BAUD  = 9600                              ; Baudrate
Zeile 64: Zeile 63:
   .error "Systematischer Fehler der Baudrate grösser 1 Prozent und damit zu hoch!"
   .error "Systematischer Fehler der Baudrate grösser 1 Prozent und damit zu hoch!"
.endif
.endif
</avrasm>
</syntaxhighlight>


Wer dennoch den internen RC-Oszillator verwenden will, muss diesen kalibrieren. Näheres findet man dazu im Datenblatt, Stichwort Register OSCCAL.
Wer dennoch den internen RC-Oszillator verwenden will, muss diesen kalibrieren. Näheres findet man dazu im Datenblatt, Stichwort Register OSCCAL.
----
</div>


Um den Sendekanal des UART zu aktivieren, muss das Bit '''TXEN''' im UART Control Register '''UCSRB''' auf 1 gesetzt werden.
Um den Sendekanal des UART zu aktivieren, muss das Bit '''TXEN''' im UART-Control-Register '''UCSRB''' auf 1 gesetzt werden.


Danach kann das zu sendende Byte in das Register '''UDR''' eingeschrieben werden - vorher muss jedoch sichergestellt werden, dass das Register leer ist, die vorhergehende Übertragung also schon abgeschlossen wurde. Dazu wird getestet, ob das Bit '''UDRE''' ("UART Data Register Empty") im Register '''UCSRA''' auf 1 ist.
Danach kann das zu sendende Byte in das Register '''UDR''' eingeschrieben werden vorher muss jedoch sichergestellt werden, dass das Register leer ist, die vorhergehende Übertragung also schon abgeschlossen wurde. Dazu wird getestet, ob das Bit '''UDRE''' (''UART Data Register Empty'') im Register '''UCSRA''' gleich 1 ist.


Genaueres über die UART-Register findet man im Datenblatt des Controllers.
Genaueres über die UART-Register findet man im Datenblatt des Controllers.


An dieser Stelle sei noch folgendes angemerkt: Das '''UDRE'''-Bit sagt nichts darüber aus, ob der Controller immer noch damit beschäftigt ist, Daten zu senden. Da das Senderegister mehrfach gepuffert ist, wird '''UDRE''' bereits gesetzt, obwohl das letzte Zeichen den AVR noch nicht komplett verlassen hat. Dies kann insbesondere bei der Verwendung von Sleep-Modes ein Problem werden, wenn der Controller schlafen gelegt wird, bevor das letzte Zeichen versendet wurde, da dies gezwungenermassen zu einem Frame-Error beim Empfänger führen wird. Um sicher zu gehen, dass der UART nicht mehr beschäftigt ist, kann das Bit '''TXC''' ("UART Transmit complete") getestet werden. Dieses wird jedoch wirklich erst nach dem Senden eines Zeichens gesetzt, beinhaltet also auch nach dem Systemstart eine 0, obwohl der Controller nichts sendet.
An dieser Stelle sei noch folgendes angemerkt: Das UDRE-Bit sagt nichts darüber aus, ob der Controller immer noch damit beschäftigt ist, Daten zu senden. Da das Senderegister mehrfach gepuffert ist, wird UDRE bereits gesetzt, obwohl das letzte Zeichen den AVR noch nicht komplett verlassen hat. Dies kann insbesondere bei der Verwendung von Sleep-Modes ein Problem werden, wenn der Controller schlafen gelegt wird, bevor das letzte Zeichen versendet wurde, da dies gezwungenermaßen zu einem Frame-Error beim Empfänger führen wird. Um sicher zu gehen, dass der UART nicht mehr beschäftigt ist, kann das Bit '''TXC''' (''UART Transmit Complete'') getestet werden. Dieses wird jedoch wirklich erst nach dem Senden eines Zeichens gesetzt, beinhaltet also auch nach dem Systemstart eine 0, obwohl der Controller nichts sendet.


Der ATmega8 bietet noch viele weitere Optionen zur Konfiguration des UARTs, aber für die Datenübertragung zum PC sind im Normalfall keine anderen Einstellungen notwendig.
Der ATmega8 bietet noch viele weitere Optionen zur Konfiguration des UARTs, aber für die Datenübertragung zum PC sind im Normalfall keine anderen Einstellungen notwendig.
Zeile 81: Zeile 80:
=== Senden von Zeichen ===
=== Senden von Zeichen ===


Das Beispielprogramm überträgt die Zeichenkette "Test!" in einer Endlosschleife an den PC.
Das Beispielprogramm überträgt die Zeichenkette „Test!in einer Endlosschleife an den PC.
 
'''Hinweis'''
 
Wenn man das nachfolgende Programm laufen lässt und Hyperterminal startet, scheint es problemlos zu funktionieren. Wenn man aber das RS232 Kabel zwischenzeitlich abzieht und wieder ansteckt wird es oft passieren, dass nur noch wirre Zeichen auf dem PC erscheinen. Das liegt daran, dass der PC aus einem ununterbrochen Zeichenstrom nicht den Anfang eines Zeichens erkennen kann. Darum muss in solchen Fällen periodisch eine kleine Pause von der Länge mindestens eines Zeichens eingelegt werden, damit der PC sich wieder synchronisieren kann.


'''Hinweis:''' Wenn man das nachfolgende Programm laufen lässt und Hyperterminal startet, scheint es problemlos zu funktionieren. Wenn man aber das RS232-Kabel zwischenzeitlich abzieht und wieder ansteckt, wird es oft passieren, dass nur noch wirre Zeichen auf dem PC erscheinen. Das liegt daran, dass der PC aus einem ununterbrochenen Zeichenstrom nicht den Anfang eines Zeichens erkennen kann. Darum muss in solchen Fällen periodisch eine kleine Pause von der Länge mindestens eines Zeichens eingelegt werden, damit der PC sich wieder synchronisieren kann.


Die folgenden Beispiele sind für den ATmega8 geschrieben.
Die folgenden Beispiele sind für den ATmega8 geschrieben.


<avrasm>
<syntaxhighlight lang="asm">
.include "m8def.inc"
.include "m8def.inc"


.def temp    = r16                              ; Register für kleinere Arbeiten
.def temp    = r16                              ; Register für kleinere Arbeiten
.def zeichen = r17                              ; in diesem Register wird das Zeichen an die
.def zeichen = r17                              ; In diesem Register wird das Zeichen an die
                                                 ; Ausgabefunktion übergeben
                                                 ; Ausgabefunktion übergeben.


.equ F_CPU = 4000000                            ; Systemtakt in Hz
.equ F_CPU = 4000000                            ; Systemtakt in Hz
Zeile 128: Zeile 124:
     out    UCSRC, temp
     out    UCSRC, temp


     sbi    UCSRB,TXEN                 ; TX aktivieren
     sbi    UCSRB, TXEN                 ; TX aktivieren


loop:
loop:
Zeile 149: Zeile 145:


serout:
serout:
     sbis    UCSRA,UDRE                 ; Warten bis UDR für das nächste
     sbis    UCSRA, UDRE                 ; Warten bis UDR für das nächste
                                         ; Byte bereit ist
                                         ; Byte bereit ist
     rjmp    serout
     rjmp    serout
Zeile 157: Zeile 153:
; kleine Pause zum Synchronisieren des Empfängers, falls zwischenzeitlich
; kleine Pause zum Synchronisieren des Empfängers, falls zwischenzeitlich
; das Kabel getrennt wurde
; das Kabel getrennt wurde
                                   
 
sync:
sync:
     ldi    r16,0
     ldi    r16, 0
sync_1:
sync_1:
     ldi    r17,0
     ldi    r17, 0
sync_loop:
sync_loop:
     dec    r17
     dec    r17
     brne    sync_loop
     brne    sync_loop
     dec    r16
     dec    r16
     brne    sync_1
     brne    sync_1
     ret
     ret
</avrasm>
</syntaxhighlight>


<div style="float:right; argin:1em">
<div style="float:right; margin:1em">
http://www.mikrocontroller.net/images/hyperterminal.gif
https://www.mikrocontroller.net/images/hyperterminal.gif
</div>
</div>
Der Befehl '''rcall serout''' ruft ein kleines Unterprogramm auf, das zuerst wartet bis das Datenregister '''UDR''' von der vorhergehenden Übertragung frei ist, und anschließend das in zeichen (=r17) gespeicherte Byte an '''UDR''' ausgibt.  
Der Befehl <code>rcall serout</code> ruft ein kleines Unterprogramm auf, das zuerst wartet, bis das Datenregister UDR von der vorhergehenden Übertragung frei ist, und anschließend das in <code>zeichen</code> (=&nbsp;r17) gespeicherte Byte an UDR ausgibt.


Bevor <i>serout</i> aufgerufen wird, wird zeichen jedesmal mit dem ASCII-Code des zu übertragenden Zeichens geladen (so wie in Teil 4 bei der LCD-Ansteuerung). Der Assembler wandelt Zeichen in einfachen Anführungsstrichen automatisch in den entsprechenden ASCII-Wert um. Nach dem Wort "Test!" werden noch die Codes 10 (New Line) und 13 (Carriage Return) gesendet, um dem Terminalprogramm mitzuteilen, dass eine neue Zeile beginnt.
Bevor <code>serout</code> aufgerufen wird, wird <code>zeichen</code> jedesmal mit dem ASCII-Code des zu übertragenden Zeichens geladen (so wie in Teil&nbsp;4 bei der LCD-Ansteuerung). Der Assembler wandelt Zeichen in einfachen Anführungsstrichen automatisch in den entsprechenden ASCII-Wert um. Nach dem Wort „Test!werden noch die Codes 10 (''Line Feed'' oder ''New Line'', Zeilenvorschub) und 13 (''Carriage Return'', Wagenrücklauf) gesendet, um dem Terminalprogramm mitzuteilen, dass eine neue Zeile beginnt.


Eine Übersicht aller ASCII-Codes gibt es auf [http://www.asciitable.com/ www.asciitable.com].
Eine Übersicht aller ASCII-Codes gibt es auf [https://4n7.de/ascii.html 4n7.de/ascii.html] oder [https://de.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange#ASCII-Tabelle Wikipedia].


Die Berechnung der Baudrate wird übrigens nicht im Controller während der Programmausführung durchgeführt, sondern schon beim Assemblieren, wie man beim Betrachten der Listingdatei feststellen kann.  
Die Berechnung der Baudrate erfolgt übrigens nicht im Controller während der Programmausführung, sondern schon beim Assemblieren, wie man beim Betrachten der Listingdatei feststellen kann.


Zum Empfang muss auf dem PC ein Terminal-Programm wie z.&nbsp;B. HyperTerminal gestartet werden. Der folgende Screenshot zeigt, welche Einstellungen im Programm vorgenommen werden müssen:
Zum Empfang muss auf dem PC ein Terminal-Programm wie z.&nbsp;B. [https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html PuTTY] gestartet werden. Der nebenstehende Screenshot zeigt, welche Einstellungen im Programm vorgenommen werden müssen.


Linux-Benutzer können das entsprechende Device (z.&nbsp;B. /dev/ttyS0) mit stty konfigurieren und mit cat die empfangenen Daten anzeigen oder ein Terminalprogramm wie minicom nutzen.
Linux-Benutzer können das entsprechende Device (z.&nbsp;B. <code>/dev/ttyS0</code>) mit <code>stty</code> konfigurieren und mit <code>cat</code> die empfangenen Daten anzeigen oder ein Terminalprogramm wie minicom nutzen.


Alternativ kann unter Windows und Linux [http://www.der-hammer.info/terminal/ HTerm] genutzt werden. (Freeware)
Alternativ kann unter Windows und Linux [https://www.der-hammer.info/pages/terminal.html HTerm] genutzt werden (Freeware).


{{Clear}}
{{Clear}}
Zeile 191: Zeile 187:
=== Senden von Zeichenketten ===
=== Senden von Zeichenketten ===


Eine bequemere Methode um längere Zeichenketten (Strings) zu übertragen ist hier zu sehen. Dabei werden die Zeichenketten im Flash gespeichert. Als Abschluss des Strings wird der Wert 0x00 genutzt, so wie auch in der Programmiersprache C.  
Eine bequemere Methode, um längere Zeichenketten (Strings) zu übertragen, ist hier zu sehen. Dabei werden die Zeichenketten im Flash gespeichert. Als Abschluss des Strings wird der Wert 0x00 genutzt, so wie auch in der Programmiersprache C.


<avrasm>
<syntaxhighlight lang="asm">
.include "m8def.inc"
.include "m8def.inc"


.def temp    = r16                              ; Register für kleinere Arbeiten
.def temp    = r16                              ; Register für kleinere Arbeiten
.def zeichen = r17                              ; in diesem Register wird das Zeichen an die
.def zeichen = r17                              ; In diesem Register wird das Zeichen an die
                                                 ; Ausgabefunktion übergeben
                                                 ; Ausgabefunktion übergeben.


.equ F_CPU = 4000000                            ; Systemtakt in Hz
.equ F_CPU = 4000000                            ; Systemtakt in Hz
Zeile 233: Zeile 229:
     out    UCSRC, temp
     out    UCSRC, temp


     sbi    UCSRB,TXEN                     ; TX aktivieren
     sbi    UCSRB, TXEN                     ; TX aktivieren


loop:
loop:
     ldi    zl,low(my_string*2);           ; Z Pointer laden
     ldi    zl, low(my_string*2);           ; Z Pointer laden
     ldi    zh,high(my_string*2);
     ldi    zh, high(my_string*2);
     rcall  serout_string
     rcall  serout_string
     rjmp    loop
     rjmp    loop
Zeile 245: Zeile 241:
serout_string:
serout_string:
     lpm                            ; nächstes Byte aus dem Flash laden
     lpm                            ; nächstes Byte aus dem Flash laden
     and    r0,r0                   ; = Null?  
     and    r0, r0                 ; = Null?
     breq    serout_string_ende      ; wenn ja, -> Ende
     breq    serout_string_ende      ; wenn ja, -> Ende
serout_string_wait:
serout_string_wait:
     sbis    UCSRA,UDRE             ; Warten bis UDR für das nächste
     sbis    UCSRA, UDRE             ; Warten, bis UDR für das nächste
                                     ; Byte bereit ist
                                     ; Byte bereit ist
     rjmp    serout_string_wait
     rjmp    serout_string_wait
     out    UDR, r0
     out    UDR, r0
     adiw    zl:zh,1                 ; Zeiger erhöhen
     adiw    zh:zl, 1               ; Zeiger erhöhen
     rjmp    serout_string          ; nächstes Zeichen bearbeiten
     rjmp    serout_string          ; nächstes Zeichen bearbeiten
serout_string_ende:
serout_string_ende:
Zeile 259: Zeile 255:
; Hier wird jetzt der String definiert und im Flash gespeichert
; Hier wird jetzt der String definiert und im Flash gespeichert


my_string:  .db "Test!",10,13,0
my_string:  .db "Test!", 10, 13, 0
</avrasm>
</syntaxhighlight>


=== Empfangen von Zeichen per Polling===
=== Empfangen von Zeichen per Polling ===


Der AVR kann nicht nur Daten seriell senden, sondern auch empfangen. Dazu muss man, nachdem die Baudrate wie oben beschrieben eingestellt wurde, das Bit '''RXEN''' setzen.  
Der AVR kann nicht nur Daten seriell senden, sondern auch empfangen. Dazu muss man, nachdem die Baudrate wie oben beschrieben eingestellt wurde, das Bit '''RXEN''' setzen.


Sobald der UART ein Byte über die serielle Verbindung empfangen hat, wird das Bit '''RXC''' im Register '''UCSRA''' gesetzt, um anzuzeigen, dass ein Byte im Register '''UDR''' zur Weiterverarbeitung bereitsteht. Sobald es aus '''UDR''' gelesen wurde, wird '''RXC''' automatisch wieder gelöscht, bis das nächste Byte angekommen ist.  
Sobald der UART ein Byte über die serielle Verbindung empfangen hat, wird das Bit '''RXC''' im Register '''UCSRA''' gesetzt, um anzuzeigen, dass ein Byte im Register '''UDR''' zur Weiterverarbeitung bereitsteht. Sobald es aus UDR gelesen wurde, wird RXC automatisch wieder gelöscht, bis das nächste Byte angekommen ist.


Das erste einfache Testprogramm soll das empfangene Byte auf den an Port D angeschlossenen LEDs ausgeben. Dabei sollte man daran denken, dass PD0 (RXD) bereits für die Datenübertragung zuständig ist, so dass das entsprechende Bit im Register PORTD keine Funktion hat und damit auch nicht für die Datenanzeige verwendet werden kann.  
Das erste einfache Testprogramm soll das empfangene Byte auf den an Port&nbsp;D angeschlossenen LEDs ausgeben. Dabei sollte man daran denken, dass PD0 (RXD) bereits für die Datenübertragung zuständig ist, so dass das entsprechende Bit im Register PORTD keine Funktion hat und damit auch nicht für die Datenanzeige verwendet werden kann.


Nachdem der UART konfiguriert ist, wartet das Programm einfach in der Hauptschleife darauf, dass ein Byte über den UART ankommt (z.&nbsp;B. indem man im Terminalprogramm ein Zeichen eingibt), also '''RXC''' gesetzt wird. Sobald das passiert, wird das Register '''UDR''', in dem die empfangenen Daten stehen, nach <i>temp</i> eingelesen und an den Port D ausgegeben.  
Nachdem der UART konfiguriert ist, wartet das Programm einfach in der Hauptschleife darauf, dass ein Byte über den UART ankommt (z.&nbsp;B. indem man im Terminalprogramm ein Zeichen eingibt), also RXC gesetzt wird. Sobald das passiert, wird das Register UDR, in dem die empfangenen Daten stehen, nach <code>temp</code> eingelesen und an den Port&nbsp;D ausgegeben.


<avrasm>  
<syntaxhighlight lang="asm">
.include "m8def.inc"
.include "m8def.inc"


Zeile 316: Zeile 312:


receive_loop:
receive_loop:
   sbis    UCSRA, RXC                      ; warten bis ein Byte angekommen ist
   sbis    UCSRA, RXC                      ; warten, bis ein Byte angekommen ist
   rjmp    receive_loop
   rjmp    receive_loop
   in      temp, UDR                      ; empfangenes Byte nach temp kopieren
   in      temp, UDR                      ; Empfangenes Byte nach temp kopieren
   out      PORTD, temp                    ; und an Port D ausgeben.
   out      PORTD, temp                    ; und an Port D ausgeben.
   rjmp    receive_loop                    ; zurück zum Hauptprogramm
   rjmp    receive_loop                    ; zurück zum Hauptprogramm
</avrasm>
</syntaxhighlight>


=== Empfangen von Zeichen per Interrupt ===
=== Empfangen von Zeichen per Interrupt ===


Dieses Programm lässt sich allerdings noch verfeinern. Statt in der Hauptschleife auf die Daten zu warten, kann man auch veranlassen dass ein Interrupt ausgelöst wird, sobald ein Byte angekommen ist. Das sieht in der einfachsten Form so aus:  
Dieses Programm lässt sich allerdings noch verfeinern. Statt in der Hauptschleife auf die Daten zu warten, kann man auch veranlassen, dass ein [[AVR-Tutorial: Interrupts|Interrupt]] ausgelöst wird, sobald ein Byte angekommen ist. Das sieht in der einfachsten Form so aus:


<avrasm>  
<syntaxhighlight lang="asm">
.include "m8def.inc"
.include "m8def.inc"


Zeile 380: Zeile 376:
     sbi    UCSRB, RXCIE                    ; Interrupt bei Empfang
     sbi    UCSRB, RXCIE                    ; Interrupt bei Empfang
     sbi    UCSRB, RXEN                    ; RX (Empfang) aktivieren
     sbi    UCSRB, RXEN                    ; RX (Empfang) aktivieren
   
 
     sei                                    ; Interrupts global aktivieren
     sei                                    ; Interrupts global aktivieren
   
 
loop:
loop:
     rjmp loop                              ; Endlosschleife
     rjmp loop                              ; Endlosschleife


; Interruptroutine: wird ausgeführt sobald ein Byte über das UART empfangen wurde
; Interruptroutine: wird ausgeführt, sobald ein Byte über das UART empfangen wurde


int_rxc:
int_rxc:
Zeile 395: Zeile 391:
     pop    temp                            ; temp wiederherstellen
     pop    temp                            ; temp wiederherstellen
     reti                                    ; Interrupt beenden
     reti                                    ; Interrupt beenden
</avrasm>
</syntaxhighlight>


Diese Methode hat den großen Vorteil, dass das Hauptprogramm (hier nur eine leere Endlosschleife) andere Dinge erledigen kann, während der Controller Daten empfängt. Auf diese Weise kann man mehrere Aktionen quasi gleichzeitig ausführen, da das Hauptprogramm nur kurz unterbrochen wird, um die empfangenen Daten zu verarbeiten.  
Diese Methode hat den großen Vorteil, dass das Hauptprogramm (hier nur eine leere Endlosschleife) andere Dinge erledigen kann, während der Controller Daten empfängt. Auf diese Weise kann man mehrere Aktionen quasi gleichzeitig ausführen, da das Hauptprogramm nur kurz unterbrochen wird, um die empfangenen Daten zu verarbeiten.


Probleme können allerdings auftreten, wenn in der Interruptroutine die gleichen Register verwendet werden wie im Hauptprogramm, da dieses ja an beliebigen Stellen durch den Interrupt unterbrochen werden kann. Damit sich aus der Sicht der Hauptschleife durch den Interruptaufruf nichts ändert, müssen alle in der Interruptroutine geänderten Register am Anfang der Routine gesichert und am Ende wiederhergestellt werden. Das gilt vor allem für das CPU-Statusregister ('''SREG''')! Sobald ein einziger Befehl im Interrupt ein einziges Bit im SREG beeinflusst, muss das SREG gesichert werden. Das ist praktisch fast immer der Fall, nur in dem ganz einfachen Beispiel oben ist es überflüssig, weil die verwendeten Befehle das SREG nicht beeinflussen. In diesem Zusammenhang wird der [[Stack]] wieder interessant. Um die Register zu sichern, kann man sie mit '''push''' oben auf den Stapel legen und am Ende wieder in der umgekehrten Reihenfolge(!) mit '''pop''' vom Stapel herunternehmen.
Probleme können allerdings auftreten, wenn in der Interruptroutine die gleichen Register verwendet werden wie im Hauptprogramm, da dieses ja an beliebigen Stellen durch den Interrupt unterbrochen werden kann. Damit sich aus der Sicht der Hauptschleife durch den Interruptaufruf nichts ändert, müssen alle in der Interruptroutine geänderten Register am Anfang der Routine gesichert und am Ende wiederhergestellt werden. Das gilt vor allem für das CPU-Statusregister (SREG)! Sobald ein einziger Befehl im Interrupt ein einziges Bit im SREG beeinflusst, muss das SREG gesichert werden. Das ist praktisch fast immer der Fall, nur in dem ganz einfachen Beispiel oben ist es überflüssig, weil die verwendeten Befehle das SREG nicht beeinflussen. In diesem Zusammenhang wird der [[Stack]] wieder interessant. Um die Register zu sichern, kann man sie mit <code>push</code> oben auf den Stapel legen und am Ende wieder in der umgekehrten Reihenfolge(!) mit <code>pop</code> vom Stapel herunternehmen.


Im folgenden Beispielprogramm werden die empfangenen Daten nun nicht mehr komplett angezeigt. Stattdessen kann man durch Eingabe einer 1 oder einer 0 im Terminalprogramm eine LED (an PB0) an- oder ausschalten. Dazu wird das empfangene Byte in der Interruptroutine mit den entsprechenden ASCII-Codes der Zeichen 1 und 0 (siehe [http://www.asciitable.com/ www.asciitable.com]) verglichen.
Im folgenden Beispielprogramm werden die empfangenen Daten nun nicht mehr komplett angezeigt. Stattdessen kann man durch Eingabe einer 1 oder einer 0 im Terminalprogramm eine LED (an PB0) an- oder ausschalten. Dazu wird das empfangene Byte in der Interruptroutine mit den entsprechenden ASCII-Codes der Zeichen 1 und 0 (siehe [https://4n7.eu/ascii.html 4n7.eu/ascii.html] oder [https://de.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange#ASCII-Tabelle Wikipedia]) verglichen.


Für den [[AVR-Tutorial:_Vergleiche|Vergleich]] eines Registers mit einer Konstanten gibt es den Befehl '''cpi register, konstante'''. Das Ergebnis dieses Vergleichs kann man mit den Befehlen '''breq label''' (springe zu label, wenn gleich) und '''brne label''' (springe zu label, wenn ungleich) auswerten.  
Für den [[AVR-Tutorial: Vergleiche|Vergleich]] eines Registers mit einer Konstanten gibt es den Befehl <code>cpi register, konstante</code>. Das Ergebnis dieses Vergleichs kann man mit den Befehlen <code>breq label</code> (springe zu <code>label</code>, falls gleich) und <code>brne label</code> (springe zu <code>label</code>, falls ungleich) auswerten.


<avrasm>  
<syntaxhighlight lang="asm">
.include "m8def.inc"
.include "m8def.inc"


Zeile 430: Zeile 426:
; Hauptprogramm
; Hauptprogramm
main:
main:
   
 
     ; Stackpointer initialisieren
     ; Stackpointer initialisieren


Zeile 457: Zeile 453:
     sbi    UCSRB, RXCIE                ; Interrupt bei Empfang
     sbi    UCSRB, RXCIE                ; Interrupt bei Empfang
     sbi    UCSRB, RXEN                ; RX (Empfang) aktivieren
     sbi    UCSRB, RXEN                ; RX (Empfang) aktivieren
   
 
     sei                                ; Interrupts global aktivieren
     sei                                ; Interrupts global aktivieren
   
 
loop:
loop:
     rjmp loop                          ; Endlosschleife
     rjmp loop                          ; Endlosschleife


; Interruptroutine: wird ausgeführt sobald ein Byte über das UART empfangen wurde
; Interruptroutine: wird ausgeführt, sobald ein Byte über das UART empfangen wurde


int_rxc:
int_rxc:
Zeile 469: Zeile 465:
     in      temp, sreg                  ; SREG sichern
     in      temp, sreg                  ; SREG sichern
     push    temp
     push    temp
   
 
     in      temp, UDR                  ; UART Daten lesen
     in      temp, UDR                  ; UART-Daten lesen
     cpi    temp, '1'                  ; empfangenes Byte mit '1' vergleichen
     cpi    temp, '1'                  ; empfangenes Byte mit '1' vergleichen
     brne    int_rxc_1                  ; wenn nicht gleich, dann zu int_rxc_1
     brne    int_rxc_1                  ; wenn nicht gleich, dann zu int_rxc_1
     cbi    PORTB, 0                    ; LED einschalten, low aktiv
     cbi    PORTB, 0                    ; LED einschalten, low-aktiv
     rjmp    int_rxc_2                  ; Zu int_rxc_2 springen
     rjmp    int_rxc_2                  ; Zu int_rxc_2 springen
int_rxc_1:
int_rxc_1:
     cpi    temp, '0'                  ; empfangenes Byte mit '0' vergleichen
     cpi    temp, '0'                  ; empfangenes Byte mit '0' vergleichen
     brne    int_rxc_2                  ; wenn nicht gleich, dann zu int_rxc_2
     brne    int_rxc_2                  ; wenn nicht gleich, dann zu int_rxc_2
     sbi    PORTB, 0                    ; LED ausschalten, low aktiv
     sbi    PORTB, 0                    ; LED ausschalten, low-aktiv
int_rxc_2:
int_rxc_2:


Zeile 485: Zeile 481:
     pop    temp                        ; temp wiederherstellen
     pop    temp                        ; temp wiederherstellen
     reti
     reti
</avrasm>
</syntaxhighlight>


== Handshake ==
== Handshake ==
Werden Daten schnell über eine serielle Leitung an ein langsames Gerät übertragen, dann kann es passieren, dass die Situation eintritt, dass das empfangende Gerät nicht mehr mitkommt. Das kann zb dadurch passieren, dass das empfangende Gerät selbst etwas Zeit für die Bearbeitung der Daten benötigt. Man denke zb an die Situation, dass an ein Modem Daten übertragen werden. Das Modem muss diese Daten bearbeiten und unter Umständen über eine langsame Telefonleitung absetzen. Überträgt der AVR seine Daten mit voller Geschwindigkeit an das Modem, so wird auch dem besten Modem irgendwann der interne Speicher ausgehen, in dem es die Daten zwischenspeichern kann.
Werden Daten schnell über eine serielle Leitung an ein langsames Gerät übertragen, dann kann es passieren, dass die Situation eintritt, dass das empfangende Gerät nicht mehr mitkommt. Das kann z.&nbsp;B. dadurch passieren, dass das empfangende Gerät selbst etwas Zeit für die Bearbeitung der Daten benötigt. Man denke z.&nbsp;B. an die Situation, dass an ein Modem Daten übertragen werden. Das Modem muss diese Daten bearbeiten und unter Umständen über eine langsame Telefonleitung absetzen. Überträgt der AVR seine Daten mit voller Geschwindigkeit an das Modem, so wird auch dem besten Modem irgendwann der interne Speicher ausgehen, in dem es die Daten zwischenspeichern kann.


Was benötigt wird, ist also eine Möglichkeit, wie die Gegenstelle dem Sender signalisieren kann: "Bitte jetzt nichts senden, ich bin beschäftigt!". Die einfachste Form eines derartigen Protokolls, nennt sich Handshake. Es gibt bei RS232 2 Arten, wie dieses Handshake implementiert werden kann: <b>Software-Handshake</b> und <b>Hardware-Handshake</b>.
Was benötigt wird, ist also eine Möglichkeit, wie die Gegenstelle dem Sender signalisieren kann: „Bitte jetzt nichts senden, ich bin beschäftigt!. Die einfachste Form eines derartigen Protokolls nennt sich ''Handshake''. Es gibt bei RS232 zwei Arten, wie dieses Handshake implementiert werden kann: ''Software-Handshake'' und ''Hardware-Handshake''.


=== Hardware Handshake ===
=== Hardware-Handshake ===
Hardware Handshake benutzt die beiden Steuerleitungen <b>RTS - Request to Send</b> und <b>CTS - Clear to Send</b> um die Flusskontrolle durchzuführen.
Das Hardware-Handshake benutzt die beiden Steuerleitungen '''RTS''' (''Request to Send'') und '''CTS''' (''Clear to Send''), um die Flusskontrolle durchzuführen.


Die etwas seltsam anmutenden Namen haben historische Ursache. Ursprünglich war RS232 dazu gedacht ein Modem (ein sog. Data Carrier Equipment oder DCE) an einen Endpunkt (DTE oder Data Terminal Equipment) anzuschliessen. Wenn das DTE Daten senden wollte, aktivierte es die Leitung RTS, es fragte praktisch beim DCE an: "Darf ich senden? (engl. Request sending)". Wenn das DCE bereit war, dann aktivierte es seinerseits die CTS Leitung und signalisierte damit "Alles ok. Daten marsch! (engl. Clear to send)". Solange das DCE nicht bereit war, Daten entgegenzunehmen, musste das DTE warten, bis es vom DCE die Freigabe zum Senden bekam.
Die etwas seltsam anmutenden Namen haben historische Ursache. Ursprünglich war RS232 dazu gedacht, ein Modem (ein sog. ''Data Carrier Equipment'' oder DCE) an einen Endpunkt (DTE oder ''Data Terminal Equipment'') anzuschließen. Wenn das DTE Daten senden wollte, aktivierte es die Leitung RTS, es fragte praktisch beim DCE an: „Darf ich senden?(engl. ''Request sending''). Wenn das DCE bereit war, dann aktivierte es seinerseits die CTS-Leitung und signalisierte damit „Alles OK. Daten marsch!(engl. ''Clear to send''). Solange das DCE nicht bereit war, Daten entgegenzunehmen, musste das DTE warten, bis es vom DCE die Freigabe zum Senden bekam.


* Für das DTE gilt: RTS ist ein Ausgang, CTS ist ein Eingang.
* Für das DTE gilt: RTS ist ein Ausgang, CTS ist ein Eingang.
Zeile 504: Zeile 500:
</center>
</center>


Das war die ursprüngliche Idee. Heutzutage ist es aber normal, dass 2 DTE miteinander über eine RS232 Verbindung gekoppelt werden. Wird in so einem Fall Hardware Handshake benutzt, so muss jedes DTE seiner Gegenstelle eine korrekte Bedienung der RTS/CTS Leitung vortäuschen.
Das war die ursprüngliche Idee. Heutzutage ist es aber normal, dass zwei DTE miteinander über eine RS232-Verbindung gekoppelt werden. Wird in so einem Fall Hardware-Handshake benutzt, so muss jedes DTE seiner Gegenstelle eine korrekte Bedienung der RTS-/CTS-Leitung vortäuschen.


<center>
<center>
Zeile 510: Zeile 506:
</center>
</center>


Der Teil, dass CTS nur dann bedient wird, wenn über RTS die Anfrage nach der Sendefreigabe erfolgt entfällt dabei. Jeder Gesprächspartner überprüft ganz einfach vor dem Sendevorgang den Zustand der CTS Leitung der Gegenstelle, während der eigene RTS Ausgang zur Signalisierung der Empfangsbereitschaft für die Gegenstelle dient. Dies ist auch der Grund warum bei einem Null-Modem-Kabel nicht nur die RX/TX Leitungen, sondern auch die RTS/CTS Leitungen gekreuzt werden müssen.
Der Teil, dass CTS nur dann bedient wird, wenn über RTS die Anfrage nach der Sendefreigabe erfolgt, entfällt dabei. Jeder Gesprächspartner überprüft ganz einfach vor dem Sendevorgang den Zustand der CTS-Leitung der Gegenstelle, während der eigene RTS-Ausgang zur Signalisierung der Empfangsbereitschaft für die Gegenstelle dient. Dies ist auch der Grund, warum bei einem Null-Modem-Kabel nicht nur die RX-/TX-Leitungen, sondern auch die RTS-/CTS-Leitungen gekreuzt werden müssen.


Möchte man obige Schaltung um eine Hardware-Flusskontrolle erweitern, so bietet es sich an, die beiden noch freien Kanäle des MAX232 dafür zu verwenden. Die Schaltung sieht dann wie folgt aus:
Möchte man obige Schaltung um eine Hardware-Flusskontrolle erweitern, so bietet es sich an, die beiden noch freien Kanäle des MAX232 dafür zu verwenden. Die Schaltung sieht dann wie folgt aus:


[[Bild:640px-AVR-RS232_RTS.png|framed|center| UART/MAX232 Beschaltung für RTS/CTS am Beispiel eines Mega16. Achtung: Pinbelegung an den Mega8 anpassen]]
[[Bild:640px-AVR-RS232_RTS.png|framed|center|UART/MAX232-Beschaltung für RTS/CTS am Beispiel eines ATmega16. Achtung: Pinbelegung an den ATmega8 anpassen!]]
 
Am ATmega8 stehen dann die Signale RTS bzw. CTS an den Pins PD4 bzw. PD5 zur Verfügung. An PD5 kann abgefragt werden, ob die Gegenstelle zum Empfang von Daten bereit ist, während der ATmega8 über PD4 signalisieren kann, dass er im Moment keine Daten über die serielle Schnittstelle empfangen kann.


Am Mega8 stehen dann die Signale RTS bzw. CTS an den Pins PD4 bzw. PD5 zur Verfügung. An PD5 kann abgefragt werden, ob die Gegenstelle zum Empfang von Daten bereit ist, während der Mega8 über PD4 signalisieren kann, dass er im Moment keine Daten über die serielle Schnittstelle empfangen kann.
Bedenken sollte man dabei allerdings, dass es nach der Rücknahme der Empfangsbereitschaft je nach Gegenstelle dazu kommen kann, dass noch ein paar Zeichen über die UART eintreffen. Wird z.&nbsp;B. der Zustand der RTS-Leitung vom Füllgrad eines Puffers abhängig gemacht, dann sollte man RTS nicht erst dann abschalten, wenn der Puffer komplett gefüllt ist, sondern schon ein paar Zeichen früher. Ursache könnte z.&nbsp;B. sein, dass die Gegenstelle über eine Hardware-UART verfügt, die einen internen Puffer besitzt. Hat die Gegenstelle erstmal diesen Hardware-Puffer gefüllt, dann gibt es oft keine Möglichkeit mehr für das dortige Programm, diese UART-Hardware zu stoppen – die Zeichen, die in die UART übertragen wurden, werden auf jeden Fall von der Hardware ausgegeben.


=== Software Handshake ===
=== Software-Handshake ===
Software Handshake benutzt die Datenleitung selbst, um die Flußkontrolle von Sender/Empfänger zu erreichen. Dazu wurden im ASCII Code 2 spezielle 'Zeichen' vorgesehen: XON (mit dem Code 0x11) und XOFF (mit dem Code 0x13).
Ein Software-Handshake benutzt die Datenleitung selbst, um die Flusskontrolle von Sender/Empfänger zu erreichen. Dazu wurden im ASCII-Code zwei spezielle „Zeichen“ vorgesehen: XON (mit dem Code 0x11) und XOFF (mit dem Code 0x13).


Bemerkt ein Empfänger, dass er in Kürze keine Daten mehr vom Sender aufnehmen kann, dann sendet er seinerseits ein XOFF, woraufhin der Sender das Senden der Daten unterbricht. Ist der Empfänger wieder aufnahmebereit, so gibt er die Übertragung durch das Senden eines XON wieder frei.
Bemerkt ein Empfänger, dass er in Kürze keine Daten mehr vom Sender aufnehmen kann, dann sendet er seinerseits ein XOFF, woraufhin der Sender das Senden der Daten unterbricht. Ist der Empfänger wieder aufnahmebereit, so gibt er die Übertragung durch das Senden eines XON wieder frei.


Der Nachteil des Software-Handshaking besteht also in mehreren Punkten
Der Nachteil des Software-Handshaking besteht also in mehreren Punkten:
* zum einen können nicht mehr alle Datenbytes übertragen werden, da ja die Bytes 0x11 und 0x13 eine spezielle Bedeutung haben. Möchte man Bytes binär übertragen, muss man daher spezielle Vorkehrungen treffen, damit diese Datenbytes nicht durch das Software-Handshaking fehlinterpretiert werden.
* Zum einen können nicht mehr alle Datenbytes übertragen werden, da ja die Bytes 0x11 und 0x13 eine spezielle Bedeutung haben. Möchte man Bytes binär übertragen, muss man daher spezielle Vorkehrungen treffen, damit diese Datenbytes nicht durch das Software-Handshaking fehlinterpretiert werden.
* zum anderen muss jeder Sender während er sendet auch gleichzeitig einen möglichen Empfang von Daten überwachen. Die Gegenstelle könnte ja mittels XOFF eine kurzfristige Unterbrechung der Sendung anfordern. Auch muss jeder Sender exakt darüber Buch führen, ob die Leitung zur Zeit im Status XOFF liegt und ob daher Übertragungen überhaupt möglich sind.
* Zum anderen muss jeder Sender, während er sendet, auch gleichzeitig einen möglichen Empfang von Daten überwachen. Die Gegenstelle könnte ja mittels XOFF eine kurzfristige Unterbrechung der Sendung anfordern. Auch muss jeder Sender exakt darüber Buch führen, ob die Leitung zur Zeit im Status XOFF liegt und ob daher Übertragungen überhaupt möglich sind.
* das Senden von XOFF muss rechtzeitig erfolgen. Denn meistens benötigt die Gegenstelle etwas Zeit um das Senden einzustellen. Es kann durchaus sein, dass nach einem XOFF nach ein paar Zeichen von der Gegenstelle eintreffen
* Das Senden von XOFF muss rechtzeitig erfolgen. Denn meistens benötigt die Gegenstelle etwas Zeit, um das Senden einzustellen. Es kann durchaus sein, dass nach einem XOFF noch ein paar Zeichen von der Gegenstelle eintreffen.
* es besteht die Gefahr eines Deadlocks, indem sich beide Seiten gegenseitig mit einem XOFF blockieren, aus dem sie nicht mehr herauskommen.
* Es besteht die Gefahr eines Deadlocks, indem sich beide Seiten gegenseitig mit einem XOFF blockieren, aus dem sie nicht mehr herauskommen.


== Weblinks ==
== Weblinks ==
* [http://www.wormfood.net/avrbaudcalc.php WormFood's AVR Baud Rate Calculator] online.
* [http://www.wormfood.net/avrbaudcalc.php WormFood's AVR Baud Rate Calculator]
* [http://www.gjlay.de/helferlein/avr-uart-rechner.html Online Baudraten-Rechner für ATmega AVRs] (JavaScript)
* [http://www.gjlay.de/helferlein/avr-uart-rechner.html Baudraten-Rechner für ATmega AVRs] (JavaScript)


----
----

Aktuelle Version vom 19. Oktober 2023, 20:48 Uhr

Wie viele andere Controller besitzen die meisten AVRs einen UART (Universal Asynchronous Receiver and Transmitter). Das ist eine serielle Schnittstelle, die meistens zur Datenübertragung zwischen Mikrocontroller und PC genutzt wird. Dafür werden zwei Pins am Controller benötigt: TXD und RXD. Über TXD (Transmit Data) werden Daten gesendet, RXD (Receive Data) dient zum Empfang von Daten.

Hardware

Um den UART des Mikrocontrollers zu verwenden, muss der Versuchsaufbau um folgende Bauteile erweitert werden:

UART/MAX232-Standardbeschaltung

Auf vielen Evaluation-Boards sind diese Bauteile bereits enthalten, man muss ggf. nur noch die Verbindungen zwischen MAX232 und AVR herstellen.

  • Der MAX232 ist ein Pegelwandler, der die −12/+12-Volt-Signale an der seriellen Schnittstelle des PCs zu den 5/0 Volt des AVRs kompatibel macht.
  • C1 ist ein kleiner Elektrolyt-, Tantal- oder Keramikkondensator, wie er immer wieder zur Entkopplung der Versorgungsspannungen an digitalen ICs verwendet wird.
  • Die vier Kondensatoren C2…C5 sind Elektrolyt-, Tantal- oder Keramikkondensatoren (siehe Datenblatt der verwendeten MAX232-Version!). Falls Elkos oder Tantals verwendet werden, auf die richtige Polung achten! Der exakte Wert ist hier relativ unkritisch, in der Praxis sollte alles von ca. 1 µF bis 47 µF mit einer Spannungsfestigkeit von 16 V und höher funktionieren.
  • X1 ist ein weiblicher 9-poliger SUB-D-Verbinder.
  • Die Verbindung zwischen PC und Mikrocontroller erfolgt über ein 9-poliges Modem-Kabel (also ein Verlängerungskabel, kein Nullmodem-Kabel!), das an den seriellen Port des PCs angeschlossen wird. Bei einem Modem-Kabel sind die Pins 2 und 3 des einen Kabelendes mit den Pins 2 und 3 des anderen Kabelendes durchverbunden. Bei einem Nullmodem-Kabel sind die Leitungen gekreuzt, sodass Pin 2 von der einen Seite mit Pin 3 auf der anderen Seite verbunden ist und umgekehrt.
  • Als Faustregel kann man annehmen: Befinden sich an den beiden Enden des Kabels die gleiche Art von Anschlüssen (Männchen = Stecker; Weibchen = Buchse), dann benötigt man ein gekreuztes, also ein Nullmodem-Kabel. Am PC-Anschluss selbst befindet sich ein Stecker, also ein Männchen, sodaß am Kabel auf dieser Seite eine Buchse (also ein Weibchen) sitzen muss. Da am AVR laut obigem Schaltbild eine Buchse verbaut wird, muss daher an diesem Ende des Kabels ein Stecker sitzen. Das Kabel hat somit an einem Ende einen Stecker und am anderen Ende eine Buchse und ist also ein normales Modem-Kabel (= nicht gekreuzt).
Kabelbeschaltungen

Software

UART konfigurieren

Als erstes muss die gewünschte Baudrate im Register UBRR festgelegt werden. Der in dieses Register zu schreibende Wert errechnet sich nach der folgenden Formel:

[math]\displaystyle{ \text{UBRR} = \frac{\text{Taktfrequenz (in Hz)}} { 16 \cdot \text{Baudrate} } - 1 }[/math]

Beim AT90S4433 kann man den Wert direkt in das Register UBRR laden, beim ATmega8 gibt es für UBRR zwei Register: UBRRL (Low-Byte) und UBRRH (High-Byte). Bei Baudraten über etwa 3.900 Bit/s (gilt nur bei Verwendung eines Takts von 16 MHz) steht in UBRRH eine 0, da der berechnete Wert kleiner als 256 ist und somit in UBRRL alleine passt. Beachtet werden muss, dass das Register UBRRH vor dem Register UBRRL beschrieben werden muss. Der Schreibzugriff auf UBRRL löst das Neusetzen des internen Taktteilers aus.

Wichtiger Hinweis 1

Es empfiehlt sich, statt der oben genannten Formel die Formel der Codebeispiele zu verwenden:

[math]\displaystyle{ \text{UBRR} = \frac{\text{Taktfrequenz (in Hz)} + (\text{Baudrate} \cdot 8)}{(\text{Baudrate} \cdot 16)}-1 \quad \left( = \frac{ \text{Taktfrequenz (in Hz)} }{ 16 \cdot \text{Baudrate} } -0.5 \right) }[/math]

Beispiel: Bei einem ATMega mit 16 MHz und 115200 Baud ist der Wert laut Datenblatt UBBRL=8. Rechnet man mit der Formel UBRRL=(F_CPU / (UART_BAUDRATE* 16L) - 1), ergibt sich ein Wert von 7,680555 und im UBRRL-Register steht somit eine 7 statt einer 8. Die Verwendung der Formel aus dem Codebeispiel ergibt 8,180555 und im UBRRL-Register steht somit der richtige Wert – nämlich 8.

Wichtiger Hinweis 2

Aufgrund permanent wiederkehrender Nachfrage sei hier ausdrücklich darauf hingewiesen, dass bei Verwendung des UART im asynchronen Modus dringend ein Quarz oder Quarzoszillator verwendet werden sollte! Der interne RC-Oszillator der AVRs ist recht ungenau! Damit kann es in Ausnahmefällen funktionieren, muss es aber nicht! Auch ist der interne Oszillator temperaturempfindlich. Damit hat man dann den schönen Effekt, dass eine UART-Schaltung, die im Winter noch funktionierte, im Sommer den Dienst verweigert.

Außerdem muss bei der Berechnung von UBRR geprüft werden, ob mit der verwendeten Taktfrequenz die gewünschte Baudrate mit einem Fehler von <1 % generiert werden kann. Das Datenblatt bietet hier sowohl die Formel als auch Tabellen unter der Überschrift des U(S)ART an.

[math]\displaystyle{ \frac{\text{Fehler}_{\text{Baudrate}}}{\text{%}} = \left( \frac{\text{UBRR}_{\text{gerundet}}+1}{\text{UBRR}_{\text{genau}}+1} -1 \right) \cdot 100 }[/math]

Siehe auch Baudratenquarz.

Wer es ganz einfach haben will, nimmt die folgenden Macros. Die rechnen sogar den Fehler aus und brechen die Assemblierung ggf. ab. Das ist dann praktisch idiotensicher.

.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

Wer dennoch den internen RC-Oszillator verwenden will, muss diesen kalibrieren. Näheres findet man dazu im Datenblatt, Stichwort Register OSCCAL.

Um den Sendekanal des UART zu aktivieren, muss das Bit TXEN im UART-Control-Register UCSRB auf 1 gesetzt werden.

Danach kann das zu sendende Byte in das Register UDR eingeschrieben werden – vorher muss jedoch sichergestellt werden, dass das Register leer ist, die vorhergehende Übertragung also schon abgeschlossen wurde. Dazu wird getestet, ob das Bit UDRE (UART Data Register Empty) im Register UCSRA gleich 1 ist.

Genaueres über die UART-Register findet man im Datenblatt des Controllers.

An dieser Stelle sei noch folgendes angemerkt: Das UDRE-Bit sagt nichts darüber aus, ob der Controller immer noch damit beschäftigt ist, Daten zu senden. Da das Senderegister mehrfach gepuffert ist, wird UDRE bereits gesetzt, obwohl das letzte Zeichen den AVR noch nicht komplett verlassen hat. Dies kann insbesondere bei der Verwendung von Sleep-Modes ein Problem werden, wenn der Controller schlafen gelegt wird, bevor das letzte Zeichen versendet wurde, da dies gezwungenermaßen zu einem Frame-Error beim Empfänger führen wird. Um sicher zu gehen, dass der UART nicht mehr beschäftigt ist, kann das Bit TXC (UART Transmit Complete) getestet werden. Dieses wird jedoch wirklich erst nach dem Senden eines Zeichens gesetzt, beinhaltet also auch nach dem Systemstart eine 0, obwohl der Controller nichts sendet.

Der ATmega8 bietet noch viele weitere Optionen zur Konfiguration des UARTs, aber für die Datenübertragung zum PC sind im Normalfall keine anderen Einstellungen notwendig.

Senden von Zeichen

Das Beispielprogramm überträgt die Zeichenkette „Test!“ in einer Endlosschleife an den PC.

Hinweis: Wenn man das nachfolgende Programm laufen lässt und Hyperterminal startet, scheint es problemlos zu funktionieren. Wenn man aber das RS232-Kabel zwischenzeitlich abzieht und wieder ansteckt, wird es oft passieren, dass nur noch wirre Zeichen auf dem PC erscheinen. Das liegt daran, dass der PC aus einem ununterbrochenen Zeichenstrom nicht den Anfang eines Zeichens erkennen kann. Darum muss in solchen Fällen periodisch eine kleine Pause von der Länge mindestens eines Zeichens eingelegt werden, damit der PC sich wieder synchronisieren kann.

Die folgenden Beispiele sind für den ATmega8 geschrieben.

.include "m8def.inc"

.def temp    = r16                              ; Register für kleinere Arbeiten
.def zeichen = r17                              ; In diesem Register wird das Zeichen an die
                                                ; Ausgabefunktion übergeben.

.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

    ; Stackpointer initialisieren

    ldi     temp, HIGH(RAMEND)
    out     SPH, temp
    ldi     temp, LOW(RAMEND)
    out     SPL, temp

    ; Baudrate einstellen

    ldi     temp, HIGH(UBRR_VAL)
    out     UBRRH, temp
    ldi     temp, LOW(UBRR_VAL)
    out     UBRRL, temp

    ; Frame-Format: 8 Bit

    ldi     temp, (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0)
    out     UCSRC, temp

    sbi     UCSRB, TXEN                 ; TX aktivieren

loop:
    ldi     zeichen, 'T'
    rcall   serout                      ; Unterprogramm aufrufen
    ldi     zeichen, 'e'
    rcall   serout                      ; Unterprogramm aufrufen
    ldi     zeichen, 's'
    rcall   serout                      ; ...
    ldi     zeichen, 't'
    rcall   serout
    ldi     zeichen, '!'
    rcall   serout
    ldi     zeichen, 10
    rcall   serout
    ldi     zeichen, 13
    rcall   serout
    rcall   sync                        
    rjmp    loop

serout:
    sbis    UCSRA, UDRE                 ; Warten bis UDR für das nächste
                                        ; Byte bereit ist
    rjmp    serout
    out     UDR, zeichen
    ret                                 ; zurück zum Hauptprogramm

; kleine Pause zum Synchronisieren des Empfängers, falls zwischenzeitlich
; das Kabel getrennt wurde

sync:
    ldi     r16, 0
sync_1:
    ldi     r17, 0
sync_loop:
    dec     r17
    brne    sync_loop
    dec     r16
    brne    sync_1
    ret

hyperterminal.gif

Der Befehl rcall serout ruft ein kleines Unterprogramm auf, das zuerst wartet, bis das Datenregister UDR von der vorhergehenden Übertragung frei ist, und anschließend das in zeichen (= r17) gespeicherte Byte an UDR ausgibt.

Bevor serout aufgerufen wird, wird zeichen jedesmal mit dem ASCII-Code des zu übertragenden Zeichens geladen (so wie in Teil 4 bei der LCD-Ansteuerung). Der Assembler wandelt Zeichen in einfachen Anführungsstrichen automatisch in den entsprechenden ASCII-Wert um. Nach dem Wort „Test!“ werden noch die Codes 10 (Line Feed oder New Line, Zeilenvorschub) und 13 (Carriage Return, Wagenrücklauf) gesendet, um dem Terminalprogramm mitzuteilen, dass eine neue Zeile beginnt.

Eine Übersicht aller ASCII-Codes gibt es auf 4n7.de/ascii.html oder Wikipedia.

Die Berechnung der Baudrate erfolgt übrigens nicht im Controller während der Programmausführung, sondern schon beim Assemblieren, wie man beim Betrachten der Listingdatei feststellen kann.

Zum Empfang muss auf dem PC ein Terminal-Programm wie z. B. PuTTY gestartet werden. Der nebenstehende Screenshot zeigt, welche Einstellungen im Programm vorgenommen werden müssen.

Linux-Benutzer können das entsprechende Device (z. B. /dev/ttyS0) mit stty konfigurieren und mit cat die empfangenen Daten anzeigen oder ein Terminalprogramm wie minicom nutzen.

Alternativ kann unter Windows und Linux HTerm genutzt werden (Freeware).


Senden von Zeichenketten

Eine bequemere Methode, um längere Zeichenketten (Strings) zu übertragen, ist hier zu sehen. Dabei werden die Zeichenketten im Flash gespeichert. Als Abschluss des Strings wird der Wert 0x00 genutzt, so wie auch in der Programmiersprache C.

.include "m8def.inc"

.def temp    = r16                              ; Register für kleinere Arbeiten
.def zeichen = r17                              ; In diesem Register wird das Zeichen an die
                                                ; Ausgabefunktion übergeben.

.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 unser Programm los

    ; Stackpointer initialisieren

    ldi     temp, HIGH(RAMEND)
    out     SPH, temp
    ldi     temp, LOW(RAMEND)
    out     SPL, temp

    ; Baudrate einstellen

    ldi     temp, HIGH(UBRR_VAL)
    out     UBRRH, temp
    ldi     temp, LOW(UBRR_VAL)
    out     UBRRL, temp

    ; Frame-Format: 8 Bit

    ldi     temp, (1<<URSEL)|(3<<UCSZ0)
    out     UCSRC, temp

    sbi     UCSRB, TXEN                     ; TX aktivieren

loop:
    ldi     zl, low(my_string*2);           ; Z Pointer laden
    ldi     zh, high(my_string*2);
    rcall   serout_string
    rjmp    loop

; Ausgabe eines Strings aus dem Flash

serout_string:
    lpm                             ; nächstes Byte aus dem Flash laden
    and     r0, r0                  ; = Null?
    breq    serout_string_ende      ; wenn ja, -> Ende
serout_string_wait:
    sbis    UCSRA, UDRE             ; Warten, bis UDR für das nächste
                                    ; Byte bereit ist
    rjmp    serout_string_wait
    out     UDR, r0
    adiw    zh:zl, 1                ; Zeiger erhöhen
    rjmp    serout_string           ; nächstes Zeichen bearbeiten
serout_string_ende:
    ret                             ; zurück zum Hauptprogramm

; Hier wird jetzt der String definiert und im Flash gespeichert

my_string:  .db "Test!", 10, 13, 0

Empfangen von Zeichen per Polling

Der AVR kann nicht nur Daten seriell senden, sondern auch empfangen. Dazu muss man, nachdem die Baudrate wie oben beschrieben eingestellt wurde, das Bit RXEN setzen.

Sobald der UART ein Byte über die serielle Verbindung empfangen hat, wird das Bit RXC im Register UCSRA gesetzt, um anzuzeigen, dass ein Byte im Register UDR zur Weiterverarbeitung bereitsteht. Sobald es aus UDR gelesen wurde, wird RXC automatisch wieder gelöscht, bis das nächste Byte angekommen ist.

Das erste einfache Testprogramm soll das empfangene Byte auf den an Port D angeschlossenen LEDs ausgeben. Dabei sollte man daran denken, dass PD0 (RXD) bereits für die Datenübertragung zuständig ist, so dass das entsprechende Bit im Register PORTD keine Funktion hat und damit auch nicht für die Datenanzeige verwendet werden kann.

Nachdem der UART konfiguriert ist, wartet das Programm einfach in der Hauptschleife darauf, dass ein Byte über den UART ankommt (z. B. indem man im Terminalprogramm ein Zeichen eingibt), also RXC gesetzt wird. Sobald das passiert, wird das Register UDR, in dem die empfangenen Daten stehen, nach temp eingelesen und an den Port D ausgegeben.

.include "m8def.inc"

.def temp = R16

.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

    ; Stackpointer initialisieren

    ldi     temp, HIGH(RAMEND)
    out     SPH, temp
    ldi     temp, LOW(RAMEND)
    out     SPL, temp

    ; Port D = Ausgang

    ldi     temp, 0xFF
    out     DDRD, temp

    ; Baudrate einstellen

    ldi     temp, HIGH(UBRR_VAL)
    out     UBRRH, temp
    ldi     temp, LOW(UBRR_VAL)
    out     UBRRL, temp

    ; Frame-Format: 8 Bit

    ldi     temp, (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0)
    out     UCSRC, temp

    sbi     UCSRB, RXEN                     ; RX (Empfang) aktivieren

receive_loop:
   sbis     UCSRA, RXC                      ; warten, bis ein Byte angekommen ist
   rjmp     receive_loop
   in       temp, UDR                       ; Empfangenes Byte nach temp kopieren
   out      PORTD, temp                     ; und an Port D ausgeben.
   rjmp     receive_loop                    ; zurück zum Hauptprogramm

Empfangen von Zeichen per Interrupt

Dieses Programm lässt sich allerdings noch verfeinern. Statt in der Hauptschleife auf die Daten zu warten, kann man auch veranlassen, dass ein Interrupt ausgelöst wird, sobald ein Byte angekommen ist. Das sieht in der einfachsten Form so aus:

.include "m8def.inc"

.def temp = R16

.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

.org 0x00
        rjmp main

.org URXCaddr                                   ; Interruptvektor für UART-Empfang
        rjmp int_rxc

; Hauptprogramm

main:

    ; Stackpointer initialisieren

    ldi     temp, HIGH(RAMEND)
    out     SPH, temp
    ldi     temp, LOW(RAMEND)
    out     SPL, temp

    ; Port D = Ausgang

    ldi     temp, 0xFF
    out     DDRD, temp

    ; Baudrate einstellen

    ldi     temp, HIGH(UBRR_VAL)
    out     UBRRH, temp
    ldi     temp, LOW(UBRR_VAL)
    out     UBRRL, temp

    ; Frame-Format: 8 Bit

    ldi     temp, (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0)
    out     UCSRC, temp

    sbi     UCSRB, RXCIE                    ; Interrupt bei Empfang
    sbi     UCSRB, RXEN                     ; RX (Empfang) aktivieren

    sei                                     ; Interrupts global aktivieren

loop:
    rjmp loop                               ; Endlosschleife

; Interruptroutine: wird ausgeführt, sobald ein Byte über das UART empfangen wurde

int_rxc:
    push    temp                            ; temp auf dem Stack sichern
    in      temp, UDR                       ; empfangenes Byte lesen,
                                            ; dadurch wird auch der Interrupt gelöscht
    out     PORTD, temp                     ; Daten ausgeben
    pop     temp                            ; temp wiederherstellen
    reti                                    ; Interrupt beenden

Diese Methode hat den großen Vorteil, dass das Hauptprogramm (hier nur eine leere Endlosschleife) andere Dinge erledigen kann, während der Controller Daten empfängt. Auf diese Weise kann man mehrere Aktionen quasi gleichzeitig ausführen, da das Hauptprogramm nur kurz unterbrochen wird, um die empfangenen Daten zu verarbeiten.

Probleme können allerdings auftreten, wenn in der Interruptroutine die gleichen Register verwendet werden wie im Hauptprogramm, da dieses ja an beliebigen Stellen durch den Interrupt unterbrochen werden kann. Damit sich aus der Sicht der Hauptschleife durch den Interruptaufruf nichts ändert, müssen alle in der Interruptroutine geänderten Register am Anfang der Routine gesichert und am Ende wiederhergestellt werden. Das gilt vor allem für das CPU-Statusregister (SREG)! Sobald ein einziger Befehl im Interrupt ein einziges Bit im SREG beeinflusst, muss das SREG gesichert werden. Das ist praktisch fast immer der Fall, nur in dem ganz einfachen Beispiel oben ist es überflüssig, weil die verwendeten Befehle das SREG nicht beeinflussen. In diesem Zusammenhang wird der Stack wieder interessant. Um die Register zu sichern, kann man sie mit push oben auf den Stapel legen und am Ende wieder in der umgekehrten Reihenfolge(!) mit pop vom Stapel herunternehmen.

Im folgenden Beispielprogramm werden die empfangenen Daten nun nicht mehr komplett angezeigt. Stattdessen kann man durch Eingabe einer 1 oder einer 0 im Terminalprogramm eine LED (an PB0) an- oder ausschalten. Dazu wird das empfangene Byte in der Interruptroutine mit den entsprechenden ASCII-Codes der Zeichen 1 und 0 (siehe 4n7.eu/ascii.html oder Wikipedia) verglichen.

Für den Vergleich eines Registers mit einer Konstanten gibt es den Befehl cpi register, konstante. Das Ergebnis dieses Vergleichs kann man mit den Befehlen breq label (springe zu label, falls gleich) und brne label (springe zu label, falls ungleich) auswerten.

.include "m8def.inc"

.def temp = R16

.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

.org 0x00
        rjmp main

.org URXCaddr
        rjmp int_rxc

; Hauptprogramm
main:

    ; Stackpointer initialisieren

    ldi     temp, HIGH(RAMEND)
    out     SPH, temp
    ldi     temp, LOW(RAMEND)
    out     SPL, temp

    ; Port B = Ausgang

    ldi     temp, 0xFF
    out     DDRB, temp

    ; Baudrate einstellen

    ldi     temp, HIGH(UBRR_VAL)
    out     UBRRH, temp
    ldi     temp, LOW(UBRR_VAL)
    out     UBRRL, temp

    ; Frame-Format: 8 Bit

    ldi     temp, (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0)
    out     UCSRC, temp

    sbi     UCSRB, RXCIE                ; Interrupt bei Empfang
    sbi     UCSRB, RXEN                 ; RX (Empfang) aktivieren

    sei                                 ; Interrupts global aktivieren

loop:
    rjmp loop                           ; Endlosschleife

; Interruptroutine: wird ausgeführt, sobald ein Byte über das UART empfangen wurde

int_rxc:
    push    temp                        ; temp auf dem Stack sichern
    in      temp, sreg                  ; SREG sichern
    push    temp

    in      temp, UDR                   ; UART-Daten lesen
    cpi     temp, '1'                   ; empfangenes Byte mit '1' vergleichen
    brne    int_rxc_1                   ; wenn nicht gleich, dann zu int_rxc_1
    cbi     PORTB, 0                    ; LED einschalten, low-aktiv
    rjmp    int_rxc_2                   ; Zu int_rxc_2 springen
int_rxc_1:
    cpi     temp, '0'                   ; empfangenes Byte mit '0' vergleichen
    brne    int_rxc_2                   ; wenn nicht gleich, dann zu int_rxc_2
    sbi     PORTB, 0                    ; LED ausschalten, low-aktiv
int_rxc_2:

    pop     temp
    out     sreg, temp                  ; SREG wiederherstellen
    pop     temp                        ; temp wiederherstellen
    reti

Handshake

Werden Daten schnell über eine serielle Leitung an ein langsames Gerät übertragen, dann kann es passieren, dass die Situation eintritt, dass das empfangende Gerät nicht mehr mitkommt. Das kann z. B. dadurch passieren, dass das empfangende Gerät selbst etwas Zeit für die Bearbeitung der Daten benötigt. Man denke z. B. an die Situation, dass an ein Modem Daten übertragen werden. Das Modem muss diese Daten bearbeiten und unter Umständen über eine langsame Telefonleitung absetzen. Überträgt der AVR seine Daten mit voller Geschwindigkeit an das Modem, so wird auch dem besten Modem irgendwann der interne Speicher ausgehen, in dem es die Daten zwischenspeichern kann.

Was benötigt wird, ist also eine Möglichkeit, wie die Gegenstelle dem Sender signalisieren kann: „Bitte jetzt nichts senden, ich bin beschäftigt!“. Die einfachste Form eines derartigen Protokolls nennt sich Handshake. Es gibt bei RS232 zwei Arten, wie dieses Handshake implementiert werden kann: Software-Handshake und Hardware-Handshake.

Hardware-Handshake

Das Hardware-Handshake benutzt die beiden Steuerleitungen RTS (Request to Send) und CTS (Clear to Send), um die Flusskontrolle durchzuführen.

Die etwas seltsam anmutenden Namen haben historische Ursache. Ursprünglich war RS232 dazu gedacht, ein Modem (ein sog. Data Carrier Equipment oder DCE) an einen Endpunkt (DTE oder Data Terminal Equipment) anzuschließen. Wenn das DTE Daten senden wollte, aktivierte es die Leitung RTS, es fragte praktisch beim DCE an: „Darf ich senden?“ (engl. Request sending). Wenn das DCE bereit war, dann aktivierte es seinerseits die CTS-Leitung und signalisierte damit „Alles OK. Daten marsch!“ (engl. Clear to send). Solange das DCE nicht bereit war, Daten entgegenzunehmen, musste das DTE warten, bis es vom DCE die Freigabe zum Senden bekam.

  • Für das DTE gilt: RTS ist ein Ausgang, CTS ist ein Eingang.
  • Für das DCE gilt: RTS ist ein Eingang, CTS ist ein Ausgang.

RS232 orig.png

Das war die ursprüngliche Idee. Heutzutage ist es aber normal, dass zwei DTE miteinander über eine RS232-Verbindung gekoppelt werden. Wird in so einem Fall Hardware-Handshake benutzt, so muss jedes DTE seiner Gegenstelle eine korrekte Bedienung der RTS-/CTS-Leitung vortäuschen.

RS232 dte.png

Der Teil, dass CTS nur dann bedient wird, wenn über RTS die Anfrage nach der Sendefreigabe erfolgt, entfällt dabei. Jeder Gesprächspartner überprüft ganz einfach vor dem Sendevorgang den Zustand der CTS-Leitung der Gegenstelle, während der eigene RTS-Ausgang zur Signalisierung der Empfangsbereitschaft für die Gegenstelle dient. Dies ist auch der Grund, warum bei einem Null-Modem-Kabel nicht nur die RX-/TX-Leitungen, sondern auch die RTS-/CTS-Leitungen gekreuzt werden müssen.

Möchte man obige Schaltung um eine Hardware-Flusskontrolle erweitern, so bietet es sich an, die beiden noch freien Kanäle des MAX232 dafür zu verwenden. Die Schaltung sieht dann wie folgt aus:

UART/MAX232-Beschaltung für RTS/CTS am Beispiel eines ATmega16. Achtung: Pinbelegung an den ATmega8 anpassen!

Am ATmega8 stehen dann die Signale RTS bzw. CTS an den Pins PD4 bzw. PD5 zur Verfügung. An PD5 kann abgefragt werden, ob die Gegenstelle zum Empfang von Daten bereit ist, während der ATmega8 über PD4 signalisieren kann, dass er im Moment keine Daten über die serielle Schnittstelle empfangen kann.

Bedenken sollte man dabei allerdings, dass es nach der Rücknahme der Empfangsbereitschaft je nach Gegenstelle dazu kommen kann, dass noch ein paar Zeichen über die UART eintreffen. Wird z. B. der Zustand der RTS-Leitung vom Füllgrad eines Puffers abhängig gemacht, dann sollte man RTS nicht erst dann abschalten, wenn der Puffer komplett gefüllt ist, sondern schon ein paar Zeichen früher. Ursache könnte z. B. sein, dass die Gegenstelle über eine Hardware-UART verfügt, die einen internen Puffer besitzt. Hat die Gegenstelle erstmal diesen Hardware-Puffer gefüllt, dann gibt es oft keine Möglichkeit mehr für das dortige Programm, diese UART-Hardware zu stoppen – die Zeichen, die in die UART übertragen wurden, werden auf jeden Fall von der Hardware ausgegeben.

Software-Handshake

Ein Software-Handshake benutzt die Datenleitung selbst, um die Flusskontrolle von Sender/Empfänger zu erreichen. Dazu wurden im ASCII-Code zwei spezielle „Zeichen“ vorgesehen: XON (mit dem Code 0x11) und XOFF (mit dem Code 0x13).

Bemerkt ein Empfänger, dass er in Kürze keine Daten mehr vom Sender aufnehmen kann, dann sendet er seinerseits ein XOFF, woraufhin der Sender das Senden der Daten unterbricht. Ist der Empfänger wieder aufnahmebereit, so gibt er die Übertragung durch das Senden eines XON wieder frei.

Der Nachteil des Software-Handshaking besteht also in mehreren Punkten:

  • Zum einen können nicht mehr alle Datenbytes übertragen werden, da ja die Bytes 0x11 und 0x13 eine spezielle Bedeutung haben. Möchte man Bytes binär übertragen, muss man daher spezielle Vorkehrungen treffen, damit diese Datenbytes nicht durch das Software-Handshaking fehlinterpretiert werden.
  • Zum anderen muss jeder Sender, während er sendet, auch gleichzeitig einen möglichen Empfang von Daten überwachen. Die Gegenstelle könnte ja mittels XOFF eine kurzfristige Unterbrechung der Sendung anfordern. Auch muss jeder Sender exakt darüber Buch führen, ob die Leitung zur Zeit im Status XOFF liegt und ob daher Übertragungen überhaupt möglich sind.
  • Das Senden von XOFF muss rechtzeitig erfolgen. Denn meistens benötigt die Gegenstelle etwas Zeit, um das Senden einzustellen. Es kann durchaus sein, dass nach einem XOFF noch ein paar Zeichen von der Gegenstelle eintreffen.
  • Es besteht die Gefahr eines Deadlocks, indem sich beide Seiten gegenseitig mit einem XOFF blockieren, aus dem sie nicht mehr herauskommen.

Weblinks