Example: Analog Threshold Control#
Read a 0-10 V analog input and light one, two, three or four outputs as the signal rises, like a bar-graph level indicator. It exercises analog scaling, comparison logic and bit manipulation together.
Hardware#
Slot |
Module |
|---|---|
1 |
SLM-AI-8-V (8-channel 0-10 V analog input) |
2 |
SLM-DCO-15P (15-channel sourcing DC output) |
Feed channel 1 of the analog module from something you can vary across 0-10 V like a potentiometer across a 10 V reference, a signal calibrator, or an SLM-AO-8-V driven by a second program. Wire indicator lamps to output channels 1 through 4.
Backplane configuration#
Add both modules in order. Leave the analog module’s Reading Format on
Raw counts and Counts Scaling on Module resolution, the defaults. The
SLM-AI-8-V has a 13-bit converter, so the input arrives as a UINT from 0 to
8191.
The generated addresses:
Slot |
Channel |
Address |
Alias |
|---|---|---|---|
1 |
AI1 |
|
|
2 |
DO1 |
|
|
2 |
DO2 |
|
|
2 |
DO3 |
|
|
2 |
DO4 |
|
|
Recall that the analog input module contributes nothing to the discrete output
space, which is why the output module in slot 2 still starts at %QX0.0.
The program#
A POU in OpenPLC Editor is two separate things. The variables pane at the
top of the editor window declares what the program owns, and the code body
underneath holds the logic. You never type PROGRAM, VAR, END_VAR or
END_PROGRAM; the editor generates that scaffolding from the variables when it
builds the project.
Variables#
Declare these in the variables pane of the program POU. It has two views, switched with the table/code icons in its top-right corner; fill in the table by hand, or paste the declarations into text mode. Both tabs say the same thing:
Name |
Class |
Type |
Location |
Initial Value |
Documentation |
|---|---|---|---|---|---|
|
Local |
|
|
0-10 V as 0-8191 counts |
|
|
Local |
|
|
First lamp |
|
|
Local |
|
|
Second lamp |
|
|
Local |
|
|
Third lamp |
|
|
Local |
|
|
Fourth lamp |
|
|
Local |
|
Scaled reading, in volts |
||
|
Local |
|
How many lamps should be lit |
Switch the variables pane to text mode with the code icon in its top-right corner, then paste this in:
VAR
level_raw : UINT AT Level_Raw; (* 0-10 V as 0-8191 counts *)
bar_1 : BOOL AT Bar_1; (* First lamp *)
bar_2 : BOOL AT Bar_2; (* Second lamp *)
bar_3 : BOOL AT Bar_3; (* Third lamp *)
bar_4 : BOOL AT Bar_4; (* Fourth lamp *)
level_volts : REAL; (* Scaled reading, in volts *)
segments : INT; (* How many lamps should be lit *)
END_VAR
The Location column accepts either the IEC address or the alias defined in Backplane Configuration. Both views above use the aliases, which keep the variables attached to the right channels even if the IEC addresses change.
Code body#
(* At module resolution the 13-bit input reads 0-8191 for 0-10 V. *)
level_volts := UINT_TO_REAL(level_raw) / 8191.0 * 10.0;
(* Map 0-10 V onto 0-4 lit segments. Thresholds at 2, 4 and 6 V;
the fourth at 7.9 V rather than 8 V so a nominal 8 V signal
reliably lights it despite converter tolerance. *)
IF level_volts >= 7.9 THEN segments := 4;
ELSIF level_volts >= 6.0 THEN segments := 3;
ELSIF level_volts >= 4.0 THEN segments := 2;
ELSIF level_volts >= 2.0 THEN segments := 1;
ELSE segments := 0;
END_IF;
bar_1 := segments >= 1;
bar_2 := segments >= 2;
bar_3 := segments >= 3;
bar_4 := segments >= 4;
Why the thresholds are not evenly spaced#
Analog inputs do not read exactly their nominal value. Converter tolerance, reference drift and the source’s own accuracy mean a nominal 8 V might read 7.98 V. A threshold placed exactly at a level the source is expected to produce flickers or never triggers. The fourth threshold is therefore 7.9 V, not 8.0 V.
Adding hysteresis#
Real installations need hysteresis: turn on at one level, turn off at a slightly lower one.
Add one more variable:
Name |
Class |
Type |
Location |
Initial Value |
Documentation |
|---|---|---|---|---|---|
|
Local |
|
|
Deadband, in volts |
The whole block, with the new line at the bottom:
VAR
level_raw : UINT AT Level_Raw; (* 0-10 V as 0-8191 counts *)
bar_1 : BOOL AT Bar_1; (* First lamp *)
bar_2 : BOOL AT Bar_2; (* Second lamp *)
bar_3 : BOOL AT Bar_3; (* Third lamp *)
bar_4 : BOOL AT Bar_4; (* Fourth lamp *)
level_volts : REAL; (* Scaled reading, in volts *)
segments : INT; (* How many lamps should be lit *)
hysteresis : REAL := 0.2; (* Deadband, in volts *)
END_VAR
Then replace the bar_1 := segments >= 1; line in the code body with:
(* Segment 1: on above 2.0 V, off below 1.8 V *)
IF level_volts >= 2.0 THEN
bar_1 := TRUE;
ELSIF level_volts < (2.0 - hysteresis) THEN
bar_1 := FALSE;
END_IF;
Note that neither branch assigns when the value sits inside the band. The output holds its state.
Anything driving a contactor, a pump or a valve needs this. Rapid cycling shortens equipment life.