Modbus TCP on Beckhoff TwinCAT: Client and Server Setup

By | July 23, 2026

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.

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:

  1. Open your PLC project
  2. In the Solution Explorer, right-click References under the PLC POU
  3. Select Add library
  4. 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.
  5. 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 BlockModbus function codePurpose
FB_MBReadCoils0x01Read Coils
FB_MBReadInputs0x02Read Discrete Inputs
FB_MBReadHoldingRegs0x03Read Holding Registers
FB_MBReadInputRegs0x04Read Input Registers
FB_MBWriteSingleCoil0x05Write Single Coil
FB_MBWriteSingleReg0x06Write Single Register
FB_MBWriteCoils0x0FWrite Multiple Coils
FB_MBWriteRegs0x10Write Multiple Registers
FB_MBReadWriteRegs0x17Read/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_ModbusSrv error 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:

  1. Open Services on the runtime PC
  2. Look for TcModbusSrv or similar service name
  3. 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.VariableName or POU_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:

  1. In the Solution Explorer, click on the PLC project node
  2. Look at the Project Settings or Properties
  3. 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:

  1. Stop the TcModbusSrv (or equivalent) Windows service
  2. Start the service again
  3. 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 = TRUE means a request is in progress
  • bError = TRUE with nErrId value 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.

Author: Zakaria El Intissar

I've spent 13 years in power system automation, electrical protection, and SCADA communication, as an automation and industrial computing engineer. ScadaProtocols.com is where I turn what I've learned on site into plain guides and working tools — so other engineers can decode, analyze, and troubleshoot industrial communication protocols without the guesswork.