S7-1500 HMD Speed Capture: Resolving 6% Timing Error in OB37

David Krause15 min read
SiemensTIA PortalTroubleshooting
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

Problem Description

A Siemens S7-1500 PLC is configured to capture the speed of a moving object (typically hot rolled stock on a rolling mill) as it travels between two hot metal detectors (HMDs) installed exactly 20 m apart. The application calls the calculation routine in cyclic interrupt OB37 with an 8 ms base period (8000 µs), and reports a speed measurement error of approximately 6% versus the expected physical value.

The application envelope as observed in the field is:

  • Path length between HMDs: 20 m (fixed, mechanically surveyed)
  • Average object speed: 15 to 16 m/s
  • Resulting transit time: ~1.25 to 1.33 s (1250 to 1330 ms)
  • Speed output update: every OB37 call (8 ms)
  • Observed error: +6% on the computed speed
Sign convention. Speed = path / transit time. A 6% positive error on speed means the calculated transit time is ~6% shorter than the real time, which is the signature of a systematic "extra" time being subtracted in software (for example a constant 8 ms bias added on top of the path traversal measurement).

Why OB37 Cannot Be Used Directly for a 1.25 s Time Capture

The cyclic interrupt OB is not a precise timekeeper; it is a scheduler. It runs at a fixed interval set in the PLC hardware configuration (8 ms in this case) and triggers the OB body. To capture the elapsed time between two HMD edges, the application must timestamp the rising edge of the first HMD and the rising edge of the second HMD and compute the difference. With OB37 as the only time reference, the resolution of any such timestamp is one OB37 period.

With:

  • OB37 period T_OB = 8 ms
  • Real transit time T_real ≈ 1.25 s

the quantisation error alone is bounded by ±T_OB = ±8 ms, which is ±0.64% of 1.25 s. That is the theoretical minimum error floor for an OB37-based capture. Anything larger, including the observed 6%, indicates that additional software-side error sources are stacked on top of the quantisation floor.

Expected error budget for a 1.25 s capture at 8 ms OB37
Component Magnitude Effect on speed
OB37 quantisation ±8 ms ±0.64%
OB37 jitter (bus, IPC sync, scan time) ±0.5 to ±3 ms typical ±0.04% to ±0.24%
Counter drift over 1.25 s at 50 ppm ~10 ppm < 0.01%
Constant offset added in code (suspected) 0 to N × 8 ms 0% to N×0.64%

The 6% observed in the field corresponds to about 75 ms of phantom time, or roughly 9 to 10 OB37 cycles' worth of offset. That is consistent with a code path that adds the OB37 period on every iteration (a "while waiting" loop, a SUM-of-deltas implementation, or an off-by-one in the time-since-edge accumulator).

Root Cause

The root cause is twofold:

  1. Wrong tool for the job. Cyclic interrupt OBs are designed for periodic control loops, not for high-resolution time-of-flight measurement. They quantise any time interval to a multiple of the OB period and accumulate jitter proportional to the main scan and bus load.
  2. Software time-base bug. The application's "elapsed time" counter is being incremented by the OB37 period in every call, including the call that fires on the HMD edge. This adds one extra OB37 period to the transit time on every measurement, which biases the captured time downward and pushes the calculated speed upward by 1 / (1 - bias) − 1. With a 75 ms bias on 1.25 s, this is exactly the 6% overshoot observed.
A 6% error on a 1.25 s real transit time means the code is reporting a transit time of about 1.175 s. The difference (75 ms) is suspicious because it is close to 9.4 × OB37 period. Always re-check the time accumulator: is the period added before or after the HMD-edge timestamp? Is the OB1 scan time (not the OB37 period) being added by mistake? Is the previous-cycle residual being summed in twice?

Solution 1: Cycle-Time Compensation in OB37

If the project is locked into OB37 (no spare counter module, no spare DI, the HMDs are wired into a non-interruptible DI module), the simplest fix is to add a deterministic correction to the captured time and filter the result.

1.1 Subtract the OB37 Period from the First and Last Count

When the HMD edge fires, the OB37 counter has just been incremented. The first "delta" sample therefore represents 0 to T_OB of real elapsed time, and the last "delta" sample represents T_real − T_OB to T_real of real elapsed time. The unbiased estimate of T_real is:

T_real_corrected = T_OB × (N − 1)

where N is the number of OB37 calls observed between the two HMD edges. For the 1.25 s example, N = 156 at 8 ms, and the corrected time is 155 × 8 ms = 1240 ms.

1.2 Add a Moving-Average Filter

With the bias removed, the residual error is quantisation + jitter, both bounded by ±8 ms. A 10-sample moving average on the speed output brings the residual well under ±0.1% on a 1.25 s transit time. Implementation in SCL (TIA Portal V18, S7-1516):

// Speed capture in OB37
VAR
    HMD1_Edge_RTrig : R_TRIG;
    HMD2_Edge_RTrig : R_TRIG;
    Tick_Counter    : DINT;        // OB37 period count
    T_OB_ms         : REAL := 8.0; // OB37 period in ms
    T_transit_ms    : REAL;
    v_mps           : REAL;
    Path_m          : REAL := 20.0;
    Speed_Fifo      : ARRAY[1..10] OF REAL;
    Fifo_Idx        : INT;
    v_avg_mps       : REAL;
    i               : INT;
END_VAR

BEGIN
    HMD1_Edge_RTrig(CLK := "HMD1_Input");
    HMD2_Edge_RTrig(CLK := "HMD2_Input");

    IF HMD1_Edge_RTrig.Q THEN
        Tick_Counter := 0;
        "Capture_Start" := TRUE;
    END_IF;

    IF "Capture_Start" AND HMD2_Edge_RTrig.Q THEN
        // Subtract the OB37 period on the first tick to remove the first-tick bias
        T_transit_ms := (Tick_Counter - 1) * T_OB_ms;
        v_mps := Path_m / (T_transit_ms / 1000.0);

        // Push into the moving average FIFO
        Speed_Fifo[Fifo_Idx + 1] := v_mps;
        Fifo_Idx := (Fifo_Idx + 1) MOD 10;
        v_avg_mps := 0.0;
        FOR i := 1 TO 10 DO
            v_avg_mps := v_avg_mps + Speed_Fifo[i];
        END_FOR;
        v_avg_mps := v_avg_mps / 10.0;

        Tick_Counter := 0;
        "Capture_Start" := FALSE;
    ELSIF "Capture_Start" THEN
        Tick_Counter := Tick_Counter + 1;
    END_IF;
END
The −1 in (Tick_Counter − 1) × T_OB removes the bias added on the call that fires the rising edge. If the application reads the DI before or after the OB37 period update, validate the sign by sweeping the input against a function generator: with 1.000 s pulse spacing and T_OB = 8 ms, the captured value should be 1000.0 ± 8.0 ms.

Solution 2: Hardware Interrupt on a DI Module

If at least one of the HMDs is wired to a digital input that supports hardware interrupts (the S7-1500 SM 521 DI 16×24 V DC HF, 6ES7521-1BH00-0AB0, supports hardware interrupts on any channel), the time capture can be done at the full 1 ns resolution of the PLC's backplane counter.

Procedure in TIA Portal:

  1. Open the device configuration of the S7-1500 CPU, navigate to the DI module that hosts the HMD inputs.
  2. In the module properties, enable "Hardware interrupt" for the HMD1 and HMD2 channels. Set the trigger to "Rising edge".
  3. Add OB40 (hardware interrupt OB) to the project. The OB40 priority class is fixed at 16 by the system, and its local time stamp OB40_TIMESTAMP (TIME data type, ns resolution on the S7-1500) is the high-resolution value needed for the capture.
  4. In OB40, read the timestamp, identify which HMD fired from the OB40_POINT_ADDR local data, and store the time in a global LREAL variable.
  5. In OB1 (or a low-priority cyclic OB), compute the speed from the two timestamps.
// OB40 (hardware interrupt) - S7-1500 TIA Portal V18
DATA_BLOCK "HMD_Capture_DB"
VAR
    HMD1_ts_us : LREAL;   // microseconds
    HMD2_ts_us : LREAL;
    HMD1_Valid : BOOL;
    HMD2_Valid : BOOL;
    v_mps      : REAL;
END_VAR
END_DATA_BLOCK

// OB40 body
IF OB40_POINT_ADDR = 0 THEN   // HMD1 channel index
    "HMD_Capture_DB".HMD1_ts_us := UDINT_TO_LREAL(OB40_TIMESTAMP) / 1.0e9; // ns to s
    "HMD_Capture_DB".HMD1_Valid := TRUE;
ELSIF OB40_POINT_ADDR = 1 THEN // HMD2 channel index
    "HMD_Capture_DB".HMD2_ts_us := UDINT_TO_LREAL(OB40_TIMESTAMP) / 1.0e9;
    "HMD_Capture_DB".HMD2_Valid := TRUE;
END_IF;

// In OB1, low priority
IF "HMD_Capture_DB".HMD1_Valid AND "HMD_Capture_DB".HMD2_Valid THEN
    "HMD_Capture_DB".v_mps := 20.0 /
        ("HMD_Capture_DB".HMD2_ts_us - "HMD_Capture_DB".HMD1_ts_us);
    "HMD_Capture_DB".HMD1_Valid := FALSE;
    "HMD_Capture_DB".HMD2_Valid := FALSE;
END_IF;

Resolution at the OB40 timestamp is 1 ns on the S7-1500, with an end-to-end accuracy of about ±10 µs (cable delay + input filter + interrupt latency). That is six orders of magnitude better than the OB37-based approach, and the 6% error vanishes. Refer to the S7-1500 SM 521 manual for hardware interrupt wiring details and the S7-1500 CPU system manual for OB40 semantics.

Solution 3: Dedicated Counter Module

For the highest accuracy — sub-microsecond — use an ET 200SP TM counter module or an S7-300 FM 350-1 / FM 350-2 counter module. These modules carry an internal 16 MHz (62.5 ns) system clock, can latch on a hardware event, and return the latch value with zero CPU involvement.

Counter modules suitable for HMD-to-HMD time capture
Module MLFB Internal clock Resolution Notes
ET 200SP TM Count 1×24 V 6ES7138-6AA01-0BA0 16 MHz 62.5 ns 1 channel, 24 V, supports HW gate, latch on event
ET 200SP TM PosInput 1 6ES7138-6BA01-0BA0 1 MHz / 16 MHz 62.5 ns / 1 µs 1 SSI or incremental channel, event latch
S7-300 FM 350-1 6ES7350-1AH03-0AE0 1 MHz 1 µs Legacy, 1 channel, 24 V counter
S7-300 FM 350-2 6ES7350-2AH00-0AE0 1 MHz 1 µs 8 channels, counter and gating

Wiring the two HMDs to two hardware-gate inputs of an ET 200SP TM Count 1×24 V (6ES7138-6AA01-0BA0) gives a 62.5 ns latch value, more than 100 000× the resolution of the OB37 approach. The TM Count module exposes the latch via a data record (DS) read; the S7-1500 reads the latch asynchronously via the RDREC / WRREC instructions without disturbing the count. For a 1.25 s measurement the latch will accumulate 20 000 000 counts at 16 MHz, well within the 32-bit count range.

Step-by-Step: Diagnosing an Existing OB37 Implementation

  1. Connect a function generator to the two HMD inputs. Set 1.000 Hz / 50% duty, 24 V push-pull. Use a precision timer on the function generator (resistance tolerance ≤ 0.1%).
  2. In TIA Portal, go online and monitor the OB37 counter that is supposed to count "ticks between two edges". Force the counter to zero on the first edge, increment every OB37 call, latch on the second edge.
  3. Plot the count over 60 s. The expected average for a 1 s pulse is 125 ± 0 ticks at 8 ms. If the average is consistently 117 to 118, the offset is 9 to 10 OB37 periods, which is the 6% error source.
  4. Check the trigger source: is the rising edge of HMD2 detected inside the same OB37 cycle as the latch, or is the latch incremented one extra cycle? The "first tick bias" needs to be removed as shown in Solution 1.1.
  5. Re-measure with the correction applied. The remaining error budget should be ±0.7% (one OB37 period) before averaging, well under the 6% threshold.
  6. Cross-check the path constant: survey the HMD-to-HMD distance with a steel tape. A 1% error in path length is a 1% error in speed. Confirm that the path constant in the code is updated to the surveyed value.

Step-by-Step: Migrating to a Hardware Interrupt

  1. Confirm that the HMD DI module is an HF-type SM 521. LF-type modules (e.g. 6ES7521-1BL00-0AB0) do not support hardware interrupts and cannot host OB40 events.
  2. Right-click the CPU in the project tree → Add new block → OB → Hardware interrupt. TIA Portal generates a stub OB40 with the correct interface (OB40_EV_CLASS, OB40_POINT_ADDR, OB40_TIMESTAMP).
  3. In the device configuration of the DI module, open the channel that holds HMD1, tick "Hardware interrupt", and set the trigger to "Rising edge". Repeat for HMD2 on the same or a different DI byte.
  4. Compile the project, download to the CPU, and go online. Force-set the HMD input with a 24 V source. Confirm that OB40 fires (use the "Block status" view of OB40 in TIA Portal) and that OB40_TIMESTAMP is monotonically increasing.
  5. Implement the SCL code in Solution 2, build the HMI tag for v_mps, and overlay the result on a strip chart to compare against a reference tachometer.
  6. Validate end-to-end accuracy: with 1.000 s function-generator pulses, the computed speed should be 20.000 ± 0.005 m/s.

Verification

After applying the correction or migrating to a hardware interrupt, verify as follows:

  • Functional: trigger a known 1.000 s pulse pair, confirm the reported speed is 20.000 ± 0.020 m/s for OB37 + averaging, or ± 0.005 m/s for OB40.
  • Operational: let the mill run for 8 h, log v_avg_mps to a CSV, plot the distribution. The standard deviation should drop from ~0.4 m/s to < 0.05 m/s when the hardware interrupt is in use.
  • Diagnostics: enable the S7-1500 diagnostic buffer (online → Online & diagnostics → Diagnostic buffer). A correct implementation shows no OB priority-class overrun (no OB80 events) and no OB40 queue-overflow (no OB73 events).
  • Calibration: at the next planned mill stop, re-survey the HMD-to-HMD distance with a steel tape. A 1% error in path length is a 1% error in speed. Confirm that the path constant in the code is updated to the surveyed value.
  • Resolution sanity check: the speed resolution at 1.25 s is path / T² × dT = 20 / 1.5625 × 8e-3 = 0.102 m/s per tick. After 10-sample averaging the resolution improves to 0.102 / sqrt(10) = 0.032 m/s, which is the noise floor to expect on a strip chart.

Troubleshooting Matrix

Symptom → likely cause → corrective action
Symptom Likely cause Action
Speed reads +5% to +10% high OB37 first-tick bias Subtract T_OB from the tick count, as in Solution 1.1
Speed reads -5% to -10% low OB37 period subtracted on every iteration, not just on first/last Re-check the accumulator code, single-pass increment only
Speed output is jittery, ±1 m/s No averaging, single-tick quantisation Add 10-sample moving average
OB40 never fires DI module is LF, not HF, or hardware interrupt not enabled in the channel config Swap to SM 521 HF, enable HW interrupt in device config
OB40 fires twice per HMD pulse Input filter too short, contact bounce on the HMD relay Increase input filter to 1 ms in the DI channel config, add de-bounce FB
Speed reads 0.0 m/s constantly HMD1_Valid or HMD2_Valid flag never cleared, latch logic stuck Reset flags after each calculation, add a watchdog timer that re-arms after 5 s of inactivity
Speed drifts over 8 h by ±0.5 m/s Temperature drift in the function generator or HMD sensor alignment Re-survey distance, re-time the function generator, check HMD lens cleanliness
OB80 (time error) reported in the diagnostic buffer OB37 execution time exceeds 8 ms period Increase OB37 period to 16 ms or shorten the OB body
Speed reads 40.0 m/s on first scan Tick_Counter initialised to 0 and divided into path before first HMD2 edge Guard the speed calculation with a "first valid measurement" flag

Safety and Commissioning Notes

  • Always wire the HMD supply through an isolated 24 V DC power supply that meets the relevant rolling-mill installation standard (e.g. IEC 60204-1 for electrical equipment of machines). Galvanic isolation prevents ground loops from injecting noise into the DI input and triggering spurious OB40 events.
  • Set the DI input filter in TIA Portal to 1 ms for the HMD channels. A 15 m/s object with a 1 m HMD beam produces a ~67 ms pulse; 1 ms of filtering is well below the pulse width and removes the relay contact bounce that would otherwise cause OB40 to fire twice.
  • During commissioning, park the object at a known position and use a 24 V push-button to manually fire each HMD. Verify the OB40 / OB40_TIMESTAMP on the online block status, then introduce a calibrated pulse train from the function generator for the 1.000 s sanity check.
  • If the HMD output is a relay contact (not a solid-state output), use a 24 V wetting voltage with a 1 kΩ pull-up so that the contact closes cleanly to 0 V. Open-collector HMDs need a 10 kΩ pull-up to 24 V.

FAQ

Why does the speed read exactly 6% high and not 0.6% or 60%?

The 6% corresponds to ~75 ms of phantom time on a 1.25 s real transit, which is 9 to 10 OB37 periods of bias. The bug is almost always the OB37 period being added to the counter on the call that fires the rising edge of HMD2, plus the fact that the count is not decremented to account for that "free" tick.

Can I use OB1 instead of OB37 for a better result?

No. OB1 has no fixed period; it is the main scan. Its execution time varies with the program length and the I/O update, so any timestamp derived from OB1 is worse than OB37. The right tool is a hardware interrupt (OB40) or a counter module (TM Count, FM 350).

Do I really need a counter module for a 20 m, 1.25 s application?

No. The OB40 hardware interrupt on an HF DI module is sufficient and gives ±10 µs accuracy, which is 0.001% of the 1.25 s transit. A counter module is justified only when the transit time is below 50 ms or the HMDs carry 24 V signals with high contact bounce.

What is the maximum OB37 frequency I can set on an S7-1500?

The S7-1500 cyclic interrupt OBs (OB30 to OB38) accept periods from 500 µs (OB30) to 60000 µs (OB38) in 500 µs steps. The practical lower limit is set by the OB execution time: if OB37 runs longer than the configured period, the CPU raises OB80 (time error) and stops the OB. Always size OB37 to 2× to 3× the worst-case execution time.

How do I confirm that the HMD signal is reaching the DI module within the OB40 window?

Use a scope on the HMD output (24 V side) and the corresponding DI channel LED. The DI input filter, configured in the channel properties, must be shorter than the shortest HMD pulse you expect. For a 15 m/s object and a 1 m HMD beam, the pulse is ~67 ms; a 1 ms filter is therefore safe. The hardware interrupt edge detection is asynchronous to OB40 and does not depend on the input filter.

Back to blog