Application Programming Interface
and Driver
for Digital Modelrailroads

Version 2.3



Guide to Application Programming with DigiAPI

Rob Hamerling
Vianen, The Netherlands

E-mail: r.hamerling@planet.nl
homepage: http://www.geocities.com/digithalys/

Copyright © R. Hamerling, 1991, 2000
All Rights Reserved.


Table of Contents

Appendix

Related publications


Functional description

This chapter describes a possible use of DigiAPI.

Overview

The DigiAPI package is a set of functions to control a 'digital' modelrailroad by programs written in C-language or BASIC for the Personal Computers family.

It comes in 2 flavours:

  • For OS/2 in the form of a Dynamic Link Library (DLL).
  • For DOS in the form of a LINK library (LIB file) Note: It can only be used with compiled programs. QBASIC that comes with DOS is not capable of producing an .OBJ file, you'll need a BASIC compiler to use this driver with BASIC programs.

    Terminology

    In this document a number of terms are used which should be clear to the reader, independent of the system being used.

    Interface
    Märklin: 6050 or 6051
    Lenz: Interface LI100
    Trix: Interface 60824 or 60842
    Feedback encoder
    Märklin: S88 (6088)
    Lenz: LR100 or LS110
    Trix: Function decoder 66821
    Switchpoint or Signal decoder
    Märklin: K83 (6083), K84 (6084)
    Lenz: LS100 or LS100
    Trix: Feedback encoder 66820

    Functional Requirements

    The driver must provide the following functionality:

    Provided Facilities

    To fulfill the functional requirements, a 'driver' has been built. It contains a number of service-routines (and for DOS also hardware interrupt service routines), and provides the following facilities

    Deliverables

    The shareware components of the DigiAPI package are:

    The registration package consists of:

    Demo

    The 'demo' version has some limitations for not-registered users:

    Of course these limitations disappear after installing the key file which you receive after registration.

    Prerequisites

    The package has been built with the following assumptions

    Restrictions and Limitations

    The following restrictions apply:

    Tests

    The drivers have been tested by the author with combinations of the following hardware and software

    NOTE

  • Not all combinations of hardware and software have been tested, but it is not likely that any viable combination will not work.
  • C-language framework

    A typical model railroad control program with this driver may look like

    
         #include "DigiAPI.h"
    
         HFILE  CHandle;                    /* I/O handle */
         char   key;                        /* keyboard input */
    
         CHandle = DigiOpen("COM1", PROTOCOL_MAERKLIN, 0); /* start */
         DigiEnableFeedback(CHandle, 64);  /* monitoring feedback encoders */
         while (key != ESC) {
           if (kbhit()) {                   /* keyboard input */
             if (key = getch()) {           /* normal key (incl alt-numpad) */
               switch(key) {
                 case '?': - - -            /* help function */
                           break;
                 case ESC: break;           /* nothing (termination pending) */
                 default:  printf("%c", BEL);  /* unsupported key */
                           break;
               }
             else {                         /* 'special' key */
               switch(key = getch()) {      /* extended character */
                 case PgDn: - - -           /* your 'extended' key */
                           break;
                 default:  printf("%c", BEL);  /* unsupported key */
                           break;
               }
             }
           for (i=0; i<MAXTRAIN; i++) {  /* scan train matrix */
             - - - - -                      /* train control function */
             }
           for (i=1; i<10, ++i) {        /* obtain feedback info */
             DigiQueryFeedback(CHandle, i);  /* read feedback contact bits */
             - - - - - -                    /* process changes */
             }
           if (!(DigiTime()%5000)) {        /* every 5 seconds */
             - - - - -                      /* anything else */
             }
           DigiSleep(25);                   /* give up remainder of timeslice */
           }
         DigiClose(CHandle);                /* terminate */
    
    

    NOTE:

    QuickBASIC framework

    An equivalent framework in QuickBASIC might look like

    
         ' $INCLUDE 'DigiAPI.INC'
    
         DIM Port as string * 6
         Port = "COM1"
    
         CHandle = DigiOpen(varptr(Port), VarSeg(Port), PROTOCOLMAERKLIN, 0);
         DigiEnableFeedback(CHandle, 64);
         key$ = INKEY$
         while key$ <> ESC$
           select case key$
             case '?'
                - - - - -
             case ESC
                - - - - -
             case else
               print "unsupported key"
           end select
           for i=0 to MAXTRAIN
             - - - - -
           next i
           for i=1 to 10
             DigiQueryFeedback(CHandle, i)
             - - - - - -
           next i
           if MOD(DigiTime,5000) < 50 then
             - - - - -
           end if
           DigiSleep(25)
           key$ = INKEY$
         wend
         DigiClose(CHandle);
    
    

    NOTES:This code may not be true and workable QuickBASIC!

    The file DigiAPI.INC contains the DECLARE lines for all functions contained in LDigiAPI.LIB. It should be included in a QuickBASIC program to obtain the required calling conventions and activates syntax checking by the . Subsequently the file LDigiAPI.LIB should be specified as library to the LINK utility to include the DigiAPI functions.

    Sample programs

    Two sample programs are included in the DIGIAPI package. A C-program for loc speed calibration and a QuickBASIC program for some funtional tests.

    OS/2 and DOS differences

    The OS/2-drivers differ from the DOS drivers in several ways:

    The sample program under DOS chokes the processor (takes all the cycles that are available). This is OK in a typical single-task environment, but not very suitable for a multitasking system. Under OS/2 a very simple solution for this problem is to insert for example a DigiSleep(1) at the end of the infinite (keyboard-)loop. There may be better solutions.


    Design

    To make the Application Programming Interface independent of the model railroad system, a certain abstraction level has to be defined. And to support different 'digital' model railroad systems with all their specific requirements, a number of concepts have been implemented. These aspects are covered in this chapter to help you to understand its facilities and make optimal use of them.

    Functional Abstractions

    The following groups of 'management' functions are provided

    Concepts

    A number of terms are used in this document, of which it is important to understand these concepts within the scope of controlling a digital model railroad based on DigiAPI.

    Numbering and Addresses

    The manufacturers have no consistent numbering and addressing scheme, and frequently the number encoding in commands differs from the numbering used in the documentation. In many cases numbers and addresses start with '1' while the encoding in the actual command starts with '0. For accessory decoders frequently a completely different numbering is used because of the grouping of several decoder functions in a single decoder (4 switches can be controlled by 1 decoder).

    Since these differences may lead to confusion, DigiAPI uses a consistent the numbering schem by itself and translates these numbers to the manufacturer's schemes. Some examples:

    The manufacturer independent numbering (or addressing) scheme of DigiAPI deviates also in some aspects from the manufacturers numbering. For example:

    See Manufacturer Specific Information for the translation between a DigiAPI address and the address of the specific manufacturer's component.


    Facilities

    Overview of available Functions

    Table of user callable functions
    General Modelrailroad Management
    DigiOpen Start communication with the and the background tasks. This function should be the first to be called, since it includes initialisation code for some other routines. Calling another DigiAPI-function before a successful DigiOpen() may cause unpredicatable results!
    DigiClose Stop 'background' tasks and shutdown the communication with the Interface
    DigiFlush Wait until all data transmitted
    DigiPurge Discard remaining data in output buffer
    DigiQuerySystemState Obtain system state information
    Emergency Management
    DigiGo Set Interface in running ('go') state
    DigiStop Set Interface in 'stop'-state
    Train Management
    DigiSetTrainSpeed Control speed of a train
    DigiQueryTrainSpeed Obtain current speed of a train
    DigiSetTrainFunction Control setting of a function of train
    DigiQueryTrainFunction Obtain current setting of a train function
    Accessory Management
    DigiQueryAccessoryState Obtain current setting of a switch or signal
    DigiSetContactOn Set accessory contact ON
    DigiSetContactOff Set accessory contact OFF
    DigiSwitchAccessory Switch an accessory
    Feedback Management
    DigiEnableFeedback Start monitoring of a feedback contact
    DigiQueryFeedbackCount Obtain the count of 'on' switches since last time the counter was reset
    DigiQueryFeedbackState Obtain the current status of a feedback contact
    DigiQueryFeedbackTime Obtain the time since a feedback contact first became 'on'
    DigiSetFeedbackAction Register a task for feedback ection
    Program / Task Management
    DigiTime Give time since last DigiOpen() with milliseconds accuracy
    DigiSleep Pause for a certain time (and give up the remainder of a time slice when running in a multitasking environment)
    DigiSound Sound generator like BASIC sound or OS/2 DosBeep()
    Problem Management
    DigiTrace Start, resume or stop recording data that is being sent to (only in OS/2-drivers).
    Miscellaneous
    DigiQueryStatistics Provide current value of a statistics counter

    Hints and Tips

    This chapter gives some recommendations about the use of the functions provided by MKLAPI.

  • Timers
  • QuickBASIC
  • Errors and Signals

    Use of timer functions

    At program start with DigiOpen(), the system time is taken as base for the function DigiTime().

    This function will return a value in milliseconds since the last DigiOpen(). The function DigiTime() has an accuracy of 1 millisecond. For most model railroad purposes this gives more accuracy than normally needed.
    The use of this timer facility provided by the DigiAPI-driver has the following advantages:

    QuickBASIC Function Specifications

    The functionality of the -driver for QuickBASIC is the same as for the drivers for C-language. However the following should be noted:


    Installation

    Application development considerations

    This DigiAPI is a program development tool for application programs in any language, but samples are given for C-language and QuickBASIC. You need DigiAPI.H (DigiAPI.INC for BASIC) at compile-time and the DigiAPI.DLL or LDigiAPI.LIB files at LINK-time. These are additions to the compiler and other toolkits you may need with your program development.

    C-Compiler considerations

    The following compiler properties apply.

    Other compiler options are (probably) not of influence on the use of DigiAPI.

    QuickBASIC Considerations

    The QuickBASIC compiler is needed to create an object deck of your BASIC program. This OBJect file is combined with LDigiAPI.LIB file as provided by this DigiAPI package to build an EXE-file with the LINK utility.

    Building an EXE-file

    One of DigiAPI libraries should be available for the link-step with your own OBJect file(s). I prefer to Compile and LINK with the help of the NMAKE utility.

    C-language and DOS

    For the DOS sample application ("CALIBRA1") I use MakeFile.

    C-language and BASIC

    For a simple test-program in QuickBASIC I use batch-file Test.Bat.

    NOTE: With the LDigiAPI.LIB need to be specified with the link. models.

    C-language and OS/2 Warp.

    For the 32-bits OS/2-sample application ("CALIBRAT") I use this MakeFile.

    Storage and performance

    There is not much to say about storage and performance of the driver. It is highly optimised code, and probably only a minor part of your 'real' modelrailroad control program.

    Memory Utilisation

    It is difficult to say how many storage is occupied by the OS/2-version of the DigiAPI-routines.

    The DOS-version requires about 6K bytes of storage.

    Processor utilisation

    You might be curious about the cycles that will be consumed by the task that repeatedly reads feedback encoders. I've done some measurements, but found a maximum difference of about 10% between feedback monitoring enabled and disabled. This number is practically independent of the number of feedback encoders which are specified for monitoring!

    Comments, Remarks, Bug-reports

    If there are bugs, requirements or questions about this -driver, the documentation or the sample program (that you get when you register), please send me a message by E-mail. You can find my name and node-(point)-number in the introduction of this document.

    Enjoy and good luck with your program development! May this driver lead to a very attractive and successful model railroad control program!

    Do not 'forget' to register!


    Internals

    This appendix describes a number of design arguments for this driver and explains decisions where a choice had to be made.

    Multitasking

    There are many situations in real-time programming, where you want the system to do something for you, while in the meantime you would like to do something else in the mainline (before the first piece of program completes).

    An example of this is reading feedback encoders. While you have to wait for the response of the Interface, you might want to update your screen, read data from disk, or process keyboard input. In many sample programs that I have seen, the wait is frequently done in the form of a short loop (a for- or while-statement). This means that the processor is working, in fact consuming cycles, while you cannot do anything (unless you make a more 'complicated' loop). And in many cases you have to experiment with this loop to get the needed wait-interval (not too long and not too short). Running the same program on a machine with a different processor-speed, requires a change in the loop.

    A solution for this is to have multiple programs to run simultaneously. Executing multiple programs (tasks) in parallel is called multi-tasking. Since we are generally dealing with a single micro processor, the tasks are simultaneaously present in memory, but only 1 of them is actually processing. There must be some mechanism to switch the processor between the tasks.

    Some operating environments have standard facilities for this (OS/2, Windows), but DOS doesn't. Under DOS you have to do it yourself. By intercepting hardware interrupts, or the timer-tick interrupt it is possible to implement a kind of task-switching.

    The method of reading feedback encoders in DigiAPI is a form of multi-tasking. For OS/2 it uses the standard OS/2 facilities (called multi-threading), for DOS it uses the system timer to execute periodical actions independently from the main-program, and it uses also the hardware interrupts of the asynchronous communications port to perform some communications work. So in fact there are a few extra tasks to:

    Although you don't have to bother about the reading of feedback encoders, it does have some influence on the design of your railroad control program. These practical aspects are explained below.

    Reading feedback encoders

    The driver generates read-feedback-encoder commands if needed (such as for Märklin) in the following way:

    1. After monitoring of feedback is enabled periodically a read-feedback command is transmitted to read all feedback encoders on systems which need this type of feedback processign (such as Märklin and Trix).
    2. The receive-part of the communications task is signalled that a response from the Interface is expected. The receive-task waits as long as not all expected input is received, or an unexpected long time had to be waited.
    3. After processing the response, the system waits some time before issuing a new command to read feedback information (typically 100 msec).
    4. Even if for some reason the previous receive did not finish within reasonable time (typically 3000 msec), a new read-command is issued.
    5. Commands for monitoring are automatically merged into the stream of control commands (to locs and switches). It is important that both types of commands will not monopolize the datastream to the Interface! DigiAPI manages tthe regulation of both streams.
    From 'life'-tests I have concluded:

    Reading feedback concact encoders is programmed as follows:

    1. When enabling feedback monitoring, and if needed (e.g. Märklin, Trix), a command is issued for 'destructive' reads (read with reset).
    2. Periodically read commands are transmitted.
    3. Spontaneous feedback information (Lenz) is processed as well.

      Feedback contact data processing
    Organisation of processing feedback signals

    In this sample a group-read of 6 Märklin S88-boxes is used.
    1. Collection of 'raw' data from the Interface. Array-1 always represents the current switch positions of all monitored feedback contacts, apart from a slight delay between consecutive reads.
    2. Cumulative ON-bits since most recent DigiQueryFeedback() for each specific feedback contact in Array-2.

    The incoming data of a contact is stored in array 1, and all 1-bits are copied immediately to array 2 with an 'or'-operation. So array 2 is a cumulation of ON status since the last time the contact (group) was queried form the application program.

    When DigiQueryFeedback(x) is executed, the contents of group x in array 2 are returned to the caller. At the same time group x in array 2 is refreshed with the then current contents of array 1. So a second DigiQueryFeedback() immediately after the first gives the actual status.
    Function DigiQueryFeedback might be changed later to be able to specify that you need data from the first or from the second array. A second call is required if you want to know for sure if the contact is currently in OFF after it has been ON since the most recent DigiQueryFeedback().

    After feedback monitoring is started, the first data to be received is the current status of the Interface. This may not be the actual status. To be sure to get the actual status, wait some time after poll-start (more than 100 msecs) before expecting to get the actual status!

    This mechanism is suitable for all types of switches that are applied in model-railroads, and ensures that you won't miss any 'switch-on' condition, even if it is 'on' during a very short period. This organisation is illustrated in the figure above.

    You have to be careful with specifying a higher value in DigiEnableFeedback() than you really have contacts (and encoders) on your model railroad.

    So specifying more feedback contacts than area really active will unnecessary downgrade the responsiveness of your program. You might decide to suspend monitoring feedback during processor-intensive work, or not to use reading feedback at all.

    DigiAPI reads always a range of feedback encoders. The range is determined from the lower an upper bound of the enable contacts. When intermediate contacts are not enabled, the encoders, even without any 'activated' contacts, are read by DigiAPI.

    Communications Interface Specification

    The contains a device driver level communication facility with between computer and , but with a rather high level interface for the application programmer. It contains many basic functions, that otherwise have to be programmed yourself. However, it enforces a specific organisation of your program and has some limitations.

    Some of the internals of the communications task are briefly explained to show why it works as it works now, and may prevent questions. On the other hand, it may trigger suggestions for changes! Feel free to go in discussion with me!

    Wiring
    Only the Read (RD), write (TD) and Clear-to-Send (CTS) lead are connected. Thus using DTR and RTS from the computer will have no effect of any kind and obviously DSR and DCD (sometimes also called CD or RLSD) will not be used by the Nevertheless DTR and RTS are set by the driver with DigiOpen(). This allows for the use of a break-out box and/or another computer in stead of the for debugging of your program (since a PC commmunications program usually needs these leads to be active). The driver itself works properly without the DSR and DCD leads.
    Speed
    DigiAPI has a table of properties for each system, among which the speed of the Interface. When you specify 0 (zero) it will take the default, but if you specify a speed, it will override that default. DigiAPI allows any speed between 10 and 115200 bps.
    FIFO
    So called FIFO hardware buffering, provided by the newer UART-chips (such as 16550A) on asynchronous communication ports is not used, since it cannot provide an improvement in this case. The Interface operates strictly in half-duplex mode, it is either in receive mode, or in transmit mode. During data transfer CTS is dropped while transmitting each command-byte and may remain low for a 'considerable' time while command interpretation by the Interface is in progress and feedback information is being sent to the computer. Hardware buffering of the COM-port's UART is not usable.
    Output flow control
    The CTS signal is dropped by the Interface during data transfer (during receive and transmit is strictly half-duplex). The CTS signal is used for 'hardware output flow control' from the computer. So the transmit routines will not send data when CTS is not high, but will automatically resume transmission when CTS rises and the buffer contains any data to transmit.
    Although not strictly needed, the DOS-version of the driver has the transmit interrupt (THRE) active to be able to run the driver against a full-duplex interface (with continuous CTS), such as another PC with a diagnostic communications program. The THRE interrupt uses the same code as the MSR interrupt.
    For initial testing of programs without actually running a model railroad, you could use a 'wrap-plug' to partially simulate the presence of an Interface. The RTS signal (raised by the driver) is feeded back to CTS, so the driver is always able to transmit.
    If also the send and receive (generally pins 2 and 3) leads are wrapped back, then the transmitted data is treated as S88 response. This may cause confusion!
    Input flow control
    There is no form of input flow control allowed by the Interface to stop sending. This will not be a problem for the driver, since there is enough buffering and CPU-power to process the low volume input in time.
    XON/XOFF flow control
    Not used. This type of flow-control is not supported by the Interface, since the commands for the Interface may contain these characters!
    Null stripping
    The standard OS/2 device driver for the COM-port allows null stripping. There are at least 2 reasons for this feature not to be activated:
    • The commands may contain bytes with all bits zero (e.g. address 256 is addressed coded as 0).
    • The Interface may send bytes with all bits zero.

    OS/2 and DOS differences

    Although all versions of this driver offer a consistent application programming interface for communication with the Interface, it is most likely that there will be differences between the programs using either of them. Some of these differences might be:

    When there is no possibility to use the same code for OS/2 and DOS, you might consider using a compiler variable (with the -D compiler option) to distinguish which pieces of codes are OS/2 or DOS specific. See for an example the MAKE-files in the DigiAPI package.


    Trace facility

    Trace records

    The trace facility produces the following records:

     unsigned long timestamp;  /* value of DigiTime() */
     unsigned char protocol,   /* protocol identifier */
                   direction,  /* IN or OUT going packet */
                   length,     /* length of the trace data */
                   data[255];  /* trace data (variable, max 255 bytes */
    

    The first record of a new trace is an identification record, containing the date and time of the trace start.

    Trace Sample

    The output of the trace formatting program DIGITRC will look like

     DigiAPI trace formatter version 2.0  -  Rob Hamerling
    ---------------------------------------------------------------------
    98/02/01 21:17:30
                Start trace of Maerklin traffic at Sun Feb  1 21:17:30 1998
        0.279 O C0
                Reset S88's after reading
        0.283 O 84
                Read S88 1...4 with reset
        0.301 I 8001
                1 16
                .....
                .....
        1.236 O 4024
                Function decoder 36 ON =
        1.301 O 4024
                Function decoder 36 ON =
        1.362 O 003C
                Loc=60  Speed=0   Function=OFF
        1.423 O 003C
                Loc=60  Speed=0   Function=OFF
                .....
                .....
       17.502 I 8001
                1 16
    
    Totals: OUT     150 trace records with     213 bytes,
             IN     625 trace records with    1250 bytes.
    
    

    This is not a complete trace, just some parts to show some essentials.

    DIGITRC does not need parameters, but you may specify the input filename [including path] if your trace is in another directory or named other than DIGIAPI.TRC, which is the default (output filename of DigiAPI).
    DIGITRC writes the output to 'stdout', which is normally the computer display. But the output may be redirected to your printer or to a file. So you could use for example the command:

      DIGITRC  >TRACE.FMT
    
    so that you can look afterwards with a text editor or file browse utility in TRACE.FMT for the formatted trace.

    NOTE: The trace facility cannot be used by multiple simulataneously running application program based on DigiAPI at a time when run with the same default directory.


    Märklin Interface peculiarities

    When writing a model railroad control program (with or without this driver), you should be aware of some peculiarities, which are not very well (or not at all) documented by the manufacturer (esp. Märklin!). With experiments during development of DigiAPI and it predecessors with my Märklin Digital modelrailroad I have found for example:

    Some Interfaces, Controller and decoders may react differently than others!


    Summary of changes

    DigiAPI offers a functionally identical programming interface for Märklin Digital, Lenz Digital Plus and Trix Selectrix.
    2.3
    February 1999
    • Systems with command/reply sequences, 'conversational mode' like Lenz, are now handled as such to prevent observed flow control problems with 'full duplex' mode (next command sent without waiting for a reponse).
    • Some minor bug fixes, internal performance improvements, and documentation extensions.
    2.2
    December 1998
    • Corrections in feedback for Lenz (reversed contact numbering).
    • Documentation extensions.
    2.1
    September 1998
    • Added support for Trix Selectrix, Märklin 6023 TRAIN-ING (6023)
    • The function DigiEnableFeedback() now activates only the specified feedback contact. So the application program should activate every individual participating contact, in stead of only the highest contact as with DigiAPI version 2.0.
      Note: when deactivating monitoring ALL monitoring is stopped, and when reactivating it, all participcating contacts have to be re-enabled again!
    • Improvements in handling of timeouts and queueing to prevent data loss in extreme situations with high command traffic.
    • The DigiGo() function now issues only a single 'GO' command for Mäerklin.
    2.0
    First release as successor of MKLAPI 1.7.
    It is a major overhaul involving many changes, and it would be largely a duplication of this documentation to make a complete list of changes.
    If you have based your model railroad application on MKLAPI, it needs a major overhaul to migrate it to DigiAPI. Some functionality that you needed to put in your application is now provided by DigiAPI. Read this Programming Guide and the Reference Manual carefully to make yourself familiar with the conceptual changes.
    Below only some 'highlights' of changes:
    • Was MKLAPI restricted to Märklin Digital only and was tightly coupled to the facilities of this system, DigiAPI is on a higher abstraction level and has hardly any dependencies on a specific system, but is is a sort of 'least common multiple'. This allows multiple systems to be supported, currently only Märlin and Lenz Digital Plus, more to come later. For your application it means that it supports automatically more than only Märklin Digital. There is nothing else you have to put in your program than a facility to indicate which protocol DigiAPI has to use.
    • The 'channel' concept has been introduced. It means that DigiAPI supports multiple COM-ports and a selectable protocol per port. Therefore most functions have a parameter to indicate to which channel a command applies.
    • Reading of feedback information is now generalised and incorporated in a 'monitoring' task. Depending on the type of system under control more than only feedback information is read. For example with Lenz also system status information and loc information (of 'active' locs only) is requested on a regular basis.
    • DigiAPI is shareware and distributed with some functional limitations. You need a key-file to unlock the limitations.