What?
CoDeSys_S7Comm is a CoDeSys 3.5.16.0 library that allows your CoDeSys controller (IPC) to communicate with Siemens S7 programmable logic controller (PLC).
Why?
Previously, a CoDeSys EtherNet/IP library (CoDeSys_EIP) was developed in order for CoDeSys IPC to be able to communicate with various EtherNet/IP capable devices via explicit messaging. This means, we still have at least two other major vendors out there: Siemens and Mitsubishi. STEP 7 Communication can be used to read/write to the Siemens PLCs (see Examples/Siemens
).
For the control engineers out there, you might already know that writing PLC code is not as flexible as writing higher level languages such as Python/Java/etc, where you can create variables with virtually any data type on the fly; thus, this library was heavily modified to accomodate control requirements. It is written to operate asynchronously (non-blocking) to avoid watchdog alerts, which means you make the call and be notified when data has been read/written succesfully. If you need to read multiple variables in one scan, then you can create a lower priority task and place the calls into a WHILE loop or use each scan cycle to generate a for loop (see Examples/Siemens
). At least 95% of the library leverages pointers for efficiency, so it might not be straight forward to digest at first. The documentation / comments is not too bad, but feel free to raise issues if needed.
Create an function block instance in your CoDeSys program, and specify the PLC's IP and port. Then create some variables:
VAR
_PLC : CoDeSys_S7Comm.Device(sIpAddress:='192.168.1.219',
uiPort:=102,
bRack:=0,
bSlot:=0);
_bReadDb : BOOL; // create var for write (e.g. _bWriteDb)
_bReadDb1 : BYTE; // create var for write
_siReadDb : SINT; // create var for write
_usiReadDb : USINT; // create var for write
_iReadDb : INT; // create var for write
_uiReadDb : UINT; // create var for write
_diReadDb : DINT; // create var for write
_udiReadDb : UDINT; // create var for write
_liReadDb : LINT; // create var for write
_uliReadDb : ULINT; // create var for write
_wReadDb : WORD; // create var for write
_dwReadDb : DWORD; // create var for write
_rReadDb : REAL; // create var for write
_lrReadDb : LREAL; // create var for write
_stReadDb : stString; // create var for write
END_VAR
In your code, toggle bEnable
of _PLC to TRUE
. There is optional bAutoReconnect
to re-establish session if terminated from idling.
Siemens is 0 bytes aligned, so make sure you specify CoDeSys STRUCTs with {attribute 'pack_mode' := '0'}
. Read the CoDeSys pack mode.
- Endianness:
- Siemens data uses Big Endian format, for your CoDeSys controller (e.g. RaspberryPi, x86, etc) is most likely Little Endian. This means if your CoDeSys controller is reading a Siemens PLC's data block, then the values will not be correct. For example, the Siemens PLC has a variable called
test
with data type ofUINT
with value 1 (16#0001
). If you read this value from your CoDeSys controller, the value will show up as 256 (16#0100
) instead because the bytes order is swapped. ForbReadDB
andbWriteDB
, if you want to automatically have this data converted, then you will need to specify the correct data struct viaeDataSize
(e.g.eDataSize:=CoDeSys_S7Comm.eDataType._UINT
). This will automatically swap the bytes for you from Little Endian (CoDeSys) to Big Endian (Siemens) and vice versa. If you have a STRUCT that contains a mix of data types, then select _STRUCT (e.g.eDataSize:=CoDeSys_S7Comm.eDataType._STRUCT
) and no conversion will be made. - Data type
STRING
doesn't require conversion (e.g.test
shows up astest
). - Big Endian:
- S7-300
- S7-400
- S7-1200
- S7-1500 (non-optimized data blocks)
- Little Endian:
- S7-1500 (optimized data blocks)
- Siemens data uses Big Endian format, for your CoDeSys controller (e.g. RaspberryPi, x86, etc) is most likely Little Endian. This means if your CoDeSys controller is reading a Siemens PLC's data block, then the values will not be correct. For example, the Siemens PLC has a variable called
- Data block optimization:
- Right-click on your data block and go to Properties... and go under
Attributes
. This is where you can check and uncheckOptimized block access
. For now, this library only handles Big Endian, but we'll be able to handle both, so check back on later revision.
- Right-click on your data block and go to Properties... and go under
- Permission:
- Go into your Siemens' controller properties (right click -> Properties...) and go under the
Protect & Security
section. Make sure to have `Full access (no protection) checked only if you want to read and write. From a security standpoint, it is never a good idea to provide access without first assessing your environment to make sure proper security measures are in place (e.g. firewall, segmented VLAN, etc).
- Go into your Siemens' controller properties (right click -> Properties...) and go under the
NOTE:
- Possible arguments for
bReadDB
:uiIndex
(UINT) [required]: Data block index number.udiOffset
(UDINT) [required]: Address offset of the data blockeDataSize
(ENUM) [required]: Data type size (e.g. BOOL has size of 1 BYTE). Choose _STRUCT when reading STRING or custom STRUCTpbBuffer
(POINTER TO BYTE) [required]: Pointer to the output buffer.uiBufferSize
(UINT): Size of the output buffer (pbBuffer).- Default:
0
- Default:
uiElements
(UINT): Elements to be requested.- Note: Not implemented.
- Default:
0
psId
(POINTER TO STRING): Pointer to caller id [e.g. psId:=ADR('Read#1: ')].- Useful for troubleshooting. If you incorrectly declare your data type , a
Data Type Inconsistent
would typically be returned. By declaringpsId:=ADR('Read#1: ')
, sError will return'Read#1: Data Type Inconsistent'
to let you know of the error.
- Useful for troubleshooting. If you incorrectly declare your data type , a
bReadDB
returns TRUE on successful read.
Below reads a PLC's variable with data type of BOOL at address offset 0 of data block #2 and writes to a CoDeSys variable with data type BOOL called _bReadDb
.
_PLC.bReadDB(uiIndex:=2,
udiOffset:=0,
eDataSize:=CoDeSys_S7Comm.eDataType._BOOL,
pbBuffer:=ADR(_bReadDb),
uiBufferSize:=SIZEOF(_bReadDb),
psId:=ADR('Read#0: '));
Below reads a PLC's variable with data type of UDINT at address offset 12 of data block #2 and writes to a CoDeSys variable with data type UDINT called _udiReadDb
.
_PLC.bReadDB(uiIndex:=2,
udiOffset:=12,
eDataSize:=CoDeSys_S7Comm.eDataType._UDINT,
pbBuffer:=ADR(_udiReadDb),
uiBufferSize:=SIZEOF(_udiReadDb),
psId:=ADR('Read#7: '));
Below reads a PLC's variable with data type LREAL at address offset 42 of data block #2 and writes to a CoDeSys variable with data type LREAL called _lrReadDb
.
_PLC.bReadDB(uiIndex:=2,
udiOffset:=42,
eDataSize:=CoDeSys_S7Comm.eDataType._LREAL,
pbBuffer:=ADR(_lrReadDb),
uiBufferSize:=SIZEOF(_lrReadDb),
psId:=ADR('Read#13: '));
Below reads a PLC's variable with data type STRING at address offset 42 of data block #2 and writes to a CoDeSys variable with data type STRUCT called _stReadDb
.
Note: Keep.
_PLC.bReadDB(uiIndex:=2,
udiOffset:=50,
eDataSize:=CoDeSys_S7Comm.eDataType._STRING,
pbBuffer:=ADR(_stReadDb),
uiBufferSize:=SIZEOF(_stReadDb),
psId:=ADR('Read#14: '));
NOTE:
- Siemens PLCs use Big Endian, so you will need to most likely swap bytes when reading a custom STRUCT. Known data types have been swapped for you already.
- Possible arguments for
bWriteDB
:uiIndex
(UINT) [required]: Data block index number.- You can also point to a STRING instead [e.g. psTag:=ADR(_sMyTestString)].
udiOffset
(UINT) [required]: Address offset of the data blockeDataSize
(ENUM) [required]: Data type size (e.g. BOOL has size of 1 BYTE). Choose _STRUCT when writing custom STRUCTpbBuffer
(POINTER TO BYTE) [required]: Pointer to the output buffer.uiBufferSize
(UINT): Size of the output buffer (pbBuffer).- Default:
0
- Default:
uiElements
(UINT): Elements to be requested.- Note: Not implemented.
- Default:
0
psId
(POINTER TO STRING): Pointer to caller id [e.g. psId:=ADR('Write#1: ')].- Useful for troubleshooting. If you incorrectly declare your data type , a
Data Type Inconsistent
would typically be returned. By declaringpsId:=ADR('Write#1: ')
, sError will return'Write#1: Data Type Inconsistent'
to let you know of the error.
- Useful for troubleshooting. If you incorrectly declare your data type , a
bWriteDB
returns TRUE on successful write
Below writes a CoDeSys variable with data type BOOL called _bWriteDb
to a PLC's variable with data type of BOOL at address offset 0 of data block #2.
_PLC.bWriteDB(uiIndex:=2,
udiOffset:=0,
eDataSize:=CoDeSys_S7Comm.eDataType._BOOL,
pbBuffer:=ADR(_bWriteDb),
uiBufferSize:=SIZEOF(_bWriteDb),
psId:=ADR('Write#0: '));
Below writes a CoDeSys variable with data type UDINT called _udiWriteDb
to a PLC's variable with data type UDINT at address offset 12 of data block #2.
_PLC.bWriteDB(uiIndex:=2,
udiOffset:=12,
eDataSize:=CoDeSys_S7Comm.eDataType._UDINT,
pbBuffer:=ADR(_udiWriteDb),
uiBufferSize:=SIZEOF(_udiWriteDb),
psId:=ADR('Write#7: '));
Below writes a CoDeSys variable with data type LREAL called _lrReadDb
to a PLC's variable with data type LREAL at address offset 42 of data block #2.
_PLC.bWriteDB(uiIndex:=2,
udiOffset:=42,
eDataSize:=CoDeSys_S7Comm.eDataType._LREAL,
pbBuffer:=ADR(_lrWriteDb),
uiBufferSize:=SIZEOF(_lrWriteDb),
psId:=ADR('Write#13: '));
Below writes a CoDeSys variable with data type STRUCT called _stWriteDb
to a PLC's variable with data type STRING at address offset 52 of data block #2.
NOTE: Writing to a PLC string must follow the format of a STRUCT made up of maxLen (BYTE), actualLen (BYTE), and a STRING. These will be set during write! See Examples/Siemens
folder for more details
_PLC.bWriteDB(uiIndex:=2,
udiOffset:=50,
eDataSize:=CoDeSys_S7Comm.eDataType._STRING,
pbBuffer:=ADR(_stWriteDb),
uiBufferSize:=SIZEOF(_stWriteDb),
psId:=ADR('Write#14: '));
Testing was done using a Raspberry Pi 3, with CoDeSys 3.5.16.0 runtime installed, to communicate with a Siemens CPU 1517F-3 PN/DP
PLC. You will need to install SysTime and OSCAT Basic (this is for time formatting).
bGetCpuInfo()
(BOOL) is automatically called after TCP connection is established to return device info. You could scan your network for other Siemens PLCs.
bGetCpuState()
(BOOL) is automatically called after TCP connection is established to return device operating state. Call this function to get Requested/Previous Mode.
- Examples:
- Retrieve single parameter as UINT using:
_uiOemId := _PLC.uiOemId;
- Output:
0
- Output:
- Retrieve single parameter as STRING using:
_sOemId := _PLC.sOemId;
- Output:
'0x0000'
- Output:
- Retrieve entire STRUCT using:
_stDevice := _PLC.stCpuInfo;
- Requires STRUCT variable:
_stDevice : CoDeSys_S7Comm.stCpuInfo;
- Output:
- sSystemName:
'S71500/ET200MP station_1'
- sModuleName:
'PLC_1'
- uiPlantId:
16#2020
- sPlantId:
'0x2020'
- sCopyright:
'Original Siemens Equipment'
- sSerialNumber:
'S C-J3P979902017'
- sCpuType:
'CPU 1517F-3 PN/DP'
- sMcSerialNumber:
'SMC_3b64ee0208 '
- uiManufacturerId:
16#002A
- sManufacturerId:
'0x002A'
- uiProfileId:
16#F600
- sProfileId:
'0xF600'
- uiProfileSpec:
16#0001
- sProfileSpec:
'0x0001'
- sOemCopyright:
''
- uiOemId:
16#0000
- sOemId:
'0x0000'
- udiOemAddId:
16#00000000
- sOemAddId:
'0x00000000'
- sLocationId:
'Test Lab'
- bRequestedMode:
16#08
- sRequestedMode:
'Run'
- bPreviousMode:
16#00
- sPreviousMode:
'Unknown'
- sSystemName:
- Requires STRUCT variable:
- Retrieve single parameter as UINT using:
bGetPlcTime()
(BOOL) requests the current PLC time. The function can handle 64b time up to nanoseconds resolution, but the PLC's accuracy is only available at the milliseconds.
- Example:
- Retrieve time as STRING:
_sPlcTime := _PLC.sPlcTime;
- Output:
'LDT#2021-12-22-07:58:21.754000000'
- Output:
- Retrieve time in nanoseconds as ULINT:
_uliPlcTime := _PLC.uliPlcTime;
- Output:
1640159905367754000000
- Output:
- Retrieve year:
_uiYear := _PLC.uiYear;
- Output:
2021
- Output:
- Retrieve year in short format:
_bYear := _PLC.bYear;
- Output:
21
- Output:
- Retrieve month:
_bMon := _PLC.bMon;
- Output:
12
- Output:
- Retrieve day:
_bDay := _PLC.bDay;
- Output:
22
- Output:
- Retrieve hour:
_bHour := _PLC.bHour;
- Output:
7
- Output:
- Retrieve minute:
_bMinute := _PLC.bMinute;
- Output:
58
- Output:
- Retrieve second:
_bSecond := _PLC.bSecond;
- Output:
21
- Output:
- Retrieve millisecond:
_uiMilli := _PLC.uiMillisecond;
- Output:
754
- Output:
- Retrieve day of the week:
_bDOW := _PLC.bDayOfWeek;
- Output:
4
- Output:
- Retrieve day of the week:
_sDOW := _PLC.sDayOfWeek;
- Output:
'Wednesday'
- Output:
- Retrieve time as STRING:
bSetPlcTime(ULINT)
(BOOL) sets the PLC time.
- Examples:
- Synchronize PLC's time to IPC's time:
bSetPlcTime()
- Set a PLC time to
Friday, July 3, 2020 10:31:18 PM GMT
with seconds level accuracy in microseconds:bSetPlcTime(1593815478000000)
- NOTE: Look at built-in
Timestamp
function block
- Synchronize PLC's time to IPC's time:
NOTE: There are a lot more, so dive into library to see what works best for you
bAutoReconnect
(BOOL) re-establishes session if disconnected.- Default:
FALSE
- Example set:
_PLC.bAutoReconnect := TRUE;
- Example get:
_bReconnect := _PLC.bAutoReconnect;
- Default:
bHexPrefix
(BOOL) attaches the '0x' prefix to the strings from CPU info STRUCT.- Default:
TRUE
- Default:
bRack
(BYTE) specifies the module rack.- Default:
0
- Default:
bSlot
(BYTE) specifies the module slot.- Default:
0
- Default:
sDeviceIp
(STRING) allows you to change device IP from the one specified initially.- Default:
'11.200.0.10'
- Toggle bEnable to update settings
- Default:
uiDevicePort
(UINT) allows you to change device port from the one specified initially.- Default:
102
- Toggle bEnable to update settings
- Default:
udiTcpWriteTimeout
(UDINT) specifies the maximum time it should take for the TCP client write to finish.- Default:
200000
microseconds
- Default:
uiCommWriteTimeout
(UINT) specifies the maximum time it should take for a write request to finish.- Default:
200
milliseconds
- Default:
udiTcpClientTimeOut
(UDINT) specifies the TCP client timeout.- Default:
500000
microseconds
- Default:
udiTcpClientRetry
(UDINT) specifies the auto reconnect interval forbAutoReconnect
.- Default:
5000
milliseconds
- Default:
gvcParameters
holds the default values from above, so adjust values to fit your needs.
bDisconnect()
(BOOL) sends a disconnect session request.- NOTE: If
bAutoReconnect
is set to TRUE, session will be re-established within the specifiedudiTcpClientRetry
period.
- NOTE: If
bResetFault()
(BOOL) call this to ACK read/write fault flags.