Manfred G. Fischer
Digitale Modellbahnsteuerung
mit
Turbo Pascal
1995
Editha Fischer Verlag
Die hier abgedruckten Programmteile sind nach bestem Wissen und Kenntnisstand entstanden. Fehler sind aber nicht völlig ausschließbar. Es kann daher nicht angenommen werden, daß diese Unterprogramme und das Hauptprogramm fehlerfrei und vollständig sind. Ferner wird an dieser Stelle ausdrücklich darauf hingewiesen, daß die abgedruckten Programmteile nicht auf allen jemals produzierten Rechnern der PC-Klasse ablauffähig sind.
Daher übernehmen Autor und Verlag keine Garantie noch die juristische Verantwortung oder irgendeine Haftung für die Nutzung der hier abgedruckten Informationen, für deren Wirtschaftlichkeit oder fehlerfreie Funktion für einen bestimmten Zweck. Ferner können weder Autor noch Verlag für Schäden, die auf eine Fehlfunktion dieser Programmteile zurückzuführen sind, haftbar gemacht werden, auch nicht für die Verletzung von Patent- und anderen Rechten Dritter, die daraus resultieren.
Die Deutsche Bibliothek - CIP-Einheitsaufnahme
Digitale Modellbahnsteuerung mit Turbo Pascal / Manfred G. Fischer. - Münster : Fischer.
Buch. - 1995
Diskette. - 1995
ISBN 3-9803309-3-1
NE: Fischer, Manfred G.
Satz+Druck: Manfred Fischer
© Editha Fischer Verlag, 1995
D-48153 Münster
Alle Rechte vorbehalten
Out of Print: Juni 2003
Veröffentlichung im WWW: Oktober 2001, Wiederveröffentlichung im WWW: Juni 2003
Fehlerkorrektur im WWW: 2.Mai 2004 (Magnetartikelschaltung) - (*korr*2004)
HTML-Debugging und Umstellung auf normale Textseite: 19. Oktober 2013
Wehe jenen, die Satzungen geben voll Unheil und bedrückende Vorschriften niederschreiben! [Isaias 10,1]
Vorwort
Was macht man mit einem in Ehren ergrauten PC, wenn er schon recht veraltet, aber doch noch nicht schrottreif, ungenutzt in der Ecke steht, weil die beiden Nachfolgemodelle nun auch schon zur Ablösung anstehen? Ein Verkauf lohnt sich meist nicht mehr. Aber er hat ja serienmäßig Schnittstellen eingebaut, eine alte Tastatur und ein Bildschirm finden sich auch meist noch irgendwo. Warum nicht die Modelleisenbahn damit steuern (die neueren Loks sind dafür heute eh schon vorbereitet)? Eine Maschine mit mäßiger Arbeitsgeschwindigkeit (25 Mhz) reicht aus, um eine mittelgroße Modellbahnanlage im Griff zu behalten. Angeregt durch einen Artikel im Märklin Magazin [18] entstand dann das auf Diskette beiliegende Programm im Verlauf des letzten halben Jahres. Mein Ziel war es, Lok- und Funktionsmodellsteuerung halbwegs komfortabel mit einem einfachen, individuell gestaltbaren Gleisbildstellwerk in einem Programm zu vereinigen. Zusätzlich sollte das Programm analog zu den Originalprogrammen von Märklin (siehe [1] und [9]) auch Betriebsabläufe automatisch steuern. Auf Rechnern der PC-Klasse gibt es eigentlich nur eine Programmiersprachen-Implementation, die lesbare, nachvollziehbare Propgrammstrukturen (die man auch veröffentlichen kann) ermöglicht: die Compiler-Familie Turbo-Pascal® von Borland International, Inc. (z.Zt. neueste Version: Turbo-Pascal® 7.0 [26], [27]). Deshalb die Entwicklung in TurboPascal®. Um den Rahmen einer kleinen Veröffentlichung nicht zu sprengen, wurde auf den Abdruck des Hauptprogramms (dieses befindet sich auf der beigefügten Diskette) verzichtet, nur die beiden wichtigsten Unterprogrammsammlungen sind hier auch im Source-Code abgedruckt.
Münster, März 1995 Manfred Fischer
Urheberrechtliche Hinweise
* IBM®, PC-DOS®, PS/2, MDA, CGA, EGA (Enhanced Graphics Adapter), IBM-PC, IBM-PCjr, IBM-PC/XT, IBM-PC/AT, IBM-Model 30 und VGA sind eingetragene Warenzeichen der International Business Machines Corporation.
* Hercules Graphics Adapter ist ein eingetragenes Warenzeichen der Hercules Computer Technology.
* Intel ist ein eingetragenes Warenzeichen von Intel Corporation.
* TurboPascal® ist ein eingetragenes Warenzeichen der Firma Borland International, Inc..
* Der Firmenname Märklin, die Produktnamen Märklin Digital und Märklin Delta sind eingetragene Warenzeichen der Gebr. Märklin & Cie GmbH.
Notationshinweise
Diese Routinen machen allesamt Gebrauch von den Möglichkeiten zum Hardware-Direktzugriff des Compilers TurboPascal®. Deshalb ist hier auch kein Standard-Pascal nach der Wirth'schen Definition [7] zu erwarten. Das hier abgedruckte Unit-Konzept ist bei TurboPascal® ab der Version 4.0 [25] verwirklicht. Die in diesem Buch abgedruckten Unterprogramme sind in Unit-Form ab TurboPascal® 4.0 verwendbar. Bei noch älteren Compilerversionen [24] braucht nur der Unit-Deklarationsteil weggelassen zu werden, um die Unterprogramme einbinden zu können.
Bei der Notation hexadezimaler Zahlen wird diesen im Text ein kleines "h" angefügt. Im Pascal-Sourcecode werden die üblichen Konventionen von TurboPascal® eingehalten (d.h. der Hex-Zahl wird ein "$" vorangestellt).
Inhaltsverzeichnis
1. Grundlagen........................................................................................ 6
1.1. Übersicht über die Interface-Befehle......................... 7
1.2. Verwendete TurboPascal Syntaxerweiterungen......... 8
2. Die Unit RS232................................................................................... 9
2.1. Interface.................................................................. 9
2.2. Bitweise Manipulationen......................................... 10
2.3. Auswertung des Equipment-Bytes........................... 11
2.4. Port-Zugriffe........................................................... 11
2.5. Übergeordnete Unterprogramme............................. 17
3. Die Unit Digital.................................................................................. 19
3.1. Interface................................................................ 19
3.2. Decoder-Schalterstellungen berechnen.................... 20
3.3. Allgemeine Digital-Prozeduren................................. 23
3.4. Loksteuerung......................................................... 25
3.5. Funktionsmodellsteuerung...................................... 26
3.6. Magnetartikel schalten............................................ 26
3.7. Rückmeldemodule auswerten.................................. 28
Anhang................................................................................................. 31
A. Übersicht über die Hauptprogrammstruktur................................ 31
B. Bedienungsanleitung des Hauptprogramms................................ 31
C. Literatur................................................................................... 35
1. Grundlagen
Die Steuerung von mehr als einer Lokomotive bzw. von mehr als einem Magnetartikel mit nur einem Stromkreis ohne aufwendige Verkabelung ermöglicht die Digitale Mehrzugsteuerung von Märklin seit 1984/85 (ähnliche Systeme haben andere Hersteller auch im Lieferprogramm, siehe [19, Seiten 20ff]). Dabei wird der konventionelle Wechselstrom (wie er vom Transformator geliefert wird) von der Zentraleinheit in eine Abfolge von positiven und negativen Spannungswerten umgewandelt (codiert), mit denen sich dann seriell Daten an die Mikroprozessoren in den Lok- und Magnetartikeldecodern weiterleiten lassen (bei gleichzeitiger Strom-versorgung). Neben den Lok-Controls des Märklin-Digitalsystems kann die Zentraleinheit auch über das Computer-Interface (Artikel-Nr. 6050/6051) ange-sprochen werden. Dieses besitzt eine modifizierte (serielle) RS 232-Schnittstelle, und ist damit an fast jeden Rechner anschließbar. Was der Programmierer neben den Eigenheiten des Gesamtsystems wissen muß (siehe [3]), ist lediglich die Pin-Belegung der Schnittstellen des Interface (siehe [1], [6] und [9]) und seines Rechners. Aber auch diese Arbeit wird ihm mit der neuen Version des Interface (Artikel-Nr. 6051) abgenommen, da diesem bereits ein PC-kompatibles Verbindungskabel beiliegt (à la "plug 'n play"). Ferner sind nur noch die Steuerungsbefehle nötig, die das Interface akzeptiert. Und um die soll es in diesem Kapitel gehen.
Der Aufbau von Märklin Digital selbst ist bestens beschrieben in der neuen Auflage der Originalbeschreibung von Märklin ([3], Artikel-Nr. 0308), die Grundlagen zur Programmierung des Digital-Interface (Artikel-Nr. 6050/6051) sind ausführlich erläutert in einem Buch aus der Reihe CHIP-Special des Vogel-Verlages [1] (die alte Fassung [2] ist nicht mehr verfügbar). Eine Übersicht über am Markt verfügbare professionelle Programme findet sich in einer Ausgabe des Märklin Magazins von 1991 [16]. Zur Geschichte der Antriebstechnik der Märklin-Lokomotiven siehe [8].
Schnittstellen-Verbindung PC - Digital-Interface (DIN-Stecker) nach [6]:
Wichtig ist die Verbindung von DCD und DSR (Data Carrier Detect und Data Set Ready) mit DTR (Data Terminal Ready) auf der Rechnerseite! (siehe [6]).
1.1. Übersicht über die Interface-Befehle
Diese Bytes sendet das Computerprogramm an das Interface 6050/6051:
Lokbefehl (2 Bytes), erst Geschwindigkeit und Lokfunktion, dann Decoder-Nummer.
0..15 Geschwindigkeit, Lokfunktion aus, 1..80 Decoder-Nummer.
16..31 Geschwindigkeit, Lokfunktion an, 1..80 Decoder-Nummer.
Magnetartikelbefehle (1 Byte oder 2 Bytes), erst Schaltstellung,
dann Magnetartikelnummer.
32 alle Magnetartikel ausschalten (1 Byte).
34 Rot/Rund schalten (2 Bytes), 0..255 Magnetartikelnummer (*korr*2004)
(0 entspricht 256).
33 Grün/Gerade schalten (2 Bytes), 0..255 Magnetartikelnummer (*korr*2004)
(0 entspricht 256).
Funktionsdecoder-Befehl (2 Bytes), erst Funktions-Bits-Status,
dann Decoder-Nummer.
64..79 Sonderfunktionswert 1..80 Decoder-Nummer.
(:= 64 + F1 + 2*F2 + 4*F3 + 8*F4),
Nothalt und Freigabe (1 Byte)
96 Freigabe nach Nothalt oder Kurzschluß.
97 Nothalt.
Rückmeldemodulbefehle (1 Byte)
128 Rücksetzen der Rückmeldemodule nach dem Einlesen ausschalten.
129..159 Auslesen aller Rückmeldemodule bis zum Modul-Nr. "Befehl - 128"
(:= 128 + Nummer des Moduls, bis zu dem alle angeschlossenen
Rückmeldemodule ausgelesen werden)
=> Interface liefert dann "Modul-Nr. * 2" Bytes.
192 Rücksetzen der Rückmeldemodule nach dem Einlesen einschalten
(Default).
193..223 Auslesen eines Rückmeldemoduls,
(:= 192 + Nummer der Moduls (1..31))
=> Interface liefert dann 2 Bytes.
Wie man sieht, ist da noch Platz für spätere Erweiterungen des Gesamtsystems gelassen worden.
1.2. Verwendete TurboPascal Syntaxerweiterungen
Da TurboPascal® keine eigene Unit beisteuert, um auf die serielle Schnittstelle des PC zuzugreifen, ist in diesem Buch eine Unit mit angefügt, die aus bewährter Literatur entwickelt wurde [4], [21], [23]. Dabei erfolgt der Zugriff über direkte Port-Adressierung des für diese Schnittstelle zuständigen seriellen Kommunikationspro-zessors UART 8250 (bzw. eines zu ihm abwärtskompatiblen Nachfolgemodells). Die Syntax-Erweiterung von TurboPascal® für die direkten Portzugriffe ist das vor-definierte array Port[] aus der TurboPascal® Unit System. Direkte Speicher-zugriffe erfolgen mit dem vordefinierten array MemW[]. Insgesamt nutzen die aufgeführten Units und an einer Stelle auch das Hauptprogramm folgende Syntaxerweiterungen aus der TurboPascal® Unit System (siehe [27]):
Var MemW : array[0..$FFFF:0..$FFFF] of Word; {RAM des PC}
Port : array[0..$FFFF] of Byte; {I/O-Ports des PC}
Function UpCase(Ch : Char) : Char;
Procedure FillChar(var x; Count : Word; Value : Char);
Function ParamCount : Word;
Function ParamStr(Index : Word) : String;
Um die zu steuernden Prozesse zeitlich zu synchronisieren, ist ein Zugriff auf die interne Computer-Systemuhr unerläßlich. Dies ist in TurboPascal® über die Unit Dos möglich:
Function GetTime(var Hour, Minute, Secs, Milli : Word);
Desweiteren nutzt das Hauptprogramm auf der Diskette vor allem die TurboPascal® Unit Crt zur Anzeige am Bildschirm, da nur Buchstabengrafik im TextMode verwendet wird. Dem interessierten Leser bleibt es unbenommen, entsprechende Erweiterungen im Grafikmodus hinzuzufügen.
Um eine spätere Umstellung auf grafische Anzeige zu erleichtern, erfolgt der Zugriff auf die Unit Crt vom Hauptprogramm aus nicht direkt, sondern es ist die Unit ScreenIO zwischengeschaltet. Die Unit RollMenu stellt ein einfaches Menusystem im TextMode zur Verfügung, die Unit BigText1 liefert Routinen zur Anzeige von Ziffern mittels jeweils 6 der "grafischen" PC-Zeichensatzsymbole in einer 2x3-Zeichenmatrix.
Die Unit Digital enthält die eigentlichen Unterprogramme, um das Märklin-Digital Interface 6050/6051 anzusprechen, die Unit RS232 liefert dazu die PC-seitigen Schnittstellengrundlagen.
2. Die Unit RS232
Der serielle Kommunikationsprozessor UART 8250 steuert die seriellen Schnittstel-len COM1: bis COM4:. Er war standardmäßig in allen bisherigen 80x86 PC's eingebaut. Moderne Nachfolgeprozessoren in PC's sind i.d.R. zu ihm abwärtskompatibel.
In dieser Unit finden sich zunächst Routinen zum Zugriff auf die Basis der Arbeit mit diesem Prozessor, nämlich auf die ihm zugeordneten Port-Adressen. Die Port-Adressen der jeweiligen Schnittstelle finden sich im RAM ab der PC-Speicheradresse 0040h:0000h (siehe [21] und [4]). Der 8250 verfügt über mehrere Register, einige (03F8h, 03F9h bei COM1:, 02F8h, 02F9h bei COM2: etc.) haben mehrere Funktionen: das Divisor Latch Access Bit (DLAB) im Line Control Register steuert die Benutzung dieser multifunktionalen Ports. Voreingestellt ist die Benutzung dieser Ports als programmierbarer Baud-Generator (bei DLAB gesetzt). Deshalb muß in jedem Fall vor der eigentlichen Datenübertragung das DLAB gelöscht werden. Anschließend kann dann die Port-Adresse 03F8h (bei COM1:; 02F8h analog bei COM2: etc.) wie folgt benutzt werden: wird hineingeschrieben, dient es als Senderegister, kommt von "außen" ein Zeichen, dient es als Empfangsregister. Die Bereitschaft zu der jeweiligen Arbeitsweise regelt das Line Status Register. Der Leitungszustand und die Bereit-schaft des Modems ist über das Modem Status Register abfragbar.
2.1. Interface
(* TurboPascal 4.0 - 7.0 Unit ******* 29.01.1995 *)
(* (c) alle Rechte bei Manfred Fischer, Münster *)
Unit RS232; {$A+,S-,I-,R-,B-,D-,E-,F-,L-,N-,O-,V-}
Interface
Type SystemString = String[25];
Function ComPorts : Byte; {Anzahl der COMx: Schnittstellen}
Function ComPortAddr(ComNo : Byte) : Word;
Procedure InitCom(ComNo : Byte; {ComNo 1..4}
Baud : Word; Data : Byte;
Parity : Char; Stop : Byte);
Procedure SendChar(ComNo : Byte; C : Char);
Function GetChar(ComNo : Byte) : Char;
Function CharThere(ComNo : Byte) : Boolean;
Function ComReady(ComNo : Byte) : Boolean;
Function IdentifyComStatus(ComNo : Byte) : SystemString;
2.2. Bitweise Manipulationen
Implementation
CONST {Offsets zum Inhalt der Variablen gPort0:}
LineControlRegister = 3; {->bei COM1: $3FB}
ProgrammableBaudGenerator_LSB = 0; { $3F8}
ProgrammableBaudGenerator_MSB = 1; { $3F9}
LineStatusRegister = 5; { $3FD}
InterruptIdentificationRegister = 2; { $3FA}
InterruptEnableRegister = 1; { $3F9}
ModemControlRegister = 4; { $3FC}
ModemStatusRegister = 6; { $3FE}
ScratchPadRegister = 7; { $3FF}
ReceiveBufferRegister = 0; { $3F8}
TransmitterHoldingRegister = 0; { $3F8}
Var
gPort0 : Word; {globale Variable, enthält Adresse des ersten Ports}
{--- Bit Manipulationsfunktionen, Bits gezählt von rechts von 0 bis 15 ---}
Function GetBit(b : Word; Nr : Byte) : Byte;
begin {Bits gezählt von 0 bis 15}
GetBit := Ord(Odd(b SHR Nr)); {liefert 0 oder 1}
end;
Function IsSetBit(b : Word; Nr : Byte) : Boolean;
begin {analog zu GetBit(); Odd(0) == FALSE}
IsSetBit := Odd(b SHR Nr);
end;
Function SetBit(b : Word; Nr : Byte) : Word;
begin {Bit setzen}
SetBit := b or (1 SHL Nr);
end;
Function BlankBit(b : Word; Nr : Byte) : Word;
begin {Bit löschen}
BlankBit := b and not (1 SHL Nr);
end;
2.3. Auswertung des Equipmentbytes
{--- Anzahl der COMx:-Schnittstellen und ihre Port-Adressen ---}
Const
BiosRamSeg = $0040; {PC-RAM-Bereich, wo das BIOS Werte speichert}
Function Equipment : Word; {Zustand des Hardware-Equipment}
begin
Equipment := MemW[BiosRamSeg:$0010];
end;
Function ComPorts : Byte; {Anzahl der COMx:-Schnittstellen}
begin
ComPorts := (Equipment SHR 9) AND 3;
end;
Function ComPortAddr(ComNo : Byte) : Word;
begin {liefert den Wert 0, wenn nicht vorhanden}
if (ComNo >= 1) and (ComNo <= ComPorts) then
ComPortAddr := MemW[BiosRamSeg:((ComNo - 1) * 2)]
else ComPortAddr := 0; {d.h. nicht vorhanden}
end;
Function DirectComPortAddr(ComNo : Byte) : Word;
begin {ComNo im Bereich 1..4, ohne Kontrolle auf Plausibilität}
DirectComPortAddr := MemW[BiosRamSeg:(Pred(ComNo) * 2)]
end;
2.4. Port-Zugriffe
{--- Routinen mit den Port-Zugriffen benötigen die globale Variable gPort0 ---}
{--- Line Control Register ---}
Function GetDatenBits : Byte;
Var i : Integer;
begin
i := Port[gPort0 + LineControlRegister];
GetDatenBits:=GetBit(i,1)*2 + GetBit(i,0) + 5;
{DatenBits möglich von 5 bis 8}
end;
Procedure SetDatenBits(Anzahl : Byte);
Var i : integer;
begin
i := Port[gPort0 + LineControlRegister];
Anzahl := (Anzahl - 5) MOD 4; {nur 2 Bit breit !}
i := i and (not 3); {Bit 0 und 1 löschen}
i := i + Anzahl; {Bit 0 und 1 entsprechend neu}
Port[gPort0 + LineControlRegister] := i;
end;
Function GetStopBits : Byte;
begin {mögliche Anzahl: 1 oder 2 StoppBits}
GetStopBits := GetBit(Port[gPort0 + LineControlRegister],2) + 1;
end;
Procedure SetStopBits(Anzahl : Byte);
begin {mögliche Anzahl: 1 oder 2 StoppBits}
if ((Anzahl MOD 2) = 0) then
Port[gPort0 + LineControlRegister] := SetBit(Port[gPort0 + LineControlRegister],2)
else Port[gPort0 + LineControlRegister] := BlankBit(Port[gPort0 + LineControlRegister],2);
end;
Function GetParity : Char;
begin
if (GetBit(Port[gPort0 + LineControlRegister],3) = 1) then
begin {ParityEnableBit ist gesetzt}
case GetBit(Port[gPort0 + LineControlRegister],4) of
0 : GetParity := 'O'; (* Odd *)
1 : GetParity := 'E'; (* Even *)
end;
end
else GetParity := 'N'; (* no parity *)
end;
Procedure SetParity(P : Char);
Var i : Integer;
begin
i := Port[gPort0 + LineControlRegister];
if (UpCase(P) = 'N') then
Port[gPort0 + LineControlRegister] := BlankBit(i,3)
else
begin
i := SetBit(i,3);
case UpCase(P) of
'O': i := BlankBit(i,4);
'E': i := SetBit(i,4);
end;
Port[gPort0 + LineControlRegister] := i;
end;
end;
{stick parity bit and break control bit not managed here}
Procedure SwitchToBaudGenerator;
{Set the Divisor Latch Access Bit (DLAB) for $3F8 and $3F9}
begin
Port[gPort0 + LineControlRegister] := SetBit(Port[gPort0 + LineControlRegister],7);
end;
Procedure TurnOffBaudGenerator;
{Turn OFF the Divisor Latch Access Bit (DLAB) for $3F8 and $3F9}
begin
Port[gPort0 + LineControlRegister] := BlankBit(Port[gPort0 + LineControlRegister],7);
end;
{--- Programmable Baud Generator ---}
{gültige Baudraten beim 8250: 50, 110, 134.5, 150, 300, 600, 1200, 1800,}
{2000, 2400, 3600, 4800, 7200, 9600. Bei neueren Prozessoren auch höher}
Function GetBaudRate : Word;
Var
w : Word;
r : Real;
begin {DLAB = 1 !}
w := (Port[gPort0 + ProgrammableBaudGenerator_MSB] SHL 8) + Port[gPort0 + ProgrammableBaudGenerator_LSB];
r := (9600.0 / w) * 12.0;
GetBaudRate := Round(r);
end;
Procedure SetBaudRate(Baud : Word);
Var Divisor : Real;
d : Word;
begin {DLAB = 1 !}
case Baud of
50..9600 : ; {da gültig}
else Baud := 300;
end;
Divisor := (9600.0 / Baud) * 12.0;
d := Round(Divisor);
case d of
56..58 : d := 58; {2000 Baud}
800..900 : d := 857; {134.5 Baud}
else if (d < 12) then d := 12; {nie größer als 9600 Baud}
end;
Port[gPort0 + ProgrammableBaudGenerator_LSB] := d MOD 256;
Port[gPort0 + ProgrammableBaudGenerator_MSB] := d SHR 8;
end;
{--- Line Status Register (read only !) ---}
{in das Line Status Register darf nie geschrieben werden !}
Function NewByteIsThere : Boolean;
begin
NewByteIsThere := IsSetBit(Port[gPort0 + LineStatusRegister],0);
end;
Function AnyError : Boolean;
begin
{bit 1: OverRun Error, empfangenes Zeichen wurde überschrieben}
{bit 2: Parity Error}
{bit 3: Framing Error, Stoppbits falsch}
AnyError := ((Port[gPort0 + LineStatusRegister] SHR 1) MOD 8) <> 0;
end;
Function ReadyForNewByte : Boolean;
begin {d.h. das Transmitterholdingregister ist durch Senden geleert worden}
ReadyForNewByte := (Port[gPort0 + LineStatusRegister] AND 96) = 96;
end;
{--- Receiving and Transmitting ---}
Procedure TransmittChar(c : Char);
begin {DLAB = 0 !}
Port[gPort0 + TransmitterHoldingRegister] := Ord(c);
end;
Function ReceiveByte : Byte;
begin {DLAB = 0 !}
ReceiveByte := Port[gPort0 + ReceiveBufferRegister];
end;
Function ReceiveChar : Char;
begin {DLAB = 0 !}
ReceiveChar := Chr(Port[gPort0 + ReceiveBufferRegister]);
end;
{--- Modem Status Register ---}
Function ClearToSend : Boolean;
begin {CTS = Verbindung besteht}
ClearToSend := IsSetBit(Port[gPort0 + ModemStatusRegister],4);
end;
Function DataSetReady : Boolean;
begin {DSR = Modem hat genug Saft und ist bereit}
DataSetReady := IsSetBit(Port[gPort0 + ModemStatusRegister],5);
end;
Function RingIndicator : Boolean;
begin {RI, hier nicht benutzt, nur bei Pufferung in Gebrauch}
RingIndicator := IsSetBit(Port[gPort0 + ModemStatusRegister],6);
end;
Function DataCarrierDetect : Boolean;
begin {DCD = Empfangssignalpegel, Lautstärke ausreichend}
DataCarrierDetect := IsSetBit(Port[gPort0 + ModemStatusRegister],7);
end;
{--- Modem Control Register ---}
Procedure SetDataTerminalReady;
begin
Port[gPort0 + ModemControlRegister] := SetBit(Port[gPort0 + ModemControlRegister],0);
end;
Procedure BlankDataTerminalReady;
begin
Port[gPort0 + ModemControlRegister] := BlankBit(Port[gPort0 + ModemControlRegister],0);
end;
Procedure SetRequestToSend;
begin {RTS = Anfrage, ob Sendebereitschaft vorliegt}
Port[gPort0 + ModemControlRegister] := SetBit(Port[gPort0 + ModemControlRegister],1);
end;
Procedure BlankRequestToSend;
begin
Port[gPort0 + ModemControlRegister] := BlankBit(Port[gPort0 + ModemControlRegister],1);
end;
{--- Zusammenfassung ---}
Procedure InitOfRS232(Baud : Word; DatenBit : Byte; Parity : Char; StopBit : Byte);
begin
SwitchToBaudGenerator;
SetBaudRate(Baud);
SetDatenBits(DatenBit);
SetParity(Parity);
SetStopBits(StopBit);
TurnOffBaudGenerator;
end;
Procedure ReadStatusOfRS232(var Baud : Word; var DatenBit : Byte; var Parity : char; var StopBit : Byte);
begin
SwitchToBaudGenerator;
Baud := GetBaudRate;
DatenBit := GetDatenBits;
Parity := GetParity;
StopBit := GetStopBits;
TurnOffBaudGenerator;
end;
Procedure ModemAn;
begin
SetDataTerminalReady;
SetRequestToSend;
end;
Procedure ModemAus;
begin
BlankDataTerminalReady;
BlankRequestToSend;
end;
2.5. Übergeordnete Unterprogramme
{--- Nun die Routinen zur Ansprache mit eigenem System ---}
Procedure InitCom(ComNo : Byte; {ComNo 1..4}
Baud : Word; Data : Byte;
Parity : Char; Stop : Byte);
begin
gPort0 := DirectComPortAddr(ComNo); {COM1=$40:00, }
InitOfRS232(Baud,Data,Parity,Stop); {COM2=$40:02 ..}
ModemAn;
end;
Procedure SendChar(ComNo : Byte; C : Char);
begin
gPort0 := DirectComPortAddr(ComNo);
TransmittChar(C);
end;
Function GetChar(ComNo : Byte) : Char;
begin
gPort0 := DirectComPortAddr(ComNo);
GetChar := ReceiveChar;
end;
Function CharThere(ComNo : Byte) : Boolean;
begin
gPort0 := DirectComPortAddr(ComNo);
CharThere := NewByteIsThere;
end;
Function ComReady(ComNo : Byte) : Boolean;
begin
gPort0 := DirectComPortAddr(ComNo);
ComReady := ReadyForNewByte AND ClearToSend;
end;
Function IdentifyComStatus(ComNo : Byte) : SystemString;
Var Erg, Temp : SystemString;
Baud : Word;
DatenBit, StopBit : Byte;
Par : Char;
begin
gPort0 := DirectComPortAddr(ComNo);
ReadStatusOfRS232(Baud,DatenBit,Par,StopBit);
Str(Baud,Erg);
Str(DatenBit,Temp);
Erg := Erg + ',' + Temp + ',' + Par;
Str(StopBit,Temp);
Erg := Erg + ',' + Temp + ' ';
if ClearToSend then Erg := Erg + ' CTS';
if DataSetReady then Erg := Erg + ' DSR';
if DataCarrierDetect then Erg := Erg + ' CD';
IdentifyComStatus := Erg;
end;
end. {kein Initialisierungsteil der Unit}
3. Die Unit Digital
Nun gilt es, die Steuerung des Märklin-Interfaces unter Verwendung der Unit RS232 in Form einer Unterprogrammsammlung zu realisieren, die von anderen Hauptprogrammen universell verwendet werden können.
3.1. Interface
(* TP 4.0 - 7.0 Unit: Upros zu Märklin Digital und Märklin Delta *)
(* (c) alle Rechte bei Manfred Fischer, Münster *)
Unit Digital;
Interface
Const ComPort : Byte = 1; {an diesem ist das Interface angeschlossen}
TimeOutLimit : Word = 5000; {Anzahl der maximalen Versuche}
PortError : Boolean = FALSE; {wenn TimeOut auftrat}
MagnetDelay : Word = 1000; {Delay in Millisekunden}
{min. 80ms, max. 1000ms, ideal 150ms}
Vstop = 0; {Halt-Geschwindigkeit bei Märklin-Digital}
Vmin = 1; {minimal mögliche Geschwindigkeit bei Märklin-Digital}
Vmax = 14; {maximal mögliche Geschwindigkeit bei Märklin-Digital}
Vturn = 15; {"Geschwindigkeit" für Fahrtrichtungsumschaltung}
Fmin = 1; {kleinste Funktionsnummer bei Funktionsmodellen}
Fmax = 4; {größte Funktionsnummer bei Funktionsmodellen}
LokAdresseMin = 1; {bei Märklin-Digital möglicher Lok-}
LokAdresseMax = 80; {Adressen-Bereich: 1..80}
FModellAdresseMin = 1; {bei Märklin-Digital möglicher Funk-}
FModellAdresseMax = 80; {tionsmodell-Adressen-Bereich: 1..80}
DeltaKonventionellAdresse = 0; {nur Delta-Decoder mä6603}
MagnetAdresseMin = 1; {bei Märklin-Digital mögl. Magnet-}
MagnetAdresseMax = 256; {artikel-Adressen-Bereich: 1..256}
RueckmeldeAdresseMin = 1; {bei Märklin-Digital mögl. Rück-}
RueckmeldeAdresseMax = 496; {meldemodul-Adressen-Bereich}
Type String4 = String[4]; {Delta Codier-Schalter mä6603}
String8 = String[8]; {Digital Codier-Schalter}
TString = String[20]; {textliche Erläuterungen}
Function DigitalSchalter2Adresse(S : String8) : Byte;
Function Adresse2DigitalText(Adresse : Byte) : TString;
Function Adresse2DigitalSchalter(Adresse : Byte) : String8;
Function DeltaSchalter2Adresse(S : String4) : Byte;
Function Adresse2DeltaText(Adresse : Byte) : TString;
Function Adresse2DeltaSchalter(Adresse : Byte) : String4;
Function IsDeltaAdresse(Adresse : Byte) : Boolean;
Function IsDigitalKranAdresse(Adresse : Byte) : Boolean;
Procedure SetComPort(Neu : Byte);
Procedure SetMagnetDelay(MilliSekunden : Word);
Function GetMagnetDelay : Word; {in Millisekunden}
Procedure DigitalInterfaceInit;
Procedure NotHalt;
Procedure Freigabe;
Procedure LokBefehl(LokNr, Geschwindigkeit : Byte; MitFunktion : Boolean);
Procedure LokHalt(LokNr : Byte; MitFunktion : Boolean);
Procedure LokUmschalten(LokNr : Byte; MitFunktion : Boolean);
Procedure FunktionsBefehl(FktNr : Byte; F1, F2, F3, F4 : Boolean);
Procedure MagnetAus;
Procedure MagnetBefehl(ArtikelNr : Byte; Befehl : Byte);
Procedure MagnetGruen(ArtikelNr : Byte);
Procedure MagnetRot(ArtikelNr : Byte);
Procedure RueckmeldemoduleRuecksetzen(Ausschalten : Boolean);
Function RueckmeldemodulLesen(ModulNr : Byte) : Word;
Function TestRueckmeldemodul(ModulNr : Byte; Eingang : Byte) : Boolean;
Function TestRueckmeldeEingang(Eingang : Word) : Boolean;
3.2. Decoder-Schalterstellungen berechnen
Um nicht ständig in langen Listen die Adresse aus der Schalterstellung des Lok- bzw. Deltadecoders ermittteln zu müssen, habe ich diese kleine Sammlung an Umrechnungs-Unterprogrammen mit eingefügt. Der Schalter besteht aus 8 Dipschaltern (bei Delta: 4 Dipschalter), die in Paaren zusammengefaßt sind und mit 3 gültigen Stellungen drei Zustände repräsentieren (d.h. ternäre Codierung). Es sind also pro Paar nicht alle 4 möglichen Schalterstellungen erlaubt. Denn aus einem Paar darf immer nur ein Dipschalter auf "on" geschaltet sein.
Implementation
Uses Crt {für Delay}, RS232;
Function DigitalSchalter2Adresse(S : String8) : Byte;
Var Erg : Byte;
Loop : Byte;
begin
if (S[1] <> '-')and(S[3] <> '-')and(S[5] <> '-')
and(S[7] <> '-') then Erg := LokAdresseMax
else
begin
Erg := 0;
for Loop := 4 downto 1 do
begin
Erg := Erg * 3;
if (S[Loop * 2] <> '-') then Erg := Erg + 1
else if (S[(Loop * 2) - 1] <> '-')
then Erg := Erg + 2;
end;
Erg := LokAdresseMax - Erg;
end;
DigitalSchalter2Adresse := Erg;
end;
Function Adresse2DigitalText(Adresse : Byte) : TString;
begin case Adresse of
8 : Adresse2DigitalText := 'Micheline grün';
10 : Adresse2DigitalText := 'Kellnerwagen';
20 : Adresse2DigitalText := 'Tanzwagen';
59 : Adresse2DigitalText := 'Köf II';
62 : Adresse2DigitalText := 'Micheline rot';
76 : Adresse2DigitalText := 'Glaskasten grün';
77 : Adresse2DigitalText := 'B VI Tristan';
79 : Adresse2DigitalText := 'LAG Triebwg. ET 194';
else Adresse2DigitalText := '';
end;
end;
Function Adresse2DigitalSchalter(Adresse : Byte) : String8;
{Setzt eine Lokadresse um in die Schalterstellungsfolge des Digitaldecoders.}
{Ternäre Speicherung der Adresse: Schalterpaare sind niemals gleichzeitig ON;}
Var Erg : String8; {2 Bits werden zur Darstellung von 3 Zuständen}
Loop, A : Byte; {verbraucht; Adresse 80 ist ein Sonderfall.}
begin
if (Adresse >= LokAdresseMax) then Erg := '1-3-5-7-' {Adr. 80}
else begin
Erg := '';
A := LokAdresseMax - Adresse;
for Loop := 1 to 4 do
begin
case (A MOD 3) of 0 : Erg := Erg + '--';
1 : Erg := Erg + '-x';
2 : Erg := Erg + 'x-';
end;
A := A DIV 3;
end;
for Loop := 1 to 8 do if (Erg[Loop] = 'x')
then Erg[Loop] := Chr(Loop + Ord('0'));
end;
Adresse2DigitalSchalter := Erg;
end;
Function Adresse2DeltaSchalter(Adresse : Byte) : String4;
{Setzt eine Lokadresse um in die Schalterstellungsfolge des Deltamoduls 6603.}
{Ternäre Speicherung der Adresse, beim Delta-Modul fallen gegenüber dem}
Var S : String8; {Digitaldecoder aber die "geraden" Schalter weg.}
Loop : Byte; {Adresse 0: alles auf "off" -> konv. Wechselstrom}
begin
if (Adresse = DeltaKonventionellAdresse) then S := '--------'
else S := Adresse2DigitalSchalter(Adresse);
S := S[1] + S[3] + S[5] + S[7];
for Loop := 1 to 4 do
if (S[Loop] <> '-') then S[Loop] := Chr(Loop + Ord('0'));
Adresse2DeltaSchalter := Copy(S,1,4);
end;
Function IsDigitalKranAdresse(Adresse : Byte) : Boolean;
begin {voreingestellt ist bei den Digitalkränen Adresse 30}
IsDigitalKranAdresse := (Adresse in [1,3,4,9,10,12,13,27,28,30,31,36,37,39,40,80];
end;
Function DeltaSchalter2Adresse(S : String4) : Byte;
Var DigitalS : String8;
Loop : Byte;
begin
if (S = '----')
then DeltaSchalter2Adresse := DeltaKonventionellAdresse
else
begin
DigitalS := '--------';
for Loop := 1 to 4 do DigitalS[Pred(Loop * 2)] := S[Loop];
DeltaSchalter2Adresse := DigitalSchalter2Adresse(DigitalS);
end;
end;
Function Adresse2DeltaText(Adresse : Byte) : TString;
begin
case Adresse of
0 : Adresse2DeltaText := 'konv. Wechselstrom';
24 : Adresse2DeltaText := 'Delta-Elektrolok';
60 : Adresse2DeltaText := 'Delta-Triebwagen';
72 : Adresse2DeltaText := 'Delta-Diesellok';
78 : Adresse2DeltaText := 'Delta-Dampflok';
80 : Adresse2DeltaText := 'Delta-Handregler';
2,6,8,18,20,26,54,56,62,
74 : Adresse2DeltaText := 'Delta-Modul 6603';
else Adresse2DeltaText := '';
end;
end;
Function IsDeltaAdresse(Adresse : Byte) : Boolean;
begin
IsDeltaAdresse := (Adresse in [0,2,6,8,18,20,24,26,54,56,60,62,72,74,78,80]);
end;
3.3. Allgemeine Digital-Prozeduren
Procedure SetComPort(Neu : Byte);
begin {überprüft auf gültige Werte}
if (Neu in [1..RS232.ComPorts]) then ComPort := Neu
end;
Procedure SetMagnetDelay(MilliSekunden : Word);
begin
if (MilliSekunden <= 1000) then {nicht länger als 1sec!}
MagnetDelay := MilliSekunden
end;
Function GetMagnetDelay : Word; {in Millisekunden}
begin
GetMagnetDelay := MagnetDelay;
end;
Das Interface 6050/6051 erfordert als Übertragungsparameter eine Baudrate von 2400 Bit/s, 1 Startbit, 2 Stoppbits, 8 Datenbits und keine Paritätsüberprüfung (siehe [1, Seite 62], [3, Seite 178] und [9, Seite 7]).
Procedure DigitalInterfaceInit;
Var TimeOut : Word;
begin
RS232.InitCom(ComPort, {globale "Const" dieser Unit, default COM1:}
2400, {Baud}
8, {Datenbits}
'N', {= no Parity}
2); {Stopbits}
TimeOut := 0;
repeat Inc(TimeOut) until RS232.ComReady(ComPort)
or (TimeOut > TimeOutLimit);
RS232.SendChar(ComPort,Chr(32)); {alle Magnetartikel ausschalten}
PortError := (TimeOut > TimeOutLimit);
end;
Nothalt und Freigabe erfordern nur das Senden eines Bytes:
Procedure NotHalt;
begin
RS232.SendChar(ComPort,Chr(97));
{nun liegt am Com-Port ständig "not CTS"}
end;
Procedure Freigabe;
begin
{egal, ob der Com-Port "CTS" ist oder nicht}
RS232.SendChar(ComPort,Chr(96));
end;
3.4. Loksteuerung
Zur Steuerung der Lokomotiven müssen immer Geschwindigkeit und Lokfunktion gesendet werden; entsprechend wird die Prozedur LokBefehl() mit drei Parametern aufgerufen: Digitaladresse der Lok, Geschwindgkeit und Status der Lokfunktion:
Procedure LokBefehl(LokNr, Geschwindigkeit : Byte; MitFunktion : Boolean);
Var TimeOut : Word;
begin
TimeOut := 0;
if (LokNr in [LokAdresseMin..LokAdresseMax]) then
begin
repeat Inc(TimeOut) until RS232.ComReady(ComPort)
or (TimeOut > TimeOutLimit);
PortError := (TimeOut > TimeOutLimit);
if not PortError then
begin
if MitFunktion then RS232.SendChar(ComPort,
Chr((Geschwindigkeit AND Vturn)+ 16))
else RS232.SendChar(ComPort,
Chr(Geschwindigkeit AND Vturn));
TimeOut := 0;
repeat Inc(TimeOut) until RS232.ComReady(ComPort)
or (TimeOut > TimeOutLimit);
PortError := (TimeOut > TimeOutLimit);
{nun aber in jedem Fall}
RS232.SendChar(ComPort,Chr(LokNr));
end;
end;
end;
Procedure LokHalt(LokNr : Byte; MitFunktion : Boolean);
begin
LokBefehl(LokNr,Vstop,MitFunktion);
end;
Procedure LokUmschalten(LokNr : Byte; MitFunktion : Boolean);
begin
LokHalt(LokNr,MitFunktion);
Delay(150); {damit die Lok-Decoder den Befehl akzeptieren, s. [3, S. 121]}
LokBefehl(LokNr,Vturn,MitFunktion);
LokHalt(LokNr,MitFunktion); {sonst sendet die 6021 ständig Vturn}
end;
3.5. Funktionsmodellsteuerung
Ähnlich werden auch die Funktionsmodelle gesteuert, der gesamte Status aller 4 Funktionen muß gesendet werden. Das erste zu sendende Byte wird nach der Formel Byte := 64 + F1 + 2*F2 + 4*F3 + 8*F4 ermittelt (F1 bis F4 mit Wert 0, wenn Funktion ausgeschaltet, und mit Wert 1 bei eingeschalteter Funktion, siehe [3, Seite 125] und [9, Seite 8]):
Procedure FunktionsBefehl(FktNr : Byte; F1,F2,F3,F4 : Boolean);
Var TimeOut : Word; B : Byte;
begin
TimeOut := 0;
if (FktNr in [FModellAdresseMin..FModellAdresseMax]) then
begin
repeat Inc(TimeOut) until RS232.ComReady(ComPort)
or (TimeOut > TimeOutLimit);
PortError := (TimeOut > TimeOutLimit);
if not PortError then
begin
B := 64 + Ord(F1) + (2 * Ord(F2)) + (4 * Ord(F3))
+ (8 * Ord(F4)); {da Ord(TRUE) == 1}
RS232.SendChar(ComPort,Chr(B));
TimeOut := 0;
repeat Inc(TimeOut) until RS232.ComReady(ComPort)
or (TimeOut > TimeOutLimit);
PortError := (TimeOut > TimeOutLimit);
{nun aber in jedem Fall}
RS232.SendChar(ComPort,Chr(FktNr));
end;
end;
end;
Beim Digitalkran 7651 bzw. Umbausatz 7652 sind nur zwei Funktionen möglich: F1 eingeschaltet bedeutet "Kran drehen", F2 eingeschaltet bedeutet "Last heben/senken".
3.6. Magnetartikel schalten
Für das Ausschalten von Magnetartikeln gibt es einen einheitlichen Befehl (Senden des Bytes 32), ein Magnetartikel wird aber auch durch das Einschalten eines anderen Magnetartikels ausgeschaltet. Daher werden z.B. bei einer Fahrstraßenschaltung nacheinander die erforderlichen Einschaltbefehle gesendet (Byte 33 für rote Schalt-stellung, Byte 34 für grüne Schaltstellung) und nur am Ende ist das Senden von Byte 32 erforderlich. Die Schaltdauer des einzelnen Magnetartikels ist abhängig von dem Zeit-abstand zwischen dem Senden der einzelnen Befehle. Im folgenden ist daher im Unterprogramm MagnetAus eine Warteschleife eingebaut (Delay() aus der Unit Crt). Den zeitlichen Abstand zwischen einzelnen Aufrufen des Unterprogramms Magnet-Befehl() muß dagegen das aufrufende Hauptprogramm übernehmen (ebenso wie das individuelle Setzen der globalen Variablen MagnetDelay).
Procedure MagnetAus;
{mit dem Senden von Byte 32 werden alle Magnetartikel ausgeschaltet}
Var TimeOut : Word;
begin
Delay(MagnetDelay); (* Maerklin empfiehlt 150ms *)
TimeOut := 0;
repeat Inc(TimeOut) until RS232.ComReady(ComPort)
or (TimeOut > TimeOutLimit);
{in jedem Fall versuchen: alle Magnetartikel ausschalten}
RS232.SendChar(ComPort,Chr(32));
PortError := (TimeOut > TimeOutLimit);
end;
Procedure MagnetBefehl(ArtikelNr : Byte; Befehl : Byte);
Var TimeOut : Word;
begin
TimeOut := 0;
repeat Inc(TimeOut) until RS232.ComReady(ComPort)
or (TimeOut > TimeOutLimit);
PortError := (TimeOut > TimeOutLimit);
if not PortError then
begin
RS232.SendChar(ComPort,Chr(Befehl));
TimeOut := 0;
repeat Inc(TimeOut) until RS232.ComReady(ComPort)
or (TimeOut > TimeOutLimit);
PortError := (TimeOut > TimeOutLimit);
{in jedem Fall versuchen: Befehl vervollständigen}
RS232.SendChar(ComPort,
Chr(ArtikelNr MOD MagnetAdresseMax));
{Magnetartikel-Nr 256 wird als Adresse 0 gesendet}
end;
end;
Procedure MagnetGruen(ArtikelNr : Byte);
begin
MagnetBefehl(ArtikelNr, 33) (*korr*2004*)
end;
Procedure MagnetRot(ArtikelNr : Byte);
begin
MagnetBefehl(ArtikelNr, 34) (*korr*2004*)
end;
3.7. Rückmeldemodule auswerten
Das Auswerten von Rückmeldemodulen erfordert nicht nur das Senden, sondern auch das Einlesen von Bytes über die serielle Schnittstelle. Nach dem Auslesen des Status eines Rückmeldemoduls wird vom Digitalsystem das Rückmeldemodul zurückgesetzt, die angeforderten Informationen muß sich also das Computerprogramm in passender Weise entweder merken oder aber Informationsverluste in Kauf nehmen. Mit dem Senden von Byte 128 an das Interface kann dieses Rücksetzen ausgeschaltet werden (z.B. sinnvoll bei Gleisbesetzt-Anzeigen, wenn sich das Computerprogramm nicht alles selber merken will), und durch Senden von Byte 192 wird der Ausgangszustand wieder hergestellt.
Procedure RueckmeldemoduleRuecksetzen(Ausschalten : Boolean);
{Voreingestellt ist, daß die Rückmeldemodule nach jedem Auslesen}
{der Werte zurück gesetzt werden; dies läßt sich mit dem Senden}
{von Byte 128 ausschalten und mit dem Senden von Byte 192 wieder}
{einschalten.}
Var TimeOut : Word;
begin
TimeOut := 0;
repeat Inc(TimeOut) until RS232.ComReady(ComPort)
or (TimeOut > TimeOutLimit);
PortError := (TimeOut > TimeOutLimit);
if not PortError then
begin
if Ausschalten then RS232.SendChar(ComPort,Chr(128))
else RS232.SendChar(ComPort,Chr(192)); {Einschalten, ist Default}
end;
end;
Die Rückmeldemodule werden durchgezählt von 1 bis 31, jedes hat 16 Eingänge, sodaß 496 einbittige Zustände abgefragt werden können. Auf das Senden von Byte "192 + Modul-Nummer" liefert dann das Interface den Status von allen 16 Eingängen dieses Moduls in zwei Bytes zurück (siehe [1, Seite 93] und [9, Seite 9]). Wird stattdessen Byte "128 + Modul-Nummer" gesendet, so werden die Stati aller Module von Nr. 1 bis zum Modul "Modul-Nummer" zurückgeliefert (Fehler in der 1. Aufl.age von [1, Seite 73]). Um auf Nummer sicher zu gehen, empfiehlt es sich, jedes Modul einzeln auszuwerten, und zwar zunächst das gültige Byte senden, und dann auf hereinkommende Bytes warten, bis keine mehr kommen. Die letzten beiden empfangenen Bytes sind auf jeden Fall die gesuchten Werte.
Function RueckmeldemodulLesen(ModulNr : Byte) : Word;
{Ein Rückmeldemodul (sie werden gezählt von 1 bis 31) hat 16 Eingänge.}
{Das Senden von Byte "192 + ModulNr")}
{liefert den Zustand dieser Eingänge in zwei aufeinanderfolgenden Bytes.}
{Diese Funktion liefert das Ergebnis in einem Word (16 Bit) zurück.}
Var TimeOut : Word;
InByte1, InByte2 : Char; {da die Schnittstelle nur Bytes liefert}
begin
TimeOut := 0;
repeat Inc(TimeOut) until RS232.ComReady(ComPort)
or (TimeOut > TimeOutLimit);
PortError := (TimeOut > TimeOutLimit);
if PortError then RueckmeldemodulLesen := 0 {Fehler}
else
begin {noch vorhandenen Datenmüll in der Leitung entfernen}
while RS232.CharThere(ComPort)
do InByte1 := RS232.GetChar(ComPort);
InByte2 := Chr(0);
TimeOut := 0;
RS232.SendChar(ComPort, Chr(192 + ModulNr)); {abfragen}
while RS232.CharThere(ComPort)
or (TimeOut < TimeOutLimit) do {lesen}
begin
if RS232.CharThere(ComPort) then
begin
InByte1 := InByte2;
InByte2 := RS232.GetChar(ComPort);
TimeOut := 0;
end
else Inc(TimeOut);
end; {die letzten 2 eingelesenen Bytes stammen vom gesuchten Modul}
RueckmeldemodulLesen := (Ord(InByte1) * 256) + Ord(InByte2);
end;
end;
Function TestRueckmeldemodul(ModulNr : Byte; Eingang : Byte) : Boolean;
{liefert TRUE, wenn an Modul "ModulNr" der Eingang "Eingang" gesetzt ist}
begin {"Eingang" gezählt von 1 bis 16 !}
ModulStatus := RueckmeldemodulLesen(ModulNr);
TestRueckmeldemodul := Odd(ModulStatus SHR Pred(Eingang))
end;
Function TestRueckmeldeEingang(Eingang : Word) : Boolean;
{die Eingänge kann man auch von 1 bis 496 durchzählen}
begin
if (Eingang >= RueckmeldeAdresseMin)
and(Eingang <= RueckmeldeAdresseMax) then
TestRueckmeldeEingang :=
TestRueckmeldemodul(Succ(Pred(Eingang) DIV 16),
Succ(Pred(Eingang) MOD 16))
else TestRueckmeldeEingang := FALSE;
end;
end. {kein Initialisierungsteil der Unit}
Anhang
A. Übersicht über die Hauptprogrammstruktur
TP-Units Basis-Units spezielle Units
_____ _____
| |---[UpCase(),ParamCount,ParamStr()]------->| |
| | __________ | |
| S |---[UpCase(),FillChar()]-->| | | H |
| | _____ | | | A |
| | | | | | | U |
| Y | | Dos |---[GetTime()]-->| ScreenIO |--->| P |
| | |_____| | | | T |
| | _____ | | | P |
| S | | |---------------->|__________| | R |
| | | | __________ | O |
| | | |----[Delay()]--->| | | G |
| T | | | | | | R |
| | | | _______ | | | A |
| | | | | | | Digital |--->| M |
| E | | Crt | | RS232 |---->| | | M |
| | | | |_______| |__________| | |
| | | | __________ | |
| M | | |---[GotoXY()]--->| | | |
| | | |[WhereX,WhereY]->| BigText1 |--->| |
| | | |---[write()]---->|__________| | |
| | | | __________ | |
| | |_____|---------------->| | | |
| | | RollMenu |--->| |
|_____|----[FillChar()]---------->|__________| |_____|
B. Die Bedienung des Hauptprogramms
Bei Aufruf des Programms können ihm Parameter mitgegeben werden. Auf den Kommandozeilenparameter "?" (bzw, "/?" oder "-?") reagiert das Programm nach Ausgabe eines erläuternden Textes mit sofortigem Ende. Als weitere Kommandozei-lenparameter sind Angaben bezüglich der zu verwendenden seriellen Schnittstelle (COM1:, COM2:, COM3: oder COM4:) zulässig.
Nach Auswertung der Kommandozeilenparameter und Initialisierung globaler Variablen erfolgt das Einladen von Lokdaten, Fahrplänen und den Daten des Gleisbildstellwerkes, so wie sie in einer vorherigen Sitzung des Programms angelegt wurden. Danach wird der Bildschirm vorbereitet: in der obersten Bildschirmzeile erscheint eine Menüzeile, die folgende Befehle (ausführbar mit einem Tastendruck) ermöglicht:
S Alles stoppen, an alle verfügbaren Lokomotiven wird der Befehl für Geschwindigkeit "0" gesandt.
N Nothalt-Befehl an das Interface senden. Falls Nothalt bereits vorliegt: Freigabe-Befehl an das Interface senden.
Q Quit, Beenden des Programms.
I Sichern der Lokdaten, Fahrpläne und GBS-Daten am Programmende im aktuellen Verzeichnis auf dem aktuellen Laufwerk in den Dateien LOKDATEI, LOKPROGS und GBSDATEI.
M Einblenden eines Pulldown-Menüs, in dem weitere Befehle präsentiert werden. Beenden dieses Menüs mit der Escape-Taste.
W Bei EGA- oder VGA-Karten Anzeige des Gleisbildstellwerkes in den Bildschirmzeilen 26 bis 50 (bzw. 43).
Waren bereits Lokdaten gespeichert, werden maximal bis zu 15 "Loksteuerpulte" angezeigt, zwischen denen der Benutzer mit der Tabulator-Taste (bzw. Umschalttaste + Tabulator-Taste) hin- und herschalten kann. Das jeweils aktuelle "Loksteuerpult" ist farblich hervorgehoben und akzeptiert folgende Befehle (jeweils ein Tastendruck):
F Fahrtstufe um 1 erhöhen.
B Bremsen, Fahrstufe um 1 vermindern.
U Umschalten der Fahrtrichtung. dabei keine Änderung der Fahrtstufe!
0, H Halt, d.h. Fahrstufe 0.
L Licht an-/ausschalten (entspricht Magnet an/aus beim Digitalkran).
T Telex betätigen.
P Fahrplan dieser Lok starten bzw. unterbrechen bzw. bei unterbroche-nem Fahrplan fortsetzen. Einen Fahrplan ganz beenden erfolgt über den Pulldown-Menüpunkt "Fahr-Programm AUS".
D Modus des Digitalkrans einstellen auf: Drehen.
A Modus des Digitalkrans einstellen auf: Last auf-/absenken.
1..4 bei Funktionsmodellen oder Spur-1-Digitalloks mit erweitertem Funktionsdecoder: an-/ausschalten der Funktion mit der jeweiligen Nummer. Bei (anderen) Loks: einstellen der Fahrstufe 1 bis 4.
5..9 Bei Lok bzw. Kran: einstellen der jeweiligen Fahrstufe.
Wird die Taste "W" gedrückt, erscheint (nur bei EGA- und VGA-Karten) im unteren Teil des Bildschirms (bei verkleinerter Darstellung) das Gleisbildstellwerk. Wurden noch keine GBS-Elemente eingegeben, so ist die Fläche leer. Es erscheint dann ledig-lich ein "Cursor" (4 Zeichen groß, farblich hervorgehoben), der die aktuelle Position im GBS anzeigt. Dieser "Cursor" kann mit den Pfeiltasten verschoben werden. Jedes GBS-Element besteht aus einer 2 mal 2 Zeichen großen Box und repräsentiert bis zu 3 verschiedene Schaltstellungs-Programme (Rot, Grün, Zusatz). Die jeweils aktuelle GBS-Position akzeptiert folgende Befehle mit einem Tastendruck:
R Schaltstellung ROT. Es wird das zugrunde liegende Weichenpro-gramm abgearbeitet.
G Schaltstellung GRÜN (-> Weichenprogramm wird abgearbeitet).
Z Zusätzliches Schaltstellungsprogramm (z.B. für Dreiwegeweichen) wird abgearbeitet.
- Minuszeichen: es wird ein funktionsloses GBS-Element an dieser Stelle eingefügt mit horizontalem Schienenverlauf.
/ Schrägstrich: einfügen eines GBS-Elements mit Schienenverlauf schräg nach oben.
\ umgekehrter Schrägstrich: einfügen eines GBS-Elements mit Schie-nenverlauf schräg nach unten.
Delete Löschen des GBS-Elements.
Es stehen also immer alle Tastaturkommandos zur Verfügung, solange nicht über die Taste "M" das Pulldown-Menü aktiviert wird. Die Auswahlpunkte dieses Menüs werden mit der Return-Taste angewählt und sind:
PC-Schnittstelle COMx: Änderung der seriellen Schnittstelle, an der das Interface angeschlossen ist.
Fahrzeug-Wechsel das aktuelle "Loksteuerpult" wird mit einem anderen Fahrzeug belegt, welches aus einem weiteren Pulldown-Menü ausgewählt werden muß. Abbruch dieses Menüs ebenfalls mit Escape-Taste möglich.
Neues Fahrzeug einfügen es werden (unkomfortabel) Daten eines neu aufzunehmenden Fahrzeugs (Lok, Kran etc.) abgefragt. Anschließend erscheint dieses Fahrzeug in einem eigenen "Loksteuerpult", sofern noch nicht alle mög-lichen 15 Plätze vergeben wurden. Sonst muß hinter-her der vorherige Menüpunkt noch benutzt werden, um dieses neue Fahrzeug auch steuern zu können.
Doppel-/Mehrfachtraktion mit dem aktuellen "Loksteuerpult" können neben dem primären Fahrzeug noch weitere Fahrzeuge verknüpft werden, wenn sie vom ähnlichen Typ sind. Die Tastatur-Kommandos werden dann sowohl an das primäre Fahrzeug als auch an alle weiteren damit verketteten Fahrzeuge weiter gesendet. Der Name des ersten mitverketteten Fahrzeugs wird im "Loksteuerpult" mit angezeigt (sofern Platz ist; Beschränkungen von sinnvoller Doppeltraktion auf Fahrzeuge mit gleichem Getriebe siehe auch [3, Seite 121]).
Mehrfachtraktion AUS die mit dem aktuellen "Loksteuerpult" verbundenen Verkettungen zu weiteren Fahrzeugen werden aufgelöst ("abkuppeln"). Die Tastatur-Befehle gelten nun wieder nur für das im "Loksteuerpult" namentlich angezeigte primäre Fahrzeug.
Fahrprogramm ändern zu jedem Fahrzeug kann ein eigener "Fahrplan", im weiteren als Fahrprogramm bezeichnet, zur automatischen zeitabhängigen Steuerung erstellt werden (ähnlich wie ein BASIC-Programm mit Zeilennummern und u.a. mit Auswertung von Rückmeldemodulen und als Endlosschleife möglich). Über diesen Menüpunkt kann das Fahrprogramm des aktuellen "Loksteuerpults" verändert werden. Dazu erscheint ein eigenes Menü, das die möglichen Kommandos auch mit erläutert. Abbruch dieses Menüs mit Escape.
Fahrprogramm AUS das im "Loksteuerpult" aktivierte Fahrprogramm wird gestoppt und die zugehörige Variable Programm-Position (aus dem Record FahrzeugType) auf 0 gesetzt. Das nächste Drücken der Taste "P" zum Starten des Fahrprogramms läßt dieses wieder von vorne beginnen. Einzige Möglichkeit, um Fahrprogramm-Endlosschleifen komplett zurückzusetzen.
Nothaltfreigabe (Kurzschluß) nach einem Kurzschluß weigert sich die Zentraleinheit von Märklin-Digital, weitere Befehle auszuführen, solange nicht "Freigabe" gedrückt bzw. vom Interface das entsprechende Byte 96 gesendet wurde (siehe [3, Seite 66]).
Digitalschalter-Rechner ähnlich einem Taschenrechner kann über diesen Menüpunkt ein kleiner Rechner aktiviert werden, mit dem bequem eine Digitaladresse in die entsprechende Decoder-Schalterstellung umgerechnet werden kann und umgekehrt. Dieser Rechner arbeitet in 2 Modi: über "A" im Adressenmodus: zweistellige Adresse ein-tippen (Ziffern 0 bis 9); und über "O" im Schaltermodus: die Ziffern 1 - 8 setzen/löschen den entsprechen-den Schalter, die zugehörige Adresse wird angezeigt.
Deltaschalter-Rechner analog zu oben ein Rechner für die Schalterstellungen des Märklin-Delta Decoders 6603.
GBS-Element ändern hier verbirgt sich ein weiteres Menü, über welches das aktuelle GBS-Element in seinem Aussehen und die zugeordneten Schaltstellungs-Programme verändert werden können.
Programmende eine weitere Möglichkeit, das Programm zu beenden.
C. Literatur
[1] Bader, Gerhard: Alles über Digital-Modellbahn-Steuerungen: eine Publikation in Zusammenarbeit mit Gebr. Märklin & Cie GmbH. Vogel Verlag, Würzburg 1989 (Chip special).
[2] Bader, Gerhard: Computer steuert Märklin-Modellbahn. Vogel Verlag, Würzburg 1987 (Chip special).
[3] Einstieg in Märklin Digital - die Mehrzugsteuerung, Märklin Artikel-Nr. 0308. Gebr. Märklin & Cie GmbH, Göppingen 1994.
[4] Fischer, Manfred G.: PC-BIOS Zugriffe mit Turbo Pascal. Editha Fischer Verlag, Münster 1990.
[5] Horn, Wolfgang: Die Modellbahn. 6. Modellbahn und Computer: Modell-bahnsteuerung mit dem C64. Franckh Verlag, Stuttgart.
[6] Interface Computeranschluß. In: Club News Märklin-Insider 4/93, Seite 4, Gebr. Märklin & Cie GmbH, Göppingen, Juni 1993.
[7] Jensen, Kathleen, Wirth, Niklaus: PASCAL: user manual and report, Springer-Verlag, Berlin Heidelberg New York 1974.
[8] Koll, Joachim: Koll's Preiskatalog 1992, Band 1, Triebfahrzeuge, Seiten 74 bis 117. Verlag Joachim Koll, Bad Homburg 1991 (14. Aufl.).
[9] Märklin Digital mit Computeranschluß (Hardware-Dokumentation), Handbuch und Demodiskette (Märklin Artikel-Nr. 66918) zum Märklin Digital Interface 6051. Gebr. Märklin & Cie GmbH, Göppingen 1994.
[10] Mayer, Frank: Das Memory, (k)ein Hexenwerk, Teil 1. In: Märklin Magazin 5/90, Seiten 8 bis 10. Modellbahnen-Welt Verlags-GmbH, Göppingen, Oktober 1990.
[11] Mayer, Frank: Das Memory, (k)ein Hexenwerk, Teil 2. In: Märklin Magazin 6/90, Seiten 10 bis 13. Modellbahnen-Welt Verlags-GmbH, Göppingen, Dezember 1990.
[12] Mayer, Frank: Das Memory, (k)ein Hexenwerk, Teil 3. In: Märklin Magazin 1/91, Seiten 24 bis 27. Modellbahnen-Welt Verlags-GmbH, Göppingen, Februar 1991.
[13] Mayer, Frank: Märklin Digital, Zug um Zug, Folge 1. In: Märklin Magazin 2/91, Seiten 38 bis 41. Modellbahnen-Welt Verlags-GmbH, Göppingen, April 1991.
[14] Mayer, Frank: Märklin Digital, Zug um Zug, Folge 2. In: Märklin Magazin 3/91, Seiten 16 bis 20. Modellbahnen-Welt Verlags-GmbH, Göppingen, Juni 1991.
[15] Mayer, Frank: Märklin Digital, Zug um Zug, Folge 3. In: Märklin Magazin 4/91, Seiten 17 bis 19. Modellbahnen-Welt Verlags-GmbH, Göppingen, August 1991.
[16] Mayer, Frank: Märklin Digital, Zug um Zug, Folge 4: Computerprogramme für die Modellbahn. In: Märklin Magazin 5/91, Seiten 8 bis 11. Modellbahnen-Welt Verlags-GmbH, Göppingen, Oktober 1991.
[17] Mayer, Frank: Märklin Digital, Zug um Zug, Folge 5. In: Märklin Magazin 6/91, Seiten 12 bis 14. Modellbahnen-Welt Verlags-GmbH, Göppingen, Dezember 1991.
[18] Meyer, Martin: Loksteuerung unter DOS. In: Märklin Magazin 5/90, Seiten 11 bis 13. Modellbahnen-Welt Verlags-GmbH, Göppingen, Oktober 1990.
[19] Modellbahnsteuerungen, MIBA Spezial 1, 41. Jahrgang Sonderausgabe. MIBA-Verlag Werner Walter Weinstötter GmbH & Co, Nürnberg 1989.
[20] Modelleisenbahn digital gesteuert, Märklin Artikel-Nr. 0306. Gebr. Märklin & Cie GmbH, Göppingen 1987. (nicht mehr verfügbar).
[21] Schäpers, Arne: Turbo Pascal 4.0/5.0, Band 2, Seiten 267 bis 315. Addison-Wesley Verlag, Bonn 1989.
[22] Schneider, Hans Lorenz: Märklin Digital H0 mit dem Commodore 64. Schneider Verlag, München.
[23] Stiller, Andreas: PC-BIOS-Variable. In: c't 6/1989, Seiten 247 bis 250. Heise Verlag, Hannover, Juni 1989.
[24] Turbo Pascal 3.0 Handbuch (Software-Dokumentation), Herausgeber: Heimsoeth Software GmbH & Co., München 1985.
[25] Turbo Pascal 4.0, Band 2: Referenzhandbuch (Teil der Software-Dokumentation), Übertragung ins Deutsche: Arne Schäpers. Herausgeber: Heimsoeth Software GmbH & Co., München 1987.
[26] Turbo Pascal 7.0 Programmierhandbuch (Teil der Software-Dokumentation), übertragen ins Deutsche von Arne Schäpers und text und form, München. Herausgeber: Borland GmbH, Langen 1992.
[27] Turbo Pascal 7.0 Referenzhandbuch (Teil der Software-Dokumentation), übertragen ins Deutsche von Arne Schäpers und text und form, München. Herausgeber: Borland GmbH, Langen 1992.