Overview
Calculating the speed of a movement from a resistive strip position sensor on a Siemens SIMATIC S7-300 requires two ingredients: a high-resolution position sample and an accurate time base. A typical hardware stack uses a CPU 317-2 DP (e.g. 6ES7317-2AK14-0AB0 or 6ES7317-2AJ10-0AB0) reading a 0-10 V analog module such as the SM 331 (6ES7331-7KF02-0AB0 in 8-channel, 12-bit resolution) wired to the wiper of a linear potentiometer. Velocity is the difference quotient v = (xn - xn-1) / (tn - tn-1), so the engineering problem reduces to:
- Capturing a clean, scaled position value in engineering units (mm, mm/s compatible integer).
- Sampling it at a deterministic time interval, or stamping it with a high-resolution system clock.
- Subtracting the previous sample and dividing by the elapsed time.
Two S7-300 mechanisms are appropriate, and they answer two slightly different questions:
- Cyclic interrupt OB3x (OB30 to OB38) — best for a continuous variable such as the wiper voltage. The cycle is a constant, selectable hardware time base, so the denominator is fixed and only the numerator (Δx) needs to be measured.
- TIME_TCK (SFC 64) — best when velocity is needed between two events (e.g. two limit switches or two position setpoints). The numerator is fixed (Δx between two known points) and only the elapsed time has to be measured.
The recipe below implements both, with the same FB interface, and explains why the value "is not visible in the program when it was running online" — the most common failure mode when scaling a 0-10 V wiper into STEP 7.
Prerequisites
| Item | Specification | Notes |
|---|---|---|
| CPU | SIMATIC S7-300, CPU 317-2 DP (6ES7317-2AK14-0AB0) | 1 MB work memory, 0.1 ms bit execution; supports OB3x and SFC 64. |
| Analog input | SM 331 AI8x12 (6ES7331-7KF02-0AB0) | Set measuring range to "B" (0-10 V) via DIP switches; 0-10 V is scaled to 0-27648 in STEP 7. |
| Position sensor | Conductive plastic / wire-wound resistor strip with wiper | 10 kΩ typical; feed stabilized 10 V from the SM 331 sensor supply or a SITOP. |
| Programming tool | STEP 7 V5.5 + SPx or TIA Portal V16+ with S7-300 HSP | Classic STEP 7 is recommended for legacy CPU 317-2 DP projects. |
| Online viewer | Variable Table (VAT) or Monitor/Modify | Required to verify scaled position and Δx are actually updated. |
Conditioning the 0-10 V Position Signal
The wiper voltage is converted by the SM 331 into a raw integer in the range 0-27648. This raw value must be scaled into engineering units (mm) before any differencing is meaningful. The standard tool is FC105 SCALE from the STEP 7 standard library (TI-S7 Converting Blocks). For a 0-10 V wiper spanning 0-500 mm of physical travel:
| Input (IN) | HI_LIM | LO_LIM | BIPOLAR | OUT (mm × 100) |
|---|---|---|---|---|
| PIW 288 (raw 0-27648) | 50000 | 0 | FALSE (0) | MD 200 — scaled to 0.00 - 500.00 mm |
Internally FC105 computes:
OUT = [ ((IN - K1) / (K2 - K1)) * (HI_LIM - LO_LIM) ] + LO_LIM
where K1 = 0, K2 = 27648 for unipolar 0-10 V
Store the result in a DINT (32-bit signed integer) tag such as DB100.DBD0 "Position_mmE2" expressed in hundredths of a millimetre. DINT is preferred over REAL for the difference quotient because integer subtraction has no floating-point rounding error and no NaN risk.
Time Measurement Strategies in S7-300
Siemens documents two complementary methods in the S7-300/400 Time Measurement entry and in the System and Standard Functions reference manual.
Method 1 — Cyclic Interrupt OB3x
S7-300 OBs 30 through 38 run at fixed, hardware-derived time bases. They are the right tool for sampling a continuous process variable:
| OB | Default period | Typical use |
|---|---|---|
| OB30 | 5 s | Very slow processes, thermal loops |
| OB31 | 2 s | Heating/cooling |
| OB32 | 1 s | Level, slow flow |
| OB33 | 500 ms | Pressure, mid-speed motion |
| OB34 | 200 ms | Hydraulic axis tracking |
| OB35 | 100 ms | Default; PID, fast motion |
| OB36 | 50 ms | High-speed motion |
| OB37 | 20 ms | Fast spindle feedback |
| OB38 | 10 ms | Minimum; consumes OB35-priority headroom |
For a hydraulic axis driven by a proportional valve with a wiper read by SM 331, OB35 at 100 ms is a good starting point. The period is constant, so the velocity calculation simplifies to v = Δx / TOB35.
Method 2 — TIME_TCK (SFC 64)
When velocity is required between two asynchronous events (limit-switch make, position setpoint reached, start of a press stroke), use SFC 64 TIME_TCK. It returns a DWORD in milliseconds since the CPU last went from STOP to RUN. The first call is a true timestamp; the second call gives t2; the elapsed time is the difference. This is the right tool when the user explicitly asks "how do I measure the time between two events?".
CALL SFC 64 // TIME_TCK
RET_VAL := MD 100 // t1 in ms (DWORD)
// ... event occurs ...
CALL SFC 64
RET_VAL := MD 104 // t2 in ms (DWORD)
// dt = MD104 - MD100 // careful: rollover after ~49.7 days
Implementing the Difference Quotient in OB35 (Ladder Logic)
The complete program fits in a single FC (FC100 "CalcVelocityMMs") called from OB35. The data block DB100 "VelocityData" holds the static tags. A separate instance DB is not required because FC100 uses only the in/out parameters and DB100.
DB100 — Data Layout
DATA_BLOCK DB100
STRUCT
Position_mmE2 : DINT; // scaled position × 100 (e.g. 12345 = 123.45 mm)
Position_prev : DINT; // previous cycle value
DeltaX_mmE2 : DINT; // difference, signed
T_OB35_ms : INT; // OB35 period in ms, configurable
Velocity_mmE2 : DINT; // delta_x * 100 / T_OB35 = mm/s × 100
Velocity_mm_s : REAL; // REAL copy for HMI
FirstScan : BOOL; // initialisation latch
InitDone : BOOL;
END_STRUCT;
END_DATA_BLOCK
FC100 — Ladder Implementation
Network 1 scales the analog input (call FC105). Network 2 captures the first scan. Network 3 calculates Δx. Network 4 calculates velocity. Network 5 stores the new previous value.
// =====================================================
// FC100 "CalcVelocityMMs" — Ladder excerpt
// =====================================================
NETWORK 1 // Scale wiper voltage 0-10 V to 0-500.00 mm
CALL FC105
IN := PIW288
HI_LIM:= 50000
LO_LIM:= 0
BIPOLAR := FALSE
RET_VAL := DB100.DBD0 // "Position_mmE2"
NETWORK 2 // First-scan initialisation: copy current to previous
A DB100.FirstScan // = M100.0 set in OB100
JC END // if NOT first scan, skip
L DB100.DBD0 // Position_mmE2
T DB100.DBD4 // Position_prev
SET
S DB100.InitDone
JC END
END: NOP 0
NETWORK 3 // DeltaX = Position - Position_prev
L DB100.DBD0 // current
L DB100.DBD4 // previous
-D // DINT subtraction
T DB100.DBD8 // DeltaX_mmE2
NETWORK 4 // Velocity_mmE2 = DeltaX * 100 / T_OB35
L DB100.DBD8 // DeltaX_mmE2 (hundredths of mm per 100 ms)
L 100 // convert 100 ms window to per-second
*D
L DB100.DBW12 // T_OB35_ms (100)
/D
T DB100.DBD14 // Velocity_mmE2 (mm/s × 100)
DTR // convert to REAL
L 1.000e+002 // /100
/R
T DB100.DBD18 // Velocity_mm_s (REAL, mm/s)
NETWORK 5 // Position_prev := Position (for next cycle)
L DB100.DBD0
T DB100.DBD4
OB35 Caller
// OB35 — Cyclic interrupt, 100 ms default
NETWORK 1
CALL FC100
Position_mmE2 := DB100.DBD0
Position_prev := DB100.DBD4
DeltaX_mmE2 := DB100.DBD8
T_OB35_ms := DB100.DBW12
Velocity_mmE2 := DB100.DBD14
Velocity_mm_s := DB100.DBD18
FirstScan := DB100.FirstScan
Set the OB35 period through CPU Properties → Cyclic Interrupts in STEP 7. Confirm that the time base matches the constant T_OB35_ms in DB100. If the period is changed to 200 ms (OB34), update the constant to 200; the math scales automatically.
Method 2 — Event-Based Velocity with TIME_TCK
For event-driven velocity, replace the OB3x scan with a pair of TIME_TCK calls wrapped around an event edge. The pattern below measures the time between two rising edges of I 0.0 and reports the velocity over a known mechanical stroke:
// FB101 "EventVelocity" — call from OB1
NETWORK 1 // Detect rising edge of trigger I0.0
A I 0.0
FP M 50.0
JC EV1
JU NEX
EV1: CALL SFC 64 // TIME_TCK
RET_VAL := MD 100 // t1
AN M 50.1 // first edge seen?
S M 50.1
JC NEX
L MD 100
T MD 104 // t1 stored
JU NEX
NETWORK 2 // Second edge: compute dt
A I 0.0
FP M 50.2
JC EV2
JU NEX
EV2: CALL SFC 64
RET_VAL := MD 108 // t2
L MD 108
L MD 104
-D // dt = t2 - t1
T MD 112
// Velocity = Stroke * 1000 / dt (with units check)
NETWORK 3 // Edge enable / disable logic
A I 0.0 // arm only while trigger high
= M 50.3
R M 50.1
R M 50.2
NEX: NOP 0
The same arithmetic structure applies: dt is the time the wiper takes to traverse a known mechanical segment; the segment is your numerator. If the segment is the full 0-500 mm stroke, velocity is 500000 / dt mm × ms units → adjust with constants.
Why the LGF_Library DifferenceQuotient Does Not Run on S7-300
The Siemens LGF (Library of General Functions) contains LGF_DifferenceQuotientFB and LGF_DifferenceQuotientFC, which are excellent for an S7-1500 project. They do not load on an S7-300 CPU because:
- They use the S7-1500 optimised data block layout (with user-defined PLC data types / UDTs and "non-optimised" access flags that S7-300 does not implement).
- They call 1500-only instructions such as DREM, NORM_X, SCALE_X from the extended instruction set, none of which exist in the S7-300 instruction set.
- Symbolic multi-instance FBs with the S7-1500 THIS pointer are unsupported on S7-300.
For an S7-300 application, either re-implement the difference quotient as shown above, or port the LGF block into STEP 7 V5.5 manually and replace the 1500-only instructions with the SFC 105/FC105 block, the legacy SCL subset, or the S7-300-compatible arithmetic shown in FC100.
Why "the value is not visible when running online"
This is the most common pitfall and almost always has one of four causes. The user reporting that "the value has not been visible in the program when it was running online" will find the answer in this list:
- DB is not being written to. If FC100 is called from OB1 but never from OB35, the position tag will update only on scan, not every 100 ms. Check the S7-Program folder for the OB35 block and confirm it is downloaded to the CPU. Without OB35, the difference quotient is calculated once per OB1 cycle but the previous-value update runs in the same cycle, making Δx always 0.
-
Wrong VAT — symbolic versus absolute. Open a VAT (e.g. VAT1), insert
DB100.DBD0as absolute or insert the symbolic name"VelocityData".Position_mmE2. Refresh with Monitor (F5) or the binocular icon. Inconsistent symbolic/absolute addresses look identical offline and break online visibility. - DB is in the work memory but never instantiated. The DB must be present in the S7-Program offline and downloaded, and any instance must be loaded into load memory. If the DB is only in the offline project, online monitoring shows --- or 0.
- OB35 priority / interrupt is disabled. Some legacy projects disable OB35 in HW Config to free CPU time. With OB35 disabled, no cyclic interrupt fires; the calculation never runs.
Open the VAT, press the binocular icon (Monitor/Modify), toggle Trigger > Monitor cyclic, and confirm DB100 updates at 100 ms. If the tag holds a constant 0 or stale value, trace back to which of the four causes applies.
Tuning the Sampling Period and Filter
The choice of OB3x period trades velocity resolution against CPU load. Rules of thumb for a hydraulic axis with a 0-10 V wiper:
| Stroke / cycle | Recommended period | Δx resolution (12-bit, 500 mm stroke) |
|---|---|---|
| < 10 mm/s slow jog | OB35 (100 ms) | 0.12 mm / 0.1 s → 1.2 mm/s LSB |
| 10-100 mm/s manual | OB35 (100 ms) | Same, no filtering needed |
| 100-1000 mm/s fast | OB36 (50 ms) or OB37 (20 ms) | Better transient capture |
| > 1000 mm/s or 5 kHz control loop | Counter card FM 350 / 1, not analog | 12-bit wiper insufficient |
Add a first-order lag filter (PT1) to suppress the LSB jitter of the 12-bit wiper. The discrete-time form is:
y_n = y_(n-1) + (T_s / (T_s + T_f)) * (x_n - y_(n-1))
with Ts the OB35 period (100 ms) and Tf the filter time constant (e.g. 200 ms for a moderately smooth velocity signal). The implementation in SCL/ST is straightforward; in pure ladder, multiply by the integer filter coefficient K = Ts / (Ts + Tf) × 1000 (e.g. K = 333) and use fixed-point arithmetic.
Edge Cases and Field-Proven Caveats
- Direction reversal. Δx can be negative. Keep the math signed (DINT) and the HMI signed. A squared absolute value for closed-loop control is a different topic (sign loss in regulator).
- Position rollover. If the wiper is on a rotary element that wraps, the difference quotient will spike at the wrap. Detect a single-step jump larger than the maximum mechanical speed and discard that sample.
- Wiper dead-band at the ends. Conductive plastic strips have a 1-2 mm dead zone at each end where the resistance diverges. Mask these from the velocity calculation or add a debounce.
- SM 331 resolution. The 6ES7331-7KF02-0AB0 is a 12-bit ADC; the 6ES7331-1KF02-0AB0 is 13-bit. For sub-mm/s velocity on a 100 ms cycle, use the 13-bit variant or oversample by averaging in OB35.
- Cold-junction and wiper self-heating. Heavy currents through the wiper (e.g. > 1 mA) cause thermal drift. Use the SM 331's low-current measurement mode or buffer the wiper with an op-amp follower.
-
Time base change after compile. STEP 7 sometimes resets OB35 to 100 ms after a compile, even if HW Config said 50 ms. Always re-verify the time base after recompile and update
T_OB35_msin DB100.
Commissioning and Verification Procedure
- Download HW Config, the S7 program (OB35, OB100, FC100, FC105, DB100) to the CPU.
- Switch to RUN. The CPU diagnostic buffer should show no OB-not-found events.
- Open VAT1, type
DB100.DBD0for position,DB100.DBD14for velocity, and start cyclic monitor. - Move the axis slowly by hand. The position should change in real time. The velocity should be in the range expected for your hand speed (e.g. 20-100 mm/s).
- Stop the axis. Velocity should drop to 0 within one or two OB35 cycles (200 ms).
- Move the axis in the opposite direction. Velocity should change sign. If it does not, check that Δx is signed (DINT, not WORD) and that the HMI is reading a signed tag.
- Drive the axis at a known constant speed (e.g. from the hydraulic proportional valve calibration). Compare the measured velocity to the commanded value. A 5-10 % error is acceptable for a 12-bit wiper; a larger error suggests scaling issues.
Troubleshooting Matrix
| Symptom | Likely cause | Fix |
|---|---|---|
| Velocity always 0 in VAT | OB35 not downloaded or disabled | Verify OB35 in CPU, enable in HW Config |
| Velocity jumps but never settles | DB not initialised on first scan; Δx is computed against uninitialised memory | Use FirstScan latch (Network 2 above) or OB100 to seed Position_prev = Position on cold start |
| Velocity noisy, ±20 % | 12-bit wiper, 100 ms cycle insufficient, or 10 V supply noisy | Increase Tf filter, move to OB36, replace 10 V supply with a precision reference |
| Velocity wrong sign | Wiper wired with reversed polarity (10 V on wiper side, GND on end) | Swap HI/LO wires at SM 331 terminal, or set FC105 BIPOLAR and add an offset |
| Velocity wrong scale (e.g. × 10) | T_OB35_ms constant not updated to match HW Config time base | Re-verify time base, update DB100.DBW12 |
| Online value invisible / "---" | DB not downloaded, or wrong VAT column | Re-download DB100; check VAT symbolic vs absolute |
| Velocity spikes every 49.7 days | TIME_TCK rollover not handled | Add modular subtraction check or restart cycle at a known event |
| CPU goes STOP with SF | OB35 not loaded, OB85 not configured, or programming error | Check diagnostic buffer; re-download all OBs |
| Difference quotient off by one period at startup | First OB35 call reads uninitialised previous value | Seed Position_prev in OB100 or with FirstScan flag as shown |
FAQ
Which OB should I use to calculate velocity on a CPU 317-2 DP?
Use OB35 (100 ms) for typical hydraulic axis tracking with a 0-10 V wiper, or OB36/OB37 for faster axes. The OB3x family runs at a fixed, deterministic time base so the velocity denominator is constant and only Δx needs to be measured. See S7-300/400 Time Measurement.
How do I measure the time between two events for velocity?
Call SFC 64 TIME_TCK on the first event and again on the second event; subtract the two DWORD millisecond timestamps to get dt. Handle the 32-bit rollover at ≈49.7 days with a modular subtraction check. Combine dt with the known mechanical stroke to compute velocity.
Why is the calculated velocity not visible in the online program?
Usually one of four causes: (1) OB35 is not downloaded or is disabled, (2) the DB100 instance is not downloaded, (3) the VAT is using a wrong symbolic/absolute address, or (4) the DB is being written to a different work-memory area than the VAT monitors. Open the VAT, press the binocular icon, and verify DB100.DBD0 changes when you move the axis.
Can I use LGF_DifferenceQuotientFB on an S7-300?
No. The LGF block uses S7-1500-only instructions (NORM_X, SCALE_X, optimised DTs, multi-instance FBs) and will not compile or load on a CPU 317-2 DP. Re-implement the difference quotient with FC105 (SCALE) and DINT arithmetic as shown in FC100 above.
What scaling should I use for a 0-10 V wiper on the SM 331?
Use the SM 331 in unipolar 0-10 V mode. STEP 7 maps the input to 0-27648 raw. Call FC105 SCALE with HI_LIM = mechanical stroke in engineering units × 100 and LO_LIM = 0. For a 500 mm stroke, HI_LIM = 50000 and the result is in hundredths of a millimetre — convenient for integer velocity math.