1. Overview
Two control tasks appear in nearly every S7-300 commissioning: (1) ramping an analog output to a target value over a fixed time (soft start of a valve, setpoint reference to a drive, or process ramp), and (2) generating a free-running on/off pulse from the PLC clock memory to sequence lamps, beacons, or auxiliary logic. This document specifies both, with the FB declaration, the linear-ramp math, full SCL and STL implementations, an OB1 call example, and commissioning checks. The code targets STEP 7 V5.5+ and the S7-300 CPU 31x family; it ports unchanged to S7-400 and to S7-1500 with the SCL substitutions noted in the closing section.
2. Prerequisites
- STEP 7 V5.5 or V5.7 with the S7-SCL optional package installed (needed only for the SCL version of the FB; the STL version works without SCL).
- S7-300 CPU 31x (validated on CPU 313C-2 DP, 314C-2 PN/DP, 315-2 DP, 317-2 DP).
- SM332 analog output module, for example 6ES7332-5HD01-0AB0 (4 AO, ±10 V / 4-20 mA) or 6ES7332-5HF00-0AB0 (8 AO). Confirm the output range against the SM332 Module Manual.
- STEP 7 hardware configuration: the clock memory byte must be enabled on MB0 (CPU Properties > Cycle/Clock Memory tab). See Section 3.
- STEP 7 Standard Library > IEC 61131-3 elements for the pulse generator, or you can use the classic S5 timers as shown in Section 7.
3. Clock Memory Configuration
The S7-300 clock memory byte is generated by the CPU's cycle-time interrupt and a fixed frequency divider. Without enabling it, none of the standard 100 ms/1 s/2 s bits exist. Enable it once per project:
- Open the SIMATIC Manager project, double-click Hardware to open HW Config.
- Right-click the CPU in the rack and choose Object Properties.
- Select the Cycle/Clock Memory tab.
- Tick Clock memory and set the address to
MB0(or any unused byte). - Download the hardware configuration to the CPU.
The eight bits then toggle with the periods below. The periods are fixed in firmware and cannot be reconfigured.
| Bit | Period (s) | Frequency (Hz) | Duty cycle | Typical use |
|---|---|---|---|---|
| M0.0 | 0.1 | 10 | 50 % | 100 ms tick, fast integrator |
| M0.1 | 0.2 | 5 | 50 % | 200 ms tick |
| M0.2 | 0.4 | 2.5 | 50 % | 400 ms tick |
| M0.3 | 0.5 | 2 | 50 % | 500 ms tick |
| M0.4 | 0.8 | 1.25 | 50 % | 800 ms tick |
| M0.5 | 1.0 | 1 | 50 % | 1 s heartbeat, ramp integrator |
| M0.6 | 1.6 | 0.625 | 50 % | 1.6 s tick |
| M0.7 | 2.0 | 0.5 | 50 % | 2 s tick |
For the ramp example, M0.5 is the recommended integrator because one toggle equals one second, which matches the S5T#5S sample value used in the source question. For higher resolution (e.g., 5 s total time resolved to 50 ms steps), use M0.0 (100 ms) and divide the slope by 10 or by 50 depending on resolution.
4. Analog Ramp Function Block - Theory
The block implements a linear ramp from LowLimit to HighLimit in TotalTime seconds, with the output computed at each integrator tick. The discrete-time equation is:
Output(t) = LowLimit + Slope · ElapsedSeconds
where the slope in output-units per second is:
Slope = (HighLimit - LowLimit) / TotalTimeSeconds
For the example: LowLimit = 0, HighLimit = 15000, TotalTime = 5 s, slope = 3000 units/s. After 1 s the output is 3000; after 5 s the output is 15000. If Enable stays 1 longer than 5 s, the output clamps at HighLimit (no further change). When Enable falls to 0, the output returns to LowLimit instantly and the integrator resets.
4.1 S5TIME format primer
STEP 7's S5TIME is a 16-bit BCD field used by the S5/S7 timer instructions. The upper two bits select a time base, the lower 12 bits hold a BCD value 0..999. The actual time in seconds is BCD × Base.
| Time base bits [15..14] | Resolution | Maximum (999 × base) |
|---|---|---|
| 00 | 0.01 s (10 ms) | 9.99 s |
| 01 | 0.1 s (100 ms) | 99.9 s |
| 10 | 1 s | 999 s |
| 11 | 10 s | 9990 s |
The shortest representable time at 10 ms base is 10 ms; the longest at 10 s base is 9990 s (~2 h 46 min). Values that do not fit (e.g., 7 s at 10 ms base rounds to 70 × 100 ms = 7.0 s) are accepted with rounding. This is documented in the STEP 7 V5.x System and Standard Functions Reference.
5. Analog Ramp FB - Declaration
Create a new function block in the S7 Program / Blocks folder: Insert > S7 Block > Function Block. Name it FB100 "RAMP_AO". Open the declaration table and enter:
| Section | Name | Type | Initial | Comment |
|---|---|---|---|---|
| INPUT | Enable | BOOL | FALSE | 1 = ramp active, 0 = reset to LowLimit |
| INPUT | LowLimit | INT | 0 | Start value (raw output 0..27648) |
| INPUT | HighLimit | INT | 0 | End value (raw output 0..27648) |
| INPUT | TotalTime | S5TIME | S5T#0MS | Total ramp duration |
| INPUT | ClockBit | BOOL | FALSE | Tick input (e.g., M0.5 = 1 Hz) |
| OUTPUT | Output | INT | 0 | Ramped integer to write to PQW |
| OUTPUT | Busy | BOOL | FALSE | 1 while ramping toward HighLimit |
| OUTPUT | Done | BOOL | FALSE | 1 for one cycle when ramp reaches HighLimit |
| STAT | RampState | INT | 0 | 0=idle, 1=ramping, 2=complete |
| STAT | TickCount | DINT | 0 | Number of clock pulses accumulated |
| STAT | TickCountOld | DINT | 0 | Previous TickCount for edge detection |
| STAT | TotalTimeMs | DINT | 0 | TotalTime converted to milliseconds |
| STAT | TickPeriodMs | DINT | 1000 | Period of the clock input in ms (1000 for M0.5) |
| STAT | DeltaPerTick | REAL | 0.0 | (HighLimit-LowLimit) × TickPeriodMs / TotalTimeMs |
6. Analog Ramp FB - SCL Implementation
Open FB100, switch the editor to SCL, and paste the following body. SCL is included with the S7-SCL optional package for STEP 7 V5.x; if SCL is not licensed, use the STL equivalent in Section 6.1.
FUNCTION_BLOCK FB100
TITLE = 'Linear Analog Output Ramp'
VAR_INPUT
Enable : BOOL;
LowLimit : INT;
HighLimit : INT;
TotalTime : S5TIME;
ClockBit : BOOL;
END_VAR
VAR_OUTPUT
Output : INT;
Busy : BOOL;
Done : BOOL;
END_VAR
VAR
RampState : INT;
TickCount : DINT;
TickCountOld : DINT;
TotalTimeMs : DINT;
TickPeriodMs : DINT;
DeltaPerTick : REAL;
END_VAR
BEGIN
// ---- Reset path: Enable = 0 holds output at LowLimit and clears state ----
IF NOT Enable THEN
Output := LowLimit;
Busy := FALSE;
Done := FALSE;
RampState := 0;
TickCount := 0;
TickCountOld := 0;
TotalTimeMs := 0;
DeltaPerTick := 0.0;
RETURN;
END_IF;
// ---- First-cycle initialisation: decode S5TIME to milliseconds ----
IF RampState = 0 THEN
// S5TIME layout: bit 15..14 = timebase, bit 13 = 100s marker,
// bits 11..0 = BCD value. Strip out the BCD value, then apply base.
// For standard S5TIME (no 100s marker), the effective formula is:
// ms = (WORD_AND(TotalTime, 16#0FFF)) × base_ms
// The base in ms is selected by bits 15..14: 10, 100, 1000, 10000.
IF (WORD_AND(S5TIME_TO_WORD(TotalTime), 16#3000)) = 16#0000 THEN
TotalTimeMs := DINT_TO_DWORD(WORD_AND(S5TIME_TO_WORD(TotalTime), 16#0FFF)) * 10;
ELSIF (WORD_AND(S5TIME_TO_WORD(TotalTime), 16#3000)) = 16#1000 THEN
TotalTimeMs := DINT_TO_DWORD(WORD_AND(S5TIME_TO_WORD(TotalTime), 16#0FFF)) * 100;
ELSIF (WORD_AND(S5TIME_TO_WORD(TotalTime), 16#3000)) = 16#2000 THEN
TotalTimeMs := DINT_TO_DWORD(WORD_AND(S5TIME_TO_WORD(TotalTime), 16#0FFF)) * 1000;
ELSE
TotalTimeMs := DINT_TO_DWORD(WORD_AND(S5TIME_TO_WORD(TotalTime), 16#0FFF)) * 10000;
END_IF;
TickPeriodMs := 1000; // 1 Hz clock default; change if you wire M0.0 (100 ms)
DeltaPerTick := DINT_TO_REAL(HighLimit - LowLimit)
* DINT_TO_REAL(TickPeriodMs)
/ DINT_TO_REAL(TotalTimeMs);
Output := LowLimit;
TickCount := 0;
TickCountOld := 0;
Busy := TRUE;
Done := FALSE;
RampState := 1;
END_IF;
// ---- Counting tick on the rising edge of the clock bit ----
IF ClockBit AND (RampState = 1) THEN
IF TickCount = TickCountOld THEN
TickCount := TickCount + 1;
END_IF;
TickCountOld := TickCount;
END_IF;
// ---- Update output based on accumulated ticks ----
IF RampState = 1 THEN
Output := LowLimit + REAL_TO_INT(DINT_TO_REAL(TickCount) * DeltaPerTick);
// Clamp to [LowLimit, HighLimit]
IF HighLimit >= LowLimit THEN
IF Output > HighLimit THEN
Output := HighLimit;
RampState := 2;
Busy := FALSE;
Done := TRUE; // one-shot for the scan Done is true
END_IF;
IF Output < LowLimit THEN
Output := LowLimit;
END_IF;
ELSE // negative-going ramp
IF Output < HighLimit THEN
Output := HighLimit;
RampState := 2;
Busy := FALSE;
Done := TRUE;
END_IF;
IF Output > LowLimit THEN
Output := LowLimit;
END_IF;
END_IF;
END_IF;
END_FUNCTION_BLOCK
6.1 STL equivalent (no SCL license required)
The SCL code compiles to the STL below. The block can be entered as STL directly if SCL is not available.
FB100
UN #Enable
SPB RES
L #RampState
L 0
<>I // first scan with Enable = 1
SPB CONT
// ---- S5TIME decode ----
L #TotalTime
T #TempWord
L W#16#3000
AW // isolate time base
L W#16#0000
==I
JC BASE10
L W#16#1000
==I
JC BASE100
L W#16#2000
==I
JC BASE1000
// base 10 s
L #TempWord
L W#16#0FFF
AW
ITD
L L#10000
*D
T #TotalTimeMs
JU DECODED
BASE10: L #TempWord
L W#16#0FFF
AW
ITD
L L#10
*D
T #TotalTimeMs
JU DECODED
BASE100: L #TempWord
L W#16#0FFF
AW
ITD
L L#100
*D
T #TotalTimeMs
JU DECODED
BASE1000: L #TempWord
L W#16#0FFF
AW
ITD
L L#1000
*D
T #TotalTimeMs
DECODED: NOP 0
L 1000
T #TickPeriodMs
L #HighLimit
L #LowLimit
-I
ITD
DTR
L #TickPeriodMs
ITD
DTR
*R
L #TotalTimeMs
ITD
DTR
/R
T #DeltaPerTick
L 0
T #TickCount
T #TickCountOld
L #LowLimit
T #Output
SET
S #Busy
R #Done
L 1
T #RampState
CONT: U #ClockBit
UN #ClockBitOld // edge detect via local flag
SPB INCT
SPA INCE
INCT: L #TickCount
L 1
+D
T #TickCount
INCE: U #ClockBit
= #ClockBitOld
L #TickCount
ITD
DTR
L #DeltaPerTick
*R
L #LowLimit
ITD
DTR
+R
RND
T #Output
L #HighLimit
L #LowLimit
>=I
JC POS
// negative-going
L #Output
L #HighLimit
<I
JC NDONE
L #HighLimit
T #Output
JU DONE
NDONE: L #Output
L #LowLimit
>I
JC RES
L #LowLimit
T #Output
JU RES
POS: L #Output
L #HighLimit
>I
JC DONE
L #Output
L #LowLimit
<I
JC RES
JU RES
DONE: SET
R #Busy
S #Done
L 2
T #RampState
JU RES
RES: NOP 0
BE
The STL body needs three temporary flags: ClockBitOld (BOOL), TempWord (WORD), and the DONE / NDONE labels as shown. Add them to the TEMP section of FB100.
7. Pulse Generator (On/Off Timer) with IEC 61131-3 Blocks
The second requirement in the source question is a free-running pulse: ON for 10 s, OFF for 13 s, repeat indefinitely. The cleanest implementation uses two TP (Pulse Timer) blocks from the IEC 61131-3 standard library, chained so that the falling edge of one starts the other.
7.1 Why two TPs, not one SPD or clock bit
A single TP produces one pulse per rising edge of its trigger input; it does not retrigger itself. To get a continuous 23 s period with no external Start signal, you must use the falling edge of TP1 (which occurs exactly at the end of the ON time) to trigger TP2, and the falling edge of TP2 to retrigger TP1. TP is preferred over the older S5 pulse timer because it is type-safe, cannot be retriggered while running (avoids runaway ON time if a glitch occurs), and the pulse length is passed as a TIME parameter that the compiler can range-check.
7.2 FB101 PULSE_23s in SCL
FUNCTION_BLOCK FB101
TITLE = 'Asymmetric pulse: 10s ON, 13s OFF'
VAR_INPUT
Run : BOOL;
END_VAR
VAR_OUTPUT
Q : BOOL; // equivalent to M50.0 in the source question
State : INT; // 0 = idle, 1 = ON, 2 = OFF
END_VAR
VAR
tOn : TP; // 10 s pulse
tOff : TP; // 13 s pulse
tOnPT : TIME := T#10s;
tOffPT : TIME := T#13s;
END_VAR
BEGIN
IF NOT Run THEN
Q := FALSE;
State := 0;
tOn(IN := FALSE, PT := tOnPT);
tOff(IN := FALSE, PT := tOffPT);
RETURN;
END_IF;
// Drive tOn with Run, retrigger on tOff's falling edge
tOn(IN := Run OR tOff.Q, PT := tOnPT);
tOff(IN := tOn.Q, PT := tOffPT);
Q := tOn.Q;
IF tOn.Q THEN
State := 1;
ELSIF tOff.Q THEN
State := 2;
ELSE
State := 0;
END_IF;
END_FUNCTION_BLOCK
7.3 S5-timer alternative (no IEC blocks)
For projects that use only the S5 timer set, build the same behaviour with a self-resetting SE (extended pulse) timer and a TON for the OFF delay. The ladder is in Section 8.
8. Wiring in OB1
Open OB1 and add the following networks. IW0, QW0, M0.5 are placeholders; replace with the addresses of your SM332 output and your clock bit. DB100 is the instance DB for FB100 (auto-generated when you call FB100 in OB1).
// Network 1: enable and endpoints
U "Enable_Run" // BOOL flag, e.g. E0.0 or M0.0
L 0 // LowLimit
T "DB_Ramp".LowLimit
L 15000 // HighLimit
T "DB_Ramp".HighLimit
L S5T#5S // TotalTime
T "DB_Ramp".TotalTime
U M0.5 // 1 Hz clock
= "DB_Ramp".ClockBit
U "Enable_Run"
= "DB_Ramp".Enable
// Network 2: copy the ramped output to PQW
L "DB_Ramp".Output
T PQW 288 // or whatever PQW your SM332 is mapped to
// Network 3: pulse generator for the M50.0-style 10s/13s blink
U "Enable_Pulse"
= "DB_Pulse".Run
U "DB_Pulse".Q
= M 50.0 // the bit asked for in the source question
Symbol table entries (optional but recommended):
| Symbol | Address | Type | Comment |
|---|---|---|---|
| Enable_Run | E 0.0 | BOOL | Operator start pushbutton |
| Enable_Pulse | E 0.1 | BOOL | Operator pulse enable |
| DB_Ramp | DB100 | FB100 | Instance DB for ramp FB |
| DB_Pulse | DB101 | FB101 | Instance DB for pulse FB |
9. Verification and Commissioning
- Compile FB100 and FB101, then download all blocks to the CPU in RUN-P mode.
- Open the Monitor / Modify tool (online > Monitor/Modify) on FB100 in OB1.
- Set
LowLimit = 0,HighLimit = 15000,TotalTime = S5T#5S,Enable = 1, and forceM0.5 = 0. - Watch the
Outputvalue. SetM0.5 = 1for one OB1 cycle, thenM0.5 = 0. Repeat. The output should increment by 3000 each second: 0, 3000, 6000, 9000, 12000, 15000. After 15000,Busyfalls,Donerises for one scan. - Leave
Enable = 1for 10 s. The output must remain clamped at 15000. - Set
Enable = 0. The output must return toLowLimit(0) in the same scan;TickCountresets to 0. - Repeat with
ClockBit = M0.0(100 ms) and aTickPeriodMs = 100patch in the first-scan branch. The output must update in 100 ms steps; the slope remains the same in units/second, so after 5 s the value must still be 15000. - For the pulse generator, set
Run = 1and trace M50.0 with a trend or the VAT table. Confirm the period is 23 s exactly: ON 10 s ± one OB1 cycle, OFF 13 s ± one OB1 cycle. - Connect a digital oscilloscope or trend recorder to PQW 288 and watch the analog waveform. The slope from 0 to 10 V (or 4 to 20 mA) must be linear within ±1 LSB of the SM332 (typical 11-bit effective resolution).
10. Troubleshooting Matrix
| Symptom | Likely cause | Fix |
|---|---|---|
| Output stays at 0 with Enable = 1 | Clock memory not enabled in HW Config | Re-open CPU properties, enable clock memory at MB0, download HW Config |
| Output updates only every 1 s when 100 ms expected | Wrong clock bit wired; M0.5 is 1 Hz not 10 Hz | Re-wire ClockBit to M0.0 and set TickPeriodMs := 100
|
| Output overshoots HighLimit by 1-2 LSB | Rounding of REAL_TO_INT | Add final clamp IF Output > HighLimit THEN Output := HighLimit; END_IF; after the conversion |
| Output returns to LowLimit mid-ramp | Enable chattering or OB1 not calling the FB | Add a debounce or hold Enable via SET/RESET; verify FB100 is called in OB1 (not OB100) |
| SF LED on CPU after download | Wrong instance DB length or S5TIME format mismatch | Delete DB100, recompile, download; ensure STEP 7 version matches the FB source (V5.5 vs V5.7) |
| M50.0 never goes true | TP instance not refreshed; Run not connected | Verify DB101 is updated in OB1; in online view, force Run = 1 and watch State |
| M50.0 stays true continuously | Wrong PT value (e.g., T#10ms instead of T#10s) | Re-enter PT as T#10s; rebuild FB101 |
| Output increments in jumps larger than expected | OB1 cycle time > clock period; integrator is double-counting | Add a one-shot FP on ClockBit to consume only rising edges; verify OB1 is not slowed by long FB calls |
| Output drifts when Enable held > 5 s | TickCount not clamped; integrator overflows | Clamp TickCount when Output reaches HighLimit
|
| Analog output pegged at full scale | SM332 configured for ±10 V but code writes 0..27648 expecting unipolar | Match the configured output type to the code's value range; check the SM332 wiring against the module manual |
11. Notes on Resolution, Timebase, and Output Scaling
Resolution of the integrator. With M0.5 (1 Hz) and a 5 s ramp, the output takes five discrete steps. That is sufficient for setpoint references and valve soft-starts but not for high-resolution profile generation. For 100 ms resolution, wire M0.0 and set TickPeriodMs := 100. For 10 ms resolution, switch to a true cyclic interrupt OB35 tick (called at the configured OB35 interval) and set TickPeriodMs := OB35_ms; the S7-300 default OB35 interval is 100 ms but can be reconfigured to as little as 1 ms on most CPUs, with shorter intervals increasing scan-time load.
S5TIME rounding. Because S5TIME quantises to a fixed base, a request for 5.3 s with M0.5 (1 s resolution) becomes either 5 s (round down) or 6 s (round up), depending on how the SCL literal is parsed. The IEC TIME type used inside the FB is millisecond-accurate; the conversion in Section 6 is exact, so the limitation is in the input, not the math. For non-quantised ramps, change TotalTime to TIME and pass T#5s300ms.
Direction of the ramp. The FB handles both positive and negative ramps: LowLimit > HighLimit produces a descending ramp, used for soft-stops. The clamp logic branches on the sign of HighLimit - LowLimit.
Bidirectional use with a drive. When the output feeds a Siemens Micromaster or Sinamics V20/V90 frequency setpoint (typically 0-10 V or 4-20 mA mapped to 0-50 Hz), the ramp in the PLC must be slower than the drive's own ramp-up/ramp-down parameters, otherwise the drive's internal ramp is the bottleneck and the PLC ramp looks like a step. The rule of thumb is PLC ramp ≥ 1.5 × drive ramp.
Alternative: Siemens Standard Library RAMP. Siemens also ships a generic RAMP function block in the Standard Library for S7-300/400. It is documented in the entry FAQ 1853767: RAMP application in S7-300/400. The library block supports multiple curve types (linear, quadratic, sinusoidal) and is preferred for new projects where the additional flexibility is wanted.
Porting to S7-1200 / S7-1500 (TIA Portal). The SCL body in Section 6 compiles unchanged in TIA Portal V15+. S5TIME_TO_WORD is replaced by S5TIME_TO_UINT or by an explicit WORD_TO_UINT after S5TIME_TO_WORD. The TP block in Section 7 is available in TIA Portal under Instructions > Timer operations > IEC timers; its I/O name is the same (IN, PT, Q, ET).
What is the correct clock bit for a 1-second integrator on an S7-300?
Use M0.5 after enabling the clock memory byte in HW Config (CPU Properties > Cycle/Clock Memory). M0.5 toggles at exactly 1 Hz with a 50 % duty cycle. Other bits give 100 ms (M0.0), 200 ms (M0.1), 500 ms (M0.3), 2 s (M0.7), and so on; pick the bit whose period matches the resolution you need.
Why does my output stay at 0 even though Enable is 1 and the clock bit is pulsing?
Three things to check: (1) the clock memory byte is enabled in HW Config and downloaded, (2) the FB is actually called from OB1 with a real instance DB (not a temporary instance), and (3) the TotalTime S5TIME input is non-zero. The most common cause in the field is the clock memory byte never being enabled, which makes M0.5 stay at 0 forever.
How do I get a 10-second ON / 13-second OFF pulse that runs forever?
Use two TP (Pulse Timer) blocks chained together: TP1 (PT = T#10s) drives M50.0, and TP1.Q falling edge starts TP2 (PT = T#13s); TP2.Q falling edge retriggers TP1. Section 7.2 of this document gives a copy-paste SCL FB (FB101) that does exactly that. Alternatively, the same behaviour can be built with a self-resetting SE timer plus a TON.
How do I scale the ramp output 0-100 % instead of 0-27648 raw counts?
Use FC105 SCALE from the STEP 7 Standard Library, or apply the linear conversion yourself: PQW_pct = (Output / 27648.0) × 100.0. For 4-20 mA devices, the live-zero offset is handled by FC105 when the input range is set to bipolar or unipolar with offset; see the SM332 module manual for the wiring and configuration switches.
Can I run this FB on an S7-1200 or S7-1500 in TIA Portal?
Yes. The SCL body in Section 6 compiles in TIA Portal V15 or later. The S5TIME-to-WORD helper is replaced by S5TIME_TO_UINT, and the TP block is available in the TIA instruction tree under IEC timers. Re-derive the clock memory bit frequency because the S7-1200/1500 default frequencies differ from the S7-300 (M0.5 = 0.2 Hz on S7-1200, not 1 Hz).