\documentclass[11pt,a4paper]{article} % language support \usepackage[german]{babel} \usepackage[utf8]{inputenc} \usepackage{pslatex} % use native PostScript fonts \usepackage{multicol} \usepackage{url} % \url{} \path{} with correct "hyphenation" \usepackage{fancyvrb} % for code examples % \voffset-10mm % \pagestyle{empty} % \pagestyle{plain} % \pagestyle{headings} % \renewcommand{\baselinestretch}{1.08} %\usepackage{xspace} \parindent=0pt \begin{document} \title{ByteForth}[ByteForth für den AVR] \author{Willem Ouwerkerk} \maketitle (Red.: Der Autor leitet seit vielen Jahren als Voorzitter die Geschicke der HCC--Forth--gebruikersgroep. Wir freuen uns über die stets gute Zusammenarbeit mit unseren Forth--Nachbarn im Westen. Die holländische Originalfassung wurde von Fred Behringer übersetzt.) \renewcommand{\figurename}{Tabelle} \begin{multicols}{2} \section{Was ist AVR ByteForth?} AVR--ByteForth ist ein 8--Bit--Forth--Makrocompiler unter MS--DOS, der Maschinencode für einen Mikro--Controller ohne externes ROM und RAM erzeugt. Es soll auch kleine Mikro--Controller, die zu wenig RAM für ein komplettes Forth--System haben, in die Lage versetzen, mit Forth betrieben zu werden. Das ByteForth--System liefert selbstständig arbeitenden Maschinencode ohne den bekannten Forth--Interpreter, es hat einen eingebauten Simulator und der erzeugte Code kann auf dem PC ausgetestet werden. Zum Testen der Hardware muss der Code natürlich auf dem Mikro--Controller laufen. ByteForth ist ein Alles--in--einem--Paket und enthält: einen optimalisierenden Compiler, einen Assembler, einen Disassembler, einen Simulator, einen Tracer, einen Flash--ISP--Programmer und eine Online--Hilfefunktion. Vorhanden ist eine Bibliothek mit einer großen Zahl von getesteter Software, beispielsweise zur Ansteuerung von LEDs, numerische Routinen, I2C, RS232 usw. Außerdem sind viele vollständig ausgearbeitete Anwendungen vorhanden, so z.B. der Pincode--Türöffner, ein I2C--Thermometer, synchrone Servosteuerung usw. Im 'Egelwerkbuch der HCC--Forth--gg findet man eine ganze Reihe von Anwendungen für ByteForth. Die zugehörigen Dateien können auch aus dem Internet heruntergeladen werden. \section{Makrocompiler für ByteForth} \begin{itemize} \item Basiert auf ANS--Forth \item Stackbreite: 8 Bit, doppeltgenau 16 Bit, 4--fachgenau 32--Bit. \item Arrays, sowohl für 8-- als auch für 16--Bit--Zahlen. \item Einstellbare Speichereinteilung. \item Definierende Worte: \verb"CONSTANT" \verb"2CONSTANT" \verb"VARIABLE" \verb"2VARIABLE" \verb"VALUE" \verb":" \verb"CODE" \verb"CREATE" \verb"DOES>". \item Nichtstandard--Erweiterungen: \verb"BIT-SFR" \verb"FLAG" \verb"SFR" \verb"CONSTANTS" \verb"VARIABLES" \verb"2VARIABLES" \verb"REGISTER". \item Kontrollstrukturen: \verb"IF" \verb"ELSE" \verb"THEN" \verb"DO" \verb"LOOP" \verb"BEGIN" \verb"UNTIL" \verb"FOR" \verb"NEXT" usw. \item Präfixe für Datenworte: \verb"FROM" \verb"TO" \verb"+TO" \verb"INCR" \verb"DECR" \verb"CLEAR" usw. \item Alle Interrupts werden unterstützt. \item Makros können als Unterprogramme eingeführt werden. \item Spezielle Compiler--Anweisungen für das Arbeiten mit Strings: \verb|."| \verb|S"| \verb|[CHAR]| und \verb|LITERAL|. \end{itemize} \begin{figure*} \begin{tabular}{p{5.5cm}p{10.5cm}} {\bf Die 32 internen Register}& \\ Lade Konstante(n) in Extra--Akku& R0 oder/und R1 zur Verwendung mit dem Befehl LPM, \\ ByteForth--System & R1 oder R2 Highlevel--LOOP--Zähler\\ Bit--Variablen & Zusammen mit Register--Variablen in R2 oder R3 bis R15\\ Register--Variablen &Zusammen mit Bit--Variablen in R2 oder R3 bis R15\\ Akkus für Code--Definitionen& 10 Bytes, R16 bis R25\\ Pointer Reg. X, Y, Z & Obere 6 Bytes R26 bis R31\\ &X = Daten--Stack--Pointer\\ &Y = Variablen--Basis--Pointer\\ &Z = Frei für Locals, SLITERAL\\ &DOES> \verb"INLINE$" EXECUTE\\ \end{tabular} \medskip \begin{tabular}{p{5.5cm}p{10.5cm}} {\bf I/O--Register} & Wie vom Hersteller der SFR--0 bis SFR--63 oder SFR--255 definiert\\ \end{tabular} \medskip \begin{tabular}{p{5.5cm}p{10.5cm}} {\bf Internes RAM}&\\ Reg. und I/O--Reg.& Auch auf den RAM--Adressen 0 bis 95 oder 255\\ Datenstack &Adresse 127 bis 96 oder 287 bis 256\\ Localstack & Adresse 128 oder 288 nach oben zu unter RSP\\ Returnstack & Mit Locals im selben RAM--Bereich von Adr. 255 oder 319 ab nach unten\\ Variablen & Oberhalb des Returnstacks bis RAMTOP. Maximal 64 an der Zahl.\\ Variablen & Arrays beginnen bei RAMTOP und wachsen nach unten auf Variablen zu\\ Zahlenumwandlung &Über Array im ByteForth--System\\ \end{tabular} \medskip \begin{tabular}{p{5.5cm}p{10.5cm}} {\bf ROM}&\\ Interrupt--Vektoren & 0 bis xxx (je nach AVR verschieden)\\ Programm & yyy bis ROMTOP\\ \end{tabular} \caption{\label{byteforth:speicheraufteilung}Wie ByteForth den Speicher aufteilt} \end{figure*} \begin{center} \includegraphics[width=0.9\columnwidth]{2007-Sonderheft-AVR/S8252AA}\\ {\footnotesize Eine einfache Entwicklungs--Platine mit ATMega8515--Prozessor} \end{center} \section{Interaktives Testen (Software--Simulator)} Alle mit AVR--ByteForth geschriebenen Routinen können vom eingebauten Simulator durch Eintippen ihres Namens ausgeführt werden. Variablen können gelesen und beschrieben werden, (Teil)--Programme können ausgeführt werden. \verb"FLYER" ist eine Routine, die die Ausführung auf dem PC zum AVR--Simulator leitet. Sie ist in der Routine \verb"VARIABLES" enthalten und wird später vom internen Code aus AVR –ByteForth angepasst. Wenn neuer Code zu compilieren ist, beginne man mit \verb"EMPTY" (entferne jeden eventuell noch im Programmpuffer enthaltenen Code) und den Namen des betreffenden Mikro--Controllers (z.B. TINY2313). Mit dem eingebauten Simulator kann viel Code getestet werden, die Hardware natürlich nicht. Beispiel: \begin{verbatim} 1 DUP .S \ Teste die Bibliotheksroutine DUP \ mit der Zahl 1 auf dem Stack ( 1 1 ) \ Die Antwort von Forth \end{verbatim} Das Wort NIP zu Forth hinzufügen: \begin{verbatim} : NIP ( x y -- y ) SWAP DROP ; 1 2 NIP .S \ Teste NIP ( 2 ) \ Die Antwort von Forth \end{verbatim} Die Routinen \verb"DUP" und \verb"NIP" funktionieren. Alle selbstgemachten Codeteile können auf die gleiche Art getestet werden. \section{Der ByteForth--Arbeitszyklus} \begin{enumerate} \item Den Crosscompiler (wieder) starten. \item Das Programm compilieren. \item ISP--Kabel anschließen, soweit das noch nicht geschehen ist. \item Den Flash--Speicher leeren: E. \item Den Flash--Speicher programmieren: P. \item Den Flash--Speicher verifizieren: V. \item Das Programm absichern: Lock1 und Lock2. \item ..... und das Programm läuft. \end{enumerate} \section{Der interne Aufbau von ByteForth} Ich darf mit der Speichereinteilung nach dem Start von ByteForth beginnen. Davon hängen viele Entwurfsentscheidungen ab. Das Format von RAM und ROM richtet sich nach dem gewählten AVR. \verb"RAMTOP" und \verb"ROMTOP" bekommen daher je nach gewähltem AVR den einen oder anderen Wert. R16 und höher sind die Akkus für Codedefinitionen von ByteForth. Es sind die einzigen Register, in welche man Konstanten direkt einspeichern kann. Die Grundeinstellung wird mit Hilfe von internen Tabellen vorgenommen, welche Informationen über die Speicherausmaße des gewählten AVRs enthalten. Über die Worte \verb"MEMORY" oder \verb"MAP" kann diese Einteilung verschieden gewählt werden, so dass sich der AVR optimal einstellen lässt. Die \figurename\ \ref{byteforth:speicheraufteilung} fasst zusammen, wie ByteForth die Register, RAM und ROM verwendet. \section{Beispiele von internem Code aus dem ByteForth--Compiler} Die Beschreibung aller unterstützten AVR--Typen ist in einer Anzahl von Tabellen festgelegt. Die erste davon enthält die Größe des Flash--Speichers. Der älteste und kleinste ist der AT90S2313, der jedoch schon wieder überholt ist. Der neueste in diesem Beispiel ist der ATmega649 mit 64 kByte Flash. \verb"ROMTOP" verwendet diesen Wert. \begin{verbatim} $FD00 $8000 $FD00 $FD00 $FD00 $FD00 $8000 $2000 $1000 $0800 $2000 $1000 $0800 $A000 $4000 $4000 $2000 $1000 $0800 $0400 $2000 $4000 $4000 $8000 $2000 $0800 $FD00 $4000 $2000 $8000 $FD00 $4000 $4000 $2000 $2000 $0400 $0800 $0400 $2000 $2000 $1000 $1000 $1000 $0800 $0800 $0800 $0800 #AVR AVRDATA iflash \end{verbatim} \begin{figure*} \begin{center} \begin{tabular}{ll} {\bf IRTC}&{\bf ByteForth}\\ \verb": TST 3 I/O PORTA C! ;" & \verb": TST 3 TO PORTA ;"\\ \\ {\bf Ergebnis IRTC ohne Optimierer} & {\bf AVR--ByteForth ohne Optimierer}\\ \verb"( 20 Byte Code exkl. C! )" & \verb"( 10 Byte Code )"\\ \verb"PUSHT," & \verb"R16 3 LDI,"\\ \verb"TOSL 3 LDI," & \verb"-X R16 ST,"\\ \verb"TOSH 0 LDI," & \verb"R16 X+ LD,"\\ \verb"\verb"PUSHT," & \verb"PORTA R16 OUT,"\\ \verb"TOSL PORTA LDI," & \verb"RET,"\\ \verb"TOSH 0 LDI," & \\ \verb"' C! CALL," & \\ \verb"RET," & \\ \\ {\bf Ergebnis IRTC mit Optimierer} & {\bf AVR-ByteForth mit Optimierer }\\ \verb"( 8 Bytes )" & \verb"( 6 Bytes )"\\ \verb"R16 3 LDI," & \verb"R16 3 LDI,"\\ \verb"PORTA R16 STS," & \verb"PORTA R16 OUT,"\\ \verb"RET," & \verb"RET,"\\ \end{tabular} \caption{\label{byteforth:vergleich}IRTC und ByteForth genetieren unterschiedlichen Maschinencode.} \end{center} \end{figure*} Da nicht alle AVRs alle Maschinenbefehle unterstützen, habe ich die Befehlssätze in verschiedene Untergruppen aufgeteilt. Der Assembler und der Disassembler machen davon Gebrauch. Es gibt AVRs mit 90 Befehlen, die größeren Typen haben jedoch gut 131 Maschinenbefehle, worunter sich auch einige Varianten der Hardware--Multiplikation befinden. \begin{verbatim} \ Instruction set size #131 #131 #131 #131 #131 #131 #131 #120 #120 #120 #120 #120 #120 #131 #131 #131 #131 #131 #120 #120 #131 #131 #131 #131 #131 #118 #131 #131 #131 #131 #121 #131 #131 #128 #118 #090 #118 #090 #118 #118 #118 #118 #118 #118 #118 #118 #118 #AVR AVRDATA iopcode \end{verbatim} Nun einige Makros. Ein Makro ist ein vorassembliertes Stück Code, hauptsächlich für die wichtigsten Forth--Primärworte, wie z.B. SWAP und UM*, usw. Jedes Makro bekommt zunächst sein Stackverhalten mit. \$C1 bei DUP zeigt an, dass der Optimierer vorher Daten in R16 erwartet und danach R16 auf dem Stack ablegt. \$01 bei OVER bedeutet, dass vorher kein Standard--Optimierverhalten vorliegt und hinterher R16 auf den Stack gelegt wird. Und schließlich legt \$21 bei + fest, dass vorher R16 und R17 erwartet und nachher R16 wieder gePUSHt wird. \begin{verbatim} $C1 MACRO DUP ( x -- x x ) \ Makro "dupe" R16 X LD, -X R16 ST, RET, END-CODE $01 MACRO OVER ( x1 x2 -- x1 x2 x1 ) \ Makro XL 1 ADIW, R16 X LD, XL 1 SBIW, -X R16 ST, RET, END-CODE $21 MACRO + ( x1 x2 -- x3 ) \ Makro "plus" R16 X+ LD, R17 X+ LD, R16 R17 ADD, -X R16 ST, RET, END-CODE \end{verbatim} \begin{figure*} \begin{center} \includegraphics[width=0.8\textwidth]{2007-Sonderheft-AVR/willemsAVRklas}\\ Willem Ouwerkerk erläutert, wie man einen AVR--Prozessoren programmiert. \end{center} \end{figure*} Um ByteForth noch etwas weiter zu optimieren, wurden zusätzlich zu den Standard--Optimierungsschritten noch einige Spezialfälle aufgenommen. Es wird ein bisschen Code von der/den vorausgegangenen Routine/n entfernt und dafür ein besser passendes Stück Code erzeugt. Im Folgenden wird ein Teil des Optimierers gezeigt, der die Plus--Operation weiter verbessert. Es treten hier vier Fälle auf, aber in Wirklichkeit sind das zehn Fälle. In allen Fällen wird der Code für das Literal (oder Konstante) entfernt und die zugehörigen Daten werden aufbewahrt. In zwei Fällen kann auch ein \verb"PUSH" auf den Datenstack entfernt werden. Im letzten Fall werden beide Literals entfernt und das Aufzählen wird schon während des Compilierens vorgenommen, wonach dann ein neues Literal assembliert wird. \begin{footnotesize} \begin{verbatim} :SPECIAL + CASE 2STATES $05 OF REMOVE-LIT >R R16 X+ LD, R16 R> ADDI, -X R16 ST, 1 RESET-OPT 0 ENDOF ( Stack + Literal ) $15 OF REMOVE-LIT&PUSH >R R16 R> ADDI, -X R16 ST, 1 RESET-OPT 0 ENDOF ( R16 + Literal ) $25 OF REMOVE-LIT&PUSH >R R16 R> ADDI, -X R16 ST, 1 RESET-OPT 0 ENDOF ( Literal + Literal ) $55 OF REMOVE-LIT >R REMOVE-LIT R> + 255 and >R R16 R> LDI, -X R16 ST, OPT3 RESET-OPT 5 SET-OPT 0 ENDOF -1 SWAP ( Kein Spezialfall gefunden ) ENDCASE ;SPECIAL \end{verbatim} \end{footnotesize} Die definierenden Worte in ByteForth, die sich um die verschiedenen RAM--Ausmaße kümmern, werden schon während des Auswählens des AVR--Typs, der Variablen \verb"VA" (Variablen--Adresse) und der Variablen \verb"VE" (Variablen--Ende) gesetzt. Sie werden gebraucht, um zu kontrollieren, ob noch genügend RAM für \verb"VARIABLE" vorhanden ist. \begin{footnotesize} \begin{verbatim} : VARIABLE ( "name" -- addr ) \ Cross VA VE > 20 ?ERROR \ Noch innerhalb des gueltigen RAMs? CREATE VA , 1 DUP , +TO VA IMMEDIATE DOES> @ POSTPONE LITERAL \ Variablen-Offset auf den Stack ; \ waehrend der Laufzeit! (wie ueblich). \end{verbatim} \end{footnotesize} \section{Unterschiede zu anderen AVR--Compilern} Zunächst der Unterschied zwischen IRTC und AVR--ByteForth bei der Code--Erzeugung, danach die einfache Syntax des I/O--Bit--Befehls gegenüber dem IAR--C--Compiler. Der MPE(IRTC)--Forth--Compiler verwendet überall die langen STS-- und STD--Befehle. ByteForth verwendet sie nirgends. IRTC ist ein 16--Bit--Forth, ByteForth ist ein 8--Bit--Forth; \figurename\ \ref{byteforth:vergleich} zeigt die Gegenüberstellung. Der IAR--C--Compiler benötigt einen viel umständlicheren Code, um ein einfaches SET , CLEAR oder FROM (lies) Bit zu verwirklichen. AVR--ByteForth kennt neben diesen Präfix--Operatoren noch ADR . ADR fischt die I/O--(Bit)Adresse für die Verwendung in Code--Definitionen heraus. Sodann gibt es noch TO , TOGGLE und PULSE . Man überlege sich deren Funktion selbst. Die Präfix--Operatoren funktionieren nicht nur mit BIT--SFR , sondern mit allen in AVR--ByteForth eingebauten Datentypen. Die unten stehenden Beispiele erzeugen beide einen SBI-- oder CBI--Befehl. Es ist sogar möglich, Präfix--Operatoren einem neuen Datentyp zuzufügen, und zwar mit Hilfe von METHODS . Zunächst IAR--C : \begin{verbatim} // Macros for setting/clring IO bits // in the IAR C-compiler // a=[sfr name] , b=[bit number] #define set_sfrbit(a,b) ( (a) |= (1<<(b))) #define clr_sfrbit(a,b) ( (a) &= (~(1<<(b))) ) #define test_sfrbit(a,b) ( (a) & (1<<(b)) ) #define DIVVALVE_PORT PORTB #define DIVVALVE_FOR 6 // Port B.6 void DivValveOnForward(void) { set_sfrbit(DIVVALVE_PORT, DIVVALVE_FOR); } \end{verbatim} Nun AVR--ByteForth: \begin{verbatim} PORTB 6 BIT-SFR VALVE \ PB.6 controls valve : VALVE-ON ( -- ) SET VALVE ; \end{verbatim} Programmbeispiel: Zuerst wird der gewünschte Mikro--Controller ausgewählt und in diesem Zusammenhang werden auch alle Einstellungen, die zu diesem Controller gehören, vorgenommen, wie z.B. das Speichermodell, die einsetzbaren Opcodes und das ISP--Programmierprotokoll. Danach werden alle Labels und Interrupt--Vektoren, die zu diesem Chip gehören, geladen. Dann beginnt das eigentliche Programm. Mit Hilfe des Labels PORTB und des definierenden Wortes SFR wird ein I/O--Register zugänglich gemacht. Dann die erste Colon--Definition namens BLINKER , die die LEDs auf PORTB mal schnell aufblitzen lässt. Dann geht es wieder zum Hauptprogramm, wo zunächst die Forth--Stackmaschine initialisiert wird. Sodann wird das I/O--Register auf Ausgabe geschaltet und BLINKER wird compiliert. Danach treten wir in eine Endlos--Schleife ein, in welcher fast alle Standard--Worte stehen, mit Ausnahme von TO LEDS. Auf das Wort ; folgt das Wort MAIN , das ähnlich wie IMMEDIATE auf das zuletzt definierte Wort rückwirkt und den Resetvektor mit der Adresse dieses Wortes füllt. Das war's. \section{Links} ByteForth Homepage (niederländisch):\\ \hspace*{\fill}\url{http://www.forth.hccnet.nl/byteforth.htm} \end{multicols} \newpage \begin{listing}[1]{1} \ Lauflicht mit ATtiny2313 auf Port B, Laenge: 162 Bytes, laenger \ hauptsaechlich wegen des inline expandierten MS-Makros. TINY2313 \ Compiliere fuer ATtiny2313 INCLUDE TARGET.FRT \ Lade Labels & Vektoren PORTB SFR LEDS \ Port B mit 8 LEDs : BLINKER ( -- ) \ Zeige Hochfahren an 0 TO LEDS 250 MS \ Alle LEDs an -1 TO LEDS 250 MS ; \ Alle LEDs aus : LAUFLICHT ( -- ) SETUP-BYTEFORTH \ Installiere Forth-Maschine -1 SETDIR LEDS \ Port B wird als Ausgang verwendet BLINKER BEGIN 8 0 DO \ Durchlaufe Schleife achtmal 1 I LSHIFT \ Mache Bitmuster INVERT TO LEDS \ Kehre um und steuere die LEDs an 100 MS \ Noch etwas warten LOOP AGAIN \ Fange wieder von vorn an ; MAIN \ Setze das LAUFLICHT in den Resetvektor \end{listing} \vfill \begin{center} \includegraphics[width=0.8\textwidth]{2007-Sonderheft-AVR/avrushi}\\ Ein \emph{malender} Ushi--Roboter mit einer AVR--Steuerung\\ und einem AVR--Sensormodul --- programmiert in ByteForth \end{center} \vfill \end{document}