Laut Wikipedia gibt es die Programmiersprache „BASIC“ jetzt über 50 Jahre. Dem einfachen Syntaxaufbau mag es geschuldet sein, dass auch heute noch neue Projekte damit realisiert werden.
Auch ich habe mit Basic ein paar Jahre programmiert und obwohl höhere Programmiersprachen viele Vorteile besitzen, kann man dennoch auch mit Basic einiges anfangen.
Dieses Projekt zeigt einen Basic-Interpreter, der auf einem 32Bit ARM-M0 System von Infineon läuft und mit dem der User, mit Hilfe von ein paar Buttons und einem Graphic-LCD, einfache Spiele und Anwendungen selbst erstellen kann.
Das ganze habe ich „uPlay“ getauft…abgeleitet von dem benutzten Basic-Interpreter „uBasic“ und als batteriebetriebenen kleinen „Handheld“ designed.
Inhaltsverzeichnis
Einleitung
Das „uPlay“ basiert auf dem Basic-Interpreter „uBasic“ von Adam Dunkels der von mir etwas „erweitert“ wurde. Als CPU-Board kommt ein XMC-2Go von Infineon mit dem XMC1100 Prozessor zum Einsatz. Als Display habe ich ein 84×48 Pixel Grafik-Display benutzt und zur Eingabe gibt es 5 Buttons. Die Hardwarekosten für einen Nachbau belaufen sich auf etwa 15 EUR. Der Aufbau der Hardware sollte an einem regnerischen Wochenende erledigt sein.
- Kostengünstiges CPU Board (ca. 6 EUR)
- Kostengünstiges Grafik-Display (ca. 5 EUR)
- Einfacher Hardwareaufbau
- Eigene Basic-Programme können als Ascii-File per UART vom PC geladen werden
- Kleine Abmessungen
- Batteriebetrieb möglich
Hardware
Als Minimalhardware wird eigentlich nur eine CPU benötigt, wenn das Basic-Programm im Flash vorhanden ist und man kein Display zur Ausgabe benötigt.
Mehr „Spaß“ bringt es natürlich, wenn man ein Display benutzen kann und zur Eingabe ein paar Buttons vorhanden sind. In dieser Beschreibung gehe ich davon aus, das die Hardware so aufgebaut wird, wie im Schaltplan beschrieben. Es kann aber auch eine andere Kombination gewählt werden, wenn die Software entsprechend angepasst wird.
CPU/BOARD
Wie schon geschrieben habe ich das ganze für das XMC2-2Go Board von Infineon und die XMC1100 CPU programmiert. Prinzipiell kann hier auch jede andere CPU benutzt werden, solange genug RAM für das Basic-Programm vorhanden ist und sie schnell genug ist um die Anwendung auch in einer vernünftigen Zykluszeit abzuarbeiten.
Hier ein paar Eckdaten der XMC1100 :
- CPU : 32bit ARM-M0
- Clock : 32 MHz
- Flash : 64 kByte
- Ram : 16 kByte
Mit dieser CPU liegt die Zykluszeit von einer Basic-Zeile (je nach Umfang) bei ca. 500 us.
Am XMC-2Go Board sind nur 14 GPIO-Pins an Pfostenleisten zugänglich, was den Einsatz der restlichen Hardware natürlich einschränkt. Aber wie an dem Projekt zu sehen ist, reicht es trotzdem für alle Funktionen.
DISPLAY
Als Display habe ich das vom Nokia Handy 5110 benutzt. Das ist günstig zu bekommen, hat eine Auflösung von 84×48 Pixel und wird per SPI-Schnittstelle betrieben. Als LCD-Controller ist ein PCD8544 verbaut.
Ein weiterer Vorteil von dem Display ist, das für einen kompletten Refresh vom Grafik-Inhalt nur 504 Bytes Daten übertragen werden müssen. Bei der max SPI-Frq die das Display mitmacht (4MHz), dauert das rechnerisch nur 1ms.
Zum Betrieb des Displays werden 5 GPIO Pins benötigt :
- MOSI = P0.7
- CLK = P0.8
- CS = P0.9
- D/C = P0.14
- RESET = P0.15
Um Strom zu sparen, habe ich die Hintergrundbeleuchtung per Jumper abschaltbar gemacht.
Break-Button
Der MISO Pin der SPI-Schnittstelle wird für das Display nicht benötigt. Damit dieser PIN nicht unbenutzt bleibt, habe ich daran einen „Break-Button“ angeschlossen mit dem man ein laufendes Basic-Programm abbrechen kann.
Der Pin hat einen internen PullUp, es reicht also einen Taster gegen GND einzubauen.
Der Status vom Button wird automatisch bei jedem refresh vom Display abgefragt. Falls er nicht betätigt ist, ist der Rückgabewert 0xFF. Falls er betätigt ist, wird eine 0x00 zurückgeliefert.
- Break-Button = P0.6
ADC
Einen Pin der CPU habe ich als 10bit ADC-Eingang initialisiert. Dieser kann vom Basic-Programm aus abgefragt werden. In wie weit man das in seiner Anwendung benutzen will, bleibt jedem selbst überlassen.
Im „uPlay“ wird der ADC nicht benutzt und ist nur im Schaltplan vorhanden.
Beim abfragen vom Basic-Programm aus wird ein Integer Wert von 0…1023 zurückgeliefert, was einer Spannung von 0 bis 3,3V entspricht.
Die Umrechnungsformel „ADC->VOLT“ lautet demnach : Spannung = 3,3V / 1023 x ADC_WERT
- ADC-0 = P2.6
GPIOs
Von den 14 Pins des XMC-2Go sind 7 vom Display und ADC belegt, bleiben also noch 7 GPIOs übrig. Um Spiele zu realisieren braucht man mindestens 5 Buttons „Steuerkreuz + Enter“ als Eingabe.
Ich habe das „uPlay“ also mit 5 Inputs und 2 Outputs designed. Wer das ganze Projekt für andere Zwecke benutzen will, kann das natürlich abändern wie er lustig ist.
Beachtet werden muss nur, das nicht jeder Pin vom XMC-2Go als Ausgang geschaltet werden kann :
- P0.0 = STD_IN / STD_OUT
- P0.5 = STD_IN / STD_OUT
- P2.0 = STD_IN / STD_OUT / ADC_IN
- P2.7 = STD_IN / ADC_IN
- P2.9 = STD_IN / ADC_IN
- P2.10 = STD_IN / STD_OUT / ADC_IN
- P2.11 = STD_IN / STD_OUT / ADC_IN
INPUTS
Damit User-Eingaben ausgewertet werden können, habe ich 5 Buttons angeschlossen. 4 als „Steuerkreuz“ und 1 „Enter“.
Per Software sind die PullUps der CPU aktiviert also reichen Taster gegen GND zum schalten. Daraus folgt, das ein nicht betätigter Button eine „1“ beim einlesen vom Basic-Programm zurückliefert und ein gedrückter Button eine „0“.
- Button-0 (UP) = P2.10
- Button-1 (DOWN) = P0.0
- Button-2 (LEFT) = P2.9
- Button-3 (RIGHT) = P2.11
- Button-4 (ENTER) = P2.7
OUTPUTS
Zwei der GPIO-Pins habe ich als Digital-Ausgang initialisiert, um z.B. eine LED oder ein Piepser anzuschließen.
Die Outputs sind als PushPull geschaltet. Im „uPlay“ werden die Outputs nicht benutzt und sind nur im Schaltplan vorhanden. (laut Datenblatt sind die PortPins nur bis max 10mA belastbar)
- Output-0 = P0.5
- Output-1 = P2.0
UART
Die UART vom XMX-2Go Board wird benötigt um Basic-Programme nachzuladen. Sie wird auch im laufenden Basic-Programm als Standardausgabe der „PRINT“ Befehle benutzt.
Die Schnittstelle wird mit 115200 Baud initialisiert mit dem Frame-Parameter „8N1“.
- TXD = P2.1
- RXD = P2.2
Spannung/Strom
Als Spannungsversorgung ist eine 3V Lithium Knopfzelle mit 550mAh vorgesehen (CR-2450). Wenn der USB Stecker an den PC angeschlossen ist, wird das Board per USB versorgt.
Eigentlich sollte man am Board, wenn es per USB versorgt wird, keine externe Spannung an die Stiftleiste anlegen. Ich habe aus dem Grund zum Schutz eine Diode vor der Batterie vorgesehen. Ein Jumper zum trennen der Batterie ist auch vorhanden (weil diese trotz Diode leergesaugt wird).
Die Stromaufnahme vom Board+Display (ohne Hintergrundbeleuchtung) beträgt ca. 9 mA.
Entwicklungsumgebung
Als Entwicklungsumgebung habe ich KEIL uVision5 benutzt. Davon gibt es eine kostenlose Version zum download die auch das XMC-2Go Board unterstützt. Die max. 32k Flash Einschränkung ist für dieses Projekt nicht relevant.
Unter dem gleichen Link wie uVision gibt es auch den USB-Treiber für das Debug-Interface von Segger „JLink“
uPlay
Die Software besteht im Prinzip aus zwei Teilen :
1. Dem „uPlay“ System das die angeschlossene Hardware kontrolliert (LCD, Buttons, UART usw). Über dieses System kann ein Basic-Programm per UART nachgeladen werden und dieses startet dann auch den Basic-Interpreter.
2. Dem Basic-Interpreter „uBasic“ der ein Basic-Program das im RAM liegt übersetzt und abarbeitet.
Startmenu/Kommunikation
Nach dem PowerOn vom „uPlay“ steht das System im „Standby-Mode“ und es wird ein Menu auf dem Display angezeigt.
Man kann hier entweder ein Basic-Programm aus dem internen Flash der CPU starten oder ein schon geladenes Programm aus dem RAM. Die Auswahl erfolgt durch die Buttons „Left/Right/Enter“.
Zur Demo habe ich eine Version von dem Spiel „Snake“ programmiert und als Basic-Programm ins Flash gepackt. Das Programm liegt als Quellcode in der Datei : „bas_snake.h“.
Alternativ kann auch eine UART Verbindung aufgebaut werden. Mit dem UART-Befehl „HELP“ kann man die Versions-Nummer vom „uPlay“ und vom „uBasic“ auslesen.
Basic-Programm download
Um ein Basic-Programm zum „uPlay“ zu senden, muss dieses zuerst im „Standby-Mode“ stehen. Danach kann mit dem UART-Befehl „LOAD“ in den „Load-Mode“ gewechselt werden. (Es wird jetzt auf die Übertragung von einem Basic-Programm gewartet)
Während der Übertragung wird die Anzahl der empfangenen Zeilen auf dem Display angezeigt. Um das Ende der Übertragung zu signalisieren muss ein „END“ übertragen werden. Damit wird wieder in den „Standby-Mode“ gewechselt.
Das Programm steht jetzt im RAM und kann entweder mit dem UART-Befehl „RUN“ oder über die Buttons gestartet werden.
Hinweis : das Basic-Programm wird solange abgearbeitet, bis es zu dem Basic-Befehl „END“ kommt oder der „Break-Button“ betätigt wird.
Hier ein Bild wie ein Download per UART und die Ausgabe dazu aussieht. Das ganze funktioniert auch nur mit dem reinen XMC-2Go (also auch ohne Display und Buttons).
Status-LEDs
Die beiden LEDs auf dem XMC-2Go zeigen den aktuellen Status vom System an :
- LED1 blinkt zyklisch einmal pro Sekunde (Heartbeat)
- LED2 blinkt 3mal pro Sekunde wenn ein Basic-Programm abgearbeitet wird
DISPLAY
Für das Display gibt es einen 504 Byte großen Graphic-Puffer im RAM (84 x 48 Pixel). Die Befehle aus dem Basic-Programm schreiben/lesen in diesen Puffer und alle 100ms wird der komplette Inhalt vom Puffer zum Display gesendet.
Im Moment ist nur eine Schriftart implementiert in der Größe 6×8 Pixel. Damit passen 6 Zeilen auf das Display mit je 14 Zeichen.
ADC
Der ADC wird im Single-Conversation-Mode betrieben, wird also bei jedem Aufruf vom Basic-Programm aus neu gestartet.
GPIOs
Die Basic-Befehle für Digital-IN, Digital-OUT werden direkt an die entsprechenden GPIOs weitergegeben.
Durch die relativ langsame Zykluszeit vom Basic-Programm ist ein entprellen der Eingänge nicht notwendig. (zumindest denke ich das 🙂
UART
Die UART ist die standard Ausgabe der „PRINT“-Befehle vom Basic-Programm aus.
Ein „10 PRINT 123“ sendet also den String „123“ über die UART. Als Stringendekennung wird ein CarriageReturn+Linefeed (0x0D,0x0A) angehängt.
Beim senden von Daten vom PC an das „uPlay“ muss der PC ein CarriageReturn (0x0D) am Ende von jedem String anhängen.
uBasic
Der Basic-Interpreter von Adam Dunkels ist sehr einfach aufgebaut und die Funktionsweise ist auch ohne Dokumentation leicht per Debugger zu verstehen.
- uBascic von Adam Dunkels[8]
Dieser Interpreter wurde schon von vielen als Basis benutzt. Bekannt aus diesem Forum sind z.B. die Portierungen für den AVR von Rene Boellhoff und Uwe Berger.
- AVR-Basic[9]
Ich selbst habe nur mit der original Version von Adam Dunkels gearbeitet und wollte diese so wenig wie möglich abändern um den einfachen Aufbau nicht zu verlieren.
Der Interpreter besteht nur aus 4 Files : Dem „uBasic.c.+h“ File und dem „Tokenizer.c+h“ File.
Im „Tokenizer“ sind alle Basic-Sprachbefehle hinterlegt. Dieser liefert beim Aufruf der Funktion „get_next_token“ zurück, um welchen Befehl oder welches Zeichen es sich im geladenen Basic-Programm gerade handelt. Der Rückgabewert besteht aus einem einzelnen Bytewert, dem „Token“.
Um z.B. die Basic-Zeile „10 PRINT a“ zu zerlegen, müsste man den Tokenizer 3mal aufrufen und dieser würde 3 Tokens zurückliefert mit den Namen : „LINENUM“ , „PRINT“ , „VARIABLE“
Das „uBasic“ kümmert sich um das aufrufen vom Tokenizer und entscheidet je nach empfangenen Token was daraufhin gemacht werden soll. Hier sind die Funktionen aller Basic-Befehle „ausprogrammiert“ das bedeutet in unserem Beispiel würde die Funktion „print“ angesprungen werden, der Inhalt der Variable „a“ würde ausgelesen und per UART versendet werden.
Das ganze ist recht simpel gestrickt und kann dementsprechend leicht geändert und angepasst werden.
Anpassungen
Die ersten Änderungen die ich am uBasic gemacht habe, waren reine Bugfixes :
- Der Variablen Name „a“ konnte nicht benutzt werden
- Die Funktion „IF/THEN/ELSE“ hat nicht richtig gearbeitet
Als zweiten Schritt habe ich das uBasic etwas „erweitert“ :
- Keine Unterscheidung zwischen Groß- und Kleinschreibung der Basic-Befehle (z.B. „10 IF a>5 Then goto 100 else GoSub 200“)
- Bei der IF-Anweisung kann das THEN entfallen (z.B. „10 IF a>5 GOTO 100“)
- Variablen und Ausdrücke können negativ sein (z.B. „10 a = -5“)
- Den Wertebereich der Variabeln von char auf int vergrößert
- Die FOR/NEXT-Schleife so angepasst, das sie auch decrementieren kann (z.B. „10 FOR a=8 TO 2“)
- Die FOR/NEXT-Schleife um „STEP“ erweitert. (z.B. „10 FOR a=2 TO 13 STEP 3“)
- Den Befehl „REM“ hinzugefügt für Bemerkungstexte (z.B. „10 REM -BEISPIELPROGRAMM-„)
Der dritte Schritt war das vergrößern vom Basic-Sprachumfang um die angeschlossene Hardware zu steuern. Dazu gehören folgende Punkte :
- System-Befehle : PAUSE / CLRTIC / GETTIC
- LCD-Befehle : INVERSE / CLS / LCD / SETPX / CLRPX / GETPX / LINE / RECT / FILL / CIRCLE / CLRCOLL / GETCOLL
- GPIO-Befehle : IN / OUT
- ADC-Befehle : ADC
Verbesserungen
Hier wäre eigentlich die Arbeit am Basic-Interpreter fertig gewesen (und hat auch soweit funktioniert) wenn nicht die Durchlaufzeit der Basic-Programme erbärmlich langsam gewesen wäre.
Der Tokenizer der das Basic-Ascii-Programm durchackert muss jedes einzelne Zeichen untersuchen um aus dem String „print“ (in allen möglichen Schreibweisen) das Token „PRINT“ zu ermitteln. Und das bei jedem Durchlauf obwohl sich der Befehl nie mehr ändert.
Auch das Suchen der Zeilen-Nummern bei „GOTO/GOSUB/NEXT/RETURN“ verbraucht immens viel zeit, weil jedes Mal von der ersten Zeile des Basic-Programmes aus gesucht wird.
Ich habe mich darauf hin entschlossen diese zwei Sachen zu verbessern und einen „Pre-Parser“ einzubauen, der vor dem eigentlichen Programmstart einmal abgearbeitet wird. Dieser Eingriff ändert die Original-Version nur minimal aber das Ergebnis ist um ein vielfaches besser.
Der Pre-Parser ist 3 Teilig aufgebaut und hat folgende Aufgaben :
1. Schritt : Ersetzung aller Basic-Keywords
Zuerst wird das komplette Basic-Programm nach Ascii-Keywords durchsucht (z.B. „PRINT“ oder „GOTO“) und durch einen zwei Byte Hex-Code ersetzt. Da jedes Keyword mindestens zwei Zeichen lang sein muss, ist das kein Problem. Das erste Byte ist eine Kennungs-ID mit dem Wert 0x01. An dieser Kennung erkennt der Parser später das es sich um eine Ersetzung handelt. Das zweite Byte ist direkt die Array-Nummer vom gefundenen Keyword. Das bedeutet der Parser muss später nicht mit einer For-Schleife das passende Keyword suchen, sondern kann direkt mit der Array-Nummer arbeiten. (damit es zu keiner Verwechslung mit anderen Ascii-Zeichen kommt, wird zusätzlich das Bit7 gesetzt)
2. Schritt : Speichern der Sprungziele
In diesem Schritt wird das Basic-Programm nach Keywords durchsucht, die ein Sprung zur folge haben (also „GOTO“,“GOSUB“,“FOR“). Bei so einem Keyword, wird das Sprungziel (bzw. Rücksprungziel) als Zeilen-Nummer in einem Array gespeichert. Doppelte Einträge werden abgefangen.
3. Schritt : Suchen der Pointer-Adressen der Sprungziele
Hier wird das Programm noch mal durchsucht und zwar um rauszufinden welche Programm-Adresse welchem Sprungziel entspricht. Es werden also zu allen gefundenen Sprungzielen die passenden Einsprungadressen gespeichert. Später im eigentlichen Programmlauf wird dann z.B. bei einem „GOTO 100“ im Array nachgesehen welche Pointeradresse die Zeilen-Nummer 100 hat und es wird direkt dort mit dem Programmlauf weitergemacht.
Ergebnis
Das Ergebnis vom Pre-Parsing ist eine enorme Geschwindigkeitsverbesserung im Vergleich zur Ur-Version von Adam Dunkels. hier eine Vergleichsmessung mit zwei For-Schleifen die 1000 mal eine Subroutine mit einer Print-Anweisung aufrufen :
10 REM ============= 15 REM Speed-Test 20 REM ============= 25 PRINT "Start" 30 CLRTIC 35 FOR n = 0 to 10 40 FOR m = 0 to 100 45 GOSUB 100 50 NEXT m 55 NEXT n 60 GETTIC a 65 PRINT "Stop" 70 PRINT a,"ms" 75 END 80 REM -------------- 100 PRINT n,m 110 RETURN |
Dieses Programm dauert ohne Pre-Parser 45 Sekunden und mit Pre-Parser nur noch 1,8 Sekunden ! Das ist 25 mal so schnell. Mit dieser Geschwindigkeit lassen sich auch kleine Spiele realisieren.
Syntax
Hier eine kurz Zusammenfassung über die Syntax vom uBasic :
- Jede Zeile muss mit einer eindeutigen Zeilen-Nummer beginnen
- In jeder Zeile darf nur ein Befehl stehen (Ausnahme : IF/ELSE)
- Variabeln-Namen bestehen aus einem einzelnen Kleinbuchstaben
- Es dürfen nur Ganzzahlen verwendet werden (-99999 bis 99999)
- Sprungziele müssen aus reinen Zahlen bestehen (keine Variablen oder Rechnungen)
- Strings werden in Anführungszeichen gefasst (z.B. 10 PRINT „Hallo“)
- Rechenoperatoren : -,+,*,/,%
- Vergleichsoperatoren : =,<,>
- Logik-Operationen : &,|
- Klammern : (,)
Es folgt eine Liste aller im Moment unterstützten Basic-Keywords :
Grundbefehle | Bedeutung | Beispiel |
---|---|---|
REM | Kommentar | 10 REM Testprogramm |
LET | Variablenzuweisung (optional) |
10 LET a=5 20 b = -4 |
Ausgabe per UART | 10 PRINT „Hallo“ 20 PRINT 123 |
|
IF/THEN/ELSE | Bedingung (THEN ist optional) |
10 IF a>3 THEN PRINT b 20 IF a>5 PRINT „NEIN“ ELSE GOTO 100 |
FOR/TO/STEP/NEXT | Schleife (STEP ist optional) |
10 FOR i=2 TO 20 STEP 5 20 PRINT i 30 NEXT i |
GOTO | Sprung | 10 GOTO 100 |
GOSUB/RETURN | Sprung mit Rücksprung |
10 IF a>3 GOSUB 1000 20 PRINT „nach Return“ 30 END 1000 PRINT „Subroutine“ 1010 RETURN |
END | Ende vom Basic Programm | 10 PRINT a 20 END |
System-Befehle | Bedeutung | Beispiel |
PAUSE | Pause (Zeit in ms) |
10 PAUSE 100 |
CLRTIC | löschen vom Timer | 10 CLRTIC |
GETTIC | auslesen vom Timer (Zeit in ms) |
10 GETTIC a 20 PRINT a |
LCD-Befehle | Bedeutung | Beispiel |
INVERSE | Mode der Ausgabe 0=normal 1=invers |
10 INVERS 0 |
CLS | ClearScreen | 10 CLS |
LCD | Ausgabe auf LCD (an x,y Position) |
10 LCD x,y,“Hallo“ 20 LCD 0,0,123 |
SETPX | setzen eines Pixels (an x,y Position) |
10 SETPX x,y |
CLRPX | löschen eines Pixels (an x,y Position) |
10 CLRPX x,y |
GETPX | auslesen eines Pixels (an x,y Position, Wert=[0,1]) |
10 GETPX a=x,y 20 PRINT a |
LINE | zeichnen einer Line (von x,y nach a,b) |
10 LINE x,y,a,b |
RECT | zeichnen eines Rechtecks (von x,y nach a,b) |
10 RECT x,y,a,b |
FILL | füllen eines Rechtecks (von x,y nach a,b) |
10 FILL x,y,a,b |
CIRCLE | zeichnen eines Kreises (an x,y mit Radius r) |
10 CIRCLE x,y,r |
CLRCOLL | loeschen vom Kollisions Ergebnis vor einer Zeichenoperation |
10 CLRCOLL 20 SETPX 5,6 |
GETCOLL | auslesen vom Kollisions Ergebnis nach einer Zeichenoperation (1=kollision) |
10 CLRCOLL 20 SETPX 5,6 30 GETCOLL a 40 PRINT a |
GPIO-Befehle | Bedeutung | Beispiel |
IN | einlesen eines Input-Kanals (c=Channel [0…4], Wert=[0,1]) |
10 IN a=c 20 PRINT a |
OUT | setzen eines Output-Kanals (c=Channel [0…1], Wert=[0,1,2]) |
10 OUT c,1 |
ADC-Befehle | Bedeutung | Beispiel |
ADC | einlesen eines ADC-Kanals (c=Channel [0], Wert=[0…1023]) |
10 ADC a=c 20 PRINT a |
Befehlsbeschreibungen
Die meisten Befehle sollten man auch ohne Beschreibung verstehen. Hier ein paar Hinweise für Befehle die ich mir selbst ausgedacht habe und deren Funktionsweise nicht so offensichtlich sind :
„PRINT“
Beim „PRINT“ können mehrere Ausgaben hintereinander gehängt werden z.B. <10 PRINT „Zeit=“ a „ms“> ergibt „Zeit=123ms“. Mit einem „,“ kann ein Space als Trennzeichen eingefügt werden <10 PRINT „Zeit=“,a,“ms“> ergibt „Zeit = 123ms“. Ein „;“ am Zeilenende verhindert das senden vom Linefeed.
„CLRTIC“,“GETTIC“
Der Befehl „GETTIC“ liefert die Zeit in ms die seit dem letzten „CLRTIC“ vergangen ist. Die Zeitmessung ist nicht besonders genau, weil der interne Quarz der CPU nicht sehr exakt läuft.
Dieser Befehl kann auch als „Zufallsgenerator“ missbraucht werden. z.B. durch Auswertung vom niederwertigsten Bit beim einlesen von GETTIC (da die Umlaufzeit nie 100% gleich ist.
„IN“
Der Befehl „IN“ erwartet eine Kanal-Nummer, um die 5 verschiedenen INPUT-Pins zu unterscheiden. Ein „10 IN a=0“ ließt also den INPUT-Pin mit der Kanal-Nr „0“ ein.
„OUT“
Der Befehlt „OUT“ erwartet wie „IN“ eine Kanal-Nummer und zusätzlich den Pegel auf den geschaltet werden soll. Wobei „0=Lo_Pegel“, „1=Hi_Pegel“, „2=Pegel_toggeln“ entspricht
„CRLCOLL“,“GETCOLL“
Um eine Kollision beim zeichnen zu erkennen, muss der Kollisionsmerker zuerst gelöscht werden mit „CLRCOLL“ und nach dem zeichnen kann dann das Ergebnis per „GETCOLL“ ausgelesen werden. Eine Kollision hat dann stattgefunden, wenn ein Pixel auf dem LCD gesetzt wurde, das vorher schon gesetzt war.
Standalone Version
Wer den reinen Basic-Interpreter benutzen will (z.B. auf einer anderen CPU) kann das relativ einfach machen.
1. Einbinden der Files
Man braucht die 4 Files (uBasic.c+h, Tokenizer.c+h) die man in sein Projekt einbinden muss. Diese befinden sich im uPlay-Projekt im Unterordner „uBasic“.
2. RAM-Puffer
Dann wird zum speichern von einem Basic-Programm ein RAM-Buffer benötigt
char ram_buffer[1024]; // 1k Puffer fuer Basic-Programm |
In diesen RAM-Buffer muss man irgendwie das Basic-Programm bekommen (z.B. durch kopieren aus dem Flash, oder per UART von einem PC usw)
3. Notwendige Funktionen
Was jetzt noch fehlt sind die Funktionen die vom Basic-Programm aus aufgerufen werden können und die Hardware betreffen.
Dazu sind diese Prototypen (und die entsprechenden Funktionen) notwendig :
void XMC2GO_Systic_Pause_ms(uint32_t ms); void XMC2GO_Systic_ClrTic(void); uint16_t XMC2GO_Systic_GetTic(void); void XMC2GO_LCD_Inverse(uint8_t mode); void XMC2GO_LCD_Clear(void); void XMC2GO_LCD_SetPixel(uint8_t xpos, uint8_t ypos); void XMC2GO_LCD_ClearPixel(uint8_t xpos, uint8_t ypos); uint8_t XMC2GO_LCD_ReadPixel(uint8_t xpos, uint8_t ypos); void XMC2GO_LCD_DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2); void XMC2GO_LCD_DrawRect(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2); void XMC2GO_LCD_DrawFill(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2); void XMC2GO_LCD_DrawCircle(uint8_t x0, uint8_t y0, uint8_t radius); void XMC2GO_LCD_DrawString(uint8_t x, uint8_t y,char *ptr, UB_Font *font); void XMC2GO_LCD_ClearCollision(void); uint8_t XMC2GO_LCD_GetCollision(void); uint8_t XMC2GO_DIn_Read(uint8_t din_name); void XMC2GO_DOut_Lo(uint8_t dout_name); void XMC2GO_DOut_Hi(uint8_t dout_name); void XMC2GO_DOut_Toggle(uint8_t dout_name); uint16_t XMC2GO_Adc_Read(uint8_t adc_nr); |
Natürlich müssen nicht alle Funktionen benutzt werden. Wer z.B. gar kein LCD anschließen will, kann alle Befehle die das LCD betreffen aus dem Quellcode löschen.
4. Interpreter starten
Zum Schluss muss der Interpreter gestartet werden, dazu sind eigentlich nur 5 Zeilen Code notwendig :
// init vom Basic-Programm (und start vom Pre-Compiler) ubasic_init(ram_buffer); if(!ubasic_failed()) { // abarbeiten vom Basic-Programm do { ubasic_run(); } while(!ubasic_finished()); } |
Für den Fall eines Endlosprogrammes z.B. („10 GOTO 10“) sollte man noch eine Möglichkeit finden die DO-WHILE zu beenden.
Einschränkungen
Natürlich gibt es bei einer so einfachen Umsetzung eines Basic-Interpreters einige Einschränkungen :
- Wegen dem Pre-Parser muss das Basic-Programm im RAM liegen
- Nur 26 Variabeln nutzbar (a…z)
- Keine „Float“ Unterstützung
- Systembedingt keine sehr hohe Abarbeitungsgeschwindigkeit
Nachbau
Mit Hilfe vom Schaltplan sollte ein Nachbau eigentlich keine Probleme machen, so viele Bauteile sind es ja nicht.
Hier ein paar Bilder von meinem Aufbau :
Größe
Die Platine hat die Abmessungen : 42mm x 70mm.
Platzierung
Die obere Hälfte nimmt das Display ein. Dadurch das es gesockelt ist, bleibt unter dem Display genug Platz für das XMC-2Go und der Batterie bzw dem Batteriehalter.
Auf der unteren Hälfte sind die 6 Buttons verteilt. Rechts habe ich (liegend) die zwei Jumper für die Hintergrundbeleuchtung und die Batterie angebracht.
Die Kondensatoren und die Pins für die 3 nicht benötigten GPIOs (2xOUT, 1xADC) habe ich der einfachheit halber gar nicht bestückt.
XMC-2Go
Die freie Höhe unter dem Display ist begrenzt. Man kann das XMC-2Go entweder direkt auf die Platine löten oder (wie ich) die Leisten von IC-Sockeln benutzen um das Board trennbar zu halten.
Da ich die Sockel vor dem Aufbau vom „uPlay“ schon in das XMC-2Go gelötet hatte und ich die nicht mehr rauslöten wollte, hab ich in meiner Version das ganze „über Kopf“ montiert. Das sieht zwar etwas komisch aus aber funktioniert genauso.
Display
Das Display verdeckt, nach dem es aufgesteckt ist, das XMC-2Go und die Batterie.
Funktionstest
Hier ein Basic-Programm, das die Funktion aller 5 Buttons testet.
10 REM ================= 15 REM uPlay Button-Test 20 REM ================= 30 CLS 35 INVERSE 1 40 LCD 6,0, " uPlay " 45 LCD 6,10,"Button-Test" 50 INVERSE 0 100 IN a=0 110 IF a=0 LCD 15,30,"U" ELSE LCD 15,30,"-" 120 IN a=1 130 IF a=0 LCD 25,30,"D" ELSE LCD 25,30,"-" 140 IN a=2 150 IF a=0 LCD 35,30,"L" ELSE LCD 35,30,"-" 160 IN a=3 170 IF a=0 LCD 45,30,"R" ELSE LCD 45,30,"-" 180 IN a=4 190 IF a=0 LCD 55,30,"E" ELSE LCD 55,30,"-" 200 GOTO 100 210 END |
SNAKE
Hier das Basic-Programm zum bekannten SNAKE Spiel.
10 REM == SNAKE == 20 REM == V : 1.0 UB == 30 REM ============== 41 REM Info : 42 REM s = speed 43 REM x,y = Player position 44 REM r = Player direction 45 REM a,b = Computer position 46 REM c = computer direction 47 REM l = distance of wall check 48 REM i = local variable 49 REM ============== 50 s=50 99 REM ============== 100 x=10 110 y=24 120 r=2 130 a=74 140 b=24 150 c=4 160 l=5 197 REM ============== 198 REM startscreen 199 REM ============== 200 CLS 210 RECT 0,0,83,47 215 LCD 10,10,"-SNAKE-" 220 LCD 10,20,"SPEED=" 230 LCD 50,20,s 240 PAUSE 1000 245 CLS 250 RECT 0,0,83,47 497 REM ============== 498 REM game 499 REM ============== 500 GOSUB 1000 510 PAUSE s 520 GOSUB 2000 530 GOSUB 3000 540 GOSUB 5000 550 GOTO 500 997 REM ============== 998 REM zeichnen 999 REM ============== 1000 CLRCOLL 1010 SETPX x,y 1020 GETCOLL i 1030 IF i=1 GOTO 4000 1040 SETPX a,b 1050 GETCOLL i 1060 IF i=1 GOTO 6000 1070 RETURN 1997 REM ============== 1998 REM bewegen 1999 REM ============== 2000 IF r=2 x=x+1 2010 IF r=4 x=x-1 2020 IF r=1 y=y-1 2030 IF r=3 y=y+1 2040 IF c=2 a=a+1 2050 IF c=4 a=a-1 2060 IF c=1 b=b-1 2070 IF c=3 b=b+1 2080 RETURN 2997 REM ============== 2998 REM tastatur 2999 REM ============== 3000 IN i=1 3010 IF i=0 GOTO 3100 3020 IN i=0 3030 IF i=0 GOTO 3200 3040 IN i=3 3050 IF i=0 GOTO 3300 3060 IN i=2 3070 IF i=0 GOTO 3400 3090 RETURN 3100 IF r=1 RETURN ELSE r=3 3110 RETURN 3200 IF r=3 RETURN ELSE r=1 3210 RETURN 3300 IF r=4 RETURN ELSE r=2 3310 RETURN 3400 IF r=2 RETURN ELSE r=4 3410 RETURN 3997 REM ============== 3998 REM verloren 3999 REM ============== 4000 LCD 0,0,"LOOSE" 4030 PAUSE 2000 4040 END 4997 REM ============== 4998 REM computer move 4999 REM ============== 5000 IF c=4 GOTO 5100 5010 IF c=2 GOTO 5200 5020 IF c=1 GOTO 5300 5030 IF c=3 GOTO 5400 5090 RETURN 5100 GETPX i=a-l,b 5110 IF i=0 RETURN 5115 GETTIC i 5120 i=i&1 5125 IF i=0 c=1 ELSE c=3 5130 RETURN 5200 GETPX i=a+l,b 5210 IF i=0 RETURN 5215 GETTIC i 5220 i=i&1 5225 IF i=0 c=3 ELSE c=1 5230 RETURN 5300 GETPX i=a,b-l 5310 IF i=0 RETURN 5315 GETTIC i 5320 i=i&1 5325 IF i=0 c=2 ELSE c=4 5330 RETURN 5400 GETPX i=a,b+l 5410 IF i=0 RETURN 5415 GETTIC i 5220 i=i&1 5425 IF i=0 c=4 ELSE c=2 5430 RETURN 5997 REM ============== 5998 REM gewonnen 5999 REM ============== 6000 LCD 0,0,"WIN" 6030 PAUSE 2000 6040 IF s>5 s=s-5 6050 GOTO 100 |
Ausblick
Weil mir das ganze Projekt so viel Spaß gemacht hat, und es nicht sehr viel mehr Aufwand ist, habe ich das ganze noch auf die STM32F4, STM429 und STM32F7 CPU portiert:
- 95-uBasic-Library (STM32F4)
- 28-uBasic-Library (STM32F429)
- 12-uBasic-Library (STM32F746)
Anmerkungen
Wie immer bei „Bastelprojekten“ können Fehler in der Hardware und Software nicht ausgeschlossen werden. Ich habe zwar viele Testprogramme geschrieben um das uBasic zu überprüfen aber es sind mit Sicherheit noch BUGs vorhanden.
Wer Fehler findet (egal welcher Art) oder wem Verbesserungen einfallen kann mir ja eine EMail schreiben (oder per PN eine Nachricht schicken)
Ich würde mich auch über Feedback freuen, wenn jemand das „uPlay“ erfolgreich nachgebaut und ein eigenes Basic-Programm damit realisiert hat.
Gruß und viel Spaß mit dem Projekt.
-Uwe-
Downloads
- Schaltplan Datei:UPlay v100.pdf
- Projektfiles Datei:Xmc2go uPlay v100.zip
Web Ressourcen/Einzelnachweise
- http://www.infineon.com/dgdl/Board_Users_Manual_XMC_2Go_Kit_with_XMC1100_R1.0.pdf?folderId=db3a30433580b3710135a47f3eb76c98&fileId=db3a3043444ee5dc014453d6c75078c6
- http://www.infineon.com/dgdl/xmc1100_ds_v1.2_2013_12.pdf?folderId=db3a30433580b3710135a47f3eb76c98&fileId=db3a30433d1d0bbe013d256b60160b7f
- http://www.infineon.com/dgdl/xmc1100_rm_v1+0_2013_03.pdf?folderId=db3a30433580b3710135a47f3eb76c98&fileId=db3a30433cfb5caa013d1600856033eb
- https://www.sparkfun.com/datasheets/LCD/Monochrome/Nokia5110.pdf
- https://www.sparkfun.com/products/10168
- http://www2.keil.com/infineon/mdk/
- http://mikrocontroller.bplaced.net/wordpress/?page_id=580
- http://dunkels.com/adam/ubasic/
- http://www.mikrocontroller.net/svnbrowser/avr-basic/
Hinweis: Dieses Projekt habe ich als Artikel bei Mikrocontroller.net veröffentlicht:
UPlay Basic Interpreter per XMC2Go