From a current loop to a number
Most plant measurements still travel as a current between 4 and 20 milliamps. A level transmitter on a 30 m³ buffer tank, a Rosemount 3051 on a pump discharge, a temperature transmitter on a jacketed mixer: each pushes a current through a two-wire loop, and the analog card in the rack measures it.
The 4 mA bottom is deliberate. A two-wire transmitter powers itself from the loop, so it needs a few milliamps even at zero. And a "live zero" means 0 mA is not a valid reading: it means a broken wire, a blown fuse or a dead transmitter.
The card does not give your program milliamps. It gives an integer, and the integer range depends on the vendor:
| Card | 4 mA | 20 mA | Notes |
|---|---|---|---|
| Siemens S7-1500 / ET 200SP AI | 0 | 27,648 | Overrange up to 32,511; 32,767 (7FFFh) when a diagnosed fault such as wire break is active |
| Beckhoff EL3052 / EL3152 | 0 | 32,767 | 16-bit signed; underrange shows as 0, error bits in the status word |
| Rockwell CompactLogix 5069-IF8 | configurable | configurable | The module usually scales to engineering units itself; check the module properties before scaling again |
| CODESYS-based I/O (WAGO 750-455 etc.) | 0 | 32,760 or 32,767 | Read the module datasheet; the last few counts differ |
Two rules follow. Look the range up for the exact module, and never assume 32,767. And keep raw and engineering values in separate variables: diagnostics need the first, control the second.
The loop before the card
A program can only scale what the loop delivers, and a surprising number of "PLC problems" on analog signals are loop problems. Knowing how the loop is built tells you which faults are possible and what each one looks like in the raw value.
In a two-wire (loop-powered) loop, one pair of wires carries both the transmitter's power and its signal. A 24 V DC supply, usually on the analog card or in the panel, drives current round the loop: out to the transmitter, through it, back to the card's measuring resistor. The transmitter controls how much current flows; the card measures it. A four-wire transmitter has its own mains or 24 V supply and drives the current out on a separate pair, so it is the source and the card is a passive input. Analog cards are ordered or configured for one or the other (Siemens ET 200SP, for example, has separate 2-wire and 4-wire measuring types for current), and wiring a four-wire transmitter to a loop-powering channel puts two supplies in series.
The loop has a voltage budget. Every element drops some voltage at the current flowing, and the transmitter needs a minimum voltage at its own terminals to work. Emerson specifies 10.5 V DC at the terminals for the 4–20 mA HART version of the Rosemount 3051. Work through a loop:
| Element | At 22 mA |
|---|---|
| Supply | +24.0 V |
| Transmitter minimum at its terminals | −10.5 V |
| Left for everything else in the loop | 13.5 V |
| Maximum loop resistance, 13.5 V ÷ 0.022 A | about 614 Ω |
| 250 Ω HART resistor, if the card does not provide one | 250 Ω |
| 1 km of loop (2 × 500 m) of 0.75 mm² copper | about 23 Ω |
| What remains for the card input, barriers and indicators | about 340 Ω |
The calculation uses 22 mA rather than 20 mA because a transmitter signals a fault above 21 mA (more on that below), and the loop must still deliver that current or the fault signal never arrives. Put the loop's numbers in the I/O list next to the transmitter's datasheet values.
Linear scaling
Turning counts into engineering units is a straight line through two known points: (x0, y0) at the bottom of the range and (x1, y1) at the top. For any reading x:
y = (x − x0) · (y1 − y0) / (x1 − x0) + y0
On a Siemens card for a 0–5 m level transmitter that is x0 = 0, x1 = 27,648, y0 = 0.0, y1 = 5.0. The same formula scales counts to milliamps (y0 = 4, y1 = 20), milliamps to percent, or anything linear to anything linear. It belongs in a function, written once:
FUNCTION F_Scale : REAL
VAR_INPUT
X : REAL; (* reading *)
X0 : REAL; (* reading at the bottom of the range *)
X1 : REAL; (* reading at the top of the range *)
Y0 : REAL; (* engineering value at X0 *)
Y1 : REAL; (* engineering value at X1 *)
END_VAR
IF X1 = X0 THEN
F_Scale := Y0; (* misconfigured range: never divide by zero *)
RETURN;
END_IF;
F_Scale := (X - X0) * (Y1 - Y0) / (X1 - X0) + Y0;
END_FUNCTION
PROGRAM Main
VAR
LoopmA : REAL := 12.0; (* input: current in the loop *)
RawS7 : INT; (* what a Siemens card would report *)
RawEL : INT; (* what a Beckhoff EL3052 would report *)
LevelFromS7 : REAL; (* m *)
LevelFromEL : REAL; (* m *)
END_VAR
(* Simulate the two cards measuring the same current *)
RawS7 := REAL_TO_INT(F_Scale(X := LoopmA, X0 := 4.0, X1 := 20.0, Y0 := 0.0, Y1 := 27648.0));
RawEL := REAL_TO_INT(F_Scale(X := LoopmA, X0 := 4.0, X1 := 20.0, Y0 := 0.0, Y1 := 32767.0));
(* What your program does: counts to metres *)
LevelFromS7 := F_Scale(X := INT_TO_REAL(RawS7), X0 := 0.0, X1 := 27648.0, Y0 := 0.0, Y1 := 5.0);
LevelFromEL := F_Scale(X := INT_TO_REAL(RawEL), X0 := 0.0, X1 := 32767.0, Y0 := 0.0, Y1 := 5.0);
END_PROGRAMType 4, 12 and 20 mA into LoopmA. The counts differ but the level is the same, as long as each program uses its own card's range. Then type 2 mA. Both cards produce negative counts and the "level" goes negative. That is the next problem.
Do the scaling in REAL. Integer versions like Raw * 500 / 27648 look tempting for a level in centimetres, but 13824 * 500 is 6.9 million, far beyond an INT. Whether the intermediate result wraps depends on the compiler (some widen it to 32 bits, some do not), so convert to REAL first and the question never arises. TIA Portal's NORM_X / SCALE_X pair and the old SCALE FC do the same job; in Studio 5000 ladder you would reach for SCP or a CPT. In ST the formula is shorter than the call.
Ranges that do not start at zero
Nothing in the formula assumes the engineering range starts at zero or rises with the current. Three cases come up often:
- A compound range. A pressure transmitter ranged −1 to +9 bar (vacuum to pressure) gives y0 = −1.0, y1 = 9.0. At 4 mA it reads −1 bar, at 6.4 mA 0 bar.
- A suppressed or elevated zero. A level transmitter mounted 300 mm above the tank floor, ranged 0.3 to 2.3 m, gives y0 = 0.3, y1 = 2.3. The scaling does the offset; do not add it again in the program.
- A reverse-acting range. Some differential-pressure level installations, and some valve position transmitters, are ranged so that 4 mA is the top. Then y0 is the high value and y1 the low one, and the formula still works. Write the reversal into the scaling parameters, with a comment, and never as a
100.0 - xsomewhere downstream where nobody will look for it.
The transmitter's own range (its LRV and URV, lower and upper range values, set in the transmitter by HART or on its display) and the PLC's scaling must agree. If someone reranges a transmitter in the field from 0–5 m to 0–6 m and nobody changes the PLC, every reading is wrong by up to a metre, and nothing in the program can tell. The I/O list should carry the transmitter range, and commissioning should check it with a loop calibrator at 4, 12 and 20 mA.
Resolution: how fine a count is
A count is not a measurement unit you can make finer by adding decimal places. On the Siemens card above, 0–5 m spread over 27,648 counts gives 5000 mm ÷ 27,648 ≈ 0.18 mm per count. The Beckhoff EL3052 is a 12-bit terminal that reports in a 16-bit representation: it has 4,096 steps across its range, so the raw value moves in steps of 8 counts and the level in steps of about 1.2 mm. Neither is a problem for a level. Both show up if you compute a rate of change from consecutive samples, because a single-count step divided by a 10 ms interval is a large apparent rate (the rate section below comes back to this).
Resolution is also not accuracy. The transmitter, its calibration and the installation contribute far larger errors than a count. Display a value to the precision the measurement chain supports, which for most process signals is three significant figures, and the operators stop asking why the level "moves" when nothing happens.
Is the signal believable? Wire break and NAMUR NE 43
A scaled value is only useful if the signal is real. NAMUR recommendation NE 43 gives transmitters and control systems a shared language for this:
| Loop current | Meaning |
|---|---|
| 3.8 – 20.5 mA | Valid measurement (slightly outside 4–20 is still a reading, just saturated) |
| 3.6 – 3.8 mA or 20.5 – 21.0 mA | Out of range: the process is beyond what the transmitter is set up for |
| below 3.6 mA | Failure: transmitter fault signal, or a broken wire / blown fuse at 0 mA |
| above 21.0 mA | Failure: transmitter fault signal, or a short circuit |
A NAMUR transmitter that detects an internal fault deliberately drives its output to ≤ 3.6 mA or ≥ 21 mA, so these thresholds catch transmitter failures as well as wiring faults. Which way it drives is usually selectable in the transmitter (often a jumper or a configuration setting called alarm direction, high or low), and it should be chosen with the process in mind: on a tank's overfill measurement, a fault that reads full and stops the inlet is safer than one that reads empty. Your program should compute the current from the raw value, test it against these limits and treat the reading as bad when it fails. Ignore this and a broken wire does something specific and dangerous:
What to do with a bad signal is a process decision: hold the last good value for a few seconds and then go to a safe state, substitute a fixed value, or switch the loop to manual. Whichever you choose, make it visible with an alarm and a quality bit on the HMI, so nobody spends a night chasing a "process problem" that is a loose terminal. The first challenge below builds this check.
Use the card's diagnostics as well
The current is not the only evidence. Modern analog cards diagnose the channel themselves and report it alongside the value, and a good program reads both.
- Siemens S7-1500 and ET 200SP analog modules have channel diagnostics (wire break, overflow, underflow) that you enable per channel in the device configuration. With them enabled, a diagnosed fault replaces the value with 32,767. That converts to about 23 mA, so a current-only check classes a broken wire as over-range, not under. Enable the module's value status as well and each channel gets a quality bit in the process image (1 = value valid), which tells you the reading is bad without decoding the number.
- Beckhoff EL30xx/EL31xx terminals return a status word with each channel's value: underrange, overrange and error bits among others, linked in TwinCAT as separate variables. The error bit is the one to use for "bad".
- Rockwell 5069 and 1756 analog modules report per-channel fault and status bits in the module's input tags, next to the channel data.
Combine the two: Bad := CardFault OR mA < 3.6 OR mA > 21.0;. The card bit catches what the current cannot show (a module fault, a channel that has lost its configuration); the NE 43 test catches a transmitter signalling its own fault through an otherwise healthy channel.
Square-root extraction for flow
Not every transmitter's signal is linear in the quantity you want. The classic case is flow measured with an orifice plate or a venturi: the differential pressure across the restriction rises with the square of the flow. Double the flow and the DP goes up four times. To get flow back, the square root of the DP has to be taken somewhere:
Flow = FlowMax · √(DP / DPmax)
That "somewhere" is either the transmitter or the PLC, and the single most important rule is that it is done in exactly one of them. Most DP transmitters, the Rosemount 3051 among them, can be configured for a square-root output, in which case the 4–20 mA is already proportional to flow and the PLC scales it linearly. If the transmitter outputs linear DP, the PLC takes the root.
PROGRAM Main
VAR
DpPct : REAL := 25.0; (* input: true DP across the orifice, % of range *)
TxDoesSqrt : BOOL := TRUE; (* input: transmitter configured for square-root output *)
TrueFlow : REAL; (* the real flow, % of max *)
SignalPct : REAL; (* what arrives on the 4-20 mA loop, % *)
ProgLinear : REAL; (* a program that scales the signal linearly *)
ProgTakesRoot : REAL; (* a program that takes the square root *)
END_VAR
TrueFlow := 100.0 * SQRT(MAX(DpPct, 0.0) / 100.0);
(* The transmitter *)
IF TxDoesSqrt THEN
SignalPct := TrueFlow; (* already proportional to flow *)
ELSE
SignalPct := DpPct; (* proportional to DP *)
END_IF;
(* Two ways the PLC program might treat that signal *)
ProgLinear := SignalPct;
ProgTakesRoot := 100.0 * SQRT(MAX(SignalPct, 0.0) / 100.0);
END_PROGRAMAt 25 % DP the true flow is 50 %. With the transmitter extracting the root, the linear program is right and the program that takes the root again shows 70.7 %. Switch TxDoesSqrt off and the roles swap: now the program must take the root, and the linear one shows 25 %. Both mistakes agree with the right answer at exactly 0 % and 100 %, which is why they survive a commissioning check that only tests the end points. Check one point in the middle.
| True flow | DP | Root taken once | Root taken twice | Root never taken |
|---|---|---|---|---|
| 10 % | 1 % | 10 % | 31.6 % | 1 % |
| 50 % | 25 % | 50 % | 70.7 % | 25 % |
| 80 % | 64 % | 80 % | 89.4 % | 64 % |
| 100 % | 100 % | 100 % | 100 % | 100 % |
The square root has a second problem near zero. Its slope is steep at the bottom, so a tiny DP (noise, a transmitter zero that has drifted by 0.2 %) becomes a visible flow: √0.002 is 4.5 % of full flow from a DP of 0.2 %. A totaliser fed with that counts product all night through a closed valve. So every square-root flow gets a low-flow cutoff: below a set DP or flow, the flow is forced to zero. The cutoff also protects the maths, because a slightly negative DP from a zero drift would otherwise go into the square root.
Filtering noise
Analog signals are noisy. A level transmitter on an agitated tank sees the surface slop; a flow signal jitters with pump pulsation. The standard PLC answer is a first-order filter, also called exponential smoothing. Each scan, the filtered value moves a fixed fraction of the way towards the raw value:
Filtered := Filtered + K · (Raw − Filtered), with K = Ts / (Tf + Ts)
Ts is the scan (or task) time and Tf the filter time constant. With Ts = 0.1 s and Tf = 1 s, K is about 0.09: each scan the output covers 9 % of the remaining gap. After one time constant it has covered about 63 % of a step, after three about 95 %.
PROGRAM Main
VAR
Tf : REAL := 1.0; (* input: filter time constant, s *)
StepUp : BOOL; (* input: process level steps from 40 to 60 % *)
Seed : DINT := 12345; (* pseudo-random noise generator *)
Signal : REAL; (* what the transmitter sends: level + noise *)
Filtered : REAL;
K : REAL;
First : BOOL := TRUE;
Gap : REAL;
END_VAR
Seed := (Seed * 1103 + 12345) MOD 32768;
Signal := SEL(StepUp, 40.0, 60.0) + DINT_TO_REAL(Seed MOD 101) / 20.0 - 2.5; (* ±2.5 % noise *)
K := 0.1 / (MAX(Tf, 0.0) + 0.1); (* Ts = 0.1 s on this page *)
IF First THEN
Filtered := Signal; (* start at the signal, not at zero *)
First := FALSE;
END_IF;
Gap := Signal - Filtered;
Filtered := Filtered + K * Gap;
END_PROGRAMWatch Signal jump around by ±2.5 % while Filtered stays within a few tenths. Flip StepUp and notice the price: the filtered value takes seconds to follow a real change. Try Tf = 5 and the lag becomes obvious; try 0 and the filter switches off.
Cards filter too: an S7-1500 AI module has an integration time against 50/60 Hz mains noise and a smoothing setting; Beckhoff EL3xxx terminals have filters in their CoE objects. Use the card for electrical noise, the program for process noise, and record both in the I/O list.
Three details that decide whether a filter works
K depends on the scan time. The formula has Ts in it for a reason. Put the same code with a hard-coded K in a 10 ms task instead of a 100 ms one and the filter becomes ten times faster, because it takes ten times as many steps per second. In a cyclic task with a fixed period, compute K from that period. In a free-running task whose scan time wanders, measure the time since the last call (a running TON's ET, as in chapter 16) and compute K from that each scan.
Filter the right variable. Filter the engineering value or the raw count, and keep the unfiltered value as well. The trend and the high-high trip should usually see the unfiltered value; the display and the controller can use the filtered one. Write down which consumer gets which.
Decide what the filter does when the signal goes bad. If the filter keeps running on a value of 32,767 during a wire break, it takes several time constants to recover after the repair, and the alarms that use it go off on the way back down. The usual rule is to freeze the filter while the signal is bad and restart it from the first good reading afterwards, which is what the analog block later in this chapter does.
A moving average over the last N samples is the other common filter. It needs an array and an index (chapter 13), it delays a step by about N/2 samples, and it removes a periodic disturbance completely if N samples span exactly one period of it, which is useful against the stroke of a reciprocating pump. For random noise, the first-order filter does nearly as well with one variable instead of an array.
HH, H, L and LL alarms
A typical tank level carries four alarm limits: HH (high-high, trips the inlet), H (high, warns the operator), L (low, warns) and LL (low-low, trips the pump to protect it from running dry). Each limit needs two refinements or it will chatter:
- Hysteresis (dead band): the alarm comes on above the limit and goes off only below the limit minus a margin. A level sitting at 90.0 % with 0.3 % of noise would otherwise flick the H alarm on and off ten times a second, and the alarm log fills with rubbish. Chapter 6 introduced hysteresis and the dead-band starting values that alarm-management guidance suggests; here it lives in a reusable block.
- On-delay: the condition must persist for a set time before the alarm is raised. A wave on the surface should not trip the plant. Typical values are 2–10 s for process alarms, much shorter for trips.
One generic block covers all four limits, a natural use of the function blocks from chapter 18:
FUNCTION_BLOCK FB_LimitAlarm
VAR_INPUT
Value : REAL;
Limit : REAL;
Hyst : REAL; (* dead band below a high limit / above a low one *)
High : BOOL; (* TRUE = alarm above the limit *)
Delay : TIME; (* condition must persist this long *)
END_VAR
VAR_OUTPUT
Active : BOOL;
END_VAR
VAR
Dly : TON;
Beyond : BOOL;
Clear : BOOL;
END_VAR
IF High THEN
Beyond := Value > Limit;
Clear := Value < Limit - Hyst;
ELSE
Beyond := Value < Limit;
Clear := Value > Limit + Hyst;
END_IF;
Dly(IN := Beyond, PT := Delay);
IF Dly.Q THEN
Active := TRUE;
ELSIF Clear THEN
Active := FALSE;
END_IF;
END_FUNCTION_BLOCK
PROGRAM Main
VAR
Level : REAL := 50.0; (* input: tank level, % *)
AlmHH : FB_LimitAlarm;
AlmH : FB_LimitAlarm;
AlmL : FB_LimitAlarm;
AlmLL : FB_LimitAlarm;
END_VAR
AlmHH(Value := Level, Limit := 95.0, Hyst := 2.0, High := TRUE, Delay := T#1s);
AlmH(Value := Level, Limit := 85.0, Hyst := 2.0, High := TRUE, Delay := T#3s);
AlmL(Value := Level, Limit := 20.0, Hyst := 2.0, High := FALSE, Delay := T#3s);
AlmLL(Value := Level, Limit := 10.0, Hyst := 2.0, High := FALSE, Delay := T#1s);
END_PROGRAMType 86 and watch AlmH.Dly.ET count to 3 s before AlmH.Active comes on. Type 84: the alarm stays on, because 84 is inside the dead band. Type 82.9 and it clears. Type 86 for a second and back to 80, and nothing happens, because the delay filtered out the wave.
Notice the structure: the delay applies to raising the alarm, the hysteresis to clearing it. Latching alarms until an operator acknowledges them, first-out logic and alarm priorities are the business of chapter 22. This block only answers "is the condition true, convincingly?"
Where the HH and LL belong
The HH on a tank that can overflow and the LL on a pump that can be destroyed by running dry are protective functions. In a process plant, the ones that protect people or the environment belong in a safety instrumented system designed to IEC 61511, with their own transmitter or switch, not in the same PLC program and from the same transmitter as the level control. If a single failed transmitter can both cause the overfill (by reading low, so the control keeps filling) and blind the HH alarm that should catch it, the HH protects nothing. On a machine, the equivalent rule comes from the risk assessment under ISO 13849-1. The FB_LimitAlarm here is an operator alarm, and a good one, but it is not a safety function.
Rate of change
Some faults show in how fast a value moves rather than where it is. A level falling 1.5 %/s with every outlet shut is a leak; a bearing temperature climbing 5 °C per minute is in trouble long before its high limit; a jump of 40 % in one scan is a failed transmitter.
Rate of change is the difference between two readings divided by the time between them. Do not compute it scan to scan: on a 10 ms task, ordinary noise of 0.2 % becomes an apparent rate of 20 %/s. Sample at a fixed, longer interval instead (one second is typical for levels) and compare samples:
PROGRAM Main
VAR
OutletOpen : BOOL; (* input: outlet valve open *)
Leak : BOOL; (* input: simulate a leaking flange *)
Level : REAL := 80.0; (* simulated tank level, % *)
Prev : REAL;
Rate : REAL; (* %/s, negative = falling *)
SampleCnt : INT;
First : BOOL := TRUE;
LeakAlarm : BOOL;
END_VAR
(* Plant simulation, per 100 ms scan *)
IF OutletOpen THEN Level := Level - 0.05; END_IF; (* normal draw: 0.5 %/s *)
IF Leak THEN Level := Level - 0.15; END_IF; (* leak: 1.5 %/s *)
(* Sample once per second: 10 scans of this 100 ms task *)
IF First THEN
Prev := Level;
First := FALSE;
END_IF;
SampleCnt := SampleCnt + 1;
IF SampleCnt >= 10 THEN
SampleCnt := 0;
Rate := Level - Prev; (* change over 1 s, so already %/s *)
Prev := Level;
END_IF;
LeakAlarm := NOT OutletOpen AND Rate < -0.3;
END_PROGRAMOpen the outlet: the rate settles at −0.5 %/s and there is no alarm, because the drop is explained. Close it and switch Leak on: −1.5 %/s with the outlet shut raises LeakAlarm. On a real controller, put this in a cyclic task with a fixed period (a 100 ms cyclic interrupt OB on Siemens, a fixed-interval task in CODESYS or TwinCAT) so that counting calls really does count time. In a free-running task the scan time varies, and you would measure the sample interval with a timer instead. (The long decimals in the watch are this engine's 64-bit REAL arithmetic; a PLC's 32-bit REAL would show different rounding, and neither should ever be compared with =.)
Resolution comes back here. On a Beckhoff EL3052 a single 8-count step is about 0.024 % of span. Divided by a 10 ms scan interval, that one step is an apparent rate of 2.4 %/s, larger than the leak this program is looking for. Over a 1 s sample interval it is 0.024 %/s and disappears into the noise where it belongs.
A value that does not move
The opposite fault is a signal that stops changing. A transmitter with a frozen output (some failure modes hold the last current), a card channel that has stopped updating, or a value copied from a network device whose communication has dropped can all deliver a believable number that never moves. Real process signals always have a little noise. If a level on an agitated tank has not changed by a single count in five minutes while the agitator runs, the reading is suspect. A stuck-value check compares the current value with the one recorded at the start of a window and raises a maintenance alarm if the difference stays below one or two counts for the whole window. Use it on signals where a frozen value would go unnoticed, and qualify it with a condition that says the process should be moving.
One block per analog input
Everything so far in this chapter is a separate piece: scale, check, hold, filter. On a real project you want them together, in one function block, instantiated once per transmitter, so that every analog signal in the plant is treated the same way and a fix reaches all of them. This is the block most experienced integrators carry from project to project, and writing your own is a good test of chapters 17 and 18.
FUNCTION F_Scale : REAL
VAR_INPUT
X : REAL;
X0 : REAL;
X1 : REAL;
Y0 : REAL;
Y1 : REAL;
END_VAR
IF X1 = X0 THEN
F_Scale := Y0;
RETURN;
END_IF;
F_Scale := (X - X0) * (Y1 - Y0) / (X1 - X0) + Y0;
END_FUNCTION
FUNCTION_BLOCK FB_AnalogIn
VAR_INPUT
Raw : INT; (* counts: S7 card, 4..20 mA = 0..27648 *)
CardFault : BOOL; (* channel diagnostic or value status from the card *)
EuLo : REAL; (* engineering value at 4 mA *)
EuHi : REAL; (* engineering value at 20 mA *)
FilterS : REAL; (* filter time constant, s; 0 = off *)
Ts : REAL; (* task period, s *)
END_VAR
VAR_OUTPUT
mA : REAL; (* loop current, for diagnosis *)
Value : REAL; (* filtered engineering value; last good value while Bad *)
Bad : BOOL; (* card fault or NAMUR NE 43 failure current *)
Primed : BOOL; (* at least one good reading since start-up *)
END_VAR
VAR
Eu : REAL;
END_VAR
mA := F_Scale(X := INT_TO_REAL(Raw), X0 := 0.0, X1 := 27648.0, Y0 := 4.0, Y1 := 20.0);
Bad := CardFault OR mA < 3.6 OR mA > 21.0;
IF NOT Bad THEN
Eu := F_Scale(X := LIMIT(4.0, mA, 20.0), X0 := 4.0, X1 := 20.0, Y0 := EuLo, Y1 := EuHi);
IF NOT Primed OR FilterS <= 0.0 OR Ts <= 0.0 THEN
Value := Eu; (* first good reading, or filter off *)
Primed := TRUE;
ELSE
Value := Value + Ts / (FilterS + Ts) * (Eu - Value);
END_IF;
END_IF;
END_FUNCTION_BLOCK
PROGRAM Main
VAR
Raw : INT := 13824; (* input: raw counts, 12 mA *)
CardFault : BOOL; (* input: card diagnostic *)
LT101 : FB_AnalogIn; (* buffer tank level, 0..5 m *)
LevelM : REAL;
FillPermit : BOOL;
END_VAR
LT101(Raw := Raw, CardFault := CardFault, EuLo := 0.0, EuHi := 5.0, FilterS := 2.0, Ts := 0.1);
LevelM := LT101.Value;
FillPermit := NOT LT101.Bad AND LT101.Primed AND LevelM < 4.2;
END_PROGRAMType a few raw values and watch LT101.Value follow with a 2 s lag. Then type 32767 (a diagnosed wire break on an S7 card) or switch CardFault on: Bad comes on, Value freezes at the last good level and FillPermit drops. Type a good value again and the filter carries on from where it froze, instead of climbing back from 32,767's worth of nonsense.
Some design choices in the block are worth defending in a review:
- The engineering value is clamped to the 4–20 mA range before scaling, so a saturated 20.4 mA shows 5.0 m and not 5.13 m. The raw current is still available in
mAfor anyone diagnosing the loop. Primedstops the program acting on the initialValueof 0.0 before the first good reading. Without it, a tank whose transmitter is faulty at start-up looks empty, which is the broken-wire predict from earlier in a different costume.- The consumer decides what bad means. The block reports
Bad; it does not decide that the pump stops. The fill logic usesNOT LT101.Badas a permissive because, for this tank, a bad level means "stop filling". A different consumer of the same signal might hold its last output instead.
Production versions of this block add more outputs (separate under-range and over-range bits, a substitute value, a quality code for the HMI, a simulation input for testing) but the shape stays the same. Write it once, test it with a case table as in chapter 17, and use it for every 4–20 mA signal in the project.
Analog outputs
The same scaling runs backwards for an analog output. A valve positioner or a drive's speed reference takes 4–20 mA; the card takes counts; your program has a percentage. On a Siemens AQ card, 0 counts is 4 mA and 27,648 counts is 20 mA, exactly as for the input, so a 0–100 % command becomes Counts := REAL_TO_INT(F_Scale(X := Cmd, X0 := 0.0, X1 := 100.0, Y0 := 0.0, Y1 := 27648.0));.
Three rules keep outputs out of trouble:
- Clamp before you convert. A controller output of 104 % becomes 28,753 counts, inside the card's overrange; one of −3 % becomes negative. Clamp the percentage to 0–100 first, so the actuator only ever sees commands inside its calibrated range. Chapter 20 returns to this for controllers.
- Know which way the actuator fails. An air-to-open control valve closes on loss of air or loss of signal; at 4 mA it is shut. An air-to-close valve is the opposite, and its 4 mA is fully open. The direction comes from the process design (which way is safe for this valve), and the PLC scaling has to match it. Write the valve's action into the I/O list.
- Decide what the output does when the CPU stops. S7-1500 analog output modules offer a configurable reaction to CPU STOP: shut down (output 0 mA), keep the last value, or output a substitute value. Other platforms have equivalent fault-mode and program-mode settings per channel, Rockwell's analog output modules among them. A valve that holds its last position when the PLC stops may be exactly right for one loop and dangerous for another. Choose per channel, deliberately.
From measuring to controlling
So far the program has only watched the process. The next chapter closes the loop: ramping a setpoint so a heater or pump changes gently, and writing a PI controller that drives a valve or a drive speed until the measured value meets the setpoint, with a simulated water bath in the same program, so you can watch it settle.