This product is available via the open source license described at the end of this document
This manual describes GPIB support for EPICS. It provides the following features
The support consists of the following components:
It is designed to support any device that uses the VXIBus VXI-11 standard.. This is supported on vxWorks, RTEMS, Solaris, Linux, Darwin and HPUX.
drvCommonGpib is written so that support for additional GPIB controllers can be provided. Support is available for Industry Pack and for GPIB via bitbus in other unbundled EPICS products.
After obtaining a copy of the distribution, it must be installed and built for use at your site.
.../support/gpibCore/
gunzip <file>.tar.gz tar xvf <file>.tar
.../support/gpibCore/gpibCoreX-Ywhere X-Y is the release. For example.
.../support/gpibCore/gpibCore2-0beta1
gpibCore includes a test application. Two ioc boot directories are provided: iochost and iocvxWorks. Each contains a st.cmd file. Both configure drvVxi11, and on vxWorks also configures drvNi1014. Before booting you must edit the st.cmd files.
After editing the above file boot your IOC. Once your IOC is running just type:
GI
GI is an interactive tool that allows you to send commands and receive replies from gpib devices. It should be self explanatory.
Directory <top>/testApp/adl contains two files: startmedmHpLan and startmedmNi1014. Choose the one for the gpib driver you are testing.
gpibCore includes a test application: a test device support module, a test database, and an medm adl file.
The device support modules provides support for the following GPIB commands.
An EPICS record is connected to gpib by the fields DTYP and INP or OUT.
The DTYP field has the format:
field(DTYP,"<device support name>")
where
<device support name> - The name from a device database definition, i.e. a definition of the form:
device(<record type>,GPIB_IO,<dsetName>,"<device support name>")
You or someone else must provide the device support for specific gpib devices.
The INP or OUT field has the format:
field(INP,"#L<link> A<addr> @<number>") or field(INP,"#L<link> A<addr> @<number>")where:
A9 primary address 9 A900 extended address: primary address is 9, secondary address is 0 A906 extended address: primary address is 9, secondary address is 6.
recordType @<number> Description
If such documentation is not available, look at the device support itself for statements like:
/* Param 12 */ {&DSET_LI, GPIBREAD, IB_Q_LOW, "*ESR?", "%ld", 0, 20, 0, 0, 0, 0, 0, -1},
The above states that @12 is a gpib read command via a longin record. Thus the record definition would be:
record(longin,"<name>") { field(DTYP,"<device support name>") ... field(OUT,"#L<link> A<addr> @12") ... }
Before the definition of EPICS_BASE add the statement:
GPIB=<full path name to gpibCore>
Add the statements:
<prod>_LIBS += gpib
Add one or more of the statements as appopriate for your application:
include "gpib.dbd" include "drvVxi11.dbd" include "drvTermiosTty.dbd" include "drvNi1014.dbd"NOTES:
In addition you must provide definitions for any device support you need. Look at testApp/src/ for an example of how to build and use gpib device support.
Must contain commands to configure gpib low level drivers. gpibCore supports three low level drivers: drvVxi11,drvTermiosTty, and drvNi1014.
The configuration commands for drvVxi11 are:
drvVxi11Config(int link, char *inetAddr, int recoverWithIFC, int defTimeout, char *vxiName,int isSingleLink) E2050Reboot(char *inetAddr) E5810Reboot(char *inetAddr,char *password)
where:
The configuration command for drvNi1014 is:
drvNi1014GpibConfig(int link,int nPorts, int base, int vector, int level)
where:
The configuration command for drvTermiosTtyGpib is:
drvTermiosTtyGpibConfigure(int link,char *serialLineName,int openOnReset)
where:
Serial line parameters can be set with the IOC shell stty command. The stty command accepts the following arguments:
Serial line parameters should be set before the call to drvTermiosTtyGpibConfigure.
Commands that can be issued via the IOC shell.
drvGpibLogData(int onOff,int link,int addr)
drvGpibPoll(int link,int addr,int onOff)
drvGpibResetLink(int link)
link - link to reset
setibDebug(int value)
value - The higher the value the more debugging information is displayed
setibSeqDebug(int value)
value - The higher the value the more debugging information is displayed
setibSrqLock(int value)
value - (0,1) means (don't, do) allow srq processing.
setibTimeoutSquelch(int value)
value - (0,1) means (don't, do) report srq processing errors.
setibSrqTimeout(int value)
value - Number of seconds to wait for sollicited SRQs.
setibSrqRingSize(int value)
value - Number of SRQ events in ring buffer. MUST be set before iocInit.
setibSrqPollTimeout(int value)
value - Timeout in milliseconds for srqPoll of a particular device.
setibSrqPollRate(double value)
value - How often (in seconds) to issue an srqStatus. Some low level devices do not support SRQ interrupts. drvCommonGpib periodically asks if an SRQ is present. The command is used to set the rate.
This section describes how to write device support for GPIB devices. It is assumed that the reader is already familiar with the dialogue required to operate a GPIB instrument and EPICS record and device support. gpibCore provides a facility devCommon for creating support for specific gpib devices.
A GPIB device support module provides access to the operating parameters of a GPIB device.
GPIB devices typically have many parameters, each of which may be thought of in terms of the standard types of database records available in EPICS. It is the job of the device support module designer to decide how the mapping of these parameters will be made to the available record types. Once this mapping is complete, the device support module may be written.
The writing of the device support module consists primarily of the construction of a parameter table. This table is used to associate the database record types with the operating parameters of the GPIB instrument.
Other aspects of module design include the handling of SRQ events and errors. SRQ processing is described in a separate section below.
device(<record type>,<link type>,<DSET name>,"<DTYP name>")
where:
For example the definitions for the test supplied with gpibCore are:
device(ai,GPIB_IO,devAiTestGpib,"GPIB Test") device(ao,GPIB_IO,devAoTestGpib,"GPIB Test") device(bi,GPIB_IO,devBiTestGpib,"GPIB Test") device(bo,GPIB_IO,devBoTestGpib,"GPIB Test") device(longin,GPIB_IO,devLiTestGpib,"GPIB Test") device(longout,GPIB_IO,devLoTestGpib,"GPIB Test") device(mbbi,GPIB_IO,devMbbiTestGpib,"GPIB Test") device(mbbo,GPIB_IO,devMbboTestGpib,"GPIB Test") device(stringin,GPIB_IO,devSiTestGpib,"GPIB Test") device(stringout,GPIB_IO,devSoTestGpib,"GPIB Test")
For more information about device support, and also how to define INP or OUT links of records, see the the EPICS Application Developers Guide.
The common GPIB support code includes a file >top>/gpib/devCommon/devSkeletonGpib.c. If you are writing a new GPIB device support module just:
A GPIB device support module consists of DSET entries, an array of gpibCmds, efast tables (optional), name tables (optional), a devGpibParmBlock, a debugging flag, and init_ai routine, an SRQ handler function (optional), and some custom conversion functions (optional.).
A simplified version of the skeleton file is:
/* devSkeletonGpib.c */ #include <devCommonGpib.h> /* define all desired DSETs */ #define DSET_AI devAiSkeletonGpib #define DSET_BI devBiSkeletonGpib #define DSET_MBBI devMbbiSkeletonGpib #include <devGpib.h> /* must be included after DSET defines */ int SkeletonDebug = 0; #define TIME_WINDOW 2000 /* wait 2 seconds after device timeout */ #define IO_TIME 1000 /* wait 1 second for IO */ /* example choices for BI */ static char *offOnList[] = { "Off", "On" }; static struct devGpibNames offOn = { 2, offOnList, NULL, 1 }; /* example EFAST table */ static char *(userOffOn[]) = {"USER OFF;", "USER ON;", NULL}; /* Array of structures that define all GPIB messages */ static struct gpibCmd gpibCmds[] = { /* Param 0 */ {&DSET_BO, GPIBEFASTO, IB_Q_HIGH, NULL, NULL, 0, 32, NULL, 0, 0, userOffOn, &offOn, -1}, /* definitions for other parameters follow*/ }; /* The following is the number of elements in the command array above. */ #define NUMPARAMS sizeof(gpibCmds)/sizeof(struct gpibCmd) /* User MUST define init_ai */ static long init_ai(int parm) { if(parm==0) { devSupParms.debugFlag = &SkeletonDebug; devSupParms.respond2Writes = -1; devSupParms.timeWindow = TIME_WINDOW; devSupParms.hwpvtHead = 0; devSupParms.gpibCmds = gpibCmds; devSupParms.numparams = NUMPARAMS; devSupParms.magicSrq = -1; devSupParms.name = "devXxSkeletonGpib"; devSupParms.dmaTimeout = IO_TIME; devSupParms.srqHandler = devGpibLib_srqHandler; devSupParms.wrConversion = 0; } return(devGpibLib_initDevSup(parm, &DSET_AI)); }
The meaning of each portion of the code should become clear as you read the following sections:
The following statements create the Device Support Entry Tables
#define DSET_AI devAiSkeletonGpib #define DSET_BI devBiSkeletonGpib #define DSET_MBBI devMbbiSkeletonGpib ... #include <devGpib.h> /* must be included after DSET defines */
The actual DSETs are created by devGpib.h based on which DSET_xx definitions are defined. Thus you must have a #define for each record type your support uses. Also you MUST have a definition for DSET_AI because you must also supply an init_ai routine as described below.
The definition of gpibCmd is:
typedef struct gpibCmd { gDset *rec_typ; /* address of DSET for specified record type */ int type; /* enum - GPIBREAD...GPIBEFASTIW */ short pri; /* request priority--IB_Q_HIGH or IB_Q_LOW */ char *cmd; /* CONSTANT STRING to send to instrument */ char *format; /* string used to generate or interpret msg */ long rspLen; /* room for response error message */ long msgLen; /* room for return data message length */ int (*convert) (); /* custom routine for conversions */ int P1; /* user defined parameter used in convert() */ int P2; /* user defined parameter used in convert() */ /* P3 plays dual role. For EFAST address of EFAST table */ /* For convert it is passed to convert*/ char **P3; struct devGpibNames *namelist; /* pointer to name strings */ int companion; /* companion command (used at init time) */ }gpibCmd;
In the example above the definitions for the gpibCmds are:
static struct gpibCmd gpibCmds[] = { /* Param 0 */ {&DSET_BO, GPIBEFASTO, IB_Q_HIGH, NULL, NULL, 0, 32, NULL, 0, 0, userOffOn, &offOn, -1}, /* definitions for other parameters follow*/ };
This example defines a single command. A database record using this definition must define field OUT as
field(OUT,,"#L<link> A<addr> @0")
The format of the parameter table is as follows:
static struct gpibCmd gpibCmds[] = { /* Parameter 0 */ {f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13}, /* parameter 1 */ ... };
where
IB_Q_HIGH
or IB_Q_LOW
.dpvt.rsp
. Holds the
message read back from a device when performing a responds-to-writes
read operation. Set this field to zero when not used. See the section
"Machines That Respond to Everything" for more information.
GPIBWRITE
, GPIBREAD
, or
GPIBREADW
. Set this field to zero when not used.GPIBSOFT
, or to perform a
conversion/parsing operation when f2 is set to any of the
other operation types. This function is intended to be used to generate
and parse strings being sent to and from an instrument, but can be used
for anything. This function must make sure that it does not overflow
the dpvt.msg field.. This function is passed f9, f10,
and f11 as parameters.
For output operations the function is called to generate the string (possibly using the records VAL field) that is to be sent to the instrument. For input type operations, it is called to scan the response string from the instrument (and fill in the records VAL field.) It is highly recommended that if a custom conversion routine be used, that the designer of the function be familiar with the record-specific library function that calls it.
The function should be declared as returning an int. This return
value is passed back to the caller of the device support module after a
processing request when f2 is set to GPIBSOFT
and
is ignored in other cases.
Set to NULL when no conversion function is present.
Set this field to NULL when it is not used.
Set this to NULL when no Name Table is used.
This section describes the operation types.
sscanf(dpvt.msg, f5, &(precord->val));Otherwise, a call is made to the function pointed to by f8 as follows:
(*(f8))(&dpvt, f9, f10, f11);
This reads and parses data from the instrument . If f8 is NULL, the value is determined by the sscanf function. Keep in mind the data type of the VAL field for the associated record type. For example the Analog Input record type has a double precision floating point data type. So a %lf (percent ell eff) is required, not a %f. If f8 is not NULL, see the discussion of f8 below.
The GPIBREAD setting is only valid for input record types.
sprintf(dpvt.msg, f5, precord->val);Otherwise, a call is made to the function pointed to by f8 as follows:
(*(f8))(&dpvt, f9, f10, f11);This allows the module to create a character string that includes the val field of a record. As in the case of the GPIBREAD operation, keep the specific data type of the VAL field in mind. An oddity of the sprintf() function that comes with vxWorks is that the %lf (percent ell eff) format command will generate nothing when the VAL field is zero. So you should use a length specifier of at least one when using floating point formatting. For example %.1f (percent dot one eff). IS THIS STILL TRUE?
(*(parmblock.wrConversion))(read_status, pdpvt);
(*(parmblock.wrConversion))(read_status, pdpvt);
(*f8)(&dpvt, f9, f10, f11);When GPIBSOFT is specified, f8 must be set to point to the processing function.
dpvt.rsp
buffer. If the secondary conversion function pointer is not
NULL
in the parm block, it is invoked as follows:
(*(parmblock.wrConversion))(read_status, pdpvt);
Note that setting f8 to a non-NULL value is invalid for this operation type. Results in that case should be considered catastrophic.
Note that setting f8 to a non-NULL value is invalid for this operation type. Results in that case should be considered catastrophic.
Valid only for BO records. If rval = (0,1) then (do nothing, pulse IFC). IFC is one of the GPIB Bus Management Lines.
Only f1,f2,and f3 need to be defined. A default EFAST table is provided.
Valid only for BO records. If rval = (0,1) then (drop,assert) REN. REN is one of the GPIB Bus Management Lines.
Only f1,f2,and f3 need to be defined. A default EFAST table is provided.
If devices are in the LLO state they can be removed from this state by toggling the REN line, i.e. turn it off and then turn it back on.
Valid only for BO records. If rval = (0,1) then (do nothing, send DCL). DCL is a Universal GPIB command, i.e. it applys to all devices on the link
Only f1,f2,and f3 need to be defined. A default EFAST table is provided.
If rval = (0,1) then (do nothing, send LLO). LLO is a Universal GPIB command, i.e. it applys to all devices on the link
Only f1,f2,and f3 need to be defined. A default EFAST table is provided.
After a LLO command is issued, as soon as a device is addressed it will disable local control, i.e. the front pannel controls will not respond. To remove devices from this state toggle the REN line. A single device can temporily be removed from LLO by sending the GPIBCTL coimmand but it will go back to LLO state as soon as it is again addressed.
If rval = (0,1) then (do nothing, send SDC). SDC is an addressed GPIB command, i.e. it applys only to the addressed device.
Only f1,f2,and f3 need to be defined. A default EFAST table is provided.
If rval = (0,1) then (do nothing, send GTL). GTL is an addressed GPIB command, i.e. it applys only to the addressed device.
Only f1,f2,and f3 need to be defined. A default EFAST table is provided.
If a device has local control locked out, local control can be temporily granted by issuing this command. However the next time the device is addressed it will again disable local control.
If rval = (0,1) then (do nothing, reset link task). This completly restarts the link thread. This is a very drastic action.
Only f1,f2,and f3 need to be defined. A default EFAST table is provided.
The format of an efast table is:
static char *(tableName[]) = { "TERM LO", /* when VAL = 0 */ "TERM HI", /* when VAL = 1 */ NULL}; /* list terminator */And is referenced in an output parameter table entry like this:
{&DSET_BO, GPIBEFASTO, IB_Q_HIGH, NULL, NULL, 0, 0,NULL, 0, 0, tableName, NULL, -1},For an input entry, it would look like this:
{&DSET_BI, GPIBEFASTI, IB_Q_HIGH, "<command>", NULL, 0, 50, NULL, 0, 0, tableName, NULL, -1},The efast table MUST be null terminated when used for input record types. It is not required for output records, but is a good idea anyway so an efast table can be used for input and output records.
The way the the table is used for outputs is that the VAL field is used to index into the efast table and select which string to send to the instrument. The string is then sent to the instrument as it appears in the efast table with no formatting.
For input operations, the f4 string is sent to the instrument without formatting, and then the response string is read from the instrument. This response string is compared against each of the entries in the efast table starting at the zeroth entry. The slot number of the first table entry that matches the response string is used as the setting for the RVAL field of the record. When strings are compared, they are compared from left to right until the number of characters in the efast table are checked. When ALL of the characters up to but NOT including the NULL of the string in the efast table match the corresponding characters of the response string, it is considered a valid match. This allows the user to check response strings fairly fast. For example, it a device returns something like "ON;XOFF;9600" or "OFF;XOFF;9600" in response to a status check, and you wish to know if the first field is either "OFF" or "ON", your efast table could look like this:
static char *(statCheck[]) = { "OFF", /* set RVAL to 0 */ "ON", /* set RVAL to 1 */ NULL}; /* list terminator */Note again that the NULL field is important here. If the instrument gets confused and responds with something that does not start with an "OF" or "ON", the GPIB support library code will end up running off the end of the table.
In the case when none of the choices in an efast table match for an input operation, The record is placed into a VALID alarm state.
To use a name table, the address of the table must be put into f12 of the parameter table. The table format for a multibit record type looks like this:
static char *tABCDList[] = { "T", /* zrst*/ "A", /* onst */ "B", /* twst */ "C", /* thst */ "D"}; /* frst */ static unsigned long tABCDVal[] = { 1, /* zrvl */ 2, /* onvl */ 3, /* twvl */ 5, /* thvl */ 6 }; /* frvl */ static devGpibNames tABCD = { 5, /* number of elements in string table */ tABCDList, /* pointer to string table */ tABCDVal, /* pointer to value table */ 3 }; /* value for the nobt field */The table format for a binary record type looks like this:
static char *disableEnableList[] = { "Disable", /* znam */ "Enable" }; /* onam */ static devGpibNames disableEnable = { 2, /* number of elements */ disableEnableList, /* pointer to strings */ NULL, /* pointer to value list */ 1}; /* number of valid bits */The devGpibNames structure is defined in the devCommonGpib.h header file. The first thing required is list of name strings defined by an array of pointers to strings. For binary record types, the strings are placed into the name fields in order from lowest to highest as shown above. For multibit binary records, there can be up to sixteen strings defined. A devGpibNames structure referencing these strings is then defined.
The value list pointer, and NOBT field are not used for binary record types, but should be specified anyway as if the binary record was a multibit binary record with only 2 values.
For multibit record types, the name strings, values, and NOBT fields are filled in from the name table information. For binary record types, only the znam and onam fields are filled in.
Name strings (and their associated values in the multibit cases) are not filled in if the database designer fills them in via DCT.
/* User MUST define init_ai */ static long init_ai(int parm) { if(parm==0) { devSupParms.debugFlag = &SkeletonDebug; devSupParms.respond2Writes = -1; devSupParms.timeWindow = TIME_WINDOW; devSupParms.hwpvtHead = 0; devSupParms.gpibCmds = gpibCmds; devSupParms.numparams = NUMPARAMS; devSupParms.magicSrq = 4; devSupParms.name = "devXxSkeletonGpib"; devSupParms.dmaTimeout = IO_TIME; devSupParms.srqHandler = devGpibLib_srqHandler; devSupParms.wrConversion = 0; } return(devGpibLib_initDevSup(parm, &DSET_AI)); }
The fields of the devGpibParmBlock are:
device type will solicit a response from the device. See the section on "Machines that Respond to Everything" for more information about this.
static int specialConvert(struct gpibDpvt *pdpvt, int p1, int p2, char **p3)and are specified in the parameter table. See the discussion of the f8 field above for more information about the parameter table entry.
Given the address of the dpvt structure as well as the developer-entered values for p1, p2, and p3 (that come from the parameter table), the custom conversion function should have all the information required to perform the needed conversion(s).
For some examples of various conversion routines, see the Dg535 device support module.
The GPIB device support library currently supports devices like these by providing a flag in the parm block that can be set to a non-negative value indicating that the library should read data from the device on every type of operation defined in the parameter table.
If the responds to writes flag in the parm block is not set to -1, the library will wait for the number of milliseconds specified by this flag and then, if the f6 parameter is non-zero, will perform a read operation from the device. The data from the read operation will be placed into dpvt.rsp. Then a call will be made to the secondary conversion routine (if not specified as NULL in the parm block) and it will be passed the status from the read operation and the address of the dpvt structure. The prototype of a secondary conversion routine function is:
static int secondaryConversion(int status; struct gpibDpvt *pdpvt)Where status is the number of bytes read from the device or -1 if the read operation failed. And pdpvt is the address of the dpvt structure associated with the record being processed.
The return value from the secondary conversion function must either be OK or ERROR. If ERROR is returned the record will be placed into a VALID_ALARM state. Otherwise, the processing of the I/O operation will be completed as normal.
In the future, modifications the the GPIB library will be made to correct this problem and secondary conversion routines will no longer be required or supported.
If the device support generated by devGpib.h doesn't satisfy your needs then you must provide your own. Two ways are:
It is fine to mix all three methods.
There is a library of commonly used functions available to the GPIB module designer. It contains enough code such that a device support module can be written that contains as little as two lines of executable C code.
All GPIB device support library functions have names that are prefixed
with devGpibLib_
and include the type of record they apply to.
For example, the devGpibLib_initAi()
function is used to
initialize analog input records. And the devGpibLib_readAi()
function is used to fill in the VAL field on an analog input record.
long devGpibLib_initDevSup(int parm, gDset *dset,)Call with parm=0 before any calls are made to the record-type specific init functions, and again with parm != 0 after all calls have been made to record-type specific init functions. The DSET value must point to any one of the DSET data structures for the GPIB device type that is being initialized.
This function does nothing more than print an initialization time message. It might be used in the future to initialize the value fields of output record types.
long devGpibLibReport(gDset *dset)Print a one-liner report of the device name, its addressing information, link type and the total number of observed time-outs. This function is provided so that the dbior function can be used to coarsely observe the operation of a device.
dbProcess()
processing a record.
If the queue request to the driver fails, the record is placed in a VALID_ALARM state.
The return value from the second call in the asynchronous processing for each function is 2 for each of the following functions except for the MBBI and BI versions... then the return value is 0.
long devGpibLib_readAi(struct aiRecord *pai) long devGpibLib_readBi(struct biRecord *pbi) long devGpibLib_readLi(struct longinRecord *pli) long devGpibLib_readMbbi(struct mbbiRecord *pmbbi) long devGpibLib_readSi(struct stringinRecord *psi)
If the queue request to the driver fails, the record is placed in a VALID_ALARM state.
The return value from the second call in the asynchronous processing is zero for each of the following functions.
long devGpibLib_writeAo(struct aoRecord *pao) long devGpibLib_writeBo(struct boRecord *pbo) long devGpibLib_writeLo(struct longoutRecord *plo) long devGpibLib_writeMbbo(struct mbboRecord *pmbbo) long devGpibLib_writeSo(struct stringoutRecord *pso)
The output group is fairly simple. An output function formats a message as specified in the parameter table and then calls the driver to send it. After the message is sent, a callbackRequest is made to dbProcess() so that the second half of the asynchronous processing may take place. If the output operation fails, the record is placed into a VALID_ALARM state before the callback to dbProcess() is made.
int devGpibLib_aoGpibWork(struct gpibDpvt *pdpvt) int devGpibLib_boGpibWork(struct gpibDpvt *pdpvt) int devGpibLib_loGpibWork(struct gpibDpvt *pdpvt) int devGpibLib_mbboGpibWork(struct gpibDpvt *pdpvt) int devGpibLib_stringoutGpibWork(struct gpibDpvt *pdpvt)The input group is a little more complex because a message is not only sent to a device, but a response is read back afterward. If the parameter table entry specifies that it is to be treated as an operation that includes an SRQ to indicate completion, these functions return to the driver before reading the response message back. In the SRQ case, the driver will end up calling the srqHandler function defined in the parm block when it arrives. The srqHandler is responsible for then calling the record specific SRQ handling function described in the section "SRQ Functions" below. This process is described in the section "The SRQ Handler" above.
In the non-SRQ based style of operation, the message specified in the parameter table is sent to the device, the response read back, the response converted to the required VAL field data type as specified in the parameter table, and a callbackRequest() is made to dbProcess to initiate the second half of the asynchronous record processing.
If any errors are encountered, the record is placed in a VALID_ALARM state before the callbackRequest() is made to dbProcess().
int devGpibLib_aiGpibWork(struct gpibDpvt *pdpvt) int devGpibLib_biGpibWork(struct gpibDpvt *pdpvt) int devGpibLib_liGpibWork(struct gpibDpvt *pdpvt) int devGpibLib_mbbiGpibWork(struct gpibDpvt *pdpvt) int devGpibLib_stringinGpibWork(struct gpibDpvt *pdpvt)
int devGpibLib_aiGpibFinish(struct gpibDpvt *pdpvt) int devGpibLib_biGpibFinish(struct gpibDpvt *pdpvt) int devGpibLib_liGpibFinish(struct gpibDpvt *pdpvt) int devGpibLib_mbbiGpibFinish(struct gpibDpvt *pdpvt) int devGpibLib_stringinGpibFinish(struct gpibDpvt *pdpvt)
int devGpibLib_aiGpibSrq(struct gpibDpvt *pdpvt; int srqStatus) int devGpibLib_biGpibSrq(struct gpibDpvt *pdpvt; int srqStatus) int devGpibLib_liGpibSrq(struct gpibDpvt *pdpvt; int srqStatus) int devGpibLib_mbbiGpibSrq(struct gpibDpvt *pdpvt; int srqStatus) int devGpibLib_stringinGpibSrq(struct gpibDpvt *pdpvt; int srqStatus)
drvGpib is the interface between devCommonGpib and low level drivers. It can also be used by other code, e.g. GI makes calls directly to drvGpibCommon. The interface is descrtibed in two header files: drvGpib.h and drvGpibInterface.h It implements the following interface:
typedef struct dpvtGpibHead { ELLNODE node; int (*workStart) (); /* work start function for the transaction */ int link; int device; /* GPIB primary address for transaction */ int secondary; /* secondary address. -1 means only primary*/ ibLink *pibLink; /* used by generic driver */ } dpvtGpibHead; typedef struct drvGpibEt { long number; long (*report)(int level); long (*init)(void); ibLink* (*getLink)(int link, int gpibAddr, int secondary); long (*qGpibReq)(dpvtGpibHead *pdpvt,int prio); long (*registerSrqHandler)(ibLink *pibLink, int gpibAddr, int secondary, srqHandlerFunc handler, void *parm); long (*read)(ibLink *pibLink, int gpibAddr, int secondary, char *data, int length, int timeout, int eos); long (*write)(ibLink *pibLink, int gpibAddr, int secondary, char *data, int length, int timeout); long (*addressedCmd)(ibLink *pibLink, int gpibAddr, int secondary, char *data, int length, int timeout); long (*universalCmd)(ibLink *pibLink, int cmd); long (*ifc)(ibLink *pibLink); long (*ren)(ibLink *pibLink,int onOff); long (*reset)(ibLink *pibLink); } drvGpibEt; extern drvGpibEt drvGpib;
drvGpibCommon creates a thread for each link, i.e. for each gpib interface. All gpib I/O request MUST be made via that thread.
Code that uses drvGpibEt has the following structure:
... typedef struct myWork{ struct dpvtGpibHead head; .... }myWork; ibLink *pibLink; ... static int myWorkFunc(dpvtGpibHead *phead) { myWork *pmyWork = (myWork *)phead; int status; /* Issue all I/O requests from this function */ /*For example */ status = drvGpib.read(phead->pibLink,phead->device,phead->secondary,buffer,buflen,1000,-1); return(status); } ibLink *pibLink; myWork work; ... work.head.workStart = myWorkFunc; work.head.link = link; work.head.device = gpibAddr; work.head.secondary = secondary; work.head.pibLink = 0; ... status = (*drvGpib.qGpibReq)(@work.head,IB_Q_LOW); ...
Thus all I/O requests are made via a work function called by drvGpibCommon.
The methods provided by drvGpibEt are:
drvGpibCommon itself has no knowledge of the details about specific gpib controllers. Instead each link has an associated low level driver. A low level driver must implement the following interface:
struct drvHwGpibEt { long (*genLink)(ibLink *pibLink, int pass); long (*destroyLink)(ibLink *pibLink); long (*reportLink)(ibLink *pibLink, int level); long (*probe)(ibLink *pibLink); long (*read)(ibLink *pibLink, int gpibAddr, int secondary, char *data, int length, int timeout, int eos); long (*write)(ibLink *pibLink, int gpibAddr, int secondary, char *data, int length, int timeout); long (*addressedCmd)(ibLink *pibLink, int gpibAddr, int secondary, char *data, int length, int timeout); long (*universalCmd)(ibLink *pibLink, int cmd); long (*ifc)(ibLink *pibLink); long (*ren)(ibLink *pibLink,int onOff); long (*srqStatus)(ibLink *pibLink); long (*srqEnable)(ibLink *pibLink); long (*srqDisable)(ibLink *pibLink); long (*serialPollBegin)(ibLink *pibLink); unsigned char (*serialPoll)(ibLink *pibLink,int gpibAddr, int secondary, int timeout); long (*serialPollEnd)(ibLink *pibLink); };
where:
WHAT SHOULD LOW LEVEL DEVICE DO? IS IT NEEDED?
Does a gpib read. Driver should do the following:
LAD for device requested
TAD for controller
read data bytes.
UNT UNL
NOTE: when reading If eos is -1 then EOL terminates the message otherwise eos is the end of message character.
Does a gpib write. Driver should do the following:
TAD for controller
LAD for device
write data bytes. Set EOL for last byte
UNT UNL
Interface Clear
Assert IFC
wait gpib specified time
release IFC
UNL
Assert SPE
Read and return a status byte from the device:
TAD device being polled
read a single data byte
UNT
SPD
UNT
NOTE: A low level device can also do nothing in serialPollBegin and serialPollBegin and do the following in serialPoll:
UNL
SPE
TAD device being polled
read status byte
SPD
UNT
A low level driver must allocate an ipLink, even though the information in an ipLink is private to drvGpibCommon. It normally does this by defining:
typedef struct xxxLink { ibLink head; /*for use by drbGpibCommon */ .... private info for xxxLink }xxxLink;
A low level driver starts as follows:
pxxxLink->head.pdrvHwGpibEt = &xxxGpibEt; pxxxLink->head.link = link; status = drvGpibLinkInit(&pxxxLink->head);
EXTREMELY IMPORTANT: Previously timeouts were in units of 1/60th of a second. Now timeouts are in milliseconds. Thus previously device support would have definitions like:
#define TIME_WINDOW 120 /* 2 seconds */ #define DMA_TIME 60 /* 1 second */
Now the definitions would be:
#define TIME_WINDOW 2000 /* 2 seconds */ #define DMA_TIME 1000 /* 1 second */
Older device created it's own DSET_xxx definitions. Remove all of these and use the techique of defining DSET_AI as shown above, i.e.
#define devAiXXXGpib #define devBiXXXGpib ... #include <devGpib.h>
Older device support initialized devGpibParmBlock with the statement:
static struct devGpibParmBlock devSupParms = { &xxxDebug, ... };
Newer support initialized it by a function:
static long init_dev_sup(parm) int parm; { if(parm==0) { devSupParms.debugFlag = &xxxDebug;
Now this is done via the function:
static long init_ai(int parm) { if(parm==0) { devSupParms.debugFlag = &Dg535Debug; ...
In a device support module used GPIBIOCTL it will have to be changed to use one on the new GPIBxxx command types.
drvGpibLogData(int onOff,int link,int addr) is now drvGpibLogData(int link,int addr,int onOff)
The ioctl method is gone. It is replaced by addressedCmd, universalCmd, ifc, ren, and reset. drvHwGpibEt also has the new methods serialPollBegin, serialPoll, and serialPollEnd.
The link thread uses a different method to decide when to call srqStatus. It now calls it every ibSrqPollRate seconds.
The low level drivers now issue UNT UNL commands as given by the guidelines above.
The fields number, report, init are removced from drvHwGpibEt
More than one device that misses messages or commands that are given one after the other because they are too close together in time has been identified during the testing of the GPIB support library. There are handshaking lines that are supposed to throttle the speed, but are apparently improperly implemented by device vendors, or make the (wrong) assumption that the controller in charge is slow in its ability to burst bytes down the bus. The only way that this problem can be worked around is to add delays in the GPIB device device support modules. The current device support library does not provide any means to do this.
Very often, a device will slow down over 800% when a user presses a button on the front panel of the device. This can cause the GPIB message transfer to time out, alarms to be set and so on. When devices of this type have to be used, operators will have to be instructed to "look, but don't touch."
Some devices like to go out to lunch once every hour, or day or so and not respond to a command for up to 5 seconds or so (the DG 535 has done this on more than one occasion.) This can be more frustrating that anything else. All that can be said about these types of things is BEWARE of machines that actually work as advertised. There is probably something wrong with it that won't surface until it is in use and controlling something very important.
Test, test, and test your devices after writing a new device support module. Many devices can run fine if doing only three or five transactions per second, but crank it up to 50 or more, and watch it go down in flames. Even if all the records in an EPICS database are scanned slowly, they can still get processed in bursts. EPICS can actually process over 20,000 records in one second if they are all ready to go at the same time. And if there are enough records tied to the same device there is no telling how fast the device will be pushed.
Copyright (c) 2002 University of Chicago, The Regents of the University of California, and Berliner Elektronenspeicherring Gesellschaft fuer Synchrotronstrahlung m.b.H. (BESSY) All rights reserved. gpibCore - driver suite gpibCore is distributed subject to the following license conditions: SOFTWARE LICENSE AGREEMENT Software: gpibCore 1. The "Software", below, refers to gpibCore (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.