Serial Support
Message Passing Facility
Operating System Independent Version

Release 2-4-2

Marty Kraimer and Mark Rivers
Febuary 2004

License Agreement

MPF is available via the open source license described at the end of this document.

Contents

Introduction
Serial Test
serialPortPeek
Building applications
Initialization Commands
Serial Support Theory
devStringMpf
serialServer
serial Port Drivers License Agreement

Introduction

mpfSerial provides support for EPICS records to communicate with serial devices, i.e. RS232, RS485, etc. The basic concept is:

The support is structured so that special requirements can be satisfied by writing device support and/or a serial server. As long as this support satisfies the conventions defined in serialserver.h and SerialPort.h then the low level drivers should still work.

mpfSerial consists of the folowing:

The major difference between the OSI (Operating System Independent) version of mpfSerial and the previous version is that the low lever driver support now follows standards rather than developing specialized support. This approach allows mpfSerial to be used with anything that supports the standard. On solaris/linux mpfSerial works with the regular serial ports. On vxWorks mpfSerial works with the regular serial ports and with the tyGSOctal driver that comes with the ipac support.

NOTE: The only serial device support discussed in this manual is devStringMpf. Most mpfSerial users use the genericBus/serialMpfApp support that comes with SYNAPPS.

Serial Test

The iocBoot directory has sub directories for running serial tests. The tests use a combination of standard serial ports, SBS OctalUART ports, and MOXA ports. The tests assume that a port echos each character written to it, i.e. pin2 is connected to pin 3. Tests are provided for vxWorks (iocserialvxWorks) and for Unix/Linux (iocserialhost). Each provides two versions of the test: a version that uses writeRead requests, and a version that uses separate write and reads.

To use the the version that uses writeRead requests on Unix/Linux do the following:

cd iocBoot/iocserialhost
../../bin/<arch>/testSerial st.cmd

NOTE: The st.cmd file starts ports for the MOXA box in my office. Just comment out all references to port2,...port5.

The test starts a serialServer and creates records for each port. The following is the record template for a port.
record(calc,"serial$(port):calc$(ind)")
{
    field(SCAN, "Event")
    field(EVNT, "1")
    field(CALC,"a=1000?0:a+1")
    field(FLNK,"serial$(port):StringOut$(ind)")
    field(INPA,"serial$(port):calc$(ind)")
}
record(stringout,"serial$(port):StringOut$(ind)")
{
    field(DOL,"serial$(port):calc$(ind)")
    field(OMSL,"closed_loop")
    field(FLNK,"serial$(port):StringIn$(ind)")
}
record(stringin,"serial$(port):StringIn$(ind)")
{
    field(DTYP,"MPFwriteRead")
    field(INP, "#C$(card) S0 @serial$(port),serial$(port):StringOut$(ind)")
}
The calc record  counts from 0 to 1000 and links to a stringout record which reads the value of the calc record and forward links to a  stringin record.  The stringin record reads the value from the stringout record and sends it to serverChar8array. The reply from serverChar8array is put into the val field. Studying this example should give a good idea of how serial support works.

serialPortPeek("portName",seconds)

This is a diagnostic routine that displays all bytes sent to/from a particular port for a specified number of seconds. For example:

serialPortPeek "port0",2
port2 T 0.00                      
port2 T 0.03 326.                 33 32 36 00 
port2 R 0.05 326.                 33 32 36 00 
port2 T 0.53 327.                 33 32 37 00 
port2 R 0.56 327.                 33 32 37 00 
port2 T 1.03 3                    33 
serialPortPeek done
The output displays up to 20 characters per line. It first shows the port name, T or R (transmit or receive), and the number of seconds since serialPortPeek started. It then shows the ascii value (non-printable ascii values are shown as "."). It then shows the hex value for each character. It starts a new line when the port switches between transmit and receive and also every two seconds.

Building applications

Add the following to configure/RELEASE

MPF_SERIAL=<path name>
MPF=<path name>
IPAC=<path name>
EPICS_BASE=<path name>

In the directory where the application is built. NOTE: xxx is replaced by the name of the application you are building.

Initialization Commands

This section describes initialization commands that appear in a st.cmd file. A simple example may make this section easier to understand. The following commands initialize everything needed for accessing a single serial port on an x86 Linux based ioc.

localMessageRouterStart(1)
initTtyPort("port0","/dev/ttyS0",38400,"N",1,8,"N",1000)
initSerialServer("serial0","port0",1000,2,"")

The following is an example record that connects to this port.

record(stringin,"name")
{
    field(DTYP,"MPFread")
    field(INP, "#C1 S0 @serial0")
}

NOTE: #C1 means DevMpf location 1, which is the argument given to localMessageRouterStart.

mpfSerial initialization Commands

The complete set of mpfSerial init commands are:

int initSerialServer(const char *serverName,const char *portName,
    int bufsize, int queueSize, const char *eomstr);
/* The following is for the POSIX termios driver */
int initTtyPort(const char *portName, const char *fileName,
    int baud,const char *parity, int stopBits,int bitsPerChar,
    const char *flowType,int bufsize);
/* The following is for the vxWorks tyCo driver */
int initTtyPort(const char *portName, const char *fileName,
    int baud, int bufsize);
/* The following is for ethernet serial port devices like MOXA */
int initInetPort(char *portName, char *inetAddr,int port,int bufsize);
*/

The arguments are:

serverName The server name which is specified in INP or OUT fields
portName The port name
buffersize Input buffer size. Both the server and low level drivers need a buffer.
queueSize The number of message a server is willing to buffer.
eomString End of message string for input messages. The initial value can be overridden by device support. The eomString can be any of the following:
  • null string - server doesnt look for end of message. Device support must specify exact length for input messages.
  • "", i.e. zero length string. A character with binary value 0 signifies the end on message.
  • a one or two character string that appears at the end of input messages.
baud Baud rate. Allowed values determined by lowest level driver
parity "E", "O", "N" => Even, Odd, None
stopBits 0 or 1
bitsPerChar 5,6,7, or 8
flowType "H", "N" => Hardware, None
inetAddr Internet address
port Tcp port

Other Initialization Commands

In addition the following initialization commands may be needed.

Example st.cmd file for a Linux x86 IOC

The following example starts two serial ports on the ioc and four serial ports on a MOXA ethernet serial server that resides at address :164.54.9.90 and responds to TCP connect requests at TCP ports 4001,...,4004.

localMessageRouterStart(1)
initTtyPort("port0","/dev/ttyS0",38400,"N",1,8,"N",1000)
initTtyPort("port1","/dev/ttyS1",38400,"N",1,8,"N",1000)
initInetPort("port2","164.54.9.90",4001,1000)
initInetPort("port3","164.54.9.90",4002,1000)
initInetPort("port4","164.54.9.90",4003,1000)
initInetPort("port5","164.54.9.90",4004,1000)
initSerialServer("serial0","port0",1000,2,"")
initSerialServer("serial1","port1",1000,2,"")
initSerialServer("serial2","port2",1000,2,"")
initSerialServer("serial3","port3",1000,2,"")
initSerialServer("serial4","port4",1000,2,"")
initSerialServer("serial5","port5",1000,2,"")

Example st.cmd for a vxWorks IOC

The vxWorks support uses the standard vxWorks serial ports and/or drvIpac.. The following example shows how to configure support for an mv162 . See the drvIpac documentation for how to configure other carriers. Note that the ipacAddCarrier command must be on a single line. The example starts one mv162 serial port and eight serial ports for a Green Springs OctalUART Industry Pack module.

...
localMessageRouterStart(1)
#The following starts IP support on an mv162
ipacAddMVME162("A:l=3,3 m=0xe0000000,64;B:l=3,3 m=e0010000,64")

#The following starts code that uses a single serial port on CPU
initTtyVxPort("tyCo1","/tyCo/1",9600,1000)
#The following starts code that uses tyGSOctal.c
tyGSOctalDrv 1
octalUart0 = tyGSOctalModuleInit("GSIP_OCTAL232", 0x80, 0, 1)
port0 = tyGSOctalDevCreate("/tyGS/0/0",octalUart0,0,1000,1000)
port1 = tyGSOctalDevCreate("/tyGS/0/1",octalUart0,1,1000,1000)
port2 = tyGSOctalDevCreate("/tyGS/0/2",octalUart0,2,1000,1000)
port3 = tyGSOctalDevCreate("/tyGS/0/3",octalUart0,3,1000,1000)
port4 = tyGSOctalDevCreate("/tyGS/0/4",octalUart0,4,1000,1000)
port5 = tyGSOctalDevCreate("/tyGS/0/5",octalUart0,5,1000,1000)
port6 = tyGSOctalDevCreate("/tyGS/0/6",octalUart0,6,1000,1000)
port7 = tyGSOctalDevCreate("/tyGS/0/7",octalUart0,7,1000,1000)
tyGSOctalConfig(port0,38400,'N',1,8,'N')
tyGSOctalConfig(port1,38400,'N',1,8,'N')
tyGSOctalConfig(port2,38400,'N',1,8,'N')
tyGSOctalConfig(port3,38400,'N',1,8,'N')
tyGSOctalConfig(port4,38400,'N',1,8,'N')
tyGSOctalConfig(port5,38400,'N',1,8,'N')
tyGSOctalConfig(port6,38400,'N',1,8,'N')
tyGSOctalConfig(port7,38400,'N',1,8,'N')
initTtyVxPort("tyGS00","/tyGS/0/0",38400,1000)
initTtyVxPort("tyGS01","/tyGS/0/1",38400,1000)
initTtyVxPort("tyGS02","/tyGS/0/2",38400,1000)
initTtyVxPort("tyGS03","/tyGS/0/3",38400,1000)
initTtyVxPort("tyGS04","/tyGS/0/4",38400,1000)
initTtyVxPort("tyGS05","/tyGS/0/5",38400,1000)
initTtyVxPort("tyGS06","/tyGS/0/6",38400,1000)
initTtyVxPort("tyGS07","/tyGS/0/7",38400,1000)

# Only one of the following two statements can be selected
initSerialServer("serial0","tyCo1",100,2,"")
#initSerialServer("serial0","tyGS00",100,2,"")
initSerialServer("serial1","tyGS01",100,2,"")
initSerialServer("serial2","tyGS02",100,2,"")
initSerialServer("serial3","tyGS03",100,2,"")
initSerialServer("serial4","tyGS04",100,2,"")
initSerialServer("serial5","tyGS05",100,2,"")
initSerialServer("serial6","tyGS06",100,2,"")
initSerialServer("serial7","tyGS07",100,2,"")
      

Serial Support Theory

mpfSerial support consists of three levels:

mpfSerial provides code for all three levels but is designed so that additional code can be written at any of the three levels in order to support special requirements.

The mpfSerial support interfaces are described in the following files:

serialStatus.h

This defines status values used by all levels.

typedef enum {
    serialSuccess,
    serialTimeout,
    serialOverflow,
    serialFailure
}serialStatus;
serialSuccess operation was successful
serialTimeout operation timeout.
serialOverflow input buffer overflow
serialFailure other error

serialServer.h

The interface between device support and the serial server. The definitions use fields of messages that are defined by MPF.

#define cmdWrite        0x1
#define cmdRead         0x2
#define cmdWriteRead    (cmdWrite|cmdRead)
//Following are additional options
#define cmdFlush        0x4
#define cmdSetEom       0x8
//Following are for serialGetConfig
#define cmdGetConfig    0x1
/*
 * cmdFlush - flush before starting write and/or read
 * cmdSetEom - change current eom
 *
 * for example the following could be specified
 * message->cmd = cmdWriteRead|cmdFlush;
*/
/* Generic serial server messages

SerialConfigMessage.
   This just calls SerialPort::config.
   It returns an Int32Message. status is 0 for success.

Int32Message.
       cmdGetConfig
              Returns a SerialConfigMessage with current settings
Char8ArrayMessage
       cmdWrite        : write request
       cmdRead         : read request
       cmdWriteRead    : writeRead request

       other fields from input message

       timeoutUnits     timeout units.
       timeout          The timeout value in timeoutUnits
       extra            If >0 the maximum number of chars to read

       If cmdSetEom is set

       eomSize          If 0 dont look for eom; otherwise must be 1 or 2
       eomString        1 or 2 character eomString

       Return values
                        A Char8ArrayMessage message, which
                        contains any input characters read

                        status is one of the serialStatus values
*/

serialServer.h is the interface between device suport and a serial server.

Some serial devices have a protocal that is not compatible with serialServer and/or devStringMpf. In such cases new device support and/or a new server must be supplied. SerialServer and devStringMpf provide a model of how to write special support. If a special application requires BOTH new device support and a new server than serialServer.h may not be applicable. If, however, either serialserver or devStringMpf is used then the conventions described in serialServer.h must be followed.

SerialPort.h

This defines the interface between serialServel and low level drivers. Low level drivers implement the virtual methods defined in class serialPort.

enum requestType {requestNone,requestRead,requestWrite};
enum byteHandlerRC {byteHandlerOK,byteHandlerEndRead,byteHandlerError};

typedef byteHandlerRC (*BYTE_HANDLER)
    (void* parm,unsigned char byte);
/*
    byte handler is called by input routine. It must return one of:
    byteHandlerOK  normal return
    byteHandlerEndRead end of input message has been detected
    byteHandlerError - An error occured - end operation

*/

typedef void (*PEEK_HANDLER)
    (void* parm,bool isReceive, unsigned char byte);
// If registered PEEK_HANDLER is called for every byte transmitted or received

class SerialPort
{
public:
    SerialPort();
    int registerName(const char *portName);
    static SerialPort *find(const char *portName);
    static SerialPort *bind(const char *portName,
        void* byteHandlerParm,BYTE_HANDLER byteHandler);
    void release();
    static bool setPeekHandler(
        const char *portName,void* peekHandlerParm,PEEK_HANDLER peekHandler);
    static void freePeekHandler(const char *portName);
    virtual serialStatus write(
        unsigned char *data, int nbytes, double timeout) = 0;
    virtual serialStatus read(double timeout) =0;
    virtual void flush();
    virtual bool config( int baud, int stopBits, int bitsPerChar,
        char parity, char flowControl);
    virtual void getConfig( int *baud, int *stopBits, int *bitsPerChar,
        char *parity, char *flowControl);
protected:
    BYTE_HANDLER byteHandler;
    void* byteHandlerParm;
    PEEK_HANDLER peekHandler;
    void* peekHandlerParm;
private:
    ...
};
 
Method Implementor Usage Description
registerName SerialPort Called by derived class Register the serial port.
find SerialPort Called by servers Given the name of a port, find and return SerialPort.
bind SerialPort Called by servers Bind the caller to the specified port and if successful return the address of the SerialPort. Only one caller can be bound to a particular port. After a successful bind the caller may call write, read, writeRead, and release.
release SerialPort Called by servers Release the caller that is bound to the port.
setPeekHandler SerialPort Called by serialPortPeek or other diagnostics SerialPort locates the serial port and sets the field peekHandler. If peekHandler has a value, the Derived Class is expected to call it for every character trasmitted and received.
freePeekHandler SerialPort Called by serialPortPeek or other diagnostics SerialPort locates the serial port and clears the field peekHandler.
write Derived Class Called by servers. Send data to the serial port.
read Derived Class Called by servers Read data from the serial port.
flush Derived Class Called by servers Flush all outstanding I/O for the port. NOTE: result dependent on low level driver.
config Derived Class Called by servers Set all options specified by SerialConfigMessage. NOTE: result dependent on low level driver.
getConfig Derived Class Called by servers Get all current config options specified by SerialConfigMessage. NOTE: result dependent on low level driver.

The bind call must supply a byte handler which is called for every input character received by the serial port. The byte handler returns one of the following values.
 

Byte Handler Return Codes
byteHandlerOK character is OK; continue reading
byteHandlerEndRead got end of message. Complete read with success.
byteHandlerError complete read with failure.

devStringMpf

NOTE: Perhaps NO current mpfSerial users use this for device support, because it provides no nice way of handling termination characters.

devStringMpf implements three versions of device support, corresponding to the three commands that can be given to serialServer. The first two types are associated with a stringin record and the third with a stringout record. File devStringMpf.dbd provides the device definitions and devStringMpf.cc provides the device support.

A record which uses MPFwriteRead has DTYP and INP defined as follows:

record(stringin,"<pvname>")
{
    field(DTYP,"MPFwriteRead")
    field(INP, "#C<location> S0 @<server>,<inputRecord>")
}
 
pvname Record name
location Location of message server, i.e. the location specified via the initXXXMessageRouter commands for MPF.
server Name of the message server
inputRecord Name of field from which a serial output string is obtained.

When the record is processed the following happens:

Note the following restrictions: Unless these restrictions are acceptable specialized device support will have to be written.

A record which uses MPFread has DTYP and INP defined as follows:

record(stringin,"<pvname>")
{
    field(DTYP,"MPFread")
    field(INP, "#C<location> S0 @<server>")
}
A record which uses MPFwrite has DTYP and OUT defined as follows:
record(stringout,"<pvname>")
{
    field(DTYP,"MPFwrite")
    field(OUT, "#C<location> S0 @<server>")
}

serialServer

serialServer is a generic server which implements the serialSever.h interface and communicates with low level drivers via the SerialPort interface. It accepts SerialConfig, Int32, and Char8Array messages. Client code must include file "serialServer.h", which contains definitions for commands, options, and for return status.

When serialServer receives a SerialConfigMessage it calls SerialPort::config and returns an Int32Message. The status field in Int32Message is one of the serialStatus values. Low level drivers may only support a subset of the config options.

If an Int32Message is sent to serialServer, the only valid command is cmdGetConfig. It returns a SerialConfigMessage with the current configuration. Low level drivers may only support a subset of the config options.

When serialServer receives a Char8Array message it returns  a Char8Array message. The return message contains any characters read and a serialStatus value.
 

The cmd field of the Char8Array message can contain a command and also options. The commands are:
 

cmdWrite Write value to the port. A Char8Array message is returned. Only the status field is of interest.
cmdRead Read from from the serial port. A Char8Array message is returned. The value contains the input. Status should always be checked. Input is read until the first of the following occurs: 1) the end of message string is read, 2) extra is non zero and exactly extra bytes are read, 3) A read buffer overflow occurs, or 4) a timeout occurs.
cmdWriteRead The output string is sent to the serial port and a input string read.

The cmd field can also contain the following options:
 

cmdFlush Flush before starting I/O.
cmdSetEom Set end of message string. If the option is set then the fields eomLen and eomString determine the new end of message string.

The following additional fields of the Char8ArrayMessage are also honored by the serialServer:
 

timeout The timeout value. This field must be set. The field timeoutUnits determines if the units are milliSeconds or seconds.
numberRetrys Number of times to retry the request if the initial request fails.
extra If extra <= 0 then it is ignored. If extra is > 0 then it is the maximum number of bytes to read before ending the read. Disabling the end of message string and specifying extra allows serialServer to read binary data.
eomLen If cmdSetEom is set, this field determines the length of the end of message string. It can have the value 0, 1, or 2.
A zero value disables the end of message string. If the value is 1 or 2 the string is determined by eomString
eomString If cmdSetEom is set and eomLen is 1 or 2, this is the end of message string.

serial Port Drivers

A serial server talks to to drivers via the SerialPort interface. SerialPort.cpp implements the non-virtual methods of class serialPort. The virtual methods must be implemented low level drivers. mpfSerial provides three drivers:

License Agreement

Copyright (c) 2002 University of Chicago. All rights reserved.

MPF is distributed subject to the following license conditions:

 SOFTWARE LICENSE AGREEMENT
 Software: MPF

 1. The "Software", below, refers to mpf (in either source code, or
    binary form and accompanying documentation). Each licensee is
    addressed as "you" or "Licensee."

 2. The copyright holders shown above and their third-party licensors
    hereby grant Licensee a royalty-free nonexclusive license, subject to
    the limitations stated herein and U.S. Government license rights.

 3. You may modify and make a copy or copies of the Software for use
    within your organization, if you meet the following conditions:
      a. Copies in source code must include the copyright notice and this
         Software License Agreement.
      b. Copies in binary form must include the copyright notice and this
         Software License Agreement in the documentation and/or other
         materials provided with the copy.

 4. You may modify a copy or copies of the Software or any portion of it,
    thus forming a work based on the Software, and distribute copies of
    such work outside your organization, if you meet all of the following
    conditions:
      a. Copies in source code must include the copyright notice and this
         Software License Agreement;
      b. Copies in binary form must include the copyright notice and this
         Software License Agreement in the documentation and/or other
         materials provided with the copy;
      c. Modified copies and works based on the Software must carry
         prominent notices stating that you changed specified portions of
         the Software.

 5. Portions of the Software resulted from work developed under a U.S.
    Government contract and are subject to the following license: the
    Government is granted for itself and others acting on its behalf a
    paid-up, nonexclusive, irrevocable worldwide license in this computer
    software to reproduce, prepare derivative works, and perform publicly
    and display publicly.

 6. WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS" WITHOUT WARRANTY
    OF ANY KIND. THE COPYRIGHT HOLDERS, THEIR THIRD PARTY LICENSORS, THE
    UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND THEIR
    EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
    BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
    FOR A PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT, (2) DO NOT ASSUME
    ANY LEGAL LIABILITY OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS,
    OR USEFULNESS OF THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF THE
    SOFTWARE WOULD NOT INFRINGE PRIVATELY OWNED RIGHTS, (4) DO NOT WARRANT
    THAT THE SOFTWARE WILL FUNCTION UNINTERRUPTED, THAT IT IS ERROR-FREE
    OR THAT ANY ERRORS WILL BE CORRECTED.

 7. LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT HOLDERS, THEIR
    THIRD PARTY LICENSORS, THE UNITED STATES, THE UNITED STATES DEPARTMENT
    OF ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT, INCIDENTAL,
    CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF ANY KIND OR NATURE,
    INCLUDING BUT NOT LIMITED TO LOSS OF PROFITS OR LOSS OF DATA, FOR ANY
    REASON WHATSOEVER, WHETHER SUCH LIABILITY IS ASSERTED ON THE BASIS OF
    CONTRACT, TORT (INCLUDING NEGLIGENCE OR STRICT LIABILITY), OR
    OTHERWISE, EVEN IF ANY OF SAID PARTIES HAS BEEN WARNED OF THE
    POSSIBILITY OF SUCH LOSS OR DAMAGES.