AXC F 2152 with AXL SE AI4 I 4-20 Simple Scaling and Average

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

3 Likes