\documentclass[11pt,a4paper]{article}

% 2006-07-07 EW Adventures-1.tex
% 2006-07-22 EW Adventures-2.tex
% 2006-08-14 EW Adventures-2a.tex
% 2007-01-10 EW Adventures-3.tex
% 2007-01-13 EW Adventures-4.tex
% 2008-11-14 EW Adventures-5.tex
% 2011-01-07 EW Adventures-6.tex: rs485+mpc
%
% Aufgebessert nachdem ich 'ne Reihe Optimierungen gemacht hatte
% Und dann nochmal komplett umgeräumt!
%

% language support
\usepackage[german]{babel}
%\usepackage{german}
%\usepackage[latin1]{inputenc} % can use Umlauts now "u instead of "u
%\usepackage{lmodern} % better fonts
%\usepackage{pslatex} % use native PostScript fonts
\usepackage{cm-super}

\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{\amforth}{\texttt{amforth}}
\newcommand{\zB}{z.\,B.\ }
\begin{document}

%\title{Adventures in Forth 6: rs485 half duplex, mpc und andere, seltsame Tiere}
\title{amforth: rs485 half duplex, mpc und andere, seltsame Tiere}
\ifx\shorttitle\undefined\else
\shorttitle{Adventures in Forth 6: amforth}
\fi
\author{Erich W"alde}

\maketitle

%\begin{multicols}{2}

% --------------------------------------------------------------------
%\section{Intro}
%\label{sec:intro}

\begin{abstract}
  Auf dem Linuxtag 2010 habe ich (am Stand der Forth--Gesellschaft) ein paar
  Platinen mit atmega--32--Kontrollern gezeigt, die zusammen diverse Daten
  erfassten: Temperatur, Luftfeuchte, Luftdruck und den Stromverbrauch am
  Stand. Jeder Kontroller fragt selbstst"andig \zB alle 10 Sekunden die
  Werte der angeschlossenen Sensoren ab. Die Werte werden in physikalische
  Einheiten umgerechnet und dann so lange gesammelt (genauer aufaddiert),
  bis \textit{jemand} die Daten abholt. Dann wird der Mittelwert gebildet,
  und zusammen mit Minimum, Maximum und der Anzahl der Messwerte
  ausgegeben. "Ahnlich wird mit den aufgesammelten Z"ahlwerten des
  Stromverbrauchs verfahren.

  Das wirft nat"urlich Fragen auf: Wer ist dieser \textit{jemand}, der
  \textit{Einsammler}, wie kommuniziert der mit dem Kontroller? H"angen
  alle Kontroller an einem Kabel? Und wenn ja, wie geschieht es, dass nicht
  alle Kontroller wild durcheinander quasseln? Dieser Artikel zeigt, wie
  man Ordnung auf das Verbindungskabel bringt.

  Mein Dank gilt Matthias Trute, Lubo\v{s} P\v{e}kn\'{y}, Bernd Paysan,
  Michael Kalus, Martin Bitter und sicher noch anderen f"ur ihre Hilfe.
\end{abstract}

\begin{multicols}{2}

% --------------------------------------------------------------------
\section{Assembler oder Forth?}
\label{sec:asm-o-forth}

%% Welche W"orter m"ussen ge"andert werden??
%% \begin{itemize}
%% \item asm: rs485\_rw\_port, pin (constants)
%% \item asm: tx (rw pin bedienen)
%% \item asm: tx-complete-isr
%% \item +emit, -emit (' drop|rs485.tx is emit)
%% \item mpc (value StationID, variable mpc\_ID)
%% \item +mpc7, -mpc7
%% \item asm: rx (bin ich in mpc? bin ich gemeint?)
%% \item \~{}end, \~{}id, \~{}call
%% \item asm: stationID in prompt
%% \item init-turnkey (rw pin output, low; -emit; stationID mpc\_ID ! +mpc7)
%% \item run-turnkey (init-turnkey, run starttasker)
%% \end{itemize}
%% }


\textit{Afterwards everything is \textbf{obvious}.} Auf die Frage von Bernd, warum
denn das alles in Assembler und nicht in Forth geschrieben sei, wusste ich
zun"achst keine Antwort. Es gibt folgende Stellen, die zu \amforth{}
geh"oren, in Assembler geschrieben sind, und die sich nicht so ganz einfach
verbiegen lassen:
\begin{itemize}
\item \verb+tx+ muss den Lesen/Schreiben--Pin bedienen, w"ahrend es den
  Ausgabepuffer abarbeitet.
\item Die Adresse dieses Pins wollte ich konfigurierbar haben. Au"serdem
  wird diese Information in den Assembler--Worten \verb+tx+ und
  \verb+txc-isr+ benutzt.
\item (\verb+txc-isr+) am Ende einer "Ubertragung, die asynchron
  abgearbeitet wird, muss dieser Pin wieder auf Lesen zur"uckgesetzt
  werden. Der usart--transmit--complete interrupt bietet sich daf"ur an. Das
  Bit zu l"oschen, war in Assembler viel zu einfach.
\item in \verb+rx+ wird die Stationadresse ben"otigt, wenn sich der
  Kontroller im mpc--Modus befindet. Die muss also unverlierbar und von
  Assembler aus zug"anglich sein.
\item Die Prompts sind nicht \textit{deferred}, d.h.\ auch an dieser Stelle
  ist es einfacher, den Assembler--Code zu "andern.
\end{itemize}

Das Initialisieren des Lesen/Schreiben--Pins, das gute Benehmen am Bus
(schweigsam soll der Kontroller sein und nur nach Aufforderung sprechen)
und weitere Worte w"aren direkt in Forth schneller machbar. Teilweise habe
ich sogar \verb+g4+ von Michael Kalus benutzt, um Assembler zu erzeugen ---
teilweise dann aber mit manueller Nacharbeit verziert.

Ich wollte ein System, welches sich am Bus sofort korrekt benimmt. Es kann
auch "uber den Bus in Forth programmiert werden. Das funktioniert
allerdings nur, wenn die Bedienung des Lesen/Schreiben--Pins schon direkt nach dem
Flashen l"uckenlos klappt. Und ich habe ganz nebenbei eine Menge "uber das
Innenleben von \amforth{} gelernt.

% --------------------------------------------------------------------
\section{RS485: ein Bus f"ur viele Zwecke}
\label{sec:RS485}

Der Einsammler ist ein (perl--)Programm auf einem (Linux--)Rechner, welches
"uber einen Bus, den RS485--Bus [\ref{RS485}], mit den Kontrollern redet.
Das Signal wird differentiell "uber ein Adernpaar "ubertragen. Um ein Bit
zu "ubertragen, wird die Spannungsdifferenz zwischen den Adern A und B
entweder positiv (0) oder negativ (1).
Die Anordnung der Bits und der zeitliche Ablauf sind gleich, wie bei der
normalen seriellen Verbindung (RS232). Will man gleichzeitig Daten lesen
und schreiben, dann braucht man zwei Adernpaare, entsprechende Bausteine
zur Anschaltung und man redet von einer \textit{full duplex} Konfiguration.

Es geht aber auch mit einem Adernpaar. Dann kann ein Busteilnehmer zu einer
bestimmten Zeit nur lesen oder schreiben, nicht beides. Diese Konfiguration
hei"st \textit{half duplex} und ben"otigt eine Steuerung, die "uber Lesen
oder Schreiben entscheidet. Viel mehr ist f"ur RS485 nicht definiert.
Insbesondere ist kein Protokoll definiert, welches zum Einsatz kommen muss.
Der Bus kann betr"achtliche L"angen "uberbr"ucken, bis mindestens 1200\,m
bei 100 kBit/s. Man kann damit also bequem, falls vorhanden, die Hacienda
verkabeln und muss nicht t"aglich zum Briefkasten reiten.


Um die Anbindung in \amforth\ zu machen, sind folgende Aufgaben zu l"osen:

\begin{itemize}
\item Die Unterst"utzung f"ur RS485 soll beim Assemblieren wahlweise
  eingebunden werden k"onnen (WANT\_RS485)
\item Der RS485 W/R Pin soll konfiguierbar sein (\zB PortD.7)
\item applturnkey muss den W/R Pin initialisieren (output, low)
\item \texttt{tx} muss den Pin vor dem Senden auf \textit{Schreiben} setzen
\item Nach der "Ubertragung muss der Pin wieder auf \textit{Lesen} gesetzt
  werden (Usart Transmit Complete Interrupt)
\end{itemize}



% --------------------------------------------------------------------
\section{RS485: Ansteuerung einbauen}

\subsection{main.asm: WANT\_RS485}

Um alle Quelltextstellen, die der Unterst"utzung des RS485 half duplex Modus
dienen, selektiv ein-- oder auszuschalten, wird in der Datei \path{main.asm}
eine Assembler--Variable definiert:
{\small
\begin{verbatim}
.set WANT_RS485 = 1 ; RS485 half duplex
\end{verbatim}
}

Im Assembler--Quelltext finden sich dann Bl"ocke der Bauart:

{\small
\begin{verbatim}
.if  WANT_RS485 == 1
  ...
.else
  ...
.endif
\end{verbatim}
}


\subsection{main.asm: den W/R Pin konfigurieren}

Es war mir wichtig, dass der Pin, welcher den Lesen/Schreiben--Zustand am
Transceiver einstellt, beim Erstellen von \amforth\ konfiguriert werden
kann. Zwei weitere Assembler--Variablen werden daf"ur ben"otigt:

{\small
\begin{verbatim}
; file: main.asm
...
; rs485_rw_pin: D.7==W/R, low==read
.if  WANT_RS485 == 1
  ; PORTx: use io location, not mem mapped
  .set RS485_RW_PORT = PORTD
  .set RS485_RW_PIN  = 7
.endif
\end{verbatim}
}

\texttt{PORTD} wird in den Tiefen der von device.asm eingeschlossenen
Dateien auf den Wert \texttt{\$12} gesetzt. Um diese Definitionen aus
\amforth\ heraus benutzen zu k"onnen, wurden sie entsprechend in
\texttt{constants} verfrachtet:

{\small
\begin{verbatim}
; file: words/rs485.asm
; asm(RS485_RW_PORT) 
; PORTD constant rs485_rw_port

VE_RS485_RW_PORT:
    .dw $FF0D
    .db "rs485_rw_port",0
    .dw VE_HEAD
    .set VE_HEAD = VE_RS485_RW_PORT
XT_RS485_RW_PORT:
    .dw PFA_DOVARIABLE
PFA_RS485_RW_PORT:
    ; use mem mapped location!
    .dw (RS485_RW_PORT+$20)

; asm(RS485_RW_PIN) 
; $07 constant rs485_rw_pin

VE_RS485_RW_PIN:
    .dw $FF0C
    .db "rs485_rw_pin"
    .dw VE_HEAD
    .set VE_HEAD = VE_RS485_RW_PIN
XT_RS485_RW_PIN:
    .dw PFA_DOVARIABLE
PFA_RS485_RW_PIN:
    .dw RS485_RW_PIN
\end{verbatim}
}

Das dient nur der Bequemlichkeit.

\subsection{applturnkey.asm: den W/R Pin initialisieren}

Der RS485 W/R Pin soll beim Starten von \amforth\ als Ausgang geschaltet
werden und auf null gestellt (nicht auf eins). Dazu wird ein entsprechendes
Wort in Assembler definiert:

{\small
\begin{verbatim}
; file: words/rs485-init.asm

; : rs485.init ( -- )
;   rs485_rw_port rs485_rw_pin portpin: rs485_rw
;   rs485_rw low
;   rs485_rw pin_output
; ;

VE_RS485_INIT:
    .dw $ff0a
    .db "rs485.init"
    .dw VE_HEAD
    .set VE_HEAD = VE_RS485_INIT
XT_RS485_INIT:
    .dw PFA_RS485_INIT
PFA_RS485_INIT:
    ; set DDR bit (pin_output)
    sbi  (RS485_RW_PORT-1),RS485_RW_PIN
    ; clear PORT bit (receive)
    cbi  RS485_RW_PORT,RS485_RW_PIN
    rjmp DO_NEXT
\end{verbatim}
}

Das Wort \texttt{rs485.init} wird in \texttt{applturnkey} aufgerufen:

{\small
\begin{verbatim}
; file: words/applturnkey.asm
...
.if WANT_RS485 == 1
    .dw XT_RS485_INIT           ; rs485.init
.endif
\end{verbatim}
}

\subsection{usart-isr-tx.asm: Schreibpin bedienen}

Bis jetzt sind das alles Vorarbeiten. Der W/R Pin muss in der Funktion
\texttt{tx} bedient werden. \texttt{tx} versendet ein Byte "uber die USART,
die serielle Schnittstelle. Vorher muss der W/R Pin auf Schreiben (1)
gesetzt werden:

Ich habe auf meinen Kontrollern das Schreiben auf die serielle
Schnittstelle mittels Interrupt--Service--Routine eingeschaltet
(\verb@.set WANT_ISR_TX = 1@), so dass die hier gezeigten "Anderungen eben
in der Interrupt--Service--Routine gemacht werden. Das ist reine
Geschmacksache.

{\small
\begin{verbatim}
; file: drivers/usart-isr-tx.asm
...
usart_udre_isr:
...
usart_udre_next:
.if WANT_RS485 == 1
  sbi RS485_RW_PORT,RS485_RW_PIN
.endif
  inc xh
  andi xh,usart_tx_mask
  sts usart_tx_out,xh

  ldi zl,low(usart_tx_data)
  ldi zh,high(usart_tx_data)
  add zl,xh
  adc zh,zeroh

usart_udre_send:
  ld xl,z
  sts USART_DATA,xl
...
\end{verbatim}
}


Die Funktion \texttt{usart\_udre\_isr} schreibt das n"achste zu versendende
Byte in das Datenregister der seriellen Schnittstelle, r"aumt auf und kehrt
dann zur"uck --- \textbf{ohne} das Verschicken des Bytes
\textbf{abzuwarten}. Die Frage ist also, wer wann den W/R Pin zur"uck auf
Lesen setzt.

Gl"ucklicherweise gibt es daf"ur einen Interrupt, den \textit{usart
  transfer complete interrupt}. Den aktivieren wir und bringen der
zugeh"origen Servicefunktion bei, den W/R Pin auf null zu setzen:

{\small
\begin{verbatim}
; file: drivers/usart-isr-tx.asm
...
.if WANT_RS485 == 1
.set pc_ = pc
.org UTXCaddr
  jmp_ usart_utx_isr
.org pc_

; : txc-isr ( -- )
;   rs485_rw low
; ;
; $1E constant USART_TXCAddr 
; ' txc-isr USART_TXCAddr int!

; serial transfer complete interrupt
; clear rs485_rw_pin
usart_utx_isr:
  cbi RS485_RW_PORT,RS485_RW_PIN
  reti
.endif
\end{verbatim}
}

Um den Interrupt zu aktivieren, setzen wir in der Datei \path{main.asm} ein
zus"atzliches Bit
{\small
\begin{verbatim}
 .set USART_B_VALUE = ... | (1<<TXCIE0)
\end{verbatim}
}
selbstverst"andlich eingeklammert in die entsprechenden \texttt{.if ...
  .endif} Zeilen.


Mit diesen "Anderungen im Assembler--Quelltext sollte der Kontroller einen
RS485--Bus einwandfrei ansprechen.

% --------------------------------------------------------------------
\section{Anschlussstelle}

Will man mit einem RS485--Bus--Teilnehmer vom Rechner aus reden, so braucht
es einen geeigneten Konverter, der auf dem Rechner eine serielle
Schnittstelle zur Verf"ugung stellt und sich auf dem Bus mit anst"andigen
Pegeln meldet. Solche Konverter gibt es k"auflich zu erwerben oder auch
selbst zu l"oten.

\textit{Referenz? Schaltung?}

Dann ist man mit seinem bevorzugten Terminal--Programm schon ausreichend
ausgestattet, um mit einem Bus--Teilnehmer zu reden.

% --------------------------------------------------------------------
\section{Funkstille 1}

F"ur einen Kontroller an einem Bus mit mehreren Teilnehmern geziemt es sich
nicht, spontan und lautstark die Welt mit\\
\verb|amforth 4.2 ATmega32  ok|\\
\verb|> |\\
zu bgr"u"sen.

Mein erster Versuch, \texttt{ver} aus \texttt{applturnkey} zu eliminieren,
war nat"urlich zu kurz gedacht: der Prompt kommt immer noch. Schade. Nun
w"ar's denkbar, die Prompt--Worte entsprechend umzubiegen. Stellt sich aber
raus, dass die nicht \textit{deferred} sind. Die drei Prompt--Worte zu
vektorisieren ist nat"urlich m"oglich. Allerdings muss man sich dann die
Adressen der urspr"unglichen Worte merken, das im \texttt{user}--Bereich
ablegen und immer sch"on buchf"uhren. Die "Anderungen betreffen mehrere
Stellen im \amforth\ Quelltext. Es geht aber auch einfacher.

Der Prompt muss ja schlie"slich auch ausgegeben werden. Das bewerkstelligt
am Ende die Funktion \texttt{emit}. Die ist schon \textit{deferred}
definiert und kann einfach umgebogen werden:

{\small
\begin{verbatim}
: -emit   ['] drop is emit ;
: +emit   ['] tx is emit ;
\end{verbatim}
}

Das ist etwas simplistisch gedacht, denn \texttt{emit} k"onnte ja auch auf
etwas anderes als \texttt{tx} zeigen. Will man diesen Fall korrekt
behandeln, dann etwa so:

{\small
\begin{verbatim}
variable tmpemit
: -emit ['] emit defer@ tmpemit !
        ['] drop is emit ;
: +emit tmpemit @ is emit ;
\end{verbatim}
}


Ich habe mich hier f"ur den simplen Ansatz entschieden. Um \texttt{-emit}
schon in \texttt{applturnkey} zur Verf"ugung zu haben, habe ich die beiden
Worte mit Hilfe von Michael Kalus' Prorgamm \texttt{g4.fs} in Assembler
umgewandelt und etwas von Hand editiert. Die Datei wird dann via
\texttt{dict\_appl.inc} geladen.

{\small
\begin{verbatim}
; words/emit-on-off.asm

; : +emit   ['] tx is emit ;

VE_EMIT_ON:
    .dw $FF05
    .db "+emit",0
    .dw VE_HEAD
    .set VE_HEAD = VE_EMIT_ON
XT_EMIT_ON:
    .dw DO_COLON
PFA_EMIT_ON:
    .dw XT_DOLITERAL
    .dw XT_TX
    .dw XT_DOLITERAL
    .dw XT_EMIT
    .dw XT_DEFERSTORE
    .dw XT_EXIT

; : -emit   ['] drop is emit ;

VE_EMIT_OFF:
    .dw $FF05
    .db "-emit",0
    .dw VE_HEAD
    .set VE_HEAD = VE_EMIT_OFF
XT_EMIT_OFF:
    .dw DO_COLON
PFA_EMIT_OFF:
    .dw XT_DOLITERAL
    .dw XT_DROP
    .dw XT_DOLITERAL
    .dw XT_EMIT
    .dw XT_DEFERSTORE
    .dw XT_EXIT
\end{verbatim}
}


Dann l"asst sich \texttt{applturnkey} entsprechend "andern:

{\small
\begin{verbatim}
; words/applturnkey.asm
; ( -- ) System
; R( -- )
; application specific turnkey action
VE_APPLTURNKEY:
    .dw $ff0b
    .db "applturnkey",0
    .dw VE_HEAD
    .set VE_HEAD = VE_APPLTURNKEY
XT_APPLTURNKEY:
    .dw DO_COLON
PFA_APPLTURNKEY:
    .dw XT_INITUSER     ; init-user
    .dw XT_USART        ; +usart
    .dw XT_INTON        ; +int
.if WANT_RS485 == 1
    .dw XT_RS485_INIT   ; rs485.init
    .dw XT_EMIT_OFF     ; -emit
.endif
    .dw XT_VER          ; ver
    .dw XT_EXIT

; : init-turnkey
;   init-user
;   +usart +int
;   rs485.init -emit
; ;

\end{verbatim}
}

Jetzt ist Funkstille, wenn man den Kontroller einschaltet. Allerdings muss
man sich daran auch gew"ohnen, und erst mal blind \texttt{+emit} eingeben,
bevor man beschlie"st, dass mehr Funkstille herrscht als gew"unscht.

% --------------------------------------------------------------------
\section{Funkstille 2: mpc}


Verbindet man den Rechner mit zwei oder mehr Kontrollern an einem
RS485--Bus, dann m"ochte man auch gerne mit denen reden. Aber bitte einzeln
und nicht mit allen gleichzeitig. Der bisherige Stand erzeugt hemmungslose
echo--Schleifen, die nicht selten darin enden, dass mindestens einer der
Kontroller richtig beleidigt ist und auch nach einem Reset gar nicht mehr
reden will.

Die Atmel--Kontroller sind f"ur diesen Fall vorbereitet. Es gibt eine
Einrichtung mit dem Namen \textit{multi processor communication (mpc)}.
Schaltet man mpc ein, dann werden alle vom USART empfangenen Bytes
verworfen und keine Interrupts ausgel"ost, bis ein Byte empfangen wird, bei
dem das h"ochste Bit gesetzt ist. Dieses Byte wird als Adressbyte
behandelt. Die Interrupt--Service--Routine inspiziert dieses Byte und
vergleicht es mit der gespeicherten Stationsadresse. Sind sie gleich, wird
der mpc--Modus beendet und die Schnittstelle funktioniert ganz normal.

An einem Bus mit mehreren Kontrollern (Stationen) werten alle Kontroller
das Adressbyte aus. Aber nur der angesprochene beendet den mpc--Modus. Am
Ende der Kommunikation muss er den mpc--Modus wieder aktivieren. Solange man
nur ASCII Zeichen "ubertr"agt und keine bin"aren Daten, muss man keine
weiteren Vorkehrungen treffen. Alle Messwerte, die die Stationen
aufgesammelt haben, werden ganz normal als ASCII--Zeichenketten ausgegeben.
Diese Zeichen haben das h"ochste Bit nicht gesetzt und animieren daher auch
die Stationen nicht, dieses als Busadresse zu behandeln. Mit Umlauten oder
bin"aren Daten geht das so nicht. Au"serdem hat das den Vorteil, dass man
auf dem Bus einfach so mitlesen kann.

Um den mpc--Modus zu nutzen, sind folgende Aufgaben zu l"osen:

\begin{itemize}
\item Die Unterst"utzung f"ur den mpc--Modus soll beim Assemblieren
  wahlweise eingebunden werden k"onnen (WANT\_MPC)
\item Die Stationsadresse muss gesetzt werden (mpc\_ID)
\item mpc--Modus einschalten (\texttt{+mpc}: \textit{7 Datenbits, kein Parit"atsbit, 2 Stopbits})
\item mpc--Modus beenden (\texttt{-mpc}: \textit{8 Datenbits, kein Parit"atsbit, 1 Stopbit})
\item Die Assemblerfunktion \texttt{usart\_rx\_isr} muss pr"ufen, ob der mpc--Modus
  an ist, und wenn ja, ob diese Station gemeint ist.
\item Die Stationsadresse muss persistent gespeichert und beim Starten von
  \amforth\ wieder gesetzt werden
  % \$FE value stationID, \$60 to stationID, stationID mpc\_ID !
\item Im \texttt{amforth}--Programm braucht es ein paar Worte zur
  Bedienung, z.B \verb|~id|,  \verb|~call|, \verb|~end|
\end{itemize}

% --------------------------------------------------------------------
\section{mpc--Modus einbauen}

\subsection{main.asm: WANT\_MPC}

Wie vorher wird eine Assembler--Variable definiert, die zum Aktivieren des
entsprechenden Quelltexts benutzt wird:

{\small
\begin{verbatim}
; file: main.asm
.set WANT_MPC = 1 ; mpc mode (7N2, 8N1)
\end{verbatim}
}


\subsection{Stationsadresse in RAM}

Die Stationsadresse wird zum Vergleich mit einer empfangenen Adresse
gebraucht. Sie soll gleichzeitig als normale \amforth--Variable benutzbar
sein. Daf"ur wird die Adresse der Speicherstelle gleichzeitig in der
Assembler--Variablen \texttt{var\_mpcid} abgelegt.

{\small
\begin{verbatim}
; file: drivers/usart-isr-rx.asm
...
.if WANT_MPC == 1
; ( -- addr ) variable mpc_ID, RS485
; module address
VE_MPCID:
    .dw $ff06
    .db "mpc_ID"
    .dw VE_HEAD
    .set VE_HEAD = VE_MPCID
XT_MPCID:
    .dw PFA_DOVARIABLE
PFA_MPCID:
    .dw here
    .set var_mpcid = here
    .set here = here + CELLSIZE
.endif
\end{verbatim}

}

\subsection{mpc--Modus einschalten}

Um den mpc--Modus einzuschalten, muss das Bit \texttt{MPCM} im Register
\texttt{UCSRA} gesetzt, sowie die serielle Schnittstelle auf 7 Datenbits,
kein Parit"atsbit, 2 Stopbits konfiguriert werden (Register
\texttt{UCSRC}). Dabei ist zu beachten, dass beim Schreiben von
\texttt{UCSRC} das h"ochste Bit \texttt{URSEL} gesetzt werden muss, weil
sonst das Register \texttt{UBRRH} beschrieben wird. An dieser Stelle habe
ich l"anglich gegr"ubelt, was dem aufmerksamen Leser erspart werden kann.

{\small
\begin{verbatim}
; file: ewlib/mpc.fs
: +mpc7 (  --  )
    txc            \ wait for tx complete
    UCSRA c@ $01 or UCSRA c!   \ set MPCM
    $8C UCSRC c!               \ 7N2
;
\end{verbatim}
}


\subsection{mpc--Modus beenden}

Entsprechend wird zum Beenden des mpc--Modus das Bit \texttt{MPCM} gel"oscht
und die serielle Schnittstelle auf 8 Datenbits, kein Parit"atsbit, 1
Stopbit gesetzt.

{\small
\begin{verbatim}
; file: ewlib/mpc.fs
: -mpc7 (  --  )
    UCSRA c@ $FE and UCSRA c! \ clear MPCM
    $86 UCSRC c!              \ 8N1
;
\end{verbatim}
}


\subsection{Empfangenes Adressbyte auswerten}

Zun"achst wird \texttt{variable mpc\_ID} in Assembler definiert.

{\small
\begin{verbatim}
: file: drivers/usart-isr-rx.asm
...
.if WANT_MPC == 1
; ( -- addr ) variable mpc_ID, RS485
; module address
VE_MPCID:
    .dw $ff06
    .db "mpc_ID"
    .dw VE_HEAD
    .set VE_HEAD = VE_MPCID
XT_MPCID:
    .dw PFA_DOVARIABLE
PFA_MPCID:
    .dw here
    .set var_mpcid = here
    .set here = here + CELLSIZE
.endif
\end{verbatim}
}


Dann wird die Interrupt--Service--Routine f"ur den Empfang von Daten auf
der seriellen Schnittstelle aufgebessert.

{\small
\begin{verbatim}
; file: drivers/usart-isr-rx.asm
...
. if WANT_MPC == 0           ; original version
...
.else                        ; MPC version
usart_rx_isr:
  push xl                    ; save registers
  in xl, SREG
  push xl
  push xh
  push zl
  push zh
  push temp0

  in_ xh, UCSRA
  mov temp0, xh              ; temp0 = UCSRA

  andi xh, (1<<FE) | (1<<DOR) | (1<<PE)
                             ; set zero flag
  lds xh, USART_DATA         ; xh = UDR
  brne usart_rx_isr_finish   ; test Z flag,
                             ; finish on error
  lds xl,usart_rx_in         ; xl = i_in
  ldi zl, low(usart_rx_data) ; Z = &buf[0]
  ldi zh, high(usart_rx_data);
  add zl, xl                 ; Z += i_in
  adc zh, zeroh              ; .

  sbrc temp0, MPCM           ; if MPCM == 0
  rjmp usart_rx_isr_mpcmode  ; {
                             ; NORMAL mode
  st Z, xh                   ; . buf[i_in]=xh

  inc xl                     ; . xl += 1
  andi xl,usart_rx_mask      ; . xl %= siz
  sts usart_rx_in, xl        ; . i_in = xl

  rjmp usart_rx_isr_finish   ; } else {

usart_rx_isr_mpcmode:        ; MPC mode

  ldi zl, low(var_mpcid)     ; . Z=&var_mpcid
  ldi zh, high(var_mpcid)    ; . .
  ld  xl, Z                  ; . xl=var_mpcid
  cp  xl, xh                 ; . xl == xh?
  brne usart_rx_isr_finish   ; . if ( yes )
  andi temp0, (~(1<<MPCM))   ; . { called!
  out_ UCSRA, temp0          ; . . clear MPCM
  ldi  temp0, (USART_C_VALUE); . .
  out_ UCSRC, temp0          ; . . 8N1

usart_rx_isr_finish:         ; } _finish

  pop temp0                  ; restore
  pop zh                     ; registers
  pop zl
  pop xh
  pop xl
  out SREG, xl
  pop xl

  reti
.endif
\end{verbatim}
}


\subsection{Stationsadresse in EEPROM}

Die Stationsadresse soll persistent aufbewahrt werden und einen
Stromausfall "uberstehen. Der \amforth--Datentyp \texttt{value} stellt eine
Konstante zur Verf"ugung, deren Inhalt nicht im Flash--Speicher, sondern im
EEPROM aufbewahrt wird. Jetzt soll aber gleichzeitig dieser Wert schon beim
Assemblieren von \amforth\ angelegt werden, wenngleich mit einer
default--Adresse (\$7F). Das hat sich als etwas umst"andlich erwiesen. Ich
musste diesen Eintrag tats"achlich in der Datei \path{amforth.asm}
vornehmen, also quasi tief in den Eingeweiden. Vielleicht findet sich ein
besserer Weg, das zu tun. Daf"ur m"usste wahrscheinlich die Sprungadresse
\texttt{edp:} in eine Assembler--Variable (\texttt{.set edp = ...})
umgewandelt werden.

{\small
\begin{verbatim}
; file: amforth.asm
...
.if WANT_MPC
EE_STATIONID:
       .dw $007F
.endif
; 1st free address in EEPROM.
edp:
.cseg
\end{verbatim}
}

{\small
\begin{verbatim}
; file: words/mpc.asm
VE_STATIONID:
       .dw $ff09
       .db "stationID",0
       .dw VE_HEAD
       .set VE_HEAD = VE_STATIONID
XT_STATIONID:
       .dw PFA_DOVALUE
PFA_STATIONID:
       .dw EE_STATIONID
\end{verbatim}
}


Die Stationsadresse ist jetzt als \texttt{value} verf"ugbar. In der Datei,
die das \amforth--Programm f"ur eine bestimmte Station enth"alt, steht
daher beispielsweise \verb@$60 to stationID@. %$
Damit wird die default--Adresse "uberschrieben. Wenn das geschehen ist,
dann wird bei nachfolgenden Starts diese Stations--Adresse in die Variable
\texttt{mpc\_ID} "ubertragen:

{\small
\begin{verbatim}
; file: words/applturnkey.asm
...
.if WANT_MPC
    .dw XT_STATIONID
    .dw XT_MPCID
    .dw XT_STORE
.endif
\end{verbatim}
}


\subsection{Verwendung}

F"ur die interaktive Benutzung des mpc--Modus definiert man sich noch ein
paar Worte:
\begin{itemize}
\item \verb|~id| legt die Stationsadresse auf den Stack
\item \verb|~end| beendet die Kommunikation und schaltet den mpc--Modus
  wieder ein
\item \verb|~call| sendet das Adressbyte einer anderen Station mit
  zus"atzlich gesetztem h"ochstwertigem Bit.
\end{itemize}

{\small
\begin{verbatim}
include ewlib/mpc.fs
: ~end  -emit +mpc7 ;
: ~id   mpc_ID c@ ;
: ~call mpc_call ;
\end{verbatim}
}


{\small
\begin{verbatim}
; file: ewlib/mpc.fs
\ send address for other station
: mpc_call ( c -- ) \ ID
  dup mpc_ID @ = if
    drop
  else
    $0 tx            \ delay
    $80 or tx        \ set 7.bit+ID, for slave
    -emit            \ drop any output!
    +mpc7            \ modul off, wait for ID
  then
;
\end{verbatim}
}


Einer kleinen Unterhaltung mit zwei Stationen steht also jetzt nichts mehr
im Weg:

{\small
\begin{verbatim}
> ver
amforth 4.2 ATmega32 ok
> ~id hex .
60  ok
> $61 ~call
unsichtbar: +emit
 ok
> ~id hex .
61  ok
>
\end{verbatim}
}
%$

Bei meinem ersten Versuch mit RS485 und mpc hatte ich das Versenden des
Prompts nicht unterbunden. Ich hatte nur festgestellt, dass auf dem Bus
nach einem \verb|~end| ein paar krause Zeichen erscheinen (nicht druckbar).
Inzwischen wei"s ich, dass das die Zeichen des ok-- und des
ready--Prompts sind, wenn man sie durch \textit{7 Datenbits, kein
  Parit"atsbit, 2 Stopbits} verst"ummelt, d.\,h., das h"ochste Bit setzt.
Das hat im Dateneinsammler zu Mehraufwand gef"uhrt, um ggf.\ unbrauchbare
Zeichen zu lesen und zu filtern. Seit \texttt{-emit} ist Ruhe.

Die Stationsadresse h"alt sieben signifikante Bits, man kann also Adressen
von Null bis 127 (\$7F) vergeben. %$


% --------------------------------------------------------------------
\section{mpc: prompt}


Nach einer Weile hat es mich gest"ort, dass ich immer erst \verb|~id .|
eingeben muss, um zu sehen, auf welcher Station ich gerade bin. Und da ich
sowieso schon einen Blick in die Prompts geworfen hatte, habe ich
beschlossen, den ready--Prompt zu "andern. Ich wollte die Stationsnummer in
hexadezimal sehen, etwa so: \verb|~61>|. Also erweitert man das Wort
\verb|p_rd| sinngem"a"s um diese Zeilen:

{\small
\begin{verbatim}
...
[char] ~ emit
base @ $10 base !
mpc_ID @ 2 u0.r
base !
...
\end{verbatim}
}

{\small
\begin{verbatim}
; file: words/prompts.asm
...

; : p_rd
;   [char] ~ emit
;   base @ $10 base !
;   mpc_ID @ 2 u0.r
;   base !
;   [char] > emit space
; ;

; send the READY prompt to the command line
;VE_PROMPTRDY:
;    .dw $ff04
;    .db "p_rd"
;    .dw VE_HEAD
;    .set VE_HEAD = VE_PROMPTRDY
XT_PROMPTRDY:
    .dw DO_COLON
PFA_PROMPTRDY:
    .dw XT_CR
.if WANT_MPC == 1
    .dw XT_DOLITERAL
    .dw $7e             ; '~'
    .dw XT_EMIT         ; emit
    .dw XT_BASE         ; base @ $10 base !
    .dw XT_FETCH
    .dw XT_DOLITERAL
    .dw $10
    .dw XT_BASE
    .dw XT_STORE
    .dw XT_MPCID        ; mpc_ID @ 2 u0.r
    .dw XT_FETCH
    .dw XT_DOLITERAL
    .dw $2
    .dw XT_UZERODOTR
    .dw XT_BASE         ; base !
    .dw XT_STORE
.endif
    .dw XT_SLITERAL
    .dw 2
    .db "> "
    .dw XT_ITYPE
    .dw XT_EXIT
\end{verbatim}
}
%$

Die Unterhaltung mit den Stationen wird jetzt \textit{intuitiver}:

{\small
\begin{verbatim}
~60> ver
amforth 4.2 ATmega32 ok
~60> ~id hex .
60  ok
~60> $62 ~call
unsichtbar: +emit
 ok
~62> $61 ~call
unsichtbar: +emit
 ok
~61>
\end{verbatim}
}

So ist das Ganze recht nett geworden, jedenfalls nach meinen Ma"sst"aben
f"ur nett.

% --------------------------------------------------------------------
\section{Ausblick}

Der Artikel ist mehr eine "Ubung in Assembler als in Forth geworden.
Aber ich habe dazugelernt.

Die eigentliche Aufgabe der Stationen (Messdatenerfassung) ist hier nicht
weiter gezeigt. Ich benutze den Multitasker und baue zwei Tasks. Der eine
bedient den prompt an der seriellen Schnittstelle. Der andere z"ahlt die
Zeit, sammelt die Sensordaten ein und bereitet sie auf, bis der
Dateneinsammler sie abholt. Der Dateneinsammler schickt den Befehl
\verb+~data+, und der Kontroller versendet daraufhin seine gesammelten
Erkenntnisse --- alles streng als Text, etwa so:

{\small
\begin{verbatim}
> ~data
__Q 41:0001 1:12,+25.50,+25.54,+25.56 C-- ok
\end{verbatim}
}


% --------------------------------------------------------------------
\section{Referenzen}

\begin{enumerate}
%\item \label{Adv1} E. W"alde, Adventures in Forth,   Die 4. Dimension 3/2006, Jahrgang 22
%      gforth-ec r8c, i2c von Hand, Thermometer
%\item \label{Adv2} E. W"alde, Adventures in Forth 2, Die 4. Dimension 4/2006, Jahrgang 22
%      timeup Uhr und Kalender, twotask
%\item \label{Adv3} E. W"alde, Adventures in Forth 3, Die 4. Dimension 1/2007, Jahrgang 23
%      usart0, redirection
%\item \label{Adv4} E. W"alde, Adventures in Forth 4, Die 4. Dimension 1/2007, Jahrgang 23
%      dcf-77 Uhr
%\item \label{Adv5} E. W"alde, Adventures in Forth 5, Die 4. Dimension 4/2008, Jahrgang 24
%      Umstieg auf amforth, bit flags, sensor:, filter_mean:

%\item \label{r8c-ds} Renesas R8C/13 datasheet auf \url{www.renesas.com}
%\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
%\item \label{dcf} \url{http://de.wikipedia.org/wiki/DCF77} und Verweise darin
%\item \label{willem} ByteForth von Willem Ouwerkerk,\\  \url{http://www.forth.hccnet.nl/byteforth.htm}
\item \label{amforth} \amforth--Seite: \url{amforth.sourceforge.net/}
%\item \label{ron} Ron Minke, Forth von der Pike auf  Die 4. Dimension 3+4/2005 \ldots 4/2006, sowie Sonderheft AVR und  3+4/2007
%\item \label{namedbits} M. Kalus, M. Trute, Named Bits, Die 4. Dimension 2/2007, Jahrgang 23
\item \label{lubos} Lubo\v{s} P\v{e}kn\'{y}s Seite: \url{http://www.forth.cz}
\item \label{RS485} Wikipedia (RS485) \url{de.wikipedia.org/wiki/RS485}
\item \label{RS485a} \url{www.mikrocontroller.net/articles/RS-485}
\item \label{g4.fs} Michael Kalus: g4.fs \url{www.forth-ev.de/repos/g4/g4.fs}
\item \label{atmega32} atmega32--Datenblatt (doc2503.pdf) \url{www.atmel.com}
\end{enumerate}
\end{multicols}

\vfill
\begin{center}
\includegraphics[height=10cm]{2011-01/Tobias}\\
\emph{Tobias, ich denke, Du hast die schrittweise Verfeinerung dieses Moduls weit genug getrieben.}\\
{\tiny Quelle: Thinking Forth}
\end{center}
\vfill

% --------------------------------------------------------------------
\section{Listings}

Die Listings des kompletten Projekts sind etwa 700 Zeilen lang, m.\,E.\
viel zu lang f"ur die Druckausgabe. Die Dateien stehen unter
\url{www.forth-ev.de} zum Herunterladen zur Verf"ugung.

Lediglich die Datei \path{main.asm}, eine ge"anderte Kopie der Datei
\path{amforth/releases/4.2/appl/template/template.asm}, sei hier gezeigt,
weil die "altere Generation der atmega--Kontroller ein paar mehr
Informationen im Assemblerquelltext verlangt.


\begin{quote}
\begin{small}
%\begin{multicols}{2}
\listinginput[1]{1}{2011-01/main.asm}
%%\listinginput[1]{1}{2011-01/dict_appl.inc}
%%\listinginput[1]{1}{2011-01/dict_appl_core.inc}
%%%
%%% startup
%%\listinginput[1]{1}{2011-01/applturnkey.asm}
%%%
%%% rs485 module
%%\listinginput[1]{1}{2011-01/rs485-init.asm}
%%\listinginput[1]{1}{2011-01/rs485.asm}
%%\listinginput[1]{1}{2011-01/usart-isr-tx.asm}
%%%
%%% -emit
%%\listinginput[1]{1}{2011-01/emit-on-off.asm}
%%%
%%% mpc module
%%\listinginput[1]{1}{2011-01/mpc.asm}
%%\listinginput[1]{1}{2011-01/mpc.fs}
%%\listinginput[1]{1}{2011-01/usart-isr-rx.asm}
%%%
%%% application
%%\listinginput[1]{1}{2011-01/main-61.fs}
%%%
%%% ready prompt
%%\listinginput[1]{1}{2011-01/prompts.asm}

%\end{multicols}
\end{small}
\end{quote}

\section{Schaltung}

\bigskip
\begin{figure}
\centerline{\includegraphics[scale=0.5,angle=0]{2011-01/p_rs485_bus.eps}}
\caption{Anschluss des RS485 Transceivers an den Kontroller. R1 ist ein
  pull--down Widerstand, so dass diese Leitung auch dann low ist, wenn der
  D.7 Pin noch auf Eingang steht. Die Steckbr"ucken sind manchmal
  praktisch, um den Transceiver vor"ubergehend vom Bus und/oder Kontroller
  abzuh"angen. Die Widerst"ande R4, R5 und R6 werden nur unter bestimmten
  Umst"anden ben"otigt (Definition der Pegel und Busabschluss).}
\end{figure}


\end{document}
