If you have configured Modbus TCP on a CODESYS PLC, you might expect the same device-tree workflow in Beckhoff TwinCAT. It works differently. Although TwinCAT and CODESYS both implement IEC 61131-3 programming languages, TwinCAT uses Beckhoff’s own runtime and engineering environment. Because of that, Modbus TCP configuration is completely different from what CODESYS users expect — you will not find a “Modbus TCP Master” node to drop into a device tree here.
Instead, TwinCAT handles Modbus TCP through the TF6250 supplement — a licensed function package that provides both client (master) and server (slave) capabilities. The client uses function blocks called from your PLC code. The server uses an XML mapping file to expose PLC variables through Modbus registers. Both roles can run on the same TwinCAT system simultaneously.
This article walks through both setups on TwinCAT 3, covers licensing, and lists the pitfalls you will actually hit in the field.
Table of Contents
What you need before starting
- TwinCAT 3 XAE (eXtended Automation Engineering) — the development environment on your engineering PC. The engineering PC does not require a production TF6250 license
- TwinCAT 3 XAR (eXtended Automation Runtime) — the runtime on the target PLC or Industrial PC (IPC). The runtime is where the TF6250 license is required for production. Beckhoff offers several runtime platforms — from embedded PLCs like the CX series to standard Industrial PCs — that all run the same TwinCAT XAR runtime
- TF6250 supplement installed on the runtime system — the Modbus TCP function package
- TF6250 license on the runtime — 7-day trial license available for testing; full license required for production
- Network configuration — the TwinCAT system’s Ethernet interface properly set up with IP, subnet, and gateway
- Windows Firewall configured — for the server role, allow inbound TCP port 502 on the runtime PC. For the client role, only outbound TCP connections are normally required.
The TF6250 package installs both the client function block library (Tc2_ModbusSrv) and the server service that runs on the runtime system.
Two roles, two approaches
TwinCAT Modbus TCP works in two roles, each configured differently:
Modbus TCP Client (Master) — Your TwinCAT PLC initiates requests to remote Modbus TCP devices. Configuration is done in PLC code using function blocks from the Tc2_ModbusSrv library. No system configuration file needed.
Modbus TCP Server (Slave) — Your TwinCAT PLC accepts requests from external Modbus TCP clients (SCADA, HMI, other PLCs). Configuration is done via a Modbus.xml file that maps Modbus register addresses to ADS symbols in your PLC. No function blocks needed in your PLC code.
Both roles can coexist on the same runtime — TF6250 handles them independently.
For the general master/slave versus client/server terminology, see our Modbus Client-Server vs Master-Slave Terminology article. TwinCAT uses the traditional master/slave language in its documentation and function block names, matching most existing Modbus vocabulary.
Modbus TCP Client (Master) configuration
Use this role when your TwinCAT PLC needs to poll data from remote Modbus TCP devices — drives, meters, sensors, gateways.
Step 1: Add the Modbus TCP client library
In TwinCAT XAE:
- Open your PLC project
- In the Solution Explorer, right-click References under the PLC POU
- Select Add library
- Choose the Beckhoff Modbus TCP client library — commonly named Tc2_ModbusSrv on current TF6250 installations, though the exact library name has varied across TwinCAT and TF6250 versions. Check the TF6250 documentation for the version installed on your system.
- Click OK
The library provides function blocks for every standard Modbus function code.
Step 2: Understand the client function blocks
Modbus TCP client function blocks provided by TF6250 follow the general naming pattern FB_MB<Operation>. The main ones are listed below (exact names may vary slightly by library version):
| Function Block | Modbus function code | Purpose |
|---|---|---|
FB_MBReadCoils | 0x01 | Read Coils |
FB_MBReadInputs | 0x02 | Read Discrete Inputs |
FB_MBReadHoldingRegs | 0x03 | Read Holding Registers |
FB_MBReadInputRegs | 0x04 | Read Input Registers |
FB_MBWriteSingleCoil | 0x05 | Write Single Coil |
FB_MBWriteSingleReg | 0x06 | Write Single Register |
FB_MBWriteCoils | 0x0F | Write Multiple Coils |
FB_MBWriteRegs | 0x10 | Write Multiple Registers |
FB_MBReadWriteRegs | 0x17 | Read/Write Multiple Registers |
All follow the same parameter pattern.
Step 3: Configure a read function block
How TF6250 function blocks are meant to be called: these function blocks are designed to be called every PLC cycle, with bExecute acting as a rising-edge trigger. A common beginner mistake is to call them only once when triggering a request. In practice, the function block must run every cycle so it can complete its internal state machine (send request, wait for response, update outputs). You control when a new request starts by setting bExecute to TRUE; you do not control whether the function block runs.
Example: reading 10 holding registers from a Modbus device at 192.168.1.50.
VAR
fbRead : FB_MBReadHoldingRegs;
aRegisters : ARRAY[0..9] OF WORD;
bExecute : BOOL;
bBusy : BOOL;
bError : BOOL;
nErrId : UDINT;
END_VAR
fbRead(
sIPAddr := '192.168.1.50', // Target device IP
nTCPPort := 502, // Modbus TCP port
nUnitID := 1, // Modbus Unit ID
nQuantity := 10, // Number of registers
nMBAddr := 0, // Starting register (0-based)
cbLength := SIZEOF(aRegisters), // Buffer size
pDestAddr := ADR(aRegisters), // Pointer to destination
bExecute := bExecute,
tTimeout := T#1S, // Response timeout
bBusy => bBusy,
bError => bError,
nErrId => nErrId
);
Key parameters:
- sIPAddr — target Modbus TCP device IP address as a STRING
- nTCPPort — TCP port (default 502)
- nUnitID — Modbus Unit ID; see our Modbus Unit Identifier article for guidance
- nQuantity — number of items to read/write
- nMBAddr — starting Modbus register or coil address
- cbLength — buffer size in bytes
- pDestAddr — pointer to your PLC variable (destination for reads, source for writes)
- bExecute — rising edge triggers the request
- tTimeout — how long to wait for a response
Outputs:
- bBusy — request in progress
- bError — request failed
- nErrId — error code (check against
Tc2_ModbusSrverror definitions)
Step 4: Configure a write function block
Writing 5 holding registers to the same device:
VAR
fbWrite : FB_MBWriteRegs;
aValues : ARRAY[0..4] OF WORD := [100, 200, 300, 400, 500];
bExecuteWr : BOOL;
END_VAR
fbWrite(
sIPAddr := '192.168.1.50',
nTCPPort := 502,
nUnitID := 1,
nQuantity := 5,
nMBAddr := 100, // Write to registers 100-104
cbLength := SIZEOF(aValues),
pSrcAddr := ADR(aValues), // NOTE: pSrcAddr for writes, not pDestAddr
bExecute := bExecuteWr,
tTimeout := T#1S
);
Note the parameter difference: writes use pSrcAddr (source buffer) instead of pDestAddr.
Step 5: Trigger requests with rising edges
Function blocks trigger on the rising edge of bExecute. Common patterns:
Cyclic polling — use a timer:
VAR
tonPoll : TON;
END_VAR
tonPoll(IN := NOT tonPoll.Q, PT := T#500MS);
IF tonPoll.Q THEN
fbRead.bExecute := TRUE;
END_IF
// Reset execute when done
IF NOT fbRead.bBusy AND fbRead.bExecute THEN
fbRead.bExecute := FALSE;
END_IF
Event-triggered — trigger from application logic:
IF bStartPressed AND NOT fbWrite.bBusy THEN
fbWrite.bExecute := TRUE;
END_IF
IF NOT fbWrite.bBusy AND fbWrite.bExecute THEN
fbWrite.bExecute := FALSE;
END_IF
Sequential polling for reliability. Avoid triggering multiple Modbus requests simultaneously — especially against the same target device. Wait until bBusy becomes FALSE on the current function block before launching the next request. If you need to poll several devices, chain them in a small state machine so only one request is in flight at a time. Sequential polling generally produces more reliable communication than parallel bursts, particularly on cellular, congested, or serial-gateway-backed networks.
Step 6: Handle 32-bit values
Modbus registers are 16 bits. TwinCAT reads them as WORD (or UINT). For 32-bit values (DINT, REAL) spanning two registers, you need to combine them correctly. TwinCAT and the Modbus device may use different byte and word orderings, depending on the device manufacturer.
Portable approach using a UNION for bit-level reinterpretation:
TYPE U_DwordReal :
UNION
dw : DWORD;
r : REAL;
END_UNION
END_TYPE
FUNCTION ModbusToReal : REAL
VAR_INPUT
HighWord : WORD;
LowWord : WORD;
END_VAR
VAR
u : U_DwordReal;
END_VAR
// Combine two 16-bit registers into 32-bit DWORD (ABCD byte order)
u.dw := SHL(TO_DWORD(HighWord), 16) OR TO_DWORD(LowWord);
// Reinterpret bit pattern as IEEE 754 REAL
ModbusToReal := u.r;
For CDAB (word-swapped) devices, swap HighWord and LowWord before combining. For BADC and DCBA, swap bytes within each word. See our Modbus Byte Order and Word Order article for the four possible arrangements.
Modbus TCP Server (Slave) configuration
Use this role when external systems need to read or write your TwinCAT PLC’s variables via Modbus TCP.
Step 1: Verify the TF6250 server is running
The TF6250 server runs as a Windows service on the TwinCAT runtime system. Check:
- Open Services on the runtime PC
- Look for TcModbusSrv or similar service name
- Verify it is running (start automatically on boot)
If it is not installed, install the TF6250 supplement on the runtime system.
Step 2: Configure the Modbus.xml file
The heart of the server configuration is an XML file that maps Modbus register addresses to ADS symbols in your PLC — typically named Modbus.xml, though some TF6250 versions use TcModbusSrv.xml.
The configuration file is typically installed with TF6250 on the runtime PC. The exact path depends on the TwinCAT version and installation options. Example path on a typical TF6250 installation:
C:\TwinCAT\Functions\TF6250-Modbus-TCP\Server\Modbus.xml
Check your TF6250 documentation for the correct location on your system.
The file uses XML to declare mappings between Modbus register addresses and ADS variable references.
Example structure:
xml
<?xml version="1.0" encoding="utf-8"?>
<Configuration>
<Mapping>
<InputRegisters>
<MappingInfo>
<VarName>MAIN.Actual_Temperature</VarName>
<StartAddress>0</StartAddress>
<BitSize>16</BitSize>
</MappingInfo>
<MappingInfo>
<VarName>MAIN.Actual_Pressure</VarName>
<StartAddress>1</StartAddress>
<BitSize>16</BitSize>
</MappingInfo>
</InputRegisters>
<HoldingRegisters>
<MappingInfo>
<VarName>MAIN.Setpoint_Temperature</VarName>
<StartAddress>0</StartAddress>
<BitSize>16</BitSize>
</MappingInfo>
<MappingInfo>
<VarName>MAIN.Setpoint_Pressure</VarName>
<StartAddress>1</StartAddress>
<BitSize>16</BitSize>
</MappingInfo>
</HoldingRegisters>
<Coils>
<MappingInfo>
<VarName>MAIN.Command_Start</VarName>
<StartAddress>0</StartAddress>
<BitSize>1</BitSize>
</MappingInfo>
</Coils>
</Mapping>
<ADSSettings>
<AmsNetId>127.0.0.1.1.1</AmsNetId>
<AmsPort>851</AmsPort>
</ADSSettings>
</Configuration>
Key elements:
- VarName — the ADS symbol path to your PLC variable (typically
MAIN.VariableNameorPOU_Name.VariableName) - StartAddress — the Modbus register or coil address (0-based)
- BitSize — usually 16 for registers, 1 for coils/discrete inputs
- AmsNetId — the AMS Net ID of the runtime (127.0.0.1.1.1 for local)
- AmsPort — the ADS port of your PLC project (typically 851 for the first runtime, 852 for the second, etc.)
The exact XML schema may vary slightly by TF6250 version — consult the TF6250 documentation for the schema used by your version.
Important: PLC variables must be available as ADS symbols. TF6250 accesses your variables through the ADS interface, so anything the server maps must exist in the PLC’s exported symbol table. If a variable is excluded from the exported ADS symbol table — for example, because it is declared as a local temporary, marked as non-persistent, or otherwise not exposed — TF6250 cannot expose it via Modbus. External clients will connect successfully but reads on that address will fail. Move the variable to MAIN or a Global Variable List (GVL) and ensure it is available in the ADS symbol table.
Step 3: Match the ADS port to your PLC project
The AmsPort in the XML must match your PLC project’s ADS port. Check your TwinCAT project:
- In the Solution Explorer, click on the PLC project node
- Look at the Project Settings or Properties
- The ADS port is typically 851 for the first PLC runtime, 852 for the second, and so on
If the AmsPort is wrong, the server cannot access your PLC variables — Modbus clients will connect but reads will fail. Under the hood, the TF6250 server uses the local ADS Router to talk to the PLC runtime; the AmsPort and AmsNetId together tell the ADS Router which runtime and which project to address. If ADS routing is misconfigured (unreachable AmsNetId, wrong route entries in the TwinCAT System Manager), the Modbus server will connect at the TCP layer but every register read will fail.
Step 4: Restart the TF6250 service
After editing Modbus.xml:
- Stop the TcModbusSrv (or equivalent) Windows service
- Start the service again
- Check the service log for parsing errors
The XML is loaded when the service starts, so changes take effect only after restart.
Step 5: Test with an external Modbus client
Use a Modbus master tool (OpenModScan, ModbusMechanic — see our Best Free Modbus Software article) to connect to your TwinCAT system’s IP address on port 502. Read holding registers to verify the mapping.
For details on the standard Modbus function codes clients will use, see our Modbus Function Codes Explained article.
TwinCAT versions and licensing
TwinCAT 3 is current. TwinCAT 2 is legacy but still deployed. The workflow described above applies to TwinCAT 3. TwinCAT 2 uses different library names (TcModbusSrv.lib instead of Tc2_ModbusSrv) and slightly different function block signatures, but the concepts are the same.
TF6250 licensing model:
- 7-day trial license — activate from TwinCAT XAE for evaluation and development testing
- Full license — required for continuous production operation. Beckhoff licenses are typically tied to specific hardware (either the runtime PC’s motherboard or a Beckhoff EL6070 license key module)
- License activation happens per-runtime via the TwinCAT XAE “License” node
Without a valid license, the Modbus TCP service may operate normally in trial mode for a limited time before disabling itself. Verify licensing before commissioning a production system.
Testing your TwinCAT Modbus TCP setup
After downloading your project and starting the runtime:
Test 1: Ping the TwinCAT system
From a laptop, ping the TwinCAT PLC or Industrial PC’s IP address. Failure means basic network connectivity is wrong — fix that first.
Test 2: Verify the TF6250 service
For the server role, confirm the TcModbusSrv service is running on the runtime PLC or IPC.
Test 3: Connect with an external Modbus master
For the server role, connect from a laptop using OpenModScan or similar and read a holding register you mapped. Successful reads confirm the XML mapping and ADS port are correct.
Test 4: Watch client function block outputs
For the client role, use the TwinCAT XAE online view to monitor the function block outputs:
bBusy = TRUEmeans a request is in progressbError = TRUEwithnErrIdvalue indicates a specific error- Successful completion updates the destination buffer
Test 5: Capture with Wireshark
If problems persist, capture TCP port 502 traffic between the TwinCAT system and the target device using Wireshark. See our Wireshark for Modbus TCP article for setup guidance.
Common pitfalls
Real TwinCAT Modbus TCP integrations hit these regularly.
1. TF6250 not installed on the runtime
Symptom: Tc2_ModbusSrv library not found, or Modbus.xml mappings ignored. Fix: install the TF6250 supplement on the runtime PC, not just the engineering PC. Both need it.
2. Windows Firewall blocking port 502
Symptom: TCP connection times out, but ping to the TwinCAT PC works. Fix: allow inbound TCP port 502 in Windows Defender Firewall on the runtime PC. This is one of the most common causes of “server not responding” symptoms.
3. TF6250 license expired
Symptom: server was working, suddenly stops accepting connections after several days. Fix: check the TF6250 license status in TwinCAT XAE. Trial licenses expire after 7 days.
4. Wrong ADS port in Modbus.xml
Symptom: Modbus TCP client connects successfully, but all reads return zero or errors. Fix: verify the AmsPort in Modbus.xml matches the ADS port of your PLC project. First runtime is usually 851.
5. Wrong AMS Net ID in Modbus.xml
Symptom: server accepts connections but cannot reach any PLC variables. Fix: for a local server (running on the same PC as the PLC runtime), use 127.0.0.1.1.1. For a remote configuration, use the actual AMS Net ID.
6. Function block execute stuck at TRUE
Symptom: client function block never completes; the same request repeats forever. Fix: reset bExecute to FALSE after the function block completes (when bBusy = FALSE). Otherwise, the function block never sees the rising edge for the next request.
7. Wrong buffer size
Symptom: request fails with a length-related error code, or partial data reads. Fix: cbLength must match the actual size of your buffer in bytes. SIZEOF(myArray) is the safe way to set it.
8. Byte order issues on 32-bit values
Symptom: 16-bit values look correct, but 32-bit REAL or DINT values are wrong. Fix: apply the correct byte and word swap for your device. Use the UNION-based reinterpretation shown above rather than TwinCAT’s TO_REAL(DWORD) — the latter does numeric conversion, not bit reinterpretation.
9. Unit ID mismatch
Symptom: TCP connects but requests time out. Fix: match the nUnitID in your function block call to what the target device expects. Especially critical when talking through Modbus TCP-to-RTU gateways.
10. XML schema mismatch
Symptom: TF6250 service fails to start or logs XML parsing errors after editing Modbus.xml. Fix: consult the TF6250 documentation for the exact schema used by your version. Element names and structure have changed between TF6250 releases.
11. PLC variable not exported as ADS symbol
Symptom: Modbus client connects, TCP handshake succeeds, but reads on a specific mapped address return zero, garbage, or an ADS-related error. Fix: verify the mapped variable actually exists in the PLC’s ADS symbol table. Variables declared inside function blocks without external visibility, temporary locals inside methods, or variables excluded from the exported ADS symbol table are not accessible via ADS — and therefore not accessible via TF6250. Move the variable to MAIN or a Global Variable List (GVL) to make it globally addressable. Confirm the symbol appears in the TwinCAT XAE Symbol Manager before deploying.
Common questions
Does Beckhoff TwinCAT support Modbus TCP?
Yes, through the TF6250 supplement — the Modbus TCP function package from Beckhoff. TF6250 provides both client (master) and server (slave) functionality on TwinCAT 3. The client is implemented as function blocks in the Tc2_ModbusSrv library; the server runs as a Windows service and maps Modbus registers to PLC variables via an XML configuration file. TF6250 requires a license for production use, with a 7-day trial license available for development.
Is TwinCAT based on CODESYS?
No. TwinCAT uses Beckhoff’s own runtime and engineering environment. Although TwinCAT and CODESYS both support IEC 61131-3 languages and share some visual similarities, they are separate platforms with different runtimes, different library ecosystems, and different Modbus TCP workflows. Do not assume CODESYS-based configuration steps apply to TwinCAT.
What is TF6250 in TwinCAT?
TF6250 is Beckhoff’s TwinCAT 3 Modbus TCP function package. It adds Modbus TCP client and server capabilities to a TwinCAT 3 runtime. The package installs the Tc2_ModbusSrv function block library for the client role and a Windows service for the server role. A TF6250 license is required for full production use; a 7-day trial license is available for testing and development.
Which TwinCAT library do I use for Modbus TCP?
For most current TF6250 installations on TwinCAT 3, the client library is named Tc2_ModbusSrv, although Beckhoff has changed library names between releases. Check the library supplied with your installed TF6250 version. It contains function blocks for every standard Modbus function code (FB_MBReadHoldingRegs, FB_MBWriteRegs, FB_MBReadCoils, etc.). Add it to your PLC project via References → Add Library in the TwinCAT XAE Solution Explorer. TwinCAT 2 uses different library names entirely.
How do I expose PLC variables as Modbus TCP registers in TwinCAT?
Configure the Modbus.xml file used by the TF6250 server. Each entry in the XML maps a Modbus register address (StartAddress) to a PLC variable path (VarName, e.g., MAIN.MyVariable) and specifies the size (BitSize). External Modbus TCP clients then access these variables through standard Modbus function codes. The XML also needs the correct ADS port (typically 851) and AMS Net ID (127.0.0.1.1.1 for local) so the server can reach your PLC variables. Restart the TF6250 service after any XML change.
