\documentclass[11pt,a4paper]{article} % 2006-07-22 EW Adventures-2.tex % needs to be first for some reason \usepackage{epsfig} %\psfigurepath{./images} % language support \usepackage[german]{babel} %\usepackage[latin1]{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 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, da"s 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, da"s 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} Eine einfache M"oglichkeit, das Verstreichen der Zeit zu messen, besteht durch die Verwendung von Timern. Man konfiguriert den Timer so, da"s er immer nach einer (oder wenigen) Mikrosekunden 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, da"s eine Mikrosekunde vergangen ist. Jetzt kann man sich im Quelltext von gForth schlau machen und dem r8c--Forth eine andere Interrupt Service Routine unterjubeln, aber das hab ich als Anf"anger nicht gen"ugend durchschaut. Es geht auch ohne Durchblick. Wenn man im Quelltext von r8c--Forth liest (\path{arch/r8c/prim.fs}), dann findet man, da"s es eine Variable \texttt{timer} gibt, die von \texttt{ms-irq} hochgez"ahlt wird. Es gibt also bereits einen \textit{system tick}, 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 \textit{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 hochgez"ahlt, und ein flag gesetzt, welcher ebendies anzeigt. Diese Routine hei"st in dem o.g.\ Buch \texttt{timeup}. 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 --- oder man kehrt in die Schleife nach jeder Aufgabe zur"uck. Das hat den Vorteil, da"s man \texttt{timeup} schneller wieder aufruft, und so hoffentlich nichts verpasst. Der Nachteil, da"s beispielsweise eine Stundenaufgabe erst 3 ticks nach der Stunde aufgerufen wird, scheint dagegen klein. In \textit{Pseudocode} sieht das etwa so aus: \begin{verbatim} while (1) { if (tick over?) { timeup aufrufen } if ( Millisekunden-flag? ) { Millisekunden-Aufgabe aufrufen Millisekunden-flag l"oschen } else if ( Sekunden-flag? ) { Sekunden-Aufgabe aufrufen Sekunden-flag l"oschen } else if ( Minuten-flag? ) ... } \end{verbatim} Es gilt also, die Funktion \texttt{timeup} nachzubilden, und die Schleife zu schreiben. \section{\texttt{timeup}, erster Versuch} Um zu "Uberpr"ufen, ob die Idee "uberhaupt was taugt, schreibe ich eine Schleife, in der immer nach einer Sekunde eine Ausgabe passiert. Das l"asst sich dann einfach mit einer (Stop--)Uhr bewerten. Ein tick ist vergangen, wenn \texttt{timer} seinen Wert ver"andert hat. Also legen wir eine Kopie von \texttt{timer} in \texttt{otimer} an. In einer Schleife, die aus reiner Bequemlichkeit per Tastendruck endet, testen wir, ob sich die Werte von \texttt{timer} und \texttt{otimer} unterscheiden. %(\verb,otimer @ timer @ - 0<>,). Das ist immer der Fall, wenn $1$ Millisekunde, also $1$ tick, verstrichen ist. Diese ticks z"ahlen wir. Nach je $1000$ ticks, also nach $1$ Sekunde, soll eine Ausgabe gemacht werden. \begin{verbatim} Variable otimer Variable msec Variable sec : run timer @ otimer ! BEGIN otimer @ timer @ - 0<> IF \ tickover 1 msec +! msec @ 1000 >= IF 0 msec ! 1 sec +! sec @ timer @ cr . . ENDIF 1 otimer +! ENDIF key? UNTIL ; \end{verbatim} Versuch macht bekanntlich kluch: \begin{verbatim} run 1951 1 2951 2 3951 3 ... 30951 30 31951 31 -32585 32 <== -32585 31951 - . 1000 ok -31585 33 ... -5585 59 -4585 60 -3585 61 -2585 62 -1585 63 -585 64 415 65 <== 415 -585 - . 1000 ok 1415 66 ... \end{verbatim} Das funktioniert jetzt schon recht sch"on, auch an der Stelle, an der \texttt{timer} "uberl"auft. Die Sekunden "uberschreiten die $59$, aber das haben wir nicht anders gewollt \verb,:-), \section{\texttt{timeup}, die Zweite} Eine andere M"oglichkeit, die Zeit zu z"ahlen funktioniert so: zuerst lesen wir \texttt{timer} aus, z"ahlen dann allerdings 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 ist ein bestimmtes Zeitinterval verstrichen. Dann rufen wir \texttt{timeup} auf, um die Z"ahler zu aktualisieren. Anschlie"send wird zu \texttt{newtimer} wieder der feste Wert addiert. Auf diese Art addieren sich die Fehler durch Versp"atungen nicht. Ich glaube, das hab ich auch in (\ref{Koenig}) gelesen. Wir schreiben also erst mal ein abgespeckte Version von \texttt{timeup}. TimerC gibt \texttt{cycles} vor, die in der Variablen \texttt{timer} gespeichert werden. Die niedrigste Stufe von \texttt{timeup} z"ahlt diese \texttt{cycles} zu \texttt{ticks}, vielleicht besser \textit{software ticks} oder \textit{slow ticks} genannt. Im Beispiel ist nach 500 cycles ein slow tick vergangen, nach 2 slow ticks eine Sekunde. Mit diesen beiden Stufen lassen sich auch jobs realisieren, die mehrfach pro Sekunde laufen m"ussen. Das Programm befindet sich in der Datei \path{adv2_1.fs} und ist in den Listings abgedruckt. Zu Beginn der Schleife wird lediglich \texttt{newtimer} initialisiert. Damit ist \texttt{tickover?} sofort erfolgreich und \texttt{timeup} wird sogleich aufgerufen. Der zu einem abgelaufenen slow 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 \texttt{cycles}. \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 gegen die Stopuhr testen. Man beachte auch, da"s die Hauptschleife recht simpel aussieht. Die einzelnen Worte sind auch "uberschaubar. \section{Uhr und Kalender} \texttt{timeup} wird zwar l"anglich, wenn man Stunde, Tag, Monat und Jahr noch 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. F"ur die Monate habe ich eine Tabelle \texttt{MaxDay} als \textit{byte array} angelegt (im RAM!). Der gesuchte Monat wird um $1$ vermindert und dient dann als Index in dieses array. Fehlt nur die korrekte Behandlung des Februars in Schaltjahren. Wenn man die "ubliche Aufz"ahlweise f"ur Schaltjahre umkehrt, wird eine einfache Vorschrift daraus: Wenn ein Jahr durch 400 teilbar ist, ist es ein Schaltjahr. Wenn es nicht durch 400 aber durch 100 teilbar ist, dann ist es kein Schaltjahr. Wenn es nicht durch 400 und 100 aber durch 4 teilbar ist, dann ist es ein Schaltjahr. Alle "ubrigen Jahre sind keine Schaltjahre. \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 : leap_year ( year -- f ) dup 400 mod 0= IF \ if year%400 == 0 true \ -> leap year ELSE \ else dup 100 mod 0= IF \ if year%100 == 0 false \ -> no leap year ELSE \ else dup 4 mod 0= IF \ if year%4 == 0 true \ -> leap year ELSE \ else false \ -> no leap year ENDIF ENDIF ENDIF swap drop \ remove year from stack ; : 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 (siehe \path{adv2_timeup.fs}) lassen sich testen: \begin{verbatim} 2000 leap_year . -1 ok 2001 leap_year . 0 ok 1996 leap_year . -1 ok 1997 leap_year . 0 ok 1900 leap_year . 0 ok 2004 leap_year . -1 ok 2006 2 length_of_month . 28 ok 2006 12 length_of_month . 31 ok 2004 2 length_of_month . 29 ok 2004 3 length_of_month . 31 ok 2004 4 length_of_month . 30 ok 2004 12 length_of_month . 31 ok 2000 2 length_of_month . 29 ok 1999 2 length_of_month . 28 ok 1900 2 length_of_month . 28 ok \end{verbatim} Bei \texttt{timeup} mu"s man noch darauf achten, da"s 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! 1 monthflag ! \ month over 1 month +! ENDIF month @ 12 > IF 1 month ! \ offset 1! 1 yearflag ! \ year over 1 year +! ENDIF ; \end{verbatim} Der komplette Programmtext findet sich in \path{adv2_2.fs} und \path{adv2_timeup.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{Uhrzeit auf's LCDisplay} Sch"oner w"ar's nat"urlich, wenn die Uhrzeit auf dem Display erscheint. 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 sind in \path{adv2_lcd.fs}: \begin{verbatim} : p4! s>d <# # # # # #> ; : p2! s>d <# # # #> ; \ write string to lcd position col (0..15) \ row (0..1) with next lcdtype : lcdpos ( row col -- ) swap $40 * + $80 + lcdctrl! &1 ms ; : show.DT ( -- ) year @ p4! lcdtype month @ p2! lcdtype day @ p2! lcdtype s" -" lcdtype hour @ p2! lcdtype min @ p2! lcdtype sec @ p2! lcdtype ; \end{verbatim} F"ur die Anzeige habe ich entschieden, \texttt{show.DT} jede Minute aufzurufen, und jede Sekunde lediglich die Sekundenanzeige zu aktualisieren: \begin{verbatim} : job.sec ( -- ) 0 13 lcdpos sec @ p2! lcdtype ; : job.min ( -- ) cr ." running minute job ..." lcdpage show.DT ; \end{verbatim} Au"serdem wird \texttt{show.DT} vor der Schleife einmal aufgerufen. Der vollst"andige Programmtext findet sich in \path{adv2_3.fs}. Der entscheidende Nachteil ist jetzt nur noch der, da"s beim Einschalten des Kontrollers die Zeit erstmal die Zeit von Hand gestellt werden mu"s. Das ist l"astig. Also habe ich die i2c--Bus Funktionen vom letzten Mal hergenommen und eine batteriegepufferte PCF8583 Uhr an den i2c--Bus angeschlossen. \section{i2c--Uhr} Die Uhr anzusprechen, ist keine gro"se Kunst mehr, hier etwa um die Zeit zu lesen. Der einzige Trick hier ist der, da"s 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 sicher verbesserungsf"ahig, aber f"ur diesen Zweck ausreichend. Der vollst"andige Programmtext findet sich in \path{adv2_4.fs} und den darin eingeschlossenen Dateien. \begin{verbatim} include adv2_4.fs ok 2006 7 26 22 31 0 0 set.rtc ok run year month day hour min sec timer... 2006 7 26 22 31 4 22801 460 2006 7 26 22 31 5 23801 1000 2006 7 26 22 31 6 24801 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. \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 WikiSeite 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. \begin{verbatim} : run task ... BEGIN tickover? IF timeup ENDIF tickflag @ IF job.tick 0 tickflag ! ELSE ... ENDIF pause AGAIN ; \end{verbatim} Wenn man jetzt noch daf"ur sorgen will, da"s die Show sofort nach dem Einschalten losgeht, dann helfen die folgenden Zeilen: \begin{verbatim} include adv2_5.fs rom ' run IS bootmessge ram savesystem \end{verbatim} Das Wort \texttt{'} (\textit{sprich tick}) produziert die Adresse des ausf"uhrbaren Teils vom folgenden Wort (\texttt{run}), und \texttt{is} jubelt \texttt{bootmessage} ebendiesen Code unter (der Code, der vorher in \texttt{bootmessage} steht, ist dann allerdings verloren). \texttt{bootmessage} selbst wird beim Initialisieren des Forthsystems aufgerufen, und damit \texttt{run}, und deswegen geht's sofort nach dem Einschalten los. \texttt{savesystem} sorgt daf"ur, da"s die im RAM angelegten Variablen ins ROM "ubertragen werden, sonst gehen sie verloren. 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 kompletten Displays jede Minute und 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 Phantasie l"a"st sich der Kontroller nun f"ur zum Schalten t"aglicher Vorg"ange (Roll"aden, Licht, Kaffeemaschine) oder sensorgesteuerter Dinge (Gew"achshausl"uftung, Dachfenster) gebrauchen. Der Haussteuerung in Forth steht nichts mehr im Weg! \section{Ausblick} Der damit erreichte Stand der Dinge ist nicht schlecht. Dennoch verbleiben eine Menge Dinge f"ur einen weiteren Artikel --- falls ich die Leser nicht schon "uber Geb"uhr strapaziert habe. Ein Blick auf den belegten und freien Speicher d"urfte sich lohnen. Wieviele Variablen kann ich zur Me"swerterfassung noch benutzen? Und was passiert mit Variablen, die mit \texttt{savesystem} gerettet werden? Liegen diese nach einem Neustart im RAM oder im ROM? Falls im ROM, ist das eine schlaue Idee (begrenzte Anzahl von Schreibzyklen), oder sollte man solche Variablen besser beim Initialisieren der Schleife neu Anlegen? Wie kann man im vorhandenen Code noch deutlich Platz sparen oder die Teilung in Module verbessern? Wo und wie kann man Forth--spezifische Programmiertechniken verwenden? Messwerte wollen verschickt werden, am besten "uber die andere serielle Schnittstelle (uart0). Wie genau stellt man das an? Und als praktische Anwendung m"ochte ich einen Sensor aufbauen, der die Sonnenscheindauer erfasst --- bitte ohne irgendwelche mechanisch bewegten Teile. Und der aufmerksame Leser hat l"angst gemerkt, da"s \texttt{job.year} den im EEPROM der i2c--Uhr gespeicherten Wert f"ur das Jahr auch aktualisieren sollte. \textit{Kontakt? \url{ew.forth@online.de} oder so?} \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{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} \listinginput[1]{1}{2006-04/adv2_timeup.fs} \listinginput[1]{1}{2006-04/adv2_2.fs} \listinginput[1]{1}{2006-04/adv2_3.fs} \listinginput[1]{1}{2006-04/adv2_lcd.fs} \listinginput[1]{1}{2006-04/adv2_4.fs} \listinginput[1]{1}{2006-04/adv2_5.fs} \listinginput[1]{1}{2006-04/adv2_tasker.fs} \listinginput[1]{1}{2006-04/adv2_i2c.fs} \listinginput[1]{1}{2006-04/adv2_i2c_rtc.fs} \listinginput[1]{1}{2006-04/adv2_i2c_lm75.fs} \end{multicols} \end{small} \end{quote} \end{document}