FUNCTION_BLOCK FB_MovingAverage
VAR_INPUT
    xEnable     : BOOL;
    rInput      : REAL;         // Raw analog value
    iWindowSize : INT := 10;    // Number of samples (1..100)
END_VAR
VAR_OUTPUT
    rOutput     : REAL;         // Filtered output
    xReady      : BOOL;         // TRUE when buffer is full
END_VAR
VAR
    arBuffer    : ARRAY[0..99] OF REAL;   // Circular buffer
    iIndex      : INT := 0;
    iCount      : INT := 0;
    rSum        : REAL := 0.0;
    iWin        : INT;
END_VAR

IF NOT xEnable THEN
    rOutput := rInput;
    iIndex  := 0;
    iCount  := 0;
    rSum    := 0.0;
    RETURN;
END_IF;

// Clamp window size
iWin := MIN(MAX(iWindowSize, 1), 100);

// Subtract the oldest value from sum before overwrite
rSum := rSum - arBuffer[iIndex];

// Store new value
arBuffer[iIndex] := rInput;
rSum := rSum + rInput;

// Advance circular index
iIndex := (iIndex + 1) MOD iWin;

// Track how many samples collected
IF iCount < iWin THEN
    iCount := iCount + 1;
END_IF;

xReady  := (iCount >= iWin);
rOutput := rSum / INT_TO_REAL(iCount);   // Valid even before buffer full