Release 3.3
Marty Kraimer, Eric Norum and Mark Rivers
August 23, 2004
This product is available via the open source license described at the end of this document.
asynDriver is a general purpose facility for interfacing device specific code to low level drivers. It supports both asynchronous (can block) and synchronous (do not block) drivers.. The name asynDriver is used because, high level device support makes all calls to drivers from a callback. asynDriver is designed so that it is easy to write non blocking device support that works with both blocking and non blocking drivers.
A primary target for asynDriver is EPICS IOC device support but, other than using libCom, much of it is independent of EPICS.
The following are some of the existing EPICS general purpose device support systems that have been converted to use asynDriver.
The following are some of the existing EPICS general purpose device support systems that could be converted to use asynDriver.
Each of these systems is used at EPICS facilities for accessing GPIB and/or serial devices. Because device support has been written for many instruments and thousands of database records use the device support, users will not be easily persuaded to switch from their existing solution. Thus, asynDriver implements a framework below device support that can be used by all of the above systems so that all can share the same drivers.
Each system needs to be modified so that the device support component is compatible with existing use, but replace the driver part with asynDriver. The benefit is that all could share the same set of low level drivers.
gpibCore and mpfSerial have already been converted and is included with asynDriver.
Hopefully Dirk Zimoch will get time soon to convert STREAMS, and Allen Honey time to convert devAscii.
In the future, other protocols will be supported especially for Ethernet based devices.
This version provides
The idea of creating asynDriver resulted from many years of experience with writing device support for serial and GPIB devices. The following individuals have been most influential.
asynDriver is a software layer between device specific code and drivers that communicate with devices. It supports both blocking and non-blocking communication and can be used with both register and message based devices. asynDriver defines the following terminology:
All communication between software layers is done via interfaces. An interface definition is a C language structure consisting entirely of function pointers. An asynDriver interface is analogous to a C++ or Java pure virtual interface. Although the implementation is in C, the spirit is object oriented. Thus this document uses the term "member" rather than "function pointer".
An entity which provides access to a device. A port provides access to one or more devices.
Code that communicates with a port.
If a portDriver can block, a thread is created for each port, and all I/O to the portDriver is done via this thread.
A device (instrument) connected to a port. For example a GPIB interface can have up to 15 devices connected to it. Other ports, e.g. serial ports, can only support a single device. Whenever this document uses the word device without a quailifier, it means something that is connected to a port.
This is the name for the support described in this manual. It is also the name of the header file that describes the core interfaces
The code which implements the asynManager and asynTrace methods.
A driver that may block while communicating with a device. Typical examples are serial, gpib, and network based drivers.
A driver that does not block while communicating with a device.
Interfaces that use octet arrays for read/write operations.
Interfaces that use integers or floats for read/write operations.
Synchronous/asynchronous and message/register are orthogonal concepts. For example a register based driver can be either synchronous or asynchronous. The terminology register vs message is adapted from VXI.
Standard interfaces are defined so that most device specific code can communicate with multiple port drivers. For example if device support does all its communication via reads and writes consisting of 8 bit bytes (octets), then it should work with all port drivers that support octet messages. If device support requires more complicated support, then the types of ports will be more limited. Standard interfaces are also defined for drivers that accept 32 bit integers or 64 bit floats. Additional interfaces can be defined, and it is expected that additional standard interfaces will be defined.
Examples of portDrivers that block are GPIB controllers, serial ports, Ethernet ports, etc. Examples of portDrivers that do not block are VME register based devices.
One or more devices can be attached to a port. For example, only one device can be attached to an RS-232 port, but up to 15 devices can be attached to a GPIB port.
Multiple layers can exist between device specific code and a port driver. A software layer calls interposeInterface in order to be placed between device specific code and drivers. For more complicated protocols, additional layers can be created. For example, GPIB support is implemented as an asynGpib interface which is called by user code, and an asynGpibPort interface which is called by asynGpib.
A driver normally implements multiple interfaces. For example asynGpib implements asynCommon, asynOctet, and asynGpib.
asynManager uses the Operating System Independent features of EPICS base. It is, however, independent of record/device support. Thus, it can be used by other code, e.g. a sequence program.
This section briefly describes the interfaces provided by asynManager and standard interfaces implemented by port drivers. asynManager members are called by application threads. Except for asynCommon:report, port driver methods can only be called from the user supplied callback passed in the call to createAsynUser.
The interfaces are:
asynManager provides services for communicating with a device connected to a port. The following services are provided:
asynCommon is a set of methods that must be implemented by all low level drivers. The methods are:
asynTrace is a set of methods for generating diagnostic messages.
asynDrvUser is a set of methods for communication user specific information from a user to a driver
In addition to the standard interfaces port drivers must implement one or more of the message and/or register based interfaces described in later sections.
During initialization, port drivers register each communication port as well as all supported interfaces.
User code creates an asynUser by calling pasynManager->createAsynUser(processCallback,...). The address of the asynUser is passed to most other asynDriver methods. processCallback is the addresss of a caller supplied callback routine, which will be described shortly. An asynUser is a "handle" for accessing asynDriver facilities. It is the means by which asynManager manages multiple requests for access to a port. Device support code should NOT try to share an asynUser between multiple sources of requsts for access to a port. For example device support for EPICS records should normally allocate a separate asynUser for each record instance.
User code connects to a low level driver via a call to pasynManager->connectDevice(pasynUser,portName,addr). This call must specify the name of the port and the address of the device. It then calls findInterface to locate the interfaces with which it calls the driver.
User code makes calls to the low level driver via an interface. All calls must be made from the queue callback passed to createAsynUser. A request to call queue callback is made by calling queueRequest. A thread that calls queueRequest is guaranteed not to block. If queueRequest is called for a port that can block the request is queued to a thread dedicated to the port. If queueRequest is called for a port does not block it just calls queue callback. In either case asynManager guarantees that multiple threads do not simultaneously call a low level driver. This guarantee is valid only if low level drivers are only accessed by calling queueRequest.
The following examples are based on EPICS IOC record/device support.
Figure 1: Asynchronous Control Flow
Figure 2: Synchronous Control Flow
asynDriver.h describes the following:
Defines the status returned by most methods. If a method returns a status other than asynSuccess, and one of the arguments to the method is pasynUser, then the method is expected to write a message into pasynUser->errorMessage.
typedef enum { asynSuccess,asynTimeout,asynOverflow,asynError }asynStatus;
asynSuccess | The request was successfull. |
asynTimeout | The request failed with a timeout. |
asynOverflow | The driver has lost input data. This can happen if an internal buffer or the user supplied buffer is too small. Whenever possible, low level drivers must be written so that the user can read input in small pieces. |
asynError | Some other error occured. |
Defines the exceptions for method exceptionOccurred
typedef enum { asynExceptionConnect,asynExceptionEnable,asynExceptionAutoConnect, asynExceptionTraceMask,asynExceptionTraceIOMask, asynExceptionTraceFile,asynExceptionTraceIOTruncateSize } asynException;
asynExceptionConnect | The connection state of the port or device has changed. |
asynExceptionEnable | The enable state of the port or device has changed. |
asynExceptionAutoConnect | The autoConnect state of the port or device has changed. |
asynExceptionTraceMask | The traceMask for the port or device has changed. |
asynExceptionTraceIOMask | The traceIOMask for the port or device has changed. |
asynExceptionTraceFile | The trace file for the port or device has changed. |
asynExceptionTraceIOTruncateSize | The traceIOTruncateSize for the port or device has changed. |
This defines the priority passed to queueRequest.
typedef enum { asynQueuePriorityLow,asynQueuePriorityMedium,asynQueuePriorityHigh, asynQueuePriorityConnect }asynQueuePriority;
asynQueuePriorityLow | Lowest queue priority. |
asynQueuePriorityMedium | Medium queue priority. |
asynQueuePriorityHigh | High queue priority. |
asynQueuePriorityConnect | Queue a connect or disconnect request. This priority must be used only for connect/disconnect requests. |
Describes a structure that user code passes to most asynManager and driver methods. Code must allocate and free an asynUser by calling asynManager:createAsynUser and asynManager:freeAsynUser.
typedef struct asynUser { char *errorMessage; int errorMessageSize; /* The following must be set by the user */ double timeout; /*Timeout for I/O operations */ void *userPvt; /* pointer to user private */ void *userData; /*The following is for user to/from driver communication*/ void *drvUser; int auxStatus; /*For auxillary status*/ }asynUser;
errorMessage | When a method returns asynError ,it should put an error message
into errorMessage via a call to:
epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize, "<format>",...) |
errorMessageSize | The size of errorMessage. The user can not change this value. |
timeout | The number of seconds before timeout for I/O requests. This is set
by the user and can be changed between calls to drivers.
The user must provide a non zero value or many low level drivers will timeout. A timeout value <= 0.0 means wait forever. |
userPvt | For use by the user. The user should set this immediately after the
call to pasynManager->createAsynUser.
If this is changed while asynUser is queued, the results are undefined, e.g. it could cause a crash. |
userData | Also for use by the user. |
drvUser | A driver can use this to hold asynUser specific data. The asynDrvUser interface is used for communication between asynUser and the driver. |
auxStatus | Any method can provide additional return information in auxStatus. The meaning is determined by the method. |
This defines an interface registered with asynPortManager:registerPort or asynManager:interposeInterface.
typedef struct asynInterface{ const char *interfaceType; /*For example, asynCommonType */ void *pinterface; /*For example, pasynCommon */ void *drvPvt; }asynInterface;
interfaceType | A character string describing the interface. |
pinterface | A pointer to the interface. The user must cast this to the correct type. |
drvPvt | For the exclusive use of the code that called registerPort or interposeInterface. |
This is the main interface for communicating with asynDriver.
/*registerPort attributes*/ #define ASYN_MULTIDEVICE 0x0001 #define ASYN_CANBLOCK 0x0002 typedef void (*userCallback)(asynUser *pasynUser); typedef void (*exceptionCallback)(asynUser *pasynUser,asynException exception); typedef struct asynManager { void (*report)(FILE *fp,int details); asynUser *(*createAsynUser)(userCallback queue,userCallback timeout); asynUser *(*duplicateAsynUser)(asynUser *pasynUser, userCallback queue,userCallback timeout); asynStatus (*freeAsynUser)(asynUser *pasynUser); void *(*memMalloc)(size_t size); void (*memFree)(void *pmem,size_t size); asynStatus (*isMultiDevice)(asynUser *pasynUser, const char *portName,int *yesNo); /* addr = (-1,>=0) => connect to (port,device) */ asynStatus (*connectDevice)(asynUser *pasynUser, const char *portName,int addr); asynStatus (*disconnect)(asynUser *pasynUser); asynStatus (*exceptionCallbackAdd)(asynUser *pasynUser, exceptionCallback callback); asynStatus (*exceptionCallbackRemove)(asynUser *pasynUser); asynInterface *(*findInterface)(asynUser *pasynUser, const char *interfaceType,int interposeInterfaceOK); asynStatus (*queueRequest)(asynUser *pasynUser, asynQueuePriority priority,double timeout); asynStatus (*cancelRequest)(asynUser *pasynUser,int *wasQueued); asynStatus (*canBlock)(asynUser *pasynUser,int *yesNo); asynStatus (*lock)(asynUser *pasynUser); /*lock portName,addr */ asynStatus (*unlock)(asynUser *pasynUser); asynStatus (*getAddr)(asynUser *pasynUser,int *addr); /* drivers call the following*/ asynStatus (*registerPort)(const char *portName, int attributes,int autoConnect, unsigned int priority,unsigned int stackSize); asynStatus (*registerInterface)(const char *portName, asynInterface *pasynInterface); asynStatus (*exceptionConnect)(asynUser *pasynUser); asynStatus (*exceptionDisconnect)(asynUser *pasynUser); /*any code can call the following*/ asynStatus (*interposeInterface)(const char *portName, int addr, asynInterface *pasynInterface, asynInterface **ppPrev); asynStatus (*enable)(asynUser *pasynUser,int yesNo); asynStatus (*autoConnect)(asynUser *pasynUser,int yesNo); asynStatus (*isConnected)(asynUser *pasynUser,int *yesNo); asynStatus (*isEnabled)(asynUser *pasynUser,int *yesNo); asynStatus (*isAutoConnect)(asynUser *pasynUser,int *yesNo); }asynManager; epicsShareExtern asynManager *pasynManager;
report | Reports status about the asynPortManager. It also calls asynCommon:report for each registered port driver. |
createAsynUser | Creates an asynUser. The caller specifies two callbacks, one for successful queueRequests and one if a queueRequest has a timeout. The timeout callback is optional. If it is not provided and a queueRequest with a non-zero timeout is requested, an error message is issued and no timeout will occur. errorMessageSize characters are allocated for errorMessage. The amount of storage can not be changed. This method doesn't return if it is unable to allocate the storage. |
duplicateAsynUser | Creates an asynUser by calling createAsynUser. It then initializes the new asynUser as follows: The fields asynUser.timeout and asynUser.userPvt are initialized with values taken from pasynUser. Its connectDevice state is the same as that for pasynUser. |
freeAsynUser | Free an asynUser. The user must free an asynUser only via this call. If the asynUser is connected to a port, disconnect is called. If the disconnect fails, this call will also fail. The storage for the asynUser is saved on a free list and will be reused in later calls to createAsynUser. Thus continually calling createAsynUser and freeAsynUser is efficient. |
memMalloc | Allocate memory. memMalloc/memFree maintain a set of freelists of different sizes. Thus any application that needs storage for a short time can use memMalloc/memFree to allocate and free the storage without causing memory fragmentation. |
memFree | Free memory allocated by memMalloc. Size MUST be the same as the value specified in the call to memMalloc. |
isMultiDevice | Answers the question "Does the port support multiple devices?" This method can be called before calling connectDevice. |
connectDevice | Device code calls this to connect to a device. It passes the name
of the communication port and the address of the device. The port
Name is the same as that specified in the call to registerPort. The
call will fail if the asynUser is already connected. If the port does
not support multiple devices, than addr is ignored. The call will
fail if the asynUser is already connected to a device. connectDevice
only connects a user to the port driver for the portName,addr. The
port driver may or may not be connected to the actual device. Thus,
connectDevice and asynCommon:connect are completely different.
See the Theory of Operation section for a description of the difference between single and multi-device port drivers. |
disconnect | Disconnect from the port,addr to which connectDevice is connected. The call will fail if the asynUser is queued or locked, or has an exception callback. Note that asynManager:disconnect and asynCommon:disconnect are completely different. |
exceptionCallbackAdd | Callback will be called whenever one of the exceptions defined by asynException occurs. The callback can call isConnected, isEnabled, or isAutoConnect to find the new state. |
exceptionCallbackRemove | Callback is removed. This must be called before disconnect. |
findInterface | Find a driver interface. If interposeInterfaceOK is true, then
findInterface returns the last interface registered or interposed.
Otherwise, the interface registered by registerPort is returned. It
returns 0 if the interfaceType is not supported.
The user needs the address of the drivers interface and of pdrvPvt so that calls can be made to the driver. For example: asynInterface *pasynInterface; asynOctet *pasynOctet; void *pasynOctetPvt; ... pasynInterface = pasynManager->findInterface( pasynUser,asynOctetType,1); if(!pasynInterface) { /*error do something*/} pasynOctet = (asynOctet *)pasynInterface->pinterface; pasynOctetPvt = pasynInterface->pdrvPvt; /* The following call must be made from a callback */ pasynOctet->read(pasynOctetPvt,pasynUser,... |
queueRequest | When portRegister is called, the caller must specify if it can
block, i.e. attribute bit ASYN_CANBLOCK is set of cleared. If the
port has been registered with ASYN_CANBLOCK true then the request is
put on a queue for the thread associated with the queue. If the port
has been registered with ASYN_CANBLOCK false then queueRequest just
locks the port and calls the user callback. If either case the queue
callback specified in the call to createAsynUser is called.
If the asynUser is already on a queue, asynError is returned. The timeout starts when the request is queued. A value less than or equal to 0.0 means no timeout. The request is removed from the queue before the callback is called. Thus callbacks are allowed to unlock and issue new queue requests. The priority asynQueuePriorityConnect must be used for asynCommon:connect and asynCommon:disconnect calls, and must NOT be used for any other calls. |
cancelRequest | If a asynUser is queued, remove it from the queue. If it is not on a queue, nothing is done. In particular if the callback is active, this call has no effect. If the return value is asynSuccess, then wasQueued (0,1) if a request (was not, was) canceled, i.e. removed from the queue. |
canBlock | yesNo is set to (0,1), i.e. (false,true) if calls to the low level driver can block. The value is determined by the attributes passed to registerPort. |
lock/unlock | lock/unlock are only valid if port can block.
lock/unlock are used to block other users from accessing a device while a user is making a series of queueRequests. Only the addr specified in the connectDevice request is locked. asynManager locks when a queueRequest is taken from the queue. At that point all other entries in the queue must wait until unlock is called by the same pasynUser that locked. lock/unlock fail if a request is currently queued. The addr argument passed to connectDevice determines if the port or only a device is locked. |
getAddr | *addr is set equal to the address which the user specified in the
call to connectDevice or -1 if the port does not support multiple
devices.
See the Theory of Operation section for a description of the difference between single and multi-device port drivers. |
registerPort | This method is called by drivers. A call is made for each communication interface instance. Attributes is a set of bits. Currently two bits are defined: ASYN_MULTIDEVICE and ASYN_CANBLOCK. The driver must specify these properly. autoConnect, which is (0,1) for (no,yes), provides the initial value for the port and all devices connected to the port. If priority is 0, then a default will be assigned. If stackSize is 0, a default is assigned. The portName argument specifies the name by which the upper levels of the asyn code will refer to this communication interface instance. |
registerInterface | This is called by port drivers for each supported interface. |
exceptionConnect | This method must be called by the driver when and only when it connects to a port or device. |
exceptionDisconnect | This method must be called by the driver when and only when it disconnects from a port or device. |
interposeInterface | This is called by a software layer between client code and the port
driver. For example, if a device echos writes then a software module
that issues a read after each write could be created and call
interposeInterface for interface asynOctet.
Multiple interposeInterface calls for a port/addr/interface can be issued. *ppPrev is set to the address of the previous asynInterface. Thus the software module that last called interposeInterface is called by user code. It in turn can call the software module that was the second to last to call interposeInterface. This continues until the actual port driver is called. interposeInterface can also be called with an asynInterface that has not been previously registered or replaced. In this case *ppPrev will be null. Thus, new interfaces that are unknown to the low level driver can be implemented. |
enable | If enable is set yes, then queueRequests are not dequeued unless their queue timeout occurs. |
autoConnect | If autoConnect is true and the port or device is not connected when a user callback is scheduled to be called, asynManager calls pasynCommon->connect. See the discussion of Flow of Control below for details. |
isConnected | *yesNo is set to (0,1) if the port or device (is not, is) connected. |
isEnabled | *yesNo is set to (0,1) if the port or device (is not, is) enabled. |
isAutoConnect | *yesNo is set to (0,1) if the portThread (will not, will) autoConnect for the port or device. |
/* Device Interface supported by ALL asyn drivers*/ #define asynCommonType "asynCommon" typedef struct asynCommon { void (*report)(void *drvPvt,FILE *fp,int details); /*following are to connect/disconnect to/from hardware*/ asynStatus (*connect)(void *drvPvt,asynUser *pasynUser); asynStatus (*disconnect)(void *drvPvt,asynUser *pasynUser); }asynCommon;
asynCommon describes the methods that must be implemented by drivers.
report | Generates a report about the hardware device. This is the only asynCommon method that does not have to be called by the queueRequest callback. |
connect | Connect to the hardware device or communication path. The queueRequest must specify priority asynQueuePriorityConnect. |
disconnect | Disconnect from the hardware device or communication path. The queueRequest must specify priority asynQueuePriorityConnect. |
/*asynTrace is implemented by asynManager*/ /*All asynTrace methods can be called from any thread*/ /* traceMask definitions*/ #define ASYN_TRACE_ERROR 0x0001 #define ASYN_TRACEIO_DEVICE 0x0002 #define ASYN_TRACEIO_FILTER 0x0004 #define ASYN_TRACEIO_DRIVER 0x0008 #define ASYN_TRACE_FLOW 0x0010 /* traceIO mask definitions*/ #define ASYN_TRACEIO_NODATA 0x0000 #define ASYN_TRACEIO_ASCII 0x0001 #define ASYN_TRACEIO_ESCAPE 0x0002 #define ASYN_TRACEIO_HEX 0x0004 /* asynPrint and asynPrintIO are macros that act like int asynPrint(asynUser *pasynUser,int reason, const char *format, ... ); int asynPrintIO(asynUser *pasynUser,int reason, const char *buffer, int len, const char *format, ... ); */ typedef struct asynTrace { /* lock/unlock are only necessary if caller performs I/O other than*/ /* by calling asynTrace methods */ asynStatus (*lock)(asynUser *pasynUser); asynStatus (*unlock)(asynUser *pasynUser); asynStatus (*setTraceMask)(asynUser *pasynUser,int mask); int (*getTraceMask)(asynUser *pasynUser); asynStatus (*setTraceIOMask)(asynUser *pasynUser,int mask); int (*getTraceIOMask)(asynUser *pasynUser); asynStatus (*setTraceFILE)(asynUser *pasynUser,FILE *fp); FILE *(*getTraceFILE)(asynUser *pasynUser); asynStatus (*setTraceIOTruncateSize)(asynUser *pasynUser,int size); int (*getTraceIOTruncateSize)(asynUser *pasynUser); int (*print)(asynUser *pasynUser,int reason, const char *pformat, ...); int (*printIO)(asynUser *pasynUser,int reason, const char *buffer, int len,const char *pformat, ...); }asynTrace; epicsShareExtern asynTrace *pasynTrace;
asynDriver provides a trace facility with the following attributes:
In order for the trace facility to perform properly; device support and all drivers must use the trace facility. Device and driver support can directly call the asynTrace methods. The asynPrint and asynPrintIO macros are provided so that it is easier for device/driver support. Support can have calls like:
asynPrintIO(pasynUser,ASYN_TRACE_FLOW,"%s Calling queueRequest\n", someName);
The asynPrintIO call is designed for device support or drivers that issue read or write requests. They make calls like:
asynPrintIO(pasynUser,ASYN_TRACEIO_DRIVER,data,nchars,"%s nchars %d",
someName,nchars);
The asynTrace methods are implemented by asynManager. These methods can be used by any code that has created an asynUser and is connected to a device. All methods can be called by any thread. That is, an application thread and/or a port thread. If a thread performs all I/O via calls to print or printIO, then it does not have to call lock or unlock. If it does want to do its own I/O, it must lock before any I/O and unlock after. For example:
pasynTrace->lock(pasynUser); fd = pasynTrace->getTraceFILE(pasynUser); /*perform I/O of fd */ pasynTrace->unlock(pasynUser);
If the asynUser is not connected to a port, i.e. pasynManager->connectDevice has not been called, then a "global" device is assumed. This is usefull when asynPrint is called before connectDevice.
lock/unlock | These are only needed if some code wants to do its own I/O instead of using print and printIO. The set methods, print, and printIO all lock while performing their operations. The get routines do not lock (except for getTraceFILE) and they are safe. The worst that happens is that the user gets a little more or a little less output. |
setTraceMask | Set the trace mask. Normally set by the user requesting it via a shell command or the devTrace device support. |
getTraceMask | Get the trace mask. Device support that wants to issue trace messages calls this to see what trace options have been requested. |
setTraceIOMask | Set the traceIO mask. Normally set by the user requesting it via a shell command or the devTrace device support. |
getTraceIOMask | Get the traceIO mask. Support that wants to issue its own IO messages instead of calling asynPrintIO should call this and honor the mask settings. Most code will not need it. |
setTraceFILE | Set the stream to use for output. A NULL argument means use errlog. Normally set by the user requesting it via a shell command or by the devTrace device support. If the current output stream is none of (NULL, stdout, stderr) then the current output stream is closed before the new stream is used. |
getTraceFILE | Get the file descriptor to use for output. Device support that wants to issue its own IO messages instead of calling asynPrintIO should call this and honor the mask settings. In this case, lock must have been called first. Most code will not need it. If the return value is 0, then ouput should be directed to errlog. |
setTraceIOTruncateSize | Determines how much data is printed by printIO. In all cases it determines how many bytes of the buffer are displayed. The actual number of characters printed depends on the traceIO mask. For example ASYN_TRACEIO_HEX results in 3 characters being printed for each byte. Normally set by the user requesting it via a shell command or the devTrace device support. |
getTraceIOTruncateSize | Get the current truncate size. Called by asynPrintIO. Code that does its own I/O should also support the traceIO mask. |
If reason ORed with the current traceMask is not zero, then the message is printed. Most code should call asynPrint instead of calling this method. | |
printIO | If reason ORed with the current traceMask is not zero then the message is printed. If len is >0, then the buffer is printed using the traceIO mask and getTraceIOTruncateSize. Most code should call asynPrintIO instead of calling this method. |
These are interfaces for communicating with message based devices, where message based means that the device accepts octet strings, i.e. an array of 8 bit bytes, and responds with octet strings. Three interfaces are provided: asynOctet, asynOctetSyncIO, and asynOctetTrapReadWrite. asynOctetSyncIO provides a synchronous inteface to asynOctet and can be used by code that is willing to block. asynOctetTrapReadWrite is an interposeInterface that can be used to intercept calls to asynOctet.
#define ASYN_EOM_CNT 0x0001 /*Request count reached*/ #define ASYN_EOM_EOS 0x0002 /*End of String detected*/ #define ASYN_EOM_END 0x0004 /*End indicator detected*/ #define asynOctetType "asynOctet" typedef struct asynOctet{ asynStatus (*read)(void *drvPvt,asynUser *pasynUser, char *data,int maxchars,int *nbytesTransfered, int *eomReason); asynStatus (*write)(void *drvPvt,asynUser *pasynUser, const char *data,int numchars,int *nbytesTransfered); asynStatus (*flush)(void *drvPvt,asynUser *pasynUser); asynStatus (*setEos)(void *drvPvt,asynUser *pasynUser, const char *eos,int eoslen); asynStatus (*getEos)(void *drvPvt,asynUser *pasynUser, char *eos, int eossize, int *eoslen); }asynOctet;
NOTE: The name octet is used instead of ASCII because it implies that communication is done via 8-bit bytes.
asynOctet describes the methods implemented by drivers that use octet strings for sending commands and receiving responses from a device.
read | Read a message from the device. *nbytesTransfered is the number of 8-bit bytes read from the device. If read returns asynSuccess than eomReason tells why the read completed. |
write | Send a message to the device. *nbytesTransfered is the number of 8-bit bytes sent to the device. |
flush | Flush the input buffer. |
setEos | Set End Of String. For example "\n". Note that gpib drivers usually accept at most a one character string. If eoslen is equal to 0, the behaviour depends on the interface. The asynInterposeEos asynOctet interface provides a read method which will not return until the specified number of characters have been been read or the time limit has been reached. |
getEos | Get the current End of String. |
typedef struct asynOctetSyncIO { asynStatus (*connect)(const char *port, int addr, asynUser **ppasynUser); asynStatus (*disconnect)(asynUser *pasynUser); asynStatus (*openSocket)(const char *server, int port, char **portName); int (*write)(asynUser *pasynUser, char const *buffer, int buffer_len, double timeout); int (*read)(asynUser *pasynUser, char *buffer, int buffer_len, const char *ieos, int ieos_len, int flush, double timeout, int *eomReason); int (*writeRead)(asynUser *pasynUser, const char *write_buffer, int write_buffer_len, char *read_buffer, int read_buffer_len, const char *ieos, int ieos_len, double timeout, int *eomReason); asynStatus (*flush)(asynUser *pasynUser); int (*writeOnce)(const char *port, int addr, char const *buffer, int buffer_len, double timeout); int (*readOnce)(const char *port, int addr, char *buffer, int buffer_len, const char *ieos, int ieos_len, int flush, double timeout, int *eomReason); int (*writeReadOnce)(const char *port, int addr, const char *write_buffer, int write_buffer_len, char *read_buffer, int read_buffer_len, const char *ieos, int ieos_len, double timeout, int *eomReason); } asynOctetSyncIO; epicsShareExtern asynOctetSyncIO *pasynOctetSyncIO;
asynOctetSyncIO provides a convenient interface for software that needs to perform "synchronous" I/O to an asyn device, i.e. that starts an I/O operation and then blocks while waiting for the response. The code does not need to handle callbacks or understand the details of the asynManager and asynOctet interfaces. Examples include motor drivers running in their own threads, SNL programs, and the shell commands described later in this document.
connect | Connects to an asyn port and address, returns a pointer to an asynUser structure. |
disconnect | Disconnect. This frees all resources allocated by create. |
openSocket | Opens a new connection to a TCP/IP or UDP/IP socket, returning the name of a newly created asyn port. The name of the port created is of the form "server:port [protocol]", i.e. "corvette:21" or "164.54.160.50:21" or "corvette:21 UDP". |
write | Calls asynOctet->write and waits for the operation to complete or time out. |
read | Calls asynOctet->setEos (if ieos_len is non-zero), asynOctet flush (if flush=1), and asynOctet->read. Waits for the operation to complete or time out. |
writeRead | Calls pasynOcter->write, asynOctet->setEos (if ieos_len is non-zero), asynOctet flush (if flush=1), and asynOctet->read. Waits for the operations to complete or time out. |
flush | Calls pasynOctet->flush and waits for the operation to complete. |
writeOnce | This does a connect, write, and disconnect. |
readOnce | This does a connect, read, and disconnect. |
writeReadOnce | This does a connect, writeRead, and disconnect. |
#define asynOctetTrapReadWriteType "asynOctetTrapReadWrite" typedef void (*asynOctetTrapReadWriteCallback) (asynUser *pasynUser, const char *data, int numchars, asynStatus status); /* asynOctetTrapReadWrite methods are thread-safe but might block. * Thus, they can be called directly by a task that is allowed to * block, or indirectly with asynManager->queueRequest() * Note that, when using asynManager->queueRequest, a callback * cannot be installed or removed as long as some other asynUser has * locked the port/address. * * Before pasynUser is freed, it must remove all callbacks that it has * previously installed or the system might crash! */ typedef struct asynOctetTrapReadWrite { asynStatus (*installReadCallback) (void *drvPvt, asynUser *pasynUser, asynOctetTrapReadWriteCallback callback); asynStatus (*installWriteCallback) (void *drvPvt, asynUser *pasynUser, asynOctetTrapReadWriteCallback callback); asynStatus (*removeReadCallback) (void *drvPvt, asynUser *pasynUser, asynOctetTrapReadWriteCallback callback); asynStatus (*removeWriteCallback) (void *drvPvt, asynUser *pasynUser, asynOctetTrapReadWriteCallback callback); } asynOctetTrapReadWrite; int epicsShareAPI asynOctetTrapReadWriteConfig(const char *portName, int addr);
asynOctetTrapReadWriteConfig intercepts all asynOctet messages and calls all registered callbacks for read and write messages.
installReadCallback | install a callback that gets called after each read. |
installWriteCallback | install a callback that gets called after each write. |
removeReadCallback | remove callback. |
removeWriteCallback | remove callback. |
This section descibes interfaces for the following classes of drivers:
This class has the following interfaces:
This class has the following interfaces:
NOTE: UInt32Digital and Float64 should also have synchronous interfaces. This will be provided in the next release.
If a low level driver is declared multi-device the meaning of addr is:
ind*32*32 + shift*32 + nbitswhere:
addr = 1156 = 1*32*32 + 4*32 + 4then bits 0x00F0 of the second register are selected. This rule should provide the proper behavior for trace, etc.
#define asynInt32Type "asynInt32" typedef struct asynInt32{ asynStatus (*write)(void *drvPvt, asynUser *pasynUser, epicsInt32 value); asynStatus (*read)(void *drvPvt, asynUser *pasynUser, epicsInt32 *value); asynStatus (*getBounds)(void *drvPvt, asynUser *pasynUser, epicsInt32 *low, epicsInt32 *high); }asynInt32;
asynInt32 describes the methods implemented by drivers that use integers for communicating with a device.
write | Write an integer value to the device. |
read | Read an integer value from the device. |
getBounds | Get the bounds. |
#define asynInt32CallbackType "asynInt32Callback" /*cancelCallback cancels based on callback and userPvt */ typedef struct asynInt32Callback { asynStatus (*registerCallback)(void *drvPvt, asynUser *pasynUser, void (*callback)(void *userPvt, epicsInt32 data), void *userPvt); asynStatus (*cancelCallback)(void *drvPvt, asynUser *pasynUser, void (*callback)(void *userPvt, epicsInt32 data), void *userPvt); } asynInt32Callback;
asynInt32Callback provides methods for drivers that collect data and make it available to registered clients. An example is an interrupt based driver.
registerCallback | Registers a callback that will be called whenever the value changes. |
cancelCallback | Cancels the callback. |
#define asynInt32ArrayType "asynInt32Array" typedef struct asynInt32Array{ asynStatus (*write)(void *drvPvt, asynUser *pasynUser, epicsInt32 value, size_t nelements); asynStatus (*read)(void *drvPvt, asynUser *pasynUser, epicsInt32 *value, size_t nelements, size_t *nIn); }asynInt32Array;
asynInt32Array describes the methods implemented by drivers that use arrays of integers for communicating with a device.
write | Write an array of integers to a device. |
read | Read an array of integers from a device. |
#define asynInt32ArrayCallbackType "asynInt32ArrayCallback" /*cancelCallback cancels based on callback and userPvt */ typedef struct asynInt32ArrayCallback { asynStatus (*registerCallback)(void *drvPvt, asynUser *pasynUser, void (*callback)(void *userPvt,epicsInt32 *val,epicsUInt32 nelem), void *userPvt); asynStatus (*cancelCallback)(void *drvPvt, asynUser *pasynUser, void (*callback)(void *userPvt,epicsInt32 *val,epicsUInt32 nelem), void *userPvt); } asynInt32ArrayCallback;
asynInt32ArrayCallback provides methods for drivers that collect data and make it available to registered clients. An example is an interrupt based driver.
registerCallback | Register a callback that is called whenever new data is available. |
cancelCallback | Cancel the callback |
#define asynInt32SyncIOType "asynInt32SyncIO" typedef struct asynInt32SyncIO { asynStatus (*connect)(const char *port, int addr, asynUser **ppasynUser); asynStatus (*disconnect)(asynUser *pasynUser); asynStatus (*write)(asynUser *pasynUser, epicsInt32 value,double timeout); asynStatus (*read)(asynUser *pasynUser, epicsInt32 *pvalue,double timeout); asynStatus (*getBounds)(asynUser *pasynUser, epicsInt32 *plow, epicsInt32 *phigh); asynStatus (*writeOnce)(const char *port, int addr, epicsInt32 value,double timeout); asynStatus (*readOnce)(const char *port, int addr, epicsInt32 *pvalue,double timeout); asynStatus (*getBoundsOnce)(const char *port, int addr, epicsInt32 *plow, epicsInt32 *phigh); } asynInt32SyncIO; epicsShareExtern asynInt32SyncIO *pasynInt32SyncIO;
asynInt32SyncIO describes a asyncronous interrace to asynInt32. The code that calls it must be willing to block.
connect | Connects to a port and address, returns a pointer to an asynUser structure. |
disconnect | Disconnect. This frees all resources allocated by create. |
write | Calls pasynInt32->write and waits for the operation to complete or time out. |
read | Calls pasynInt32->read and waits for the operation to complete or time out. |
getBounds | Calls pasynInt32->getBounds and waits for the operation to complete or time out. |
writeOnce | This does a connect, write, and disconnect. |
readOnce | This does a connect, read, and disconnect. |
getBoundsOnce | This does a connect, getBounds, and disconnect. |
#define asynUInt32DigitalType "asynUInt32Digital" typedef struct asynUInt32Digital { asynStatus (*write)(void *drvPvt, asynUser *pasynUser, epicsUInt32 value, epicsUInt32 mask); asynStatus (*read)(void *drvPvt, asynUser *pasynUser, epicsUInt32 *value, epicsUInt32 mask); } asynUInt32Digital;
asynUInt32Digital describes the methods for communicating via bits of an Int32 register.
write | Modify the bits specified by mask with the corresponding bits in value. |
read | Read the bits specified by mask into value. The other bits of value will be set to 0. |
typedef enum { interruptOnZeroToOne, interruptOnOneToZero, interruptOnBoth } interruptReason; #define asynUInt32DigitalCallbackType "asynUInt32DigitalCallback" /*cancelCallback cancels based on callback and userPvt */ typedef struct asynUInt32DigitalCallback { asynStatus (*registerCallback)(void *drvPvt, asynUser *pasynUser, void (*callback)(void *userPvt, epicsUInt32 data), epicsUInt32 mask, void *userPvt); asynStatus (*cancelCallback)(void *drvPvt, asynUser *pasynUser, void (*callback)(void *userPvt, epicsUInt32 data), epicsUInt32 mask, void *userPvt); asynStatus (*setInterrupt)(void *drvPvt, asynUser *pasynUser, epicsUInt32 mask, interruptReason reason); asynStatus (*clearInterrupt)(void *drvPvt, asynUser *pasynUser, epicsUInt32 mask); asynStatus (*getInterrupt)(void *drvPvt, asynUser *pasynUser, epicsUInt32 *mask, interruptReason reason); } asynUInt32DigitalCallback;
asynUInt32Digital describes methods for communicating with interupt driven drivers.
registerCallback | Register a callback that will be called whenever the driver detects a change in any of the bits specified by mask. |
cancelCallback | Cancels the registered callback. Callback, mask and userPvt must all match the values passed to registerCallback. |
setInterrupt | Enable interrupts on bits specified by mask. |
clearInterrupt | Disable interrupts on bits specified by mask. |
getInterrupt | Get enabled interrupt bits |
#define asynFloat64Type "asynFloat64" typedef struct asynFloat64 { asynStatus (*write)(void *drvPvt, asynUser *pasynUser, epicsFloat64 value); asynStatus (*read)(void *drvPvt, asynUser *pasynUser, epicsFloat64 *value); } asynFloat64;
asynFloat64 describes the methods for communicating via IEEE double precision float values.
write | Write a value. |
read | Read a value. |
#define asynFloat64CallbackType "asynFloat64Callback" /*cancelCallback cancels based on callback and userPvt */ typedef struct asynFloat64Callback { asynStatus (*registerCallback)(void *drvPvt, asynUser *pasynUser, void (*callback)(void *userPvt, epicsFloat64 data), void *userPvt); asynStatus (*cancelCallback)(void *drvPvt, asynUser *pasynUser, void (*callback)(void *userPvt, epicsFloat64 data), void *userPvt); } asynFloat64Callback;
asynFloat64Callback is like asynInt32Callback except that it uses Float64 values
registerCallback | Register a callback that is called whenever new data is available. |
cancelCallback | Cancel the callback |
#define asynFloat64ArrayType "asynFloat64Array" typedef struct asynFloat64Array { asynStatus (*write)(void *drvPvt, asynUser *pasynUser, epicsFloat64 *value, size_t nelements); asynStatus (*read)(void *drvPvt, asynUser *pasynUser, epicsFloat64 *value, size_t nelements, size_t *nIn); } asynFloat64Array;
asynFloat64Array describes the methods for communicating via IEEE double precision float values.
write | Write an array of values. |
read | Read an array of values. |
#define asynFloat64ArrayCallbackType "asynFloat64ArrayCallback" /*cancelCallback cancels based on callback and userPvt */ typedef struct asynFloat64ArrayCallback { asynStatus (*registerCallback)(void *drvPvt, asynUser *pasynUser, void (callback)(void *userPvt, epicsFloat64 *data,epicsUInt32 nelems), void *userPvt); asynStatus (*cancelCallback)(void *drvPvt, asynUser *pasynUser, void (callback)(void *userPvt, epicsFloat64 *data,epicsUInt32 nelems), void *userPvt); } asynFloat64ArrayCallback;
asynFloat64ArrayCallback is like asynInt32Callback except that it uses an array of Float64 values
registerCallback | Register a callback that is called whenever new data is available. |
cancelCallback | Cancel the callback |
#define asynDrvUserType "asynDrvUser" typedef struct asynDrvUser { /*The following do not have to be called via queueRequest callback*/ asynStatus (*create)(void *drvPvt,asynUser *pasynUser, const char *drvInfo, const char **pptypeName,size_t *psize); asynStatus (*getType)(void *drvPvt,asynUser *pasynUser, const char **pptypeName,size_t *psize); asynStatus (*destroy)(void *drvPvt,asynUser *pasynUser); }asynDrvUser;
asynDrvUser provides methods that allow an asynUser to communicate user specific information to/from a port driver
create | The driver can create any resources it needs. It can use asynUser.drvUser to provide access to the resources. If the asynUser and the driver both know about how to access the resources they must agree and a name for the resource and a size. If pptypeName is not null the the driver can give a value to *pptypeName. If psize is not null the driver can give a value to *psize. Unless asynUser receives a typeName and size that it recognizes it must not access asynUser.drvUser. |
getType | If other code, e.g. an interposeInterface wants to access asynUser.drvUser it must call this and verify that typeName and size are what it expects. |
destroy | Destroy the resources created by create and set asynUser.drvUser null. |
#define asynOptionType "asynOption" /*The following are generic methods to set/get device options*/ typedef struct asynOption { asynStatus (*setOption)(void *drvPvt, asynUser *pasynUser, const char *key, const char *val); asynStatus (*getOption)(void *drvPvt, asynUser *pasynUser, const char *key, char *val, int sizeval); }asynOption;
asynOption provides a generic way of setting driver specific options. For example the serial port driver uses this to specify baud rate, stop bits, etc.
setOption | Set value associated with key. |
getOption | Get value associated with key. |
This can be used to simulate EOS processing for asynOctet if the port driver doesn't provide EOS support. If an EOS is specified it looks for the eos on each read. It is started by the shell command:
asynInterposeEosConfig port addr
where
this command should appear immediately after the command that initializes a port
This can be used to simulate flush processing for asynOctet if the port driver doesnt provide support for flush. It just reads and discards characters until no more characters arive before timeout seconds have occured. It is started by the shell command:
asynInterposeFlushConfig port addr timeout
where
this command should appear immediately after the command that initializes a port
NOTE: The generic device support for epics records is in an early stage of development. Although it is usable it needs improvement. It will likely evolve rapidly over the next several releases of asynDriver.
Generic device support is provided for standard EPICS records. This support should be usable for a large class of low level register based drivers. For truly complicated devices other support is required.
The support uses the following conventions for DTYP and INP or OUT.
field(DTYP,"asyn xxx") field(INP,"@asyn(portName,addr,timeout) drvParams") or field(OUT,"@asyn(portName,addr,timeout) drvParams")
where
For example:
field(DTYP,"asyn Int32") field(INP,"@asyn(portA,0,.1) thisIsForDriver")
The following support is available:
device(ai,INST_IO,devAiAsynInt32,"asyn Int32") device(ai,INST_IO,devAiAsynFloat64,"asyn Float64") device(ai,INST_IO,devAiAsynInt32Average,"asyn Int32Average") device(ai,INST_IO,devAiAsynFloat64Average,"asyn Float64Average") device(ai,INST_IO,devAiAsynInt32Interrupt,"asyn Int32Interrupt") device(ai,INST_IO,devAiAsynFloat64Interrupt,"asyn Float64Interrupt")
Support for drivers the implement interface Int32 and/or Float64. The Interrupt and Average support requires that the driver supports the asynInt32Callback and/or asynFloat64Callback interfaces.< If Interrupt is chosen the record normallly has SCAN set to "I/O Intr". A record with one of the Average supports is normally periodically scanned. Each time the record processes it receives the average of all the values obtained since the last time the record processed.
The following support is available:
device(ao,INST_IO,devAoAsynInt32,"asyn Int32") device(ao,INST_IO,devAoAsynFloat64,"asyn Float64")
Support for port drivers that implement interface asynInt32 or asynFloat64.
The following support is available:
device(bi,INST_IO,devBiAsynUInt32Digital,"asyn UInt32Digital") device(bo,INST_IO,devBoAsynUInt32Digital,"asyn UInt32Digital") device(longin,INST_IO,devLiAsynUInt32Digital,"asyn UInt32Digital") device(longout,INST_IO,devLoAsynUInt32Digital,"asyn UInt32Digital")
This needs documentation
When a low level driver calls registerPort, it declares if it handles multiple devices. This determines how the addr argument to connectDevice is handled and what getAddr returns.
The addr argument to connectDevice is ignored and getAddr always returns -1
If connectDevice is called with addr<0, the connection is to the port and getAddr always returns -1. If addr>=0, then the caller is connected to the device at the specified address. getAddr will return this address.
asynManager keeps track of the following states:
Is the port or device connected? This state is initialized to disconnected.
Is the port or device enabled? This state is initialized to enabled.
Does asynManager call connect if it finds the port or device disconnected? This is initialized to the state specified in the call to registerPort.
If the port does not support multiple devices, then port and device status are the same. If the port does support multiple devices, then asynManager keeps track of the states for the port and for every device connected to the port.
Whenever any of the states change for a port or device, then all users that previously called exceptionCallbackAdd for that port or device are called.
Low level drivers must call pasynManager:exceptionConnect whenever they connect to a port or port,addr and must call exceptionDisconnect whenever they disconnect.
The methods asynManager:report and asynCommon:report can be called by any thread, but the caller is blocked until the report finishes. The following discussion applys to all methods except report.
The asynManager methods can be called by any thread including portThread. None of these methods (except report) block.
Unless stated otherwise the methods for other interfaces must only be called by the queue callback specified in the call to createAsynUser, i.e. queueRequest must be called.
If a driver calls asynManager:registerPort with the ASYN_CANBLOCK bit of attributes set then asynManager creates a thread for the port. Each portThread has its own set of queues for the calls to queueRequest. portThread runs forever implementing the following algorithm:
The actual code is more complicated because it unlocks before it calls code outside asynManager. This means that the queues can be modified and exceptions may occur.
A special record type asynRecord is provided. Details are described in asynRecord. This section provides a brief description of how to use it.
Each IOC can load one or more instances of asynRecord. An example is:
cd ${ASYN} dbLoadRecords("db/asynRecord.db","P=asyn,R=Test,PORT=L0,ADDR=15,IMAX=0,OMAX=0")
The example creates a record with name "asynTest" (formed from the concatenation of the P and R macros) that will connect to port "L0" and addr 15. After the ioc is started, it is possible to change PORT and/or ADDR. Thus, a single record can be used to access all asyn devices connected to the IOC. Multiple records are only needed if one or more devices need a dedicated record.
An medm display is available for accessing an asynRecord. It is started as follows:
cd <asyn>/medm medm -x -macro "P=asyn,R=Test" asynRecord.adl
The following medm display appears.
The following reads from a device via octet messages:
#include <asynDriver.h> ... #define BUFFER_SIZE 80 typedef struct myData { epicsEventId done; asynOctet *pasynOctet; void *drvPvt; char buffer[BUFFER_SIZE]; }myData; static void queueCallback(asynUser *pasynUser) { myData *pmydata = (myData *)pasynUser->userPvt; asynOctet *pasynOctet = pmydata->pasynOctet; void *drvPvt = pmydata->drvPvt; asynStatus status; int writeBytes,readBytes; int eomReason; asynPrint(pasynUser,ASYN_TRACE_FLOW,"queueCallback entered\n"); status = pasynOctet->write(drvPvt,pasynUser,pmydata->buffer, strlen(pmydata->buffer),&writeBytes); if(status!=asynSuccess) { asynPrint(pasynUser,ASYN_TRACE_ERROR, "queueCallback write failed %s\n",pasynUser->errorMessage); } else { asynPrintIO(pasynUser,ASYN_TRACEIO_DEVICE, pmydata->buffer,strlen(pmydata->buffer), "queueCallback write sent %d bytes\n",writeBytes); } status = pasynOctet->read(drvPvt,pasynUser,pmydata->buffer, BUFFER_SIZE,&readBytes,&eomReason); if(status!=asynSuccess) { asynPrint(pasynUser,ASYN_TRACE_ERROR, "queueCallback read failed %s\n",pasynUser->errorMessage); } else { asynPrintIO(pasynUser,ASYN_TRACEIO_DEVICE, pmydata->buffer,BUFFER_SIZE, "queueCallback read returned: retlen %d eomReason 0x%x data %s\n", readBytes,eomReason,pmydata->buffer); } if(pmydata->done) epicsEventSignal(pmydata->done); } static void asynExample(const char *port,int addr,const char *message) { myData *pmyData; asynUser *pasynUser; asynStatus status; asynInterface *pasynInterface; int canBlock; pmyData = (myData *)pasynManager->memMalloc(sizeof(myData)); memset(pmyData,0,sizeof(myData)); strcpy(pmyData->buffer,message); pasynUser = pasynManager->createAsynUser(queueCallback,0); pasynUser->userPvt = pmyData; status = pasynManager->connectDevice(pasynUser,port,addr); if(status!=asynSuccess) { printf("can't connect to serialPort1 %s\n",pasynUser->errorMessage); exit(1); } pasynInterface = pasynManager->findInterface( pasynUser,asynOctetType,1); if(!pasynInterface) { printf("%s driver not supported\n",asynOctetType); exit(-1); } pmyData->pasynOctet = (asynOctet *)pasynInterface->pinterface; pmyData->drvPvt = pasynInterface->drvPvt; canBlock = 0; pasynManager->canBlock(pasynUser,&canBlock); if(canBlock) pmyData->done = epicsEventCreate(epicsEventEmpty); status = pasynManager->queueRequest(pasynUser,asynQueuePriorityLow, 0.0); if(status) { asynPrint(pasynUser,ASYN_TRACE_ERROR, "queueRequest failed %s\n",pasynUser->errorMessage); } if(canBlock) epicsEventWait(pmyData->done); status = pasynManager->freeAsynUser(pasynUser); if(status) { asynPrint(pasynUser,ASYN_TRACE_ERROR, "freeAsynUser failed %s\n",pasynUser->errorMessage); } epicsEventDestroy(pmyData->done); pasynManager->memFree(pasynUser->userPvt,sizeof(myData)); }
The flow of control is as follows:
The asynDriver distribution includes code to test asynDriver. It is also an example of how to interface to asynManager. The example resides in <top>/testApp and contains the following components:
Db/ test.db adl/ test.adl src/ devAsynTest.c devAsynTest.dbd echoDriver.c interposeInterface.c
echoDriver is a port driver that echos messages it receives. It implements asynCommon and asynOctet. When asynOctet:write is called it saves the message. When asynOctet:read is called, the saved message is returned and the message is flushed. echoDriverInit has an argument that determines if it acts like a multiDevice or a single device port driver.
An instance of echoDriver is started via the iocsk command:
echoDriverInit(portName,delay,noAutoConnect,multiDevice)
where
test.db is a template containing three records: a calc record, which forward links to a stringout record which forward links to a stringin record. The string records attach to the device support supplied by devAsynTest.c. The stringout and stringin records share the same asynUser. When the stringout record processes it:
devAsynTest also does additional checking for connect state and enable/disable.
Executing "medm -x test.adl" produces the display:
It assumes that an ioc has been started via:
cd <top>/iocBoot/ioctest ../../bin/solaris-sparc/test st.cmd
This starts two versions of echoDriver as port "A" and "B". port A acts as single device port. port B acts as a multiDevice port that has two devices. For each of the three possible devices, the st.cmd file starts up two sets of records from test.db The st.cmd file also loads a set of records from asynTest.db for port A and for port B and for each of the two devices attached to port B. It also loads a set of records from asynGeneric.db.
GPIB has additional features that are not supported by asynCommon and asynOctet. asynGpib defines two interfaces.
asynGpibDriver.h contains the following definitions:
#include "asynDriver.h" #define asynGpibType "asynGpib" #define asynGpibType "asynGpib" /* GPIB drivers */ typedef void (*srqHandler)(void *userPrivate,int gpibAddr,int statusByte); typedef struct asynGpib asynGpib; typedef struct asynGpibPort asynGpibPort; /*asynGpib defines methods called by gpib aware users*/ struct asynGpib{ /*addressedCmd,...,ren are just passed to device handler*/ asynStatus (*addressedCmd) (void *drvPvt,asynUser *pasynUser, const char *data, int length); asynStatus (*universalCmd) (void *drvPvt,asynUser *pasynUser,int cmd); asynStatus (*ifc) (void *drvPvt,asynUser *pasynUser); asynStatus (*ren) (void *drvPvt,asynUser *pasynUser, int onOff); /* The following are implemented by asynGpib */ asynStatus (*registerSrqHandler)(void *drvPvt,asynUser *pasynUser, srqHandler handler, void *srqHandlerPvt); void (*pollAddr)(void *drvPvt,asynUser *pasynUser, int onOff); /* The following are called by low level gpib drivers */ /*srqHappened is passed the pointer returned by registerPort*/ void *(*registerPort)( const char *portName, int attributes,int autoConnect, asynGpibPort *pasynGpibPort, void *asynGpibPortPvt, unsigned int priority, unsigned int stackSize); void (*srqHappened)(void *asynGpibPvt); }; epicsShareExtern asynGpib *pasynGpib; struct asynGpibPort { /*asynCommon methods */ void (*report)(void *drvPvt,FILE *fd,int details); asynStatus (*connect)(void *drvPvt,asynUser *pasynUser); asynStatus (*disconnect)(void *drvPvt,asynUser *pasynUser); /*asynOctet methods passed through from asynGpib*/ asynStatus (*read)(void *drvPvt,asynUser *pasynUser, char *data,int maxchars,int *nbytesTransfered, int *eomReason); asynStatus (*write)(void *drvPvt,asynUser *pasynUser, const char *data,int numchars,int *nbytesTransfered); asynStatus (*flush)(void *drvPvt,asynUser *pasynUser); asynStatus (*setEos)(void *drvPvt,asynUser *pasynUser, const char *eos,int eoslen); asynStatus (*getEos)(void *drvPvt,asynUser *pasynUser, char *eos, int eossize, int *eoslen); /*asynGpib methods passed thrtough from asynGpib*/ asynStatus (*addressedCmd) (void *drvPvt,asynUser *pasynUser, const char *data, int length); asynStatus (*universalCmd) (void *drvPvt, asynUser *pasynUser, int cmd); asynStatus (*ifc) (void *drvPvt,asynUser *pasynUser); asynStatus (*ren) (void *drvPvt,asynUser *pasynUser, int onOff); /*asynGpibPort specific methods */ asynStatus (*srqStatus) (void *drvPvt,int *isSet); asynStatus (*srqEnable) (void *drvPvt, int onOff); asynStatus (*serialPollBegin) (void *drvPvt); asynStatus (*serialPoll) (void *drvPvt, int addr, double timeout,int *status); asynStatus (*serialPollEnd) (void *drvPvt); };
asynGpib describes the interface for device support code. It provides gpib specific functions like SRQ handling. It makes calls to asynGpibPort. Note that asynGpib.c also implements asynCommon and asynOctet.
addressedCmd | The request is passed to the low level driver. |
universalCmd | The request is passed to the low level driver. |
ifc | The request is passed to the low level driver. |
ren | The request is passed to the low level driver. |
registerSrqHandler | Register an srq handler for device. The handler will be called when an SRQ is detected for that device. |
pollAddr | Set SRQ polling on or off. onOff = (0,1) means (disable, enable) SRQ polling of specified address. |
registerPort | Register a port. When asynGpib receives this request, it calls asynManager.registerPort. |
srqHappened | Called by low level driver when it detects that a GPIB device issues an SRQ. |
asynGpibPort is the interface that is implemented by gpib drivers, e.g. the VXI-11. It provides:
asynCommon methods | All the methods of asynCommon |
asynOctet methods | All the methods of asynOctet |
addressedCmd | Issue a GPIB addressed command. |
universalCmd | Issue a GPIB universal command. |
ifc | Issue a GPIB Interface Clear command. |
ren | Issue a GPIB Remote Enable command |
srqStatus | If return is asynSuccess then isSet is (0,1) if SRQ (is not, is) active. Normally only called by asynGpib. |
srqEnable | Enable or disable SRQs. Normally only called by asynGpib. |
serialPollBegin | Start of serial poll. Normally only called by asynGpib. |
serialPoll | Poll the specified address and set status to the response. Normally only called by asynGpib. |
serialPollEnd | End of serial poll. Normally only called by asynGpib. |
The drvAsynSerialPort driver supports devices connected to serial ports on the IOC.
Serial ports are configured with the drvAsynSerialPortConfigure and asynSetOption commands:
drvAsynSerialPortConfigure("portName","ttyName",priority,noAutoConnect,noEos) asynSetOption("portName",addr,"key","value")where the arguments are:
The setEos and getEos methods have no effect and return asynError. The read method blocks until at least one character has been received or until a timeout occurs. The read method transfers as many characters as possible, limited by the specified count. asynInterposeEos can be used to support EOS.
The following table summarizes the drvAsynSerialPort driver asynSetOption keys and values. Default values are enclosed in square brackets.
Key | Value |
---|---|
baud | [9600] 50 75 110 134 150 200 300 600 1200 ... 230400 |
bits | [8] 7 6 5 |
parity | [none] even odd |
stop | [1] 2 |
clocal | [Y] N |
ctrscts | [N] Y |
The clocal and crtscts parameter names are taken from the POSIX termios serial interface definition. The clocal parameter controls whether the modem control lines (Data Terminal Ready, Carrier Detect/Received Line Signal Detect) are used (clocal=N) or ignored (clocal=Y). The crtscts parameter controls whether the hardware handshaking lines (Request To Send, Clear To Send) are used (crtscts=Y) or ignored (crtscts=N). The default parameter values (clocal=Y, crtscts=N) are those of a 'data-leads-only' serial interface.
The vxWorks serial driver does not provide independent control of the hardware handshaking and modem control lines, thus clocal=Y implies crtscts=N and clocal=N implies crtscts=Y.
vxWorks IOC serial ports may need to be set up using hardware-specific commands. Once this is done, the standard drvAsynSerialPortConfigure and asynSetOption commands can be issued. For example, the following example shows the configuration procedure for a port on a GreenSprings octal UART Industry-Pack module on a GreenSprings VIP616-01 carrier.
ipacAddVIPC616_01("0x6000,B0000000") tyGSOctalDrv(1) tyGSOctalModuleInit("RS232", 0x80, 0, 0) tyGSOctalDevCreate("/tyGS/0/0",0,0,1000,1000) drvAsynSerialPortConfigure("L0","/tyGS/0/0",0,0,0) asynSetOption("L0",0,"baud","9600")
The drvAsynIPPort driver supports devices which communicate over a TCP/IP or UDP/IP connection. A typical example is a device connected through an Ethernet/Serial converter box.
TCP/IP or UDP/IP connections are configured with the drvAsynIPPortConfigure command:
drvAsynIPPortConfigure("portName","hostInfo",priority,noAutoConnect,noEos)where the arguments are:
The setEos and getEos methods have no effect and return asynError. The read method blocks until at least one character has been received or until a timeout occurs. The read method transfers as many characters as possible, limited by the specified count.
There are no asynSetOption key/value pairs associated with drvAsynIPPort connections.
asynInterposeEos and asynInterposeFlush can be used to provide additional functionality.
Consult the following documents (available on-line) for details.
VMEbus Extensions for Instrumentation VXI-11 TCP/IP Instrument Protocol Specification VXI-11.1 TCP/IP-VXIbus Interface Specification VXI-11.2 TCP/IP-IEEE 488.1 Interface Specification VXI-11.3 TCP/IP-IEEE 488.2 Instrument Interface Specification
The following commands may be specified in the st.cmd file
E2050Reboot("inet_addr") E5810Reboot("inet_addr","password") vxi11Configure("portName","inet_addr",recoverWithIFC,timeout, "vxiName",priority,noAutoConnect)where
The vxi11 driver implements two timeouts: ioTimeout and rpcTimeout (Remote Procedure Call timeout). The ioTimeout is taken from asynUser:timeout. The rpcTimeout is handled internally for each port. It has a default of 4 seconds but can be changed by calling setOptions. For example:
asynSetOption L0 -1 rpctimeout .1Will change the rpcTimeout for port L0 to .1 seconds.
This is support for the Green Springs Industry Pack GPIB carrier. The configuration command is:
gsIP488Configure(portName,carrier,module,intVec,priority,noAutoConnect)where
An example is:
#The following is for the Greensprings IP488 on an MV162 ipacAddMVME162("A:l=3,3 m=0xe0000000,64") gsIP488Configure("L0",0,0,0x61,0,0)
This is support for a National Instruments VME GPIB interface. The configuration command is:
ni1014Config(portNameA,portNameB,base,vector,level,priority,noAutoConnect)where
An example is:
ni1014Config("L0","L1",0x5000,0x64,5,0,0)
NOTES:
asynReport(filename,level) asynInterposeFlushConfig(portName,addr,timeout) asynInterposeEosConfig(portName,addr) asynSetTraceMask(portName,addr,mask) asynSetTraceIOMask(portName,addr,mask) asynSetTraceFile(portName,addr,filename) asynSetTraceIOTruncateSize(portName,addr,size) asynSetOption(portName,addr,key,val) asynShowOption(portName,addr,key) asynAutoConnect(portName,addr,yesNo) asynEnable(portName,addr,yesNo) asynOctetConnect(entry,portName,addr,oeos,ieos,timeout,buffer_len) asynOctetRead(entry,nread,flush) asynOctetWrite(entry,output) asynOctetWriteRead(entry,output,nread) asynOctetFlush(entry)
asynReport
calls asynCommon:report
for all
registered drivers and interposeInterface.
asynInterposeFlushConfig
is a generic interposeInterface that
implements flush for low level drivers that don't implement flush. It just
issues read requests until no bytes are left to read. The timeout is used for
the read requests.
asynInterposeEosConfig
is a generic interposeInterface that
implements End of String processing for low level drivers that don't.
asynSetTraceMask
calls asynTrace:setTraceMask
for the specified port and address.
asynSetTraceIOMask
calls
asynTrace:setTraceIOMask
for the specified port and address.
asynSetTraceFile
calls asynTrace:setTraceFile
.
The filename is handled as follows:
asynSetTraceIOTruncateSize
calls
asynTrace:setTraceIOTruncateSize
asynSetOption
calls asynCommon:setOption
.
asynShowOption
calls asynCommon:getOption
.
asynOctetConnect, ...asynOctetFlush provide shell access to asynOctetSyncIO methods. The entry is a character string constant that identifys the port,addr.
where
stdout.
The commands asynOctetConnect, asynOctetRead, asynOctetWrite, asynOctetWriteRead, asynOctetFlush allow I/O to a device from the ioc shell. Examples are:
asynOctetConnect("myid","A",0,"\n","\n",1,20) asynOctetWrite("myid","testnew") asynOctetRead("myid") testnew\n asynOctetWriteRead("myid","this is test") this is test\n
.../support/asyn/
gunzip <file>.tar.gz tar xvf <file>.tar
.../support/asyn/X-Ywhere X-Y is the release number. For example:
.../support/asyn/3-1
Since asynDriver does NOT provide support for specific devices an application must obtain device specific support elsewhere. This section only explains how to include asynDriver components.
In the configure/RELEASE
file add definitions for
IPAC
, ASYN
, and EPICS_BASE
.
In the src directory where the application is built:
Makefile
<app>_LIBS += asyn
<app>Include.dbd
and uncomment
the line or lines appropriate for your application:
include "asyn.dbd" #include "drvAsynSerialPort.dbd" #include "drvAsynIPPort.dbd" #include "drvVxi11.dbd" #include "drvGsIP488.dbd" #include "drvIpac.dbd" #registrar(mv162ipRegistrar)
In the st.cmd
file add:
dbLoadRecords("db/asynRecord.db", "P=<ioc>, R=<record>, PORT=<port>, ADDR=<addr>, OMAX=<omax>, IMAX=<imax>")
You must provide values for <ioc>, <record>, <port>, <addr>, <omax>, and <imax>.
Once the application is running, medm displays for an ioc can be started by: medm -x -macro "P=<ioc>,R=<record>" <asyntop>/medm/asynRecord.adl &
You must provide correct values for <ioc> and <record>. Once asynRecord is started, it can be connected to different devices.
Copyright (c) 2002 University of Chicago All rights reserved. asynDriver is distributed subject to the following license conditions: SOFTWARE LICENSE AGREEMENT Software: asynDriver 1. The "Software", below, refers to asynDriver (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.