Eine BASCOM AVR-Library mit interner Variablenverwaltung und Interrupt Service Routine
Ausgehend von der Fragestellung, wie man einen mit Carriage Return (CR = #13) und Linefeed (LF = #10) abgeschlossenen String in BASCOM über die COM Schnittstelle einlesen kann, möchte ich mal eine Lösung vorschlagen. In reinem Basic ist die Ausfilterung der Zeichen CR und LF zwar möglich, ist aber ein ganz schönes Gefummel.
Ich selbst löse Stringoperationen gern in Assembler, gerade auf dem Gebiet dieser Schnittstelle. Das steht folgende Philosophie dahinter:
Wenn ein (kleiner) Mikrocontroller mit einem PC kommuniziert, handelt es sich meist um Anweisungen vom PC an den Mikrocontroller, dass dieser etwas tun (messen)- und das Ergebnis dann zurückschicken soll. Die Aufgabe ist daher recht überschaubar wobei Steueranweisungen und Ergebnis in der Regel sehr kurze „Zeichenketten“ sind. Zur Übermittlung der Daten stehen prinzipiell 2 Wege zur Verfügung:
1. Als Binärdaten
2. Als ASCII-String terminiert mit einem Terminator (z.B. CR oder CRLF)
Die erste Methode erscheint schneller hat aber 2 gewaltige Nachteile: zum einen müssen Anweisung und Antwort immer eine bestimmte, festgelegte Länge haben (ich kann ja die Länge nicht erkennen), zum anderen kann ich die Binärdaten weder unmittelbar darstellen, noch lesen noch direkt in einer Datei speichern.
Die zweite Methode zeigt diese Nachteile nicht, im Gegenteil: die Daten können nicht nur unterschiedlich lang sein sondern wenn ich sie mit CR (oder besser gleich mit CRLF) terminiere, könnte ich sie direkt in eine Datei wegschreiben. Der Geschwindigkeitsnachteil durch die Formatierung und die höhere Anzahl an zu übertragenden Zeichen ist für mich von untergeordneter Bedeutung.
Zur Kommunikation mit dem Mikrocontroller brauche ich also 2 Routinen auf Mikrocontrollerseite:
• Eine Routine, die im Hintergrund (Interrupt) einen Kommandostring einliest, mir anzeigt wenn das Ende (CR) erreicht ist und ein eventuell vorhandenes LF herausfiltert. Bequem wäre es noch, wenn sich die Routine – sobald sie erkennt, dass ein Kommando anliegt – ohne „Hang-up“ selber blockiert, bis das Kommando an „höhere Stelle“ weitergeleitet wurde.
• Eine zweite Routine, die einen String an den PC schickt und ihn automatisch mit CRLF terminiert.
Im Prinzip geht beides auch in BASIC. Die erste Routine ist wie schon gesagt recht „unwuchtig“ zu realisieren, die zweite Aufgabe, das Senden des Strings, geht aber recht angenehm von Basic aus mittels des Befehls „Print“. Ich hab‘ trotzdem eine eigene Routine implementiert. Na ja, ich hab‘ mir gedacht „wenn schon, denn schon“ und so entsteht ein „abgeschlossenes System“. Die Routine(n), die ich gleich vorstellen möchte, sind sehr schlank (das Demoprogramm hat so 384 Byte) und arbeiten ebenfalls mit Basic.
Wie schon in der Überschrift angedeutet, habe ich diese Problemstellung durch Erstellen einer BASCOM-Library gelöst. Das Besondere an dieser Library ist, dass sie neben der Einbindung der Interrupt Service Routine für den USART-Empfang auch eine Verwaltung/Einbindung von BASCOM Variablen in die Library selbst hat. Beide Features habe ich bisher im Netz nicht gefunden; ich habe deshalb die wesentlichen Dateien in einer ZIP-Datei zusammen gepackt, dass man die einzelnen Schritte bzw. die Vorgehensweise, wie so etwas implementiert wird, auch nachvollziehen kann
In der ZIP-Datei sind folgende Dateien enthalten:
• _firstlib.lib : BASCOM Library (Quelldatei)
• _firstlib.lbx : Vorcompilierte Library Datei
• libtest.bas : Demo-/Testprogramm für die Library
• libtest.hex : HEX-Datei des Testprogramms
• libtest.asm : Re-/Disassemblat der HEX-Datei (mittels ReAVR erstellt). Ein Ein Re- oder Disassembler wie z.B. ReAVR erzeugt aus einem HEX-File wieder ein (lesbares) ASM-File. Wer ernsthaft überlegt, in die Assemblerprogrammierung einzusteigen, sollte sich diese Freeware zulegen und sich solche „Rück-Übersetzungen“ einmal anschauen. Solche Beispiele sind extrem lehrreich. Den Re-Assembler gibt’s wie gesagt als Freeware im Netz: http://avr.jassenbaum.de/ja-tools/reavr.html
• libtest_hex_Resassemblat.pdf : zum Teil kommentietes Reassemblat
Diese Dateien _firstlib.lib und _firstlib.lbx müssen in den Bascom-Library Ordner kopiert werden. Bei mir ist das: „C:\Users\ws\Programme\BASCOM-AVR\LIB“. Die letztere *.lbx-Datei kann auch in der BASCOM IDE mit dem LIB-Manager (zu finden unter „Werkzeuge“) aus der _firstlib.lib erstellt werden.
Als Hilfe bei der Erstellung der Library hatte ich im wesentlichen folgende Hilfsmittel:
• Das Kapitel „Mixing ASM and BASIC“ in der BASCOM-Hilfe
• Die Datei „mylib.lib“ (aus dem Library-Ordner) sowie andere Libraries im Ordner
• Ein recht guter Artikel zu dem Thema von den „Kollegen“ vom Roboternetz http://www.rn-wissen.de/index.php/Bascom_Libraries
• ... und viel Probieren
Die wahrscheinlich interessanteste Datei ist sicher die Library selbst (_firstlib.lib). Die Erstellung war mit Sicherheit auch ein Hauptteil der Arbeit. Ich möchte daher auch noch ein paar Kommentare dazu abgeben, vielleicht sind die hilfreich für eigene Projekte. Einige Kommentare habe ich auch in die Library selbst geschrieben.
• Die Library wird wohl von einer BASCOM-Utility übersetzt, die näher an Assembler ist als an BASCOM-Basic. So dürfen Konstanten nicht mit „Const“ sondern müssen mit „.equ“ definiert werden, genauso wie bei der symbolischen Registerdefinition die Assembler-Deklaration „.def“ anstelle der BASCOM „Alias“-Deklaration benutzt werden muss. Auch sind Kommentare nicht überall möglich und an manchen Stellen können Kommentare auch mit dem in Assembler üblichen Semikolon eingeleitet werden. Dieser Kommentarwirrwar hat mich doch schon einiges an Zeit gekostet.
• Zu Beginn, als meine Quell-Datei noch jede Menge Fehler enthielt, habe ich sie mit dem Lib-Manager precompiliert (die *.lbx Datei erstellt). Da kamen die groben Fehler mit einer (wenig informativen) Fehlermeldung auch zum Vorschein – allerdings konnte ich den Lib-Manager danach nur noch mit dem Windows Task-Manager schließen (= rauswerfen). Ansonsten gab’s keine Nebeneffekte. Also, beim Probieren lohnt es sich, den Task-Manager permanent offen zu halten. Nicht nur aufgrund diesen „bockigen Verhaltens“ glaube ich, dass das Erstellen von Libraries nicht unbedingt als Standard innerhalb BASCOM vorgesehen ist, sondern doch wohl eher zur Implementierung von speziellen, wohl eher kostenpflichtigen Erweiterungen. Da ist m.E. auch nichts gegen zu sagen. Hauptsache, die Möglichkeit der Erweiterung besteht.
• Wenn man *.lib und *.lbx Dateien vergleicht, kann man ein paar wichtige Prinzipien herausfinden. Ein Teil steht in der oben genannten Literaturstelle im Roboternetz. Im Prinzip kann man sagen, dass alles precompiliert (= im Voraus übersetzt) werden kann, dass keine Referenzen auf Konstanten oder Variablen enthält. Was vorcompiliert wird - z.B. push, pop, reine Registeroperationen ... - wird in ein OBJ-Statement (z.B. „.OBJ 93CF“) übersetzt, das dann offensichtlich den fertigen Maschinencode in ASCII-Form enthält.
• Für Statements, die Referenzen enthalten, muss am Beginn der Zeile ein „*“-Zeichen angegeben werden. Dadurch wird die Precompilierung verhindert und das Statement erst bei der Compilierung des kompletten Programms übersetzt, wenn diese Referenzen aufgelöst werden können. Da bei heutigen, schnellen PC’s kaum ein Geschwindigkeitsunterschied zu merken sein sollte, würde ich im Zweifelsfall lieber einmal einen „*“ mehr setzen als einen zu wenig. Dieser „Geschwindigkeitsnachteil“ würde allerdings so und so nur zum Tragen kommen, falls man die „*.lbx“ Datei anstelle der „*.lib“ Datei! Mit einem „einfachen *“ können so aber nur einfache Assemblerbefehle vor der „Precompilierung geschützt“ werden – also zum Beispiel das Laden einer Variablen (LDS register,{Varname}). Damit komme ich zum wichtigsten bzw. schwierigsten Punkt, dem Einbinden von Variablen.
• Die Routine einer Library wird ja erst in den Programmcode eingebunden, wenn die Routine tatsächlich gebraucht wird - und eingebunden wird der Code in den Flash-Speicher (ROM). In Assembler kann man zwar an beliebiger Stelle Variablen (RAM) einbinden, allerdings muss man dazu 4 Pseudobefehle einbinden, nämlich
• A.) .DSEG (Datensegment einschalten)
B.) Varname: (Label=Name der Variablen definieren)
C.) .BYTE 2 (Platz für 2 Byte reservieren)
D.) .CSEG (auf Codesegment zurückschalten)
Solch eine komplexe Einbindung war in BASCOM wohl nicht vorgesehen, nicht auf Assembler oder ähnlicher Ebene. Allerdings bin ich davon ausgegangen, dass so etwas wie das Einbinden von Variablen in eine Library möglich sein musste. Und es geht tatsächlich, durch das Voranstellen einer „*BASIC:“ Instruktion an den Anfang einer Zeile (siehe die Library). Hierdurch können offensichtlich (zumindest in rudimentärer Form) Basic- Operationen beim Einbinden einer Library in den Programmcode durchgeführt werden, z.B. die Deklaration von Variablen mittels „DIM“. Hier gibt’s ein paar „ungewohnte“ Einschränkungen. So können keine Arrays direkt indiziert- (wohl aber deklariert) werden oder Kommentare können in diesen Zeilen ebenfalls nicht angegeben werden.
• Die so definierten Variablen werden korrekt eingebunden -(bis auf ein 1-Byte Loch beim Übergang der normalen Variablen im BASCOM-Basic Teil der Variablen zu den Variablen im Library-Teil. Bei der Ausgabe der Variablen im Ergebnisreport tauchen die so definierten Variablen ebenfalls nicht auf. Dass die jedoch korrekt eingebunden und adressiert werden, kann man dem Reassemblat entnehmen.
Wie die Library eingebunden wird bzw. wie die neuen Funktionen aufgerufen werden, geht aus dem BASCOM-Beispielprogramm hervor.
Ich hoffe, der Eine oder andere kann etwas mit diesem post was anfangen. Würde mich freuen.
LG
Werner