This is a documentation for s7plcFW driver, which is a copy of s7plc driver
adjusted to use FETCH/WRITE communication with PLC, instead of SEND/RECEIVE.
Besides the
s7plcFWConfigure function"s7plcFW stat2" )s7plc documentation.
Only change is regarding the three above mentioned differences.
NOTE:
FETCH/WRITE mechanism can do transfers only with even number of bytes.
Any record writting even number of bytes, will force the other byte of the same 16bit WORD to be written, too,
at the beginning and at the end of the block beeing written.
This driver is intended to connect a Siemens S7 PLC (programmable logic controller) via TCP/IP to an EPICS IOC. However, it can be used for any device sending and receiving blocks of process variables (PVs) in the same way. I highly recommend to connect to the PLC on a separate physical network using a second network interface to avoid connection problems.
The driver was originally developped for SLS (Swiss Light Source) in 2000. Later is has been modified by DESY (Deutsches Elektronen Synchrotron). The current version has been completely rewritten for PPT (Puls-Plasmatechnik GmbH) to run on a R3.14.6 PC based system, but it can also run on R3.13 vxWorks system. Author of the current version is Damir Anicic (SLS) <damir.anicic@psi.ch>.
In this document, it is assumed that the reader is familiar with EPICS, the record concept and meanings of the fields of the standard records. Recommended documentation: EPICS Record Refecrence Manual.
In the IOC startup script, the s7plcFW driver needs to be configured:
s7plcFWConfigure (PLCname, IPaddr, fetchInfo, writeInfo,
bigEndian, recvTimeout, recvDelay, outIOintDelay)
PLCname is an arbitrary symbolic name for the PLC running
a pair of server TCP sockets on IPaddr:fetchPort and IPaddr:writePort.
The records reference the PLC with this name in their INP or
OUT link. PLCname must not contain the
slash character (/).
IPaddr is TCP/IP address of the PLC (in dotted notation, like: 192.168.1.10)
fetchInfo and writeInfo are the string parameters,
containing the
"fetchPort,fetchOrg,fetchDb,fetchOffsetInDb,fetchSizeOfDb"
and
"writePort,writeOrg,writeDb,writeOffsetInDb,writeSizeOfDb".
For writeInfo you can add at the end of string an writeall option,
to allways write the whole buffer to DB. This is needed for PLCs as used in SINQ.
fetchPort and writePort come in pairs. Usualy starting at 2000,2001.
Offsets and Sizes are in bytes and must be even numbers. Offset will usualy be zero, but one does not neccessarilly have to start at the beginning of Db. The size is number of bytes to fetch or write starting from given offset - it does not have to go to the end of Db and it should never exceed the size of Db.
If any of Port, Org, Db or Size are zero or if Offset or Size are not even numbers, the info is declared invalid.
fetchInfo should not be invalid, but invalid writeInfo is allowed, but the writting will be disabled.
bigEndian defines the Byte order of the PLC. If
this is 1, the IOC expects the PLC to send and receive any
multibyte PV (word, float, etc) most significant byte first. If it is
0, the data is least significant byte first. This is independent
of the byte order of the IOC.
recvTimeout is the time in milliseconds, that IOC will wait for data. If exceeded the IOC will close the connection and
try to reopen it after a few seconds. recvTimeoutshould be big enough, not to get frequent disconnects.
One, two, or even more seconds is OK. It is anyhow important only in cases when something goes wrong.
recvDelay is the IOC polling delay for getting new data (fetch), in milliseconds.
The frequency of getting the data will be smaller than 1 / recvDelay,
because data receiving time is not 0. So, the real data reading frequency would be 1 / (recvDelay + dataTransferTime)
The outIOintDelay is replacement for sendInterval of the s7plc driver.
s7plcFW driver sends written data immediately. The outIOintDelay is added to provide
interrupt based processing of output records (SCAN="I/O intr") as used in s7plc driver.
s7plcFWConfigure("VakuumPLC-10", "192.168.1.10", "2000,1,50,0,1000", "2001,1,40,0,50", 0, 1000, 200, 1000)
In the vxWorks target shell, PLCname,
IPaddr, fetchInfo and writeInfo must be quoted. In the iocsh, quotes are
optional.
The variable s7plcFWDebug can be set in the statup script or
at any time on the command line to change the amount or debug output.
The following levels are supported:
-1: fatal errors only
0: errors only
1: startup messages
2:+ output record processing
3:+ inputput record processing
4:+ driver calls
5:+ io printout
Be careful using level>1 since many messages can introduce considerable delays which may result in connection losses. Default level is 0.
On vxWorks, s7plcFWDebug can be set with
s7plcFWDebug=level
In the iocsh use
var s7plcFWDebug level
The driver supports the standard record types ai,
ao, bi, bo,
mbbi, mbbo,
mbbiDirect, mbboDirect,
longin, longout,
stringin, stringout,
and waveform. With EPICS R3.14,
calcout is supported, too.
The DTYP is "s7plcFW". If the record processes when
the PLC is not connected (off, down, unreachable), the record raises an
alarm with SEVR="INVALID" and STAT="CONN".
There are also two connection statuses supported for bi. The
DTYP are "s7plcFW stat" for fetch and "s7plcFW stat2" for write. This records do not
raise an alarm when the PLC is disconnected. It just changes to
0 state in that case.
SCAN="I/O Intr" is supported. Whenever input data is received
from a PLC, all "I/O Intr" input records connected to this PLC
are processed. In each output cyle, all "I/O Intr" output
records are processed.
The general form of the INP or OUT link is
"@PLCname/offset T=type L=low H=high B=bit"
Not all parameters T, L, H, and
B are required for each record type and parameters equal to the
default value may be omitted. The default values depend on the record type.
PLCname is the PLC name as defined by
s7plcFWConfigure in the startup script.
offset is the byte offset of the PV relative to the
beginning of the input or output data block for this PLC. It must be an
integer number or a sum of integer numbers like 20+3+2.
T=type defines the data type for transmitting the PV
from or to the PLC. It is not case sensitive and has several aliases (see
table below).
The default is T=INT16 for most record types.
L=low and H=high are used in analog
input and output records if LINR is set to "LINEAR"
to convert analog values to integer values and back. They define the raw
values which correspond to EGUL and EGUF,
respectively.
Analog output records will never write integer values lower than
L or higher than H. If necessary, the raw output
value is truncated to the nearest limit. The default values for
L and H depend on T.
| T= | Data Type | Default L= | Default H= |
|---|---|---|---|
| INT8 | 8 bit (1 byte) signed integer number | -0x7F -127 |
0x7F 127 |
| UINT8 UNSIGN8 BYTE CHAR | 8 bit (1 byte) unsigned integer number | 0x00 0 |
0xFF 255 |
| INT16 SHORT |
16 bit (2 bytes) signed integer number | -0x7FFF -32767 |
0x7FFF 32767 |
| UINT16 UNSIGN16 WORD |
16 bit (2 bytes) unsigned integer number | 0x0000 0 |
0xFFFF 65535 |
| INT32 LONG |
32 bit (4 bytes) signed integer number | -0x7FFFFFFF -2147483647 |
0x7FFFFFFF 2147483647 |
| UINT32 UNSIGN32 DWORD |
32 bit (4 bytes) unsigned integer number | 0x00000000 0 |
0xFFFFFFFF 4294967295 |
| REAL32 FLOAT32 FLOAT |
32 bit (4 bytes) floating point number | N/A | N/A |
| REAL64 FLOAT64 DOUBLE |
64 bit (8 bytes) floating point number | N/A | N/A |
| STRING | character array | 40 | N/A |
If T=STRING, L means length, not low.
The default value is the length of the VAL field.
In the case of the stringin and stringout records, this is 40 (including
the terminating null byte).
B=bit is only used for bi and bo records to define the
bit number within the data byte, word, or doubleword (depending on
T). Bit number 0 is the least significant bit.
Note that in big endian byte order (also known as motorola format) bit 0 is
in the last byte, while in little endian byte order (intel format) bit 0 is
in the first byte. If in doubt, use T=BYTE to avoid all
byte order problems when handling single bits.
Note that the output buffer is initialised with null bytes at startup and
any output record that has not been processed after reboot will send null
values to the PLC. The driver does not send anything before the global
variable interruptAccept has been set TRUE at the
end of iocInit. All records with PINI set to
"YES" have already been processed by that time. The driver
does not change the VAL field of any output record at
initialisation. Thus, auto save and restore can be used in combination with
PINI="YES".
record (bi, "$(RECORDNAME):ConnStatusFetch") {
field (DTYP, "S7plcFW stat")
field (INP, "@$(PLCNAME)")
field (ZNAM, "Disconnected")
field (ONAM, "Connected")
field (SCAN, "I/O Intr")
}
record (bi, "$(RECORDNAME):ConnStatusWrite") {
field (DTYP, "S7plcFW stat2")
field (INP, "@$(PLCNAME)")
field (ZNAM, "Disconnected")
field (ONAM, "Connected")
field (SCAN, "I/O Intr")
}
The record value is 1 if a connection to the PLC is established and 0 if not. Disconnect does not raise an alarm.
record (ai, "$(RECORDNAME)") {
field (DTYP, "s7plcFW")
field (INP, "@$(PLCNAME)/$(OFFSET) T=$(T) L=$(L) H=$(H)")
field (SCAN, "I/O Intr")
field (LINR, "Linear")
field (EGUL, "$(MINVAL)")
field (EGUF, "$(MAXVAL)")
}
record (ai, "$(RECORDNAME)") {
field (DTYP, "s7plcFW")
field (INP, "@$(PLCNAME)/$(OFFSET) T=$(T)")
field (SCAN, "I/O Intr")
}
Default type is T=INT16.
Defaults for L and H depend
on T (see table above).
If T is an integer type, the PV is read into
RVAL. If LINR is set to "LINEAR",
then the record support converts RVAL to VAL
so that RVAL=L converts to VAL=EGUL and
RVAL=H converts to VAL=EGUF.
VALtemp=(RVAL-L)*(EGUF-EGUL)/(H-L)+EGUL
After this conversion, VALtemp is still
subject to scaling and smoothing.
VAL=(VALtemp*ASLO+AOFF)*(1-SMOO)+VALold*SMOO.
If T=FLOAT or T=DOUBLE,
the PV is read directly into VAL and L,
H, EGUL and EGUF are ignored.
The device support emulates scaling and smoothing which is otherwise done
by the record support during conversion.
VAL=(PV*ASLO+AOFF)*(1-SMOO)+VALold*SMOO
T=STRING is not valid for ai records.
record (ao, "$(RECORDNAME)") {
field (DTYP, "s7plcFW")
field (OUT, "@$(PLCNAME)/$(OFFSET) T=$(T) L=$(L) H=$(H)")
field (LINR, "Linear")
field (PINI, "YES")
field (EGUL, "$(MINVAL)")
field (EGUF, "$(MAXVAL)")
}
record (ao, "$(RECORDNAME)") {
field (DTYP, "s7plcFW")
field (OUT, "@$(PLCNAME)/$(OFFSET) T=$(T)")
field (PINI, "YES")
}
Default type is T=INT16.
Defaults for L and H depend
on T (see table above).
If T is an integer type, RVAL is
written to the PV. If LINR is set to "LINEAR",
then the record support first scales OVAL.
OVALtemp=(OVAL-AOFF)/ASLO
After that, the value is converted to RVAL so that
OVALtemp=EGUL converts to RVAL=L and
OVALtemp=EGUF converts to RVAL=H.
RVAL=(OVALtemp-EGUL)*(H-L)/(EGUF-EGUL)+L
If RVAL is higher than H or lower than
L, the value is truncated to the nearest limit.
If T=FLOAT or T=DOUBLE,
OVAL is written directly to the PV. L,
H, EGUL and EGUF are ignored.
The device support emulates scaling which is otherwise done by the
record support during conversion.
PV=(OVAL-AOFF)/ASLO
T=STRING is not valid for ao records.
record(bi, "$(NAME)") {
field (DTYP, "s7plcFW")
field (INP, "@$(PLCNAME)/$(OFFSET) T=$(T) B=$(B)")
field (SCAN, "I/O Intr")
}
Default type is T=INT16. Default bit is B=0.
Depending on T, B can vary from 0 to 7, 15, or 31.
Bit 0 is the least significant bit. In little endian byte order, bit 0 is in
the first byte, in big endian byte order it is in the last byte of the PV.
If in doubt, use T=BYTE to avoid all byte order problems when
handling single bits.
The PV is read to RVAL and masked with
2B.
VAL is 1 if RVAL is not 0.
RVAL=PV&(1<<B); VAL=(RVAL!=0)?1:0
T=STRING, T=FLOAT or T=DOUBLE are not
valid for bo records. Signed and unsigned types are equivalent.
record(bo, "$(NAME)") {
field (DTYP, "s7plcFW")
field (OUT, "@$(PLCNAME)/$(OFFSET) T=$(T) B=bit")
field (PINI, "YES")
}
Default type is T=INT16. Default bit is B=0.
Depending on T, B can vary from 0 to 7, 15, or 31.
Bit 0 is the least significant bit. In little endian byte order, bit 0 is in
the first byte, in big endian byte order it is in the last byte of the PV.
If in doubt, use T=BYTE to avoid all byte order problems when
handling single bits.
If VAL is not 0, then RVAL is set to
2B, else RVAL is set to 0.
Only the referenced bit of the PV is changed while all other bits remain
untouched. Thus, other output records can write to different bits of the
same PV.
RVAL=(VAL!=0)?(1<<bit):0;
PV=(PVold&~(1<<bit))|RVAL
T=STRING, T=FLOAT or T=DOUBLE are not
valid for bo records. Signed and unsigned types are equivalent.
record(mbbi, "$(NAME)") {
field (DTYP, "s7plcFW")
field (INP, "@$(PLCNAME)/$(OFFSET) T=$(T)")
field (SCAN, "I/O Intr")
field (NOBT, "$(NUMBER_OF_BITS)")
field (SHFT, "$(RIGHT_SHIFT)")
}
Default type is T=INT16.
The PV is read to RVAL, shifted right by SHFT bits
and masked with NOBT bits. Valid values for NOBT
and SHFT depend on T:
NOBT+SHFT must not exceed the number of bits of
the type.
Bit 0 is the least significant bit. In little endian byte order, bit 0 is in the first byte, in big endian byte order it is in the last byte of the PV.
Example: Use bits 4 to 9 out of 16.
T=INT16, NOBT=6, SHFT=4
| PV | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| RVAL | 9 | 8 | 7 | 6 | 5 | 4 |
T=STRING, T=FLOAT or T=DOUBLE are not
valid for mbbi records. Signed and unsigned types are equivalent.
record(mbbo, "$(NAME)") {
field (DTYP, "s7plcFW")
field (OUT, "@$(PLCNAME)/$(OFFSET) T=$(T)")
field (PINI, "YES")
field (NOBT, "$(NUMBER_OF_BITS)")
field (SHFT, "$(LEFT_SHIFT)")
}
Default type is T=INT16.
RVAL is masked with NOBT bits, shifted left by
SHFT bits and written to the PV. Valid values for
NOBT and SHFT depend on T:
NOBT+SHFT must not exceed the number of bits of
the type.
Bit 0 is the least significant bit. In little endian byte order, bit 0 is in the first byte, in big endian byte order it is in the last byte of the PV.
Only the referenced NOBT bits of the PV are changed. All other
bits remain untouched. Thus, other output records can write to different bits
of the same PV.
Example: Use bits 5 to 8 out of 16.
T=INT16, NOBT=4, SHFT=5
| RVAL | 8 | 7 | 6 | 5 | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| PV | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
T=STRING, T=FLOAT or T=DOUBLE are not
valid for mbbo records. Signed and unsigned types are equivalent.
record(mbbiDirect, "$(NAME)") {
field (DTYP, "s7plcFW")
field (INP, "@$(PLCNAME)/$(OFFSET) T=$(T)")
field (SCAN, "I/O Intr")
field (NOBT, "$(NUMBER_OF_BITS)")
field (SHFT, "$(RIGHT_SHIFT)")
}
Default type is T=INT16.
The PV is read to VAL, shifted right by SHFT
bits and masked with NOBT bits (see mbbi).
Valid values for NOBT and SHFT depend
on T: NOBT+SHFT must
not exceed the number of bits of the type.
Bit 0 is the least significant bit. In little endian byte order, bit 0 is in the first byte, in big endian byte order it is in the last byte of the PV.
T=STRING, T=FLOAT or T=DOUBLE are not
valid for mbbiDirect records. Signed and unsigned types are equivalent.
record(mbboDirect, "$(NAME)") {
field (DTYP, "s7plcFW")
field (OUT, "@$(PLCNAME)/$(OFFSET) T=$(T)")
field (PINI, "YES")
field (NOBT, "$(NUMBER_OF_BITS)")
field (SHFT, "$(LEFT_SHIFT)")
}
Default type is T=INT16.
VAL is masked with NOBT bits, shifted left by SHFT
bits and written to the PV (see mbbo). Valid values for
NOBT and SHFT depend on T:
NOBT+SHFT must not exceed the number of bits of
the type.
Bit 0 is the least significant bit. In little endian byte order, bit 0 is in the first byte, in big endian byte order it is in the last byte of the PV.
Only the referenced NOBT bits of the PV are changed. All other
bits remain untouched. Thus, other output records can write to different bits
of the same PV.
T=STRING, T=FLOAT or T=DOUBLE are not
valid for mbboDirect records. Signed and unsigned types are equivalent.
record(longin, "$(NAME)") {
field (DTYP, "s7plcFW")
field (INP, "@$(PLCNAME)/$(OFFSET) T=$(T)")
field (SCAN, "I/O Intr")
}
Default type is T=INT16.
The PV is read to VAL. If the type has less than 32 bits, the
value is zero extended or sign extended depending on the signedness of
the type.
T=STRING, T=FLOAT or T=DOUBLE are not
valid for longin records.
record(longout, "$(NAME)") {
field (DTYP, "s7plcFW")
field (OUT, "@$(PLCNAME)/$(OFFSET) T=$(T)")
field (PINI, "YES")
}
Default type is T=INT16.
Depending on T, the least significant 8, 16, or 32 bytes
of VAL are written to the PV.
T=STRING, T=FLOAT or T=DOUBLE are not
valid for longout records.
record(stringin, "$(NAME)") {
field (DTYP, "s7plcFW")
field (INP, "@$(PLCNAME)/$(OFFSET) L=$(LENGTH)")
field (SCAN, "I/O Intr")
}
Default and only valid type is T=STRING.
Default length is L=40.
L bytes are read from the PV to VAL and null
terminated. Thus, the effective string length is maximal
L-1 bytes.
record(stringout, "$(NAME)") {
field (DTYP, "s7plcFW")
field (OUT, "@$(PLCNAME)/$(OFFSET) L=$(LENGTH)")
field (PINI, "YES")
}
Default and only valid type is T=STRING.
Default length is L=40.
L bytes are written from VAL to the PV.
If the actual string length of VAL is shorter than
L, the remaining space is filled with null bytes. If
it is longer than L, the string is truncated and not
null terminated
record(waveform, "$(NAME)") {
field (DTYP, "s7plcFW")
field (INP, "@$(PLCNAME)/$(OFFSET)")
field (SCAN, "I/O Intr")
field (NELM, "$(NUMBER_OF_ELEMENTS)")
field (FTVL, "$(DATATYPE)")
}
NELM elements are read from the PV to VAL.
The default type depends on FTVL. For example
FTVL=LONG results in T=INT32.
T and FTVL must match but can differ
in signedness. In most cases, better just specify FTVL and
leave T to the default.
If T=STRING, FTVL must be "CHAR" or
"UCHAR". L=length can be specified but
defaults to and must not exceed NELM.
If L is less than NELM, the
remaining elements are left untouched.
FTVL="STRING" is not supported.
The special type T=TIME is supported for
waveforms records only. FTVL must be
"CHAR" or "UCHAR" and NELM should be
"8". The input bytes are converted from BCD (binary coded decimal)
to integer values in the range from 0 to 99 each. This type is intended
to transfer BCD coded real time clock timestamps.
The Siemens "STEP 7" manual defines the 8 byte PLC timetamp as follows:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| year | month | day | hour | minute | second | msec(hi) | msec(lo)*10+day of week |
Years 90 to 99 mean 1990 to 1999, years 0 to 89 mean 2000 to 2089. Months
and days start with 1. Hour is 0 to 23, minute and second 0 to 59. Msec are
milliseconds in the range 0 to 999. The first two digits (0-99 hundredth of
a second) are in msec(hi). The last digit (0-9 thousandth of a second)
is multiplyed by 10 and added to the day of week (Sunday=1 to Saturday=7).
If you want to have the unconverted BCD bytes, do not use
T=TIME.
record(calcout, "$(NAME)") {
field (DTYP, "s7plcFW")
field (OUT, "@$(PLCNAME)/$(OFFSET) T=$(T) L=$(L) H=$(H)")
field (PINI, "YES")
}
Default type is T=INT16.
Defaults for L and H depend
on T (see table above).
OVAL (the result of CALC or OCAL,
depending on DOPT) is written to the PV. If
T is an integer type, the value is truncated to an
integer and compared to L and H.
If OVAL is lower than L or higher than
H, it is truncated to the nearest limit.
If T=FLOAT or T=DOUBLE,
OVAL is written to the PV directly without any conversion.
T=STRING is not valid for calcout records.
To use this device support with calcout records, you need EPICS R3.14.
Device support for other record types can be written with calls to the following driver functions:
s7plcFWStation* s7plcFWOpen (char* PLCname);
int s7plcFWRead (s7plcFWStation* station,
unsigned int offset, unsigned int dlen,
void* pdata);
int s7plcFWReadArray (s7plcFWStation* station,
unsigned int offset, unsigned int dlen,
unsigned int nelem, void* pdata);
int s7plcFWWrite (s7plcFWStation* station,
unsigned int offset, unsigned int dlen,
void* pdata);
int s7plcFWWriteMasked (s7plcFWStation* station,
unsigned int offset, unsigned int dlen,
void* pdata, void* pmask);
int s7plcFWWriteArray (s7plcFWStation* station,
unsigned int offset, unsigned int dlen,
unsigned int nelem, void* pdata);
int s7plcFWWriteMaskedArray (s7plcFWStation* station,
unsigned int offset, unsigned int dlen,
unsigned int nelem, void* pdata, void* pmask);
The functions s7plcFWRead(), s7plcFWWrite(),
s7plcFWWriteMasked(), and s7plcFWWriteArray()
are actually macros for
s7plcFWReadArray() and s7plcFWWriteMaskedArray() with
nelem=1 and/or mask=NULL.
station is a handle previously obtained by a call to
s7plcFWOpen().
offset is the byte offset of the PV relative
to the beginning to the data block.
dlen is the length of the PV in bytes (one element in case of
arrays). If the endianess of the PLC differs from the IOC, the byte order of
the dlen bytes is swapped by the driver.
nelem is the number of elements in an array.
pdata is a pointer to a buffer of
nelem*dlen bytes.
PVs are read to or written from this buffer.
mask is a pointer to a bitmask of dlen bytes.
Only those bits are changed where the mask contains 1 bits. All other bits
remain untouched.
For strings, use array functions with dlen=1 and
nelem=buffersize.