Files
Codesys-Library-Atom-Modbus/docs/USER-GUIDE.md
T
2026-10-06 23:50:02 -06:00

10 KiB

atMb 0.4 user guide

Thread and PLC calls

One C worker progresses all TCP/RTU client transactions and TCP slave connections. It owns no pointers into IEC memory: request values, replies and slave tables live in fixed C storage. The external call copies buffers synchronously and serializes state access using a priority-inheritance engine mutex. No network wait or sleep is performed while that mutex is held. Serial driver calls can still take time; this is not a hard real-time or lock-free PLC interface.

Continue calling each client/request and slave every PLC scan. Client request selection is still performed by the IEC scheduler. A transaction can finish while the PLC is not scanning, and its result waits for the next scan; the worker does not independently cycle through the PLC request registry. TCP slaves respond from the last copied memory snapshot between scans.

Optional worker configuration and a separate core

VAR
    commWorker : atMb.Worker;
END_VAR

// Example only: CPU 2 must be online and permitted on this PLC.
commWorker(in_cpu := 2, in_interval := T#1MS, in_watchdog := T#2S);

Use only one Worker configuration instance for the component. Clients/servers start the worker automatically with defaults even without this FB. Default CPU -1 uses the thread creator's inherited affinity mask; it is not a promise of all CPUs. A nonnegative value pins the worker to that Linux logical CPU ID. CPU affinity does not reserve an exclusive core or move the PLC task: choose a CPU outside the PLC task's configured affinity where the runtime allows it. Changes affect this worker only. Failure is visible in out_errorCode and out_osError; no silent fallback.

The worker uses normal OS scheduling and explicitly avoids inheriting a PLC task's real-time priority. Sleep interval is 1..20 ms, default 1 ms; scheduling jitter can make actual intervals longer. Very short reply deadlines must account for worker interval and transaction framing. out_cycles helps monitor worker progress.

Scan-loss watchdog

Each client/server refresh call renews its own lease; another client's scans do not keep a stopped client alive. Default expiry is 2 s, configurable 100 ms..60 s. If scanning stops, the worker closes that context's connection/listener. In-flight requests finish as cancelled; potentially transmitted writes retain uncertainty. Handles/storage remain allocated for recovery. The next client/server refresh resumes connection attempts using the same context. Explicit release frees it.

Choose a watchdog longer than the worst expected PLC scan gap. This bounds how long a stopped PLC's server can respond with its last memory snapshot; it is not an immediate CODESYS application-stop hook. Component exit stops and joins the worker before freeing connections. Repeated thread shutdown/start is tested.

TCP read

VAR
    mb : atMb.ClientTCP;
    readHolding : atMb.ReqReadHolding;
    values : ARRAY[0..9] OF UINT;
END_VAR

mb(in_ipAddress := atMb.To_IPAddress(192,168,50,10), in_port := 502,
   in_enable := TRUE, in_timeout := T#500MS, in_reconnectDelay := T#100MS);
readHolding(client := mb, in_unitId := 1, in_startAddr := 0,
   in_quantity := 10, in_dataOffset := 0, data := values);

Keep one IEC task as owner of the related FB instances and arrays. Call the client before request registration on startup; polling starts after registration. The C entry point is serialized but that does not make concurrent IEC FB access safe. TCP addresses are four BYTEs; slave bind addresses are packed DWORDs.

RTU uses ClientRTU(in_device := '/dev/ttyUSB0', in_baudRate := 9600, in_parity := 0, in_dataBits := 8, in_stopBits := 1, in_rs485 := FALSE) and the same request FBs. RTU IDs must be 1..247. The runtime account needs serial permissions. Kernel RS485 direction control requires a supported driver. USB automatic-direction adapters generally use FALSE. Supported baud rates: 1200,2400,4800,9600,19200,38400, 57600,115200. Physical timing/driver validation remains necessary.

Requests, data and priorities

Block Function Array Maximum quantity
ReqReadCoil 1 BOOL 2000
ReqReadDiscrete 2 BOOL 2000
ReqReadHolding 3 UINT 125
ReqReadInput 4 UINT 125
ReqWriteCoil 15 BOOL 1968
ReqWriteHolding 16 UINT 123

Protocol addresses are zero-based; in_dataOffset is the actual array index, not a relative offset. Effective length is MIN(quantity, remaining array capacity). Zero length disables a request and now retains its statistics history. Arrays and FB instances must stay valid. There are 16 shared TCP/RTU client slots, 100 IEC requests per client, one in-flight transaction per client and four TCP server slots.

Lower in_priorityCurrent wins; oldest start age breaks ties. A request need not reach zero to run. Success restores in_prioritySetpoint; errors use 10/40/90 priority backoff. in_minDelay is start-to-start eligibility, not a fixed period.

Successful reads update the destination slice. Errors retain previous data; use out_validData AND NOT out_stale. Data age includes time the completed reply waited for a PLC scan. Client release invalidates cached data but does not erase arrays. An exception reply indicates communication, but does not produce valid read data.

Writes snapshot their source slice at submission. The worker never automatically replays a request. The IEC scheduler still polls write FBs cyclically. Set client in_holdUncertainWrites := TRUE to pause an uncertain write after a failure; writeNow() clears the hold and sets priority zero once the application decides retrying is appropriate. It does not bypass min-delay/connection conditions.

Timeouts and reconnects

Request in_timeout is numeric milliseconds (SYSTIME); client in_timeout is TIME. Nonzero request timeout wins, then client timeout, then the existing baud/data-rate formula. TCP's calculated default is about 38 ms, so an explicit timeout is clearer. The C worker uses CLOCK_MONOTONIC for deadlines, independent of PLC-provided time.

in_reconnectDelay defaults to 100 ms. Recovery proceeds without PLC ticks while the scan lease is valid. Actual downtime includes failure detection, retry delay, worker scheduling and remote-device readiness. Idle TCP disconnects are detected; there is no zero-downtime guarantee. TCP_NODELAY reduces small-message coalescing.

recreateClient() closes/recreates a context and cancels any active request. release() also sets in_enable := FALSE; a subsequent call explicitly passing TRUE re-enables it. Closing a context clears C lifetime counters.

Client statistics

Both clients expose stats : ClientStats and a public string property toString. The monitor display and stats.toString use OK:<doneCnt>/Er:<errorCnt>. For example, requests with 10/2 and 7/1 completions/errors show OK:17/Er:3 on the client.

Client member Aggregation
doneCnt / errorCnt Sum of registered request successes/failures
sentCnt / replyCnt Sum of accepted submissions / normal or exception replies
exceptionCnt / timeoutCnt Sum of exception replies / result 5 or 6 timeouts
offlineCnt Sum of request online-to-offline transitions
requestCount Registered requests, including disabled ones
activeCount Nonzero effective-length requests
onlineCount / offlineCount / unknownCount Active request communication state
busyCount Active requests executing
staleCount Active read requests with stale data
heldWriteCount Active write requests paused after uncertain delivery
totalValidDuration Sum of successful transaction duration totals, ms
averageValidDuration Weighted mean: totalValidDuration / doneCnt, integer ms
longestValidDuration Maximum successful duration across requests
online / offline Nonempty active set, all online / all explicitly offline

Historical counters include registered inactive requests; state counts include only active requests. An empty client is neither online nor offline. Aggregate counters are ULINT. Per-request counters retain their existing types and saturate rather than wrap; request offline counts saturate at UINT max. Successful duration accumulation stops when the associated success counter saturates to keep the mean consistent. There is no misleading aggregate of consecutive-error streaks or unrelated start intervals.

numMsgSent/numMsgReply remain as saturated UDINT mirrors of aggregate totals. The one-second window fields remain scan-published counts/duration totals. Request lastValidDuration now measures C submit-to-completion, excluding the PLC pickup delay. resetStats() resets totals, duration history, timeout and offline counts consistently, while retaining connection state and data freshness. Resetting during a transaction allows its eventual completion to be counted after reset.

out_errorCode is the request result; out_lastErrorCode is the client's last engine error. OS errno is out_socketError. Historical last errors may persist after recovery. Result 260 means worker/affinity setup failed. A non-timeout failure no longer increments timeoutCnt. See the API reference for all declarations.

TCP slave memory exchange

SlaveTCP and ModbusSlave provide the same TCP server behavior. Supply four arrays: aCoilsMemory, aDiscreteInputsMemory, aHoldingRegistersMemory, aInputRegistersMemory. Their lower/upper bounds are Modbus addresses within 0..65535; max 2000 per table. FC1/2/3/4 read; FC5/6/15/16 write coils/holding registers. Bind IP 0 means all interfaces; interfaceName must remain empty. Up to eight peers per server.

Remote changes are marked dirty in C. An old PLC push cannot overwrite dirty values before pull acknowledges them. Multiple remote writes before a scan expose the latest value, not an event history. After pull, a later application push may intentionally override those values. Other tasks must not race the array exchange. Table bounds/length changes require in_reset := TRUE to recreate the server; a changed mapping is rejected rather than silently discarding pending writes.

Online-change pointer ownership and physical hardware certification remain future work. This release needs a full download and on-target validation.