PAUL SCHERRER INSTITUT
EPICS at PSI
PSIEPICSSLSSwissFELProscan

EPICS

EPICS at PSI
Software
Training

web epics.web.psi.ch

Updated: 06.04.2021


Printer friendly version
 
S7plc EPICS driver

S7plc EPICS driver documentation

Contents

  1. Introduction
  2. Theory of Operation
  3. Driver Configuration
  4. Device Support
    1. Connection Status
    2. Analog Input
    3. Analog Output
    4. Binary Input
    5. Binary Output
    6. Multibit Binary Input
    7. Multibit Binary Output
    8. Multibit Binary Input Direct
    9. Multibit Binary Output Direct
    10. Long Input
    11. Long Output
    12. String Input
    13. String Output
    14. Waveform Input
    15. Calculation Output
  5. Driver Functions

1 Introduction

This driver is intended to connect a Siemens S7 PLC (programmable logic controller) via TCP/IP to an EPICS IOC, using the so called "send/receive" protocol. 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 Dirk Zimoch <dirk.zimoch@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.

2 Theory of Operation

The driver and the PLC periodically exchange data (process variables) over the network. For each direction (IOC to PLC and PLC to IOC), there is one fixed size data block that bundles all process variables for this direction. The process variables are identified by their bytes offset in the data block.

Process variables can be 8, 16, or 32 bit wide signed or unsigned integers or bit fields, single or double precision floating point values, or arrays of these, as well as strings of single byte characters. Both byte orders, big endian and little endian, are supported.

The IOC programmer typically connects one input record to each process variable sent from the PLC to the IOC and one output record for each process variable sent from the IOC to the PLC. This requires that the programmer of the PLC and the programmer of the IOC agree on the data block sizes and layouts. The layout is not negotiated between IOC and PLC in any automated way.

When the IOC starts, the driver tries to connect to the PLC which must run a TCP server. If connection cannot be established (e.g. because the PLC is off) the driver periodically retries to set up the connection. Once connected, the driver waits for data blocks sent by the PLC. The PLC must be set up to send its data periodically. If the driver does not receive any data within a configurable timeout (which should be 2 to 10 times the send period of the PLC) or the data block does not have the correct size, the driver considers the communication broken and closes the connection. After a short time it tries to reconnect.

Upon reciving the data block, the driver copies the process variables from this block into input records and then triggers processing of the records. To allow this triggering, the input records should use "I/O Intr" scanning (or alternatively be processed by another record that uses "I/O Intr" and is conencted to the same PLC). All input records connected to the same PLC are triggered at the same time after all input values have been updated. The order of processing is undefined.

On the other hand, the driver periodically checks if any of the output records connected to this PLC has processed since the last cycle. If and only if this is the case, a data block containing all output variables is sent to the PLC. Sending output only starts after the "PINI" run at IOC startup. This allows to initialize all output records before any data is sent to the IOC. Thus all output records should have the "PINI" field set to "YES" (or alternatively be processed by another record that uses "PINI"). Any output that has not been processed before the data block is sent to the PLC transfers only 0 bytes (usually meaning integer 0, floating point 0.0, or empty string, depending on the data type).

Data blocks are always transfered completely. That means all process variables in that block are transfered, even if they have not changed since the last cycle. There is no way to write or read only a sub-set of process variables. In between two cycles, any number of process variables may have changed. These changes are only transfered to the other communication partner in the next cycle. Thus, there is always a delay until the values update on the other side of the connection. Also if values change too quickly, faster than the transfer cycle, intermediate values are lost.

3 Driver Configuration

In the IOC startup script, the s7plc driver needs to be configured:

s7plcConfigure (PLCname, IPaddr, port, inSize, outSize, bigEndian, recvTimeout, sendIntervall)

PLCname is an arbitrary symbolic name for the PLC running a server TCP socket on IPaddr:port. The records reference the PLC with this name in their INP or OUT link. PLCname must not contain the slash character (/).

inSize and outSize are the data block sizes in bytes read from and sent to the PLC, respectively. Any of them can be 0. Byte order is defined by bigEndian. 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.

If the IOC does not receive new data from the PLC for recvTimeout milliseconds, it closes the connection and tries to reopen it after a few seconds. recvTimeout should be 2 to 10 times the send intervall of the PLC.

The IOC checks for data to send every sendIntervall milliseconds. If any output record has been processed in this time, the complete buffer is sent to the PLC. If no new output is available, nothing is sent.

Example:

s7plcConfigure ("vak-4", "192.168.0.10", 2000, 1024, 32, 1, 500, 100)

In the vxWorks target shell, PLCname, and IPaddr must be quoted. In the iocsh, quotes are optional.

The variable s7plcDebug 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, s7plcDebug can be set with s7plcDebug=level

In the iocsh use var s7plcDebug level

4 Device Support

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 "S7plc". 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 is also a connection status support for bi. The DTYP is "S7plc stat". This record does 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 s7plcConfigure 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 TypeDefault 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/AN/A
REAL64
FLOAT64
DOUBLE
64 bit (8 bytes) floating point number N/AN/A
STRING character array 40N/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".

4.1 Connection Status

 record (bi, "$(RECORDNAME)") {
  field (DTYP, "S7plc stat")
  field (INP,  "@$(PLCNAME)")
  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.

4.2 Analog Input

With conversion from integer data type:
 record (ai, "$(RECORDNAME)") {
  field (DTYP, "S7plc")
  field (INP,  "@$(PLCNAME)/$(OFFSET) T=$(T) L=$(L) H=$(H)")
  field (SCAN, "I/O Intr")
  field (LINR, "Linear")
  field (EGUL, "$(MINVAL)")
  field (EGUF, "$(MAXVAL)")
 }
Using floating point data type:
 record (ai, "$(RECORDNAME)") {
  field (DTYP, "S7plc")
  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.

4.3 Analog Output

With conversion to integer data type:
 record (ao, "$(RECORDNAME)") {
  field (DTYP, "S7plc")
  field (OUT,  "@$(PLCNAME)/$(OFFSET) T=$(T) L=$(L) H=$(H)")
  field (LINR, "Linear")
  field (PINI, "YES")
  field (EGUL, "$(MINVAL)")
  field (EGUF, "$(MAXVAL)")
 }
Using floating point data type:
 record (ao, "$(RECORDNAME)") {
  field (DTYP, "S7plc")
  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.

4.4 Binary Input

 record(bi, "$(NAME)") {
  field (DTYP, "S7plc")
  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.

4.5 Binary Output

 record(bo, "$(NAME)") {
  field (DTYP, "S7plc")
  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.

4.6 Multibit Binary Input

 record(mbbi, "$(NAME)") {
  field (DTYP, "S7plc")
  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.

4.7 Multibit Binary Output

 record(mbbo, "$(NAME)") {
  field (DTYP, "S7plc")
  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.

4.8 Multibit Binary Input Direct

 record(mbbiDirect, "$(NAME)") {
  field (DTYP, "S7plc")
  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.

4.9 Multibit Binary Output Direct

 record(mbboDirect, "$(NAME)") {
  field (DTYP, "S7plc")
  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.

4.10 Long Input

 record(longin, "$(NAME)") {
  field (DTYP, "S7plc")
  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.

4.11 Long Output

 record(longout, "$(NAME)") {
  field (DTYP, "S7plc")
  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.

4.12 String Input

 record(stringin, "$(NAME)") {
  field (DTYP, "S7plc")
  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.

4.13 String Output

 record(stringout, "$(NAME)") {
  field (DTYP, "S7plc")
  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

4.14 Waveform Input

 record(waveform, "$(NAME)") {
  field (DTYP, "S7plc")
  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.

4.15 Calculation Output

 record(calcout, "$(NAME)") {
  field (DTYP, "S7plc")
  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.

5 Driver Functions

Device support for other record types can be written with calls to the following driver functions:

s7plcStation* s7plcOpen (char* PLCname);

int s7plcRead (s7plcStation* station, unsigned int offset, unsigned int dlen, void* pdata);

int s7plcReadArray (s7plcStation* station, unsigned int offset, unsigned int dlen, unsigned int nelem, void* pdata);

int s7plcWrite (s7plcStation* station, unsigned int offset, unsigned int dlen, void* pdata);

int s7plcWriteMasked (s7plcStation* station, unsigned int offset, unsigned int dlen, void* pdata, void* pmask);

int s7plcWriteArray (s7plcStation* station, unsigned int offset, unsigned int dlen, unsigned int nelem, void* pdata);

int s7plcWriteMaskedArray (s7plcStation* station, unsigned int offset, unsigned int dlen, unsigned int nelem, void* pdata, void* pmask);

The functions s7plcRead(), s7plcWrite(), s7plcWriteMasked(), and s7plcWriteArray() are actually macros for s7plcReadArray() and s7plcWriteMaskedArray() with nelem=1 and/or mask=NULL.

station is a handle previously obtained by a call to s7plcOpen().

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.


Dirk Zimoch, March 2005 - February 2012
Updated: 06.04.2021   Source: /afs/psi.ch/project/epics/webhosting/software/s7plc/s7plc.html