Digitale Modellbahnsteuerung in TurboPascal (1995)

 

 

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]:

Steckerverbindung PC-AT DB9 - Märklin Interface

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}

Var ModulStatus : Word;

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.