% Content-encoding: UTF-8 \documentclass[ngerman]{article} \usepackage[T1]{fontenc} \usepackage[latin1]{inputenc} \setcounter{secnumdepth}{0} \setcounter{tocdepth}{0} \renewcommand{\reftextbefore}{auf der vorherigen Seite} \renewcommand{\reftextfacebefore}{auf der gegenüberliegenden Seite} \renewcommand{\reftextafter}{auf der nächsten Seite} \renewcommand{\reftextfaceafter}{auf der gegenüberliegenden Seite} \renewcommand{\figurename}{Bild} \begin{document} \title{Forth von der Pike auf — Teil 10} \author{Ron Minke} \maketitle \vspace{-0.5ex} Die mit freundlicher Genehmigung der HCC--Forth--gebruikersgroep in der Vierten Dimension in einer Übersetzung von Fred Behringer wiedergegebene achtteilige Artikelserie erschien ursprünglich in den Jahren 2004 und 2005 in der Zeitschrift \emph{Vijgeblaadje} unserer niederländischen Forth--Freunde. Die Firma Atmel hat inzwischen größere Bausteine ihrer Serie entwickelt. Das reizte den Autor dazu, sein Projekt auf einen größeren Mikro--Controller zu übertragen. Im Vijgeblaadje war Teil 9 der Serie (deutsche Übersetzung im VD--Heft 3--4/2007) erschienen. Und hier geht es nun weiter: Auch die vorliegende Übersetzung aus dem Niederländischen stammt von Fred Behringer. Hier kommt nun Teil 10 der Wiedergabe des Versuchs, ein AVR--Forth--System mit der Voraussetzung \emph{from scratch} zu erstellen. \begin{figure*}[b] \begin{center} \begin{tabular}{rllrllll} &&\multicolumn{2}{l}{AND ( n2 n1 --- n3 )}&&& tmp. & Daten-\\ && &&&& AVR- & Stack-\\ & \multicolumn{2}{l}{Input} && \multicolumn{2}{l}{Output}& Reg. & Adresse\\ &&&&&&&\\ &0 &&& 0 && --- & 0x100 \\ &1 & n2 unteres Byte & & 1 & n3 unteres Byte & R20 & 0x0FF\\ &2 & n2 oberes Byte & SP $\rightarrow$ & 2& n3 oberes Byte & R21 & 0x0FE\\ &3 & n1 unteres Byte & & 3&&R22 & 0x0FD\\ SP $\rightarrow$ & 4 & n1 oberes Byte && 4 && R23 & 0x0FC\\ & 5 & && 5 &&\\ \end{tabular} \caption{\label{pike10:and-effekt}Darstellung der Stack--Einträge im Speicher am Beispiel von \texttt{AND}} \end{center} \end{figure*} \begin{figure*}[t] \begin{center} \begin{tabular}{rllrllll} &&\multicolumn{2}{l}{AND ( n2 n1 --- n3 )}&&& tmp. & Daten-\\ && &&&& AVR- & Stack-\\ & \multicolumn{2}{l}{Input} && \multicolumn{2}{l}{Output}& Reg. & Adresse\\ &&&&&&&\\ & 0 &&& 0 && --- & 0x100\\ & 1 & n2 oberes Byte && 1 & n3 oberes Byte & R20 & 0x0FF\\ & 2 & n2 unteres Byte &SP $\rightarrow$ & 2 & n3 unteres Byte & R21 & 0x0FE\\ & 3 & n1 oberes Byte & & 3 & & R22 & 0x0FD\\ SP $\rightarrow$ & 4 & n1 unteres Byte& & 4 & & R23 & 0x0FC\\ & 5 & && 5 &&\\ \end{tabular} \caption{\label{pike10:and-effekt2}Neue Darstellung der Stack--Einträge im Speicher am Beispiel von \texttt{AND}} \end{center} \end{figure*} \begin{multicols}{2} \section{Reihenfolge der Daten} Im vorliegenden Teil überlegen wir uns, ob die Entscheidungen, die wir in Teil 2 dieser Serie getroffen haben, gute Entscheidungen waren. {\bf Wir trafen in Teil 2 drei Entscheidungen:} \begin{enumerate} \item Die Forth--Stacks SP und RP wachsen nach UNTEN. \item Das untere Byte eines 16--Bit--Wortes wird zuerst auf den Stack gelegt, darunter dann das obere Byte. \item Der Forth--Pointer zeigt auf das obere Byte eines 16--Bit--Wortes (und also – in Abweichung von der Arbeitsweise des AVR--SPs – nicht auf den leeren Platz unter dem Wort). \end{enumerate} Die Entscheidung 1 bleibt so. Die Entscheidungen 2 und 3 haben mit der Art und Weise zu tun, wie der AVR--Prozessor selbst mit seinem eigenen (Return--)Stack umgeht. \section{Was bewirkt das?} Was geschieht da nun genau? Angenommen, ein willkürlich herausgegriffenes Stück AVR--Maschinencode leitet einen Unterprogramm--Aufruf (CALL) ein. Beim Abarbeiten des CALLs sieht die Reihenfolge (in Pseudocode, in Bytes) wie folgt aus: \begin{verbatim} MOV (AVR-SP),unteres-Byte-aktueller-PC + 1 DEC AVR-SP MOV (AVR-SP),oberes-Byte-aktueller-PC + 1 DEC AVR-SP MOV PC,(aktueller-PC) \end{verbatim} Wir sehen hier, dass der Stack nach UNTEN wächst und dass ZUERST das Datenwort abgespeichert und dann erst weiterer Platz geschaffen wird. Der AVR--SP zeigt also offensichtlich auf den ersten freien Platz auf dem Stack. So hatten die Entwickler bei ATMEL sich das zumindest überlegt. Beim Einbringen von Struktur in die Verwendung der Register (siehe Teil 5) haben wir uns an diese Reihenfolge (Entscheidung 3) gehalten. Benötigt ein Wort beispielsweise zwei Stack--Einträge, dann bekommen wir das % folgende Bild \vref{pike10:and-effekt} (wir nehmen das Wort AND als Beispiel). In diesem Beispiel wurde der Top--of--Datastack auf die Adresse 0x100 gesetzt. Das obere Byte eines Datenstack--Eintrages liegt an einer bestimmten Adresse, das untere Byte liegt genau eine Adresse höher. Auf diese Weise kam ein experimentelles Forth zustande, das ausgezeichnet arbeitet! Trotzdem wird man das Gefühl nicht los, dass es auch einfacher geht. \section{Ein Stück Forthcode} Irgendwo im Quelltext unseres Forth--Programms steht die Wortfolge: \centerline{$\ldots$\ \texttt{DUP}\ \texttt{OVER}\ $\ldots$} Beim Übersetzen in Maschinencode durch den Atmel--Assembler kriegen wir im vorgefertigten Teil des Flash--Speichers: \begin{tabular}{lll} Flash--Adresse & Code & Quelle\\ $\vdots$ & $\vdots$& $\vdots$\\ 0230 & 0526 & .dw DUP\\ 0231 & 078A & .dw OVER\\ $\vdots$ & $\vdots$& $\vdots$\\ \end{tabular} Bei der \emph{Außenbord}--Version von Forth, mit externem RAM--Speicher, wird dieser Code--Teil beim Hochfahren ins RAM kopiert. Um jedoch der Entscheidung 3 zu genügen, müssen wir die Reihenfolge der Bytes innerhalb eines Wortes im Flash umdrehen. Warum das? Wir holen ein Wort aus dem Flash über das Z--Register und den Befehl LPM ab. Aus dem unteren Byte des ersten Wortes holen wir uns \texttt{26}, und aus dem oberen Byte \texttt{05}. Wir halten uns an die Absprache: Das obere Byte kommt an eine bestimmte Adresse, das untere Byte genau eine Adresse darüber. Wenn die Flash--Daten in den RAM--Speicher kopiert werden, müssen wir also erst das obere Byte \texttt{05} abspeichern, und danach dann das untere Byte \texttt{26}. Die Reihenfolge ist hier andersherum! Die Kopier--Routine erledigt das prima für uns, aber aufgepasst! \begin{figure*}[t] \begin{center} \begin{tabular}{rllrllll} &&\multicolumn{2}{l}{AND ( n2 n1 --- n3 )}&&& tmp. & Daten-\\ && &&&& AVR- & Stack-\\ & \multicolumn{2}{l}{Input} && \multicolumn{2}{l}{Output}& Reg. & Adresse\\ &&&&&&&\\ & 0 &&& 0 && --- & 0x100\\ & 1& n2 oberes Byte &&1 &n3 oberes Byte &R19& 0x0FF\\ & 2& n2 unteres Byte &SP $\rightarrow$ &2 &n3 unteres Byte& R18 & 0x0FE\\ & 3& n1 oberes Byte &&3 && R17& 0x0FD\\ SP $\rightarrow$ &4 & n1 unteres Byte &&4 && R16& 0x0FC\\ & 5 & && 5 &&\\ \end{tabular} \caption{\label{pike10:and-effekt3}Endgültige Darstellung der Stack--Einträge im Speicher am Beispiel von \texttt{AND}} \end{center} \end{figure*} \section{Stück Text} Schwieriger wird es, wenn ein Stück Text kopiert werden soll. Text steht im Quelltext (Übers.: Ich belasse es beim holländischen Original) so wie: \begin{verbatim} .db "Stukje tekst" \end{verbatim} Dieser Text wird Buchstabe für Buchstabe paarweise in den Flash--Speicher gesetzt. (Der Flash enthält Wörter, keine Bytes.) \begin{tabular}{lll} Flash--Adresse & Code & Quelle\\ $\vdots$ & $\vdots$& $\vdots$\\ 0310&7453& \verb|.db "Stukje tekst"|\\ 0311&6B75&\\ 0312&656A&\\ 0313&7420&\\ 0314&6B65&\\ 0315&7473&\\ $\vdots$ & $\vdots$& $\vdots$\\ \end{tabular} Wenn wir dieses Stück Text mit unserer Kopier--Routine ins RAM platzieren, merken wir, dass die Zeichen paarweise vertauscht erscheinen. Dadurch, dass wir erst das obere Byte abspeichern, und danach dann das untere Byte, werden die Buchstaben verkehrt herum abgelegt. Wie lösen wir das Problem? Ganz einfach: Im Quelltext setzen wir sie von in natürlicher Reihenfolge paarweise nach in umgekehrter Reihenfolge. Mit einem kleinen Software--Zusatz, einem Pre--Compiler, passen wir den Quelltext so an, dass Textteile paarweise umgedreht werden. \begin{tabular}{lll} Flash--Adresse& Code & Quelle\\ $\vdots$ & $\vdots$& $\vdots$\\ 0310&5374 & \verb|.db "tSkuejt kets"|\\ 0311&756B\\ 0312&6A65\\ 0313&2074\\ 0314&656B\\ 0315&7374\\ $\vdots$ & $\vdots$& $\vdots$\\ \end{tabular} Was bringt uns nun diese ganze Mühe ein? Auf jeden Fall ein funktionierendes Forth--System! Aber geht das nicht auch einfacher $\ldots$?? Es wird Zeit, darüber nachzudenken, ob unsere Entscheidungen 2 und 3 die richtigen waren. \section{Zurückdrehen} Die in Teil 2 gewählte Reihenfolge der Bytes auf dem Stack war an die Art und Weise gekoppelt, wie Atmel den Returnstack--Pointer des Systems implementiert hat. Für den Bau eines einfachen und begreifbaren Forth--Systems ist diese Reihenfolge unhandlich. Es ist aber nie zu spät einzusehen, dass eine Entscheidung nicht die richtige war! Wir drehen die Reihenfolge der Bytes auf dem Stack einfach um. Das Beispiel mit dem Wort \texttt{AND} ändert sich nun, wie im Bild \vref{pike10:and-effekt2} zu sehen ist. Wenn wir uns den Inhalt der Prozessor--Register anschauen, sehen wir, dass das untere Byte im \emph{oberen} Register gelandet ist, und umgekehrt. Wie lösen wir nun wieder dieses Problem? Dazu schauen wir uns zunächst die Abbildung der AVR--Register auf den internen Speicher an. \section{AVR--Register} Der AVR--Prozessor hat 32 frei verfügbare 8--Bit--Register an Bord: R00 bis einschließlich R31. Hiervon können (und sollen) 8 Register zu 4 Registerpaaren zusammengekoppelt werden. Sie lauten: \begin{verbatim} R30 – R31 Z R28 – R29 Y R26 – R27 X R24 – R25 W \end{verbatim} Die Register liegen der Reihe nach wie folgt im internen AVR--Speicher: \begin{verbatim} R31 – 0x1F ZH R30 – 0x1E ZL R29 – 0x1D YH R28 – 0x1C YL . . . . R01 – 0x01 R00 – 0x00 \end{verbatim} Wenn wir uns diese Reihenfolge genauer betrachten, fällt uns auf, dass bei den {\bf Registerpaaren} das untere Byte an einem niedrigeren Speicherplatz liegt als das obere Byte. Das ist genau die Reihenfolge, die wir benötigen! Wir müssen die Reihenfolge beim Verarbeiten der Register im Beispiel beim Wort \texttt{AND} also {\bf umkehren}. Wir wählen dabei \texttt{\bfseries R16} als neuen Startpunkt. Die endgültige Version lautet dann, wie im Bild \vref{pike10:and-effekt3} zu sehen ist. Mit dieser Maßnahme sorgen wir dafür, dass bei einem Dump des betreffenden Stücks Speicher (der Register, bzw.\ des Datenstacks) dasselbe Bild entsteht. \section{Stück Text II} Nun, da wir die Reihenfolge der Bytes innerhalb eines Wortes zurückgedreht haben, kommt das Textbeispiel \emph{Stukje tekst} auch wieder in der richtigen Reihenfolge im Flash--Speicher zu liegen. Den Pre--Compiler für das Paarweise--Umdrehen haben wir nicht mehr nötig. Auch die Kopier--Routine braucht nicht mehr verkehrt herum zu kopieren; alles kann nun Byte für Byte 1 zu 1 kopiert werden. Es ist gelungen: Eine einfachere Version unseres Forth--Codes! Die ursprünglichen Entscheidungen 2 und 3 erweisen sich im Nachhinein als keine glückliche Wahl. Die neuen Entscheidungen lauten: \begin{enumerate} \item Bleibt. \item Das obere Byte eines 16--Bit--Wortes wird zuerst auf den Stack gelegt, darunter dann das untere Byte. \item Der Forth--Pointer zeigt auf das {\bf untere} Byte eines 16--Bit--Wortes (und also --- in Abweichung von der Arbeitsweise des AVR--SPs --- nicht auf den leeren Platz unter dem Wort). \end{enumerate} %\begin{center} %\includegraphics[width=0.90\columnwidth]{2007-0304/pike9-memmap} %\end{center} \end{multicols} \end{document}