UPlay Basic Interpreter per XMC2Go

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

von Benutzer:derexponent (Becker Uwe)

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.

Fertig aufgebautes uPlay

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 84x48 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.

  • Manual vom XMC-2Go[1]
  • Datenblatt vom XMC1100[2]
  • RefManual vom XMC1100[3]

DISPLAY

Als Display habe ich das vom Nokia Handy 5110 benutzt. Das ist günstig zu bekommen, hat eine Auflösung von 84x48 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.

  • Datenblatt vom PCD8544 [4]
  • Bezugsquelle vom Nokia5110 Display [5]

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"

  • KEIL uVision5 [6]
  • Installationsanleitung [7]

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).

Test per Terminal-Programm

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 6x8 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.

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)
  • Variablen-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 :

uBasic-Keywords
Grundbefehle Bedeutung Beispiel
REM Kommentar 10 REM Testprogramm
LET Variablenzuweisung
(optional)
10 LET a=5
20 b = -4
PRINT 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.

Platzierung der Bauteile


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.

XMC 2Go
uPlay mit XMC 2Go

Display

Das Display verdeckt, nach dem es aufgesteckt ist, das XMC-2Go und die Batterie.

Das fertige uPlay

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


Ausblick

Weil mir das ganze Projekt so viel Spaß gemacht hat, und es nicht sehr viel mehr Aufwand ist, werde ich das ganze wahrscheinlich noch auf eine STM32F4 CPU portieren.

Dadurch sollte sich die Zykluszeit noch mal mindestens um den Faktor 5 erhöhen.

Anmerkung: Inzwischen habe ich das Ganze auf die STM32F407, STM32F429 und STM32F756 CPUs portiert:

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)

Fehler hier im Text können von euch selbst korrigiert werden ;-)

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

Siehe auch

Web Ressourcen/Einzelnachweise