Calculating Velocity in S7-300 Ladder Logic with TIME_TCK

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

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:

  1. Capturing a clean, scaled position value in engineering units (mm, mm/s compatible integer).
  2. Sampling it at a deterministic time interval, or stamping it with a high-resolution system clock.
  3. 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.
Always use a stabilized 24 V / 10 V reference for the potentiometer. A drifting supply adds a multiplicative error that looks like velocity noise. The SM 331 provides a 24 V sensor supply on the front connector, but the wiper should be fed from a clean 10 V reference (e.g. the on-board Uref of the SM 331 where available, or an external precision reference).

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
Rollover handling: TIME_TCK is a 32-bit millisecond counter and rolls over at 232 ms ≈ 49.7 days. The unsigned subtraction automatically handles the wrap when the result is interpreted as a 32-bit modular difference. If your axis may run continuously, add a check that dt < 2 147 483 648 ms before using the value, or restart the cycle at known events.

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:

  1. 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.
  2. Wrong VAT — symbolic versus absolute. Open a VAT (e.g. VAT1), insert DB100.DBD0 as 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.
  3. 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.
  4. 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_ms in DB100.

Commissioning and Verification Procedure

  1. Download HW Config, the S7 program (OB35, OB100, FC100, FC105, DB100) to the CPU.
  2. Switch to RUN. The CPU diagnostic buffer should show no OB-not-found events.
  3. Open VAT1, type DB100.DBD0 for position, DB100.DBD14 for velocity, and start cyclic monitor.
  4. 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).
  5. Stop the axis. Velocity should drop to 0 within one or two OB35 cycles (200 ms).
  6. 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.
  7. 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.

Back to blog