← Files RungstoneARCHIVED FILE
skills/rungstone-plc/references/translation-pitfalls.md
15.9 KB · Nov 3, 2026 · 06:12 UTC
# Translation pitfalls Where cross-vendor translation silently changes machine behavior. Read before every Translate and Verify job. Each pitfall gives: what differs, how to detect it in the source, what to do in the target, and the default review-queue risk. Raise the risk if the signal drives motion, heat, pressure, chemical dosing, or anything near people. Risk scale used in the review queue: - **high** — can change what the machine physically does, when it does it, or how it fails (timing, scaling, fault/power-up behavior, interlocks, motion, PID). Blocks export until decided. - **medium** — semantics differ but the difference shows up in testing or only in edge cases (rounding, counter limits, HMI rebinds, naming of status bits). - **low** — cosmetic or documentation (naming, comments, structure). ## Contents 1. Scan order and execution model 2. Immediate I/O 3. PID algorithm forms 4. Motion 5. Analog raw ranges and scaling 6. Timers (ACC vs ET, time bases, reset behavior) 7. One-shots and edges 8. Counters 9. Data types, overflow, rounding 10. Retentive memory and power cycle 11. First-scan bits and startup 12. Fault behavior 13. Protocols (Modbus, DH+, EtherNet/IP, Profinet) 14. Program-control constructs (MCR, JMP, indirect addressing, subroutines) 15. Strings, arrays, bit order --- ## 1. Scan order and execution model — high - **Rockwell Logix:** I/O updates **asynchronously** at the module RPI; an input can change between two rungs of one scan. Tasks preempt by priority; continuous task runs in the gaps. - **SLC/PLC-5, S7, CODESYS/TwinCAT:** synchronous process image — inputs frozen for the whole scan, outputs written at the end (per task/OB in S7 with process image partitions). - Source logic that reads the same input in several rungs may rely on (or suffer from) asynchronous updates; translated to a process image, it becomes consistent. Usually safer, but note it. The reverse (S7 -> Logix) can break logic that assumes a frozen input: buffer inputs at the top of the routine (`MOV`/`COP` to a local image) and flag it. - **Rung order matters:** OTE on the same bit in two rungs -> last one wins (dependency map flags these). Seal-in and "last-write-wins" patterns must keep their relative order. - **Periodic tasks / cyclic OBs:** keep the rate and priority. PID and integrators must stay in a fixed-rate task (OB3x on S7-1500), not OB1. - Detection: `tasks` in parse output; rungs writing the same tag; inputs read in many rungs. ## 2. Immediate I/O — high - SLC `IIN`/`IOT`, PLC-5 `IIN`/`IOT`, Logix `IOT` (immediate output), S7 peripheral access `%IW64:P` / `%QW64:P`, CODESYS/TwinCAT have no general equivalent (task-level I/O). - Translate to the target's peripheral access when it exists; otherwise move the logic to a faster task and say the immediacy is lost. Always a review item. ## 3. PID algorithm forms — high (never auto-tune, never claim equivalence) | Source | Form | Notes | |---|---|---| | SLC/MicroLogix `PID` | ISA-ish dependent, Kc/Ti(min/rep)/Td(min); integer PV/CV 0–16383 (or scaled) | control block PD or N file (23 words) | | Logix `PID` (ladder) | independent **or** dependent (`.PE`), Kp/Ki (1/s or 1/min per `.MO`)/Kd | `.SP .PV .CV .KP .KI .KD .MAXO .MINO .DB .SWM .MO` | | Logix `PIDE` (FBD) | velocity form, PCMD/OCMD/... modes, autotune | lots of mode logic | | S7-1500 `PID_Compact` | proportional-integral-derivative with own tuning, Gain/Ti(s)/Td(s), pretuning/fine tuning | call in a cyclic interrupt OB | | S7-300 `FB41 CONT_C` | GAIN, TI, TD (TIME) | | | IEC/CODESYS/TwinCAT | library-specific (`PID` in Util, `FB_BasicPID`, `Tc2_ControllerToolbox`) | | Do: carry over setpoints, limits, deadband, manual/auto handshake, output clamping, and anti-windup intent; convert gain units with the formula shown; put the loop in a fixed-rate task; queue a **high** item: "loop requires re-tuning/verification at commissioning". The simulator reports PID as "not run". Never present translated gains as equivalent. ## 4. Motion — high (stub, do not translate behavior) Logix motion (`MSO MSF MAJ MAM MAS MAH MAG MCD MAPC MCCM...` with AXIS/MOTION_INSTRUCTION tags), Kinetix/CIP Motion, Siemens Technology Objects (`MC_Power`, `MC_Home`, `MC_MoveAbsolute`, `MC_MoveJog`...), PLCopen Motion (`MC_*` in CODESYS/TwinCAT `Tc2_MC2`). Concepts map roughly (PLCopen MC is the common ground) but axis configuration, units, homing, error handling and handshakes are drive- and configuration-specific. Generate the call skeleton with TODOs and a high review item per axis. Simulator: "not run". ## 5. Analog raw ranges and scaling — high, always ask Typical raw counts (always confirm the actual module catalog number and configured range): | Platform / module | 0–10 V | 4–20 mA | Notes | |---|---|---|---| | SLC 1746-NI4 | 0..16383 | 3277..16384 | NO4I out: 0..32764 = 0–21 mA | | MicroLogix embedded / 1762-IF4 | 0..4095 or 0..32767 (config) | 0..4095 / 6241..31206 (raw/prop) | data format setting | | ControlLogix 1756-IF8 etc. | **engineering units configured in the module** | | the tag may already be in EU — check module config | | CompactLogix 1769-IF4 | raw/proportional 0..32767 or EU | | | | S7-1500 / S7-1200 | 0..27648 | 0..27648 (live zero inside module) | overrange to 32511; 32767 = overflow / wire break | | Beckhoff EL30xx/EL31xx | 0..32767 | 0..32767 | 0x7FFF overrange | | Generic 12-bit | 0..4095 | 819..4095 | | Detection: `SCP`, `SCL` (SLC), `CPT` with constants, `NORM_X`/`SCALE_X`, divisions by 16383/4095/27648/32767, any analog input word (`I:3.0`, `Local:3:I.Ch0Data`, `%IW..`). Do: translate the *engineering* intent (0–100 %, 0–10 bar) using the target raw range, keep the source raw range in the mapping table notes, add underrange/wire-break handling where the source had it (or note it was missing), and queue a **high** item listing: source module/range (or "unknown"), target module/range (inferred), the formula before/after, and the request to confirm. Integer scaled values (e.g. 0..1000 = 0.1 %) — keep the same scaled integer if HMI/SCADA reads it, or flag the HMI rebind. ## 6. Timers — high when HMI/SCADA or other logic reads members, else medium | | SLC/PLC-5 | Logix | IEC / S7 / CODESYS | |---|---|---|---| | Preset/accum | `.PRE/.ACC` INT in **time-base** units (1.0 s, 0.01 s; MicroLogix 0.001) | `.PRE/.ACC` DINT ms | `PT`/`ET` TIME (ms) | | Status | `/EN /TT /DN` | `.EN .TT .DN` | `Q` only (`IN` is an input) | | TON reset | rung false -> ACC=0, DN=0 | same | `IN` false -> ET=0, Q=0 | | ACC at done | stops at PRE | stops at PRE | ET stops at PT | | Retentive | `RTO` + `RES` | `RTO` + `RES` | `TONR` (S7-1500, TwinCAT) with `R`; not in base IEC | | Not scanned | timer freezes (rung not evaluated) | freezes; JSR not called -> freezes | instance not called -> ET not updated; Q keeps last value | | Preset change while timing | takes effect immediately | same | PT read every call; usually same | Pitfalls: - **HMI/SCADA reads `.ACC` (elapsed time display) or `.TT`:** no direct IEC equivalent. ET is a TIME; HMI numeric objects usually need a DINT. Create an explicit `xxx_Elapsed_ms : DInt := TIME_TO_DINT(tmr.ET)` (and `xxx_Timing := tmr.IN AND NOT tmr.Q` for `.TT`) and queue an HMI rebind. `dependency_map.py --target` flags these. - **HMI writes `.PRE`:** create a DINT setpoint tag (retentive), convert with `DINT_TO_TIME`, clamp to sane limits; rebind HMI. - **SLC time base:** `TON T4:0 0.01 500 0` = 5.00 s; `1.0 100` = 100 s. Wrong base = 100x error. - **Timer used for its `.DN` in other rungs before the TON rung executes** (scan-order effect) — IEC instance call position must preserve it. - **Timer inside a conditional subroutine/IF:** IEC instance not called keeps ET — call unconditionally with `IN := condition`. - TOF, RTO and pulse semantics: Logix TOF `DN` is true while rung true and during delay; IEC TOF `Q` same. RTO -> `TONR` or `ET` accumulator logic; reset by `RES` -> `R` input. ## 7. One-shots and edges — high if they gate counts/latches - `ONS(storage)`: rung-out true for one scan on false->true; the storage bit must be unique. `OSR(storage, output)` / `OSF` (falling) write an output bit and keep the rung condition unchanged. - Logix sets ONS storage on **prescan** (so no pulse on the first scan after run); SLC similar. IEC `R_TRIG` memory initializes FALSE -> a TRUE input on the first scan **does** produce a pulse. Flag where a first-scan pulse would count, latch, or start something. - Shared storage bits (same storage used by two ONS) = existing bug: report, don't copy blindly. - `R_TRIG` inside IF/ELSE misses edges when not called: call every scan. - Siemens LAD `P`/`N` contacts need a unique edge memory bit (often `%M` — retentivity matters). ## 8. Counters — medium (high if count drives a recipe/batch) - SLC: `.ACC` INT -32768..32767; overflow sets `/OV` and **wraps**; `CTU` and `CTD` can share one counter address (up/down) -> IEC `CTUD`. - Logix: DINT; counts on rung false->true; continues past PRE (`DN` stays true); `OV/UN` on wrap. - IEC `CTU`: counts while `CV < PVmax`, stops at the type limit (no wrap), `Q := CV >= PV`; S7 `CTU` CV is Int — use `CTU_DINT` for Logix DINT counts. `.CU/.CD` memory bits do not exist. - `RES` -> `R` input; preset loads -> `LD` on CTD/CTUD. ## 9. Data types, overflow, rounding — medium to high - Width: SLC N = 16-bit; Logix DINT default; S7 Int is 16-bit — translate SLC N7 to Int only if HMI/comms need 16-bit, otherwise DInt and say so. - **Overflow:** SLC sets `S:5/0` minor fault and the overflow trap; if the trap bit is not cleared by end of scan the processor **major faults**. Logix sets `S:V` and continues (wraps). S7 integer arithmetic wraps silently (ENO false in LAD/FBD). Source logic that clears `S:5/0` is handling overflow on purpose — translate the intent. - **Integer division:** SLC `DIV` with an integer destination **rounds** (the unrounded quotient goes to `S:13/S:14`). Logix `DIV` with integer operands truncates, but rounds when a REAL operand feeds an integer destination. IEC/SCL `/` on integers **truncates**. SLC `7/2` -> 4 vs IEC 3. This is how `simulate.py` models it. Confirm against the controller's instruction reference for the firmware in use, and put every integer DIV in the review queue (medium). - **REAL -> INT:** Logix MOV rounds to nearest; IEC `REAL_TO_INT` rounds (implementation dependent at .5), `TRUNC` truncates. State which you used. - Signed/unsigned and BCD (`TOD`/`FRD`) conversions — flag. - Floating compare `EQU` on REAL — note fragility. ## 10. Retentive memory and power cycle — high | Platform | Default on power cycle | |---|---| | Logix | **All tag values retained**; prescan clears OTE outputs, resets TON/ONS states; OTL bits stay latched | | SLC/PLC-5 | Data table retained (battery/capacitor); OTE outputs non-retentive; OTL retained | | S7-1500 | **Non-retentive by default**; only members marked Retain (DB/FB/tag table %M range) keep values | | CODESYS/TwinCAT | `VAR` reinitialized; `VAR RETAIN`/`PERSISTENT` keep values (hardware/runtime dependent) | Every latched state, sequence step, counter, totalizer, setpoint, recipe value and mode selector in a Rockwell source needs an explicit retain decision on S7/IEC targets, and a "what should happen after power loss" question to the plant when not obvious (restart in the middle of a sequence is a classic hazard). Default proposal: retain setpoints/recipes/ totalizers/counters; **do not** retain run commands and sequence steps unless the source logic depended on it — and put both in the review queue (high). ## 11. First-scan bits and startup — medium - SLC `S:1/15` first pass, Logix `S:FS`, S7 `OB100` (or the `FirstScan` flag of OB1 start info on some CPUs), CODESYS/TwinCAT: none standard — use a `bFirstScan` pattern (`VAR bInit : BOOL := TRUE; END_VAR` cleared at end of first cycle) or TwinCAT `_TaskInfo[].FirstCycle`. - Logix power-up handler program, SLC fault/power-up routines -> OB100 + explicit init code. ## 12. Fault behavior — high - Array index out of range: Logix major fault (controller stops unless a fault routine clears it); SLC major fault; S7-1500 error OB/`GET_ERR_ID` handling (CPU reaction depends on model and firmware); CODESYS/TwinCAT runtime exception (stop) unless checked (`CheckBounds`). Generated code must bounds-check every computed index and say so. - Module/connection failure: Logix inputs hold last state or go to zero per module config and a connection fault is visible via `GSV`/module status; S7 OB82/OB86 + value status; outputs on fault/program mode go off/hold/substitute value per module config. State the source module config if known, propose explicit diagnostics handling, and queue an item. - Watchdog/cycle overrun: Logix task watchdog major fault; S7 OB80 then STOP if exceeded twice. - User fault routines (SLC `S:29`, Logix MajorFaultProgram) translate to error OBs / explicit handlers — never silently drop them. ## 13. Protocols — high (anything that crosses a network) - **Modbus:** 1-based register numbers in docs (`40001`) vs 0-based offsets on the wire; 32-bit values span two registers with vendor-specific word order (swap often needed); coils vs discrete inputs vs holding vs input registers. S7-1500: `MB_CLIENT`/`MB_SERVER`; Logix: AOIs or MSG over a gateway/module; CODESYS/TwinCAT: libraries. Map register-by-register in the mapping table, keep word order explicit. - **DH+ / DH-485 / RIO:** no equivalent on S7 or IEC targets; requires a gateway or network redesign. List every `MSG` with its path and data as a review item; never pretend it works. - **EtherNet/IP:** produced/consumed tags (RPI-based, Logix-to-Logix) and `MSG` (CIP data table read/write, PCCC for SLC). S7/IEC targets need an EtherNet/IP adapter/scanner (module, firmware feature, or gateway) — confirm availability; never assume. - **Profinet:** S7 native (I-Device for controller-to-controller). Logix targets need a gateway or a supported Profinet module — confirm. - **OPC UA:** S7-1500 and TwinCAT servers expose symbols; Logix needs an external server. HMI/SCADA connections change — run the dependency map with `--target`. - Comms status bits (`MSG.DN/.ER`, `TCON` STATUS, connection faults) gate logic — translate the intent (stale-data handling), not the bit names. ## 14. Program-control constructs — medium to high - **MCR zones:** outputs (OTE) inside a false MCR zone are forced off, but OTL/OTU and timers behave differently (TON resets). No IEC equivalent — expand to explicit conditions on every output in the zone. High if the MCR was part of a stop circuit (then it is safety-related: see safety-exclusion.md). - **JMP/LBL:** skipped rungs are not evaluated (timers freeze, OTEs hold). Translate to IF blocks and keep the hold semantics explicitly. - **TND / AFI / NOP:** temporary end and always-false — usually debugging leftovers; ask. - **Indirect/indexed addressing:** SLC `N7:[N7:0]`, `#N7:0` file instructions, Logix arrays with computed index, S7 pointers/ANY/VARIANT, IEC pointers — translate to arrays with bounds checks; the dependency map cannot see these accesses (say so). - **Subroutines with parameters:** SLC `JSR` input/return parameters, Logix `SBR/RET` params — translate to FB/FC interfaces. - **File/sequencer instructions** (`FFL/FFU/LFL/LFU/BSL/BSR/SQO/SQI/SQL/FAL/FSC/COP/FLL`): re-implement with arrays and loops; the CONTROL structure (`.POS .LEN .DN .EN .ER .UL`) has no equivalent. Medium unless they drive sequencing (then high). Simulator: "not run". ## 15. Strings, arrays, bit order - Logix STRING = 82 chars + `.LEN` (DINT); S7 `String[n]` max 254 with header bytes; IEC `STRING(n)`. ASCII/`MSG` string handling differs everywhere. - Arrays: Logix 0-based only; S7 `Array[lo..hi]`; IEC any bounds. Keep 0-based for traceability. - Bit-of-word access: Logix `DINT.5`, SLC `B3:0/5`, S7 `"Word".%X5` (slice access) on optimized data — and S7 is big-endian for `%M`/absolute overlays (see siemens-scl.md § 5). - Packed BOOL arrays used for HMI/comms (bit 0 = first alarm) — keep packing explicit.
SHA-256: 45726f697c195af5550eab2420949e73fb075e1d14731052e56cb5eb71b26b66