% Content-encoding: UTF-8 \documentclass[ngerman]{article} \usepackage[utf8]{inputenc} \usepackage{multicol,babel} \usepackage{xspace} \setcounter{secnumdepth}{0} \setcounter{tocdepth}{0} %\newcommand{\code}[1]{\texttt{#1}} %\newcommand{\ret}{\textsf{$<$ret$>$}\xspace} \newcommand{\ret}{$\hookleftarrow$\xspace} \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}{Abbildung} \begin{document} \title{Interrupt--Service--Routine für den atmega168:\\ Uhr mit \texttt{MAIN}--Wort} \ifx\shorttitle\undefined\else \shorttitle{atmega168--Interrupt--Service--Routine} \fi \author{Michael Kalus, A. Krüger} \begin{document} \maketitle Klar ist wohl, dass zeitkritische Interrupts sofortigen Service erfordern. Sie müssen also zwingend auf der Maschinencode--Ebene angestoßen werden. Aber lange nicht alle Prozeduren sind dann in der folgenden Ausführung wirklich so zeitkritisch, dass sie komplett in Maschinencode formuliert sein müssten. Also möchte man Forth benutzen, weil es einfacher zu handhaben ist\footnote{Damit es nicht in Vergessenheit gerät, sei hier betont, dass auch Forth Maschinensprache ist. Denn letztendlich laufen auch die Forthworte auf der untersten Ebene als Instruktionen der zugrunde liegenden Maschine ab. Der innere Interpreter ist low level codiert. Jedenfalls gilt dies immer, wenn die Maschine nicht selbst generisch Forth ausführt. Kennt man den Aufbau des Forth--Systems, kann man es auch für eigene Zwecke manipulieren. Am Beispiel des amforth für den atmega168 ist das schön zu sehen.} Doch sollte man dann alles in Forth machen? Gibt es bei Steuerungsaufgaben überhaupt Prozeduren, die weder pünktlich noch schnell bearbeitet werden müssten, also tatsächlich einem irgendwie gearteten Forth--Tasker überlasen werden könnten? Letzteres möchten wir praktisch verneinen. So haben wir das klassische Konzept benutzt, eine ISR immer durch den dem Prozessor eigenen Mechanismus zu beginnen. Interrupts können sich dann unterbrechen. Register werden einfach auf den Returnstack gesichert, und von dort zurückgeholt. Es ist einfach und übersichtlich. Setzt allerdings voraus, dass man seinen Prozessor kennt --- bzw.\ zieht nach sich, dass man ihn kennen lernt. Auf Forth High Level muss man deswegen jedoch keineswegs verzichten. Das kommentierte Listing zeigt, wie ein Schrittmacher für eine Uhr auf dem ATmega168 gemacht werden kann. Aber erstmal schildern wir das Prinzip. \begin{multicols}{2} \section{So gehts} Über den Interrupt--Vektor des Timer2 wird zunächst die zugehörige ISR angesprungen. Diese ruft schließlich das Forthwort auf, welches den zeitunkritischen Service macht. Forth kehrt dann zu seiner aufrufenden ISR zurück, um von dort aus den Interrupt zu benden. Auf diese Weise rettet jede ISR ihre wichtigen Systemzustände, sie sind gekapselt. Und damit gegenseitig unterbrechbar. \begin{figure*}[t] \begin{center} \begin{minipage}{0.6\textwidth} \verb|sp@ depth 1- cells dump| \ret\\ Interrupt request $\Rightarrow$ interrupt flag set\\ pc $\rightarrow$ interrupt vector $\rightarrow$ ISR \ldots\ $\rightarrow$forth-word $\rightarrow$ ISR $\rightarrow$ \texttt{rti} \end{minipage} \end{center} \caption{\label{schema1}Interrupt--Schema} \end{figure*} Das folgende Beispiel für den atmega168 und amforth3.6 zeigt die Details --- den Assembler von Lubos Pekny hatten wir zuvor in den atmega168 auf das amforth geladen. Zunächst probieren wir das Gesagte als Code--Routine aus, also noch ohne den Interrupt zu benutzen. \begin{verbatim} code test XH push, ( sichere alte IP) XL push, here 4 + dup ff and XL swap ldi, ( setze neue IP) 100 / XH swap ldi, end-code ( next = neue IP ) ." hallo world" ( Ist *nicht* statesmart, ) ( compiliert sofort ins flash. ) [ here 1+ , ( Zeiger auf xt ) here 1+ , ( Zeiger zum folgenden Code ) XL pop, ( hole alte IP ) XH pop, end-code ( next = alte IP ) > ok > test hallo world ok \end{verbatim} \section{Uhr für den atmega168} Nun wenden wir diese Technik für eine ISR an, um einen Forth--Schrittmacher zu erzeugen. Im atmega168 arbeitet der Timer2 und kann freilaufend einen Überlauf--Interrupt auslösen. Damit ist er gut geeignet, um so einen Schrittmacher zu treiben. Das Listing zeigt im Detail, wie es gemacht wird. Um für die Uhr eine Sekunde zu erzeugen, lassen wir den Timer einfach im gegebenen Prozessortakt laufen\footnote{Der Software--Phasen--Akkumulator ermöglicht es, bei beliebigem Prozessortakt --- Quarze sind auch nicht ganz genau, und außerdem temperaturabhängig --- im Mittel eine recht genaue Zeit zu erhalten. Es braucht lediglich ein passendes Vielfaches. Reichen zwei Bytes dafür nicht, kann ohne weiteres eine feinere Teilung gemacht werden, indem man drei oder noch mehr Bytes benutzt; 32--Bit--Akkus sind üblich. In diesem Verfahren wird statt genauer Auflösung --- die ja letztendlich Illusion ist --- ein Phasenrauschen in Kauf genommen, besser: bewusst genutzt. Dabei sind die aufeinander folgenden Sekunden zwar durchaus verschieden lang, diese Abweichungen schwanken aber um einen Mittelwert, so dass sich letztendlich eine recht genaue Zeit ergibt. Diese direkte digitale Synthese (kurz DDS) ist ein Verfahren in der digitalen Signalverarbeitung zur Erzeugung periodischer, bandbegrenzter Signale mit praktisch beliebig feiner Frequenzauflösung.}, geteilt durch den prescaler=1024. Bei jedem Nulldurchgang des Timer2 wird dessen Interrupt tim2ov ausgelöst, und der Prozessor geht in die tim2ISR. Zunächst wird darin nur das Nötigste auf den Returnstack gerettet, um sofort einen Phasen--Akkumulator weiter zu zählen. Nur bei dessen Nulldurchgang wird die Sekunde eins höher gesetzt, sonst verlassen wir die ISR sofort wieder. So erhalten wir einen recht genauen Sekundentakt aus einer maximal schnellen ISR und ohne den Inner--Interpreter (next) zu bremsen. Erst wenn die Sekunde inkrementiert wird, geht die ISR durch eine Prozedur, bei der die benötigten Zeiger\footnotemark des Forthsystems auf den Returnstack gerettet werden. Und springt dann nach dem oben vorgestellten Prinzip das \texttt{MAIN}--Wort an, welches High--Level--Forth ausführt. Von da kommt der Prozess zurück in die ISR. So kann man mit dem Forthsystem drum herum fast ungestört weiter arbeiten, um z.B. Temperaturwerte auszulesen, oder ein Log auszulesen. Es kann auch Neues compiliert werden, während der Schrittmacher im Hintergrund abläuft. So ist es sehr komfortabel möglich, direkt im embedded controller zu entwickeln, derweil die Uhr bereits läuft und Anwendungen ausführt. Und um diese wunderbare interaktive Funktionalität zu haben, braucht man gar nichts zu tun! Denn sie ist in Forth ja schon eingebaut. \section{Anmerkung} Die Quelle \texttt{tim2ISR.asm} ist in AVR--Assembler verfasst, damit die Uhr gleich mit dem amforth geladen wird. Im Kern des amforth ist dazu eine Ergänzung nötig --- das deferred word \texttt{MAIN} muss im EEprom angelegt werden. Dafür fügen wir in der Datei \texttt{amforth.asm} Folgendes ein vor dem abschließenden \texttt{edp}: \begin{verbatim} ... ; ederferd main word of application: ; ' is mainword EE_MAINWORD: .dw XT_XLED1 .edp: \end{verbatim} Was dann vom \texttt{MAIN}--Wort ausgeführt wird, kann später bestimmt werden, hier ist bereits \texttt{xled1} zugewiesen worden, was eine LED toggelt. Außerdem wurden sämtliche Interrupt--Routinen des amforth herausgenommen, und für \texttt{KEY} und \texttt{EMIT} die gepollten Fassungen verwendet. So gibt es keine undurchsichtigen Störungen und das Ganze lief bisher stabil. Mit drei solcher atmeag168 im LAN wird eine Solarkollektor--Anlage betrieben. \footnotetext{Hier werden nur die Zeiger gerettet und dann wiederhergestellt, die auch in der ISR benutzt werden. Da die virtuelle Maschine des Forth genau bekannt ist, lassen sich so sehr sparsame und daher schnelle Routinen erstellen. Das ist bei Compilern, deren Bibliotheksaufrufe unbekannte Register und Zeiger benutzen, so nicht möglich.} \end{multicols} \section{Links} \url{http://embedded.com/ Introduction to Reentrancy}\\ \url{http://www.atmel.com/products/AVR/}\\ \url{http://de.wikipedia.org/wiki/Direct_Digital_Synthesis}\\ \url{http://en.wikipedia.org/wiki/Direct_digital_synthesis (phase accumulator)}\\ \url{http://en.wikipedia.org/wiki/Numerically-controlled_oscillator}\\ \section{Listing: tim2iSR.asm} \begin{quote} \listinginput[1]{1}{2010-01/tim2ISR.asm} \end{quote} \end{document}