Home: www.rowalt.de
Keine Angst vor Mikrokontrollern!
Der einfache Einstieg in die Welt der AVRs (4)
Roland Walter, DL7UNO

Die aktuellen Qelltexte und Compilate zu den Beiträgen

FAQ-Teil

In der letzten Folge hatte ich geschrieben, daß man den Zustand der einzelnen I/O-Pins aus dem Register PIND auslesen kann. Ja, ich weiß, das ist nicht völlig korrekt, denn PIND ist eigentlich gar kein Register. Oh, diese Pedanten :-))) Aber trotzdem Danke für den Hinweis. - Der Zustand der I/O-Leitungen wird in PIND so gelesen, wie er im Augenblick gerade ist, es wird also nichts registriert. Aber bequemerweise ist PIND so implementiert, als wäre es ein Register.

Die letzten Folge schloß mit einem UART-Sender ab, der jetzt mit einem UART-Empfänger komplettiert wird. Wir starten mit einem UART-Empfänger, der ohne Interrupts auskommt und nutzen dann die Gelegenheit, den Umgang mit Interrupts zu besprechen.

UART-Empfänger ohne Interrupts

Strategie: Für den Datenempfang gibt es zwei verschiedene Strategien. Zum einen kann man pollen, d.h. man fragt die UART permanent, ob ein neues Byte eingetroffen ist. Damit läßt sich der Umgang mit der UART gut beschreiben und das Vorgehen ist leicht nachvollziehbar. Wir werden deshalb mit dieser Methode beginnen. Der zweite Weg geht über einen Interrupt, den der AVR auslöst, sobald ein neues Byte vollständig empfangen wurde. Das ist der viel effizientere Weg, der aber einige Schritte mehr erfordert und bei dem man mehr beachten muß.

Ich nenne die beiden Wege gerne die Ost- und die West-Methode: Bei der Ost-Methode fragt der Kunde immer wieder im Laden nach, ob ein bestimmter Artikel vorhanden ist (Pollen heißt abfragen). Die Verkäuferin sagt 100 mal Ham-wa-nich bis es beim 101. mal klappt. Es gehört zu den Lebensprinzipien der Verkäufer, niemals laut das Wort Bananen auszusprechen. Bei der West-Methode werden dagegen Vertreter geschickt. Auch wenn man noch so beschäftigt ist - man muß zumindest aufstehen und Ham-schon-einen von sich geben (Interrupt heißt unterbrechen). Vertreter haben das Lebensprinzip, daß sie niemals stören. Und das niemals oft. Einzige Hilfe: Man stellt die Klingel ab.

Pollen: Das Listing im folgenden Kasten zeigt das grundsätzliche Vorgehen. In der Hauptschleife fragen wir das Bit RXC (ReceiveComplete) des UartStatusRegisters USR ab. Wenn dieses Bit auf 1 gesetzt ist, wurde ein Zeichen vollständig empfangen und kann aus dem UartDataRegister UDR ausgelesen werden. Sobald das Byte aus dem UDR-Register ausgelesen wurde, setzt der AVR das RXC-Bit automatisch auf 0 zurück, womit bei uns ein doppeltes Auslesen eines Zeichens ausgeschlossen ist. Das ist schon alles.


'004.BAS:   UART-Empfänger ohne Interrupt
'Hardware:  MAX232 an PD0/PD1, Nullmodemkabel zum PC
'---------------------------------------------------
$Regfile  = "2313def.dat" 'AT90S2313-Deklarationen
$Crystal  = 3686400     'Quarz: 3.6864 MHz
$Baud     = 9600        'Baudrate der UART: 9600 Baud

Dim i As Byte

Do
  If USR.RXC = 1 Then  'Wenn Byte empfangen...
    i = UDR            'Byte aus UART auslesen
    Select Case i
    Case "H"
      Print "Hallo AVR"
    Case "h"
      Print "hallo avr"
    Case Else
      Print "Unbekannter Befehl"
    End Select
  End If
Loop
End

Ein neues Strukturelement ist die Select-Case-Bedingungsprüfung. Diese ist bei der Überprüfung mehrerer Werte erheblich schneller und codesparender als eine lange Liste von If-Then-Else-Bedingungen. Ganz besonders gilt dies, wenn, wie bei uns, nur ein Byte überprüft wird.

Wen es interessiert: Beim Senden von Zeichen gibt es natürlich ebenso einen direkten Weg über AVR-Register wie eben beim Empfangen gezeigt. In diesem Fall schreibt man das betreffende Byte ins Register UDR und pollt so lange USR.TXC, bis das TXC-Bit (TransmitComplete) auf 1 gesetzt ist. Aber die Bascom-Print-Befehle fand ich in diesem Fall einfach bequemer.

Das Bild links zeigt die Reaktion des AVR-Programms, wenn das Zeichen H (Großbuchstabe!) empfangen wird. Bei den beiden Sonderzeichen handelt es sich übrigens, wie schon in der letzten Folge zu sehen, um die Zeilenwechsel-Zeichen CR+RT, welche der Befehl Print erzeugt. Ein Semikolon ; hinter dem String würde die Zeilenwechsel-Zeichen unterdrücken.

Interrupts

Obwohl der Hauptschwerpunkt jetzt die Arbeit mit Interrupts sein soll, bleiben wir beim Datenempfang. Damit Sie sich nicht zu viel auf einmal merken müssen, sollten Sie sich erst einmal nur mit der rein praktischen Seite befassen und das folgende Programm ausprobieren. Lesen Sie die Texte zu den Hintergründen bitte erst, wenn sie das vorangegangene tatsächlich nachvollziehen können.

Bitter verbinden Sie als erstes den AVR-Pin PD5 mit dem Speaker. Das Beispiellisting finden Sie in Kasten unten. Wie Sie sehen, brauchen wir jetzt schon etwas mehr Code. Schauen wir uns zunächst einmal die Hauptschleife an. Zur Abwechslung habe ich hier mal nicht Do-Loop verwendet, sondern ein Label namens Main: gesetzt, zu dem am Ende der Hauptschleife mittels Goto ein Sprung ausgeführt wird.

In der Hauptschleife lassen wir den angeschlossene Speaker permanent piepsen. Wir hätten hier auch irgend etwas anderes tun können. Das Piepsen des Speakers soll demonstrieren, daß ein Programmteil im Prinzip unabhängig von den Interrupts vor sich hin arbeitet. Daß dies nicht ganz so unabhängig ist, kann man dann am Ton gut hören.

In der Hauptschleife wird die Variable wSpeaker hochgezählt. Sobald sie den Wert 300 erreicht, wird der Zustand von Pin PD5 (hier haben wir den Speaker angeschlossen) von logisch 0 auf 1 oder umgekehrt gewechselt. Anschließend wird die Variable wSpeaker wieder auf 0 zurückgesetzt und das Ganze beginnt von vorn. Im Resultat erhalten wir einen hörbaren Ton. Wer eine andere Quarzfrequenz als 3,6864MHz verwendet, muß eventuell einen anderen Wert als 300 einsetzen.


'005.BAS:   UART-Empfänger mit Interrupt
'Hardware:  MAX232 an PD0/PD1, Nullmodemkabel zum PC
'           Speaker an PD5
'---------------------------------------------------
$Regfile  = 2313def.dat 'AT90S2313-Deklarationen
$Crystal  = 3686400     'Quarz: 3.6864 MHz
$Baudrate = 9600        'UART-Baudrate: 9600 Baud

Dim i As Byte         'Byte: 0...255
Dim wSpeaker As Word  'Word: 0...65535
Dim fSpeaker As Bit   'Bit:  0,1

DDRD = &B00100000  'Pin PD5 Ausgang, Rest Eingang

On URXC OnRxD      'Interrupt-Routine setzen
Enable URXC        'Interrupt URXC einschalten
Enable Interrupts  'Interrupts global zulassen
'---------------------------------------------------
wSpeaker = 0
Main:              'Hauptschleife
  Incr wSpeaker
  If wSpeaker = 300 Then  '300: Quarzabhängig
    PORTD.5 = fSpeaker      'Pin PD5 setzen
    fSpeaker = NOT fSpeaker 'Flag invertieren
    wSpeaker = 0
  End If
Goto Main
'---------------------------------------------------
OnRxD:
  i = UDR    'Byte aus der UART auslesen
  Printbin i 'Byte als Echo zurückgeben
Return

Laden Sie das compilierte Programm bitte in den AVR und starten Sie AVRTerm. Unmittelbar nachdem Sie in AVRTerm die Checkbox Connect markiert haben, muß der Speaker einen Ton ausgeben. Wenn Sie die Markierung von der Checkbox Connect entfernen, zieht AVRTerm den AVR in den Reset-Zustand und schont damit Ihre Ohren und Nerven (solange kein anderes Programm auf die LPT-Schnittstelle zugreift...). Würden Sie den Programmierstecker abziehen, dann hat die Reset-Leitung keinen Low-Pegel mehr (interner PullUp-Widerstand!) und der AVR fängt an zu laufen.
Bitte senden Sie jetzt beliebig viele Zeichen mit AVRTerm ab und wiederholen Sie dies mehrmals.
Im Speaker-Ton werden (sehr) kurze Unterbrechungen zu hören sein. Das sind die Momente, wo die Arbeit in der Hauptschleife abrupt unterbrochen und stattdessen die Interrupt-Routine ausgeführt wird. Danach, wie man hören kann, arbeitet die Speaker-Schleife weiter wie gehabt.

Die verwendeten Interrupt-Statements

Zunächst verwenden wir das Statement On URXC OnRxD. URXC ist ein Bascom-eigenes Kürzel für UART Receive Complete, wobei RX ist die übliche Abkürzung für Receive ist. Da URXC eine bequeme Abkürzung ist, bleibe ich im folgenden dabei. Das Statement insgesamt bedeutet: Wenn ein URXC-Interrupt ausgelöst wird, dann springe zum Label OnRxD: und führe den dortigen Code aus. Damit haben wir die sogenannte Interrupt-Service-Routine festgelegt, dies ist das Unterprogramm, das bei einem bestimmten Interrupt ausgeführt werden soll. Die Bezeichnungen für die anderen möglichen Interrupts sind in der Bascom-Hilfe zum Statement On zu finden. Beim AT90S2313 sind (einschließlich Reset) insgesamt 10 verschiedene Interrupts möglich.

Das Statement Enable URXC schaltet den URXC-Interrupt ein. Dieser Interrupt wird immer dann ausgelöst, wenn die UART ein Zeichen vollständig empfangen hat. Ohne das Statement Enable URXC würde der URXC-Interrupt niemals ausgelöst werden. Trotzdem würde im Moment noch kein Interrupt ausgelöst werden, denn ein letzter wichtiger Schritt ist noch nicht getan.

Zum globalen Einschalten der (freigegebenen) Interrupts muß der Befehl Enable Interrupts ausgeführt werden. Dieser Befehl, wie auch Disable Interrupts, gilt stets für alle Interrupts zusammen. Schauen Sie sich zur Vertiefung bitte auch die Bascom-Hilfe zu den Statements Enable und Disable an. Ab jetzt kann der AVR tatächlich Interrupts auslösen.

Die Interrupt-Routine

Die Interrupt-Routine selbst beginnt in Bascom stets mit einem Label (hier also OnRxD:) und endet mit dem Befehl Return. Es muß sichergestellt sein, daß das Return auch tatsächlich erreicht wird. Verheddert man sich in irgend welchen Unterroutinen oder in einer Endlosschleife, dann wird nie wieder ein Interrupt ausgelöst und das (durch den Interrupt) unterbrochene Hauptprogramm wird auch nicht mehr ausgeführt werden.

In unserer Interrupt-Routine lesen wir lediglich das empfangene Zeichen aus und schicken es als Echo an das Terminal-Programm zurück. Nach Ausführung von Return wird die Abarbeitung der Hauptschleife wieder genau an der Stelle fortgesetzt, an der sie durch den Interrupt unterbrochen wurde.

Der Interrupt als solcher

Sobald ein Interrupt ausgelöst wird, passiert für Sie als Bascom-Programmierer eigentlich nichts weiter, als wenn Sie ein Unterprogramm aufrufen. Sobald das Unterprogramm abgearbeitet ist, kehrt es zurück und die aufrufende Routine wird ganz normal weiter ausgeführt. Der entscheidende Unterschied ist aber, daß der Interrupt zu einem beliebigen Zeitpunkt an einer beliebigen Stelle des Programmablaufs auftreten kann.

Nach einem Interrupt schaltet der AVR als allererstes das Auslösen weiterer Interrupts aus. Neue Interrupts werden erst wieder zugelassen, nachdem das Return in der Interrupt-Routine ausgeführt wurde. Wenn man also zu lange in der Interrupt-Routine bleibt, dann können durchaus Ereignisse unbemerkt verlorengehen. Das zeitweise Auschalten aller Interrupts (außer Reset) ist sehr wichtig, denn ein Interrupt bei der Abarbeitung eines Interrupts würde die Programmausführung vollständig durcheinanderbringen. Im Hintergrund muß Bascom nämlich (vereinfacht ausgedrückt) alle intern verwendeten Resourcen sichern und sich außerdem merken, an welcher Stelle der normale Betrieb gerade unterbrochen wurde. Nach dem Return der Interrupt-Routine wird der Ursprungszustand wiederhergestellt. Käme während der Abarbeitung des ersten Interrupts ein zweiter hinzu, dann würden die gesicherten Daten durch neue überschrieben und eine Rückkehr zum ursprünglichen Zustand wäre nicht mehr möglich. Ich hoffe, dies reicht zum allgemeinen Verständnis. Bild 3 zeigt noch einmal schematisch den Ablauf, wobei man sich auch mehrere Interrupt-Routinen für unterschiedliche Interrupts hinzudenken kann.

Programmablauf mit Interrupt

Register-Hintergründe

Hier noch einige Erklärungen, damit Sie im Atmel-Datasheet nachvollziehen können, was im Hintergrund passiert:

URXC werden Sie im Datasheet nicht finden, denn dies ist ein Bascom-eigenes Kürzel. Mit dem Statement Enable URXC wird das Bit RXCIE (Receive <RX> Complete Interrupt Enable) im Register UCR (UART Control Register) gesetzt. Wenn dieses Bit gesetzt ist, wird der UART-Empfangs-Interrupt eingeschaltet. Die Befehle UCR.RXCIE = 1 und Set UCR.RXCIE würden exakt zum gleichen Ergebnis wie Enable URXC führen.

Wenn Sie Enable Interrupts ausführen, dann setzt Bascom das Global-Interrupt-Enable-Bit (Bit 7, auch I-Bit genannt) im AVR-Status-Register SREG. Die Befehle SREG.7 = 1 und Set SREG.7 würden exakt zum gleichen Ergebnis wie Enable Interrupts führen.

Und hier noch etwas Hintergrundwissen ohne unmittelbar praktische Bedeutung: Das Statement On URXC OnRxD führt dazu, daß Bascom einen Interrupt-Vektor an eine genau festgelegte Adresse im Programmspeicher setzt. Das finden Sie im Atmel-Datasheet unter Reset and Interrupt Handling beschrieben. Sobald man Interrupts verwendet, werden beim AT90S2313 die ersten 10 Adressen des Programmspeichers nämlich zu Zieladressen, die bei den betreffenden Interrupts direkt angesprungen werden. 10 Adressen deshalb, weil es beim AT90S2313 10 verschiedene Interrupts gibt. Der URXC-Interrupt hat z.B. die vest verdrahtete Adresse $007. Wird der URXC-Interrupt ausgelöst, dann springt der AVR zur Adresse $007. Dort muß ein Sprungbefehl zur zuständigen Interrupt-Routine stehen. Bei uns wird ein Sprung zur Routine OnRxD ausgeführt.

Im nächsten Teil werden wir mit zwei Interrupt-Routinen arbeiten und dabei weitere AVR-Features behandeln.

Bis dahin können Sie z.B. versuchen, die bisher kennengelernten Möglichkeiten zum Zugriff auf I/O-Pins mit denen der UART kombinieren. Viel Erfolg und viel Spaß bis zum nächsten Artikel.