\documentclass[11pt,a4paper]{article} % 2006-07-22 EW Adventures-2.tex % 2006-08-14 EW Adventures-2a.tex % % Aufgebessert nachdem ich 'ne Reihe Optimierungen gemacht hatte % Und dann nochmal komplett umgeräumt! % % language support \usepackage[german]{babel} %\usepackage[utf8]{inputenc} % can use Umlauts now "u instead of "u %\usepackage{lmodern} % better fonts %\usepackage{pslatex} % use native PostScript fonts \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 %\newcommand{\Forth}{\textsc{forth}\xspace} \begin{document} \title{Adventures in Forth 2} \author{Erich W"alde} \maketitle \begin{multicols}{2} Im ersten Teil (\ref{Adv1}) wurde gezeigt, wie man dem Renesas--R8C13--Kontroller beibringt, mit einem i2c--Bus zu reden. Inzwischen hat Bernd Paysan das r8c--Forth und das zugeh"orige \texttt{terminal.fs} so aufgebohrt, dass man in einem Forth--Programm die Anweisung \texttt{include} verwenden kann. Daher kann man ein l"angeres Programm in mehrere Dateien zerlegen. Man kann die Dateien auch so schreiben, dass sie von mehreren Projekten benutzt werden k"onnen, eine angenehme Erleichterung. Ich m"ochte mit dem R8C--Kontroller eine Me"sstation aufbauen, die permanent laufen soll. Um damit zeitliche Abl"aufe zu organisieren, w"are eine Art Uhr nicht schlecht. Dann kann ich einfacher sagen, \textit{alle 10 Minuten sollen die Messwerte verschickt werden}. \section{Eine Forth Uhr} Um das Verstreichen der Zeit zu messen, bedient man sich am besten der Timer, die der Kontroller mitbringt. Man konfiguriert einen Timer so, dass er immer nach einer (oder wenigen) Millisekunden einen Interrupt ausl"ost. Die dazugeh"orige Interrupt--Service--Routine z"ahlt einen Z"ahler hoch und setzt einen \textit{flag}, der dem "ubrigen Programm anzeigt, dass eine Millisekunde vergangen ist. Jetzt kann man sich im Quelltext von gforth--r8c schlau machen, wie man einem timer eine Interrupt--Service--Routine unterjubelt. Allerdings habe ich das als Anf"anger nicht gen"ugend durchschaut. Es geht auch ohne Durchblick. Bei ebendieser Suche (\path{arch/r8c/prim.fs}) findet sich eine Variable \texttt{timer}, die vom Wort \texttt{ms-irq} hochgez"ahlt wird. Es gibt also bereits eine Art Uhren-Tick (TimerC--Zyklus), den ich f"ur die Uhr verwenden kann. In einem Buch "uber PIC--Mikrokontroller (\ref{Koenig}) findet sich eine geeignete Methode, um eine Zeitz"ahlung zu realisieren. Das Programm l"auft durch eine Schleife, in der immer wieder "uberpr"uft wird, ob ein Tick abgelaufen ist. Wenn ja, dann werden die Z"ahler, die die Zeit verwalten, hochgez"ahlt (Ticks oder Millisekunden, Sekunden, Minuten, Stunden \ldots). Wenn eine ganze Sekunde verstrichen ist, dann wird der Sekundenz"ahler ebenfalls erh"oht, und ein flag gesetzt, welcher ebendies anzeigt. Diese Routine hei"st in dem o.g.\ Buch \texttt{timeup}\footnote{Ich hab's grad wieder nachgelesen: TIMUP hei"st sie dort.}. Wenn \texttt{timeup} zur"uckkehrt, sind alle Z"ahler aktualisiert, und die flags gesetzt. Im darauf folgenden Teil der Schleife werden die flags "uberpr"uft, die dazugeh"origen Aufgaben (\textit{jobs)} aufgerufen, und danach die flags gel"oscht. Wenn mehrere flags gesetzt sind, kann man entweder alle zugeh"origen Aufgaben ausf"uhren und erst danach die Schleife von vorne beginnen (\textit{serielle Schleife}\footnote{Aus der Struktur des zugeh"origen Ablaufdiagramms.}) --- oder man kehrt in die Schleife nach jeder Aufgabe zur"uck (\textit{parallele} Form). Das kann von Vorteil sein, wenn man eine Aufgabe hat, die alle $n$ Millisekunden (Tick) auszuf"uhren ist --- z.B. die Bedienung einer LED--Ziffernanzeige. Dann kann man zwischen den einzelnen Jobs immer auf den n"achsten Tick warten und diese spezielle Aufgabe einschieben. Der Nachteil, dass beispielsweise eine Stundenaufgabe erst 3 Ticks nach der vollen Stunde aufgerufen wird, ist dagegen klein. Der \textit{Pseudocode} f"ur die serielle Schleife sieht dann etwa so aus: \begin{verbatim} while (true) { if (tickover?) { timeup aufrufen } if ( Tick-flag? ) { Tick-Aufgabe aufrufen Tick-flag loeschen } if ( Sekunden-flag? ) { Sekunden-Aufgabe aufrufen Sekunden-flag loeschen } if ( Minuten-flag? ) ... } \end{verbatim} Es gilt also, die Funktion \texttt{timeup} nachzubilden und die Schleife zu schreiben. \section{Zeit messen: \texttt{timeup}} Das folgende Verfahren hat sich f"ur das Innenleben von \texttt{timeup} bew"ahrt: zuerst lesen wir \texttt{timer} aus, z"ahlen einen bestimmten Wert (z.B.\ 500) dazu und speichern das Ergebnis in \texttt{newtimer}. Wenn \texttt{timer} den Wert von \texttt{newtimer} erreicht oder "uberschritten hat, dann sind $500$\,ms verstrichen. Dann rufen wir \texttt{timeup} auf, um die Z"ahler zu aktualisieren. Anschlie"send wird zu \texttt{newtimer} wieder $500$ addiert. Sollte \texttt{timeup} etwas zu sp"at aufgerufen werden, macht das nichts, weil sich diese Fehler nicht addieren. Wir z"ahlen immer die gleiche Zahl zu \texttt{newtimer}. Anders w"are es, wenn wir den Z"ahler f"ur den Timer l"oschen und den Timer dann neu starten. Wir schreiben also erst mal eine abgespeckte Version von \texttt{timeup}. TimerC gibt Zyklen vor, die in der Variablen \texttt{timer} gespeichert werden. Im Beispiel ist nach $500$ Zyklen ein Tick vergangen, nach $2$ Ticks eine Sekunde. \texttt{timeup} wird aufgerufen, wenn ein Tick verstrichen ist. Die niedrigste Stufe von \texttt{timeup} z"ahlt diese Ticks zu Sekunden. Mit Zyklen und Ticks lassen sich auch jobs realisieren, die mehrfach pro Sekunde laufen m"ussen. \begin{verbatim} Variable newtimer Variable lastsec Variable tickflag Variable tick Variable secflag Variable sec Variable minflag Variable min 500 Constant cycles.tick \ timerC Zykl./Tick 2 Constant ticks.sec \ Ticks/Sekunde : tickover? ( -- ) newtimer @ timer @ - 0< ; : timeup ( -- ) cycles.tick newtimer +! 1 tickflag ! \ tick over! 1 tick +! tick @ ticks.sec >= IF 0 tick ! 1 secflag ! \ sec over! 1 sec +! ENDIF sec @ 60 >= IF 0 sec ! 1 minflag ! \ min over! 1 min +! ENDIF min @ 60 >= IF 0 min ! ENDIF ; \end{verbatim} \section{Was schaffen: \texttt{run}} Die Hauptschleife ist auch keine Zauberei: \begin{verbatim} : run init-loop BEGIN tickover? IF timeup ENDIF tickflag @ IF job.tick 0 tickflag ! ENDIF secflag @ IF job.sec 0 secflag ! ENDIF minflag @ IF job.min 0 minflag ! ENDIF key? UNTIL ; \end{verbatim} Zu Beginn der Schleife wird lediglich \texttt{newtimer} initialisiert. Bleiben nur die Jobs, damit auch was passiert: \begin{verbatim} : led0 ( -- ) 3 port1 bclr ; : led1 ( -- ) 3 port1 bset ; : job.tick tick @ $01 and IF led0 ELSE led1 ENDIF ; : job.sec timer @ dup lastsec @ - swap dup lastsec ! sec @ min @ cr . . . . ; : job.min cr ." running minute job ..." ; : init-loop ( -- ) cr ." min sec timer timer-lastsec" timer @ cycles.tick + newtimer ! ; \end{verbatim} Der zu einem abgelaufenen Tick geh"orige \texttt{job.tick} l"asst hier die LED an Pin P1.3 im Sekundentakt blinken. Der Sekundenjob gibt die aktuellen Z"ahler aus, sowie den Abstand seit dem letzten Sekundenjob in TimerC--Zyklen. \begin{verbatim} include adv2_1.fs ok 58 sec ! ok run min sec timer timer-lastsec 0 59 -26991 1000 1 0 -25991 1000 running minute job ... 1 1 -24991 1000 ... \end{verbatim} Die Z"ahler werden hochgez"ahlt, ganz so, wie man sich das vorstellt. Diese Schleife l"asst sich schon mal mit der Stopuhr testen. Man beachte auch, dass die Hauptschleife recht simpel aussieht. Die einzelnen Worte sind auch "uberschaubar. \section{Optimierungen 1} Ein komplettes Wort, also 16 Bit, zu belegen, wo auch ein einziges Bit ausreicht, das ist Verschwendung. Unsere flags finden in einer Variablen bequem Platz. Daf"ur m"ussen wir uns merken, welches flag in welcher Bitposition wohnt: \begin{verbatim} Variable Flags 2 Flags bset \ Setze Flag[Minute] 1 Flags bclr \ Loesche Flag[Sekunde] 0 Flags btst IF \ Flag[Tick] gesetzt? timeup ENDIF \end{verbatim} Gespart werden hier 6 Worte im RAM: die Variablen \texttt{tickflag secflag .. yearflag} werden alle in \texttt{Flags} untergebracht. \section{Uhr und Kalender} \texttt{timeup} wird zwar l"anglich, wenn man die Behandlung von Stunden, Tagen, Monaten und Jahren dazupackt, aber nicht komplexer. \texttt{timeup} erh"alt im n"achsten Schritt die restlichen Z"ahler. Sofort braucht man ein Wort, welches die L"ange eines bestimmten Monats ausgibt, inklusive Behandlung der Schaltjahre. Ein Schaltjahr ist ein Jahr, welches durch $4$ aber nicht durch $100$ teilbar ist, oder welches durch $400$ teilbar ist. In Stephen Pelcs Buch \textit{Programming Forth} (\ref{SPelc}) findet sich diese Anweisung in sehr sch"oner, kompakter Form: \begin{verbatim} : leap_year ( year -- t/f ) dup 4 mod 0= over 100 mod 0<> and swap 400 mod 0= or ; \end{verbatim} F"ur die Monate habe ich eine Tabelle \texttt{MaxDay} als \textit{byte array} angelegt, weil alle Werte unter $255$ liegen. Der gesuchte Monat wird um $1$ vermindert und dient dann als Index in dieses array. Dazu kommt die korrekte Behandlung des Februars in Schaltjahren. \begin{verbatim} ram create MaxDay 31 c, 28 c, 31 c, 30 c, 31 c, 30 c, 31 c, 31 c, 30 c, 31 c, 30 c, 31 c, rom : length_of_month ( year month -- maxday ) dup 1- \ array starts at 0 MaxDay + c@ \ get length swap 2 = IF \ if month == 2 swap leap_year IF \ and leap_year 1+ \ length += 1 ENDIF ELSE swap drop \ remove year ENDIF ; \end{verbatim} Diese Worte lassen sich testen --- wie immer in Forth direkt von der Eingabezeile: \begin{verbatim} 1900 leap_year . 0 ok 2000 leap_year . -1 ok 2001 leap_year . 0 ok 2004 leap_year . -1 ok 2004 2 length_of_month . 29 ok 2006 2 length_of_month . 28 ok 2006 3 length_of_month . 31 ok 2006 4 length_of_month . 30 ok ... \end{verbatim} Bei \texttt{timeup} muss man noch darauf achten, dass die Z"ahler f"ur Sekunden, Minuten, Stunden gegen den ersten ung"ultigen Wert (60, 60, 24) mit \verb,>=, getestet werden. Bei der L"ange des Monats produziert \texttt{length\_of\_month} aber den h"ochsten g"ultigen Wert, also ist der Vergleich ein \verb,>, und der Startwert $1$ und nicht $0$. Analog f"ur den \texttt{month} Z"ahler: \begin{verbatim} : timeup ( -- ) ... day @ year @ month @ length_of_month > IF 1 day ! \ offset 1! 5 Flags bset \ month over 1 month +! ENDIF month @ 12 > IF 1 month ! \ offset 1! 6 Flags bset \ year over 1 year +! ENDIF ; \end{verbatim} Der komplette Programmtext findet sich in \path{adv2_2.fs} und produziert etwa folgende Ausgabe: \begin{verbatim} include adv2_2.fs ok 23 hour ! 59 min ! 57 sec ! 0 tick ! ok 2006 year ! 12 month ! 31 day ! ok run year month day hour min sec timer... 2006 12 31 23 59 57 7812 7812 2006 12 31 23 59 58 8813 1001 2006 12 31 23 59 59 9813 1000 2007 1 1 0 0 0 10813 1000 running minute job ... running hour job ... running day job ... running month job ... running year job ... 2007 1 1 0 0 1 11813 1000 2007 1 1 0 0 2 12813 1000 2007 1 1 0 0 3 13813 1000 ... \end{verbatim} \section{Optimierungen 2} Der Block in \texttt{run}, welcher die Flags pr"uft und die dazugeh"origen Jobs aufruft, ist ein Kandidat f"ur eine Schleife. \begin{verbatim} : run ... BEGIN tickover? IF timeup ENDIF 0 Flags btst IF job.tick 0 Flags bclr ELSE 1 Flags btst IF job.sec 1 Flags bclr ELSE 2 Flags btst IF job.min 2 Flags bclr ELSE 3 Flags btst IF job.hour 3 Flags bclr ELSE 4 Flags btst IF job.day 4 Flags bclr ELSE 5 Flags btst IF job.month 5 Flags bclr ELSE 6 Flags btst IF job.year 6 Flags bclr ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF key? UNTIL ; \end{verbatim} F"ur eine Schleife ben"otigt man lediglich eine M"oglichkeit, die Jobs "uber den Schleifenindex aufzurufen. Dazu erzeugt man eine Tabelle \texttt{Jobs}, in der die Einsprungadressen der einzelnen \texttt{job}--Worte aufgehoben werden. Das Wort \verb,', produziert diese Adresse f"ur das nachfolgende Wort: \begin{verbatim} ' job.tick . 8842 ok ' job.sec . 8880 ok ' job.min . 8958 ok \end{verbatim} liegt eine solche Adresse auf dem Stack, dann kann diese Adresse mit dem Wort \texttt{execute} ausgef"uhrt werden: \begin{verbatim} job.sec 2007 1 1 0 0 4 1581 -2641 ok ' job.sec execute 2007 1 1 0 0 4 10299 8718 ok \end{verbatim} Die \texttt{Jobs} Liste und die Schleife sehen dann so aus: \begin{verbatim} ram create Jobs ' job.tick , ' job.sec , ' job.min , ' job.hour , ' job.day , ' job.month , ' job.year , rom : run init-loop BEGIN tickover? IF timeup ENDIF 7 0 DO I Flags btst IF I cells Jobs + @ execute I Flags bclr ENDIF LOOP key? UNTIL ; \end{verbatim} Dieser Programmtext ist etwas k"urzer als der urspr"ungliche Block. Wenn man wissen will, wie gro"s ein Programm ist, dann macht man mit \texttt{dump} einen Speicherabzug. Das Programm wohnt im Datenflash, welcher bei der Adresse \texttt{\$2000} beginnt und $4$\,kByte (\texttt{\$1000}) gro"s ist. Der freie Speicher enth"alt lediglich \texttt{FF} Werte. Im folgenden Beispiel sind die Zeilen f"ur den Druck umgebrochen: % adv2_2.fs: 1318 Byte % adv2_3.fs: 1208 Byte! \begin{verbatim} empty ok include adv2_2.fs ok $2000 $1000 dump 2000: D4 FD E5 46 6C 61 67 73 - 68 C0 FF FF 6A 04 00 20 ...Flagsh...j.. 2010: E8 6E 65 77 74 69 6D 65 - 72 20 68 C0 FF FF 6C 04 .newtimer h...l. ... 2510: 5C C1 1E 25 D8 23 24 C5 - 06 00 08 20 FC C9 36 DC \..%.#$.... ..6. 2520: 5C C1 7C 24 12 C1 FF FF - FF FF FF FF FF FF FF FF \.|$............ $2526 $2000 - . 1318 ok \end{verbatim} \path{adv2_2.fs} belegt also $1318$\,Byte. Das Programm mit der ge"anderten Hauptschleife (\path{adv2_3.fs}) belegt $1208$\,Byte. Damit hat man immerhin $110$\,Byte oder $8$\,\% eingespart. Ich finde, das ist eine ganze Menge, auch wenn das durch $14$ Byte mehr belegtes RAM bezahlt wird. \section{Uhrzeit auf's LCDisplay} Sch"on w"ar's jetzt, wenn die Uhrzeit auf dem Display erscheint und nicht (nur) im Terminal--Fenster. Dazu verwenden wir ein paar einfache Worte, die die Angaben formatieren und anzeigen. Das genaue Format ist nat"urlich Geschmacksache. Die daf"ur notwendigen Worte haben wir uns aus dem ersten Artikel ausgeliehen und stehen in den Listings (\path{adv2_lcd.fs}) \texttt{show.DT} schreibt Datum und Uhrzeit in meinem bevorzugten Format auf das LCDisplay. Ich habe entschieden, \texttt{show.DT} nur jede Minute aufzurufen, und jede Sekunde lediglich die Sekundenanzeige zu aktualisieren: \begin{verbatim} : job.sec ( -- ) ... 0 13 lcdpos sec @ p2! lcdtype ; : job.min ( -- ) ... 0 0 lcdpos show.DT ; \end{verbatim} Au"serdem wird \texttt{show.DT} vor der Schleife einmal aufgerufen. Die Anzeige funktioniert mit diesen Erg"anzungen wunderbar. Allerdings muss nach dem Einschalten des Kontrollers die Zeit erst gestellt werden. Das ist nichts f"ur faule Kerle wie meinereiner. Also habe ich die i2c--Bus Funktionen vom letzten Mal hergenommen und eine batteriegepufferte PCF8583 Uhr an den i2c--Bus angeschlossen. \section{Wider das Vergessen: i2c--Uhr} Die Uhr anzusprechen, ist keine gro"se Kunst mehr, hier etwa um die Zeit zu lesen. Der einzige Trick hier ist der, dass die Uhr selbst keinen Jahresz"ahler hat. Lediglich 4 Jahre werden gez"ahlt, um die (meisten) Schaltjahre zu behandeln. Daher habe ich an Adresse 0x10 im EEPROM der Uhr einen 2--Byte--Z"ahler (nicht BCD, low Byte zuerst) f"ur das ganze Jahr verwendet. \begin{verbatim} : get.rtc $01 \ start address to rtc 1 i2c_addr_rtc NB>i2c \ send start address 6 i2c_addr_rtc NBi2c \ start address to rtc 2 i2c_addr_rtc NBdec ( n.bcd -- n.dec ) $ff and dup 4 rshift 10 * \ extract high nibble as 10s swap $0f and \ extract low nibble as 1s + \ add ; : dec>bcd ( n.dec -- n.bcd ) &100 mod \ 99 is largest number for &10 /mod \ 8 bit bcd 4 lshift + $ff and \ truncate to 8 bit ; \end{verbatim} Meine L"osungen sind ganz sicher verbesserungsf"ahig, aber f"ur diesen Zweck ausreichend. \begin{verbatim} include adv2_4.fs ok 2006 8 15 18 35 55 0 set.rtc ok run year month day hour min sec timertimer-lastsec 2006 8 15 18 35 56 25877 -25261 2006 8 15 18 35 57 26353 476 2006 8 15 18 35 58 27353 1000 2006 8 15 18 35 59 28353 1000 2006 8 15 18 36 0 29353 1000 running minute job ... 2006 8 15 18 36 1 30353 1000 2006 8 15 18 36 2 31353 1000 ... \end{verbatim} Die Uhrzeit wird zun"achst an die i2c--Uhr "ubertragen, die letzte Zahl vor \texttt{set.rtc} sind die Hundertstelsekunden. So ausger"ustet "uberlebt die Uhr jetzt auch das Ausschalten des Kontrollerboards. Ein $0.47$\,F--GoldCap Kondensator kann meine Uhr l"anger als 2 Wochen betreiben. Als Zugabe enth"alt das Programm noch das LM75--Thermometer von der ersten Folge. Der vollst"andige Programmtext findet sich in \path{adv2_4.fs} und den darin eingeschlossenen Dateien. \section{Optimierungen 3} In der Funktion, "ah, im Wort \texttt{timeup} sind f"ur jeden Z"ahler die gleichen Dinge zu erledigen: Z"ahler erh"ohen, falls Grenzwert erreicht: Z"ahler zur"ucksetzen und den n"achsth"oheren Z"ahler erh"ohen, sowie den flag f"ur den n"achsth"oheren Z"ahler. Um diese Funktion vollst"andig in eine Schleife umzuwandeln, sind allerhand Vorarbeiten n"otig. Die Z"ahler--Variablen \texttt{tick} bis \texttt{year} werden in einen Datenblock mit dem Namen \texttt{Counts} gesteckt. Aus \verb,year @, wird dann \verb,6 cells Counts + @, und analog f"ur alle "ubrigen Z"ahler. Ebenso werden die Grenzwerte in ein Bytearray \texttt{Limits} verwandelt. Das klappt zun"achst f"ur alle au"ser dem Monat. Aus \verb,60 >= IF, wird dann eben \verb,2 Limits + c@ >= IF,. Um den Block f"ur den Monat ebenfalls in die Schleife aufzunehmen, habe ich entschieden, dass ich daf"ur sorge, dass im Feld $4$ von \texttt{Limits} immer die L"ange des aktuellen Monats steht. Also muss vor Beginn der Schleife, wenn die Uhrzeit aus der i2c--Uhr gelesen wurde, die L"ange des aktuellen Monats nach \texttt{Limits} kopiert werden: \begin{verbatim} year @ month c@ length_of_month 4 Limits + c! \end{verbatim} Au"serdem muss \texttt{job.month} den Eintrag f"ur den neuen Monat aktualisieren. Damit ist unter normalen Umst"anden die korrekte Behandlung des Monats gesichert. Verbleibt noch der \textit{Offset 1} Fehler in Tag und Monat. F"ur beide Z"ahler habe ich den Startwert auf $0$ gesetzt. Damit hat etwa der Januar Tage von $0$ bis $30$. Die $31$ in \texttt{Limits} ist jetzt der erste ung"ultige Wert und der Vergleich im entsprechenden Block von \texttt{timeup} wird von \verb,>, zu \verb,>=,, wie bei den anderen Blocks. An allen Stellen, an denen Tag und Monat benutzt werden, muss jetzt $1$ dazugez"ahlt oder abgezogen werden. Das ist un"ubersichtlich, aber es funktioniert. Die "Anderungen betreffen auch das Lesen der i2c--Uhr und die Anzeige auf dem LCDisplay. Nach diesen Ver"anderungen sehen jetzt alle Blocks gleich aus, bis auf den Index. Jetzt kann man \texttt{timeup} tats"achlich enorm reduzieren: \begin{verbatim} : timeup ( -- ) cycles.tick newtimer +! 0 Flags bset \ tickflag++ 1 Counts +! \ tick++ \ for i in tick sec min hour day month 6 0 DO I cells Counts + @ I Limits + c@ >= IF \ if C[i] >= L[i] 0 I cells Counts + ! \ C[i]=0; I 1+ Flags bset \ F[i+1]++; 1 I 1+ cells Counts + +! \ C[i+1]++; ENDIF \ endif LOOP ; \end{verbatim} Das Programm \path{adv2_4.fs} belegt $2708$ Bytes, das neue Programm \path{adv2_5.fs} $2686$ Bytes. Wenn man die Worte \texttt{tick} bis \texttt{year} konsequent durch \verb,n cells Counts +, ersetzt, dann spart man erstaunlicherweise nochmal $2686-2652=34$ Bytes. Danach ist das Programm aber deutlich unleserlich geworden. Fazit: Das Programm wird nicht deutlich kleiner, wenn man \texttt{timeup} in eine Schleife umwandelt, weil die n"otige Infrastruktur die Ersparnis auffrisst. \section{Uhr als Task} Jetzt bleibt nur noch, die Uhr als Hintergrunds--\textit{Task} laufen zu lassen, so wie das in den Beispielen auf der Wiki--Seite schon geschieht. Dazu habe ich bei Rafael Deliano (\ref{Deliano}) eine Erkl"arung gefunden, wie ein \textit{PAUSE--Tasker} generell funktioniert. An der Stelle, an der ein Task unterbrechbar ist, ruft er die Funktion \texttt{pause} auf. Dann l"auft der n"achste Task. Der hier verwendete Tasker kann genau zwei Tasks verwalten. In den Beispielen ist der zweite Task eine Variante von \texttt{key}, der unsere Eingaben am Terminal bedient. Man kann also mit dem System reden, obwohl die Uhr l"auft. Die Umwandlung von \texttt{run} in ein Task ist fast trivial: \texttt{run} wird als \texttt{task} gekennzeichnet, die \texttt{BEGIN ... UNTIL} Schleife wird in eine \texttt{BEGIN ... AGAIN} Schleife umgewandelt, und vor dem Ende der Schleife wird \texttt{pause} aufgerufen. Dort darf \texttt{run} unterbrochen werden. Ausgehend von \path{adv2_4.fs} muss man nur \texttt{run} ersetzen und den tasker bekannt machen: \begin{verbatim} include adv2_tasker.fs ... : run task init-loop BEGIN tickover? IF timeup ENDIF 7 0 DO I Flags btst IF I cells Jobs + @ execute I Flags bclr ENDIF LOOP pause AGAIN ; \end{verbatim} Wenn man jetzt noch daf"ur sorgen will, dass die Show sofort nach dem Einschalten losgeht, dann helfen die folgenden Zeilen: \begin{verbatim} include adv2_6.fs rom ' run IS bootmessge ram savesystem \end{verbatim} % include adv2_6.fs -> 2850 Byte % nach savesystem -> 3094 Byte + 244 Byte Die Adresse des ausf"uhrbaren Teils von \texttt{run} wird \texttt{bootmessage} untergeschoben (\texttt{is}). \texttt{savesystem} rettet alle Variableninhalte aus dem RAM ins ROM und hinterlegt weiteren Code, der beim Start die Inhalte wieder ins RAM transferiert, bevor das Anwenderprogramm startet. Sehr feine Sache das. Nachdem \path{adv2_6.fs} geladen und gesichert wurde, belegt es $3094$\,Byte. Allerdings kann man die Ausgaben am Terminal noch sparen, wenn die Uhr alleine laufen soll. Fast "uberfl"ussig zu erw"ahnen \ldots mit den \texttt{job}-Worten kann ich jetzt sehr bequem regelm"a"sige Aufgaben verteilen. Die Anzeige der Sekunde jede Sekunde, das L"oschen und Wiederbeschreiben des LCDisplays jede Minute, das Ein-/Ausschalten der LED und die Anzeige der Temperatur alle halbe Sekunde (slow tick) sind die derzeit eingebauten regelm"a"sigen Aufgaben. Mit etwas Fantasie l"asst sich der Kontroller nun zum Schalten zeitlich bestimmter Vorg"ange (Roll"aden, Licht, Kaffeemaschine) oder sensorgesteuerter Dinge (Gew"achshausl"uftung, Dachfenster) gebrauchen. Der Haussteuerung in Forth steht nichts mehr im Weg au"ser dem nun doch etwas begrenzten Speicher von ROM \ldots \section{Ausblick} Der damit erreichte Stand der Dinge ist ganz ordentlich. Dennoch verbleiben eine Menge Dinge f"ur einen weiteren Artikel --- falls ich die Leser nicht schon "uber Geb"uhr strapaziert habe. Der aufmerksame Leser hat l"angst erkannt, dass \texttt{job.year} die Kopie von \texttt{year} im RAM der i2c--Uhr aktualisieren sollte. Die M"oglichkeiten und Grenzen des gew"ahlten Verfahrens sollten ausgelotet werden. Messwerte wollen verschickt werden, am besten "uber die andere serielle Schnittstelle (uart0). Und wie kann man nun dem Forth--System eine neue Interrupt Service Routine unterjubeln, etwa um die Flanken eines DCF77--Empf"angers (Zeitsignal) zu detektieren und bedienen? Unbelegte Pins f"ur Experimente sind noch gen"ugend vorhanden. Als praktische Anwendung m"ochte ich einen Sensor aufbauen, der die Sonnenscheindauer erfasst --- bitte ohne irgendwelche mechanisch bewegten Teile. Das Auslesen von 4 Fotodioden an einem AD--Wandler "uber den i2c--Bus funktioniert schon. Messungen am Sonnenlicht und das Entwickeln der Datenauswertung fehlen noch. Erg"anzungen, Kommentare, Korrekturen sind ausdr"ucklich erw"unscht. Sie erreichen mich unter \url{ew.forth@nassur.net} \section{Referenzen} \begin{enumerate} \item \label{Adv1} E. W"alde, Adventures in Forth, Die 4. Dimension 3/2006, Jahrgang 22 \item \label{Koenig} A.\ K"onig und M.\ K"onig, Das PICmicro Profi Buch, Franzis Verlag 1999, ISBN 3-7723-4284-1 \item \label{SPelc} Stephen Pelc, Programming Forth, \\ \url{http://www.mpeforth.com/arena/ProgramForth.pdf} \item \label{Deliano} \url{http://www.embeddedforth.de/emb3.pdf} S.\ 9 \end{enumerate} \end{multicols} \section{Listings} \begin{quote} \begin{small} \begin{multicols}{2} \listinginput[1]{1}{2006-04/adv2_1.fs} % abgespecktes timeup + Schleife \listinginput[1]{1}{2006-04/adv2_2.fs} % Jobs + Schleife \listinginput[1]{1}{2006-04/adv2_timeup.fs} % . vollständiges timeup \listinginput[1]{1}{2006-04/adv2_3.fs} % Jobs + vereinfachte Schleife \listinginput[1]{1}{2006-04/adv2_4.fs} % Uhr mit LCDisplay und i2c-rtc \listinginput[1]{1}{2006-04/adv2_lcd.fs} % . Formatierung fürs LCD \listinginput[1]{1}{2006-04/adv2_i2c.fs} % . i2c Bus \listinginput[1]{1}{2006-04/adv2_i2c_rtc.fs} % . i2c-rtc Uhr \listinginput[1]{1}{2006-04/adv2_i2c_lm75.fs} % . i2c-lm75 Thermometer \listinginput[1]{1}{2006-04/adv2_5.fs} % wie 4, aber mit timeup als Schleife %\listinginput[1]{1}{2006-04/adv2_6.fs} % wie 4, aber run als task \listinginput[1]{1}{2006-04/adv2_tasker.fs} % . tasker \end{multicols} \end{small} \end{quote} \end{document}