Dokumentation zu Nanopascal

Nanopascal ist ein Einphasen-Compiler mit direkter Codeerzeugung für Mikrocontroller aus der 8051-Familie.

1. Aufruf und Parameter

Der Compiler wird aufgerufen mit

nano51 <OPTION>... <QUELLDATEI>

Option Bedeutung
-w Der Compiler erzeugt Fehler statt Warnungen.
-e Der Compiler beendet die Übersetzung beim ersten Fehler.
-l Der Compiler schreibt ein Programmlisting in eine ".lst"-Datei.
-c Der Compiler schreibt auch den erzeugten Code in das Listing.
-s Der Compiler schreibt auch die Symboltabelle in das Listing.
-t Der Compiler schreibt jedes lexikalische Element in das Listing.
-L [Dateiname] Der Compiler schreibt das Listing in die angegebene Datei.
- steht für die Standard-Ausgabe.
-H [Dateiname] Der Compiler schreibt den Maschinencode in die angegebene Datei.
-V Der Compiler zeigt seine Version an.

Bei fehlerfreier Übersetzung speichert der Compiler den erzeugten Maschinencode in eine ".hex"-Datei im Intel-hex-Format.

2. Präprozessor

Der Compiler enthält einen sehr einfachen Präprozessor.

Die Steuerbefehle werden mit einem Punkt am Anfang der Zeile markiert (Wordstar lässt grüßen):

.fi Dateiname
Fügt an dieser Stelle eine Include-Datei ein.
.ifdef <name>
Bedingte Übersetzung: übersetzt die folgenden Zeilen nur, wenn der angegebene Name im Programm als Konstante, Variable oder Prozedur definiert ist.
.ifndef <name>
Bedingte Übersetzung: übersetzt die folgenden Zeilen nur, wenn der Name nicht definiert ist.
.else
.endif
Ende eines bedingt übersetzten Bereiches.
.show text
Gibt während der Übersetzung den Text auf die Konsole aus.
.eof
Beendet eine Include-Datei vorzeitig.
.o+
.o-
.o=zahl
o steht für eine Option a...z. Setzt den Wert der angegebenen Option auf 1, 0 bzw. die angegebene Zahl.
  • Der Präprozessor besitzt keine eigene Symboltabelle, sondern fragt die Symboltabelle des Compilers ab.
  • Am Ende einer Include-Datei enden automatisch und ohne Warnung alle mit .ifdef und .ifndef begonnenen Bereiche.

3. Lexikalische Elemente

Programme werden aus folgenden Elementen zusammengesetzt:

Namen:
Die Namen von Konstanten, Variablen, Prozeduren und Parametern dürfen bis zu 80 Zeichen lang sein. Die Großschreibung ist bedeutungslos. Als erstes Zeichen sind nur die Buchstaben A bis Z erlaubt, danach dürfen weitere Buchstaben, Ziffern und die Zeichen _ und ? stehen.
Zahlenkonstanten:
Das Programm verarbeitet die Zahlen von 0 bis 65535 (216-1). Zahlen können auch hexadezimal in den Schreibweisen $2a und 0x2a oder binär in den Schreibweisen %00101010 und 0b00101010 angegeben werden.
Zeichenkonstanten:
Es gibt keinen Datentyp CHAR. Zeichenkonstanten in der Schreibweise 'c' liefern den Zahlenwert des angegebenen Zeichens, also z.B. 65 für ein A.
Zeichenkettenkonstanten:

Zeichenketten werden mit doppelten Anführungszeichen gebildet:

"Das ist ein Text.\n"

.

Das Zeichen \ beginnt spezielle Gruppen:

  • \n fügt ein Zeichen Linefeed (0x0a) ein.
  • \r fügt ein Zeichen Carriage return (0x0d) ein.
  • \b fügt ein Zeichen Backspace (0x08) ein.
  • \e fügt ein Zeichen Escape (0x1b) ein.
  • \" fügt das doppelte Anführungszeichen (") (0x22) ein, das ohne \ die Zeichenkette beenden würde.
  • \\ fügt ein Zeichen \ (0x5c) ein.
Schlüsselwörter:

ADDR, AND, ARRAY,
BEGIN, BREAK, BYTE,
CASE, CHAR, CLR, CONST, CONTINUE, CPL,
DECR, DIV, DO,
ELSE, END, EXIT, EXTERN,
FOR,
HI,
IF, IN, INCR, INLINE, INTEGER, INTERN, INTERRUPT,
LO, LOCK, LOOP,
MOD,
NOT,
OF, OR,
PROCEDURE, PROGRAM,
REPEAT, RETURN,
SET, SHL, SHR,
STRING, TABLE, THEN, TO,
UNTIL,
VAR,
WHILE, WORD,
XOR

Auch bei den Schlüsselwörtern ist die Großschreibung bedeutungslos.

Operatoren:

Arithmetisch:   +   !+   -   !-   *   ~

Vergleiche:   <   <=   <>   =   >   >=

Logisch:   !

Speicherzugriff:   @   ^

Interpunktion:

(   )   ,   .   ..   :   :=   ;   ;   [   ]

Kommentare:

Kommentare beginnen mit { und enden mit }. Kommentare können nicht geschachtelt werden und enden automatisch am Ende einer Datei.

  • In Namen und Schlüsselwörten ist die Großschreibung ohne Bedeutung.
  • Es gibt keine negativen Zahlen.
  • Es gibt keinen Datentyp CHAR.
  • Es gibt keinen Kommentar der Form (* ... *).

4. Hauptprogramm

Das Hauptprogramm besitzt die Form:

PROGRAM <Name>;
	<Deklarationen>
BEGIN
	<Befehle>
END.

Der Einphasen-Compiler legt Programm und Variablen auf Speicheradressen ab und muß den zur Verfügung stehenden Speicherbereich kennen. Als Default wird verwendet:

Speicherart Segment von bis Größe Kommentar
Programmspeicher code 0040 1fff 8 KByte Flash/Eprom Programmcode und Zeichenketten
Interner Datenspeicher data 20 bf 160 Bytes davor: 4 Registerbanks, dahinter: Stack
Externer Datenspeicher xdata 8000 ffff 32 KByte RAM Variablen der Klasse EXTERN

Im Codebereich werden abgelegt (beginnend bei den niedrigen Adressen): der Code für die Prozeduren, Tabellen und das Hauptprogramm. Unter dem Codebereich muß Platz bleiben für die Interruptvektoren der verwendeten Interrupts. Vom oberen Ende absteigend werden die Zeichenkettenkonstanten abgelegt.

Im Programmkopf kann der zur Verfügung stehende Speicher spezifiziert werden:

PROGRAM <Name> ( [mincode..maxcode] [minxdata..maxxdata] [mindata..maxdata] );

  • Wenn Sie Prozeduren oder Interrupts verwenden, sollten Sie am Anfang des Hauptprogrammes dem Stackpointer einen Startwert zuweisen.
  • Der Stack wächst in Richtung größerer Adressen.

5. Prozeduren

PROCEDURE <Name> ( <Name1>: <Typ1> , ... ) ;
  <Deklarationen>
BEGIN
  <Befehle>
END;

Parameter können durch Voranstellung des Schlüsselworts VAR als VAR-Parameter markiert werden. An Var-Parameter können nur interne Variablen übergeben werden:

PROCEDURE <Name> ( VAR <Name1>: <Typ1>, ... ) ;

Parametern kann eine bestimmte Adresse zugeordnet werden:

PROCEDURE <Name> ( <Name1> [<Konstante>]: <Typ1> , ... ) ;

Parameter und lokale Variablen der Prozeduren werden nicht auf dem Stack angelegt, sondern auf festen Adressen im internen RAM. Deshalb sind keine rekursiven Aufrufe möglich.

  • Keine rekursiven Aufrufe
  • Keine Funktionen

6. Interrupt-Handler

Bei bestimmten Ereignissen kann der Prozessor die Programmabarbeitung unterbrechen und ein Unterprogramm aufrufen. Das Ereignis ist ein Interrupt und die dem Ereignis zugeordnete Adresse der Interrupt-Vektor.

Die möglichen Ereignisse sind durchnummeriert; jedem Ereignis ist eine feste Adresse zugeordnet, und zwar dem Ereignis mit der Nummer i die Adresse i*8-5. Bei Ereignis 2 (Überlauf des Timers 0) wird also das Unterprogramm auf Adresse 0x000b aufgerufen.

Interrupt-Handler werden wie Prozeduren deklariert. Allerdings wird kein Name, sondern die Ereignisnummer angegeben:

INTERRUPT <Nummer>;
  <Deklarationen>
BEGIN
  <Befehle>
END;

Alternativ kann auch der Interrupt-Vektor angeben werden:

INTERRUPT @<Adresse>;

An der vorgegebenen Adresse ist nur Platz für 8 Bytes. Ein Interrupt-Handler braucht zumeist mehr Platz, deshalb legt der Compiler den Interrupthandler an eine andere Stelle und belegt die vorgegebene Adresse nur mit einen Sprungbefehl.

Der Compiler erzeugt am Beginn des Interrupt-Handlers Code, um die Register A, B, PSW und DPTR auf den Stack zu sichern. Am Ende des Interrupt-Handlers werden die Ursprungswerte vom Stack zurückgeladen.

Wenn mehr als diese Register benötigt werden, kann eine alternative Registerbank ausgewählt werden mit der Schreibweise:

INTERRUPT <Nummer> [<Bank>];

Der Compiler erzeugt dann zusätzlich eine Zuweisung an das PSW-Register. Sie können die Register-Banks 1, 2 und 3 benutzen. Die Registerbank 0 ist dem Hauptprogramm vorbehalten.

Bei extrem zeitkritischen Interrupt-Handlern können Sie den Compiler anweisen, keinen Code für das Retten und Restaurieren der Register zu erzeugen, und diese Aufgabe selbst übernehmen:

INTERRUPT <Nummer> [0];


7. Konstanten

Sie können Konstanten definieren:

CONST <Name> = <Ausdruck>;

Auf der rechten Seite sind nicht nur numerische und Zeichenketten-Konstanten erlaubt, sondern beliebige Ausdrücke. Natürlich müssen diese konstant auswertbar sein.

  • Die CONST-Deklaration ersetzt auch das #define.

8. Variablen

Die Prozessoren der 8051-Familie unterstützen zwei Speichersegmente für Daten:

  1. bis zu 256 Bytes internen Speicher und
  2. bis zu 64KBytes externen Speicher.

Auf den internen Speicher kann mit einer Vielzahl von Instruktionen zugegriffen werden, während für den externen Speicher nur Lesen und Schreiben möglich ist.

Zur Auswahl des Speicherbereiches verwenden Sie die Schlüsselwörter INTERN und EXTERN:

VAR <Name> : <Typ>;

VAR <Name> : <Typ> INTERN;

VAR <Name> : <Typ> EXTERN;

Es gibt folgende Typen:

BYTE
Die vorzeichenlose Zahl mit dem Wertebereich 0 bis 255 belegt ein Byte.
WORD
Die vorzeichenlose Zahl mit dem Wertebereich 0 bis 65535 belegt zwei Byte.
STRING
Die Adresse einer Zeichenkettenkonstante im Programmspeichersegment belegt zwei Byte.

Array-Variablen werden immer beginnend mit 0 indiziert. Sie geben nur die Anzahl der Elemente und nicht den Indexbereich an:

VAR <Name> : ARRAY <Konstante> OF <Typ>;

VAR <Name> : ARRAY <Konstante> OF <Typ> INTERN;

VAR <Name> : ARRAY <Konstante> OF <Typ> EXTERN;

Der Compiler ordnet den Variablen Adressen in einem der Speichersegmente zu:

Gruppe Auswahl Segment Adressvergabe
Skalar - Intern aufsteigend
INTERN
EXTERN Extern aufsteigend
ARRAY - Intern absteigend
INTERN aufsteigend
EXTERN Extern aufsteigend

Sie können diese automatische Zuordnung umgehen:

VAR <Name> [<Konstante>]: <Typ>;

Auf diese Weise können zum Beispiel die Prozessorregister mit Namen versehen werden:

VAR PSW [0xd0] : BYTE;

9. Bitvariablen

Die acht Bits einer BYTE-Variablen können benannt und dann über ihren Namen angesprochen werden:

VAR <Name> : [ <Bit7> <Bit6> <Bit5> <Bit4> <Bit3> <Bit2> <Bit1> <Bit0> ];

Invertierte Bits werden durch ein vorangestelltes ! markiert; nicht benutzte Bits werden durch * ersetzt:

VAR <Name> : [ * * * * * * !<Bit1> <Bit0> ];

Auf diese Weise werden die Bits der Prozessorregister mit Namen versehen:

VAR PSW[0xd0]: [Cy AC F0 RS1 RS0 OV * Parity];

Man kann sie dann abfragen und ändern:

IF Parity THEN SET(Cy);


10. Tabellen (initialisierte konstante Arrays)

Tabellen von Zahlen oder Zeichenketten werden im Programmspeicher abgelegt:

TABLE <Name>: ARRAY <Konstante> OF <Typ> = [ <Konstante0>, <Konstante1> ... ];

Tabelleneinträge werden wie Elemente von Arrays angesprochen, können aber zur Laufzeit des Programmes nicht geändert werden.


11. Ausdrücke

Basiselemente sind Konstanten, Variable, Array-Elemente und TABLE-Einträge:

  • Elementen vom Typ WORD kann ein .LO oder .HI angehängt werden, um nur das nieder- oder höherwertige Byte anzusprechen.
  • Mit ADDR(<Variable>) kann die Adresse einer Variable abgefragt werden.

Die Basiselemente können mit Operatoren und Klammern zu komplexen Ausdrücken verknüpft werden:

Arithmetische Operatoren:
+ Vorzeichenlose Addition 8bit oder 16bit
- Vorzeichenlose Subtraktion 8bit oder 16bit
!+ Vorzeichenlose Addition mit Berücksichtigung des Carry-Flags 8bit oder 16bit Wird nur bei sehr maschinennaher Programmierung genutzt.
!- Vorzeichenlose Subtraktion mit Berücksichtigung des Carry-Flags 8bit oder 16bit
* Vorzeichenlose Multiplikation 8bit × 8bit = 16bit Der 8051 unterstützt keine 16bit Multiplikation oder Division.
DIV Vorzeichenlose Division 8bit÷8bit
MOD Vorzeichenloses Modulo 8bit÷8bit
Vergleichsoperatoren:

Mit diesen sechs Operatoren können Zahlenwerte und Zeichenketten jeweils untereinander verglichen werden:

= <>
< <= > >=

Außerdem können Sie abfragen, ob ein bestimmtes Bit in einem Byte gesetzt ist:

<Bitnummer> IN <Ausdruck>

Die Bitnummer muß eine Konstante zwischen 0 und 7 sein.

Logische Operatoren:
Operator Bedeutung als Ausdruck Bedeutung als Bedingung
AND Bitweises Und Logisches Und
OR Bitweises inklusives Oder Logisches Oder
XOR Bitweises exklusives Oder
~ Bitweises Komplement
NOT Ungleich 0 Logisches Nicht
Klammern:

Klammerausdrücke werden mit ( und ) gebildet. Die Klammertiefe ist durch die verfügbaren Register begrenzt.


12. Befehle

Zuweisung:

<Variable> := <Ausdruck>

Befehlsfolge:

BEGIN
<Befehl1>;
<Befehl2>;
...
<Befehln>
END

If-Abfrage:

IF <Ausdruck> THEN <Befehl1>

IF <Ausdruck> THEN <Befehl1> ELSE <Befehl2>

Case-Verzweigung:

CASE <Byte-Ausdruck> OF
<Konstantei1>: <Befehli>
<Konstantej1>..<Konstantej2>: <Befehlj>;
<Konstantek1>, <Konstantek2>: <Befehlk>;
<Konstantel1>, <Konstantel2>..<Konstantel3>: <Befehlj>;
ELSE <Befehln>
END

Der ELSE-Teil darf entfallen.

  • Es wird nur das niederwertige Byte des CASE-Ausdrucks ausgewertet.
  • Als CASE-Konstanten sind nur Werte von 0 bis 255 zulässig.
  • Die CASE-Verzweigung wird mit einer verketteten Liste von Vergleichen und Sprungbefehlen übersetzt. Einträge weiter vorne brauchen weniger Zeit.
While-Schleife:

WHILE <Ausdruck> DO <Befehl>

So oft der Ausdruck einen Wert ungleich 0 hat, wird der Befehl ausgeführt.

Repeat-Schleife:

REPEAT <Befehl> UNTIL <Ausdruck>

Der Befehl wird mindestens einmal ausgeführt und dann wiederholt, solange der Ausdruck einen Wert von 0 hat.

For-Schleife:

FOR <Variable> := <Ausdruck1> TO <Ausdruck2> DO <Befehl>

  • Der Ausdruck2 muß größer oder gleich dem Ausdruck1 sein.
  • Der Befehl benutzt eine unsichtbare BYTE-Variable im internen Speicher.
  • Die Anzahl der Durchläufe ist auf 256 begrenzt.
Loop-Schleife:

LOOP
<Befehl1>;
<Befehl2>;
...
<Befehln>;
END

Bedingslose Schleife. Wird mit EXIT, BREAK oder RETURN verlassen.

Sprünge:

EXIT

BREAK

Mit EXIT oder BREAK wird die innerste Schleife verlassen.

CONTINUE

Der Befehl CONTINUE startet den nächsten Durchlauf der innersten Schleife.

RETURN

Springt zum Ende einer Prozedur und verläßt damit die Prozedur.

  • Es gibt kein GOTO ('cause goto considered harmful)
Kritischer Bereich:

LOCK
<Befehl1>;
<Befehl2>;
...
<Befehln>;
END

Die Folge von Befehlen wird mit gesperrtem Interrupt durchlaufen. Beim END wird der alte Zustand des Interrupt-Freigabe-Registers wiederhergestellt.

  • Im LOCK-Bereich sind die Befehle EXIT, BREAK, CONTINUE und RETURN nicht erlaubt.

13. Vordefinierte Prozeduren

Bit-Bearbeitung:

Sie können ein einzelnes Bit setzen:

SET (<Bit-Variable>)

SET (<Byte-Variable>, <Bitnummer>)

Sie können ein einzelnes Bit löschen:

CLR (<Bit-Variable>)

CLR (<Byte-Variable>, <Bitnummer>)

Sie können ein einzelnes Bit invertieren:

CPL (<Bit-Variable>)

CPL (<Byte-Variable>, <Bitnummer>)

  • Die Bitnummer muß eine Konstante zwischen 0 und 7 sein.
  • Bei Word-Variablen wird nur das niederwertige Byte bearbeitet.
  • Operationen auf Variablen im externen Segment sind nicht atomar.
Inkrementieren und Dekrementieren:

Sie können den Wert einer Variable um 1 erhöhen:

INCR (<Variable>)

Sie können den Wert einer Variable um 1 erniedrigen:

DECR (<Variable>)

Schieben:

Sie können die Bits einer Variable nach links schieben:

SHL (<Variable>)

Sie können die Bits einer Variable nach rechts schieben:

SHR (<Variable>)


14. Fehlermeldungen

Ausdruck vom Typ "WORD" erwartet
Ausdruck vom Typ "STRING" erwartet
Der vorgefundene Ausdruck hat nicht den erwarteten Typ.
Befehl erwartet
Syntax-Fehler in Befehl oder Befehlsfolge.
Bedingung wird immer zu "TRUE" ausgewertet.
Bedingung wird immer zu "FALSE" ausgewertet.
Eine logische Bedingung wird immer zu Wahr oder immer zu Falsch ausgewertet. Das kann ein Hinweis auf einen Programmierfehler sein.
Compilerrestriktion 'case:range'
Ein CASE-Bereich darf nur 255 Elemente umfassen.
Die interne Grenze "MaxIncludeLevel" wurde erreicht.
Die maximale Schachteltiefe für Include-Dateien ist erreicht.
Die interne Grenze "MaxNameLength" wurde erreicht.
Das lexikalische Element ist zu lang.
Die interne Grenze "MaxSymbols" wurde erreicht.
Die Symboltabelle ist voll.
Die interne Grenze "MaxNodes" wurde erreicht.
Der Ausdruck ist zu kompliziert.
Die Resource "<name>" ist aufgebraucht.
Der verfügbare Speicher oder die verfügbaren Register sind aufgebraucht. Der Speicher ist zu klein oder das Programm zu groß, es gibt zuviele Daten, oder ein Ausdruck ist zu komplex.
Ein "<element>" wurde erwartet, aber nicht gefunden.
Fehler in der Programmsyntax
Fehler beim Lesen von "<datei>": <meldung>
Fehler beim Oeffnen von "<datei>": <meldung>
Fehler beim Zugriff auf eine Datei. Die Meldung des Betriebssystems wird durchgereicht.
Interner Fehler "<procname>"
Kann nicht vorkommen.
Wenn doch, bitte senden Sie ein möglichst kurzes Programm, das den Fehler auslöst, an mich.
Konstanter Ausdruck erwartet
Es wird eine Konstante benötigt, der vorgefundene Ausdruck läßt sich aber nicht konstant auswerten.
Name "<name>" ist nicht definiert.
Der benutzte Name wurde noch nicht definiert.
Name "<name>" wurde mehrfach definiert.
Der Name wurde im aktuellen Gültigkeitsbereich bereits einmal definiert.
Typangabe (BYTE, WORD, STRING) erwartet
Integer, Short, Float, Real, Double usw. kennt der Compiler nicht.
Überlauf beim Berechnen eines konstanten Ausdrucks
Das Ergebnis eines konstanten Ausdrucks läßt sich nicht als 16bit-Wert darstellen.
Unbekanntes Praeprozessorkommando "<command>"
Benutzen Sie nur die definierten Kommandos.
Unerwartetes Ende der Datei
Die Datei ist zu Ende, das Programm aber noch nicht.
Ungueltiger Wert fuer "register bank"
Als Register-Bank sind nur 1 bis 3 und die 0 erlaubt.
Ungueltiger Wert fuer "interrupt vector"
Der angegebene Interrupt-Vektor ist unzulässig.
Ungueltiger Wert fuer "segment"
Das Speichersegment, in dem die Variable definiert wurde, ist hier nicht erlaubt.
Variable erwartet
In einer FOR-Scheife, als Argument für einen VAR-Parameter einer Prozedur oder als Argument einer eingebauten Prozedur wird eine Variable erwartet.
Variable, Konstante oder '(' erwartet
Syntax-Fehler in einem Ausdruck.
Variable oder Konstante erwartet
In einem Ausdruck darf ein Name nur eine Variable oder eine Konstante bezeichnen.
Verirrtes Zeichen "<zeichen>" im Programm
Mit diesem Zeichen beginnt kein gültiges lexikalisches Element.
Zeichenkette nicht beendet
Zeichenketten sind auf eine Zeile begrenzt und müssen auf dieser Zeile mit einem doppelten Anführungszeichen beendet werden.

15. Beschränkungen

Diese Beschränkungen können durch Neuübersetzung des Compilers verändert werden:

MaxIncludeLevel=4
Maximale Schachteltiefe für Dateien inklusive der Hauptdatei.
Auf dieser Tiefe werden weitere .fi-Direktiven ignoriert.
MaxInputLineLength=4096
Maximale Länge einer Quellcode-Zeile.
Längere Zeilen werden rücksichtslos in mehrere Zeilen umgebrochen.
MaxNameLength=80
Maximale Länge eines lexikalischen Elementes.
Längere Elemente werden rücksichtslos beschnitten.
MaxSymbols=1000
Größe der Symboltabelle.
MaxNodes=100
Maximale Größe eines Ausdrucks in der internen Repräsentation.
Komplexere Ausdrücke werden nicht mehr übersetzt.