Bauen und Programmieren von AVR-Mikrocontrollern am Beispiel von ATMEL ATTiny 2313 und ATMega 8


Start AVR-KursStartseiteserielles ISP-Interface

16. Chat mit dem PC - die RS232-Schnittstelle
(Last Update: 07.11.2010)


In diesem Kapitel:


Die RS232-Schnittstelle

Neben der Centronics-Schnittstelle war die RS232- oder V24-Schnittstelle lange Zeit Standard bei einem PC. USB-Bus und Firewire haben die beiden Ports des PC zur Außenwelt weitgehend abgelöst.

Die Bausteine der ATMEL-Familie haben sie weitgehend noch an Bord, die RS232, wie diese Schnittstelle vom verwendeten Protokoll her heißt. Von den zahlreichen Leitungen, die das Handshaking zwischen einem DTE (Terminal (PC)) und einer DCE (Datenendeinrichtung (z.B. MODEM)) gemanagt haben, braucht man als notwendiges und hinreichendes Minimum die Sendeleitung TXD und die Empfangsleitung RXD.

Ein kleines Problem stellen noch die verwendeten Spannungspegel bei der Übertragung zum PC dar. Ein AVR arbeitet bei einem 5V-Pegel. Auf den V24-Leitungen werden aber +/- 12V-Pegel benötigt. Die Umformung übernimmt ein Baustein von MAXIM, der MAX232. Über sogenannte Ladungspumpen stellt er die benötigten Pegel zur Verfügung und übernimmt auch gleichzeitig die Pegelwandlung über entsprechende Treiber. Die 0V/5V- Pegel der TXD-Sendeleitung des AVR (PORTD.1 = Pin 3) wird an Pin 10 des MAX232 eingespeist und an Pin 7 des MAX232 als +/-12V-Pegel abgegriffen. Das Signal ist an den Buchsenkontakt 2 der SUBD9-Buchse angeschlossen. Über ein geschirmtes Kabel gelangt es an den Steckkontakt 2 des PC der auf dieser Leitung die Pegel "abhört".

Der PC seinerseits sendet auf Leitung 3 mit +/-12V-Pegel. Dem MAX232 führt man dieses Signal am Pin 8 zu. Am Pin 9 des MAX232 steht das TTL-Signal zur Verfügung, das dem Pin 2 des AVR (PORTD.0 = RXD) zugeführt wird, auf dem der AVR die Leitung abhört. Als Verlängerung zwischen AVR und PC muss eine geschirmte, mindestens 2-polige Leitung mit einem SUBD9-Stecker auf der einen und einer SUBD9-Buchse auf der anderen Seite verwendet werden. Buchse und Stecker sind 1:1 mit einander verbunden, Buchse.2 auf Stecker.2 und Buchse.3 auf Stecker.3. Die Schirmung wird als Masseleitung (GND-Potential) eingesetzt.

Die 5 Elektrolytkondensatoren auf der Platine dienen teils der Pufferung der Betriebspannung und zum Teil als Container für die Ladungspumpen, welche die Betriebsspannung auf 12V anheben bzw. in die Spannung -12V umformen.

     

Für den Aufbau ist die Richtung, in der die Elkos eingelötet werden von entscheidender Bedeutung. Bei falscher Polung pflegen die kleinen Alubecher der Kondensatoren gerne mal den Leuten mit einem lauten Knall um die Ohren zu fliegen. Die Polungsrichtung ist auf beiden Fotos und auch auf dem Bestückungsplan der kleinen Baugruppe ersichtlich. Auf dem linken Foto ist ganz rechts unten der Verbindungsstecker zum AVR zu erkennen. Die Verbindung wird über ein kurzes 4-poliges Flachkabel hergestellt. Über diese Verbindung wird die Baugruppe auch mit der 5V-Betriebsspannung versorgt. Die Anschlüsse auf der RS232-Platine und dem Experimentierboard sind in gleicher Reihenfolge gruppiert, sodass keine Leitungskreuzungen nötig sind.

Die Datenübertragung

Die RS232-Schnittstelle arbeitet asynchron, das heißt ohne die Verwendung eines Taktsignals. Von der CPU wird ein Datenbyte an das Senderegister im USART als ganzes parallel (8 Bit auf einmal) übergeben. Damit die Übermittlung ohne Datenverluste abläuft, muss der Empfänger wissen, wann dieses Datenbyte (0xC9) gesendet werden soll. Zu diesem Zweck stellt die USART-Einheit dem Daten-Transfer ein Startbit voran und schließt ihn mit einem Stoppbit ab. Alle übermittelten Bits stellen einen Frame dar - einen Datenrahmen oder auch ein Datenpaket.

Im Ruhezustand (idle) liegt die Sendeleitung auf -12V, was einer logischen 1 (TTL-Pegel 5V) im AVR entspricht. Will der Sender ein Byte übermitteln, legt er die Sendeleitung für die Dauer eines Bits auf +12V, was dem TTL-Pegel 0V und dem Logikpegel 0 entspricht. Man beachte, dass die Zeitachse in der folgenden Darstellung nach rechts verläuft, und die Zeitwerte in dieser Richtung größer werden. Daher sind zeitlich früher liegende Ereignisse weiter links und später auftretende Ereignisse weiter rechts angeordnet. Die Bitfolge des zu sendenden Bytes wird dadurch scheinbar umgekehrt. Dieses Bild ergibt sich so auch auf dem Oszilloskop. Das Startbit |_| (Kanal A) dauert ca. 100 µs.

 

   

Nach dem Startbit, das der USART bereitstellt, nicht die CPU, werden die einzelnen Bits des Datenbytes übertragen - im Beispiel 0xC9 = 0b11001001. Das LSB (least significant bit), das niederwertigste Bit wird als erstes gesendet, das MSB (most significant Bit) als letztes. Sind die Daten-Bits alle übertragen, legt der Sender die TXD-Leitung mindestens für die Dauer eines Bits auf -12V also auf logisch 1, das stellt das Stoppbit dar. Dann ist die Leitung wieder im Leerlauf (idle). Die nächste Grafik vermittelt das Geschehen vom logischen Standpunkt her besser als es das U-t-Diagramm kann.

Der Empfänger kennt die Dauer eines Bits aus dem bekannten Wert der Baudrate und überprüft seine Empfangsleitung dann erstmals 1,5 Bitlängen nach der ersten Flanke von -12V auf +12V oder von +5V auf 0V (= Beginn Startbit) und dann jeweils nach der Zeitdauer eines Bits. Der zugehörige Pegel auf der Leitung zu dieser Zeit wird als Bitwert in das Empfangsregister der Schnittstelleneinheit übernommen. Weil der Empfänger aufgrund des vereinbarten Datenrahmens (8 Bit, no parity, 1 Stoppbit) weiß, wann ein Byte-Rahmen fertig übertragen ist, kann er sich mit dem nächsten Startbit erneut synchronisieren.

Die Dauer eines Bits wird programmtechnisch durch die Baud-Rate bestimmt. Die Baud-Rate gibt an, wie viele Bits pro Sekunde übertragen werden. 9600 Baud wie hier im Projekt, heißt also 9600 Bits pro Sekunde, Start- und Stoppbits mitgezählt. Die Übertragung in diesem Projekt erfolgt mit 8 Datenbits und einem Stoppbit aber ohne das sogenannte Parity-Bit, das eine Prüffunktion in Bezug auf die Integrität der Daten hat. Das macht also zehn Bits pro zu übertragendem Byte.

Diese Information kann man zum hardwaremäßigen Überprüfen der Datenverbindung nutzen. Wird ständig das Byte 0x55 = 0b01010101 = "U" gesendet, dann ensteht zusammen mit dem Startbit (0), den 8 Datenbits (in der Reihenfolge 1 0 1 0 1 0 1 0) und dem Stoppbit eine Folge von 5 0-1-Perioden gleicher Länge. Mit einem Frequenzmessgerät wird die Frequenz 4807 Hz gemessen, das ist ziemlich genau die halbe Baudrate (1,5 °/oo zuviel). Die Pulsdauer wurde mit 104,0µs gemessen, was dem errechneten Wert 1000000µs : 9600 = 104,17µs sehr genau entspricht. Die Abweichungen sind völlig innerhalb des Toleranzbereichs von 1%. Sie ergeben sich aus der Quarzfrequenz, der auf ganzen Zahlen basierenden Berechung des Teilers der Systemfrequenz für das Erreichen der Baudrate und natürlich aus den Toleranzen des Messgeräts ansich.

   

Mit einem Messgerät, das Impulse zählen kann, könnte man ferner feststellen, ob die Schnittstelle die Datenbytes auch alle richtig sendet. 100 0x55-Bytes müssten dann genau 100 mal 5 = 500 Impulse ergeben. Auch diesen Test hat die Projektanordnung bestanden.

Sendung und Empfang erfolgt in Controllern mit integrierter USART-Baugruppe über die Schnittstelle selbst ohne Zutun des Prozessors. Nach erfolgter Initialisierung der USART-Baugruppe durch das Setzen bestimmter Werte in den Steuerrergistern des AVR wird der zu sendende Wert einfach an die Baugruppe übergeben, den Rest erledigt der USART (Universal Synchronous / Asynchronous Receiver and Transmitter) selbständig. Ähnlich verhält es sich mit dem Empfang von Daten. Ein Bit in einem Statusregister teilt der CPU beim Abfragen des Statusregisters mit, wann ein Byte empfangen wurde und vom Programm eingelesen werden kann.

Zur Steigerung des Komforts stehen für die Sende- wie auch die Empfangsfunktionen verschiedene Interruptquellen zur Verfügung. Das Hauptprogramm kann dann komplett für sich arbeiten und wird einfach unterbrochen, wenn Zeichen angekommen sind, oder der Sendepuffer nach dem Senden des letzten Zeichens wieder frei ist. Als Zwischenlager sollten dann aber Speicherbereiche im SRAM eingerichtet werden.

Vorbereitung und Initialisierung des USART im AVR

Es ist schon angeklungen, dass für die serielle Datenübermittlung bestimmte Register mit den passenden Werten zu füllen sind. Einen Teil dieser Werte werden im Programm durch den Assembler berechnet und bereitgestellt.

Die Quarzfrequenz wird dem Assembler bekanntgegeben und die gewünschte Baudrate genannt. Die nächsten drei Zeilen dienen dem Berechnen des Teilers der Systemfrequenz für das Einstellen der Baudrate. Grundlage ist eine Ganzzahlarithmetik, es gibt also keine Kommastellen. Dieses Rechnen mit ganzen Zahlen zwingt zu einigen Tricks, wenn man auf die Segnungen der Berechnung mit Fließkommazahlen - z. B. Runden - nicht ganz verzichten will. Die zugrunde liegende Formel stammt aus dem Datenblatt des Tiny2313, Seite 118, Tabelle 48.

Die dritte Zeile stellt so eine Rundung auf ganze Zahlen für diese Formel dar.

Um den Fehler berechnen zu können benötigen wir den Wert der Baudrate, der sich umgekehrt aus dem Auflösen der Formal aus dem Datenblatt nach der Baudrate ergibt. Das erledigt Zeile 4 aus dem dargestellten Listing des Quellcodes. Zeile 5 enthält einen weiteren Trick der Ganzzahlarithmetik. Eigentlich soll der prozentuale Fehler berechnet werden, dann hätte man aber keine Nachkommastelle. Deshalb wird der Promillefehler berechnet, der die Nachkommastelle der Prozentangabe als Einerstelle der ganzzahligen Promillangabe enthält.

Das Konstrukt mit den Assemblerdirektiven .if ... .else ... .endif bricht die Programmübersetzung ab, wenn der Fehler größer als 1 Prozent nach oben oder unten ist.

Die letzte Anweisung definiert die Variable "debuglevel". Der zugewiesene Wert entscheidet später im Quellcode darüber, ob gewisse Sequenzen übersetzt werden sollen oder nicht. Auf diese Weise kann man durch das Ändern eines einzigen Zahlenwerts ganze Programmteile für Testzwecke ein oder ausblenden ohne diese einfügen und dann für die endgültige Version (debuglevel = 0) wieder löschen zu müssen.

 

Hier werden für den unteren Bereich von PORTB vier LED-Ausgänge vorbereitet und schließlich die Variable "vwert" als Vergleichswert für den 16-Bit-Timer/Counter1 belegt.

 

Für eine effektivere Zeitrechnung wird jetzt statt des 8-Bit-Timers0 der 16-Bit-Timer1 verwendet, der für den CTC-Modus (Clear Timer on output Compare) eingestellt wird wie bislang der Timer0. Die Adresse der ISR ist "Timer1_oc1a".

 

Der 16-Bit-Wert "ubrr_val" der oben vom Assembler nach unseren Angeben berechnet wurde, wird in Highbyte und Lowbyte gespalten und den entsprechenden Registern der USART-Einheit UBRRH und UBRRL zugewiesen (Datenblatt Seite 137). Die Assemblerroutinen HIGH() und LOW() erledigen das für uns.

Der nächste Absatz regelt das Frameformat. Die Beschreibung der Bits aus dem IO-Register UCSRC findet man auf Seite 136/137 des Datenblatts zum Tiny2313.

Die Sende- und Empfangseinheit des AVR sind getrennt aktivierbar. Das erledigen die Bits TXEN (TXd ENable) und RXEN (RXd ENable) des IO-Registers UCSRB (Datenblatt Seite 134/135).

Die Einstellungen für den USART des AVR sind damit abgeschlossen.

 

Wenn alle Verbindungen zum PC durchgeschaltet und alle genannten Einstellungen korrekt sind, dann sendet dieser UP-Aufruf einen Begrüßungstext an den PC, der mittels eines Terminalprogramms (in Windows: Hyperterminal) empfangen werden kann.

 

Erster Absatz: Die LED-Anschlüsse werden als Ausgänge programmiert, und die LEDs durch Hochlegen der Portleitungen ausgeschaltet (die einfache LED-Taster-Platine wird benutzt).

  

Der Timer wird auf das Zählen von 40000 Takten (0 bis 39999) eingerichtet. Der CTC-Modus 4 wird eingestellt (Datenblatt Seite 110), der Timer ohne Vorteiler gestartet (Datenblatt Seite 111).

Durch Löschen der Bits COM1A1 und COM1A0 in IO-Register TCCR1A wird ein Durchschalten des Vergleicherausgangs auf das Pin15 des AVR verhindert.

Durch Setzen des OCIE1A-Bits im TIMSK-IO-Register wird der Interrupt, der durch einen Timergleichstand mit dem OCR1H/L-Registerpaars ausgelöst wird, an das System weitergeleitet und die zugehörige ISR "Timer1_oc1a" über den IRQ-Vektor bei 0x0006 aufgerufen (sofern das globale IRQ-Flag in SREG gesetzt ist (SEI-Befehl)).

 

Senden von Daten

Die Senderoutine ist kurz und leicht überschaubar. UDR ist das Register, in das zu sendende Daten geschrieben werden müssen. Im Register temp1 muss da zu sendende Byte übergeben werden. Sobald das Senderegister UDR beschrieben ist, beginnt der USART mit dem Hinausschieben der Bitinformation über die TXD-Leitung. Ob das Senderegister frei ist, sagt uns das Bit UDRE (UDR Empty) im UCSRA-Rgister. Die internen Speicherstellen für das Senderegister UDR und das Empfangsregister UDR sind verschieden aber von der Adresslage her identisch. In das Senderegister kann nur geschrieben, aus dem Empfangsregister nur gelesen werden.

Die folgende Routine sendet die oben bereits beschriebenen ASCII-"U"s in permanenter Folge und dient so dem Vermessen der RS232-Verbindung.

Die Routine "serout_say_hallo" sichert die verwendeten Register und bereitet dann im zentralen Teil die Sendung des Begrüßungsstrings, der ab dem Label "my_string" abgelegt und mit den Zeichen ASCII 10 = Zeilenvorschub und ASCII 13 = Wagenrücklauf (an den Zeilenbeginn) sowie einem 0-Byte als Textendezeichen abgeschlossen wird. Das Lesen der Daten aus dem Programmspeicher und deren Transfer übernimmt die Procedur "serout_string". Diese Procedur beendet sich, wenn das aus dem Speicher gelesene Byte das 0-Byte ist.

 

Empfangen von Daten

Das Empfangen von Daten über die RS232-Schnittstelle funktioniert ähnlich wie deren Sendung. Das RXC-Bit signalisiert, dass der Empfang eines Frames abgeschlossen ist und das Byte aus UDR gelesen werden kann. Das Byte wird im Register temp1 zurückgegeben.

Eine Variante der Routine "serin" ist die "serin_echo"-Routine. Sie wird verwendet, wenn im Terminalprogramm die direkte Ausgabe unterdrückt ist und hilft zu prüfen, ob die Zeichen richtig bei AVR angekommen sind. Zu diesem Zweck sendet die "serin_echo"-Routine jedes über die RS232 empfangene Zeichen sofort an den Sender zurück, der die so empfangenen Daten in seinem Kommunikationsfenster darstellt. Kommen andere Zeichen zur Anzeige, wie die über die Tastatur gesendeten, dann liegt ein Kommunikationsproblem vor, das zu lösen ist.

 

Die Zeitrechnung

Zwei Dinge sind an der ISR für Timer1 bemerkenswert.

Das Zählen der 8 MHz-Impulse des ohne Vorteiler verwendeten Quarztaktes bis zu einem Wert von 40000 bedingt, dass jeder dieser Zähldurchgänge 1/200 einer Sekunde dauern. Die Jiffies, die mit jedem ISR-Aufruf hochgezählt werden definieren mit dem Zählerstand 200 die Zeitdauer einer Sekunde. Anders gesagt gibts es pro Sekunde 200 Zeitscheiben, in denen abhängig vom Zählerstand kürzere Jobs erledigt werden können. Wir hatten so was schon mal im Kapitel Interrupts bei der Tastaturabfrage mit entprellen. Hier wird davon aber kein Gebrauch gemacht.

Wird die Variable "debuglevel" beim Assemblieren aber mit 1 belegt, dann wird in der ISR der Teil des Quellcodes zwischen .if und .endif mit übersetzt. Das führt dazu, dass bei einem ersten Programmtest die LED Nr. 1 im Sekundentakt blinkt und damit dem Programmierer die Information leifert, dass zumindest das IRQ-System der Zeitrechnung funktioniert. Nach diesem Test, kann man den Debuglevel auf einen anderen Wert setzen. Die ISR zu interpretieren, sollte in diesem Kursstadium keine Probleme mehr verursachen.

 

die Jobschleife

Falls der Wert der Variablen "debuglevel" = 0 ist, wird er Inhalt der Jobschleife, deren Start durch das Label "loop" markiert ist, übersetzt und nach den Vorbereitungen des AVR-Programms durchlaufen. Die Struktur ist sehr einfach aufgebaut. Nachdem ein Zeichen über die RS232 mittels "serin" in das Register temp1 gelesen wurde, wird dessen Identität überprüft. Im Fall eines vom PC gesendeten "e" wird ein weiteres Zeichen erwartet und auf ASCII "1" bis "4" überprüft. Liegt das Zeichen in diesem Bereich, dann wird die zugehörige LED auf der Zusatzplatine eingeschaltet. Das geschieht stets durch Rücksetzen des entsprechenden Portbits.

Ist das zuerst übermittelte Zeichen ein "a" dann schaltet die danach gesendete Ziffer "1" bis "4" die zugehörige LED aus.

Wer genau hinsieht, stellt fest, dass weder das "e" noch das "a" an den PC zurückgesendet wird. In der Terminalanzeige erscheint nur die Ziffer, mit der die jeweilige LED angesprochen wird. Das liegt daran, dass die Buchstabentaste mit "serin" und die Ziffern mit "serin_echo" eingelesen werden. Damit der Tastendruck "t" am PC nur die Rücksendung des Zeichens ASCII 0xC9 zur Folge hat, wurde dieser Weg gewält. So kann man mit einem Speicheroszilloskop den Spannungsverlauf für diesen Bytewert aufzeichnen, so wie es oben geschehen ist.

Die Taste "s" schließlich löst die permanente Aussendung des ASCII "U" = 0x55 aus und erlaubt so den bereits beschriebenen Test der Versuchsanordnung. Sollte die RS232 Probleme bereiten, dann kann man diese Testroutine bereits vor dem Erreichen der Jobschleife direkt durch das Programm veranlassen.

 

Eine zweite Testsequenz

Falls der Wert der Variablen "debuglevel" nicht 2 ist, wird der "rjmp loop"-Befehl übersetzt, also für 0, 1, 3, 4 ... steht er im Maschinenprogramm des AVR und wird ausgeführt. Dadurch wird die folgende Testsequenz niemals durchgeführt. Für debuglevel = 2 wird er bei der Übersetzung unterdrückt, die nachfolgende Sequenz wird im Programm "sichtbar". Sie sendet die Zeichen "Test", Zeilenvorschub, Wagenrücklauf über die serielle Schnittstelle. Wird dieser Text von einem Termionalprogramm wie Hyper Terminal korrekt empfangen, dann funktioniert die RS232 richtig. Für debuglevel=2 wird auch der Inhalt der Jobschleife nicht übersetzt. Natürlich kann dann der AVR auch nicht auf die vom Terminal evtl. gesendeten Zeichen reagieren.

 

Das Terminalprogramm unter Windows

Zum Unterhalten gehören immer zwei, wenn der AVR spricht, muss der Windows-PC zuhören (können). Auf dem PC erledigt das ein Terminalprogramm wie das hauseigene "Hyper Terminal". Diese Anwendung lauscht auf die Daten, die an der RS232-Schnittstelle ankommen und stellt sie in einem Fenster dar. Über die Tastatur kann man Daten eingeben, die dann direkt an den AVR geschickt werden.

Hyper Terminal wird mit Windos installiert, muss aber noch für den Verkehr mit dem AVR eingerichtet werden. Zu finden ist das Programm im Zubehörordner unter Kommunikation.

Beim ersten Start erscheint folgender Dialog. Die Verbindung wird benannt und es wird ihr ein Icon zugeordnet.

Die verfügbaren RS232-Schnittstellen auf dem PC werden mit COM und einer Nummer benannt. Natürlich muss hier die COM-Schnittstelle eingestellt werden, an welcher der AVR mit seiner seriellen Schnittstelle hängt - nicht mit der Programmierschnittstelle.

Die Anschlusseinstellungen sind wie angegeben einzustellen.

Nach Klick auf OK geht der PC auf der Leitung COM3 auf Empfang. Wenn jetzt der AVR (neu) gestartet wird, erscheint die folgende Willkommensnachricht im Terminalfenster - vorausgesetzt, dass der AVR mit dem richtigen HEX-File (rs232.hex) programmiert wurde.

Die 2. Zeile im Terminalfenster beginnt mit 4 Hieroglyphen-Zeichen. Das ist die Antwort des AVR auf jeweils ein "t", das über die PC-Tastatur eingegeben und an den Controller gesandt wurde. Der AVR erhält durch den "t"-Befehl den Auftrag ein Zeichen mit dem ASCII-Code 0xC9 zu senden. Richtig, es ist genau das Zeichen, das oben als Oszillogramm dargestellt ist. Diese Beobachtung zeigt auch, dass es ganz einfach ist, die Datenübermittlung auf der RS232-Leitung "abzuhören". Die beiden Einser rühren vom Einschalten der LED1 mittels "e1" und Ausschalten über "a1" her. Im Listing wurde bereits darauf hingewiesen, dass das erste Zeichen eines Befehls vom AVR nicht zurückgesandt wird. e und a werden also quasi verschluckt. die Nummern der Schaltstellen 1, 2, 3 oder 4 werden durch den "serin_echo"-Befehl dagegen als Echo wieder verschickt und vom PC im Terminalfenster dargestellt.

Wenn bereits eine Verbindung angelegt wurde und Hyper Terminal ein weiteres Mal gestartet wird, kann der Dialog zum Anlegen einer neuen Verbindung abgebrochen werden. Die Kontaktaufnahme erfolgt dann über die Schaltfläche

öffnen.

Im Dialog wird die vorher angelegte Verbindung per Doppelklick aktiviert. Das Programm ist dadurch wieder kommunikationsbereit.

                

Hier wurde die LED1 ein und wieder ausgeschaltet, dann wurden LED2, 3 und 4 ein und in umgekehrter Richtung wieder ausgeschaltet.

 

So sieht es aus, wenn mit dem "s"-Befehl das permanente Aussenden des Zeichens "U" = 0x55 ausgelöst wurde. Das Terminalfenster läuft dann sehr schnell voll. Dieser Modus ist nur durch einen Reset am AVR zu beenden. Während die Sendung läuft, kann man am Pin 3 (TXD/PD1) die oben beschriebenen Messungen machen.

 

Wir steuern den AVR via PC

Bisher wurden bereits 4 mögliche Steuerbefehle an den AVR gesendet, die er auch prompt ausgeführt hat. Zur Demonstration seien zwei weitere Beispiele gezeigt.

Der Hexadezimalzähler

Beschreibung: Auf Sendung des Zeichens "h" soll der AVR die vier LEDs im Sekundentakt im Hexadecimalsystem hochzählen. Wird das Zeichen "c" gesendet, soll das Zählen abgebrochen und alle LEDs ausgeschaltet werden.

Bei der Lösung brauchen wir zwei weitere Einträge in der Jobschleife, eine Befehlssequenz in der ISR von Timer1 zum Updaten der Anzeige und einen neuen Befehl, weil die Bits, die wir vom "sekunden"-Zähler ausleihen, wegen unserer Anzeigeeinheit alle invertiert werden müssen. Ferner brauchen wir ein Merkerbit im "flag"-Register, das ich "fcounthex" genannt habe. Es ist das Bit 2.

Der "h"-Befehl setzt einfach nur das "fcounthex"-Bit.

Der "c"-Befehl muss das "fcounthex"-Bit zurücksetzen und dann alle LEDs ausschalten. Das bedarf wohl keiner großen Erklärung, hatten wir schon ein paar Male.

Bevor die Jobschleife erreicht wird, werden alle Bits im "flag"-Register gelöscht, die LEDs auch.

 

In die ISR "timer1_oc1a" wird nach dem Hochzählen des Sekundenzählers die Sequenz zum Update der LED-Anzeige eingefügt - "counting". Diese Sequenz bedarf einer detailierten Erläuterung.

In der Sequenz "counting" werden die Reister temp1 und temp2 verwendet. Das heißt, dass diese Register am Beginn der ISR auf den Stack zu retten sind und vor Ende der ISR in umgekehrter Reihenfolge wieder durch POP-Befehle vom Stack restauriert werden müssen (hier nicht gelistet!).

Der SBRS - Befehl überspringt den RJMP-Befehl, falls das "fcounthex"-Bit im "flag"-Register gesetzt ist. Es wird dann, wie gewünscht das Anzeigeupdate durchgeführt. Bei gelöschtem "fcounthex"-Bit sorgt der RJMP-Befehl für das Überspringen der Updatesequenz.

Mit MOV wird der Sekundenzähler nach temp2 kopiert.

ANDI löscht die oberen 4 Bit von temp2, weil wir die eh nicht brauchen können und weil man nicht weiß, was mit den oberen Bits anderweitig passiert. Es könnte ja ein anderes Team mit diesen Bits etwas planen und diesen Leuten dürfen wir durch unachtsame Programmierung nicht ins Handwerk pfuschen.

LDI lädt Register temp1 mit der Konstanten 0x0f

Jetzt kommt ein neuer Befehl. EOR bedeutet EXOR, zu deutsch exklusives Oder. Wir vergleichen die Wahrheitstabellen für OR = Oder und EXOR. Dabei geht es um eine Bitoperation. Während Oder auch bei der Kombination 1   1 eine 1 als Ergebnis liefert, ist der Wert dieser Kombination bei der Exor-Verknüpfung 0. Anders ausgedrückt, bei der Exor-Verknüpfung zweier Bits kommt nur dann eine 1 heraus, wenn die beiden Eingabebits unterschiedliche Wertigkeit haben. Das entspricht im wesentlichen dem deutschen Sprachgebrauch des Wortes "oder". Entweder ich sitze oder ich stehe, beides gleichzeitig wie beim logischen "oder" geht nicht.

OR = Oder
0
1
0
0
1
1
1
1

EOR = excl. Oder
0
1
0
0
1
1
1
0

Mit dem EOR-Befehl kann man nun mehere Bit in einem Byte auf einmal im Wert umkehren. Die folgende Tabelle macht das klar. Der EOR-Befehl wirkt stets auf einander entsprechende Bits und hat wie AND und OR keine Nebenwirkungen auf Nachbarbits.

Byte1 1 1 0 1 0 1 1 1
Byte2 0 1 0 1 1 0 0 1
Byte1 EOR Byte2 1 0 0 0 1 1 1 0

Diese Art der Anwendung des EOR-Befehls stellt eine einfache Art der Verschlüsselung dar, wie sie im Zeitalter des legendären Comodore C64 gerne zum Softwareschutz eingesetzt wurde. (Sehr leicht zu knacken!)

Byte1 1 0 1 0 0 1 0 1
Byte2 0 0 0 0 1 1 1 1
Byte1 EOR Byte2 1 0 1 0 1 0 1 0

Diese zweite Art der Anwendung zeigt die Wirkung des EOR-Befehls für den vorliegenden Fall. Byte1 wird mit dem Byte2 = 0b00001111 exoderiert. Das obere Nibble von Byte1 bleibt im Ergebnis 1:1 erhalten, während die Bits des Lownibbles invertiert werden. Genau das ist erwünscht, denn die oberen 4 Bits des PortsB sollen nicht verändert werden. Die unteren 4 Bits müssen jedoch invertiert werden, damit bei einem 0-Bit am Ausgang die LED leuchtet und bei einem 1-Bit nicht. Die LEDs liegen mit dem Sammelanschluss halt nicht an GND-Level, sondern an + 5V.

IN holt den Inhalt des PORTB-IO-Registers in Register temp1.

ANDI löscht die unteren 4 Bits von temp1 ohne die oberen 4 Bits zu verändern.

OR setzt genau diejenigen von den unteren 4 Bits in temp1, die auch in temp2 gesetzt sind und die das "aus" der korrespondierenden LEDs bewirken.

OUT setzt die unteren 4 Portbits entsprechend dem Inhalt von Register temp1.

 

Ein Countdownzähler

Wir stellen einen Sekundenwert bis 59 Sekunden per Zahleneingabe ein. Mit Eingabe der zweiten Ziffer wird der Zeitablauf gestartet und z. B. ein Relais eingeschaltet. Nach Ablauf der Zeitperiode wird das Relais ausgeschaltet.

Hardwareänderung: Die Relaisplatine wird über PORTB.3 angestöpselt, Vcc und GND nicht vergessen.

Softwareergänzungen:

Im Declarationsteil wird die Variable cnt_relais definiert, die den Zähler für den Countdown darstellt.

In die Jobschleife wir ein weiterer Job integriert, der durch ein "x" aufgerufen wird. Es wird über die Procedur "v24_in_number" eine zweistellige Zahl über die RS232 eingelesen und in den Zähler transferiert. Portleitung einschalten (die Relaisplatine hat highaktive Eingänge) - fertig - den Rest erledigt nebenbei die ISR von Timer1.

Nachdem das "x" identifiziert und berücksichtigt ist, wartet die Procedur "v24_in_number" auf ein weiteres Zeichen von der RS232. Ohne weitere Plausibilitätskontrolle wird das höhere Nibble genullt und das untere Nibble mit 2 multipliziert, doppelten Wert in temp2 merken. Dann noch mal 4, also insgesamt mal 8. Den doppelten Wert addieren, mach den zehnfachen Wert. der eingegebenen Ziffer, der jetzt in temp2 steht. Die Einerstelle wird von der RS232 geholt, das obere Nibble abgeschnitten und der Zehneranteil aus temp2 addiert. Register temp2 restaurieren und Bytewert der zweistelligen Zahl in temp1 zurückgeben ans Hauptprogramm.

Warum geht das? Ziffern werden im ASCII-Code folgendermaßen geschrieben:

Ziffer
Code
Highnibble
Lownibble
0
0x30
0x3
0x0
1
0x31
0x3
0x1
2
0x32
0x3
0x2
...
9
0x39
0x3
0x9

Wenn man das Highnibble durch Undieren mit 0x0F nullt, bleibt im Lownibble der Ziffernwert des Zeichens übrig.

Zeichen 0x37 für "7" 0 0 1 1 0 1 1 1
Maske                                ANDI     0x0F 0 0 0 0 1 1 1 1
Ergebnis 0x07 0 0 0 0 0 1 1 1

Mal 10 = mal 2 plus mal 8 oder wie man im Zweiersystem durch linksschieben der Bits einfach mit 2 multiplizieren kann.

Mal 2 geht im Zweiersystem eben genau so einfach wie im Zehnersystem mal 10. Man schiebt die Ziffern nach links und hängt rechts eine 0 an.

Bytewert 7 0 0 0 0 0 1 1 1
LSL= Byte mal 2 0 0 0 0 1 1 1 0
LSL= Byte mal 2 0 0 0 1 1 1 0 0
LSL= Byte mal 2 0 0 1 1 1 0 0 0
3 mal LSL = Byte mal 8 0 0 1 1 1 0 0 0
1 mal LSL = Byte mal 2 0 0 0 0 1 1 1 0
addieren                                                     Übertrag   1 1 1        
Byte mal 8 + Byte mal 2 = Byte mal 10 0 1 0 0 0 1 1 0
Wertigkeit der Bits   
128 64 32 16 8 4 2 1
64 + 4 + 2 = 70
  64       4 2  

Weil man das System ohne Überpfüfung der Eingaben ganz schön linken kann, sollte man natürlich bei einem Kundensystem schon testen, ob eine Ziffer eingegeben wurde und alles andere abweisen. Solche Kontrollen erhöhen halt leider nicht nur die Sicherheit sondern auch den Programmumfang.

 

Jetzt fehlt nur noch die Zeitkontrolle, das Herunterzählen der Variable cnt_relais und das Ausschalten des Relais. Der "TST"-Befehl prüft, ob die Variable den Wert Null hat. Wenn ja, muss das Relais ausgeschaltet werden. Falls nicht wird der Wert der Variable um eins erniedrigt, fertig bis zum nächsten Durchlauf. Das Relais wird ausgeschaltet, indem die Portleitung PORTB.3 low gelegt wird.

Frage:

Warum kann man mit dem Befehl "e4" das Relais während der Einschaltzeit ausschalten?

Die Antwort findet man im Text dieses Kapitels oder in der Assembler-Quelldatei. Zum Ausprobieren für Eilige gibt es auch wieder die fertige HEX-Datei zum Brennen des ATMEL Tiny 2313.

 


Start AVR-KursAn den Seitenbeginnnicht verfügbar