UBasic-avr
Aus BraLUG-Wiki
Inhaltsverzeichnis |
Motivation
Welcher Mikrocontroller-Programmierer kennt das Problem nicht: man hat eine schicke Firmware auf den MC gebrannt, braucht schnell eine neue (einfache) Funktionalität und will/kann nicht gleich an den C-Code der Firmware ran. Was liegt also näher, Funktionen beliebig "nachladen" und ausführen zu lassen? Dieses Ansinnen mit Binär-Code-Fragmenten zu machen, dürfte ein schwieriges, wenn nicht sogar unmögliches Unterfangen sein. Und man ist wieder von einem Kompiler abhängig. Script-Sprachen sind da viel besser geeignet, da sie verständlicher und leichter zu programmieren sind. Voraussetzung ist dabei natürlich, dass ein entsprechender Script-Interpreter auf der Zielplattform verfügbar ist.
Jetzt könnte man natürlich einen eigenen und u.U. speziell für Mikrocontroller designden Sprachsyntax entwickeln und implementieren (Bsp. ECMDSript bei ethersex). Viel sinnvoller erscheint es aber, wenn man auf alt bewährte und bekannte Dinge zurückgreift: jeder (alte) Programmierer hat mal mit Basic angefangen oder zumindestens davon gehört! Der Zufall wollte es, dass Adam Dunkle ein C-Gerüst für einen kleinen und ressourcenschonenden TinyBasic-Interpreter (uBasic) veröffentlicht hat. Mit minimalen Modifikationen ist das Ding auch auf einem AVR sofort lauffähig und beeindruckt durch den geringen Ressourcen-Verbrauch.
Auf dieses Gerüst aufsetzend, entsteht hier AVR-uBasic.
Ziel ist es, einen universell einbindbaren Basic-Interpreter zu haben, der in AVR-Programme einfach eingebunden werden kann. Die Basic-Programme sollen dann über vorhandene Schnittstellen (seriell, Ethernet o.ä.) geladen werden oder sind auf einem externen Speichermedium (SD-Card, Dataflash o.ä.) verfügbar und einlesbar. Das ist aber noch Zukunftsmusik. Das jetzige Stadium dieses Projektes ist noch als "pre-alpha" zu bezeichnen. Diese Seite soll zum "Mitüberlegen" anregen. Vielleicht hat ja jemand noch tolle Ideen, Anmerkungen oder auch Kritik...
Testumgebung
(Bild Hardware...)
(Bild serielle Ausgabe...)
Erste Versuche mit dem originalen uBasic kann man auf jeder Plattform machen, für die es auch einen Standard-C-Kompiler gibt. Das funktioniert auf Anhieb. Bei eigenen Experimenten habe ich ein paar kleinere Bugs gefunden und teilweise bereits behoben.
Interessant wird die Sache, wenn man zusätzlich Mikrocontroller-spezifische Befehle implementieren möchte. Dazu braucht man natürlich eine entsprechende Hardware-Umgebung. Ich habe mir dazu ein einfaches Board auf Lochraster mit folgenden Komponenten in der jeweiligen Standardbeschaltung aufgebaut (siehe auch Bild):
- Mega168; 16MHz-Quarz; Reset-Taster
- MAX232 für die serielle Schnittstelle
- ISP-Anschluss, über den die Schaltung auch mit Strom versorgt wird
- ein paar herausgeführte I/O-Pins, um mal einen Taster oder eine LED anschliessen zu können
Über die serielle Schnittstelle können zu Testzwecken Basic-Programme geladen und gestartet werden (gesteuert über die Hauptschleife im Testprogramm). Die Ausgaben (PRINT-Befehl, Debug-Ausgaben) erfolgen ebenfalls über diese Schnittstelle.
Ursprünglich hatte ich damit angefangen den Basic-Quelltext mit in das C-Testprogramm reinzuschreiben. Das ist aber beim Testen diverser Kombinationen von Basic-Elementen schon etwas mühselig: C-Quelltext anpassen, übersetzen, flashen... Deshalb wurde recht schnell eine Möglichkeit geschaffen Basic-Quelltext über die serielle Schnittstelle nachzuladen und zu starten. Die entsprechenden Befehle dazu sind im Quelltext von main.c "dokumentiert" und mit Sicherheit nicht die tollste Lösung, aber zum Testen reicht es erstmal.
Mal abgesehen von den etwas beschränkten Möglichkeiten für Ein-/Ausgabe und Speicher, ist dieses kleine Mikrocontroller-Board leistungsstärker, als ein KC oder ähnliche damalige Home-Computer...;-)
Funktionsweise des Interpreters
Der uBasic-Interpreter unterteilt sich in zwei Komponenten:
- dem Tokenizer (tokenizer.*)
- dem eigentlichen uBasic (ubasic.*)
Der Tokenizer analysiert den zu interpretierenden Code schrittweise nach ihm bekannten Syntaxelementen. Dazu sind in tokenizer.c diverse Schlüsselwörter (keywords[]) und Einzelzeichen (singlechar()) definiert, nach denen der Programmtext durchsucht wird. Desweiteren stellt tokenizer.c diverse Funktionen zur Verfügung, um das aktuelle Token abzufragen, die Tokenanalyse fortzuführen sowie bei einigen Token deren Wert zurückzugeben (String, Variable, Wert etc.).
In ubasic.c ist die vorgeschriebene Reihenfolge der einzelnen Tokenelemente, welche damit den eigendlichen Basic-Syntax ausmachen, und die daraus resultierenden Reaktionen, teilweise über mehrere Prozeduren verteilt, implementiert. Dabei wird hauptsächlich zwischen Statements (statement()) und Expression-Elementen (expr() -> term() -> factor()...) unterschieden.
Einige der Funktionen/Prozeduren werden rekursiv aufgerufen (vorallem expr() in ubasic.c). Dies bringt, gerade bei den beschränkten Ressourcen auf einem Mikrocontroller, die Gefahr mit sich, dass der Stack überlaufen könnte. Hautsächlich könnte dies bei sehr komplexen Expressions auftreten. D.h., also, dem Interpreter bei solchen Problemen nicht zu komplexe Basic-Ausdrücke mit vielen Klammern vorwerfen.
Durch diese sinnvolle und konsquente Aufteilung der Interpreter-Funktionen ist es relativ leicht möglich weiter Syntax-Elemente hinzuzufügen. Der "Durchlauf" durch die einzelnen Interpreter-Elemente ist leicht zu verstehen. Einfach mal den Quelltext lesen!
Einbinden in eigene AVR-Programme
Das Einbinden des Interpreters in eigene Programme geht relativ einfach. Prinzipiell ist ubasic.h entsprechend zu includieren.
Das abzuarbeitende Basic-Programm muss in dem Char-Array stehen, welches der Prozedur ubasic_init() übergeben wird. Wie das Programm in dieses Char-Array gelangt, ist jedem selbst überlassen. Die einzelnen Basic-Zeilen in dem Array müssen mit '\n' (0x0A) abgeschlossen sein.
Zur Abarbeitung des Basic-Programmes ist ungefähr folgender Konstrukt im eigenen Programm einzubauen:
ubasic_init(program); do { ubasic_run(); } while(!ubasic_finished());
ubasic_init() setzt einen internen Pointer auf den Anfang des Programmtextes und initialisiert ein paar weitere interne Variablen.
Die folgende do-while-Schleife wird solange abgearbeitet, bis das Basic-Programm endet, wobei ubasic_run() jeweils immer genau eine Basic-Zeile abarbeitet. Es ist also z.B. denkbar, in dieser Schleife auch noch das zu tun/aufzurufen, was wärend des Basic-Programmlaufes "parallel" im Mikrocontroller abgearbeitet werden soll. Anmerkung: es kann aber kein zweites Basic-Programm parallel laufen!
Derzeitiger Sprachumfang
Siehe auch Datei referenz.txt im Quelltext-Archiv, fuer die genaue Syntaxbeschreibung.
Prinzipiell sind folgende allgemeinen Basic-Elemente vorhanden:
- for next
- goto, gosub
- if then else
- print (auf Standardausgabe)
- Grundrechenoperationen
AVR-spezifische Befehle sind vorhanden bzw. geplant:
- Setzen/Lesen des EEPROM
- Konfiguration/Setzen/Lesen I/O-Ports
- ADC auslesen
Denkbar sind natürlich noch weitere Mikrocontroller-spezifische Befehle. Z.B.:
- 1wire
- TWI (I2C)
- Ausgabe auf LCD
- usw.
Weitere Spracherweiterungen sollten ohne Probleme möglich sein. Siehe dazu auch die Beschreibung zur Funktionsweise des Interpreters bzw. den Quelltext selbst.
Desweiteren wären do-while- oder repeat-until-Schleifen manchmal ganz hilfreich, erfordern aber schon etwas mehr Aufwand bei die Implementierung (und Speicherplatz im wertvollen RAM --> siehe Funktionsweise von for-next oder gosub).
Beispielprogramme (uBasic-avr)
Kommen noch...
Aktuelle Quellen
Kommen noch....!