I have beein looking for a single analog library to use, that allows me to use pure ST code in my projects. Most of the time, using analog signals you need only to Scale and Average RAW signals. So why implement large libraries when it can be done simpler.
Simple Analog Scaling and Moving Average Function Blocks for PLCnext Engineer
I would like to share two small IEC 61131-3 Structured Text function blocks that I use for analog signal handling in PLCnext Engineer.
The purpose is:
-
Scale raw analog input values to engineering values
-
Apply a simple moving average filter
-
Avoid dependency on external libraries
-
Keep the code easy to understand and debug
-
Use PLCnext-friendly user-defined data types for arrays
The examples below are used with 4ā20 mA analog input modules where the raw value is typically scaled from 0..16000.
1. Data Type Declaration
In PLCnext Engineer, declare array types in a Data Type file.
Example:
TYPE
arrAvg_Buffer_300 : ARRAY[1..300] OF REAL;
END_TYPE
This array type is used inside the moving average function block.
2. Function Block: FB_AnalogScale_R
This function block scales a raw integer value to a REAL engineering value.
Example:
0..16000 ā 0.0..100.0
// ============================================================
// FB_AnalogScale_R
// Linear scaling of analog raw value to engineering value
// ============================================================
IF RawMax = RawMin THEN
OutValue := EngMin;
ELSE
OutValue :=
EngMin +
(
(TO_REAL(RawValue - RawMin) *
(EngMax - EngMin))
/
TO_REAL(RawMax - RawMin)
);
END_IF;
Suggested interface
RawValue : INT;
RawMin : INT;
RawMax : INT;
EngMin : REAL;
EngMax : REAL;
OutValue : REAL;
3. Function Block: FB_AnalogAverage_R_300
This function block performs a simple moving average using a ring buffer.
It supports buffer sizes from 1 to 300 samples.
A runtime exception can occur if the buffer index is used before it is validated. Therefore, the index is checked before every array access.
// ============================================================
// FB_AnalogAverage_R_300
// Moving average filter for REAL signals
// Ring buffer 1..300 samples
// Protected against array index out of range
// ============================================================
IF NOT xActivate THEN
rAverage := rIn;
Ready := FALSE;
Avg_Sum := 0.0;
Avg_Index := 1;
Avg_Count := 0;
Avg_Size := 1;
ELSE
// Limit buffer size
IF uiBufferSize < UINT#1 THEN
Avg_Size := 1;
ELSIF uiBufferSize > UINT#300 THEN
Avg_Size := 300;
ELSE
Avg_Size := TO_INT(uiBufferSize);
END_IF;
// Protect index BEFORE array access
IF Avg_Index < 1 THEN
Avg_Index := 1;
END_IF;
IF Avg_Index > Avg_Size THEN
Avg_Index := 1;
END_IF;
// Remove old value from sum
Avg_Sum := Avg_Sum - Avg_Buffer[Avg_Index];
// Write new value to buffer
Avg_Buffer[Avg_Index] := rIn;
// Add new value to sum
Avg_Sum := Avg_Sum + rIn;
// Count up until buffer is filled
IF Avg_Count < Avg_Size THEN
Avg_Count := Avg_Count + 1;
END_IF;
// Calculate average
IF Avg_Count > 0 THEN
rAverage := Avg_Sum / TO_REAL(Avg_Count);
ELSE
rAverage := rIn;
END_IF;
// Next buffer position
Avg_Index := Avg_Index + 1;
IF Avg_Index > Avg_Size THEN
Avg_Index := 1;
END_IF;
// TRUE when buffer has been filled once
Ready := Avg_Count >= Avg_Size;
END_IF;
Suggested interface
xActivate : BOOL;
rIn : REAL;
uiBufferSize : UINT;
rAverage : REAL;
Ready : BOOL;
Suggested internal variables
Avg_Buffer : arrAvg_Buffer_300;
Avg_Sum : REAL;
Avg_Index : INT;
Avg_Count : INT;
Avg_Size : INT;
4. Example Usage in an ST Program
Example for one analog channel:
// Convert input word/value to INT raw value
AI_1_1_RAW := TRUNC_INT(TO_REAL(TO_DINT(AI1_1_IN)));
// Scale raw input to engineering value
AI1_1_Scale(
RawValue := AI_1_1_RAW,
RawMin := 0,
RawMax := 16000,
EngMin := 0.0,
EngMax := 100.0
);
// Apply moving average filter
AI1_1_Average(
xActivate := TRUE,
rIn := AI1_1_Scale.OutValue,
uiBufferSize := UINT#100
);
// Final calibrated value
AI1_1_Calib := TRUNC_INT(AI1_1_Average.rAverage);
For slower or more noisy signals, I use a larger buffer:
AI2_3_Average(
xActivate := TRUE,
rIn := AI2_3_Scale.OutValue,
uiBufferSize := UINT#300
);
5. Notes
The important detail is that the ring buffer index must be validated before using it as an array index.
Without this protection, a PLC runtime exception may occur, for example:
Array index out of range
Runtime exception occurred on the device
This can happen if an internal FB variable starts with 0, while the array is declared as [1..300].
The index protection before array access prevents this issue.
6. Typical Buffer Sizes
I normally use:
100 samples - normal analog signals
300 samples - slow or noisy signals such as tank level or temperature
This has worked well on PLCnext, and the values are stable and easy to monitor online.
I hope this can be useful for others working with analog signal handling in PLCnext Engineer.
RAC