ACC PLC Simulator
Browser-based Ladder Logic & Structured Text Simulator · v1.132 · accautomation.ca
The ACC PLC Simulator is a fully functional, browser-based ladder logic simulator. It runs entirely in a single HTML file with no installation, no server, and no dependencies beyond a modern web browser.
Use it to learn ladder logic fundamentals, prototype control programs, demonstrate PLC concepts in a classroom, or connect to 3D industrial scenes for a visual simulation experience.
Key Features
- Full ladder logic editor — contacts, rising/falling edge contacts, coils, timers (incl. retentive), counters (incl. up/down), compare and limit test, MOV
- Math and data — ADD, SUB, MUL, DIV, MOD, INC, DEC, CPT expressions, SCL scaling, COP, FLL, PACK, UNPACK on 4000 data registers (D1–D4000) with INT, UINT, WORD, DINT and REAL types, plus indirect addressing with pointers (D[Dn])
- Sequencing and program flow — bit shift registers (BSL, BSR), a drum sequencer (DRUM) with a step-table editor, jump and label (JMP, LBL) and master control zones (MCR, END MCR)
- System bits & words — first scan, clock pulses, real-time clock and scan statistics (SB1–SB18, SW1–SW19)
- Real-time scan cycle — 100 ms scan with live power-flow highlighting
- Online editing — change a ladder or Structured Text program while it runs, review the marked changes, then ACCEPT to swap it in between two scans (see Online Editing)
- Analog I/O — AX1–AX8 inputs (slider-driven, 0–4095) and AY1–AY4 outputs
- Parallel branches — OR logic, up to any depth
- Save / Load — 10 named slots in browser localStorage plus JSON file export/import
- 3D Scene connection — BroadcastChannel link to interactive machine visualisations
- No installation — single HTML file, works offline after first load
Interface Tour
Layout of the main simulator window
Toolbar (top)
| Control | Description |
|---|---|
| ▶ RUN | Start the continuous scan cycle (100 ms interval). Also F5. |
| ■ STOP | Halt the scan. All outputs de-energise. Also F4. |
| ⏭ STEP | Execute exactly one scan. Only available in STOP mode with no scene connected. Fades out (disabled) when a 3D scene is linked. |
| ✎ EDIT | Shown in RUN only. Opens an online edit: change the program while it runs, then ACCEPT or CANCEL from the banner. Any edit in RUN opens one automatically. See Online Editing. |
| ✕ CLEAR | Delete all rungs and reset all memory (with confirmation). Also clears the undo history and address-comment map. On the next page load the default Control Panel program will open (auto-restore is suppressed once). Refused while the program is running — stop first — and while an online edit is pending. |
| ↩ UNDO | Reverse the last edit. Greyed out when no history is available. Also Ctrl+Z. In RUN with no edit pending, the previous program opens as an online edit to review and accept. |
| ↪ REDO | Re-apply an undone edit. Also Ctrl+Y or Ctrl+Shift+Z. |
| ☀ THEME | Toggle between dark (default) and light theme. Your preference is saved and restored automatically on the next visit. |
| 💾 SAVE | Save the current program to a named slot or file. While an online edit is pending, the running program is saved, not the edit. |
| 📂 LOAD | Restore a previously saved program. Refused while an online edit is pending. |
| CONNECT | Open the 3D Scene browser to link a machine visualisation. |
| LED + State | Green pulsing = RUN, red = STOP. |
| Scan counter | Shows last scan time (ms) and total scan count. |
Zoom Controls
A − / 100% / + / 1:1 control cluster sits in the bottom-right corner of the ladder panel.
| Control | Description |
|---|---|
| − | Zoom out 10% per click. Minimum 30%. |
| + | Zoom in 10% per click. Maximum 300%. |
| 1:1 | Reset zoom to 100%. |
| FIT | Scale the whole program so every rung fits in the window at once. |
| Ctrl + Scroll | Hold Ctrl (⌘ on Mac) and scroll the mouse wheel over the ladder panel to zoom smoothly. |
Left Panel — Instruction Palette
Click an instruction type to select it (it highlights in blue). You can then insert it into the ladder in two ways:
- Right-click any contact on the ladder and choose Insert Before / Insert After. For a compare, the dialog also asks for the Data Type used to read D registers.
- Drag and drop directly from the palette onto a rung or the Add New Rung strip (see below).
The address field pre-fills with the next available address for the selected type. A ◀ collapse tab sits on the right edge of the palette. Click it to collapse the panel to a thin strip, giving the ladder more horizontal space. Click the ▶ tab again to restore it.
Drag and Drop from the Palette
Every instruction in the palette can be dragged directly onto the ladder. This is the fastest way to build programs.
- Drag onto an existing rung — a blue dashed border highlights the target rung as you hover. Drop to insert the instruction:
| Instruction type | What happens on drop |
|---|---|
| XIC, XIO, P, N | New contact inserted before the output element. Edit modal opens immediately to confirm the address. |
| GRT, LES, GEQ, LEQ, EQU, NEQ, LIM | New compare block inserted before the output. Edit modal opens to set the address and comparand (or Low/High limits for LIM). |
| OTE, OTL, OTU | On a rung that ends in a coil, the coil type changes (address kept). On a rung that ends in a block, the block is replaced by a coil at the next free Y. Edit modal opens. |
| TON, TOF, RTO, CTU, CTD, CTUD, RES, MOV, and the Math and Move / Data blocks | The output element is replaced with the block. Edit modal opens to set the address and operands (preset for timers and counters, the count-down input for CTUD). |
| BSL, BSR, DRUM, JMP, MCR | The output element is replaced with the block. Edit modal opens (MCR shows its fixed address, MCR). |
| LBL | Inserted at the start of the rung. A rung takes one label; a second is refused. |
| END MCR | Inserts a new END MCR rung below the target rung — END MCR always sits on a rung of its own. |
When a drop replaces the rung's output, the status bar says so — press UNDO to get it back. In RUN, a palette drop or a new rung opens an online edit instead of changing the running program.
- Drag onto an Insert Strip (between rungs) or Add New Rung strip — a new rung is created at that position with a sensible default pairing (e.g. XIC → OTE, GRT → OTE, XIC → TON, XIC → DRUM, LBL · XIC → OTE; END MCR on its own). The edit modal opens on the primary element.
Centre — Ladder Canvas
The main programming area. Rungs are displayed as horizontal rails. In STOP mode, contact bars show bit state (green = energised). In RUN mode, powered wires and energised coils glow green.
- Double-click any element to open the Edit dialog.
- Double-click the rung header bar (the comment area) to edit the rung comment directly. A faint placeholder text appears on rungs with no comment.
- Right-click any contact for the context menu (insert, delete, add parallel branch).
- Drag the handle on the rung header to reorder rungs.
- Hover between rungs to reveal the Insert Rung strip.
All of these work in RUN too: the first change opens an online edit. A full-width amber ONLINE EDIT bar with ACCEPT and CANCEL appears between the toolbar and the ladder, and changed rungs are marked EDIT, NEW or DELETED ON ACCEPT.
Right Panel — I/O Tabs
Eight tabs: I/O (used addresses summary), X, Y, C, T, CT, AN (analog), SYS (system bits and words). See the I/O Panel Tabs section for details.
A ▶ collapse tab sits on the left edge of the I/O panel. Click it to collapse the panel and give the ladder more space. Click the ◀ tab again to restore it.
Light / Dark Theme
Click the ☀ THEME button in the toolbar to switch between the default dark theme and a softer light theme. The button label changes to 🌙 THEME when light mode is active.
In the light theme, energised contacts and I/O indicators use a bright vivid green with a glow halo to ensure ON states remain clearly distinguishable from OFF states.
Status Bar (bottom)
Displays the current mode (RUN/STOP), contextual hints, and the system clock. After most actions a confirmation message appears here — including why an online-edit ACCEPT was refused and which outputs were left ON.
Quick Start
Get a rung running in under two minutes
- Open
acc-plc-simulator.htmlin any modern browser. - The default Start/Stop/Jog program loads automatically.
- Press F5 (or click RUN) — the LED turns green and the scan begins.
- On the X tab (right panel), click X1 to toggle the Start input ON — Y1 energises.
- Click X1 again to release it — Y1 stays on (seal-in circuit).
- Click X2 to pulse the Stop — Y1 de-energises.
- Press F4 to stop the scan.
Adding Your First Rung
- In STOP mode, hover near the bottom of the ladder until the ⊕ Add New Rung strip appears. Click it.
- The Rung Template picker opens — choose XIC → OTE for a simple contact/output rung.
- Double-click the new contact (address shown in amber) to open Edit. Change the address to
X4and click Apply. - Double-click the output coil and change its address to
Y4. - Press F5 to run, then toggle X4 on the X tab to see Y4 energise.
Rungs & Editing
Creating, modifying, and organising rungs
Inserting a Rung
Hover between any two existing rungs to reveal the ⊕ Insert rung after XXXX strip, or hover below the last rung for ⊕ Add New Rung. Clicking opens the Rung Template Picker with pre-built patterns.
Deleting a Rung
Click the ✕ circle button on the top-right of any rung header. A confirmation dialog prevents accidental deletion.
Reordering Rungs
Drag the ⠿ handle on the left of the rung header to a new position. An amber drop-indicator line shows the insertion point. In RUN the move goes into an online edit; a moved rung is marked EDIT.
Editing Elements
Double-click any element to open the Edit Instruction dialog. You can change the instruction type, address, preset value, and the Address Comment. You can also edit the rung comment from here.
The dialog checks the address before applying it. An address that doesn't suit the instruction — a coil on a read-only system bit, a contact on a word such as SW5, a timer on X1 — is refused with a reason in the status bar, and the dialog stays open so you can correct it. If you change the instruction type, the address is kept when it still fits and replaced with the next free address when it doesn't. Turning a contact on an input (e.g. X1) into a coil suggests the next free output (Y), because driving an input is almost never intended.
Presets must be whole numbers from 0 to 99,999,999, and so must comparands and LIM limits in a compare that reads no D register. A compare that reads a D register takes constants that fit its Data Type — decimals and negatives with REAL, for example.
Address Comments
The comment field in the Edit dialog is labelled Address Comment because it is tied to the address, not to the individual element. If you set X1 to "Start Button", every contact, output, compare instruction, or block that references X1 anywhere in the program — even on a different rung, even with a different instruction type — will display the same comment automatically.
This matches industry-standard behaviour in major PLC IDEs and means you only have to document each address once.
- Editing the comment for any instance of
X1updates all instances ofX1on apply. - Changing an element's address (e.g. from
X1toX2) updates the displayed comment to whatever is already stored for the new address. - Clearing the comment field and applying removes the comment for that address globally.
- Comments are saved with the program (both in localStorage save slots and in exported JSON files).
Editing the Rung Comment
Rung comments are different from address comments — they describe a whole rung's purpose ("Start / Stop seal-in", "Conveyor speed ramp") and apply only to that rung. Double-click anywhere on the rung header bar (the row showing the rung number and comment text) to open the Edit Rung Comment dialog. Type the new comment and click Apply or press Enter. Leave the field blank and apply to clear the comment.
Rungs with no comment show a faint "double-click to add comment…" placeholder to make the feature discoverable.
Right-Click Context Menu
| Action | Description |
|---|---|
| Edit Element | Opens the Edit dialog for the clicked contact. |
| Insert Contact Before | Inserts a new contact immediately before the clicked one. |
| Insert Contact After | Inserts a new contact immediately after the clicked one. |
| Add Parallel Branch | Wraps the contact in a parallel group or adds a branch. |
| Edit Parallel Group | Opens the Parallel Branch Editor for the enclosing group. |
| Remove This Branch | Removes the branch containing the clicked contact. |
| Delete Contact | Removes the contact from the rung (minimum one input required). |
Rung Structure Rules
- Every rung must have at least one input contact and exactly one output element (coil or block) on the right rail.
- Input contacts are evaluated left-to-right; the result drives the output.
- Timer and counter blocks occupy the output position — wire continuation exits from their right side carrying the DN bit.
- BSL, BSR, DRUM, JMP, MCR and END MCR only fit the last slot of a rung, never inside a parallel branch. END MCR sits on a rung of its own, with no contacts.
- LBL sits at the top level of the rung (not in a branch) and never last — normally first.
- The Edit dialog's type list greys out the instructions that do not fit the element's position, so a contact cannot be turned into a DRUM, nor an output into a LBL.
- After every edit the editor also checks jumps, labels and MCR zones across the whole program and adds the first problem to the status bar (e.g.
⚠ rung 0004: JMP L1 has no matching LBL L1.). An import reports the number of problems and the first one. See Jump & Label and MCR Zones.
Contacts
Input-side bit-reading instructions
| Symbol | Name | Address | Description |
|---|---|---|---|
| -| |- | XIC | X, Y, C, SB, T/DN, CT/DN | Examine If Closed (Normally Open). Passes power when the addressed bit is 1 (ON). The most common contact type. |
| -|/|- | XIO | X, Y, C, SB, T/DN, CT/DN | Examine If Open (Normally Closed). Passes power when the addressed bit is 0 (OFF). Use for Stop buttons, interlocks, and fault conditions. |
| -|P|- | P | X, Y, C, SB, T/DN, CT/DN | Rising Edge (positive transition). Passes power for one scan when the addressed bit goes from 0 to 1. Use it to act once per button press. |
| -|N|- | N | X, Y, C, SB, T/DN, CT/DN | Falling Edge (negative transition). Passes power for one scan when the addressed bit goes from 1 to 0 — for example, when a button is released. |
Address Examples
X1— Digital input 1Y3— Digital output 3 (used as a contact to read back its state)C100— Internal control relay 100T1/DN— Timer 1 Done bitT1/EN— Timer 1 Enable bitT1/TT— Timer 1 Timing bitCT1/DN— Counter 1 Done bit- Bare
T1resolves toT1/DNautomatically SB3— System bit: first scan (see System Bits & Words)
Contacts read bits. To test a word such as AX1, SW5 or T1/ACC, use a compare (GRT, LIM, …).
Edge Contacts (P and N)
An edge contact watches its bit, not the rung. It remembers the bit's state from the previous scan and passes power only on the scan where the bit changed. That memory updates every scan, even when contacts to its left are open.
- Hold X1 ON and a
-|P|- X1contact is true for exactly one scan, then false until X1 is released and pressed again. - The edge memory starts at OFF each time the PLC enters RUN, so a bit that is already ON fires its rising edge once on the first scan — the same rule as an IEC
R_TRIGin Structured Text. - In STOP mode edge contacts always show OFF. Use STEP to watch an edge fire: toggle the input, then STEP once.
In STOP Mode
Contact bars show the current bit state regardless of power flow. A green bar means the bit is 1. Wires remain dark — power flow is only shown in RUN mode.
Y1 as an XIC contact in a seal-in branch — it reads the physical output state, creating a latching circuit without a dedicated C relay.Outputs
Output coil instructions — right-rail elements
| Symbol | Name | Address | Description |
|---|---|---|---|
| -( )- | OTE | Y, C | Output Energize. Sets the bit to 1 when powered; resets it to 0 every scan when not powered. Standard output — mirrors rung power state each scan. |
| -(L)- | OTL | Y, C | Output Latch (Set). Sets the bit to 1 when powered. The bit stays 1 even if the rung loses power. Use with OTU on a separate rung to unlatch. |
| -(U)- | OTU | Y, C, SB9–SB11 | Output Unlatch (Reset). Resets the bit to 0 when powered. Used as the companion to OTL, and the only way a ladder program can clear the error flags SB9–SB11 (in Structured Text, assign FALSE). |
Timers
TON, TOF and RTO — time-based control
| Symbol | Name | Description |
|---|---|---|
| [TON] | TON | Timer On-Delay. When the rung becomes TRUE the accumulator starts counting. When Acc ≥ Preset the DN bit sets. When rung goes FALSE the timer resets (Acc = 0, DN = 0). |
| [TOF] | TOF | Timer Off-Delay. When the rung is TRUE the DN bit is immediately set. When the rung goes FALSE the accumulator starts counting. DN clears when Acc ≥ Preset. |
| [RTO] | RTO | Retentive Timer On. Times while the rung is TRUE, like TON, but keeps its accumulated time when the rung goes FALSE. Time adds up across separate ON periods until Acc ≥ Preset. Only a RES on the same address clears it. |
Timer Parameters
- Address —
T1throughT256 - Preset — time in milliseconds. 1000 = 1 second, 5000 = 5 seconds.
- Accumulator — counts up from 0 each scan (approximately 100 ms steps).
Timer Bits
| Bit | Address | Description |
|---|---|---|
| DN | T1/DN or T1 | Done — set when Acc ≥ Preset |
| EN | T1/EN | Enable — set while rung is TRUE (TON) or was TRUE (TOF) |
| TT | T1/TT | Timing — set while timer is actively counting (EN && !DN) |
T1/TT as an XIC contact to create a flashing output — it toggles roughly every Preset milliseconds.Reset
Add a RES block (address T1) on a separate rung. When that rung is powered, Acc resets to 0 and DN clears. An RTO needs this — it never resets itself.
XIC Y1 → RTO T5 accumulates whenever the motor runs, through any number of stops, and T5/DN flags the service interval. A maintenance reset button drives RES T5.Reading the Accumulator
Compares can read a timer's elapsed time as T1/ACC (in milliseconds). For example, GRT T1/ACC 1500 is true once timer 1 has run for more than 1.5 s — useful for staged actions from a single timer.
Counters
CTU, CTD, CTUD and RES — event counting
| Symbol | Name | Description |
|---|---|---|
| [CTU] | CTU | Count Up. Increments the accumulator on each rising edge (OFF→ON) of the rung. When Acc ≥ Preset the DN bit sets. |
| [CTD] | CTD | Count Down. Decrements the accumulator on each rising edge. DN sets when Acc ≤ 0. |
| [CTUD] | CTUD | Count Up / Down. Counts up on each rising edge of the rung and down on each rising edge of a second bit — the Count-Down Input set in the Edit dialog (e.g. X4). DN sets when Acc ≥ Preset. Never goes below 0. If both edges arrive on the same scan they cancel out. |
| -(R)- | RES | Reset. When powered, clears the Acc to 0 and resets DN. Address must match the timer or counter to reset (e.g. CT1 or T1). |
Counter Parameters
- Address —
CT1throughCT256 - Preset — integer count target (no units)
- DN bit —
CT1/DNor bareCT1
X3 on the rung counts up, exit sensor X4 as the Count-Down Input counts down, and CT1/DN with Preset 10 means "buffer full". Read the live count in a compare as CT1/ACC.Analog I/O
AX inputs (0–4095) and AY outputs (0–4095)
The simulator supports 12-bit analog channels using 12-bit AX/AY registers. All values are integers in the range 0–4095, representing 0–10 V (or 4–20 mA in real hardware).
Analog Inputs — AX
| Register | How to set | Source |
|---|---|---|
| AX1–AX8 | Drag slider on the AN tab, or connect a 3D scene | Manual / Scene |
Analog Outputs — AY
| Register | How to set | Source |
|---|---|---|
| AY1–AY4 | Driven by MOV and math blocks during RUN; manually typed in STOP mode | Program / Manual |
Compare Instructions
| Symbol | Name | Condition |
|---|---|---|
| [GRT] | GRT | Greater Than — passes power if A > Comparand |
| [LES] | LES | Less Than — passes if A < Comparand |
| [GEQ] | GEQ | Greater or Equal — passes if A ≥ Comparand |
| [LEQ] | LEQ | Less or Equal — passes if A ≤ Comparand |
| [EQU] | EQU | Equal — passes if A = Comparand |
| [NEQ] | NEQ | Not Equal — passes if A ≠ Comparand |
| [LIM] | LIM | Limit Test — passes if Low ≤ A ≤ High (both limits included). If Low is greater than High the band wraps: it passes when A ≥ Low or A ≤ High, i.e. outside the gap between them. |
Compare blocks sit in the input area of a rung and pass or block power flow on every scan based on the live value of A versus B (or the limits). B and the limits can be a constant or another word, such as D20 or AX2.
What A can be
AX1–AX8— analog inputsAY1–AY4— analog outputsT1/ACC— a timer's elapsed time in ms;CT1/ACC— a counter's current countSW1–SW19— system words, e.g.SW5= hour of dayD1–D4000— data registers, read as the compare's Data Type (INT unless you choose another — see Data Registers)
LIM AX1 1000 3000 → OTE Y3 lights Y3 only while the level is inside its normal band — a job that otherwise takes two compares in series.MOV — Move a Value
The MOV block occupies the output position. When the rung is powered it writes the Source value to the Destination, every scan. The source can be a constant (e.g. 2048) or any readable word: D1, AX1, AY2, SW6, T1/ACC or CT1/ACC. The destination is AY1–AY4 or a data register D1–D4000.
- A value written to AY is rounded and limited to 0–4095, so a timer at 5000 ms moved to AY1 gives 4095.
- When a side is a D register, MOV reads it as the Source type and writes it as the Destination type — this is how you convert, e.g. REAL D10 → INT D20 (rounded half away from zero: 2.5 → 3, −2.5 → −3).
- A MOV of the constant
0intoSW17clears the last error code; no other value or destination is accepted for system words.
Math & Data Instructions
Calculate, scale, copy and pack values in data registers
These blocks sit in the output position and work on words: data registers D1–D4000, analog AX/AY, system words SW, and timer/counter accumulators. Drag them from the Math and Move / Data palette sections, then double-click to set their operands. Hover over a block or compare on the ladder to see the whole instruction.
XIC X1 → INC D1 adds 1 ten times a second while X1 is held. To act once per press, use a rising-edge contact: P X1 → INC D1.Data Type
Every math block and compare has a Data Type — INT (default), UINT, WORD, DINT or REAL. It says how the block reads and writes D registers; AX, AY, SW and accumulators are always read as their own value. Integer types round results half away from zero; a result that does not fit wraps and sets the overflow flag. See Data Registers for the ranges and System Bits & Words for fault codes.
Math
| Symbol | Name | What it does |
|---|---|---|
| [ADD] | ADD | Destination := A + B |
| [SUB] | SUB | Destination := A − B |
| [MUL] | MUL | Destination := A × B |
| [DIV] | DIV | Destination := A ÷ B. Integer types truncate toward zero (−7 ÷ 2 = −3). B = 0 writes nothing and sets SB10. |
| [MOD] | MOD | Destination := remainder of A ÷ B; the sign follows A (−7 MOD 2 = −1). |
| [INC] | INC | Destination := Destination + 1 |
| [DEC] | DEC | Destination := Destination − 1 |
| [CPT] | CPT | Destination := an expression, e.g. (AX1 - 410) * 100 / 3685 |
| [SCL] | SCL | Linear scaling: maps Input from In min…In max to Out min…Out max. Not clamped — an input outside its range gives an output outside its range. |
A and B are constants or words. The destination is a D register, or AY1–AY4 (rounded and limited to 0–4095).
CPT expressions
- Numbers, pointers
D[Dn]and the wordsD,AX,AY,SW(moveT1/ACCorCT1/ACCinto a D register first). - Operators
+ - * / MOD **, unary minus and parentheses. - Functions
ABS,SQRT,MIN(a,b,…),MAX(a,b,…),LIMIT(min,in,max),TRUNC,ROUND,EXPT(a,b). - With a REAL Data Type the expression is calculated in floating point. With an integer type every value and every division is whole (
7 / 2 * 2= 6), and decimal constants are refused — use REAL, or scale by whole numbers (* 3 / 2). Integer intermediates are exact up to 253.
Move / Data
| Symbol | Name | What it does |
|---|---|---|
| [MOV] | MOV | Copies one value, converting between Source type and Destination type. See Analog I/O. |
| [COP] | COP | Copies Length words, unchanged, from a source start (D, AX, AY or SW) to a D start. Overlapping ranges copy correctly. |
| [FLL] | FLL | Writes one value into Length elements of the Data Type, starting at a D register (a REAL fill uses 2 × Length registers). |
| [PACK] | PACK | Packs 1–16 bits, starting at an X, Y or C address, into one D word. The first bit becomes bit 0. |
| [UNPK] | UNPACK | Writes bits 0–15 of a word (D, AX, AY or SW) to 1–16 Y or C bits, bit 0 first. |
Examples
- Level in percent:
XIC SB1 → SCL AX1 0…4095 → 0…100 → D20, thenGRT D20 80 → OTE Y3. - Count parts:
P X1 → INC D1; reset withXIC X2 → MOV 0 → D1. - Engineering units:
CPT D10 (REAL) := (AX1 - 410) * 100 / 3685turns a 4–20 mA signal into 0–100.0 %. - Fault alarm:
XIC SB12 → OTE Y8; acknowledge withXIC X8 → OTU SB9,OTU SB10,OTU SB11andMOV 0 → SW17.
Pointers (D[Dn])
Indirect addressing — pick the register at run time
A pointer is written D[D5]: the register inside the brackets (D5, the pointer register) holds a number, and the instruction uses the register with that number. If D5 holds 102, MOV D[D5] → D20 copies D102 into D20. Change D5 to 103 and the same rung copies D103 instead. This is how one rung can step through a table of values — recipes, setpoints, a log.
Where pointers can be used
| Instruction | Fields that accept D[Dn] |
|---|---|
| MOV | Source and destination |
| ADD SUB MUL DIV MOD | A, B and destination |
| INC DEC | Destination |
| SCL | Input and destination |
| CPT | Destination, and terms inside the expression: D[D5] * 2 + D10 |
| COP | Source start and destination start |
| FLL | Fill value and destination start |
| PACK / UNPACK | The D word (PACK destination, UNPACK source) — not the bits |
| GRT LES GEQ LEQ EQU NEQ LIM | A, B, and LIM's Low and High |
Contacts, coils, timers, counters and RES do not take pointers. Only a D register can be the pointer: D[5], D[AX1] and D[D[D5]] are refused. Case and spaces don't matter — d[ d5 ] is stored as D[D5].
How it is resolved
- The pointer register is read as INT — a plain register number, whatever the instruction's Data Type.
- It is resolved every time the instruction runs, so a new pointer value takes effect the next time the instruction runs — in the same scan when the pointer is calculated on an earlier rung, or earlier on the same rung.
- The target must fit inside D1–D4000 for the access: a DINT or REAL needs two registers, so its pointer can go up to 3999; for COP and FLL the whole run must fit.
- A pointer outside that range is a range fault: SB11 and SB12 turn on, SW17 = 4, SW18 = the rung, and the instruction writes nothing. A compare reads the bad operand as 0.
- While the PLC runs, each block shows where its pointers point:
D[D5]→D102, orD[D5]→?when the pointer is out of range. Hover over a block for the whole instruction.
Example — recipe select
- Setpoints for three recipes sit in D100, D101 and D102 (loaded once with
XIC SB3 → MOV). - The recipe number 0–2 is in D1 (set by pushbuttons with
P X1 → MOV 0 → D1, and so on). XIC SB1 → ADD 100 + D1 → D2builds the pointer.XIC SB1 → MOV D[D2] → D10copies the selected setpoint into the working register D10.GEQ AX1 D10 → OTE Y1then compares the level with whichever setpoint is selected.
Structured Text writes the same idea as D[100 + recipe]; ST's D[...] takes any expression (see ST Overview).
Bit Shift Registers (BSL / BSR)
Move a row of bits one position per pulse — part tracking on a conveyor
A shift register is a row of ordinary bits — C or Y — that moves along by one position on each rising edge of its rung, like a counter counts. A data bit is copied in at one end, every other bit moves one place, and the bit that falls off the other end can be kept in an unload bit. Use it to follow parts down a conveyor: shift once per part pitch, put "this part is bad" in at the entry, and the bit arrives at the reject station exactly when the part does. BSL and BSR are in the Shift / Sequence palette section.
| Symbol | Name | What it does on each rising edge |
|---|---|---|
| [BSL] | BSL | Bit Shift Left. Every bit moves to the next higher address. The data bit enters at the first bit; the last bit (first + length − 1) goes to the unload bit. |
| [BSR] | BSR | Bit Shift Right. Every bit moves to the next lower address. The data bit enters at the last bit; the first bit goes to the unload bit. |
Fields
| Dialog field | Accepts |
|---|---|
| First bit of the register | C1–C256 or Y1–Y16. Not X — inputs belong to the field. |
| Number of bits in the register (1–256) | The whole register must stay in its table: 8 bits from C250 run past C256 and are refused. A register cannot run from C into Y. |
| Data bit | Any readable bit: X1, C5, T1/DN, CT2/DN, SB1 … |
| Unload bit (optional) | A C or Y bit that receives the bit shifted out. Leave empty to discard it. |
BSL and BSR sit in the output position (the last slot of the rung) and pass the rung's power through. The block shows the register range and length, the data bit (IN) with a dot for its state, the unload bit (OUT), an arrow for the direction, and the register as a row of dots — the first 16 bits, then +N for the rest. Hover over the block for the whole instruction.
Example — reject tracking
P X1 → BSL C100 ×8, data bit X2, unload C200— X1 is a sensor that pulses once per bottle pitch, X2 is "bottle is faulty" from an inspection sensor at the entry.- Each pulse moves the pattern up one place: a faulty bottle seen at C100 reaches C105 five pulses later.
XIC C105 → OTE Y3fires the reject pusher when the faulty bottle is in front of it.
The P → BSL rung template builds the first rung: a rising-edge contact driving a BSL on 8 free C bits, with the next free X as the pulse input and the following free X as the data bit.
Behaviour
- One shift per rising edge. The rung must go false and true again for the next shift. Unlike a counter, the block's edge memory is reset on every STOP→RUN, so a rung that is already true shifts once on the first scan.
- The data bit is read at the moment of the shift.
- Register bits are ordinary bits. Read them with contacts, set or clear them with OTL/OTU, or clear the register with OTU rungs (or UNPACK from a D register that holds 0, up to 16 bits at a time) — nothing special is needed.
- Inside an MCR zone that is off, or in a rung skipped by a JMP, the register sees no edge and does not shift.
- All register, data and unload bits appear in the I/O tab's used-address list.
Common mistakes
- A chattering pulse source. The block shifts on every rising edge of its rung, so
XIC X1works without a P contact — but a sensor that chatters shifts once per bounce. Debounce the pulse source, or drive the rung from a timer done bit. - Expecting the register to survive STOP. STOP clears every Y and C bit, so the tracking pattern is lost. Loading or importing a program clears it too.
- Off-by-one stations. With BSL the data bit lands in the first bit on the first shift, so after n shifts it is at first bit + n − 1. Count stations from the bit the data enters.
- Overlapping registers. Two shift registers, or a register and other logic, writing the same C bits corrupt each other. Give each register its own block of bits and an address comment.
Drum Sequencer (DRUM)
Step through output patterns on time, on events, or both
A drum sequencer is a table of steps. Each step says which outputs are ON, how long to stay in the step, and optionally which bit must be ON before moving on. The current step number is kept in a D register. Traffic lights, wash cycles and fixed machine sequences are the classic uses — one block replaces a pile of timers and interlocks. DRUM is in the Shift / Sequence palette section.
Fields
| Dialog field | Accepts |
|---|---|
| Step register | A D register, D1–D4000. Holds the current step 1…N, read and written as INT. |
| Outputs | First output bit (a Y or C bit) and the number of outputs, 1–16. The run must stay inside its table. |
| Reset bit (optional) | Any readable bit, e.g. X8. While it is ON the drum goes to step 1 and stays there. |
| Done bit (optional) | A Y or C bit, ON for one scan each time the drum wraps from the last step back to step 1. |
| Edit steps… | Opens the Drum Steps table. The summary beside the button shows the number of steps and the total timed length. |
The step table
Click Edit steps… in the Edit dialog. The Drum Steps dialog has one row per step and these columns:
- One checkbox per output, headed with its address (
Y1,Y2…). Tick the outputs that are ON in that step. - Time (ms) — how long the step lasts, 0–99,999,999. 0 means no time condition.
- Event — a bit that must be ON before the step can end, e.g.
X3orT4/DN. Empty means no event condition.
+ Add step appends a step (up to 16); − Remove last deletes the last one (at least 1 remains). Save steps checks the table and returns to the Edit dialog — the steps are stored when you press Apply there. Cancel or Esc discards the table edits; Enter saves steps. Set the number of outputs before opening the table: the table shows that many columns, and ticks beyond it are dropped when you save.
In a saved file each step is {p, t, ev}: p is the output pattern as a number 0–65535, where bit 0 is the first output.
How it runs
- Rung true: the current step's pattern is written to the outputs every scan and the step timer runs (real elapsed time, up to 500 ms per scan, like a timer).
- The drum moves to the next step when both conditions are met: the step's time has elapsed (or is 0) and its event bit is ON (or has none). A step with time 0 and no event lasts one scan. The new step's pattern is written in the same scan, and the step time starts again from 0.
- After the last step it returns to step 1, and the done bit is ON for that one scan.
- Rung false: the drum pauses. The step and the time already spent in it are held, and the outputs are not written — they keep whatever they last had, so drive them OFF elsewhere (or use C bits and gate the real outputs) if they must drop.
- Reset bit ON is checked every scan, rung true or false: step := 1, step time := 0, and the drum does not advance while reset stays ON.
- A step register value outside 1…N — written by a MOV, typed on the D tab, or left over after steps were removed — is treated as step 1 and corrected. It is not a fault.
- The step register is a normal D register: it keeps its value through STOP/RUN and is saved with the program, so the drum resumes at the saved step. The time spent in the step is not saved and restarts from 0 on every STOP→RUN, like a TON.
The block shows the step register, STEP 2 and 2/4 (current step of total), the time in the step against its time (or no time), the event bit with a dot for its state, and the step's output pattern as dots.
Example — traffic light
XIC X1 → DRUM D10, outputsY1×3 (green, amber, red — the Traffic Light scene's north–south lamps), resetX8.- Step 1: Y3 (red), 5000 ms. Step 2: Y1 (green), 4000 ms. Step 3: Y2 (amber), 1500 ms.
- Give step 2 the event
X2(a car waiting on the side road) and the light stays green until at least 4 s have passed and a car is waiting.
The DRUM → outputs rung template inserts XIC → DRUM on the next free D register with four outputs and four 2-second steps, one output per step.
Common mistakes
- One held event bit on consecutive steps. The event is a level, checked every scan. If two steps in a row use the same event bit and it stays ON, the second step's event condition is already met, so it ends as soon as its own time has passed (on the next scan if its time is 0). Use a rising-edge contact into a C bit, or a different bit per step.
- Expecting outputs to drop when the rung goes false. They hold. Put the outputs on C bits and drive the real Y outputs with rungs that include the run condition.
- Sharing the step register. Another instruction writing the same D register moves the drum (the new step's time starts from 0). Give it its own register and an address comment; read it with a compare (
EQU D10 3) to act on a step. - Two drums on the same outputs. Both write every scan they run; the lower rung wins.
Jump and Label (JMP / LBL)
Skip a block of rungs while a condition is true
A JMP with power makes the scan skip forward to the rung that holds the matching LBL. The rungs in between are not executed at all on that scan. Use it to leave out a section of logic — a manual-mode section while in auto, or a rarely needed calculation. JMP and LBL are in the Program Control palette section.
| Symbol | Name | Address | Description |
|---|---|---|---|
| [JMP] | JMP | L1–L256 | Jump to Label. Output position. When its rung is true, the scan continues at the rung that holds LBL with the same number. |
| [LBL] | LBL | L1–L256 | Label. Input side, normally the first element of a rung. Always passes power — it only marks where a jump lands. |
Rules
- Forward only. The LBL must be on a later rung than the JMP.
- Each label number is used once. Several JMPs may jump to the same label.
- A LBL sits at the top level of a rung (not in a parallel branch), and not as the last element — the rung needs an output after it. One label per rung.
- A jump may not go into or out of an MCR zone; keep the JMP and its LBL in the same zone (or both outside any zone).
- The rung holding the LBL does run; only the rungs between the JMP rung and the LBL rung are skipped.
What a skipped rung does
- Nothing is evaluated. OTE coils keep their last state — they are not turned off.
- Timers do not run and do not reset; counters do not count; word blocks, drums and shift registers do not execute.
- Edge contacts do not update their memory, so a bit that changed while skipped can fire its edge on the first scan the rung runs again.
- A JMP inside the skipped rungs does not execute.
- The rung is drawn dimmed with SKIPPED (JMP L5) in its top-right corner, and its contacts show their bit states without power flow.
Example — manual section
- Rung 0004:
XIC C100 → JMP L1— C100 = auto mode. - Rungs 0005–0007: jog and manual-move rungs.
- Rung 0008:
LBL L1 · XIC X1 → OTE Y1— the jump lands here and the rung runs normally.
Make sure the manual rungs leave their outputs OFF before auto is selected — a skipped OTE holds.
Checks
The editor checks jumps and labels after every edit and on import, and puts the first problem in the status bar, e.g. ⚠ rung 0004: JMP L1 has no matching LBL L1. The program still runs. At run time a JMP that finds no LBL further down is ignored and raises a range fault: SB11 and SB12 ON, SW17 = 4, SW18 = the JMP's rung (see System Bits & Words).
Common mistakes
- Jumping backwards to repeat rungs. Not supported: the scan already repeats every 100 ms. Use the next scan instead.
- Expecting skipped outputs to turn off. They hold. Turn them off before the jump, or use OTL/OTU deliberately.
- Timing across a jump. A TON in a skipped section freezes its accumulator; a timer that must keep timing belongs outside the jump.
Master Control Zones (MCR / END MCR)
Turn off the power rail for a group of rungs
An MCR rung opens a zone and an END MCR rung closes it. While the MCR rung is true, the rungs in the zone run normally. While it is false, every rung in the zone is scanned with the power rail OFF — as if all their conditions were false. Use it for a section of a machine that must drop out together, such as everything powered by one guard circuit. MCR and END MCR are in the Program Control palette section.
| Symbol | Name | Description |
|---|---|---|
| [MCR] | MCR | Master Control. Output position. Its rung condition switches the zone below ON or OFF. In RUN the block shows ZONE ON or ZONE OFF. |
| [END MCR] | END MCR | End of Master Control Zone. On a rung of its own with no contacts. Closes the zone; rungs after it are not affected. |
Neither has an address — the dialog shows MCR in a read-only field. In the palette, END MCR is labelled [END] End MCR Zone; dropping it on a rung inserts a new END MCR rung below that rung.
Inside a zone that is OFF
| Instruction | Behaviour |
|---|---|
| OTE | Turns OFF |
| OTL / OTU | Hold — a latched bit stays latched |
| TON | Resets, as with a false rung |
| TOF | Starts timing, as with a false rung — its DN drops when the preset runs out |
| RTO | Holds its time |
| Counters, BSL / BSR | See no rising edge — a true rung when the zone turns back on counts or shifts once. A CTUD's CD input is read every scan and still counts down. |
| MOV, math and data blocks | Do not execute |
| DRUM | Pauses; its reset bit still works |
| JMP | Does not jump |
Unlike a jump, the zone's rungs are scanned: contacts and edge memory are still evaluated. Zone rungs carry a side bracket on the left rail (orange while the zone is off) and show MCR OFF with an orange tint.
Example — guarded section
- Rung 0010:
XIC X5 → MCR— X5 = guard closed. - Rungs 0011–0013: conveyor and pusher outputs.
- Rung 0014:
END MCR.
The MCR zone rung template inserts all three parts: an XIC → MCR rung, one rung inside the zone, and the END MCR rung.
Rules and checks
- No nesting: an MCR inside an open zone is an error — close the first zone with END MCR.
- An END MCR with no MCR above it is an error.
- An MCR with no END MCR runs to the end of the program. It is reported but still loads and runs.
- A JMP may not jump into or out of a zone.
As with jumps, the editor reports the first problem in the status bar after each edit and on import.
Common mistakes
- Latching inside a zone. OTL bits stay ON when the zone goes off. Use OTE for outputs that must drop with the zone, or unlatch them outside it.
- Contacts on the END MCR rung. END MCR closes the zone unconditionally; it takes no conditions.
- Forgetting the END MCR. Every rung below the MCR then follows the zone.
Parallel Branches
OR logic — multiple paths to the output
A parallel group connects two or more contact paths in parallel. If any branch passes power the output is energised — this is Boolean OR logic.
Adding a Parallel Branch
- Right-click a contact and choose Add Parallel Branch. The contact is wrapped in a two-branch group; the new branch contains a default XIC contact.
- Or click the ‖+ button that appears below an existing parallel group (in RUN this opens an online edit).
Editing the Parallel Group
Right-click any contact in the group and choose Edit Parallel Group, or double-click the left rail of the group, to open the Parallel Branch Editor. Apply keeps the changes as one undo step; Cancel (or closing the dialog, or pressing RUN/STOP while it is open) discards everything changed in it. In RUN, opening the editor starts an online edit, which closes again if you cancel with nothing changed.
In the editor you can:
- Add/remove contacts within each branch (series AND logic inside a branch)
- Add new branches (more OR paths)
- Remove branches (minimum 2 required; removing down to 1 unwraps the group)
Series contacts inside a branch
Each branch can contain multiple contacts in series — all must be TRUE for that branch to pass power (AND logic within the OR group).
Memory Map
All addressable registers in the simulator
| Prefix | Range | Type | Description |
|---|---|---|---|
| X | X1–X16 | Digital Input | Physical inputs. Toggle on X tab or I/O tab. Can be driven by a connected 3D scene. |
| Y | Y1–Y16 | Digital Output | Physical outputs. Set by OTE/OTL/OTU rungs. Sent to connected scene each scan. |
| C | C1–C256 | Internal Relay | Program-internal bits. Not physical I/O. Toggle on C tab. Use C100+ by convention to avoid conflicts. |
| T | T1–T256 | Timer | Each T register holds EN, DN, Acc, Pre. Addressed as T1 (DN), T1/DN, T1/EN, T1/TT. |
| CT | CT1–CT256 | Counter | Each CT register holds DN, Acc, Pre. Addressed as CT1 (DN) or CT1/DN. |
| AX | AX1–AX8 | Analog Input | 12-bit read-only input registers (0–4095). Set by slider on AN tab or by a connected scene. |
| AY | AY1–AY4 | Analog Output | 12-bit output registers (0–4095). Written by MOV and math blocks. Sent to connected scene. Manually editable in STOP mode. |
| D | D1–D4000 | Data Register | 16-bit words for math and data. Read and written as INT, UINT, WORD, DINT or REAL by each instruction; DINT and REAL use two registers. Keep their values through RUN/STOP and are saved with the program. See Data Registers. |
| SB | SB1–SB18 | System Bit | Read-only status bits maintained by the simulator: always on/off, first scan, scan toggle, clock pulses, error flags, scene and gamepad status. |
| SW | SW1–SW19 | System Word | Read-only 16-bit words: real-time clock, scan timing, scan count, time in RUN, error code, scene count. |
Timer and counter accumulators are also readable as words: T1/ACC (ms) and CT1/ACC (count).
Data Registers (D)
D1–D4000 · untyped 16-bit words, read by each instruction as its Data Type
A data register holds 16 bits. It has no type of its own: the instruction that uses it decides how those bits are read — the same way registers work in most PLCs. The default is INT.
| Data Type | Registers | Range |
|---|---|---|
| INT | 1 | −32,768 … 32,767 |
| UINT | 1 | 0 … 65,535 |
| WORD | 1 | 0 … 65,535 (a bit pattern — shown in hex on blocks and on the D tab) |
| DINT | 2 — Dn low word, Dn+1 high word | −2,147,483,648 … 2,147,483,647 |
| REAL | 2 — Dn low word, Dn+1 high word | 32-bit floating point, about ±3.4e38, ~7 significant digits |
Constants used with REAL are stored the same way as REAL registers (32-bit), so EQU D1 0.1 is true after MOV 0.1 → D1. Comparing REAL values for exact equality is still risky after arithmetic — prefer GEQ/LEQ or LIM.
Keeping values
- D values stay through RUN/STOP and through undo — like retentive memory in a real controller.
- They are saved with a ladder program: in save slots, exported files and the automatic restore. Loading or importing a ladder program brings its own values; a file without saved values starts with every D at 0.
- CLEAR in ladder mode sets every D register to 0.
- Structured Text programs do not carry D values: ST save, load, import and CLEAR leave the registers as they are.
The D tab
- Used by the program lists the registers your rungs touch, including the second word of DINT/REAL values and COP/FLL ranges (the first 64; the rest are reached with Go to).
- Go to moves the 16-register window; ◀ ▶ step by 16.
- The format box sets how each register is shown: DEC, UDEC, HEX, BIN, DINT or REAL. DINT and REAL show Dn and Dn+1 together. A register your program writes as DINT or REAL shows in that format automatically, and one written as WORD shows in HEX. Formats are saved with the program.
- In STOP, type a new value and press Enter; values that do not fit the format are refused. In a HEX field, type
0x1F40orFFfor hex — plain digits such as255are taken as decimal. In RUN the values are read-only.
Pointers
An instruction can reach a register through another one: D[D5] means "the register whose number is in D5". See Pointers (D[Dn]).
Structured Text
ST reads and writes the same registers as D[n], always as INT. See ST Overview.
System Bits & Words
SB1–SB18 and SW1–SW19 — status the simulator maintains for your program
System bits (SB) and system words (SW) report what the PLC itself is doing: whether this is the first scan, a set of clock pulses, the time of day, how long the scan takes. Your program reads them like any other address — SB with contacts, SW with compares and MOV. They are refreshed once at the start of every scan, so every rung in one scan sees the same values.
System Bits (SB)
| Address | Meaning | Access |
|---|---|---|
| SB1 | Always ON | Read-only |
| SB2 | Always OFF | Read-only |
| SB3 | First scan — ON for the first scan after entering RUN, and for the first STEP after STOP, an edit or a program load | Read-only |
| SB4 | Scan toggle — alternates ON / OFF every scan | Read-only |
| SB5 | 0.5 s clock (250 ms ON, 250 ms OFF) | Read-only |
| SB6 | 1 s clock (500 ms ON, 500 ms OFF) | Read-only |
| SB7 | 1 min clock (30 s ON, 30 s OFF) | Read-only |
| SB8 | reserved | |
| SB9 | Math overflow — result wrapped (sticky) | Clear with OTU |
| SB10 | Divide by zero / invalid result (sticky) | Clear with OTU |
| SB11 | Index / range fault (sticky) | Clear with OTU |
| SB12 | Any error — ON while any of SB9–SB11 is ON | Read-only |
| SB13–16 | reserved | |
| SB17 | At least one 3D scene linked | Read-only |
| SB18 | Gamepad connected | Read-only |
System Words (SW)
| Address | Meaning | Units |
|---|---|---|
| SW1 | Year | e.g. 2026 |
| SW2 | Month | 1–12 |
| SW3 | Day | 1–31 |
| SW4 | Day of week | 1 = Monday … 7 = Sunday |
| SW5 | Hour | 0–23 |
| SW6 | Minute | 0–59 |
| SW7 | Second | 0–59 |
| SW8 | reserved | |
| SW9 | Last scan execution time — how long solving the rungs took | µs |
| SW10 | Maximum scan execution time — reset on entering RUN; STEP scans also count toward it | µs |
| SW11 | Actual interval since the previous scan | ms (nominally 100) |
| SW12 | reserved | |
| SW13 / SW14 | Scan count, low word / high word (count = SW13 + SW14 × 65536) | scans |
| SW15 / SW16 | Time in RUN, low word / high word | s |
| SW17 | Last error code (0 = none) | Clear with MOV 0 |
| SW18 | Rung number of the last error | |
| SW19 | Number of connected 3D scenes |
Things worth knowing
- Why there's no 100 ms clock. The scan runs every 100 ms. A clock with a 100 ms period would be sampled once per cycle, so the program would see it stuck ON, stuck OFF, or flickering at random. A clock bit needs a period of at least two scans to be seen reliably — so the fastest clock is SB5 (0.5 s), and SB4 toggles every scan for anything faster. Real PLCs have the same limit: a signal changing faster than the scan cannot be read reliably.
- Clocks follow real time, not the scan count, so they stay accurate even when scans jitter.
- Two kinds of scan time. SW9 is the time spent solving your logic — usually well under 1 ms. SW11 is how often the PLC actually scans. They answer different questions: "is my program heavy?" versus "how fast is the PLC cycling?"
- The clock keeps running in STOP, as a real controller's does — watch it on the SYS tab. Scan-related words (SW9–SW16) only change when a scan runs.
- Error flags and codes. The math and data instructions report problems through SB9–SB12, SW17 and SW18 — see the table below. The flags are sticky: they stay ON until your program clears them, so a fault that lasted one scan is never missed.
- Read-only is enforced. The Edit dialog refuses a coil on any SB except an OTU on SB9–SB11, and a MOV into any SW except SW17.
- Comments. System addresses show their built-in description on the ladder until you give them your own address comment.
Fault codes (SW17)
| SW17 | Fault | Flag | What was written |
|---|---|---|---|
| 0 | No fault | ||
| 1 | Overflow — an integer result did not fit its Data Type | SB9 | The wrapped value (32767 + 1 → −32768) |
| 2 | Divide by zero (DIV, MOD, CPT — including 0 raised to a negative power — or SCL with In min = In max) | SB10 | Nothing |
| 3 | Invalid result — infinity or not a number (e.g. SQRT(-1), a REAL beyond ±3.4e38, or a CPT result too large to compute such as 10 ** 400) | SB10 | Nothing |
| 4 | Range — an address or block runs past the end of its table (e.g. a REAL at D4000, COP past D4000), or a pointer D[Dn] holds a register number outside D1–D4000; also a JMP whose label is not found further down the program (the jump is ignored) | SB11 | Nothing |
SW18 holds the rung number of the most recent fault, as shown on the ladder. Faults are raised only by scans (RUN or STEP), never by the STOP-mode display. Clear them with OTU SB9 / OTU SB10 / OTU SB11 and MOV 0 → SW17; SB12 follows by itself. Ladder CLEAR, Load and Import (and the automatic restore when the page opens) also clear them, as a new program starts with no faults.
XIC SB3 → MOV … initialises values once at start-up · XIC SB6 → OTE Y1 flashes a light at 1 Hz · LIM SW5 7 17 → OTE Y4 enables something during working hours · GRT SW10 5000 alarms when a scan ever took more than 5 ms.I/O Panel Tabs
Right-side panel — nine tabs for monitoring and forcing
| Tab | Contents | Interactive? |
|---|---|---|
| I/O | Addresses referenced in the current program, plus the block held by each connected scene. Shows X toggles, Y force-LEDs, C toggles, T/CT progress bars, AX sliders, AY bars, and any system bits (LEDs), system words and data registers (values) the program uses. | Full — all controls active (SB/SW read-only) |
| X | All X1–X16 digital inputs | Click to toggle ON/OFF |
| Y | All Y1–Y16 digital outputs | STOP: click LED to force ON/OFF |
| C | C1–C16 internal relays | Click to toggle ON/OFF |
| T | Active timers — Acc, Pre, DN, progress bar | Read-only |
| CT | Active counters — Acc, Pre, DN, progress bar | Read-only |
| AN | AX1–AX8 sliders (input simulation) + AY1–AY4 bars with editable values | AX: drag slider. AY: type value in STOP mode. |
| D | Data registers: the ones your program uses, then a 16-register window you can move with Go to and ◀ ▶. Each register has a display format (DEC, UDEC, HEX, BIN, DINT, REAL). | STOP: type a value and press Enter. RUN: read-only. |
| SYS | Real-time clock, all system bits SB1–SB18 and system words SW1–SW19 | Read-only |
Run / Stop / Step
PLC operating modes
STOP Mode
The PLC is halted. No scan cycle runs. All Y and C outputs are de-energised and AY registers are cleared. You can:
- Edit the program (add, move, delete rungs and elements) — changes apply immediately
- Toggle X inputs and C relays to preview contact states
- Force Y outputs ON/OFF via the Y tab or I/O tab
- Manually set AY values via the AN tab or I/O tab
- Execute a single scan with STEP — only available when no scene is connected
RUN Mode
The scan cycle executes every 100 ms. The ladder canvas shows live power flow (green = energised). Inputs from a connected scene update each scan.
The running program is protected. Any edit in RUN — or the ✎ EDIT button — opens an online edit: changes go to a pending copy while the running program keeps scanning, and ACCEPT swaps the copy in between two scans. This works for ladder and Structured Text. LOAD, Import and CLEAR are refused while an edit is pending (ladder CLEAR needs STOP in any case).
Pressing ■ STOP while an edit is pending makes the pending copy the program, since nothing is running any more.
STEP Mode
While in STOP mode and with no scene connected, press STEP to execute exactly one scan. Useful for tracing logic one step at a time without continuous scanning. The first STEP after STOP, an edit or a load counts as a first scan (SB3 ON). STEP is also the way to watch an edge contact fire.
Online Editing
Change the program while it runs — ladder and Structured Text · v1.127 / ST v1.019
Online editing lets you change a running program without stopping the machine. Your changes go into a pending copy; the program that is scanning keeps running unchanged until you press ACCEPT. Then the new program is swapped in between two scans. Nothing is cleared: timers, counters, D registers and bits keep their values.
STOP vs RUN
| Mode | What an edit does |
|---|---|
| STOP | Edits apply immediately, as they always have. There is nothing running to protect, so there is no online edit and no ✎ EDIT button. |
| RUN, nothing pending | The running program is protected. The first edit opens an online edit automatically. |
| RUN, edit pending | The running program keeps scanning. Every edit goes to the pending copy. Everything you can do in STOP — insert, delete and move rungs, palette drops, templates, the Edit dialog, branches, rung comments, drum steps — works here. |
Starting an Edit
- Ladder: make any edit in RUN — double-click an element and Apply, drop an instruction from the palette, insert or delete a rung, drag a rung, edit a rung comment. Or press ✎ EDIT in the toolbar (shown in RUN only) to open an empty edit first.
- Structured Text: start typing in the editor while the program runs, or press ✎ EDIT. The compiled program keeps scanning; the editor now holds the pending source.
An amber bar appears across the full width of the window, between the toolbar and the editor, and pushes the editor down (ladder and ST alike):
ONLINE EDIT2 changes (1 inserted · 1 modified)ACCEPTCANCEL
An edit that an action opened on its own closes again if the action ends with nothing changed — for example the Parallel Branch Editor cancelled, or an invalid Apply followed by Cancel. (Most dialogs open the edit only when you press Apply, pick a template or confirm a delete, so cancelling them never opens one.) An edit opened with ✎ EDIT (or by UNDO/REDO in RUN) stays open, even with 0 changes, until you press ACCEPT or CANCEL.
In ladder the count is rungs inserted, modified and deleted. In ST it is the number of changed lines, plus the number of compile errors while there are any. The banner stays until you ACCEPT, CANCEL or STOP.
What You See — Ladder
| Marker | Meaning |
|---|---|
| EDIT 0003 | Modified rung (amber). A rung that was moved to a new position also counts as modified. Click the badge to show the running version below it, labelled RUNNING NOW — replaced on ACCEPT; click again to hide it. |
| NEW 0004 | Inserted rung (green). It runs after ACCEPT. |
| DELETED ON ACCEPT | A rung you deleted is still running, so it stays on screen as a greyed ghost with a red dashed line, where it used to be. The label at the right end of its header gives its running rung number. |
- Unchanged rungs keep showing live power flow from the running program.
- Changed and new rungs show no power flow — they are not running yet. Their contacts show bit state, as in STOP.
- Values on the I/O panel, the SYS and D tabs, connected scenes and Modbus follow the running program throughout. The used-address lists on the I/O panel show the addresses of both the running and the pending program.
What You See — Structured Text
- Line numbers in the gutter are marked: green = added line, amber = changed line, a red line across the gutter = lines were removed at that point (lines removed from the very end show as a red line under the last line).
- The marks and the banner count update when typing pauses (about 0.25 s), together with the live compile.
- Live compile and the error list work as in STOP.
- The Live Values column and the Variables & Blocks watch keep showing the running program. Values beside changed lines are hidden, because that code is not running yet.
ACCEPT
- Checks first. Ladder runs the full checks on every element plus the jump, label and MCR rules. ST compiles the pending source. Any problem refuses the accept: the status bar shows the first problem and its rung (ladder) or line (ST), for example
Can't accept — rung 0003: …. The edit stays pending so you can fix it. - Confirmation. The dialog lists what changes:
- Ladder: rungs inserted · modified · deleted.
- ST: changed lines, variables added, removed or with a changed type, function blocks added or removed.
- Both: outputs that are ON now and that nothing in the new program writes — they will stay ON (see the trap at the end of this page). In ladder any coil (OTE, OTL or OTU), shift register, drum or UNPACK on the bit counts as writing it, so a removed coil is not listed while an OTU of the same bit remains.
- Swap. Press Accept (Back returns to the edit). The swap happens between two scans — no scan ever runs part old, part new. The next scan runs the new program. The status bar confirms it and repeats any output left ON.
- One undo step. The whole online edit becomes a single step in the undo history.
Accepting with no changes simply closes the edit.
What Carries Over on ACCEPT
| Item | Ladder | Structured Text |
|---|---|---|
| Matched by | Element identity (an element you edited stays the same element) and instruction type. | Name (case-insensitive) and type. |
| Kept | Edge memory of P/N contacts, counters and shift registers; a DRUM's time in the current step. | VAR values; ARRAY values at indices that still exist (same element type); function-block instances — timer ET/Q, counter CV, edge memory; a user FUNCTION_BLOCK's inner variables, matched the same way even if its definition changed. |
| Fresh | New elements, or an element whose type changed: edge memory OFF, drum step time 0. | New variables, variables with a changed type, new array indices and new instances: initial value. |
| Initializers | — | Not re-applied. Changing count : INT := 0; to := 10; does not set count to 10 — it keeps its running value. |
| Memory | Never cleared by an accept: X, Y, C, T, CT, AX, AY, D, SB, SW and fault flags keep their values. | |
| Removed instructions | Bits keep their last state. A timer or counter whose instruction is removed keeps its value until a RES (or CLEAR, or a load). | |
CANCEL
Discards the pending copy. The running program was never changed, so nothing happens to the machine. You are asked to confirm only when there are changes: Discard or Keep editing. In ST, the running source is put back in the editor.
Undo and Redo
- Inside an edit (ladder), ↩ UNDO / ↪ REDO step through the pending edit's own changes. Your normal history is set aside and comes back when the edit closes. In ST, use the editor's own text undo (Ctrl+Z in the code box).
- In RUN with nothing pending (ladder), UNDO or REDO does not swap anything directly: it opens the previous (or next) program in the history as an online edit, with markers, for you to review and ACCEPT or CANCEL. Accepting it unchanged moves along the history instead of adding a step, so repeated UNDO + ACCEPT walks back one step at a time, and REDO + ACCEPT walks forward again.
Other Controls While an Edit Is Pending
| Control | Behaviour |
|---|---|
| ■ STOP / F4 | Nothing is running any more, so the pending copy becomes the program — no ACCEPT checks or dialog. The status bar says so and reports any problem, as editing in STOP does. Outputs clear as on any STOP. |
| 💾 SAVE / Export | Saves the running program. The status bar says the pending edit was not saved. |
| LOAD, Import, CLEAR, ST Examples (load into editor), LADDER ↔ ST switch | Refused — the status bar says "Accept or cancel the online edit before …" (loading a program, importing, clearing, switching editors). |
| Leaving or reloading the page | The browser asks you to confirm — the pending edit would be lost. |
| ⏭ STEP | STOP only, so it never meets a pending edit. |
Files and save slots never contain a pending edit, so the ladder JSON and .st formats are unchanged.
Try It — Control Panel Scene
Uses the default Start/Stop/Jog program (✕ CLEAR, then reload, if you have replaced it). Rung 0000 seals in Y1; rung 0001 drives the jog relay C1.
- CONNECT the Control Panel scene and press F5. Press Start — the run light (
Y1) comes on. - Add a rung while running. Hover below the last rung and click ⊕ Add New Rung, choose XIC → OTE, set the contact to
Y1and the coil toY2. The banner appears and the rung shows NEW 0002, with no power flow. The light is still on — the running program has not changed. - Press ACCEPT. The dialog shows Rungs inserted: 1. Accept —
Y2turns on in the Y tab on the next scan. - Spring the trap. With the light still on, double-click the
Y1coil on rung 0000, change it toY5and Apply. Click the EDIT 0000 badge to compare it with the running rung. - Press ACCEPT. The dialog warns: "These outputs are ON and nothing in the new program writes them — they will stay ON: Y1". Accept, then press Stop on the panel. The light stays on: no rung writes
Y1any more. - Recover. Press ↩ UNDO. The previous program opens as an online edit (EDIT 0000). ACCEPT it. The dialog now warns that
Y5will stay ON — the same trap in reverse, since nothing writesY5any more. Rung 0000 writesY1again and Stop works; press STOP to clearY5.
Y1 := to Y2 := — the line turns amber in the gutter and its live values disappear. The accept dialog lists Y1 as staying ON.Common Mistakes — the Online-Edit Trap
- Deleting or readdressing a coil leaves the output ON. An OTE only writes while it is scanned. Remove it and the bit keeps its last state — exactly as on a real controller. Read the "will stay ON" list before you accept. To turn the output off: leave the coil in with a false rung for one scan, add an OTU, or STOP (which clears outputs).
- Timers and counters keep their values. Lowering a preset below the accumulated value makes the done bit come on at once. Removing a RES rung leaves the count where it is.
- ST initializers are not re-applied. Changing
:= 0to:= 10does nothing to a running variable. Rename it, change its type, or write it from code if you need the new value. - Changing an element's type resets its memory. Turning an XIC into a P contact, or a CTU into a CTUD, starts its edge memory fresh — a bit that is already ON can fire a rising edge on the first scan.
- Pending is not running. Testing on the scene before ACCEPT tests the old program. Watch for the banner.
- SAVE saves the running program, not your pending edit. Accept first, then save.
Scan Cycle
How the simulator executes your program each 100 ms
(X, AX from scene)
(SB, SW latched)
(Rung 0 → last)
(Y, AY to scene)
(redraw ladder + I/O)
Execution Order
- System bits and words (first scan, clocks, real-time clock, scan count) are updated once, before the first rung.
- Rungs execute from Rung 0000 to the last rung, top to bottom.
- Within each rung, elements are evaluated left to right.
- Parallel branches: all branches are evaluated; the rung is powered if any branch is TRUE.
- An OTE writes its rung's result each time its rung is scanned; an OTE in a rung skipped by a JMP keeps its last state.
- OTL/OTU bits persist across scans — they only change when a latch/unlatch rung is powered.
- A JMP with power skips the following rungs up to the rung holding its LBL; skipped rungs are not evaluated. Rungs inside an MCR zone whose MCR rung is false run with the power rail off. See Jump & Label and MCR Zones.
Timing Resolution
The nominal scan interval is 100 ms. Timer accumulators increment by the actual elapsed time since the last scan (capped at 500 ms). The scan time counter in the toolbar shows the most recent execution time in milliseconds. Your program can read the same figures: SW9 (execution time, µs), SW10 (maximum) and SW11 (actual interval, ms).
Online Edits and the Scan
While an online edit is pending, every scan runs the running program; the pending copy is not executed. ACCEPT swaps the programs between two scans, so no scan ever runs part old, part new. Memory is not touched by the swap — outputs, timers, counters and D registers carry straight on. See Online Editing.
Forcing I/O
Manually overriding bit and register states
Forcing X Inputs
Click any X button on the X tab or I/O tab at any time (RUN or STOP). In RUN mode the forced value is used in the next scan immediately. In STOP mode it triggers a display refresh of contact states.
Forcing Y Outputs
Y bits can only be forced in STOP mode. Click the LED on the Y tab or I/O tab to toggle. The change propagates to a connected scene immediately via BroadcastChannel.
Forcing C Relays
Click any C button on the C tab or I/O tab at any time. Useful for simulating latch states or internal flags without having to construct a full rung to set them.
Forcing AX Analog Inputs
Drag the slider on the AN tab or I/O tab for any AX channel. The new value takes effect on the next scan in RUN mode, or updates contact comparisons immediately in STOP mode.
Forcing AY Analog Outputs
In STOP mode only, type a value (0–4095) into the AY input field on the AN tab or I/O tab and press Enter. In RUN mode the field is read-only — the program controls AY via MOV and math blocks.
Save & Load
Program storage — browser slots and JSON files
Saving
- Click 💾 SAVE in the toolbar.
- Type a name in the Program Name field.
- Click Save — the program is stored in browser localStorage under that name.
- If a slot with the same name already exists it is overwritten. Up to 10 slots are kept.
Loading
- Click 📂 LOAD.
- Click a slot to load it (confirmation required to prevent accidental overwrite).
- Memory resets on load: X, Y, C, T, CT, AX and AY clear, fault flags (SB9–SB12, SW17, SW18) clear, and the D registers take the values saved with the program.
Auto-Restore
On page load the simulator automatically restores the most recently saved slot. No manual action is needed between sessions.
File Export / Import
In the Save/Load dialog, use ⬇ Export .json to download the program as a JSON file. Use ⬆ Import .json to load a previously exported file — useful for sharing programs between computers.
Rung Templates
Pre-built rung patterns for common tasks
When inserting a new rung the Rung Template Picker opens. Addresses are auto-filled with the next available address for each type — you will rarely need to edit them manually.
| Template | Description |
|---|---|
| XIC → OTE | One normally-open contact energises one output. Starting point for most rungs. |
| XIC → OTL | Contact sets (latches) an output. Use with XIC → OTU to create a Set/Reset pair. |
| XIC → OTU | Contact resets (unlatches) an output. |
| PAR → OTE | Two contacts in parallel (OR) driving an output. Useful for start/seal-in circuits. |
| XIC → TON | On-delay timer — preset 2 seconds. Double-click block to change preset. |
| XIC → TOF | Off-delay timer — preset 2 seconds. |
| XIC → CTU | Count-Up counter — preset 10 counts. |
| XIC → RES | Reset rung targeting a counter by default. Edit the address to target a timer. |
| GRT → OTE | Analog compare — output ON when AX > 2048 (mid-scale). Edit comparand as needed. |
| GRT → OTL/OTU | Two-rung hysteresis band — inserts both the Set (high threshold) and Reset (low threshold) rungs together. |
| XIC → MOV | Move a constant value to an AY register when the contact is ON. |
| XIC → RTO | Retentive timer — preset 5 seconds. Keeps its time when the rung drops; add a RES rung to clear it. |
| XIC → CTUD | Up/down counter — the rung counts up, the next free X input counts down. Preset 10. |
| LIM → OTE | Output ON while AX is between 1000 and 3000. |
| P → INC | Counts button presses in a D register: a rising-edge contact makes INC add 1 once per press. |
| SCL → D | Scales AX 0–4095 to 0–100 in a D register every scan. |
| P → Toggle | Push-on / push-off from one button. Inserts two rungs: a rising edge makes a one-scan pulse, and the pulse flips the output. |
| P → BSL | Shift a part bit along C bits on each pulse: a rising-edge contact drives a BSL on 8 free C bits, with the next free X as the pulse input and the following free X as the data bit. See Shift Registers. |
| DRUM → outputs | Step through output patterns on time: XIC drives a DRUM on the next free D register with 4 outputs and four 2-second steps. Double-click the block, then Edit steps…. See Drum Sequencer. |
| MCR zone | Rungs that run only while a condition is ON. Inserts three rungs: XIC → MCR, one rung inside the zone, and END MCR. See MCR Zones. |
Connecting a 3D Scene
Linking the simulator to an interactive machine visualisation
3D Scenes are separate HTML files that display animated industrial machines. When connected to the simulator they exchange I/O state in real time via the browser's BroadcastChannel API — no server or network connection is needed.
How to Connect
- Place the scene HTML file in the same folder as
acc-plc-simulator.html. - Ensure
acc-scenes.jsis also in the same folder and lists the scene. - Click CONNECT in the simulator toolbar.
- The Connect dialog lists all available scenes from
acc-scenes.js. - Click LAUNCH ↗ next to the scene you want — it opens in a new browser window.
- Once the scene loads, the CONNECT button changes to LINKED with a pulsing blue dot.
- The connected scene name appears next to the button — or N SCENES when more than one is connected.
Disconnecting
Close the scene window. The simulator detects the loss of heartbeat messages within 2 seconds and resets the CONNECT button to its idle state.
Connecting Several Scenes at Once
From v1.120 the simulator can drive more than one scene at the same time, so a single program can run several machines. Earlier versions allowed only one scene and disabled the other LAUNCH buttons with BUSY; that restriction is gone.
Two scenes both using X1–X3 would fight over the same inputs, so the simulator gives each scene its own block of PLC memory and translates between them. The scene keeps its own numbering internally; your program uses the shifted addresses.
The rule is one addition: PLC address = scene address + that scene's offset, applied separately to X and to Y.
What the Connect Dialog Shows
| Element | Meaning |
|---|---|
| PLC MEMORY strip | X1–X16 and Y1–Y16 as cells, coloured per connected scene, red where two scenes contend. Hovering a cell names its owner. |
| + ADD SCENE ↗ | Replaces LAUNCH ↗ once a scene is already connected. |
| SUGGESTED | The offset was worked out just now to clear the scenes already connected. Editable before you launch. |
| STORED | An offset you set earlier, kept from a previous session. It is not a suggestion, and it may no longer suit the scenes currently connected. |
| ↻ suggest | Moves that scene to the lowest free block. |
| clear of: … | Names the connected scenes that the suggested offset is avoiding. |
Reading a Scene's Addresses
A scene mapped to a non-zero offset relabels its own I/O rows to the PLC address and states the mapping in the panel header — for example INPUTS X (scene X1–X3 → PLC X4–X6). Those are the addresses to write your program against. The scene's About panel, its I/O map and its event log keep the scene's own numbering, because they describe the machine, which does not move.
How Many Scenes Can Run at Once
The practical limit is the 16-point memory map, not the browser. All six live scenes together would need 32 X and 21 Y, so they do not fit. Two scenes almost always fit; the widest pair — Palletizer and Tank Fill, ten inputs each — cannot run together. Four is the most that fits, and only one combination of four does. When there is no room the dialog says so rather than silently overlapping.
Standalone Scenes
Running a 3D scene without the simulator
Each scene HTML file is fully self-contained and can be opened directly in a browser without the simulator running. In standalone mode the scene operates as a 3D demonstration — you can interact with the machine but PLC logic is not executed.
Opening a Scene Standalone
- Open the scene HTML file directly in your browser (e.g.
acc-conveyor-scene.html). - The scene will initialise and display the machine in its default state.
- Interact with the machine elements as designed (click buttons, observe animations).
Connecting the Simulator Later
With the scene already open in one window, open acc-plc-simulator.html in a second window. The scene will detect the simulator's presence via BroadcastChannel and link automatically — you do not need to re-open the scene.
Use Cases for Standalone Scenes
- Demonstrating machine operation without needing to explain ladder logic
- Exploring the 3D environment before writing the control program
- Classroom display on a projector while students write programs on their own machines
Scene I/O Mapping
Which signals are exchanged between the simulator and a scene
The BroadcastChannel (acc-plc-link) carries two message types each direction:
Scene → Simulator (Inputs)
| Signal | Type | Description |
|---|---|---|
| X1–X16 | Digital | Physical pushbuttons, limit switches, sensors in the scene. From v1.120 a scene sends only the addresses it owns, as an address-keyed object ({X1:true, X2:false}) rather than a padded array — a padded array would assert that every other scene's inputs were OFF. |
| AX1–AX8 | Analog 0–4095 | Analog sensors — e.g. position pot, pressure transducer, temperature. Arrive as integer array. |
Simulator → Scene (Outputs)
| Signal | Type | Description |
|---|---|---|
| Y1–Y16 | Digital | Motor contactors, solenoids, pilot lights, alarms. Sent as boolean array after every scan. |
| AY1–AY4 | Analog 0–4095 | Speed reference, valve position, display value. Sent as integer array after every scan. |
Scene I/O Chips
When you open the CONNECT dialog, each scene card shows coloured chips listing its specific I/O assignments — green chips are inputs to the simulator (from the scene), olive chips are outputs from the simulator (to the scene).
Keyboard Shortcuts
All keyboard shortcuts available in the simulator
| Key | Action |
|---|---|
| F1 | Open the help at the instruction under the mouse — a palette item, an ST list item, or an instruction on the ladder. Otherwise opens the help home (ST mode: the ST Instruction List). |
| F5 | Start RUN mode (continuous scan) |
| F4 | Switch to STOP mode |
| Ctrl+Z | Undo last edit |
| Ctrl+Y | Redo (re-apply undone edit) |
| Ctrl+Shift+Z | Redo (alternative shortcut) |
| Esc | Close any open dialog or modal. In the Drum Steps table, closes only the table (edits discarded) and returns to the Edit dialog. |
| Enter | Apply / confirm the currently open dialog. In the Drum Steps table, Enter is Save steps. |
| Dbl-click | Open Edit dialog for any ladder element |
| Dbl-click header | Edit the rung comment |
| Right-click | Open context menu for a contact |
Undo / Redo
The simulator maintains an undo history of up to 50 states. A snapshot is taken automatically before every edit operation, including:
- Inserting or deleting a contact
- Editing an instruction (address, type, preset, comment)
- Adding, editing, or removing parallel branches
- Inserting or deleting a rung
- Dragging rungs to reorder them
The ↩ UNDO and ↪ REDO buttons in the toolbar are greyed out when there is nothing to undo or redo. Loading a program (via LOAD or Import) and using CLEAR both reset the history to start fresh.
Gamepad
Drive simulator inputs with any standard USB or Bluetooth controller
The simulator supports any gamepad recognised by the browser's standard W3C Gamepad API — Xbox, PlayStation, generic USB HID, and most Bluetooth controllers. Plug in a controller, press any button, and the toolbar's 🎮 NO PAD badge turns green and shows the controller name.
AX1–AX5 (0–4095) — the same range a real potentiometer or 4–20 mA transmitter produces. Face buttons act exactly like momentary pushbuttons. This is the closest browser-side input to a real industrial control surface.Default Mapping
| Control | Address | Behaviour |
|---|---|---|
| A / Cross | X1 | Momentary — ON while held |
| B / Circle | X2 | Momentary |
| X / Square | X3 | Momentary |
| Y / Triangle | X4 | Momentary |
| D-pad ↑ | X5 | Momentary |
| D-pad ↓ | X6 | Momentary |
| D-pad ← | X7 | Momentary |
| D-pad → | X8 | Momentary |
| LB / L1 | X9 | Momentary |
| RB / R1 | X10 | Momentary |
| LT / L2 (analog) | AX1 | 0–4095, 0.05 deadzone at rest |
| RT / R2 (analog) | AX2 | 0–4095, 0.05 deadzone at rest |
| Left stick Y | AX3 | 0–4095, centred at 2048 |
| Left stick X | AX4 | 0–4095, centred at 2048 |
| Right stick Y | — | Zoom — hold up to zoom in, hold down to zoom out (one 5% step every 250 ms while held) |
| Right stick X | AX5 | 0–4095, centred at 2048 |
| Right stick click | — | Reset zoom to 100% (same as the 1:1 button) |
| START / Options | — | RUN (same as F5) |
| SELECT / Share | — | STOP (same as F4) |
| Left stick click | — | STEP (one scan, STOP mode only) |
Keyboard Still Works
The gamepad is purely additive. F5, F4, Ctrl+Z, Ctrl+Y, Esc, Enter and every other shortcut behave identically whether a controller is plugged in or not. The two paths call the same functions — they do not conflict.
Interaction with Scenes
When a 3D scene is connected and is driving an X or AX address, the gamepad cannot override that address. The scene's sensor or pushbutton wins. The gamepad still drives the remaining unused addresses — for example, the Conveyor scene uses X1–X4, so the gamepad's D-pad (X5–X8) and shoulder buttons (X9–X10) remain available to inject extra test inputs.
Browser Requirements
- Chrome / Edge / Brave — full support including triggers and rumble
- Firefox — full support; some Bluetooth pads require pairing through OS settings first
- Safari — partial support; trigger axis polarity may be inverted on some pads
- First-input requirement: the Gamepad API only reports a controller after the user presses a button or moves a stick. If the badge stays grey after plugging in, give it a button press to wake it
Address Reference
Complete syntax for all register types
| Address | Type | Range | Notes |
|---|---|---|---|
| Xn | Digital In | X1–X16 | Boolean. Toggleable from X/IO tabs. |
| Yn | Digital Out | Y1–Y16 | Boolean. Forceable in STOP mode. |
| Cn | Internal | C1–C256 | Boolean. Use C100+ by convention. |
| Tn | Timer | T1–T256 | Resolves to Tn/DN. |
| Tn/DN | Timer | T1–T256 | Done bit — Acc ≥ Pre. |
| Tn/EN | Timer | T1–T256 | Enable bit — rung powered. |
| Tn/TT | Timer | T1–T256 | Timing bit — EN && !DN. |
| CTn | Counter | CT1–CT256 | Resolves to CTn/DN. |
| CTn/DN | Counter | CT1–CT256 | Done bit — Acc ≥ Pre. |
| AXn | Analog In | AX1–AX8 | Integer 0–4095. Read by compares, MOV and math. |
| AYn | Analog Out | AY1–AY4 | Integer 0–4095. Written by MOV and math blocks (clamped). |
| Tn/ACC | Timer word | T1–T256 | Elapsed time in ms. Readable in compares and as a MOV or math source. |
| CTn/ACC | Counter word | CT1–CT256 | Current count. Readable in compares and as a MOV or math source. |
| Dn | Data Register | D1–D4000 | 16-bit word, read as the instruction's Data Type. Through a pointer: D[D5] uses the register whose number is in D5. DINT and REAL use Dn (low word) and Dn+1. ST: D[n] as INT. |
| Ln | Label | L1–L256 | Jump target for JMP / LBL. Not memory — a label number only. Each label is used by one LBL. |
| SBn | System Bit | SB1–SB18 | Read-only. SB9–SB11 can be cleared with OTU. |
| SWn | System Word | SW1–SW19 | Read-only. SW17 can be cleared with MOV 0. |
Example Programs
Common ladder logic patterns
1 — Start / Stop / Seal-In (Motor Starter)
| Rung | Logic | Purpose |
|---|---|---|
| 0000 | (XIC X1 || XIC C1) && XIC X2 → OTE C1 | Start PB or Seal-In, gated by Stop PB. C1 = Run relay. |
| 0001 | XIC C1 → OTE Y1 | Run relay drives motor output. |
2 — Jog Circuit (no seal-in)
| Rung | Logic | Purpose |
|---|---|---|
| 0000 | (XIC X1 || XIC C1) && XIC X2 && XIO C2 → OTE C1 | Normal run, interlocked against jog (C2). |
| 0001 | XIC X3 && XIO C1 → OTE C2 | Jog PB, interlocked against run. |
| 0002 | (XIC C1 || XIC C2) → OTE Y1 | Motor runs on either relay. |
3 — On-Delay Timer (Fan delay)
| Rung | Logic | Purpose |
|---|---|---|
| 0000 | XIC X1 → TON T1 Pre=5000 | Start 5-second timer when X1 is ON. |
| 0001 | XIC T1 → OTE Y1 | Y1 energises 5 s after X1 goes ON. |
4 — Analog Threshold (High-level alarm)
| Rung | Logic | Purpose |
|---|---|---|
| 0000 | GRT AX1 > 3276 → OTE Y3 | Alarm ON when AX1 > 80% (3276/4095). Drag AX1 slider to test. |
5 — Hysteresis Band (Heating control)
| Rung | Logic | Purpose |
|---|---|---|
| 0000 | GRT AX1 > 2500 → OTL Y4 | Heater OFF when temperature rises above setpoint. |
| 0001 | LES AX1 < 1500 → OTU Y4 | Heater ON when temperature falls below lower threshold. |
6 — Push-On / Push-Off (one button)
| Rung | Logic | Purpose |
|---|---|---|
| 0000 | -|P|- X1 → OTE C100 | One-scan pulse each time the button is pressed. |
| 0001 | (XIC C100 && XIO Y1) || (XIO C100 && XIC Y1) → OTE Y1 | The pulse flips Y1; between pulses Y1 holds. Insert it with the P → Toggle template. |
7 — Working-Hours Light (real-time clock)
| Rung | Logic | Purpose |
|---|---|---|
| 0000 | LIM SW5 7…17 && LIM SW4 1…5 → OTE Y1 | Y1 ON 07:00–17:59, Monday to Friday. |
| 0001 | XIO Y1 && XIC SB6 → OTE Y2 | Outside those hours, Y2 flashes at 1 Hz. |
8 — Accumulated Run Time
| Rung | Logic | Purpose |
|---|---|---|
| 0000 | XIC X1 → RTO T5 Pre=10000 | X1 = motor running feedback. Adds up run time across every start and stop. |
| 0001 | XIC T5 → OTE Y6 | Service light after 10 s of total running. |
| 0002 | XIC X8 → RES T5 | Maintenance reset. |
9 — Bottle Tracking (shift register)
| Rung | Logic | Purpose |
|---|---|---|
| 0000 | -|P|- X1 → BSL C100 ×8, data X2, unload C200 | X1 pulses once per bottle pitch; X2 = bottle faulty at the entry. |
| 0001 | XIC C105 → OTE Y3 | Reject pusher fires when the faulty bottle reaches the sixth position. |
10 — Traffic Light (drum sequencer)
| Rung | Logic | Purpose |
|---|---|---|
| 0000 | XIC X1 → DRUM D10, outputs Y1 ×3, reset X8 | Steps: Y3 red 5 s · Y1 green 4 s · Y2 amber 1.5 s, then back to red. |
| 0001 | EQU D10 2 → OTE Y4 | Pedestrian "wait" lamp while the drum is on step 2 (green). |
Structured Text (ST)
IEC 61131-3 textual programming · acc-st.js v1.020
Structured Text is a second way to write a program — a text-based PLC language that looks like Pascal, defined by the IEC 61131-3 standard. Use the LADDER | ST toggle in the toolbar to switch modes. A program is written in ladder or ST; both drive the same I/O (X, Y, C, AX, AY) and the same 100 ms scan, so every 3D scene and the Modbus bridge work with either, unchanged.
Switching to ST
Click ST in the toolbar (the LANG · LADDER · ST switch at the top-left). The ladder canvas is replaced by a code editor with a line-number gutter and a live error list. Press ▶ RUN (or F5) to execute; the program is compiled first, and RUN is refused until any errors are fixed. The I/O panel, Connect, gamepad, Save/Load, and Step all work exactly as in ladder mode.
= instead of := for an assignment reads "Use ':=' for assignment, not '='." Click a message to jump to the line.VAR, IF, TON, CASE…) or an I/O address (X1, Y2, AX1) and a short explanation with an example pops up — handy while you're learning the language.VAR with a free name such as tmr1; a FOR loop declares its counter i if needed; a data type (BOOL, INT …) declares a new variable in VAR. In RUN, inserting opens an online edit like any other change. Press ? or F1 on an item to open its entry in the ST Instruction List.The right-hand I/O tab lists the addresses your ST program actually uses and updates as you type, so a newly added output like Y2 := NOT X2; appears there right away.
Use the zoom control at the bottom-right of the editor (or hold Ctrl and scroll the mouse wheel) to enlarge or shrink the code, just like the ladder editor.
CLEAR in the toolbar wipes whichever program you're editing — in ST mode it clears the Structured Text (after a confirm); in ladder mode it clears the ladder. The two are independent. CLEAR, LOAD, loading an example and the LADDER | ST switch are refused while an online edit is pending.
Watching the logic run (online monitoring)
While the program runs, ST shows live state three ways:
- Live Values column — a pane beside the code, aligned line-by-line, listing each address and variable used on that line with its current value. True conditions are highlighted green; false ones are greyed, so you can read which parts of each expression are satisfied. Analog values and counters show their number; function-block outputs show too (e.g.
flash.Q = 1,onDelay.ET = 400). It refreshes every scan. Toggle it with the 👁 Live Values button. - Variables & Blocks watch — the right-hand pane; a compact list of every variable and function-block instance (Q / ET / CV).
- I/O panel — the shared X / Y / C / AX / AY tabs, exactly as in ladder mode.
You can edit while the program runs: typing in the editor (or pressing ✎ EDIT) opens an online edit. The compiled program keeps running; added and changed lines are marked green and amber in the gutter, their live values are hidden, and ACCEPT compiles the new source and swaps it in between two scans. Variables and function blocks that keep their name and type keep their values — initializers are not re-applied. Drag the dividers between the program, Live Values, and Variables & Blocks panes to resize them; your widths are remembered.
Addresses (shared with ladder)
These names are the "wired" I/O — use them directly, do not declare them in VAR:
| Name | Meaning | Type | Access |
|---|---|---|---|
| X1–X16 | Discrete inputs | BOOL | read / write |
| Y1–Y16 | Discrete outputs | BOOL | read / write |
| C1–C256 | Internal relays | BOOL | read / write |
| AX1–AX8 | Analog inputs | INT 0–4095 | read / write |
| AY1–AY4 | Analog outputs | INT 0–4095 | read / write |
| T1–T256 | Ladder timer done bits | BOOL | read only |
| CT1–CT256 | Ladder counter done bits | BOOL | read only |
| SB1–SB18 | System bits (first scan, clocks, …) | BOOL | read only · SB9–SB11 may be cleared with := FALSE |
| SW1–SW19 | System words (clock, scan timing, …) | INT | read only · SW17 may be cleared with := 0 |
| D[1]–D[4000] | Data registers, shared with ladder | INT (−32768…32767, wraps) | read / write · index may be an expression: D[i + 1] |
D on its own is an error — always write D[n]. A constant index outside 1–4000 is a compile error; a computed one stops the PLC with a run-time error. If your program (or a FUNCTION / FUNCTION_BLOCK) declares its own variable called D, that variable is used instead of the data registers. ST reads and writes D only as INT; use ladder instructions for DINT, REAL and the other types.
Your own working variables go in a VAR ... END_VAR block. ST timing and counting use IEC function blocks (see next page), not the ladder T/CT table.
Hello, seal-in
VAR Running : BOOL; END_VAR // Start OR sealed-in, gated by Stop (N.C.) Running := (X1 OR Running) AND NOT X2; Y1 := Running; // motor / pilot light
Press RUN, then in the X or I/O tab toggle X1 ON — Y1 latches on and stays on after you release X1. Toggle X2 to drop it.
ST Syntax & Function Blocks
What the ST engine supports
Data types
BOOL, INT/DINT/WORD (integer — values truncate on assignment), REAL (floating point), TIME (use T# literals like T#500ms, T#2s, T#1m30s), and ARRAY[lo..hi] OF TYPE. Identifiers are case-insensitive.
Operators
Boolean AND OR XOR NOT; comparison = <> < > <= >=; arithmetic + - * / MOD **. Assignment is :=. Boolean AND/OR short-circuit; divide/MOD by zero yields 0.
Control flow
IF cond THEN ... ELSIF cond2 THEN ... ELSE ... END_IF; CASE step OF 0: Y1 := TRUE; 1, 2: Y2 := TRUE; 3..5: Y3 := TRUE; ELSE Y4 := TRUE; END_CASE; FOR i := 1 TO 8 BY 1 DO ... END_FOR; WHILE cond DO ... END_WHILE; REPEAT ... UNTIL cond END_REPEAT;
WHILE loop to "wait". A per-scan loop budget stops runaway loops so a bad WHILE TRUE reports an error instead of freezing the browser.Function blocks
Declare an instance in VAR, call it each scan with named parameters, read its outputs as members (.Q, .ET, .CV):
VAR onDelay : TON; END_VAR onDelay(IN := X1, PT := T#2s); Y1 := onDelay.Q; // TRUE 2 s after X1 AY1 := onDelay.ET; // elapsed ms
| Block | Inputs | Outputs | Purpose |
|---|---|---|---|
| TON | IN, PT | Q, ET | On-delay timer |
| TOF | IN, PT | Q, ET | Off-delay timer |
| TP | IN, PT | Q, ET | Pulse timer |
| CTU | CU, R, PV | Q, CV | Count up (Q when CV≥PV) |
| CTD | CD, LD, PV | Q, CV | Count down (Q when CV≤0) |
| CTUD | CU, CD, R, LD, PV | QU, QD, CV | Count up and down (QU when CV≥PV, QD when CV is 0; never below 0) |
| R_TRIG | CLK | Q | Rising-edge one-scan pulse |
| F_TRIG | CLK | Q | Falling-edge one-scan pulse |
Standard functions
ABS, SQRT, MIN, MAX, LIMIT(min,in,max), TRUNC, ROUND, SEL, SHL, SHR, and converters. You can also define your own FUNCTION and FUNCTION_BLOCK.
// scale analog 0–4095 to 0–100 %
VAR pct : INT; END_VAR
pct := LIMIT(0, AX1 * 100 / 4095, 100);
Save, load, share
In ST mode the toolbar SAVE / LOAD (and the ST bar's own buttons) store named ST programs in the browser, and Export / Import .st writes plain text files. Your source auto-saves as you type. Comments use // or (* ... *).
ST Instruction List
Every keyword, block, function and address the Structured Text engine understands · acc-st.js v1.020
The same list sits on the left of the editor in ST mode. Hover an item there for a quick card, click it to insert it at the cursor, or press ? / F1 to jump here.
Program structure
Optional wrapper around a program. A bare list of declarations and statements works too.
PROGRAM Main VAR run : BOOL; END_VAR run := X1; END_PROGRAM
Declares your working variables: name : TYPE, with an optional initial value. Do not declare I/O addresses (X1, Y1 …) — they already exist.
VAR count : INT := 0; running : BOOL; END_VAR
Inside a FUNCTION or FUNCTION_BLOCK: VAR_INPUT lists the inputs, VAR_OUTPUT the outputs (read them as inst.name).
VAR_INPUT inp : BOOL; END_VAR VAR_OUTPUT out : BOOL; END_VAR
A reusable calculation that returns one value. Assign the result to the function name. Functions have no memory between calls.
FUNCTION Pct : INT VAR_INPUT raw : INT; END_VAR Pct := raw * 100 / 4095; END_FUNCTION
A reusable block that keeps its own memory between scans. Declare an instance in VAR, call it each scan, read its outputs as inst.name.
FUNCTION_BLOCK Debounce VAR_INPUT inp : BOOL; END_VAR VAR_OUTPUT out : BOOL; END_VAR VAR t : TON; END_VAR t(IN := inp, PT := T#50ms); out := t.Q; END_FUNCTION_BLOCK
Notes for people, ignored by the PLC: // to the end of the line, or (* … *) and /* … */ around a block.
// start the pump (* several lines *)
Data types
A bit: TRUE or FALSE.
running : BOOL;
A whole number. Decimals are dropped when you assign to it. DINT, SINT, UINT and LINT behave the same here.
count : INT := 0;
A number with decimals (floating point). LREAL behaves the same.
level : REAL;
A duration in milliseconds, written with a T# literal: T#500ms, T#2s, T#1m30s, T#1h.
delay : TIME := T#2s;
A whole number used as a group of bits. WORD, DWORD and BYTE are treated like INT here.
flags : WORD;
A fixed list you index with [ ]. The bounds are part of the declaration.
steps : ARRAY[1..8] OF BOOL; steps[3] := TRUE;
Statements
Stores the value on the right in the variable or output on the left. Every statement ends with a semicolon.
Y1 := X1 AND NOT X2;
Runs statements only when a condition is true. Add ELSIF for more tests and ELSE for everything else.
IF X1 THEN Y1 := TRUE; ELSIF X2 THEN Y1 := FALSE; END_IF;
Runs one branch chosen by an integer value. Labels can be single values, lists (1, 2) or ranges (3..5). Ideal for step sequences.
CASE step OF 0: Y1 := TRUE; 1, 2: Y2 := TRUE; 3..5: Y3 := TRUE; ELSE step := 0; END_CASE;
Repeats a fixed number of times in one scan. Use BY for a step other than 1.
FOR i := 1 TO 8 DO steps[i] := FALSE; END_FOR;
Repeats while a condition stays true — inside ONE scan. Never use it to wait for an input; use a timer or a state variable instead.
WHILE i < 10 DO i := i + 1; END_WHILE;
Runs the body, then repeats until the condition is true. The body always runs at least once.
REPEAT i := i + 1; UNTIL i >= 10 END_REPEAT;
Leaves the innermost FOR, WHILE or REPEAT loop at once.
IF found THEN EXIT; END_IF;
Skips the rest of this loop pass and starts the next one.
IF NOT steps[i] THEN CONTINUE; END_IF;
Leaves the current program, FUNCTION or FUNCTION_BLOCK for this scan.
IF NOT enable THEN RETURN; END_IF;
Operators
TRUE only when both sides are TRUE. & is the same.
Y1 := X1 AND X2;
TRUE when either side is TRUE.
Y1 := X1 OR X2;
TRUE when the two sides differ.
Y1 := X1 XOR X2;
Turns TRUE into FALSE and FALSE into TRUE.
Y1 := NOT X1;
Compare two values: =, <> (not equal), <, >, <=, >=. The result is BOOL. Comparisons bind tighter than AND / OR.
Y1 := AX1 > 2048; Y2 := (count >= 5) AND X1;
+ − * / on numbers; MOD gives the remainder, ** raises to a power. / gives a decimal result — storing it in an INT drops the decimals. Dividing by 0 gives 0.
pct := AX1 * 100 / 4095; r := count MOD 10;
Timers, counters, edges
Q turns ON after IN has been TRUE for PT. ET is the elapsed time. IN going FALSE resets it.
Inputs: IN, PT Outputs: Q, ET — declare an instance in VAR (name : TON;), call it once every scan, read outputs as name.Q.
tmr1(IN := X1, PT := T#2s); Y1 := tmr1.Q;
Q is ON while IN is TRUE and stays ON for PT after IN goes FALSE.
Inputs: IN, PT Outputs: Q, ET — declare an instance in VAR (name : TOF;), call it once every scan, read outputs as name.Q.
tmr1(IN := X1, PT := T#2s); Y1 := tmr1.Q;
When IN goes TRUE, Q gives one pulse of exactly PT, however long IN stays on.
Inputs: IN, PT Outputs: Q, ET — declare an instance in VAR (name : TP;), call it once every scan, read outputs as name.Q.
pls1(IN := X1, PT := T#1s); Y1 := pls1.Q;
CV counts up on each rising edge of CU. Q turns ON when CV ≥ PV. R resets CV to 0.
Inputs: CU, R, PV Outputs: Q, CV — declare an instance in VAR (name : CTU;), call it once every scan, read outputs as name.Q.
cnt1(CU := X1, R := X2, PV := 3); Y1 := cnt1.Q;
CV counts down on each rising edge of CD. LD loads PV into CV. Q turns ON when CV reaches 0.
Inputs: CD, LD, PV Outputs: Q, CV — declare an instance in VAR (name : CTD;), call it once every scan, read outputs as name.Q.
cnt1(CD := X1, LD := X2, PV := 3); Y1 := cnt1.Q;
CV counts up on CU and down on CD (never below 0). QU when CV ≥ PV, QD when CV is 0. R resets, LD loads PV.
Inputs: CU, CD, R, LD, PV Outputs: QU, QD, CV — declare an instance in VAR (name : CTUD;), call it once every scan, read outputs as name.Q.
cnt1(CU := X1, CD := X2, R := X3, PV := 10); Y1 := cnt1.QU;
Q is TRUE for exactly one scan when CLK goes FALSE → TRUE.
Inputs: CLK Outputs: Q — declare an instance in VAR (name : R_TRIG;), call it once every scan, read outputs as name.Q.
rise1(CLK := X1); IF rise1.Q THEN count := count + 1; END_IF;
Q is TRUE for exactly one scan when CLK goes TRUE → FALSE.
Inputs: CLK Outputs: Q — declare an instance in VAR (name : F_TRIG;), call it once every scan, read outputs as name.Q.
fall1(CLK := X1); Y1 := fall1.Q;
Functions
Removes the sign: ABS(-5) = 5.
err := ABS(sp - pv);
Square root of a number.
flow := SQRT(dp) * k;
The smallest of two or more values.
low := MIN(AX1, AX2);
The largest of two or more values.
high := MAX(AX1, AX2);
LIMIT(MN, IN, MX) keeps IN between MN and MX.
pct := LIMIT(0, raw * 100 / 4095, 100);
Cuts off the decimals: TRUNC(2.9) = 2, TRUNC(-2.9) = -2.
whole := TRUNC(level);
Rounds to the nearest whole number; an exact .5 rounds up (2.5 → 3, −2.5 → −2).
n := ROUND(level);
EXPT(base, exponent) — the same as base ** exponent.
area := EXPT(r, 2) * 3.14159;
SEL(G, IN0, IN1) returns IN0 when G is FALSE and IN1 when G is TRUE.
AY1 := SEL(X1, 0, 4095);
SHL(v, n) shifts the bits of v left by n places; SHR shifts right.
mask := SHL(1, bitNo);
REAL_TO_INT (rounds), INT_TO_REAL, BOOL_TO_INT (TRUE → 1), INT_TO_BOOL (non-zero → TRUE).
n := REAL_TO_INT(level); on := INT_TO_BOOL(count);
Addresses
Discrete inputs (BOOL). Scenes write them; toggle them in the X or I/O panel while testing.
Y1 := X1;
Discrete outputs (BOOL) you write with := . Scenes read them.
Y1 := X1 AND NOT X2;
Memory bits shared with the ladder — like control relays.
C1 := X1 OR C1;
Analog inputs, 0–4095 (INT).
pct := AX1 * 100 / 4095;
Analog outputs, 0–4095 (INT). Values are clamped to that range.
AY1 := 2048;
The ladder timer and counter done bits — read-only in ST. For timing and counting in ST use TON / CTU blocks.
Y2 := T1;
Read-only status bits: SB1 always ON, SB3 first scan, SB5/SB6/SB7 0.5 s / 1 s / 1 min clocks, SB9–SB12 fault flags (SB9–SB11 can be cleared with := FALSE).
IF SB3 THEN count := 0; END_IF; Y1 := SB6; // 1 Hz flasher
Read-only status words: real-time clock (SW1–SW7), scan time and count, error code SW17 (clear with := 0) and rung SW18.
Y1 := (SW5 >= 7) AND (SW5 < 18);
The ladder D registers, read and written here as INT (wraps at ±32767). The index can be any expression.
D[10] := AX1; FOR i := 1 TO 5 DO D[100 + i] := 0; END_FOR;
Values
The two BOOL values. TRUE is 1 / ON, FALSE is 0 / OFF.
Y1 := TRUE;
A duration: T#250ms, T#2s, T#1m30s, T#1h, T#1d. Stored in milliseconds.
tmr1(IN := X1, PT := T#1m30s);