The device tree fixes a Modbus device list at download time. The ModbusFB library makes each connection and request a function block, so the enable flag, unit ID, IP address, port, serial baud and response timeout become ordinary variables you write while the application runs. The article shows which settings move, what an offline device costs, and when each path wins.
Yes, a CODESYS application can add a Modbus device, re-address one, or switch a node off while it runs. The device tree that every CODESYS project shows is one path, and it fixes the device list when the application downloads. The ModbusFB library is the other path: each connection and each request becomes a function block instance, so the settings that were device-tree properties become ordinary variables. This article explains what the library changes, which settings you can write at runtime, what an offline device costs either way, and when the static tree is still the right tool.
What the device tree freezes
In the default workflow you add a Modbus TCP master or a Modbus serial master to the device tree, one slave object per remote device, and fixed request channels under each slave. The IP address, unit ID, serial framing and channel list are properties of those objects, and the runtime resolves them when the application downloads.
Three consequences follow:
- Adding a device, removing one, or changing an address means editing the tree, recompiling and downloading a new application.
- A configured device that is offline is polled anyway. Each request to it runs out the full response timeout before the master moves on, so the sweep stretches while the device is gone.
- Per-device runtime control is indirect. A few drivers expose an enable or parameter channel from code, but that depends on the driver version, and the address itself stays out of reach.
None of that is a defect. The tree fits a machine whose node list is known at build time and never changes. The friction appears when devices come and go, which is exactly the pattern of bringing a line up one node at a time and proving each before the next joins.
What the ModbusFB library changes
The Modbus Function Blocks library, ModbusFB for short, builds the same Modbus stack out of function blocks. You create one client instance per physical link, one request instance per transaction, and your own scheduler decides when each executes. The device list lives in your code as an array of records, and every parameter is a field you can write.
Two resources anchor the work:
- The Modbus example project in the CODESYS Forge example collection shows a complete client application, and it is the structural reference for instantiating clients and sequencing requests.
- The library documentation in your Library Manager is the source for input and method names. They differ between versions, and the CODESYS Modbus release notes record behaviour fixes along the way: the 4.5.0.0 notes, for example, list a timeout fix in the ModbusServer block. Read the notes for the version installed on your machine before you rely on a timeout behaviour.
The settings that become variables
Plan one record per device. The field names below are illustrative; the ModbusFB library's own input names come from its documentation in the Library Manager.
TYPE ST_MODBUS_DEVICE :
STRUCT
xEnabled : BOOL := TRUE; (* device takes part in the schedule *)
sIpAddress : STRING(15) := '192.168.0.10'; (* TCP endpoint *)
uiPort : UINT := 502; (* registered Modbus TCP port *)
byUnitId : BYTE := 1; (* unit or slave address *)
udiBaud : UDINT := 19200; (* serial links only *)
uiTimeoutMs : UINT := 500; (* response timeout per request *)
END_STRUCT
END_TYPE
What each field gives you at runtime:
- Enable flag. The wrapper polls the device only while it is true, so an offline device costs nothing.
- IP address and port. Modbus TCP uses registered port 502, the address is per device, and both are writable between connections.
- Unit ID. On TCP the unit ID travels in the MBAP header, the Modbus TCP routing header: a gateway needs it to reach a serial device behind it, while a native TCP device often ignores it. On serial the slave address runs from 1 to 247 per the Modbus serial-line specification, and 0 is broadcast, which draws no reply. If a re-addressed device starts returning zeros on its Modbus TCP analog values, that article separates the causes.
- Serial framing. Baud, parity and stop bits are runtime-writable, but on a shared port they are port-level: set the port to 19200 bit/s and every device on it moves with it, so change the whole port's records together.
- Response timeout. Set it per device, a little above the slowest reply you measured during commissioning. This article's example uses 500 ms. For scale, the documentation of one controller's Modbus TCP client gives 3000 ms as its default response timeout, adjustable in 10 ms steps, which is far more waiting than a plant-network device that answers in tens of milliseconds needs.
The wrapper pattern and the serial arbiter
Wrap each device in a small state machine so the rest of the application sees one clean interface:
- Idle: wait for the enable flag.
- Open: apply the record's endpoint fields to the client block and open the link. On a serial port, if the open fails, the usual cause on a PC-class runtime is another program holding the port; the fix for CODESYS COM.Open error 5004 walks that error down.
- Poll: issue the request set on the schedule, counting good and failed responses.
- Close: when the flag goes false or a change is pending, drop the link cleanly.
- Fault: on repeated failure, start a back-off timer of a second or two so one dead device cannot consume the scheduler, then retry from idle.
Two rules keep reconfiguration safe:
- Never move an endpoint while a request is in flight. Keep a shadow copy of each record; when a field changes, force the wrapper through close, copy the new values, then reopen. The device comes back on the next pass with the new address.
- Persist the records in a retain area, a recipe, or a file read at startup. That is the decision that removes the rebuild-to-reconfigure problem, because field changes survive a power cycle with no download.
On serial, add one more object: a per-port arbiter. Each wrapper asks the arbiter for the port, runs exactly one transaction, and hands it back. Without it, two wrappers interleave bytes on the same port and produce checksum errors and crossed replies that look like device faults. A wrong slave address on serial shows up as a no-reply or an address error; the Modbus RTU no-reply and address errors article separates the two.
TCP and serial: what differs
| Aspect | Modbus TCP | Modbus RTU |
|---|---|---|
| Endpoint | IP address plus port 502 per device; each device gets its own connection | One shared serial port; the framing belongs to the port |
| Address field | Unit ID in the MBAP header; required by gateways, often ignored by native TCP devices | Slave address 1 to 247; 0 is broadcast, no reply |
| Framing change at runtime | Per device, between connections | Port-level; every device on the port moves together |
| Concurrency | Requests to different devices can overlap | One transaction on the wire at a time; the scheduler serializes |
| Offline device | Its connect or response timeout, then back-off | Its full response timeout stalls every device on the port |
Budget serial time honestly. A read of 4 holding registers puts 8 request bytes and 13 response bytes on the wire, 21 bytes total. At 11 bits per byte (start bit, 8 data bits, parity or a second stop bit) that is 231 bits, about 12 ms at 19200 bit/s. The Modbus serial-line specification adds a silent gap of 3.5 character times, about 2 ms, on each side of each frame, so budget about 16 ms of wire time per read plus the device's own processing. Six such reads take about 96 ms of wire time per sweep before any timeout.
Example: one dead device on a six-device line
Inputs, and where each comes from:
- Six Modbus TCP devices polled in sequence, a mid-size line typical for this pattern.
- Response timeout per request: 500 ms, the value configured in the record above, sized for devices that answer in tens of milliseconds on a plant network.
- Comparison figure: 3000 ms, the documented default response timeout of one controller's Modbus TCP client, adjustable in 10 ms steps.
- The rule: a request to a powered-off device gets no reply, so the scheduler waits the full response timeout before moving on, and the wait repeats in every sweep.
Arithmetic:
- Dead device with the 3000 ms default left in place: every sweep carries a 3000 ms wait, so the whole line refreshes no faster than once every 3 s, however healthy the other five devices are.
- Dead device with the timeout tuned to 500 ms: the wait drops to 500 ms per sweep, a floor of 0.5 s between refreshes. Tuning alone recovers 3000 - 500 = 2500 ms of every sweep.
- Dead device with its enable flag cleared at runtime: the wait is 0 ms. The five live devices are polled back to back and the sweep returns to its all-devices-alive period.
Result: the enable flag is worth the whole timeout, up to 3000 ms of stall per sweep at a default nobody tuned.
Decision: tune each timeout to just above the device's slowest measured reply, and clear the flag the moment a device leaves service. A device that will sit offline for more than one sweep should be disabled, because leaving it polled holds up its full timeout on every pass.
Which path to pick
Pick the device tree when the node list is known at build time and never changes after download: a machine with fixed, always-present devices. The tree is less code, and the runtime's own driver handles polling, retries and bus scheduling for you.
Pick ModbusFB when devices are added, removed or re-addressed after commissioning: a line brought up node by node, gateways whose serial devices move, a test stand that takes whatever device is connected. You write the scheduler, the wrapper and the arbiter, and every address becomes field-serviceable data.
Beyond both sits raw socket work: framing the bytes yourself for a device the standard stack cannot express. You then own the checksum, the header and the timeouts, which is worth it only for genuinely nonstandard equipment.
Recommendation: use the ModbusFB library whenever the device set changes in the field, and keep the device records in a retain area or recipe so a re-addressed line survives a power cycle without a download. Stay on the device tree for a fixed machine whose node list never changes; there the tree costs less code and the driver does the scheduling. If the polled values also belong on a wall board, that board is a separate display job with its own content: the shop-floor screens and andon boards we build are made for that side, and the Modbus work stays in the PLC.
Questions
Can I add a Modbus device while the application is running?
With the ModbusFB pattern, yes. Write the new device's record into the configuration array with its enable flag cleared, check the endpoint, then set the flag. The wrapper opens the link and starts polling on the next scheduler pass, with no download. When you change an existing device's address instead, follow the same sequence the wrapper enforces: close the link, write the new values, reopen.
What does a dead device cost if the client retries?
Budget the worst case as attempts times timeout. With two retries enabled and a 500 ms response timeout, a device that never answers can hold the scheduler three times per sweep: 3 × 500 ms = 1500 ms. Clear that device's enable flag and the cost drops to zero on the next pass. Keep attempts times timeout, added up over every configured device, inside the update period the application needs.
How do I confirm a runtime change took effect?
Watch two values. The device's request should complete well inside its response timeout, with the error count no longer rising, and the sweep period should return to the figure you measured with every enabled device alive. If the sweep stays one timeout long, a request is still expiring, and the usual cause is a record whose IP address, unit ID or serial settings do not match the device.
Planning information, not legal or engineering advice. Rules differ by place: check codes and permits with your local authority and your licensed trades.