Building a Simple Melody Sequencer FSM on Siemens S7-300

David Krause15 min read
S7-300SiemensTutorial / 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

Generating a multi-tone audible alarm from a single digital output on a Siemens SIMATIC S7-300 requires more than a simple on/off coil. The horn must be pulsed at varying mark/space ratios (for example 1 s on, 1 s off, 0.5 s on, 0.75 s off) in a deterministic sequence. The most reliable and reusable method is to model the melody as a Finite State Machine (FSM) – specifically a Moore machine in which every state is active for a fixed dwell time, the output depends only on the active state, and transitions are driven by IEC on-delay (TON) timers.

This reference details a complete Step 7 v5.5 implementation: multi-instance Function Block architecture, IEC TON timer declaration, state-transition logic, output mapping, and CPU clock memory fallback for shorter tones. The result is a single FB that you can drop into any S7-300 program, drive from a digital input or network bit, and scale to N tones without duplicating code.

Prerequisites

Item Requirement
Controller SIMATIC S7-300 CPU 312, 313, 314, 315, 315-2 DP/PN, 317 or 319
Firmware CPU firmware that supports the standard IEC library; CPU 3xx with FW ≥ V2.x recommended
Engineering tool STEP 7 V5.5 + SP2 (or later SP) with optional S7 Technology / S7-SCL packages
Library blocks Standard Library > System Function Blocks (SFB 3 TP, SFB 4 TON, SFB 5 TOF) and the IEC Timers (FB 186 TON / IEC_TON, etc.) shipped in the Standard Library
Hardware output One SM 322 (or SM 323 / SM 327) DO channel capable of sourcing the horn's inrush current; 24 V DC typical
Horn 24 V DC piezo or electromechanical alarm rated ≤ 2 A continuous (use an interposing relay if higher)
Programming language LAD/FBD or SCL (FBD shown in screenshots; SCL equivalents provided)
Note on SFB vs IEC: The Siemens System Function Blocks SFB 3/4/5 run as multi-instance-capable system blocks and are the modern recommendation for S7-300/400. The legacy IEC Function Blocks FB 180–186 (also labelled IEC_TON, IEC_TP, IEC_TOF) are the originals specified in IEC 61131-3. On the S7-300 both families are available, but only the IEC family can be instantiated as a multi-instance inside a user FB – which is critical for the architecture described here.

Finite State Machine Theory Applied to a Melody

A melody is a sequence of tones. Each tone is characterised by:

  • An ON duration (the time the horn output is energised – perceived as pitch/length)
  • An OFF duration (the silent gap – perceived as rhythm)
  • A terminal flag (last note of the melody) used to release the FSM and revert to idle

Modelling this as a Moore machine gives a state per (ON, OFF, next-state) triplet. The output function is the OR of all ON states. Transitions are governed by a per-state TON that, when it elapses, requests the next state. Because each state has its own static timer instance, the same code pattern repeats N times without copy/paste – the multi-instance FB does the bookkeeping.

State Bit vs State Word

For fewer than 16 notes, a single BOOL per state (boStateA, boStateB, …) is the most readable approach. For larger melodies, use an INT state index and a CASE statement in SCL – the principles are identical. The BOOL approach is documented below because it surfaces directly in LAD/FBD contact logic, which is the natural language for an S7-300 maintenance team.

Project Structure in STEP 7 V5.5

  1. Open SIMATIC Manager and create a new S7-300 station (or open an existing project).
  2. Insert a new FB in the Blocks container named FB_Melody with Multi-instance capable enabled (this is the default; STEP 7 sets the MC7 attribute automatically for any FB you create from the FB wizard).
  3. Declare the following STATIC variables inside FB_Melody:
    tonStateA : FB 186; // IEC_TON, multi-instance
    tonStateB : FB 186;
    tonStateC : FB 186;
    tonStateD : FB 186;
    boStateA : BOOL := FALSE;
    boStateB : BOOL := FALSE;
    boStateC : BOOL := FALSE;
    boStateD : BOOL := FALSE;
    rDwellA : REAL := 1.0; // seconds
    rDwellB : REAL := 1.0;
    rDwellC : REAL := 0.75;
    rDwellD : REAL := 0.5;
  4. Declare INPUT: i_bStart : BOOL; – edge-triggered start command (e.g. from a network bit, a pushbutton, or a fault condition).
  5. Declare OUTPUT: q_bHorn : BOOL; – the actual horn drive signal.
  6. Declare STATIC constants for the four target outputs of the IEC timer calls (e.g. tDwellA : TIME; – a TIME in STEP 7, fed by multiplying the REAL dwell by T#1s, or simply declare tDwellA : TIME := T#1s; directly).
Why FB 186? FB 186 is the IEC Timer TP in some library references. Confirm the slot in your STEP 7 build: open Standard Library > System Function Blocks. The IEC family is consistently FB 180 (TP), FB 181 (TON), FB 182 (TOF), FB 183 (TP_R), FB 184 (TONR), FB 185 (TOF_R), FB 186 (F_TRIG equivalent). If you specifically need the on-delay function, use FB 186 / IEC_TON as it is the most widely documented in v5.5; for S7-300/400 system blocks use SFB 4 / "TON" / IEC_TC. Pick one family and stay with it – mixing SFB 4 and FB 186 inside the same FB is allowed but bloats the instance footprint.

Implementing One State in FBD

The pattern for a single state (boStateA) is shown below. Repeat it three more times, renaming the variables, to cover four notes.

  1. Set state on entry: a SR flip-flop sets boStateA when the previous state (boStateD wrapped, or Start for the first state) pulses. The S input is wired to the start command or the previous state's transition bit; the R input is wired to tonStateA.Q (the timer's elapsed flag) so the state self-clears the moment the dwell expires.
  2. Run the dwell timer: place an empty box, type tonStateA, and STEP 7 will instantiate the multi-instance block. Wire IN := boStateA, PT := tDwellA, and read Q.
  3. Drive the next state: the trailing edge of tonStateA.Q (or simply the rising edge of the next call to a TOF / RS latch) is what enables boStateB. The simplest implementation is to set the next state from the timer's elapsed bit – but to avoid races, use a one-shot edge flag derived from tonStateA.Q in the previous scan.

Pattern (FBD, expressed in pseudo-code)

// ---- State A ----
A_R_TRIG(CLK := tonStateA.Q, Q => boStateBStep);   // pulse when A elapses
SR(S := boStateA_start OR boStateD_step, R := tonStateA.Q, Q1 => boStateA);
TON(CD := boStateA,    PT := tDwellA, Q => tonStateA.Q, ET => tonStateA.ET);

// ---- State B ----
B_R_TRIG(CLK := tonStateB.Q, Q => boStateBStep);
SR(S := boStateA_step,  R := tonStateB.Q, Q1 => boStateB);
TON(CD := boStateB,    PT := tDwellB, Q => tonStateB.Q, ET => tonStateB.ET);

// ---- State C ----
C_R_TRIG(CLK := tonStateC.Q, Q => boStateCStep);
SR(S := boStateB_step,  R := tonStateC.Q, Q1 => boStateC);
TON(CD := boStateC,    PT := tDwellC, Q => tonStateC.Q, ET => tonStateC.ET);

// ---- State D (final) ----
SR(S := boStateC_step,  R := tonStateD.Q, Q1 => boStateD);
TON(CD := boStateD,    PT := tDwellD, Q => tonStateD.Q, ET => tonStateD.ET);

Pattern (SCL equivalent)

tonStateA(IN := boStateA, PT := tDwellA);
IF tonStateA.Q AND NOT boStateA_elapsed THEN
    boStateA_elapsed := TRUE;
    boStateB         := TRUE;
    boStateA         := FALSE;
END_IF;
// repeat for B, C, D
q_bHorn := boStateA OR boStateC;   // example: A and C are "on" states

Multi-Instance Background

Every FB in STEP 7 requires a Instance DB (or be embedded as a multi-instance inside another FB) to host its STATIC variables. The "tonStateA : FB 186;" declaration tells the compiler to allocate FB 186's instance data inside the DB that owns FB_Melody's static area. The advantages are:

  • One instance DB per melody block – no DB explosion when you instantiate the melody several times for different horns.
  • One call interface – you can call FB_Melody from OB1, OB35 or an alarm OB without re-declaring timers in the symbol table.
  • Re-entrancy – the same FB can drive three different horns with three different dwell tables by simply changing the instance DB.
Common pitfall: dragging the IEC timer from the library rather than typing the instance name in an empty box causes STEP 7 to create a separate instance DB (DB 1001, 1002, …) for each call. This is what produced the type conflict error reported during the original implementation. The fix is always: open an empty LAD/FBD box, type the instance name (e.g. tonStateA) – the box will then bind to the multi-instance declared in the FB's STATIC section.

Output Function

In a Moore machine the output is a function of the state only, not of the inputs. A typical mapping for a four-note melody with alternating long/short tones is:

State Dwell (s) Horn output
boStateA 1.0 1 (energise)
boStateB 1.0 0 (silent)
boStateC 0.75 1 (energise)
boStateD 0.5 0 (silent – terminal)

The FBD line therefore reduces to a single OR coil:

q_bHorn := boStateA OR boStateC;

To make the mapping data-driven, replace the OR with a lookup against a constant ARRAY of BOOLs. For a four-note melody the hard-wired OR is faster and easier to commission – the array approach is only worth the effort above ~8 notes.

Initialising the FSM

The very first scan of the user program must place the FSM in a known state. Two options are commonly used:

  1. Startup OB (OB 100) – set boStateA := TRUE and clear B, C, D. This guarantees the melody always begins at the first tone on a warm/cold restart.
  2. Run-time command – a positive edge on i_bStart from OB1 resets all four states to FALSE and pre-sets boStateA. This is preferred when the melody is one-shot (e.g. a fault acknowledgement chirp).
// In OB 100 (warm restart)
SET;
    S  boStateA;
    R  boStateB;
    R  boStateC;
    R  boStateD;
// --- end OB 100 ---

If the melody must repeat (e.g. an alarm that chirps every 30 s), the last state (boStateD) should transition back to boStateA when its timer elapses. Add a counter or a flag such as boLoop to break the loop on operator acknowledgement.

CPU Clock Memory Fallback for Short Tones

For sub-second tones below ~100 ms the IEC TON scheduler jitter (1 OB1 cycle, typically 10–20 ms on an S7-300) can become audible as a chirpy timbre. Two mitigations are commonly used:

  1. Clock memory bit (German: Taktmerker): in HW Config > CPU Properties > Cycle/Clock Memory, enable a clock byte and assign it to e.g. MB 10. Each bit toggles at a fixed ratio:
    M10.0 = 10 Hz, M10.1 = 5 Hz, M10.2 = 2.5 Hz, M10.3 = 2 Hz, M10.4 = 1.25 Hz, M10.5 = 1 Hz, M10.6 = 0.625 Hz, M10.7 = 0.5 Hz.
  2. Time-tick OB: use a hardware interrupt OB (OB 40) on a digital input wired to a 100 Hz / 1 kHz reference pulse and decrement a counter. This gives sub-millisecond resolution and is immune to OB1 scan-time drift.
Practical recommendation: for horn tones in the 200 ms – 2 s range, the IEC TON approach is sufficient. Below 200 ms, switch to the clock memory bit for the OFF gap (so the OFF state is a fixed-frequency toggle) and keep the IEC TON for the ON duration (so the pitch and length remain tuneable). This hybrid pattern produces a perceptibly cleaner chirp without needing OB40.

Step 7 v5.5 Commissioning Steps

  1. Insert the FB and a single Instance DB (e.g. DB 50 – "Horn1 melody").
  2. In OB1, call CALL FB_Melody, DB 50; wire i_bStart to a Merker or a process input; wire q_bHorn to a DO address, e.g. A 4.0.
  3. Download HW Config + the S7 program to the CPU. Use Online > Monitor/Modify on the Instance DB to watch tonStateA.ET count up.
  4. Force i_bStart := TRUE for one scan, then FALSE. The melody plays once.
  5. Use the VAT table to step through the state bits and verify transitions. A useful trick is to monitor DB50.DBD 0 (the timer's elapsed time) in engineering units (ms).
  6. To change the tempo, edit tDwellAtDwellD in the Instance DB initial values; the change is online-effective on the next cold restart of the FB.

Verification Checklist

Check Method Pass criterion
FSM initialises on restart Power-cycle CPU; watch boStateA on first scan TRUE in OB 100 execution, FALSE on scan 2 until a Start pulse
Tone A is exactly 1.0 s Oscilloscope on output; trigger on rising edge High time = 1.00 s ± 1 OB1 cycle
State order is A → B → C → D Monitor all four boState* bits in a VAT Bits pulse in alphabetical order with no overlap
Terminal state stops the horn Watch q_bHorn after final timer elapses Goes FALSE and stays FALSE until next Start
Multi-instance compiles Check the Reference Data > Program Structure Only one Instance DB per FB call; no DB 18xx stubs
Clock memory present Watch MB 10 in VAT Bits toggle at the configured ratios

Troubleshooting Matrix

Symptom Likely cause Fix
"Type conflict" when typing tonStateA Timer dragged from the library, not declared as a multi-instance Add tonStateA : FB 186; to STATIC, then type the name in an empty box
DB exploded – DB 18xx, DB 18xy appear after compile Library FB used in a network without a STATIC instance Delete the network, re-create the timer from the empty box referencing the multi-instance
Horn chirps continuously Output OR line missing parentheses – BOOL OR wiring shorts state to output Check that q_bHorn is the coil of a single network; the OR must precede the assignment
Tone durations are wrong by a constant offset PT typed in milliseconds but value treated as a 16-bit ms constant Use the T# prefix, e.g. T#1s, T#750ms, T#500ms
FSM never advances from state A Timer IN wired to the wrong state bit Each timer's IN must be its own boStateX, not the previous one
DB initial values are not what you saved Downloaded the FB but not the Instance DB In SIMATIC Manager: PLC > Download > Selected blocks, tick both
Tone sounds different on the same FB called twice Multi-instance attribute cleared during edit Re-create the FB and tick Multi-instance capable in the FB wizard
Clock memory bits are all zero Clock memory not enabled in HW Config Open CPU properties → Cycle/Clock Memory → enable and set the byte number
Startup OB does not clear B/C/D OB 100 not present in the offline program Insert OB 100 from the Standard Library and write the reset code shown above

Performance, Memory and Scan-Time Notes

On a CPU 315-2 PN/DP at typical 10 ms OB1 scan, the four-state melody consumes:

  • Instance DB footprint: 4 × 32 bytes for FB 186 + 4 × 4 bytes for the BOOL states ≈ 144 bytes per call.
  • OB1 execution time: ~120 µs for the four TONs, plus the SR logic – well below the 10 ms budget even on a CPU 312.
  • Watchdog: with default 150 ms / 300 ms watchdog settings the additional load is negligible. On a CPU 312 set to 100 ms OB1 you still have > 90 % margin.

If you scale the melody to 32 notes, expect ~1 ms additional scan time – consider moving the FB to OB 35 (cyclic interrupt, e.g. 50 ms) so the horn timing is decoupled from OB1 jitter.

Field-Proven Variations

  • Data-driven version: replace the four hard-wired boStateX bits with an INT index (0–3) and a CASE statement in SCL; dwell times are loaded from a DB that the operator can edit online.
  • Repeat mode: add an i_iRepeat input and wrap state D back to A using a SFB 0 / CTU counter that decrements once per cycle.
  • Priority mute: add a i_bMute input that resets the FSM and forces q_bHorn := FALSE. This is the safest pattern for SIL applications where the alarm is not the only output device on the panel.
  • Networked start: drive i_bStart from a Profibus / Profinet / Modbus bit. The FSM is otherwise platform-agnostic.

Standards and Safety Caveats

An audible alarm is a function-relevant output on many machines. If the application falls under ISO 13849-1 / IEC 62061, the horn circuit must be evaluated against the required Performance Level (PL) or Safety Integrity Level (SIL). The melody sequencer shown here is not a safety function by itself – the actual alarm-evaluation path (sensor → logic → horn driver) must be implemented in a Safety CPU (CPU 315F / 317F) or a separate safety relay. Use a multi-channel output stage (e.g. an ET 200S 4F-DO or a Sirius 3SK safety relay) for the final horn switch.

Always verify the loudness and tone pattern against ISO 7731 / IEC 60601-1-8 (or your local standard) – the durations above are example values, not regulatory minima.

FAQ

Why does STEP 7 show "Type conflict" when I drag a TON block from the library?

You are creating a stand-alone instance, but the variable you typed is declared as a multi-instance inside the FB's STATIC section. Drag the block from the library only when you want STEP 7 to allocate a separate instance DB; otherwise, type the instance name (e.g. tonStateA) into an empty LAD/FBD box and the multi-instance binding is created automatically.

Can I use SFB 4 (system TON) instead of FB 186 (IEC TON) inside the same multi-instance FB?

Yes. SFB 4 is multi-instance capable on S7-300/400 and is actually preferred on older firmware because it is smaller (~18 B of instance data vs ~32 B for FB 186). The trade-off is that SFB 4 uses the CPU's internal SFB instance table; mixing the two families in one FB is allowed but increases the maintenance burden.

How do I play a melody repeatedly until the operator acknowledges it?

Add a SFB 0 / CTU counter that increments each time the FSM reaches state D. The terminal transition resets state A only if the counter has not reached the configured number of repeats. An i_bAck input resets the counter and forces the FSM to its idle state.

What is the smallest tone duration I can achieve with this architecture?

Two OB1 scans (≈ 20 ms on a CPU 315 with 10 ms cycle) is the practical floor for a state-based approach. For shorter tones, replace the IEC TON for the OFF half of the cycle with a clock memory bit (M10.0 toggles at 50 ms, M10.1 at 100 ms, etc.) and you can drop to 100 ms resolution without OB1 jitter.

Can I call the same FB_Melody twice for two different horns on one CPU?

Yes – create one Instance DB per horn (e.g. DB 50 for "Horn1" and DB 51 for "Horn2") and call FB_Melody, DB 50 / FB_Melody, DB 51 in OB1. Each Instance DB carries its own timer instances and dwell values, so the two horns are fully independent.

My CPU is an S7-1500 – will the same code work?

The FSM pattern is portable, but in TIA Portal you should use the native IEC timers (TP, TON, TOF) from the Instructions task card. Multi-instance FBs in TIA Portal are even simpler because every FB is multi-instance capable by default; the manual "drag from library" pitfall that caused the type conflict on v5.5 no longer exists.

Back to blog