← Files RungstoneARCHIVED FILE

skills/rungstone-plc/references/openplc.md

6.86 KB · Nov 3, 2026 · 06:12 UTC

↓ Download file

See the change to this file →

# OpenPLC Runtime / Beremiz (target `openplc`)

Checked against OpenPLC_v3 commit b5d4135 (April 2026): its MatIEC compiler, compile script and
Modbus server (a translation compiled, ran, and its coils and holding registers read and wrote as
listed in section 5). Newer runtime versions may differ.

Read when translating to OpenPLC or Beremiz. Both compile IEC 61131-3 with MatIEC. OpenPLC Runtime
takes one `.st` file uploaded on its Programs page; OpenPLC Editor and Beremiz keep projects as
PLCopen XML (`plc.xml`).

## Contents
1. What it is suited to
2. File layout (the runtime is strict about it)
3. Language: what MatIEC takes
4. Located variables and I/O
5. Modbus: how HMI/SCADA sees the program
6. Retentive data
7. Tasks and timing
8. Deliverables and checks

---

## 1. What it is suited to

Machines, test rigs, training and smaller process jobs. It is not a safety platform: safety logic
stays excluded and must run on certified safety hardware. It has no motion control: axes go to a
drive or motion controller. Say both whenever the source has them.

## 2. File layout

One `.st` file, in this order:

```
(* GENERATED BY Rungstone - DRAFT FOR ENGINEER REVIEW - NOT APPROVED.
   Source: <file>. Not for use on a production system without review and testing by a qualified engineer. *)

TYPE ... END_TYPE                       (if any)
FUNCTION_BLOCK FB_x ... END_FUNCTION_BLOCK
PROGRAM prog_Main ... END_PROGRAM

CONFIGURATION Config0
  RESOURCE Res0 ON PLC
    TASK Main(INTERVAL := T#20ms, PRIORITY := 0);
    PROGRAM Inst0 WITH Main : prog_Main;
  END_RESOURCE
END_CONFIGURATION
```

The names `Config0` and `Res0` are required: OpenPLC's compile script looks for the C files
MatIEC generates from them. Every PROGRAM needs an instance in the resource or it never runs.

## 3. Language: what MatIEC takes

- Comments: `(* ... *)` only. Don't write `//`.
- Located (`AT %...`) and plain variables go in **separate** `VAR ... END_VAR` blocks; MatIEC
  rejects a block that mixes them.
- Don't name a POU, type or variable like a standard function or block (`TON`, `MID`, `LIMIT`,
  `INT_TO_REAL`...); `TON` as a program name crashes the compiler. Don't name a POU or type like
  their parameters (`IN`, `P`, `L`, `N`, `K`, `Q`, `G`, `MN`, `MX`, `CLK`): use `prog_...` /
  `FB_...` names.
- No `{...}` anywhere. MatIEC treats braces as pragmas and passes their contents into the
  generated C code.
- Edition 2 ST plus references: `REF_TO`, `REF()`, `^` and `NULL` are accepted (OpenPLC compiles
  with references enabled). No OOP (EXTENDS, IMPLEMENTS, METHOD, PROPERTY, INTERFACE, THIS^),
  no `POINTER TO` / `ADR()`, no `__NEW`, `__TRY`, `S=`/`R=`, bit access `x.3`, `UNION`, `LTIME`,
  `CONTINUE`.
- Standard blocks: TON, TOF, TP, R_TRIG, F_TRIG, CTU, CTD, CTUD, SR, RS, and the standard
  conversion functions (`INT_TO_REAL`, `REAL_TO_INT`...). REAL to integer conversions round.
  MatIEC's library also has PID, RAMP, HYSTERESIS, INTEGRAL, DERIVATIVE, RTC and SEMA.
- Literals: `T#500ms`, `16#FF`, `TRUE`/`FALSE`.
- `openplc_check.py` enforces all of the above; fix its errors rather than arguing with them.

## 4. Located variables and I/O

| Address | Type | Notes |
|---|---|---|
| `%IX a.b`, `%QX a.b` | BOOL | b = 0..7, a = 0..1023 |
| `%IB n`, `%QB n` | BYTE/SINT/USINT | not visible over Modbus |
| `%IW n`, `%QW n` | INT/UINT/WORD | analog in/out on most boards |
| `%ID n`, `%QD n` | DINT/UDINT/DWORD/REAL | not visible over Modbus |
| `%IL n`, `%QL n` | LINT/ULINT/LWORD/LREAL | |
| `%MW n`, `%MD n`, `%ML n` | 16/32/64-bit memory | n = 0..1023 |

There is **no `%MX` or `%MB`**. Internal BOOLs are plain (unlocated) variables; a BOOL the HMI
must see goes on a `%QX` coil.

Located variables are declared only in a PROGRAM (or the CONFIGURATION's VAR_GLOBAL), never inside
a FUNCTION_BLOCK: pass I/O into FBs as inputs/outputs.

Which `%IX`/`%IW` is which physical terminal depends on the hardware the runtime is set up for
(the board's pin map), and slave devices start at `%IX100.0` / `%IW100`. Always a high review
item: "Confirm the I/O map for the target board and that each input/output lands on the terminal
wired to the field device."

Analog ranges depend on the board (often 0..65535 or 0..1023). Ask; until confirmed, the
scaling is a high review item and the `mapping.csv` `raw:` note uses the assumed range so the
simulator can compare.

## 5. Modbus: how HMI/SCADA sees the program

OpenPLC serves data to HMIs and SCADA as a Modbus TCP server, and only through located
variables:

| Address | Modbus object (zero-based) |
|---|---|
| `%QX a.b` | coil a*8+b |
| `%IX a.b` | discrete input a*8+b |
| `%IW n` | input register n |
| `%QW n` | holding register n (0..1023) |
| `%MW n` | holding register 1024+n |
| `%MD n` | holding registers 2048+2n, 2049+2n (high word first) |
| `%ML n` | holding registers 4096+4n .. 4099+4n |

Every tag the HMI reads or writes needs one of these addresses. HMI setpoints go on `%MW`/`%MD`
(the HMI writes holding registers), and status bits the HMI reads go on `%QX`. Timer
accumulators the HMI showed (`.ACC`) become an explicit `%MD` DINT of elapsed ms. The check's
`located` list gives the register for each tag: put it in the answer as the HMI rebind table, and
note that many HMIs number registers from 1 (holding register 1024 = 41025 in 4xxxx notation).

## 6. Retentive data

OpenPLC does not keep `VAR RETAIN` values through a restart. Its Persistent Storage setting
(Settings page), when enabled, saves `%MW`, `%MD` and `%ML` to disk every N seconds (the polling
period set there), so a value changed just before power loss can still be lost. So:
values that must survive a power cycle (counters, totals, recipes, setpoints) go on `%MW`/`%MD`/
`%ML`, and a high review item asks the engineer to enable Persistent Storage and test a power
cycle. Rockwell sources keep every tag through power loss: list what that changes.

## 7. Tasks and timing

Keep one cyclic task unless the source genuinely needs more; test any multi-task program on the
runtime before relying on its timing. Pick the interval from the source's scan or task period
(Logix continuous task: ask the engineer for the actual scan time; 10-20 ms is common). OpenPLC
on a general-purpose OS is not hard real time: mention it when the source has fast tasks or
tight timing.

## 8. Deliverables and checks

1. `out/<project>_openplc.st` with the layout above.
2. `python3 <skill>/scripts/openplc_check.py out/<project>_openplc.st`: zero errors before you
   present anything; warnings become review items.
3. `python3 <skill>/scripts/st_to_plcopen.py out/<project>_openplc.st -o out/plc.xml` for
   OpenPLC Editor / Beremiz.
4. `mapping.csv` with `new_address` = the located address, and the Modbus rebind table.
5. Verify with `simulate.py compare` against the `.st` (or the `plc.xml`): results are
   simulated, not hardware-tested.

If the engineer has MatIEC installed, `openplc_check.py --iec2c <path> --iec-lib <lib dir>` also
compiles the file the way OpenPLC does.

SHA-256: be73da75d3bedf98a84857d99177d570f159132620a9ed34745b57bc716d5d54