PLC Programming Plan States, Subroutines, and Initialization

David Krause15 min read
HMI ProgrammingSiemensTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Overview: Why PLC Programming Is Not "Just Code"

PLC programming is frequently taught backwards. Most vendor-sponsored courses and on-the-job training concentrate on the trivia associated with specialized PLC functions: which bit to set on a TON timer, which data block holds a PID block's instance DB, which input word corresponds to the analog card. That trivia is necessary, but it is not programming. It is the vocabulary. Writing sentences requires vocabulary; the sentences still must follow grammar, structure, and intent.

The discipline below treats PLC programming as a structured-engineering problem with the same shape regardless of the controller family — Siemens SIMATIC S7-1200 / S7-1500, Mitsubishi MELSEC iQ-R with GX Works3, Allen-Bradley ControlLogix with RSLogix 5000 / Studio 5000, or any IEC 61131-3 platform. The vocabulary changes; the grammar does not.

Working rule: roughly 30% of project time is spent writing code. The other ~70% is spent thinking, describing, and validating the state model. Skip the thinking phase and a two-week project reliably becomes a failed six-month project.

Prerequisites Before You Touch a PLC

Before opening any programming software, build a foundation in general program structure. A one-semester C or Fortran course is sufficient; you do not need to become a software engineer, but you do need to internalize the following concepts:

  • Main program and subroutines (or functions / function blocks). A controller scans one main routine repeatedly; the work is divided into subroutines that are called, either conditionally or by event, from the main. See the IEC 61131-3 program organization unit (POU) model in IEC 61131-3:2013.
  • Database / tag memory. Discrete tags, word tags, retentive vs non-retentive, and instance data blocks (Siemens) or program tags (Allen-Bradley). Each variable lives somewhere and survives scan cycles or does not.
  • Fixed-point arithmetic. Real-world signals are scaled integers. A 4–20 mA loop at 12.00 mA is a raw 12,000 count, not 12.00. Plan the scaling before connecting anything.
  • Looping and conditional branching. FOR/WHILE/IF and the PLC equivalents (LOOP, JMP, --( )-- contacts).
  • Debugging with watch tables, cross-references, and single-step. You must be able to stop the scan, observe a tag, force a value, and resume. On Siemens this is the Watch and Force Table; on Allen-Bradley it is the Data Monitor within RSLogix 5000.
  • Reusable code. A function block (FB) with an instance DB in Siemens terminology, or an Add-On Instruction (AOI) in Allen-Bradley terminology. The same scale_linear block should not be rewritten four times.

Once these concepts are in place, the PLC's specialized functions — bit manipulation, timers, counters, indirect addressing, HSC, PTO — become a matter of looking up the syntax, not inventing the architecture.

The 70/30 Rule: Where Project Time Actually Goes

Empirical observation across hundreds of industrial projects converges on the same ratio:

Phase Activity Share of effort
1 Conceptual essay / functional description ~20%
2 Comment-only skeleton in the editor ~25%
3 I/O cross-check with mechanical and electrical design ~15%
4 Writing the actual code (LD, FBD, ST, SCL) ~30%
5 Desk simulation, FAT, SAT, field commissioning ~10%

Pressure to compress Phase 1 and 2 produces monolithic ladder files exceeding one hundred pages. These files cannot be debugged by anyone except the original author, fail the moment a sensor is replaced, and routinely get scrapped and rewritten. A disciplined three-page subroutine per POU is the practical ceiling for human readability.

Day 1: Write the Design Essay Before Any Code

Before opening TIA Portal, GX Works3, Studio 5000, or any other editor, write a plain-text essay that describes the block of code in unambiguous prose. Save it as a project document. The essay contains:

  1. Startup sequence. What must be true (or made true) for the machine to leave the safe state and enter Run.
  2. Run sequence. The state transitions during normal operation, with the conditions that drive each transition.
  3. Shutdown sequence. What must be parked, de-energized, or vented before power is removed or E-Stop is acknowledged.
  4. Fault handling. Detected faults, latched conditions, and the recovery path that does not require a power cycle.
  5. List of all machine states altered by this block. Valve positions, carriage positions, part counts, process durations, weights, temperatures, speeds, interlock flags.
  6. List of all input and output variables. Symbolic names, addresses, signal types (24 VDC discrete, 4–20 mA, encoder, PTO).
  7. Memory requirements. Approximate count of bool, int, real, and string tags; whether the data must be retentive.

With this essay in hand, walk it through with the mechanical designer. Confirm every motor, valve, limit switch, prox, photo-eye, and pressure transducer that the code expects is actually present on the drawing and physically mounted where the logic assumes. Then walk it through with the electrical designer. Confirm the wire list, the I/O card slotting, and the field-device terminal designations match the symbolic names in the essay.

Why this matters: the essay surfaces I/O gaps while the cost is still a wire change, not a code rewrite six weeks after the panel ships. Discovering a missing home-position sensor during commissioning routinely burns one shift; discovering it during client acceptance testing burns the schedule.

Day 2: Write the Comments Before the Code

Open the PLC editor and create the routine(s) called for in the essay. Drop the essay into the routine header as a multi-line comment. Then write only the comments for every rung or statement. No contacts, no coils, no assignments. The comments must say what the line below them is supposed to do.

This step is iterative. Writing comments forces the following questions:

  • Are the discrete inputs in the right order to express the interlock cleanly, or am I jumping through inverted contacts?
  • Is the timing plausible? Will the cylinder actually reach the end-of-stroke within the timer preset? Will the motor accelerate within the interlock window?
  • Are actuators sized for the duty cycle implied by the sequence?
  • Do I need a third part that the original P&ID did not show?

The same step is also where you estimate scan-time headroom and memory headroom. If the comment block for a routine implies 800 timers and 2,000 booleans on a controller with 256 timers and 8 KB of work memory, that fact must surface during the comment phase — not when the controller throws a resource fault during download.

Editor Format: Why Statement List Plus Comments Reads Cleanly

Ladder logic is a graph, and dense ladder logic does not compress. A hundred-page ladder file is unreadable; a hundred-page statement list with one comment line per executable line is. Siemens STEP 7 / TIA Portal SCL (Structured Control Language, IEC 61131-3 ST) and Mitsubishi MELSEC structured text both lend themselves to this layout. One executable line, one comment line, executed in source order. The graph view of ladder is preserved for the I/O forcing and troubleshooting stage, but the source of truth is the structured text.

Day 3: Write the Code to Match the Comments

With the comments in place, the actual code becomes bookkeeping. For every comment line, drop in the contact, coil, timer, function block call, or assignment that implements it. Because the comment already names the inputs, outputs, and state, the code rarely requires a design decision — only an instruction lookup.

Repeat the three-day cycle for every subroutine. If a routine exceeds roughly three pages of structured text, it is a sign that the block should be decomposed into smaller POUs. Three pages is the human-readable ceiling; beyond that, debugging requires the author in the room.

State Modeling: The Machine as a Finite-State Machine

A PLC controls a machine, and a machine has a finite, named set of states. Common state names that must appear explicitly in the program:

State category Examples Representation in code
Mechanical position CarriageHome, ClampUp, GateClosed Latched boolean in retentive memory
Process variable TankLevel_High, Pressure_OK, TempInRange Compare-result against scaled tag
Sequencer step Step10_Dwell, Step22_Transfer Integer step number in a state register
Mode Auto, Manual, Service, Bypass Enumerated (int) tag
Fault E_StopActive, DriveFault, SensorTimeout Latched bool, cleared by reset routine

A simple drum sequencer in SCL illustrates the pattern:

// State machine for two-station index table
CASE iStep OF
    0:  // Idle - waiting for start and home confirmed
        IF bStart AND bHomeOK AND NOT bEStopActive THEN
            iStep := 10;
        END_IF;

    10: // Extend clamp cylinder
        bClampExtend := TRUE;
        IF bClampExtended THEN
            bClampExtend := FALSE;
            iStep := 20;
            tDwell(IN:=TRUE, PT:=T#500ms);
        END_IF;

    20: // Dwell for clamp confirmation
        IF tDwell.Q THEN
            tDwell(IN:=FALSE);
            iStep := 30;
        END_IF;

    30: // Rotate table to next station
        bTableRotate := TRUE;
        IF bTableInPosition THEN
            bTableRotate := FALSE;
            iStep := 40;
        END_IF;

    40: // Retract clamp
        bClampRetract := TRUE;
        IF bClampRetracted THEN
            bClampRetract := FALSE;
            iStep := 0;
            bCycleComplete := TRUE;
        END_IF;

    ELSE
        // Invalid step - latch fault, force to safe
        bSequenceFault := TRUE;
        iStep := 0;
END_CASE;

Every transition has an explicit guard. The state register (iStep) is retentive so a brief power loss does not leave the machine mid-stroke. The ELSE arm catches the impossible state — a code change, a firmware update, or a corruption event that pushes the step register to a value outside the design set.

PLC Database Initialization: The Most-Common Beginner Mistake

Non-programmers routinely overlook the startup problem. When the PLC powers up cold, every tag is at its default value: bools at FALSE, ints at 0, reals at 0.0. The machine is not in state zero; the machine is in an unknown state. The PLC must determine the actual physical state before it does anything else.

Initialization Subroutine (OB100 on Siemens, SFC_PRG / First Scan on Mitsubishi)

// OB100 - Startup / Warm Restart
// Initialize non-retentive tags and force safe outputs

// 1. Force all discrete outputs to safe state
bMotorRun      := FALSE;
bValveOpen     := FALSE;ClampExtend    := FALSE;
bTableRotate   := FALSE;
bHeaterEnable  := FALSE;

// 2. Clear non-retentive flags
bCycleComplete := FALSE;
bSequenceFault := FALSE;
iCycleCounter  := 0;

// 3. Initialize timers and counters
tDwell(IN := FALSE);
cRejectCount := 0;

// 4. Request physical state scan
// (reads inputs to determine actual machine position)
bInitInProgress := TRUE;
bRequestHomeMove := TRUE;

// 5. State machine returns to Step 0
iStep := 0;

On Siemens S7-1200/1500, OB100 runs once on warm restart and OB102 on cold restart. The retentive bit-memory area (M area marked retentive in the PLC tag table) and retentive instance DBs preserve state across warm restarts. On Mitsubishi iQ-R, the equivalent is the SFC that runs on the first scan after a STOP-to-RUN transition. On Allen-Bradley ControlLogix, the SFC forces routine execution on first scan via the S:FS system bit in RSLogix 5000.

Field-proven rule: never assume that "all outputs off" is the safe state. A horizontal cylinder floating under gravity needs the corresponding directional valve to be in the blocked-center position, not just de-energized. The initialization routine must set the safe-output pattern explicitly, not rely on the controller's power-on default.

Structured Programming: Main Routine Plus Subroutines

The recommended organization for a SIMATIC S7-1500 project:

Block (OB / FB / FC) Purpose Scan behavior
OB1 / MainTask Cyclic main — calls subroutines in fixed order Every scan
OB100 / Startup One-shot initialization at power-on Once
OB82 / OB86 / OB121 Error / diagnostics handlers On event
FC10_Inputs Scale and debounce raw inputs Every scan
FC20_Sequencer State machine Every scan
FC30_Outputs Apply output pattern with interlocks Every scan
FC40_Alarms Alarm detection and latching Every scan
FC50_HMI HMI data exchange Every scan or event-driven
FB100_ScaleAnalog Reusable analog scaling FB with instance DB Called

The main routine becomes a five-line dispatcher:

// OB1 - Main (cyclic)
FC10_Inputs();        // Read and scale all field I/O
FC20_Sequencer();     // Drive state machine
FC30_Outputs();       // Apply validated outputs to terminals
FC40_Alarms();        // Evaluate and latch alarm conditions
FC50_HMI();           // Update HMI tags and request packets

This pattern matches IEC 61131-3 program organization and works identically on Mitsubishi MELSEC, Allen-Bradley ControlLogix (RSLogix 5000 periodic task), and Beckhoff TwinCAT. The vendor-specific trivia lives inside the FCs; the architecture does not change.

Memory and Scan-Time Planning

The essay phase must produce a rough estimate. For a SIMATIC S7-1214C with 100 KB work memory, typical budget:

Resource Estimate Margin Decision
Program code (SCL) 20 KB Sufficient
Instance DBs (FBs) 15 KB Sufficient
Global tags 8 KB Sufficient
HMI buffers 10 KB Sufficient
Scan time (target 20 ms) 8 ms estimated 2.5× Sufficient
Retentive tag count (max 8,192 bits) 1,200 bits 6.8× Sufficient

If the scan-time estimate exceeds the process requirement (a high-speed indexing application may require 1 ms scan), move the time-critical routine to a separate fast task (Siemens OB35 / cyclic interrupt) rather than shortening the main scan to unsafe levels. Validate the scan time after the comment-only phase using the PLC's online diagnostics — on Siemens this is Online & diagnostics → Scan cycle time.

Practical Walkthrough: Three-Stage Conveyor with E-Stop

Concrete application of the three-day method:

Day 1 Essay Excerpt

Conveyor system CV-100 has three motors (M1, M2, M3), three upstream photocells (PE1, PE2, PE3), and one downstream eye (PE4) for jam detection. On startup, all three motors must be off, downstream jam must be clear, and E-Stop circuit must be healthy. Run mode: each motor starts only when the upstream eye is blocked, with a 200 ms interlock to prevent simultaneous inrush. E-Stop must de-energize all motor contactors within 100 ms of circuit opening. Fault conditions: any motor overload, any photocell dark for > 10 s, downstream eye blocked for > 5 s.

Day 2 Comment Skeleton

// --- Conveyor Sequencer ---
// IF E-Stop circuit healthy AND no motor overload AND no jam THEN
//     allow individual motor start logic
// ELSE
//     force all motor contactors off, latch fault

// --- Per-motor start rung ---
// IF upstream eye blocked AND downstream interlock satisfied
//    AND no upstream motor fault AND 200 ms delay elapsed
// THEN start motor, assert running flag

// --- Photocell debounce ---
// Latch photocell-active for 50 ms to ignore vibration-induced transitions

Day 3 Code (SCL extract)

// Global E-Stop and fault gate
bPermitRun := bEStopHealthy
             AND NOT bM1Overload
             AND NOT bM2Overload
             AND NOT bM3Overload
             AND NOT bJamDetected;

FOR iMotor := 1 TO 3 DO
    IF bPermitRun THEN
        // Upstream-eye interlock
        IF aEye[iMotor].Q THEN
            TON_StartDelay[iMotor](IN := TRUE, PT := T#200ms);
            IF TON_StartDelay[iMotor].Q THEN
                aMotorRun[iMotor] := TRUE;
            END_IF;
        ELSE
            TON_StartDelay[iMotor](IN := FALSE);
            aMotorRun[iMotor] := FALSE;
        END_IF;
    ELSE
        aMotorRun[iMotor] := FALSE;
        TON_StartDelay[iMotor](IN := FALSE);
    END_IF;
END_FOR;

Three POUs, ~80 lines of SCL, single comment line per executable line. Field-troubleshootable by the on-shift electrician who was not the author.

Common Pitfalls and Field Caveats

Pitfall Symptom Prevention
No initialization of non-retentive tags Random outputs on power-up, intermittent faults after power dip OB100 routine, explicit safe-output pattern
Monolithic ladder > 100 pages Unmaintainable, single-author dependency Three-page-per-POU ceiling, decompose into subroutines
State machine with implicit transitions Mode confusion after fault, two sequences running in parallel Explicit CASE / state register, ELSE arm catches invalid steps
Mixing retentive and non-retentive without a list Counts and positions reset on warm restart unexpectedly Tag-by-tag retentive table in the design essay
Hard-coded timing instead of timer tags Timing change requires code edit, no live tuning All preset values as HMI-writable tags
Forgetting encoder index pulse after battery backup Drift after power cycle, wrong home position Re-home routine on startup, verify with physical marker
Edge-triggered logic without debounce Phantom counts from contact bounce IEC 61131-3 R_TRIG with 50 ms input filter or hardware debounce

Verification Before Site Commissioning

  1. Desk simulation. Run the project in PLCSIM (Siemens), GX Simulator3 (Mitsubishi), or Studio 5000 Logix Emulator. Force each input transition and confirm the expected output pattern and state register advance.
  2. Scan-time measurement. Read the controller's online scan-time diagnostic and confirm the maximum scan is below the process requirement (typically < 50% of the fastest required response).
  3. Memory utilization. Confirm program and data memory are below 70% of the controller's published capacity. Above 70%, fragmentation and download time become problems.
  4. Cross-reference audit. Generate a cross-reference list and confirm every input address used in code is wired to a real terminal and every output address drives a real load.
  5. Comment completeness check. Every executable line has a corresponding comment. If a line is missing a comment, the code is not finished.
  6. Power-cycle test. With the machine in mid-cycle, remove power. Restore power. Verify the initialization routine brings the machine to a known safe state without operator intervention.

Recommended Learning Path

  1. Complete a one-semester C or Fortran course covering program structure, loops, branches, functions, and debugging.
  2. Read the IEC 61131-3 standard overview to understand POUs, data types, and the program organization model.
  3. Complete the Mitsubishi Programming Basics self-study module for ladder fundamentals.
  4. Work through the Siemens S7-1200 easy book and the matching TIA Portal tutorial project.
  5. Pick a real machine — even a benchtop conveyor or pneumatic pick-and-place — and apply the three-day method end to end.
  6. After delivery, return to the code in six months and try to modify it without the original author present. If you cannot, the structure was insufficient.

FAQ

What is the single biggest beginner mistake in PLC programming?

Skipping PLC database initialization on startup. When the controller powers up, every non-retentive tag is zero — not the value the machine was in before power loss. A dedicated startup OB (OB100 on Siemens, first-scan SFC on Mitsubishi) must set every output to a known safe state and force a physical-state scan before the main logic is allowed to run.

How long should a single PLC routine be?

Roughly three pages of structured text (ST / SCL) or equivalent ladder. Beyond that, debugging requires the original author in the room because the cognitive load exceeds what a shift electrician can hold. If a routine is growing past three pages, decompose it into subroutines or function blocks.

Ladder logic or structured text — which should a beginner learn first?

Learn ladder first because it maps directly to the electrical drawings the mechanical and electrical engineers produce. Then learn structured text (SCL on Siemens, ST on Mitsubishi and Beckhoff) because it scales: state machines, math, and string handling are unreadable in ladder. Most production codebases mix both — ladder for I/O interlock, ST for sequencers and scaling.

How much of project time should be spent writing actual code?

About 30%. The remaining ~70% is split between a written functional description (the design essay), comment-only skeleton in the editor, and I/O cross-check with mechanical and electrical design. Compressing the thinking phase produces monolithic, unmaintainable code and routinely blows the project schedule.

Do I need to learn a general programming language before touching a PLC?

Yes — at least one semester of C, Fortran, Pascal, or similar. The PLC's specialized functions are vocabulary; the program structure (main, subroutines, data, loops, branches, debugging) is grammar. Without the grammar, the vocabulary produces spaghetti. Vendor courses teach vocabulary; you still need the grammar from somewhere.

Back to blog