Resolving HMI Value Flicker with a Structured Text DEFLICKER FB

Stefan Weidner6 min read
HMI ProgrammingOther ManufacturerTechnical Reference
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

A REAL value read over a noisy interface makes the HMI digit toggle, for example between 19.9 and 20.0, whenever the noise straddles a rounding boundary. Because of the carry, the toggle can reach the most significant digit. A short Structured Text function block that switches between the truncated and the rounded value holds the digit steady and adds no filter lag. It works as long as the noise peak-to-peak stays under half a display count.

Where on the path from sensor to HMI digit does the flicker start?

Noise enters at the first two hops. The flicker is created at the last one, where the value is quantized to display resolution. Take a tag sitting at 19.95 with a few hundredths of noise. The noise does no harm in the REAL itself, but it crosses the boundary between 19.9 and 20.0 on every other sample. A 19.95 to 20.0 transition changes three digits at once.

Hop What happens to the value What to check
Transmitter / sensor wiring Electrical noise, ground loops Shield termination, single-point grounding
Analog input or serial/fieldbus interface Noise digitized into the raw count Module filter settings, conversion resolution
PLC REAL tag (IN) Scaled value carries the noise Peak-to-peak spread over a window
HMI numeric field Rounding to N decimals turns sub-count noise into digit toggling Decimal places configured on the field

Fix the physical layer first. Measure what remains at the PLC tag with a min/max capture run under worst-case plant conditions, with drives and contactors operating:

IF bResetPP THEN
  maxV := IN; minV := IN; bResetPP := FALSE;
END_IF
IF IN > maxV THEN maxV := IN; END_IF
IF IN < minV THEN minV := IN; END_IF
ppNoise := maxV - minV;

Check: ppNoise has settled with the process at steady state. This number drives the next decision.

How do I choose DEC from the measured noise?

DEC sets the display resolution. Use 10 for tenths, 100 for hundredths, and so on. Pick the finest resolution whose digit does not itself contain the noise, using this rule: ppNoise × DEC < 0.5. The reason for the 0.5 limit is in the next section.

Measured ppNoise DEC Noise in display counts Result
0.03 100 3.0 Flickers, too fine
0.03 10 0.3 Stable
0.3 10 3.0 Flickers
0.3 1 0.3 Stable
0.004 100 0.4 Stable, small margin

Check: ppNoise × DEC is below 0.5 with margin. If the product sits at 0.4 or higher, go one decade coarser or reduce noise upstream.

What does the block compute each scan?

FUNCTION_BLOCK DEFLICKER
VAR_INPUT
  IN, DEC: REAL;
END_VAR
VAR_OUTPUT
  OUT: REAL;
END_VAR
VAR
  inpTrunc, inpRound, inpTruncPrev, inpRoundPrev: DINT;
END_VAR

inpRound := REAL_TO_DINT(IN * DEC);
inpTrunc := TRUNC(IN * DEC);
IF inpRound <> inpRoundPrev THEN
  OUT := inpTrunc / DEC;
ELSIF inpTrunc <> inpTruncPrev THEN
  OUT := inpRound / DEC;
END_IF
inpRoundPrev := inpRound;
inpTruncPrev := inpTrunc;

Rounding boundaries sit at half-counts (199.5). Truncation boundaries sit at whole counts (200.0). The two sets interleave exactly half a count apart. Noise smaller than half a count can therefore cross only one kind of boundary at a time, and the block responds as follows:

  • If the rounded value toggles, the truncated value is steady, so the block outputs the truncated value.
  • If the truncated value toggles, the rounded value is steady, so the block outputs the rounded value.
  • If neither changes, OUT holds.

A genuine process change moves both values alternately, and OUT follows each step in the same scan. There is no time constant involved.

The block depends on two conversion semantics. REAL_TO_DINT must round, and TRUNC must truncate toward zero. If the target's REAL_TO_DINT truncates, both variables are always equal and the block never switches. Some compilers also reject DINT/REAL division or perform it as integer division. A portable body with explicit conversions and a first-scan initialization looks like this:


Check: In online mode, write IN = 19.97 with DEC = 10. You should read inpRound = 200 and inpTrunc = 199. If both read 199, the rounding conversion is wrong for this platform.

How do I wire the instance between the input tag and the HMI tag?

  1. Declare one instance per displayed channel. The block stores previous values, so a shared instance or a FUNCTION conversion will not work.
  2. Call the instance unconditionally every scan, in the same task that updates IN. For example: deflicker1(IN := rawValue, DEC := 10, OUT => hmiValue);
  3. Bind the HMI numeric field to OUT, not to IN.
  4. Set the field's decimal places to match DEC: one decimal for 10, two for 100. Fewer decimals than DEC reintroduces a rounding boundary inside the HMI, and the flicker returns.
  5. Leave alarms, interlocks, PID, and logging on IN.

Check: Force IN to a constant. The HMI should show that value at DEC resolution with no motion.

When does the block stop holding the digit, and what replaces it?

Condition Effect Action
Noise ≥ 0.5 display count Both boundaries crossed. The rounded branch wins, and the truncated value toggles. Use a coarser DEC or pre-filter
IN × DEC outside DINT range (±2,147,483,647) Conversion overflow Use a lower DEC or rescale engineering units
Output on the truncated path Display may be up to 1 count low in magnitude Acceptable for coarse display only
Negative values near zero Truncation bin at 0 spans −0.99 to +0.99 counts Display holds 0.0 and never shows −0.0
Method Display lag Handles noise ≥ 0.5 count Display error
DEFLICKER (trunc/round switch) None No Up to 1 count
First-order low-pass Time-constant dependent Yes Transient during ramps
Moving (sliding window) average About half the window Yes Transient during ramps

A low-pass filter is adequate where the operator does not need fast updates. When noise exceeds half a count, use a short moving window to bring it under 0.5 counts, then pass the result through DEFLICKER.

How do I prove the display is stable end to end?

  1. Offline, with DEC = 10, drive IN with alternating test values and confirm OUT against this table:
    IN alternates Internal behavior Expected OUT
    19.94 ↔ 19.96 Round 199↔200, trunc steady at 199 19.9 steady
    19.98 ↔ 20.02 Trunc 199↔200, round steady at 200 20.0 steady
    19.94 ↔ 20.02 Both change (0.8 count span) Toggles 19.9/20.0, the expected failure
    Ramp 19.80 → 20.20 Alternating changes Steps 19.8 … 20.2 with no lag
  2. Online, rerun the min/max capture under worst-case load and confirm ppNoise × DEC < 0.5.
  3. Park the process near a half-count boundary (x.x5) and then near a whole-count boundary (x.x0). Watch the HMI field at each point for several minutes. No digit should move.
  4. Ramp the process through several counts and confirm the HMI steps in the same update as the IN trend.
  5. Trend IN and OUT together. Confirm that |OUT − IN| never exceeds 1/DEC.

FAQ

Does the DEFLICKER block add lag like a low-pass filter?

No. OUT updates in the same scan that the rounded or truncated value changes, so ramps display without delay. The trade-off is a display error of up to one count of 1/DEC.

Can I use the deflickered value for alarms or PID control?

No. OUT can be up to one display count away from the true value and holds between boundary crossings. Feed alarms, interlocks, and control loops from IN, and use OUT for the HMI field only.

Can I combine a moving average with the deflicker block?

Yes. When measured peak-to-peak noise is 0.5 display count or more, a short moving window ahead of the block brings it below 0.5 counts, and the block removes the remaining toggling. Display lag then equals the averaging delay, roughly half the window.

Does the trunc/round method work with negative values?

Yes. TRUNC rounds toward zero, so truncation and rounding boundaries stay half a count apart on both sides of zero. The zero bin is wider, and the display holds 0.0 rather than flickering to −0.0.

Back to blog