Virtual PLC – Structured Text, IF, CASE and simulation
Build simulation logic step by step: insert IF and CASE blocks at the cursor, add commands and test execution with Start, Pause, Continue and Stop.
ST: start here
The virtual PLC uses Structured Text (ST) exclusively. It implements a limited IEC 61131-3-style subset with additional simulation commands. Variables come from the PLC table; no variable declarations are needed in the program.
- Open the virtual PLC properties and choose Configure V-PLC logic (V-SPS-Logik einstellen in the German interface).
- New ST program creates
PROGRAM V_SPS, an empty line andEND_PROGRAM. This replaces the editor text. Ctrl+Z undoes the replacement. - Place the cursor on an empty line inside the program. To add an independent block, choose a line between existing blocks.
- Choose Insert command → New IF block or CASE … OF (Select Case), fill in the fields and check the preview.
- After Insert, the cursor is in the first empty action line. Add the required actions there.
- Choose Apply in the main editor, confirm the object properties and save the project.
Insert initially creates a template. Empty IF or CASE branches are not executable programs. The dialog's Name field becomes a // comment; it does not declare a variable.
Old texts using NETWORK and END_NETWORK are no longer accepted in the editor. Keep a copy if needed and create a new ST program; there is no automatic conversion.
What does the virtual PLC do?
In play mode, the virtual PLC repeatedly checks conditions and changes assigned simulation values. The first example means: While enable is on, copy the setpoint into the numeric display.
- Project with no communication driver selected: the virtual PLC uses local simulation values. No external test server is required. This also supports local demo exercises.
- Project with a communication driver selected: the local project simulator must be enabled and online. Used symbols must belong to the local simulator; arbitrary remote PLC connections do not meet this requirement.
F5 opens play mode. Then click the virtual PLC's own Start button. Selecting a play startup page does not automatically start its virtual PLC. Examples: demo crossing and tank demo.
Insert the virtual PLC
Select Structure → Virtual PLC (V-SPS). Open object properties. The example uses X = 20, Y = 130, width = 420 and height = 170. Text / initial value contains “Simulationslogik”.
Open V-SPS-Logik einstellen. The initial “SET Ziel fehlt BIT 0” text belongs to the unconfigured object.
Insert command templates
The dialog contains 13 templates. Symbol opens the variable list with search and filters. Double-click to fill the enable, source or target field. Insert PLC symbol in the main editor inserts the symbol name at the cursor.
Choose the insertion point
New IF and CASE blocks are inserted at the current cursor position. Before opening the dialog, place the cursor on an empty line where the block should go. New blocks are no longer automatically moved before END_PROGRAM. In a completely empty editor, a program wrapper is added automatically.
For an action, place the cursor inside the intended IF or CASE branch. An empty line is filled with the action; if the line already contains text, a new line is inserted below it. To insert WAIT before an existing action, first create an empty line above that action. Insertion checks do not replace full-program validation with Apply.
| Template | Fields and generated ST |
|---|---|
| New IF block | Name and enable symbol; generates IF Symbol = 1 THEN, an empty action line and END_IF;. |
| CASE … OF (Select Case) | Name and source variable used as selector; generates branches 0:, 1:, ELSE and END_CASE;. |
| WRITE | Target and value: Target := 25;. Constant assignments also work inside IF branches. |
| INC / DEC | Target, step, interval in ms, minimum and maximum. Defaults: 1, 500 ms, 0 to 100. |
| INC_ONCE / DEC_ONCE | Target, step and limits; one step per activation, with no time field. |
| WAIT | Time in ms: WAIT(T#500ms);. Place before a suitable action. |
| RND | Target, minimum and maximum: Target := RND(1, 10);. Use inside an IF branch. |
| MOVE | Source and target: MOVE(Source, Target);. |
| COPY_WORDS | Source, target and number of contiguous 16-bit words. |
| SET / RESET | Target and bit number: generates Target := TRUE; or Target := FALSE;. Bit −1 addresses a whole BOOL symbol; a bit number addresses a word bit. |
Fill in every visible field. Action templates check numbers, target types and write access; Apply validates the complete program. Defaults are suggestions: adjust limits and intervals for your project.
Enter the example program
The initial command catalog contains placeholder symbols. Replace all of that sample text with:
PROGRAM Sollwert_Kopieren
IF Anlage_Freigabe = 1 THEN
Anlage_Zahlenwert := Anlage_Sollwert;
END_IF;
END_PROGRAMThese existing communication-table symbols belong to the local Modbus_Testserver in this project and use UINT16:
| Symbol | Address |
|---|---|
| Anlage_Freigabe | 40004 |
| Anlage_Sollwert | 40007 |
| Anlage_Zahlenwert | 40008 |
Use SPS-Symbol einfügen to select an existing project symbol and insert it at the cursor. This exercise needs no new symbols.
Click Apply to check syntax and symbol references. Apply the object properties too, then save the project.
Each command explained
| Command / symbol | Meaning |
|---|---|
| PROGRAM Sollwert_Kopieren | Begins the program. Sollwert_Kopieren is its chosen name. |
| IF | Introduces a condition: “if”. |
| Anlage_Freigabe = 1 | Checks whether the enable symbol equals 1. The equals sign compares; it does not change a value. |
| THEN | Introduces the instruction to execute when the condition is true. |
| Anlage_Zahlenwert := Anlage_Sollwert; | Copies the value on the right into the destination on the left. Anlage_Sollwert is the source; Anlage_Zahlenwert is the destination. The source is unchanged. |
| := | Assignment: write the right-hand value into the left-hand destination. |
| ; | Ends the statement, like a period ending a sentence. |
| END_IF; | Ends the if-then block. |
| END_PROGRAM | Ends the program text. |
Remember: = compares; := assigns. Indentation improves readability. There is no ELSE branch here: when enable is 0, no assignment occurs and the numeric value is retained.
The running virtual PLC checks the condition repeatedly. Enable permits ongoing copying here; it is not merely a one-shot trigger.
Prepare the test controls
Add an enable control for Anlage_Freigabe that switches between 0 and 1. Use a value input for Anlage_Sollwert and a numeric display for Anlage_Zahlenwert.
For the Modbus exercise, enable the local project simulator and wait for online values. In a project with no driver selected, use local simulation values instead. Start play mode with F5. Prepare enable 0, setpoint 60 and numeric value 50. Set the initial numeric value with an assigned input control or in the local simulator if needed. The virtual PLC initially shows “Gestoppt” (stopped).
Test the logic step by step
| Step | Action | Result |
|---|---|---|
| 1 | Click Start, keeping enable at 0 | Runs cyclically with no condition met; numeric value stays 50. |
| 2 | Switch enable to 1 | Setpoint 60 is copied; numeric value becomes 60. |
| 3 | Enter setpoint 80 and confirm with Enter | With enable active, numeric value follows to 80. |
| 4 | Switch enable to 0, then change setpoint to 60 | Numeric value stays 80; the condition is no longer met. |
“Keine Bedingung erfüllt” (no condition met) is expected here. The virtual PLC is running and waiting for enable. “RUN · Aktiv: ST-Netzwerk 1: MOVE” indicates the copy operation. MOVE means copying a value here.
Start, Pause, Continue and Stop
- Start: begins cyclic execution.
- Pause: pauses execution and sets the assigned outputs to LOW/0. Internal values and the sequence position remain unchanged.
- Continue: resumes from the current state without catching up the pause time. The program sets its outputs again.
- Stop: ends execution and sets the assigned outputs to LOW/0. Internal values remain unchanged.
In the V-PLC editor, use Set to 0 on Pause/Stop to identify actuator outputs. Add known symbols with Select signal to switch off …; separate entries with semicolons. A whole symbol resets the entire value, while Symbol.0 resets only bit 0. Other bits remain unchanged. These outputs always switch off on Pause/Stop; there is no hold-output option.
Tank example: TANK_VALVE; TANK_PUMP.0. The valve closes and the pump run bit clears; level, sequence phase and pump status bits remain unchanged. Do not assign internal values such as the tank level as outputs. Existing SET/ASSIGN bit outputs are suggested for older projects; explicitly assign numeric actuator outputs. A page change stops the running V-PLC and also clears its outputs. Manual inputs remain available after Pause/Stop.
Continue differs from a new Start: Continue preserves timer and one-shot execution state. Stop ends that state; a new Start begins fresh execution. Stored process and step values are not all reset to their PLC-table initial values.
What does “Select signal to switch off …” do?
The button opens the PLC symbol picker. Select an existing symbol and apply the selection. Its name is appended to Set to 0 on Pause/Stop. This neither creates a new variable nor inserts a command into the ST program. Apply and save the project to store the assignment with the virtual PLC object.
AMPEL_NS; AMPEL_OW means resetting both complete values to 0 on Pause or Stop. AMPEL_NS.0 clears only bit 0 and preserves the other bits. List the output signals that should switch off. Keep internal sequence values such as PHASE out of the list if their values should be retained.
The output list must contain existing symbol names only. Remove question marks or other invalid entries. Example: AMPEL_NS; AMPEL_OW. Cancelling the symbol picker leaves the list unchanged.
Choose between several cases: CASE
Create CASE with the command dialog
- Place the cursor on an empty line inside the program, outside any existing IF or CASE block.
- Choose Insert command → CASE … OF (Select Case).
- Enter a name for the comment. Use Source variable → Symbol to select an integer selector, such as Anlage_Betriebsart.
- Check the preview and choose Insert. The block appears at the cursor; the cursor moves to the empty action line under
0:. - Under
0:, use WRITE to put 0 in the target. Under1:, use MOVE to copy the setpoint to the target. Under ELSE, enter a fallback action, such as WRITE with value 0. - Edit the text to add further values, lists or ranges if needed. Every branch needs an action. Choose Apply and save the project.
The template initially provides 0, 1 and ELSE. The following complete exercise adds the list 2, 3 and the range 10..20:
The construct known as Select Case in other languages is written CASE … OF … END_CASE here. IF checks a condition; CASE selects a branch matching a symbol value. Think of a selector switch with several positions.
This second exercise uses Anlage_Betriebsart at address 40003 as its selector. Replace the previous program entirely for this test, so two programs do not write the same numeric destination:
PROGRAM Betriebsart_Auswahl
CASE Anlage_Betriebsart OF
0:
Anlage_Zahlenwert := 0;
1:
Anlage_Zahlenwert := Anlage_Sollwert;
2, 3:
Anlage_Zahlenwert := 50;
10..20:
Anlage_Zahlenwert := 80;
ELSE
Anlage_Zahlenwert := 0;
END_CASE;
END_PROGRAM| Syntax | Plain-language meaning |
|---|---|
| CASE Anlage_Betriebsart OF | Read the operating mode and select its matching case. |
| 0: | For value 0, write 0 to the numeric value. The colon introduces the case instructions. |
| 1: | For value 1, copy the current setpoint. |
| 2, 3: | A list: either 2 or 3 writes 50. |
| 10..20: | A range: every integer from 10 through 20 inclusive writes 80. |
| ELSE | Fallback for any unlisted value, such as 4 or 21. This example writes 0. |
| END_CASE; | Ends the selection. No additional BREAK command is needed. |
These branches are not successive steps of a sequence. The current operating-mode value selects the branch. This program does not change that value itself and does not include an enable condition.
Try it: Stop the virtual PLC, replace and apply the program, save and restart simulation. Keep Anlage_Sollwert at 60. Using an input control for Anlage_Betriebsart or the local simulator at 40003, set the value to 0, 1, 2, 3, 10, 20 and 21 in turn. Expected numeric outputs are 0, 60, 50, 50, 80, 80 and 0.
Enable inside a CASE branch
Operating mode 1 copies the setpoint only while enable is active. With enable off or another mode selected, the target is 0. Replace the previous program for this exercise:
PROGRAM Freigabe_im_CASE
CASE Anlage_Betriebsart OF
0:
Anlage_Zahlenwert := 0;
1:
IF Anlage_Freigabe = 1 THEN
Anlage_Zahlenwert := Anlage_Sollwert;
ELSE
Anlage_Zahlenwert := 0;
END_IF;
ELSE
Anlage_Zahlenwert := 0;
END_CASE;
END_PROGRAMSupported IF and CASE structures
- Independent IF blocks: exactly one action per THEN or ELSE branch, optionally preceded by WAIT. Add independent IF blocks for additional independent actions.
- CASE: an integer selector symbol, single values, lists and ranges. Every defined branch needs at least one action. Remove unused branches, including ELSE, completely or fill them with a useful action.
- Multiple actions: supported inside a CASE branch. One level of IF … THEN … ELSE … END_IF; is also supported there. Independent IF and CASE blocks may follow each other in one program.
- Nesting: no CASE inside CASE, no CASE inside a normal IF and no multiply nested IF. ELSIF and loops are outside the supported subset.
- RND: supported in a normal IF branch, including next to an independent CASE block. Currently unsupported directly in CASE branches or their inner IF blocks.
- Comparisons: a symbol or bit on the left, a constant number on the right, or TRUE/FALSE for BOOL/bit references. Comparisons between two symbol values are unsupported.
Use distinct, non-overlapping CASE values. Mixed AND and OR conditions are evaluated from left to right by this virtual PLC; do not assume the operator precedence used by another language. Start with only AND or only OR in each condition. Nested parentheses are unsupported.
Inserting at the cursor still requires a supported program structure. For example, insert a second independent IF block after the first END_IF;. Keep actions inside PROGRAM … END_PROGRAM.
Sequences: a CASE program must explicitly change its step variable to advance. A selector change can affect later actions in the same execution pass; a whole CASE branch is not processed as an indivisible unit here. For a first exercise, set the selector with a control and leave it unchanged inside CASE.
Command reference
The ? button next to the dialog heading opens this guide directly. The code editor uses the full width; validation messages appear below it.
The following sections explain the supported commands. Source, Target and Symbol are placeholders for existing project symbols. Replace them with your symbol names. Ellipses stand for omitted program text.
PROGRAM, IF, CASE and logical operators
| Command | Meaning |
|---|---|
| PROGRAM Name / END_PROGRAM | Begins and ends a program. Choose its name. |
| IF … THEN … ELSE … END_IF; | Executes the THEN branch when the condition is true, otherwise the optional ELSE branch. |
| CASE … OF … ELSE … END_CASE; | Selects a branch by value; the CASE section covers individual values, lists and ranges. |
| AND | Both conditions must be true. |
| OR | At least one condition must be true; both may be true. |
| NOT | Reverses the truth value of the following condition. |
IF Anlage_Freigabe = 1 AND Anlage_Sollwert >= 60 THEN
Anlage_Zahlenwert := Anlage_Sollwert;
END_IF;This copies only when enable is 1 and setpoint is at least 60. OR would allow either condition to suffice. NOT Anlage_Freigabe = 1 means enable is not equal to 1.
Use simple conditions to start. The editor implements a supported ST subset; do not assume arbitrary nesting and parentheses from other PLC examples are accepted.
The six comparisons
| Operator | Meaning | Example at setpoint 60 |
|---|---|---|
| = | equal | Anlage_Sollwert = 60 → true |
| <> | not equal | Anlage_Sollwert <> 60 → false |
| > | greater than | Anlage_Sollwert > 50 → true |
| >= | greater than or equal | Anlage_Sollwert >= 60 → true |
| < | less than | Anlage_Sollwert < 60 → false |
| <= | less than or equal | Anlage_Sollwert <= 60 → true |
A comparison produces true or false without writing a value. = differs from assignment :=.
TRUE, FALSE and individual bits
TRUE means true/on; FALSE means false/off. Use these with BOOL symbols or individual bits. A word can contain several independent on/off flags.
Symbol.0 addresses bit 0, not a decimal fraction. Bits 0, 1 and 2 have weights 1, 2 and 4. A word containing 5 therefore has bits 0 and 2 set.
The reference lists bits 0 through 31. Use only bits supported by the actual type: UINT16 has bits 0 through 15; a 32-bit word has bits 0 through 31.
IF Anlage_Freigabe = 1 THEN
Anlage_Status.0 := TRUE;
ELSE
Anlage_Status.0 := FALSE;
END_IF;This alternative example sets bit 0 of Anlage_Status (40015) while enabled and clears it otherwise, preserving the other bits. Without ELSE, this logic would not explicitly clear a previously set bit when the condition becomes false.
Assign and change values
| Syntax | Behavior and use |
|---|---|
Target := Source; | Copies a symbol value. Source and target must have the same data type; use whole symbols without bit access. |
Target := 25; | Writes a constant to an integer target. Supported in both IF and CASE branches. The WRITE template generates this syntax. |
Target := Target + 1;Target := Target - 1; | Enter manually inside a CASE branch: one counting step per activation. The right-hand symbol must be the same target; the step must be a positive integer. |
Use INC_ONCE or DEC_ONCE for one counting step in a normal IF branch. Use INC or DEC for repeated counting. Arbitrary arithmetic expressions are not supported.
PROGRAM Fester_Wert
IF Anlage_Freigabe = 1 THEN
Anlage_Zahlenwert := 25;
END_IF;
END_PROGRAMWRITE maintains its configured value while the condition is active. A manual change can therefore be overwritten. Stop the virtual PLC or turn off the relevant condition before changing the value manually.
WAIT: delay an action
WAIT(T#2s); means two seconds; WAIT(T#500ms); means 500 milliseconds, half a second. T# identifies a duration, s means seconds and ms means milliseconds.
PROGRAM Uebung
IF Anlage_Freigabe = 1 THEN
WAIT(T#2s);
Anlage_Zahlenwert := Anlage_Sollwert;
END_IF;
END_PROGRAMEnable must remain 1 for two continuous seconds before copying. If it becomes 0 earlier, the timer resets. WAIT delays this conditional network’s action rather than blocking the entire program. Here it acts as an on-delay, not a periodic two-second delay for every later source change.
This pattern is for the illustrated copy action. Use the INC/DEC interval parameter to set the delay between counting steps.
The time must be between 0 and 3,600,000 ms (one hour). Use WAIT before SET/RESET, a value assignment, MOVE or COPY_WORDS; RND can also follow it in a normal IF. WAIT does not delay counting commands: use the INC/DEC interval parameter instead. Inside CASE, WAIT applies only to the next action; add a separate WAIT for each action that needs one.
INC and DEC: count repeatedly
INC(Target, 1, T#500ms, 0, 100); increases a target by one every 500 ms while its condition is true.
| Parameter in order | Meaning |
|---|---|
| Target | Symbol to modify. |
| 1 | Step size. |
| T#500ms | Time between steps: half a second. |
| 0 | Lower limit. |
| 100 | Upper limit. |
PROGRAM Uebung
IF Anlage_Freigabe = 1 THEN
INC(Anlage_Zahlenwert, 1, T#500ms, 0, 100);
END_IF;
END_PROGRAMStarting at 50, successive timed steps produce 51, 52, 53 … up to 100. The value stops at the limit rather than wrapping to 0. With enable 0, counting stops and the last value remains.
DEC(Target, 2, T#1s, 0, 100); counts down by two each second, for example 50, 48, 46 … to 0. INC and DEC are intended for integer counting.
Execution is checked approximately every 100 ms. Millisecond durations do not promise millisecond-accurate real-time control. Time spent paused is not treated as counting time to catch up later.
INC_ONCE and DEC_ONCE: once per activation
INC_ONCE(Target, 1, 0, 100); increases by one once when its condition activates. Its four arguments are target, step size, minimum and maximum; there is no time argument.
PROGRAM Uebung
IF Anlage_Freigabe = 1 THEN
INC_ONCE(Anlage_Zahlenwert, 1, 0, 100);
END_IF;
END_PROGRAMStarting at 50, turning enable on produces 51. Leaving it on keeps 51. Turning it off and on again produces 52. A condition already true at program startup can also trigger the first execution.
DEC_ONCE(Target, 2, 0, 100); decreases by two once per activation. ONCE does not mean once for the program’s entire lifetime: after a false condition, a new activation permits another step.
MOVE: explicitly copy a value
MOVE(Source, Target); copies from source to target. The source is first and destination second.
PROGRAM Uebung
IF Anlage_Freigabe = 1 THEN
MOVE(Anlage_Sollwert, Anlage_Zahlenwert);
END_IF;
END_PROGRAMThis corresponds to Anlage_Zahlenwert := Anlage_Sollwert; in our first example. MOVE does not remove or alter the source. Later source changes are also copied while the condition remains true.
Source and target must have the same data type; the source must supply a valid value and the target must be writable. Use whole symbols, not bit copies with Symbol.0.
Full version with a suitable communication driver: the current demo build cannot resolve word ranges because its drivers are disabled. For local demo exercises, copy individual symbols with MOVE instead.
COPY_WORDS: copy several words
COPY_WORDS(Source, Target, 3); copies three contiguous 16-bit words. Source and target identify the first symbol in each range. The count must be between 1 and 128.
- Both ranges belong to the same device and driver.
- Every word must exist in the PLC table. The command does not create missing symbols.
- Corresponding source and target words must have the same 16-bit data type, and every target must be writable.
- Supported ranges are consecutive Modbus registers and S7 words at two-byte intervals within the same area or data block. Arbitrary OPC UA node lists do not form a word range.
Example table layout: sources at 40020, 40021 and 40022; targets at 40030, 40031 and 40032, all UINT16 on the same local device. Select the names of the first symbols in the template and enter count 3. Test with three different source values, then check all three targets after activation.
For one value, MOVE is simpler. COPY_WORDS also uses the local simulation prerequisites.
RND: generate a random integer
Available from version 2.61.514: Target := RND(1, 10); returns an integer between 1 and 10, including both bounds.
IF Anlage_Freigabe = 1 THEN
Anlage_Zahlenwert := RND(1, 10);
END_IF;A value is generated once per activation of the condition. No further random assignment occurs while the condition remains true. When it becomes false and then true again, a new draw occurs; the same value may occur again. A preceding WAIT delays the assignment.
Both bounds must be constant integers within the writable integer target’s data type range. The lower bound must not exceed the upper bound; equal bounds are allowed. Bit targets and floating-point targets are not supported. RND is a simulation extension, not a variable delay.
Use RND in a normal IF branch. A CASE branch, including an IF nested within it, currently does not accept RND. An independent IF block next to CASE can perform the random assignment.
Position values for image movement
The local V-PLC can update dedicated X/Y position symbols cyclically. Assign them to an image under Image movement. One unit equals one pixel of offset from the saved editor position. Your program sequence and timing determine speed and resetting.
Use RND(from, to) for random integers, including both limits. A continuously true condition does not generate a new random value on every cycle. Stop an automatically writing V-PLC before entering manual values, otherwise the next program step may overwrite the input.
For a simple movement test, use existing integer symbols Freigabe and Auto_Move_Y, and assign Auto_Move_Y to the image Y movement:
PROGRAM Bildbewegung
IF Freigabe = 1 THEN
INC(Auto_Move_Y, 10, T#500ms, 0, 600);
END_IF;
END_PROGRAMWith enable 1, the offset increases by 10 pixels every 500 ms up to 600. With enable 0, the value stays unchanged. To reset it, stop first and set the position value to 0.
Troubleshooting
| Observation | Check |
|---|---|
| Block inserted in the wrong place | Press Ctrl+Z, place the cursor on the intended empty line inside PROGRAM … END_PROGRAM and insert the template again. |
| “Place the cursor inside an IF block or CASE branch first” | Close the dialog and select an action line after THEN or after a CASE label ending with a colon. |
| Empty branch / missing action | Fill the IF action and every defined CASE branch; remove unused CASE branches completely. WAIT alone is not an action. |
| Error on Apply | Read the message. Check symbol names, write access, data types, :=, semicolons and END_IF;/END_CASE;. Replace placeholders and check the supported ST subset. |
| “Simulator not online” | With drivers selected, enable the local project simulator and wait for online values. Without a selected driver, check that all used local symbols have valid simulation values. |
| Value remains unchanged | Check play mode and the virtual PLC's Start button. Does the IF condition or CASE selector match? Check symbol assignments. “No condition met” means no action is currently enabled. |
| Value is overwritten again | Check other IF/CASE blocks and other virtual PLCs writing the same target. Stop automatically writing logic for manual tests. |
| RND fails inside CASE | Move RND to an independent IF block with its own condition. The action preview alone does not establish validity inside CASE. |
| Output stays on after Pause/Stop | Check “Set to 0 on Pause/Stop”. Only outputs explicitly assigned there are reset. |
Further help: keyboard shortcuts and the PLC table. For support, include help ID OBJ-VPLC, program version, complete ST text, symbol assignments and runtime status.