How to create EPICS device support for a simple serial or GPIB deviceW. Eric Norum |
This tutorial provides step-by-step instructions on how to create EPICS support for a simple serial or GPIB (IEEE-488) device. The steps are presented in a way that should make it possible to apply them in cookbook fashion to create support for other devices. For comprehensive description of all the details of the I/O system used here, refer to the asynDriver and devGpib documentation.
This document isn't for the absolute newcomer though. You must have EPICS installed on a system somewhere and know how to build and run the example application. In particular you must have the following installed:
Serial and GPIB devices can now be treated in much the same way. The EPICS 'asyn' driver devGpib module can use the low-level drivers which communicate with serial devices connected to ports on the IOC or to Ethernet/Serial converters or with GPIB devices connected to local I/O cards or to Ethernet/GPIB converters.
I based this tutorial on the device support I wrote for a CVI Laser Corporation AB300 filter wheel. You're almost certainly interested in controlling some other device so you won't be able to use the information directly. I chose the AB300 as the basis for this tutorial since the AB300 has a very limited command set, which keeps this document small, and yet has commands which raise many of the issues that you'll have to consider when writing support for other devices.
If you'd like to print this tutorial you can download a PDF version.
The first order of business is to determine the set of operations the device will have to perform. A look at the AB300 documentation reveals that there are four commands that must be supported. Each command will be associated with an EPICS process variable (PV) whose type must be appropriate to the data transferred by the command. The AB300 commands and process variable record types I choose to associate with them are shown in table 12Determine the required I/O operationstable.1.
CVI Laser Corporation AB300 filter wheel Command EPICS record type Reset longout Go to new position longout Query position longin Query status longin
There are lots of other ways that the AB300 could be handled. It might be useful, for example, to treat the filter position as multi-bit binary records instead.
Now that the device operations and EPICS process variable types have been chosen it's time to create a new EPICS application to provide a place to perform subsequent software development. The easiest way to do this is with the makeSupport.pl script supplied with the EPICS ASYN package.
Here are the commands I ran. You'll have to change the /home/EPICS/modules/soft/asyn to the path where your EPICS ASYN driver is installed.
Edit the configure/RELEASE file which makeSupport.pl created and confirm that the entries describing the paths to your EPICS base and ASYN support are correct. For example these might be:
EPICS_BASE=/home/EPICS/base
Edit the configure/CONFIG file which makeSupport.pl created and specify the IOC architectures on which the application is to run. I wanted the application to run as a soft IOC, so I uncommented the CROSS_COMPILER_TARGET_ARCHS definition and set the definition to be empty:
The contents of the device support file provide all the details of the communication between the device and EPICS. The makeSupport.pl command created a skeleton device support file in AB300Sup/devAB300.c. Of course, device support for a device similar to the one you're working with provides an even easier starting point.
The remainder this section describes the changes that I made to the skeleton file in order to support the AB300 filter wheel. You'll have to modify the steps as appropriate for your device.
Since the AB300 provides only longin and longout records most of the DSET_xxx define statements can be removed although leaving them in place will not hurt anything. Because of the way that the device initialization is performed you must define an analog-in DSET even if the device provides no analog-in records (as is the case for the AB300).
The default value of TIMEWINDOW (2 seconds) is reasonable for the AB300, but I increased the value of TIMEOUT to 5 seconds since the filter wheel can be slow in responding.
The skeleton file provides a number of example character string arrays. None are needed for the AB300 so I just removed them. Not much space would be wasted by just leaving them in place however.
This is the hardest part of the job. Here's where you have to figure how to produce the command strings required to control the device and how to convert the device responses into EPICS process variable values.
Each command array entry describes the details of a single I/O operation type. The application database uses the index of the entry in the command array to provide the link between the process variable and the I/O operation to read or write that value.
The command array entries I created for the AB300 are shown below. The elements of each entry are described using the names from the GPIB documentation.
Note that the process variable value is not used (there's no printf % format character in the command string). The AB300 is reset whenever the EPICS record is processed.
This command array entry is almost identical to the previous entry. The only change is that a different custom conversion function is used.
As mentioned above, special conversion functions are need to convert reply messages from the AB300 into EPICS PV values. The easiest place to put these functions is just before the gpibCmds table. The conversion functions are passed a pointer to the gpibDpvt structure and three values from the command table entry. The gpibDpvt structure contains a pointer to the EPICS record. The custom conversion function uses this pointer to set the record's value field.
Here are the custom conversion functions I wrote for the AB300.
Some points of interest:
Because of way code is stored in object libraries on different systems the device support parameter table must be initialized at run-time. The analog-in initializer is used to perform this operation. This is why all device support files must declare an analog-in DSET.
Here's the initialization for the AB300 device support. The AB300 immediately echos the command characters sent to it so the respond2Writes value must be set to 0. All the other values are left as created by the makeSupport.pl script:
This file specifies the link between the DSET names defined in the device support file and the DTYP fields in the application database. The makeSupport.pl command created an example file in AB300Sup/devAB300.dbd. If you removed any of the DSET_xxx definitions from the device support file you must remove the corresponding lines from this file.
device(ai, GPIB_IO, devAiAB300, "AB300") device(longin, GPIB_IO, devLiAB300, "AB300") device(longout, GPIB_IO, devLoAB300, "AB300") include "asyn.dbd"
This is the database describing the actual EPICS process variables associated with the filter wheel.
I modified the file AB300Sup/devAB300.db to have the following contents:
record(longout, "$(P)$(R)FilterWheel:reset") { field(DESC, "Reset AB300 Controller") field(SCAN, "Passive") field(DTYP, "AB300") field(OUT, "#L$(L) A$(A) @0") } record(longout, "$(P)$(R)FilterWheel") { field(DESC, "Set Filter Wheel Position") field(SCAN, "Passive") field(DTYP, "AB300") field(OUT, "#L$(L) A$(A) @1") field(LOPR, 1) field(HOPR, 6) } record(longin, "$(P)$(R)FilterWheel:fbk") { field(DESC, "Filter Wheel Position") field(SCAN, "Passive") field(DTYP, "AB300") field(INP, "#L$(L) A$(A) @2") field(LOPR, 1) field(HOPR, 6) } record(longin, "$(P)$(R)FilterWheel:status") { field(DESC, "Filter Wheel Status") field(SCAN, "Passive") field(DTYP, "AB300") field(INP, "#L$(L) A$(A) @3") }
Notes:
Change directories to the top-level directory of your device support and:
(gnumake on Solaris).
If all goes well you'll be left with a device support library in lib/<EPICS_HOST_ARCH>/, a device support database definition in dbd/ and a device support database in db/.
Now that the device support has been completed it's time to create a new EPICS application to confirm that the device support is operating correctly. The easiest way to do this is with the makeBaseApp.pl script supplied with EPICS.
Here are the commands I ran. You'll have to change the /home/EPICS/base to the path to where your EPICS base is installed. If you're not running on Linux you'll also have to change all the linux-x86 to reflect the architecture you're using (solaris-sparc, darwin-ppc, etc.). I built the test application in the same <top> as the device support, but the application could be built anywhere. As well, I built the application as a 'soft' IOC running on the host machine, but the serial/GPIB driver also works on RTEMS and vxWorks.
Several files need minor modifications to use the device support in the test, or any other, application.
Edit the configure/RELEASE file which makeBaseApp.pl created and confirm that the EPICS_BASE path is correct. Add entries for your ASYN and device support. For example these might be:
ASYN=/home/EPICS/modules/soft/asyn/3-2
EPICS_BASE=/home/EPICS/base
Your application database definition file must include the database definition files for your instrument and for the ASYN drivers. There are two ways that this can be done:
include "base.dbd" include "devAB300.dbd" include "drvAsynIPPort.dbd" include "drvAsynSerialPort.dbd"
You must link your device support library and the ASYN support library with the application. Add the following lines
xxx_LIBS += asyn
before the
line in the application Makefile.
The st.cmd application startup script created by the makeBaseApp.pl script needs a few changes to get the application working properly.
dbLoadRecords("db/devAB300.db","P=AB300:,R=,L=0,A=0")
In all of the above examples the first argument of the configure and set port option commands is the link identifier and must match the L value in the EPICS database record INP and OUT fields. The second argument of the configure command identifies the port to which the connection is to be made. The third argument sets the priority of the worker thread which performs the I/O operations. A value of zero directs the command to choose a reasonable default value. The fourth argument is zero to direct the device support layer to automatically connect to the serial port on startup and whenever the serial port becomes disconnected. The final argument is zero to direct the device support layer to use standard end-of-string processing on input messages.
A better way to control the amount and type of diagnostic output is to add an asynRecord to your application.
Change directories to the top-level directory of your application and:
(gnumake on Solaris).
If all goes well you'll be left with an executable program in bin/linux-x86/AB300.
Change directories to where makeBaseApp.pl put the application startup script and run the application:
dbLoadRecords("db/devAB300.db","P=AB300:,R=,L=0,A=0")
drvAsynIPPortConfigure("L0","164.54.3.137:4001",0,0,0)
asynSetTraceMask("L0",-1,0x9)
asynSetTraceIOMask("L0",-1,0x2)
iocInit()
############################################################################
### EPICS IOC CORE built on Apr 23 2004
### EPICS R3.14.6 $$Name: $$ $$Date: 2006/06/04 00:55:03 $$
############################################################################
Starting iocInit
iocInit: All initialization complete
Check the process variable names:
Reset the filter wheel. The values sent between the IOC and the filter wheel are shown:
Read back the filter wheel position. The dbtr command prints the record before the I/O has a chance to occur:
Now the process variable should have that value:
Move the wheel to position 4:
Read back the position:
And it really is 4:
Here is the complete device support file for the AB300 filter wheel (AB300Sup/devAB300.c):
/* * AB300 device support */ #include <epicsStdio.h> #include <devCommonGpib.h> /****************************************************************************** * * The following define statements are used to declare the names to be used * for the dset tables. * * A DSET_AI entry must be declared here and referenced in an application * database description file even if the device provides no AI records. * ******************************************************************************/ #define DSET_AI devAiAB300 #define DSET_LI devLiAB300 #define DSET_LO devLoAB300 #include <devGpib.h> /* must be included after DSET defines */ #define TIMEOUT 5.0 /* I/O must complete within this time */ #define TIMEWINDOW 2.0 /* Wait this long after device timeout */ /* * Custom conversion routines */ static int convertPositionReply(struct gpibDpvt *pdpvt, int P1, int P2, char **P3) { struct longinRecord *pli = ((struct longinRecord *)(pdpvt->precord)); if (pdpvt->msgInputLen != 3) { epicsSnprintf(pdpvt->pasynUser->errorMessage, pdpvt->pasynUser->errorMessageSize, "Invalid reply"); return -1; } pli->val = pdpvt->msg[0]; return 0; } static int convertStatusReply(struct gpibDpvt *pdpvt, int P1, int P2, char **P3) { struct longinRecord *pli = ((struct longinRecord *)(pdpvt->precord)); if (pdpvt->msgInputLen != 3) { epicsSnprintf(pdpvt->pasynUser->errorMessage, pdpvt->pasynUser->errorMessageSize, "Invalid reply"); return -1; } pli->val = pdpvt->msg[1]; return 0; } /****************************************************************************** * * Array of structures that define all GPIB messages * supported for this type of instrument. * ******************************************************************************/ static struct gpibCmd gpibCmds[] = { /* Param 0 -- Device Reset */ {&DSET_LO, GPIBWRITE, IB_Q_LOW, NULL, "\377\377\033", 10, 10, NULL, 0, 0, NULL, NULL, "\033"}, /* Param 1 -- Go to new filter position */ {&DSET_LO, GPIBWRITE, IB_Q_LOW, NULL, "\017%c", 10, 10, NULL, 0, 0, NULL, NULL, "\030"}, /* Param 2 -- Query filter position */ {&DSET_LI, GPIBREAD, IB_Q_LOW, "\035", NULL, 0, 10, convertPositionReply, 0, 0, NULL, NULL, "\030"}, /* Param 3 -- Query controller status */ {&DSET_LI, GPIBREAD, IB_Q_LOW, "\035", NULL, 0, 10, convertStatusReply, 0, 0, NULL, NULL, "\030"} }; /* The following is the number of elements in the command array above. */ #define NUMPARAMS sizeof(gpibCmds)/sizeof(struct gpibCmd) /****************************************************************************** * * Initialize device support parameters * *****************************************************************************/ static long init_ai(int pass) { if(pass==0) { devSupParms.name = "devAB300"; devSupParms.gpibCmds = gpibCmds; devSupParms.numparams = NUMPARAMS; devSupParms.timeout = TIMEOUT; devSupParms.timeWindow = TIMEWINDOW; devSupParms.respond2Writes = 0; } return(0); }
The asynRecord provides a convenient mechanism for controlling the diagnostic messages produced by asyn drivers. To use an asynRecord in your application:
To run the asynRecord screen, add <asynTop>/medm to your EPICS_DISPLAY_PATH environment variable and start medm with P, R, PORT and ADDR values matching those given in the dbLoadRecords command:
Devices which use the same character, or characters, to mark the end of each command or response message need not specify these characters in the GPIB command table entries. They can, instead, specify the terminator sequences as part of the driver port configuration commands. This makes it possible for a single command table to provide support for devices which provide a serial or Ethernet interface (and require command/response terminators) and also provide a GPIB interface (which does not require command/response terminators).
For example, the configuration commands for a TDS3000 digital oscilloscope connected through an Ethernet serial port adapter might look like:
The configuration command for the same oscilloscope connected to an Ethernet GPIB adapter would be:
An example command table entry for this device is shown below. Notice that there is no \n at the end of the command and that the table 'eos' field is NULL:
This document was translated from LATEX by HEVEA.