Keine Angst vor Mikrokontrollern! Der einfache Einstieg in die Welt der AVRs (6) Roland Walter, DL7UNO |
Die aktuellen Qelltexte und Compilate zu den Beiträgen
Vorweg: Ich komme mit der Beantwortung von Mails nicht mehr hinterher. Ich würde gerne auch weiterhin bei der Beantwortung der vielen kleinen Detailfragen helfen, aber es ist einfach nicht mehr zu schaffen. Mails an mich sind deshalb trotzdem nicht sinnlos; soweit möglich, werde ich auf Einzelprobleme in den Artikeln oder im FAQ-Bereich meiner Homepage eingehen. Bitte nehmen Sie mir das nicht übel, aber ich muß ja auch noch zum Arbeiten kommen ;-)
Noch einmal UART
Für viele Leute scheint die serielle Schnittstelle ein schwer zu bändigendes Etwas zu sein, weshalb ich hier schweren Herzens noch einige Ergänzungen zur UART einfügen will. In etlichen Mails wurde mir berichtet, daß zwar alle Einzelschritte leicht zu verstehen sind. Probleme bereitet aber die Vorstellung, eine Reihe von Bytes via UART nach und nach zu empfangen, während man zwischendurch ganz andere Programmschritte abarbeitet. Aus den mir zugeschickten Listings ließen sich einige sehr typische Probleme und Denkfehler extrahieren, auf die ich jetzt in zusammengefaßter Form eingehen will.
Um einfacher vorgehen zu können, setzen viele Leute zum UART-Empfang die Bascom-Funktion Input ein. Diese Funktion arbeitet intern nach der Poll-Methode und kehrt erst zurück, wenn alle angegebenen Bytes empfangen wurden. Will man mit Input z.B. Zeichen in einen String einlesen, der mit 10 Bytes Länge dimensioniert wurde, dann kehrt Input erst zurück, wenn tatsächlich 10 Bytes empfangen wurden, vorher nicht(!). Das kann beim Test im Terminalprogramm den Eindruck erwecken, als wenn das Programm hängengeblieben ist. Analog gilt dies für Integer: Hat man eine Variable z.B. als WORD dimensioniert, dann kehrt Input erst zurück, wenn 2 Bytes empfangen wurden - ein WORD besteht nun einmal aus zwei Bytes. Das ist das erste Problem, bei dem viele hängenbleiben.
Das zweite Problem ist die Ineffizienz der Input-Funktion. Soll Input z.B. 10 Bytes einlesen, dann wartet man, bis endlich alle 10 Zeichen empfangen wurden. Bis dahin kann man nichts, aber auch gar nichts anderes tun. Wenn wir 9.600 Baud bei 8N1 annehmen, dann muß der AVR bei 10 Bytes mindestens 1/96 Sekunde lang warten, was bei 10 MHz AVR-Taktfrequenz 104167 Takten Totzeit entspricht. Das ist schon eine sehr bedeutende Verschwendung von Rechenkapazität, die man sich nur leisten sollte, wenn der AVR ohnehin nichts anderes zu tun hat. Das folgende Listing zeigt einen praktikablen Weg zum Einlesen eines Strings ohne Input.
'0011.BAS: Einfacher String-Empfang $Regfile = "2313def.dat" $Crystal = 3686400 $Baud = 9600 Dim s As String*10 On URXC OnRxD Enable URXC Enable Interrupts Main: If Len(s) > 9 Then Print s s = "" End If Goto Main OnRxD: s = s + Chr(UDR) Return |
Wer das Vorgehen String=String+Zeichen nicht mag, kann mit einem kleinen Trick optimal vorgehen: In Bascom ist es möglich, ein und denselben SRAM-Platz mit mehreren Variablen zu belegen. Wir können z.B. anweisen, daß unsere String-Variable auf Adresse &H60 gesetzt werden soll. Gleichzeitig können wir ein Feld mit 11 Bytes dimensionieren, das auf der gleichen Adresse liegen soll (ein 10 Byte langer String benötigt 11 Bytes Platz). Damit können wir ganz einfach und effizient über völlig verschiedene Methoden auf denselben Speicher zugreifen. Schauen Sie sich bitte das folgende Listing an.
'0011.BAS: Optimierter String-Empfang $Regfile = "2313def.dat" $Crystal = 3686400 $Baud = 9600 Dim s As String*10 At &H60 Dim b(11) As Byte At &H60 Overlay Dim n As Byte On URXC OnRxD Enable URXC Enable Interrupts Main: If n > 9 Then Print s n = 0 End If Goto Main OnRxD: Incr n b(n) = UDR Return |
Und noch ein drittes UART-Mißverständnis trat wiederholt auf: Der UART-Empfangs-Interrupt meldet, daß ein Zeichen empfangen wurde. In der Interrupt-Routine kann man deshalb auch immer nur ein Zeichen auslesen. Zum Beispiel macht es daher keinen Sinn, in der Interrupt-Routine den Bascom-Befehl Input einzusetzen, um doch mehrere Zeichen empfangen zu können.
Jetzt aber endlich zur Fortsetzung der Serie. Die letzte Folge schloß mit dem 8-Bit-Timer ab. Wie angekündigt soll jetzt der 16-Bit-Timer beschrieben werden.
Timer1
Zur Erinnerung: Der AT90S2313 verfügt über zwei voneinander unabhängige Timer. Timer0 ist 8 Bit breit und kann deshalb von &H00 bis &HFF (0 bis 255) zählen und Timer1 ist 16 Bit breit und kann deshalb von &H0000 bis &HFFFF (0 bis 65535) zählen. Das ist zunächst der auffallendste Unterschied zwischen den beiden Timern.
Darüber hinaus hat Timer1 aber zusätzlich eine Vergleichsfunktion, kann als Pulsweitenmodulator (anderer Name: Pulsabstandsmodulator) dienen und hat außerdem eine Capture-Funktion, bei der ein externes Signal die Ausgabe des aktuellen Zählerstandes bewirken kann. Bei der Verwendung der Capture-Funktion kann als Signaleingang von Timer1 außerdem auch noch der Analog-Komparator des AT90S2313 verwendet werden. Timer1 hat also wirklich weitaus mehr Möglichkeiten als Timer0.
Davon abgesehen, daß Timer1 etliche Funktionen mehr hat hat, wird er in Bascom im Prinzip genau wie Timer0 behandelt. Nur beim direkten Zugriff auf die AVR-Register muß beachtet werden, daß Timer1 andere Register als Timer0 verwendet und außerdem müssen die 16-Bit-Werte getrennt als HiByte und LoByte in 8-Bit-Register gegeben oder aus diesen gelesen werden.
Ich spare mir die Erörterung der Funktionen, die bei Timer0 und Timer1 gleich sind. Die drei Beispiellistings zum Timer0 können grundsätzlich auch mit Timer1 verwendet werden - man muß lediglich TIMER0 durch TIMER1 ersetzen und sinnvollerweise den Namen der Interrupt-Routine von OnTimer0 in OnTimer1 ändern. Wenn man den Wert des Timers in eine Variable auslesen will, sollte diese Variable bei Timer1 natürlich nicht als Byte (8 Bit), sondern als Word (16 Bit) dimensioniert worden sein.
Timer1-Capture
Am besten springen wir gleich mit einem praktischen Beispiel ins kalte Wasser, denn was ein Timer als solcher ist, wissen wir ja bereits von Timer0.
Gleich vorweg will ich noch auf einen Mini-Bug hinweisen, der zumindest bis Bascom-Version 1.11.6.7 bestand: Wenn man Timer1 im Capture-Modus benutzt, stellt der AVR ein Störfilter zur Verfügung, das man bei Bedarf einschalten kann. Das Störfilter läßt das Eingangssignal nur dann durch, wenn es über 4 Takte hinweg stabil ist. Dafür gibt es beim Befehl Config Timer1 die Einstellmöglichkeit "Noise Cancel=1/2" ... oder zumindest hätte es so heißen müssen. Stattdessen muß man im Sourcecode (falsch) "Noice Cancel=" eingeben. Der Fehler ist auch genau so in der Bascom-Hilfe angegeben. Behalten Sie das im Hinterkopf - benötigen werden wir es jetzt nicht.
Beim folgenden Listing verwenden wir AVRTerm als Signalquelle und zur Anzeige. Sie müssen also eine Verbindung per Nullmodem-Kabel herstellen. Außerdem verbinden Sie bitte den AVR-Pin PD6/ICP mit dem RTS-Ausgang des MAX232. Die RTS-Checkbox in AVRTerm wird unsere Signalquelle sein. Wenn die Checkmarke gesetzt ist, liegt am RTS-Pin des MAX232 HIGH an. Wenn die Checkmarke entfernt ist, führt dieser Pin LOW-Pegel. Die "Hardware"-Taster verwenden wir nicht, weil wir dann das bekannte Tasten-Prellen am Hals hätten.
Verdrahtung für die Capture-Programme
Wir wollen jetzt die Zeit messen, die zwischen zwischen zwei LOW/HIGH-Flanken vergeht. Nachdem Sie das AVR-Programm geladen und die Verbindung mit AVRTerm hergestellt haben klicken Sie dazu bitte rhytmisch mit der Maus auf die RTS-Checkbox in AVRTerm. Klicken Sie bitte zunächst schnell hintereinander und werden Sie dann allmählich langsamer. Im Terminalfenster sollten jetzt die Meßwerte in Hundertstel Sekunden angezeigt werden. Beachten Sie bitte auch, daß eine LOW/HIGH-Flanke immer nur in dem Augenblick entsteht, wenn Sie die Checkmarke setzen. Anders ausgedrückt: Für eine vollständige Periode sind immer zwei Mausklicks erforderlich.
Die RTS-Checkbox in AVRTerm
Schauen wir uns jetzt das Programm an:
'0013.BAS: InputCapture mit Timer1 (Einfache Stopuhr) 'Hardware: MAX232 an PD0/PD1 ' Nullmodemkabel zum PC ' Pin ICP/PD6 an RTS '-------------------------------------- $Regfile = "2313def.dat" $Crystal = 3686400 '3,6864MHz-Quarz $Baud = 9600 Dim wCount As Word On ICP1 OnCapture 'Interrupt-Routine Config Timer1=Timer,Prescale=1024,Capture Edge=Rising Enable ICP1 'Capture-Interrupt Enable Interrupts 'Interrupts global Main: 'Hauptschleife Goto Main '-------------------------------------- OnCapture: 'Timer1-Capture wCount = Timer1 'Erst Wert sichern Timer1 = 0 'Sofort Timer-Reset wCount = wCount \ 36 '1/100 Sekunde Print wCount;" "; Return |
Jetzt wäre die Frage logisch, wie man die Auflösung der Stopuhr erhöhen kann, ohne daß Fehler durch Timer1-Überläufe ins Spiel kommen. Das ist ganz einfach möglich. Wir nehmen zum Capture-Interrupt einfach noch den Overflow-Interrupt hinzu und zählen so die Anzahl der Timer-Overflows. Zum Schluß (nach dem Capture-Interrupt) wird dann die Anzahl der Overflows und der aktuelle Zählerstand zum eigentlichen Meßwert zusammenaddiert. Das ist schon alles. Und weil das eine wichtige praktische Rolle spielt, gebe ich das erweiterte Stopuhr-Listing vollständig wieder:
'0014.BAS: InputCapture mit Timer1 (2) (Frequenzzähler) 'Hardware: MAX232 an PD0/PD1 ' Nullmodemkabel zum PC ' Pin ICP/PD6 an RTS '-------------------------------------- $Regfile = "2313def.dat" $Crystal = 3686400 '3,6864MHz-Quarz $Baud = 115200 'Hohe Baudrate Dim lCount As Word At &H60 Dim wCountLo As Word At &H60 Overlay Dim wCountHi As Word At &H62 Overlay On ICP1 OnCapture 'Interrupt-Routine On OVF1 OnOverflow 'Interrupt-Routine Config Timer1=Timer,Prescale=1,Capture Edge=Rising Enable ICP1 'Capture-Interrupt Enable OVF1 'Overflow-Interrupt Enable Interrupts 'Interrupts global Main: 'Hauptschleife Goto Main '-------------------------------------- OnCapture: 'Timer1-Capture wCountLo = Timer1 'Erst Wert sichern Timer1 = 0 'Sofort Timer-Reset Print lCount;" "; wCountHi = 0 Return '-------------------------------------- OnOverflow: Incr wCountHi 'lCount=lCount+65536 Return |
Noch ein Wort zu den beiden letzten Listings: Als Verbesserung könnte man z.B. beim Timer1-Reset gleich einen Wert voreinstellen. Und außerdem sollte man natürlich nicht davon ausgehen, daß Windows auch nur annähernd sofort auf einen Mausklick reagiert. Es geht hier einzig und allein um eine schnell zu durchschauende grundsätzliche Demonstration der Capture-Funktion. Den Feinschliff überlasse ich Ihnen.
Wer übrigens in der Bascom-Hilfe danach sucht, wie man den den Analog-Komparator als Timer1-Capture-Eingang benutzt, wird nicht fündig. Diese Funktion wurde offensichtlich einfach vergessen. Ich habe das als Bug gemeldet und muß die Beschreibung wohl oder übel später nachreichen. Wer gerne direkt in den AVR-Registern herumwühlt, steht hier aber nicht im Regen, denn über das direkte Setzen von Bits im "Analog Comparator Control and Status Register" ACSR kommt man auch zum Ziel.
Die Einsatzgebiete für die Capture-Funktion sind schier unendlich. Beispielsweise wird bei der Funkübertragung von Analogwerten gerne die Pulsabstandsmodulation verwendet. Dabei entspricht dann die Größe eines Analogwertes dem zeitlichen Abstand zwischen zwei Pulsen. Dieser Pulsabstand kann mit der Capture-Funktion bequem festgestellt werden. Will man mehrere Kanäle übertragen, dann fügt man einen Synchronisationskanal hinzu, der kleiner ist, als die anderen Kanäle jemals sein können.
Ein weiteres Anwendungsbeispiel wäre ein sehr preiswerter programmierbarer und softwareseitig abgleichbarer Frequenzähler. Wer das tatsächlich vorhat, dem kann ich gleich noch ein paar Tips geben: Die meisten käuflichen LCD-Anzeigen basieren auf dem Hitachi-LCD-Controller HD44780A oder einem kompatiblen Controller. Und dafür hat Bascom eine ganze Reihe von Funktionen fix und fertig vorzuliegen. Wer LCD-Anzeigen mit 2 mal 16 Zeichen billig sucht, wird auf www.j-bitzer.de fündig. Dort gibt es diese Anzeigen gebraucht für 4 Euro. Ich habe mich bereits ausreichend versorgt, deshalb kann ich den Tip jetzt auch weitergeben.
(Wird fortgesetzt)