CCBAS2MC
Echter BASIC-Compiler für das
C-Control/BASIC-Mikrokontrollersystem

Autor: Dietmar Harlos


Kurzbeschreibung

Mit diesem Compiler kann ein BASIC-Programm eines C-Control/BASIC-Mikrokontrollers in echten Maschinencode umgewandelt werden. Das erzeugte Kompilat ist bis zu 250 mal (!) schneller als die interpretierte Variante. Das kompilierte BASIC-Programm wird im internen EEPROM gespeichert und darf daher (leider) nicht allzu groß sein. Dieser Compiler kann also immer dann sinnvoll eingesetzt werden, wenn ein kleines Programmsegment eines BASIC-Programms beschleunigt werden soll und das interne EEPROM noch frei ist.

Zum fehlerfreien Kompilieren werden der Standard-BASIC-Compiler (CCBAS.EXE) und der AS05 Assembler (AS05.EXE) benötigt. Der Standardcompiler liegt der DOS-IDE bei und kann zusammen mit dem Assembler auf der C-Control-intern-Homepage heruntergeladen werden.

Durch welchen Assemblercode ein BASIC-Befehl in Maschinensprache umgewandelt wird ist für den Anwender vollständig transparent und kann in einer Konfigurationsdatei festgelegt werden. Wer etwas von der Assemblersprache versteht, kann den Compiler durch Ändern dieser Datei eigenen Wünschen anpassen und hochoptimierte Kompilate erstellen lassen.
 

Interpretiertes BASIC kontra kompiliertes BASIC

Normalerweise sieht die Programmentwicklung beim C-Control/BASIC-Mikrokontroller mit Hilfe der DOS-IDE folgendermaßen aus: Das BASIC-Programm wird auf dem PC geschrieben und mit Hilfe des Standardcompilers CCBAS.EXE in eine Tokendatei (mit der Endung DAT) übersetzt, welche anschließend mit dem Downloadprogramm CCDL.EXE zur C-Control übertragen wird. Diese Token werden vom Mikrokontroller im seriellen EEPROM gespeichert.

Während der Programmausführung holt die C-Control ein Token nach dem anderen über den I²C-Bus aus dem EEPROM und führt die zu jedem Token gehörende Betriebssystemroutine aus. Viele Routinen holen zudem noch Datenbytes aus dem EEPROM, die hinter dem Token folgen.

Für das Lesen eines Tokens aus dem EEPROM und das anschließende Suchen und Aufrufen der entsprechenden Betriebssystemroutine benötigt der Mikrokontroller etwa 377 Taktzyklen (ungefähr 188 µs). Jedes Datenbyte, das zusätzlich gelesen werden muß, schlägt noch einmal mit mindestens 208 Taktzyklen (104 µs) zu Buche. Selbst der einfachste Befehl ("DEACT wordport") kann aus diesem Grund also maximal 5000 mal pro Sekunde ausgeführt werden. Ein Befehl wie "a=b+c" wird vom Standardcompiler in die Befehlsbytes "22 1 22 2 51 25 0" übersetzt. Hierbei sind 22, 51 und 25 Tokenbytes und 1, 2 und 0 Datenbytes. Für die Interpretation der Token werden in diesem Fall etwa 754 µs und für das Lesen der Datenbytes etwa 312 µs benötigt. Das eigentliche Durchführen der Berechnung mit Hilfe der Betriebssystemroutinen benötigt hingegen "nur" 190 µs. In diesem Fall wird also 85 % (!) der benötigten Ausführungszeit für die Interpretation und das Lesen aus dem EEPROM verschenkt. Letzteres ist ein besonderes Nadelöhr - die C-Control verbringt fast 60 % ihrer Zeit mit dem Lesen vom I²C-Bus!

Alle Berechnungen und Wertzuweisungen werden vom Betriebssystem mit Hilfe eines Rechenstacks durchgeführt. Bevor eine Berechnung durchgeführt wird, müssen erst die Zahlen, die an der Rechnung beteiligt sind, auf den Stack gepusht, die Berechnung ausgeführt und das Ergebnis vom Stack gepoppt werden. Selbst so einfache Wertzuweisungen wie "a=b" werden durchgeführt, indem zuerst der Wert von b auf den Stack geschrieben, und anschließend der Stackinhalt in die Variable a gespeichert wird. Durch die konsequente Verwendung des Rechenstacks kann die C-Control auch komplexe Berechnungen durchführen, nur leider benötigt das Pushen und Poppen relativ viel Zeit. In obigen Beispiel sind es etwa 130 µs.

Mit Hilfe des neuen Compilers kann der Befehl "a=b+c", ohne Lesen vom I²C-Bus und ohne Benutzung des Rechenstacks, in nur 5 µs (oder noch schneller!) ausgeführt werden.
 

Installation

Der Compiler sollte in das Verzeichnis installiert werden, in dem die Quelltexte der zu erstellenden BASIC-Programme gespeichert werden. Da die meisten C-Control-Anwender wohl mit der DOS-IDE arbeiten, heißt das Verzeichnis üblicherweise "C:\Programme\CCBasicWin\CCEW32D".

Zur Installation muß der Anwender den gesamten Inhalt des ZIP-Archivs, in dem sich dieser Text befunden hat, in das Quelltext-Verzeichnis entpacken. Anschließend müssen noch der Standardcompiler CCBAS.EXE und der Assembler AS05.EXE hinzukopiert werden.

Entscheidend zur problemlosen Kompilierung ist, daß der Compiler CCBAS2MC.EXE, die Konfigurationsdatei CCBAS2MC.CFG, der Standardcompiler und der AS05-Assembler in einem gemeinsamen Verzeichnis stehen.

Dem ZIP-Archiv liegt eine Batchdatei namens COMPILE.BAT bei. Wenn diese durch einen Doppelklick im Explorerfenster gestartet wird, dann übersetzt der Compiler das BASIC-Programm TEST1_C.BAS. Durch Editieren der Batchdatei kann der Compiler natürlich veranlaßt werden, auch jede andere BASIC-Programmdatei zu übersetzen.

Wer die C-Control bisher nur unter Windows programmiert hat, kann auch die DOS-IDE installieren und den Compiler CCBAS.EXE durch CCBAS2MC.EXE im Menü "Option -> Tools" ersetzen. Die DOS-IDE kann auch sehr gut unter Windows in einem DOS-Fenster benutzen werden (50 Zeilen-Modus bei einer Schriftgröße von 7 x 12 und einer Screenauflösung von 1024x768 Bildpunkten oder 43 Zeilen bei 800x600 Punkten). Das beiliegende Terminalprogramm MINITERM.EXE sollte allerdings nicht benutzt werden; dafür kann aber TERMINAL.EXE, aus der C-Control-intern-Sammlung eingesetzt werden.

Der Compiler unterstützt nur den C-Control/BASIC-Mikrokontroller der Version 1.1 (vom 20. Dezember 1996). BASIC-Programme für die C-Control-2 und die ältere Unit mit der Bezeichnung "XCTRL96A" können mit diesem Compiler nicht übersetzt werden.
 

Benutzung des Compilers

Zunächst muß das zu kompilierende BASIC-Programm mit Hilfe der DOS- oder Windows-IDE erstellt werden. Dabei sollte darauf geachtet werden, daß möglichst nur Bytevariablen benutzen werden. Besonders Wordvariablen führen systembedingt zu nicht optimalem Code, wodurch Ausführungszeit und Platz im internen EEPROM verschenkt wird.

Das zu kompilierende BASIC-Programm und das Programm, in das der kompilierte Code später eingebunden werden soll, müssen die gleichen Variablendeklarationen benutzen. Das heißt, am Beginn der beiden Programme müssen die gleichen DEFINE-Anweisungen stehen, sonst besteht Gefahr, daß sich Variablen überschneiden.

Der Befehl PRINT wird vom Compiler nicht voll unterstützt (was am geringen Platz im internen EEPROM liegt). Nur die Variante mit angehängtem Semikolon, bei der eine Zahl oder Variable ausgegeben wird, darf im zu kompilierenden BASIC-Programm verwendet werden (also z.B. "PRINT 123;"). Falls ein Zeilenvorschub benötigt wird, muß dieser per PUT generiert werden ("PUT 13 : PUT 10"). Auch Tabellen dürfen nicht benutzt werden.

Parameterübergaben an das kompilierte Programm müssen - wie bei jeder normalen Subroutine - immer über Variablen stattfinden. Die Rückgabe von Werten kann ebenfalls über Variablen geschehen. Siehe dazu folgendes Beispiel:
 
define a byte
define b byte
define c byte

a=b+c

Bild 1.1a: Das zu kompilierende BASIC-Programm TEST1_C.BAS

Zunächst muß obenstehendes BASIC-Programm abgetippt und unter dem Namen TEST1_C.BAS in das Verzeichnis gespeichert werden, in dem der Compiler installiert wurde. Wer zu faul zum Abtippen ist, findet die fertige Datei im ZIP-Archiv. Nun kann die Kompilierung durch einen Doppelklick auf die Batchdatei COMPILE.BAT gestartet werden. Alternativ wird die "MS-DOS-Eingabeaufforderung" gestartet und in das Compiler-Verzeichnis gewechselt. Das geschieht mit dem CD-Befehl; also zum Beispiel mittels "cd c:\temp\ccbas2mc". Dann kann die Kompilierung durch Eingabe von "CCBAS2MC test1_c.bas" gestartet werden.

Wenn der Compilerlauf ohne Probleme vonstatten gegangen ist, müßten drei neue Dateien im Verzeichnis vorhanden sein: TEST1_C.DAT (die Tokendatei), TEST1_C.ASM (Assemblercode) und TEST1_C.S19 (der assemblierte Assemblercode). Uns interessiert jetzt vor allen Dingen die S19-Datei. Sie enthält unser kompiliertes BASIC-Programm. Die Datei muß in das folgende Hauptprogramm TEST1.BAS mittels SYSCODE-Befehl eingebunden und per SYS &h101 gestartet werden:
 
define a byte
define b byte
define c byte

b=100
c=23
sys &h101
print a

syscode "test1_c.s19"

Bild 1.1b: In dieses BASIC-Programm wird das Kompilat eingebunden und mittels SYS &h101 aufgerufen

Das obenstehende BASIC-Hauptprogramm wird ganz normal mit dem Standardcompiler übersetzt und zur C-Control gesendet. Es werden nun nicht nur BASIC-Token, sondern auch sogenannte Systembytes übertragen. Die Systembytes stellen unser kompiliertes Programm dar. Sie werden von der C-Control in das interne EEPROM (ab Adresse $101) geschrieben. Nach dem Programmstart sollte der Mikrokontroller auf dem RS232-Bus die Zahl 123 übertragen. Das kann mit einem Terminalprogramm kontrolliert werden.

Wer etwas von der Assemblersprache versteht, kann sich ja auch einmal die vom Compiler erstellte ASM-Datei anschauen. Man sieht recht deutlich, daß der Compiler sehr effektiven Programmcode erzeugt hat!
 
  org $101

  lda $a2        ; 22 1 22 2 51 25 0
  add $a3        ; Addition oder Subtraktion
  sta $a1

  rts

Bild 1.1c: Der vom Compiler erzeugte Assemblercode (wird von AS05.EXE assembliert)

Um ein kompiliertes Programm zur C-Control zu übertragen und zu starten, wird grundsätzlich ein nicht kompiliertes, zweites BASIC-Programm ("Hauptprogramm") benötigt, in dem das Kompilat mittels SYSCODE eingebunden und per SYS &h101 gestartet wird. Es ist wichtig, daß die vom Compiler erstelle S19-Datei in dem Verzeichnis steht, in dem auch der Quelltext des Hauptprogramms abgespeichert wird. Denn der SYSCODE-Befehl unterstützt bei der Angabe des Dateinamens keine Pfadangabe.

Weitere Beispielprogramme befinden sich am Ende dieser HTML-Seite.
 

Technisches

Der BASIC-Compiler benutzt die vom Standardcompiler erzeugte Tokendatei, um ein Assemblerprogramm zu erzeugen, das vom Assembler AS05 assembliert werden kann. Die eigentliche Eingabe, die der Compiler sieht, ist also eine bereits tokensierte BASIC-Datei. Mit Hilfe der Konfigurationsdatei CCBAS2MC.CFG wird festgelegt, wie jeder Tokenbefehl in Assemblercode umgewandelt wird.

An dieser Stelle ist es vielleicht notwendig, die Begriffe Token, Tokenbefehl und BASIC-Befehl genauer zu unterscheiden: Ein BASIC-Befehl ist das, was der Benutzer in eine BAS-Datei schreibt (z.B. "PRINT a"). Diese BASIC-Befehle werden vom Standardcompiler übersetzt und in Tokenbefehle umgewandelt (z.B. "22 0 30 31 13 10 0"). Tokenbefehle bestehen aus einem Tokenbyte und einem oder mehreren sich anschließenden Datenbytes oder Datenwords. An dieser Stelle muß festgehalten werden, daß jeder BASIC-Befehl aus mehreren Tokenbefehlen bestehen kann. Letztere führen immer nur kleine Teilaktionen, wie das Pushen von Werten auf den Rechenstack,  Addieren mit Hilfe des Stacks, Poppen in eine Variable, oder ähnliches aus.
 
define a byte
define b byte
define byteport1 byteport[1]

#anfang
  a=byteport1
if a=b then anfang

Bild 2.1a: Der Quelltext des  zu kompilierenden BASIC-Programms

Die C-Control holt bei der Programmausführung ein Tokenbyte aus dem seriellen EEPROM und ruft die entsprechende Betriebssystemroutine auf. Falls zu dem Tokenbefehl noch zusätzliche Datenbytes gehören, werden diese selbsttätig von der Betriebssystemroutine aus dem EEPROM geholt.

Es ist überhaupt nicht schwierig, einen BASIC-Compiler zu erstellen, der stur jeden Tokenbefehl in ein entsprechendes Assembleräquivalent übersetzt, das einfach nur die passenden Betriebssystemroutinen aufruft. BASIC-Programme, die auf diese Weise kompiliert wurden sind etwa um den Faktor 7 mal schneller als Interpretierte. Da Tokenbefehle aber immer nur kleine Teilaktionen durchführen und vom Rechenstack regen Gebrauch machen, sind die erzeugten Kompilate jedoch weder geschwindigkeits- noch platzoptimiert. Dabei ist Letzteres ganz besonders entscheidend, denn das interne EEPROM ist ja nur 255 Byte groß!
 
  org $101

lab4:
  clra  ;lda #0  ; 11 0
  jsr $172e      ; 11 b    push data from byteport b to stack

  lda $92        ; 25 0
  sta $a1        ; 25 b    store Top-Of-Stack to bytevar b

  clra  ;lda #0  ; 22 0
  jsr $17aa      ; 22 b    push bytevar b to stack

  lda #1         ; 22 1
  jsr $17aa      ; 22 b    push bytevar b to stack

  jsr $155c      ; 64

  lda $91        ; 4 0 19
  ora $92        ; 4 w     IF .. THEN
  beq lab19      ;

  bra lab4       ; 3 0 4

lab19:
  rts

Bild 2.1b: Der erzeugte, nicht optimale Assemblercode. Hier werden nur Betriebssystemroutinen aufgerufen.

Aus diesem Grund besteht die Möglichkeit, in der Konfigurationsdatei CCBAS2MC.CFG mehrere, zusammengehörige Tokenbefehle gemeinsam abzuhandeln und optimierten Assemblercode zu erzeugen, der z.B. ohne Stack auskommt.

Beispiel: Wenn der C-Control-Mikrokontroller auf die Tokenbefehlskombination "11 0 25 0" trifft, pusht er zuerst den aktuellen Zustand von PORTB (an Adresse $1) auf den Stack und poppt anschließend den Wert vom Stack in die Bytevariable, die im RAM an Adresse $a1 liegt. Was liegt also näher, als diese Befehlskombination als eine Einheit ohne Umweg über den Stack in Assembler umzusetzen: "lda $1", "sta $a1" und fertig! Diese beiden Assemblerbefehle benötigen nur 4 Byte im internen EEPROM, die nicht optimierte Variante (die zudem weitaus langsamer ist) benötigt 8 bis 10 Bytes.

Ich habe in der Konfigurationsdatei bereits einige Optimierungen durchgeführt. Wobei ich mich in erster Linie auf den optimierten Zugriff auf Bytevariablen, Bytewerten, Byteports, Bitvariablen und Bitports beschränkt habe, denn bekanntlich ist die 6805'er eine 8-Bit-CPU, weshalb auf Daten im Byteformat ganz besonders leicht zugegriffen werden kann. Wenn in einem Programm Wordvariablen oder Wordwerte auftauchen, wird unoptimierter Code erstellt, der einfach zu jedem Tokenbefehl die entsprechende Betriebssystemroutine aufruft. Ebenso verhält es sich mit komplexen Berechnungen, bei denen der Rechenstack benutzt werden muß. Auf jeden Fall gibt es immer eine - wenn auch vielleicht nicht optimale - Möglichkeit, jedes beliebige BASIC-Programm in Maschinencode zu konvertieren.
 
  org $101

lab4:
  lda $01        ; 11 0 25 0
  sta $a1        ; diverse Befehle mit einem Byteparameter

  ;lda $a1       ; 22 0 22 1 64 4 0 19
  cmp $a2        ; Vergleichsbefehle IF..THEN
  beq lab4  ;bne lab19

  ;bra lab4      ; 3 0 4

;lab19:
  rts

Bild 2.1c: Durch das Zusammenziehen von Tokenbefehlen optimiertes Kompilat

Wer die Konfigurationsdatei editieren möchte, sollte sich auf der C-Control-intern-Homepage umschauen. Dort gibt es unter anderem das komplette ROM-Listing des C-Control-Betriebssystems. Eine komplette Tokenliste steht am Ende dieser Seite.
 

Die optimierten Befehle

In den zu kompilierenden BASIC-Programmen darf jeder beliebige Befehl benutzt werden. Wenn Berechnungen und Wertzuweisungen im Befehl enthalten sind (z.B. "PRINT a + b", "a = b OR c") sollten aber nach Möglichkeit nur die folgenden optimierten Befehle benutzt werden. Die anderen können vom Compiler nicht optimal in Maschinensprache umgesetzt werden und verschwenden deshalb unnötig Rechenzeit und kostbaren Speicherplatz im internen EEPROM. Alle hier aufgeführten Terme, die das Rechenergebnis auf dem Stack speichern, sind teiloptimiert. Das heißt, sie dürfen an beliebiger Stelle innerhalb des BASIC-Programms auftauchen.
.

Optimierte Befehle im Byteformat

Zusätzlich zu den hier aufgeführten Befehlen werden FOR..NEXT-Schleifen mit Bytevariablen optimiert. Diese sind einige hundert mal schneller als in interpretiertem BASIC.

Ein Sternchen hinter einem Ziel bedeutet, daß eine Befehlkombination nicht 100% kompatibel zum CCBASIC-Dialekt ist (siehe Inkompatiblitäten zum CCBASIC-Dialekt).
 
 ZIEL TERM
1. Operand Rechenoperation 2. Operand
IF..THEN*
Bytevariable=
Byteport=
DA-Port=
Stack=*
Bytewert
Bytevariable
Byteport
AD-Port
AND
NAND
OR
NOR
XOR
Bytewert
Bytevariable
Byteport
>
>=
<
<=
=
<>
Bytevariable=
Byteport=
DA-Port=
Stack=
MIN ( )
MAX ( )
Bytevariable=
Byteport=
DA-Port=
+
-
Bytevariable= Bytevariable SHL
SHR
1
.
WERTZUWEISUNGEN UND BEFEHLE
MIT EINEM OPERANDEN
IF..THEN
Bytevariable=
Byteport=
DA-Port=
Stack=
PRINT
PUT
GET
Bytewert
Bytevariable
Byteport
AD-Port
Bytevariable=
Byteport=
DA-Port=
NOT(Bytewert)
NOT(Bytevariable)
NOT(Byteport)
NOT(AD-Port)
IF..THEN RXD
NOT(RXD)
.

Optimierte Befehle im Bitformat

Die optimierten Bitbefehle werden zwar viel schneller ausgeführt als in interpretiertem BASIC, doch sind sie relativ verschwenderisch mit dem Platz im internen EEPROM. Es kann unter Umständen günstiger sein, im Byteformat auf Bitports oder Bitvariablen zuzugreifen. Das ist auch der Grund dafür, warum ich noch nicht alle Bitbefehle optimiert habe.
 
 ZIEL TERM
1. Operand Rechenoperation 2. Operand
IF..THEN Bitvariable
Bitport
AND
NAND
OR
NOR
XOR
Bitvariable
Bitport
=
<>
.
WERTZUWEISUNGEN UND BEFEHLE
MIT EINEM OPERANDEN
IF..THEN
Bitvariable=
Bitport=
Bitvariable
Bitport
Bitvariable=
Bitport=
ON
OFF
IF..THEN NOT(Bitvariable)
NOT(Bitport)
.

Komplexe Berechnungen

Der Compiler kann - wie oben ersichtlich - bisher nur einfache Berechnungen optimieren, und diese auch nur dann, wenn an den Berechnungen nur Zahlen, Variablen und Ports im Byteformat beteiligt sind. Das Optimieren von komplexen Berechnungen wäre deutlich schwieriger, denn hierbei müßte der Compiler genau prüfen, ob die einzelnen Teilausdrücke im Byte- oder im Wordformat errechnet werden können, bzw. müssen.
 
define a byte
define b byte
define c byte

c=(a+b) SHR 1

Bild 3.1a: Dieser Ausdruck kann vom Compiler nicht optimiert werden

Ein Ausdruck wie "a AND b OR c" könnte z.B. komplett im Byteformat ausgewertet werden, wenn a, b und c Bytevariablen sind. Dagegen kann "a + b" nur im Byteformat berechnet werden, wenn das Resultat in einer Bytevariablen oder einem Byteport gespeichert wird. Der Ausdruck "(a + b) SHR 1" kann hingegen im allgemeinen auch dann nicht im Byteformat ausgewertet werden, wenn das Ziel ein Byte ist. Denn wenn z.B. a=200 und b=100 ist, ergibt "(a + b)" das Zwischenergebnis 44 (300 MOD 256, da ja im Byteformat gerechnet wird). "SHR 1" entspricht einer Division durch 2, ergibt also 22. Der Anwender erwartet aber 150. In diesem Fall muß also komplett im Wordformat gerechnet werden. Allerdings geht das nur mit Programmcode, der deutlich mehr Platz im internen EEPROM belegt. Es ist fraglich, ob sich dieser hohe Aufwand lohnt.
 
  org $101

  clra  ;lda #0  ; 22 0
  jsr $17aa      ; 22 b    $17A7  intern, push bytevar b to stack

  lda #1         ; 22 1
  jsr $17aa      ; 22 b    $17A7  intern, push bytevar b to stack

  jsr $1342      ; 51

  ldx #1         ; 20 0 1
  jsr $125c      ; 20 w    $1796  intern, push wordvalue w to stack

  jsr $1316      ; 47

  lda $92        ; 25 2
  sta $a3        ; 25 b    $17BC  intern, store Top-Of-Stack to bytevar b

  rts

Bild 3.1b: Der Compiler erzeugt ein Kompilat, das die Berechnung mit Hilfe des Rechenstacks ausführt

Theoretisch wäre es möglich, einen Compiler zu erstellen, der alle Berechnungen nur im Byteformat durchführt. Das würde allerdings bedeuten, die Kompatibilität zum CCBASIC-Dialekt aufzugeben.
 
define a byte
define b byte
define c byte

c=a+b
c=c SHR 1

Bild 3.2a: Jetzt wurde der komplexe Term in zwei einfache aufgespalten

Bis auf weiteres müssen komplexe Berechnungen im zu kompilierenden BASIC-Programm in einfache Teilausdrücke zerlegt werden, damit der Compiler Optimierungen durchführen kann. "d = a AND b OR c" muß in "d = a AND b" und "d = d OR c" zerlegt werden; "d = (a + b) SHR 1" wird zu "d = a + b" und "d = d SHR 1"; und so weiter. Hierbei führt der Compiler automatisch Optimierungen durch, so daß im fertigen Kompilat nicht ständig in die Variable d geschrieben und aus dieser gelesen wird. Der einzige Nachteil, der in Kauf genommen werden muß, ist, daß das Schreiben des BASIC-Programms etwas umständlicher wird.
 
  org $101

  lda $a1        ; 22 0 22 1 51 25 2
  add $a2        ; Addition oder Subtraktion
  ;sta $a3

  ;lda $a3       ; 22 2 20 0 1 47 25 2
  lsra           ; SHR
  sta $a3

  rts

Bild 3.2b: Wider erwarten ist das Kompilat deutlich kürzer und sehr viel schneller

Wie bereits an anderer Stelle erwähnt, ist das natürlich kein Muß. Es kann auch jeder andere Berechungsterm verwendet werden - allerdings wird dieser dann nicht optimal in Maschinensprache umgesetzt und verschwendet unnötig Rechenzeit und kostbaren Speicherplatz im internen EEPROM.
.

Inkompatiblitäten zum CCBASIC-Dialekt

Dieser Compiler hat es sich zur Aufgabe gemacht, möglichst hundertprozentig kompatibel zum CCBASIC-Dialekt zu sein. Aus diesem Grund wird auf einige durchaus mögliche Optimierungen verzichtet. Trotz allem kann sich das gleiche BASIC-Programm unter gewissen Umständen als Kompilat anders verhalten, als wenn es interpretiert abläuft. Probleme können besonders dann auftreten, wenn im zu kompilierenden Programm unlogische Befehlskombinationen auftauchen. Falls jemand noch andere Fälle von Inkompatiblitäten findet, soll er bitte den Quelltext des Kompilats und die Konfigurationsdatei CCBAS2MC.CFG zippen und mir zumailen.

Die Inkompatiblitäten könnten durch Editieren der Konfigurationsdatei mit mehr oder weniger großem Aufwand eliminiert werden. Vermutlich wird sich darum die Liste der Inkompatiblitäten in folgenden Versionen des Compilers ändern. Trotzdem würde ich gern Rückmeldungen von anderen Anwendern erhalten, in wie weit sich der Compiler an die Vorgaben des CCBASIC-Dialekts halten soll. Es ist nämlich auch denkbar, zugunsten von Platz- und Geschwindigkeitsoptimierungen darauf weitgehend zu verzichten.
 

Editieren der Konfigurationsdatei

Wer sich in der Assemblersprache auskennt, kann die Konfigurationsdatei CCBAS2MC.CFG editieren und weitere optimierte Befehle erstellen. Falls der Compiler aus irgendeinem Grund eine falsche Mnemonic produziert kann der Fehler ebenfalls durch Editieren dieser Datei behoben werden.

Am Beginn der Datei stehen einige Informationen, die beim Ändern oder Hinzufügen nützlich sind. Dahinter folgen die 84 nicht optimierten Tokenbefehle, auf die vom Compiler immer dann zurückgegriffen wird, wenn kein optimierter Befehl existiert. Die optimierten Befehle stehen am Ende der Datei.

Bitte bei Veränderungen Vorsicht walten lassen und vorher die angegeben Beispiele genau studieren. Der Compiler führt zwar etliche Kausalitätsprüfungen durch, doch kann er natürlich nicht in jedem Fall erraten, was der Programmierer erreichen möchte.
 

Tips und Tricks

Die C-Control/BASIC-M-Unit unterstützt die Definition einer Interruptroutine (durch den INTERRUPT-Befehl). Immer wenn der Mikrokontroller am IRQ-Pin eine Low-Flanke detektiert, wird das gerade ausgeführte Programm unterbrochen und diese Routine aufgerufen. Mit dem Befehl RETURN INTERRUPT kann dann zur Ausgangsposition zurückgekehrt werden. Dummerweise gibt aber es einen Bug im Betriebssystem, der bewirkt, daß innerhalb der Interruptroutine nicht auf den Rechenstack zugegriffen werden darf. Aus diesem Grund sind Interruptroutinen kaum sinnvoll einsetzbar. Der Compiler CCBAS2MC unterstützt ebenfalls die Definition einer Interruptroutine (wenn das auch auf etwas andere Art und Weise geschieht). Da der Compiler eine Menge Befehle so optimieren kann, daß sie ohne Zugriff auf den Rechenstack auskommen, ermöglicht das ganz neue Einsatzgebiete für die C-Control. Eine innerhalb des interpretierten Hauptprogramms stehende Interruptroutine wird allerdings deaktiviert.

Das Übertragen und Empfangen von Werten über die RS232-Schnittstelle kann problemlos in interpretiertem BASIC vorgenommen werden. Die Schnittstelle ist so langsam, daß die Ablaufgeschwindigkeit des Programms so gut wie keine Rolle spielt.

Wer eigene BASIC-Befehle "designen" möchte, kann mitten im zu kompilierenden BASIC-Programm, an einer Stelle, an der normalerweise Befehle stehen, eine Tabelle einfügen oder eine Subroutine aufrufen, die eine Tabelle enthält. In diese Tabelle werden dann eigene Kunsttoken geschrieben, mit der die C-Control normalerweise nichts anzufangen weiß. Wenn nun in der Konfigurationsdatei CCBAS2MC.CFG zu diesen Token Assemblercode steht, wird der natürlich auch ins Kompilat übernommen! Als Kunsttoken dürfen alle Bytes von 85 bis 99 benutzt werden. Wer noch mehr zusätzliche Befehle benötigt, kann auch zwei Bytes als Token benutzen.

Falls der kompilierte Teil eines Programms bereits fehlerfrei funktioniert und nur noch Änderungen an dem interpretiertem Hauptprogramm durchgeführt werden müssen, sollte vor dem Übertragen des Programms zur C-Control Unit der SYSCODE-Befehl auskommentiert werden. In diesem Fall wird das Kompilat (das sich ja im internen EEPROM befindet) nicht verändert und so wertvolle Schreibzyklen gespart. Laut Motorola verträgt das interne EEPROM nur 10000 (in Worten: zehntausend) Programmierzyklen!

Der C-Control-Einplatinencomputer basiert zum Großteil auf dem MC68HC05B6 Mikrokontroller von der Firma Motorola. Conrad Electronic hat für diesen Chip ein eigenes Betriebssystem inklusive BASIC-Interpreter geschrieben, von Motorola in das ROM brennen lassen und diesen Chip anschließend zusammen mit einem Quarz und einem seriellen EEPROM auf eine Platine gelötet, und fertig war der C-Control-Einplatinencomputer. Von Motorola gibt es aber auch vollständig kompatible Mikrokontroller, die statt ROM ein EPROM besitzen (z.B. MC68HC705B16-N). Diese Kontroller haben sogar mehr RAM und das EPROM ist größer als das ROM des MC68HC05B6. Das EPROM könnte problemlos das Betriebssystem der C-Control-Unit aufnehmen, so daß noch genug Platz bliebe, um zusätzlich auch ein mehrere Kilobyte großes kompiliertes BASIC-Programm hineinzuprogrammieren. Auf diese Weise könnten relativ problemlos schnelle Programme für einen Mikrokontroller in BASIC entwickelt werden. Üblicherweise müssen diese in Assembler oder in der Sprache C programmiert werden. Wenn leichte Veränderungen am Betriebssystem vorgenommen werden, kann man sogar ohne das serielle EEPROM am I²C-Bus auskommen. Auf jeden Fall ist zu beachten, daß der ORG-Befehl am Beginn der vom Compiler erstellen ASM-Datei die Adresse enthält, an der das Kompilat später im Speicher steht. Weitere Informationen zu den EPROM-Mikrokontrollern gibt es auf der Internetsite von Motorola oder im Manual zum MC68HC05B6.
 

Zukünftige Verbesserungen

Die folgenden Erweiterungen stehen noch an. Ich werde mich damit aber erst dann befassen, nachdem eine ausreichend große Anzahl von Personen den Compiler bei mir registriert hat. Falls jemand Ideen hat, was noch verbessert oder hinzugefügt werden könnte, so kann er seine Vorschläge in einer e-mail an mich schicken.
 

Registrierung

Diese Shareware-Version darf 30 mal benutzt werden. Wer das Programm darüber hinaus einsetzen möchte, muß 5,- EUR an mich zahlen. Am schnellsten geht das per normaler Briefpost, indem ein Geldschein an die untenstehende Adresse geschickt wird. Dabei darf natürlich die Angabe einer e-mail-Adresse nicht fehlen, an die ich die Registrierungsdaten zum Freischalten des Programms schicken kann. Wer nicht per e-mail zu erreichen ist, kann auch seine Telefonnummer angeben oder muß dem Brief Rückporto beilegen.

Es ist auch möglich, das Geld auf mein Konto zu überweisen. Im Feld "Verwendungszweck" muß dann eine gültige e-mail-Adresse und dahinter "CCBAS2MC" angegeben werden. Die Durchschrift oder eine Kopie des Überweisungsbelegs sollte aus Sicherheitsgründen zusätzlich an meine Postadresse geschickt werden. Das Konto bei der Deutschen Bank ist nicht mehr aktiv. Bitte nur noch das unten angegebene Konto bei der Advance Bank benutzen.

Nach dem Freischalten kann das Programm unbegrenzt oft benutzt werden. Falls Updates erscheinen, können diese ebenfalls freigeschaltet werden oder es werden vergünstigte Updatepreise für registrierte Benutzer angeboten.

Durch das Zahlen der Registriergebühr wird die Weiterentwicklung des Programms und die Entwicklung neuer Programme direkt unterstützt. Wer sich nicht registrieren läßt, schneidet sich also ins eigene Fleisch.

Falls jemand mehr als 30 Testmöglichkeiten benötigt, kann er den Compiler (CCBAS2MC.EXE) und die Konfigurationsdatei (CCBAS2MC.CFG) löschen und anschließend das ZIP-Archiv, in dem die beiden Dateien aus dem Internet heruntergeladen wurden, neu entpacken. Danach kann der Compiler weitere 30 mal gestartet werden. Aus moralischen Gründen sollte das aber nicht zu häufig geschehen.
.
Meine Postadresse:
Dietmar Harlos
Webereistraße 25
32139 Spenge
e-mail:
dharlos@web.de
.
.
(Neue) Bankverbindung:
Advance Bank München
Bankleitzahl 702 300 00
Kontonummer 3008620647

 

Rechtliches

Alle in diesem Programmpaket enthaltenen Verfahren und Programme werden ohne Rücksicht auf die Patentlage veröffentlicht. Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt. Die Fehlerfreiheit des Programms CCBAS2MC.EXE, der Konfigurationsdatei CCBAS2MC.CFG und der Informationen in diesem Text kann nicht garantiert werden.  Der Herausgeber kann für Fehler und deren Folgen weder eine juristische Verantwortung noch irgendeine andere Haftung übernehmen. Der Anwender nutzt das Programmpaket auf eigene Gefahr. Für Verbesserungsvorschläge und Hinweise auf Fehler ist der Herausgeber dankbar.

Das Copyright der Dateien CCBAS2MC.EXE, CCBAS2MC.CFG und INFO.HTM liegt bei Dietmar Harlos. CCBAS2MC.EXE und INFO.HTM dürfen nicht ohne meine Genehmigung in irgendeiner Weise verändert und dann weitergegeben werden. Änderungen an CCBAS2MC.CFG müssen im Dateikopf hinter dem Copyright kenntlich gemacht werden.

Das ZIP-Archiv, in dem das Programmpaket enthalten war, darf unverändert an jeden interessierten Anwender weitergegeben werden, sofern dies unentgeltlich geschieht. Das registrierte Programm CCBAS2MC.EXE darf ebensowenig wie der Registrierschlüssel an eine dritte Person weitergegeben werden.


Anhang A: Beispiel mit FOR..NEXT-Schleife

Das folgende BASIC-Programm läuft interpretiert in etwa 870 ms (0,87 Sekunden) ab. Es lädt den ersten Byteport mit allen Zahlen von 0 bis 255. Wenn an die Portanschlüsse LEDs angeschlossen werden, kann das Geschehen relativ gut betrachtet werden. Kompiliert, aber nicht optimiert, läuft das Programm etwa sieben mal schneller ab. Und das optimierte Kompilat braucht sage und schreibe nur noch rund 3.8 ms - es ist also 235 mal (!) schneller. Mit LEDs kann natürlich überhaupt nichts mehr gesehen werden - man benötigt schon ein Oszilloskop.
 
define a byte
define byteport1 byteport[1]

for a=0 to 255
  byteport1=a
next a

Bild 4.1a: Die FOR..NEXT-Schleife läuft interpretiert etwa 870 ms. Als optimiertes Kompilat werden nur 3.8 ms benötigt!
.
  org $101

  clr $a1  ;lda #0 ; 20 0 0 25 0
  ;sta $a1       ; FOR a=0 ...

lab9:
  lda $a1        ; 22 0 14 0
  sta $01        ;   byteport1=a
  lda #$ff
  sta $05

  lda $a1        ; 20 0 255 74 0 0 27
  cmp #255       ;   ... TO 255 ...
  beq lab27      ; 

  inc $a1  ;lda $a1 ; 20 0 1 76 0 0 9
  ;add #1        ;   ... STEP 1
  ;sta $a1       ; NEXT a
  bra lab9

lab27:
  rts            ; END

Bild 4.1b: Die hochoptimierte FOR..NEXT-Schleife - rund 235 mal schneller als interpretiertes BASIC.
.

Anhang B: Beispiel mit Zugriff auf Bitports

Das folgende Codesegment stammt aus einem BASIC-Programm, das Daten von einer Chipkarte liest. Der Chipkartenleser ist über Bitports mit der C-Control verbunden und die Daten müssen Bit-für-Bit übertragen und gelesen werden. Naturgemäß ist das interpretierte BASIC-Programm dabei recht langsam. Es benötigt fast 50 ms für jedes gelesene Byte. Das kompilierte Programm ist mehr als 60 mal schneller. Es könnte sogar noch beschleunigt werden, indem der PULSE-Befehl durch "clock=ON : clock=OFF" ersetzt wird. Das Kompilat belegt dadurch zwar 3 Byte mehr im internen EEPROM, ist durch den Trick aber mehr als 250 mal schneller als die interpretierte Variante!

In diesem Beispiel wird außerdem gezeigt, wie Daten an das Hauptprogramm (blau hinterlegt) mit Hilfe des Befehls "RETURN wert" zurückgegeben werden können.
 
define a byte
define b byte
define clock port [2]
define io port [3]

b=0
for a=0 to 7    ' 8 Bits (ein Byte) von einer Chipkarte lesen
  b=b shl 1
  if io then b=b or 1
  pulse clock
next a
return b

Bild 5.1a: Programmsegment zum Lesen von einer Chipkarte. Die Rückgabe erfolgt über den Stack.
.
define a byte
define b byte
...
define clock port [2]
define io port [3]

...
print chipkartenbyte
...
end

#chipkartenbyte
  sys &h101
return

syscode "test51a.s19"

Bild 5.1b: Das Hauptprogramm zum Lesen von einer Chipkarte. Die Rückgabe erfolgt über den Stack.
.
  org $101

  clr $a2  ;lda #0 ; 20 0 0 25 1
  ;sta $a2       ; diverse Befehle mit einem Byteparameter

  clr $a1  ;lda #0 ; 20 0 0 25 0
  ;sta $a1       ; diverse Befehle mit einem Byteparameter

lab14:
  lda $a2        ; 22 1 20 0 1 46 25 1
  lsla           ; SHL
  sta $a2

  brclr #2,$01,lab35 ; 10 2 4 0 35

  lda $a2        ; 22 1 20 0 1 43 25 1
  or #1          ; logische Verknuepfungen
  sta $a2

lab35:
  lda #1         ; 81 1
  jsr $1784      ; 81 b    $1781  PULSE

  lda $a1        ; 20 0 7 74 0 0 51
  cmp #7         ; FOR-NEXT-Schleife mit Bytevariable: Vergleiche mit
  beq lab51      ; Bytewert ; falls beide gleich sind springe nach w2

  inc $a1  ;lda $a1 ; 20 0 1 76 0 0 14
  ;add #1        ; FOR-NEXT-Schleife mit Bytevariable: Addiere Bytewert
  ;sta $a1       ; und springe nach w
  bra lab14

lab51:
  lda #1         ; 22 1
  jsr $17aa      ; 22 b    $17A7  intern, push bytevar b to stack

  rts            ; 6

Bild 5.1c: Der vom Kompiler erzeugte Assemblercode. Der PULSE-Befehl ist platz- aber nicht geschwindigkeitsoptimal
.

Anhang C: Liste aller CCBASIC-Tokenbefehle

In dieser Liste sind alle Tokenbefehle des C-Control/BASIC-Dialekts der Version 1.7 aufgeführt. Die meisten Befehle haben eine Länge von einem Byte. Bei allen Befehlen die länger als ein Byte sind folgen hinter dem jeweiligen Token noch zusätzliche Daten im tokensierten BASIC-Programm. Diese zusätzlichen Daten werden in dieser Liste durch einen Buchstaben hinter dem Tokenbyte symbolisiert. Dabei steht b für einen Bytewert (8 Bit), w für einen Wordwert (16 Bit) und s für einen nullterminierten ASCII-String. Ob die einzelnen Befehle noch zusätzliche Daten benötigen (die meistens vom Rechenstack geholt werden) oder Werte zurückliefen, sollte dem ROM-Listing (in der C-Control-intern-Sammlung) entnommen werden.

Die in der Tabelle aufgeführte Adresse gibt an, an welcher Adresse im ROM die Hauptroutine des jeweiligen Befehls beginnt.
 
Token Adresse Befehl (und Beschreibung)
0 $15B7 ignoriert, 1 Byte lang
1 $15B8 intern, Rechenstack loeschen
2 $15BB PAUSE
3 w $15CB GOTO, ELSE
4 w $15E0 IF .. THEN (hole das letzte Word vom Stack und springe zur Adresse w, falls das Word gleich 0 ist)
5 w $169D GOSUB
6 $16A6 RETURN
7 $1A0B RETURN INTERRUPT
8 w $170A SYS
9 b $1714 SLOWMODE
10 b $1720 intern, push data from bitport b to stack
11 b $172B intern, push data from byteport b to stack
12 $1736 intern, push data from wordport to stack
13 b $1740 intern, store Top-Of-Stack to bitport b
14 b $174B intern, store Top-Of-Stack to byteport b
15 $1756 intern, store Top-Of-Stack to wordport
16 b $1761 intern, push data from ad-port b to stack
17 b $176F intern, store Top-Of-Stack to da-port b
18 $15B7 ignoriert, 1 Byte lang
19 $15B7 ignoriert, 1 Byte lang
20 w $1796 intern, push wordvalue w to stack
21 b $17A0 intern, push bitvar b to stack
22 b $17A7 intern, push bytevar b to stack
23 b $17AE intern, push wordvar b to stack
24 b $17B5 intern, store Top-Of-Stack to bitvar b
25 b $17BC intern, store Top-Of-Stack to bytevar b
26 b $17C3 intern, store Top-Of-Stack to wordvar b
27 w $17CA LOOKTAB (hole die Nummer des zu lesenden Eintrags vom Stack und speichere anschliessend das Word von Adresse w + Eintrag auf dem Stack)
28 b $1806 intern, push internal var b to stack
29 b $1881 intern, store Top-Of-Stack to internal var b
30 $18D4 PRINT number
31 s $18DF PRINT string
32 $18ED PUT
33 $18F2 RXD
34 $18F9 GET
35 $1915 INPUT
36 b $1900 HANDSHAKE
37 $190E CTS
38 $15B7 ignoriert, 1 Byte lang
39 $15B7 ignoriert, 1 Byte lang
40 $12B6 NOT
41 $12C3 AND
42 $12D2 NAND
43 $12D6 OR
44 $12E5 NOR
45 $12E9 XOR
46 $12F8 SHL
47 $1316 SHR
48 $1442 RANDOMIZE
49 $1468 RAND
50 $1334 intern, (unary -) negate last word on stack
51 $1342 intern, (+) add last two words on stack & push result to stack
52 $1351 intern, (-) subtract last word on stack from last-but-one word & push result
53 $1360 intern, (*) multiply last two words on stack & push result
54 $137E intern, (/) divide last-but-one word on stack by last word & push result
55 $139C MOD
56 $13BA ABS
57 $13E8 SQR
58 $1484 MAX
59 $14A4 MIN
60 $14C4 intern, (>) test if last-but-one word is bigger than last word & push result
61 $1510 intern, (>=) test if last-but-one word is bigger/equal last word & push result
62 $14EA intern, (<) test if last-but-one word is lower than last word & push result
63 $1536 intern, (<=) test if last-but-one word is lower/equal to last word & push result
64 $155C intern, (=) test if last two words on stack are equal & push result
65 $1579 intern, (<>) test if last two words are not equal & push result
66 $13CC SGN
67 $15B7 ignoriert, 1 Byte lang
68 $15B7 ignoriert, 1 Byte lang
69 $15B7 ignoriert, 1 Byte lang
70 b $197C Befehle zum Zugriff auf die Datendatei (OPEN# FOR READ, OPEN# FOR WRITE, OPEN# FOR APPEND, CLOSE#, PRINT# oder INPUT#)
71 $19D8 EOF
72 b $19DF BAUD
73 w $19E6 INTERRUPT w an Adresse $b9,$ba schreiben
74 b w $161F intern, fuer FOR..NEXT-Schleifen: Vergleiche Word auf dem Stack mit der Bytevariablen b, falls beide gleich sind springe nach w
75 b w $1628 intern, fuer FOR..NEXT-Schleifen: Vergleiche Word auf dem Stack mit der Wordvariablen b, falls beide gleich sind springe nach w
76 b w $1631 intern, fuer FOR..NEXT-Schleifen: Addiere zur Bytevariablen b das letzte Word auf dem Stack und springe zur EEPROM-Adresse w
77 b w $1640 intern, fuer FOR..NEXT-Schleifen: Addiere zur Wordvariablen b das letzte Word auf dem Stack und springe zur EEPROM-Adresse w
78 $15B7 ignoriert, 1 Byte lang
79 $15B7 ignoriert, 1 Byte lang
80 b $177A TOG
81 b $1781 PULSE
82 b $1788 DEACT bitport b
83 b $178F DEACT byteport b
84 $0A82 DEACT wordport
255   END
.
Erstellt am: 25. August 2000 17:56
Erste Aktualisierung: 29. August 2000 18:49
Letzte Aktualisierung: 22. Februar 2004 10:22
(c) Dietmar Harlos ADPC