Random number generator

I’ve had occasion during testing where it was convenient to generate a random integer, but I’ve recreated it each time. Here’s my latest version, in case anyone would like to use it. My goal was to not use any overly complex formulas or functions that I wouldn’t be able to remember how they work six months from now.

FB Declaration:

FUNCTION_BLOCK FB_RandomGen
VAR_INPUT
    xTrigger    : BOOL;            // rising edge trigger to refresh random value
    uiMinNum	: USINT := 1;		// minimum random value (can be zero)
    uiMaxNum     : USINT := 100;    // maximum random value (1..uiMaxNum). Range 1..255
    dwSeedIn     : DWORD := 12345;  // configurable seed input (used only on first scan or when bReloadSeed true)
END_VAR
VAR_IN_OUT
    xReloadSeed  : BOOL;   // when TRUE on rising edge, reload seed from dwSeedIn
END_VAR
VAR_OUTPUT
    uiRandomValue : SINT := 0;     // generated random value between uiMinNum and uiMaxNum (-1 if invalid)
END_VAR
VAR
    dwSeed        : DWORD := 12345; // internal PRNG state (32-bit)
    xPrevTrigger  : BOOL := FALSE;  // previous state of trigger (for rising edge detection)
    uiDiff		  : USINT;
END_VAR
// Linear Congruential Generator (LCG) parameters (32-bit)
VAR CONSTANT
    DWORD_A : DWORD := 1664525;     // multiplier
    DWORD_C : DWORD := 1013904223;  // increment
END_VAR

FB Body:

//optional reload OF seed   
IF xReloadSeed THEN
    dwSeed := dwSeedIn;
    xReloadSeed := FALSE;
END_IF

// Generate on rising edge of bXTrigger. If uiMaxNum < 1 -> output 0.
IF (xTrigger AND NOT xPrevTrigger) THEN

    // ensure seed is non-zero to avoid degenerate sequence
    IF dwSeed = 0 THEN
        dwSeed := 12345;
    END_IF

    // Update LCG: dwSeed = (a * dwSeed + c) mod 2^32 (overflow natural for DWORD)
    dwSeed := TO_DWORD((DWORD_A * dwSeed) + DWORD_C); // overflow handled by DWORD type

    // Map to uiMinNum..uiMaxNum (if uiMaxNum >= uiMinNum)
    IF uiMaxNum >= uiMinNum THEN
        // modulo reduction: (dwSeed MOD uiMaxNum) + 1
	    uiDiff := uiMaxNum - uiMinNum;
        uiRandomValue := TO_SINT((dwSeed MOD TO_DWORD(uiDiff+1)) + uiMinNum);
     ELSE
        uiRandomValue := -1;
    END_IF
END_IF

// Update previous trigger state for edge detection
xPrevTrigger := xTrigger;

Example Use Declaration:

PROGRAM PLC_PRG
VAR
    fbRnd      : FB_RandomGen;   // instance of random generator
    bTrigCmd   : BOOL := FALSE;  // manual trigger command
    uiMax      : USINT := 20;  
    uiMin	   : USINT := 1;
    uiOut      : SINT := 0;     // captured output
    Timer1	   : TON;
    xReloadSeed: BOOL := TRUE;
END_VAR

Example Use Body:

// Load desired seed once (demo)
fbRnd.dwSeedIn := 20231234;

// Provide inputs
fbRnd.xTrigger := bTrigCmd;
fbRnd.uiMinNum := uiMin;
fbRnd.uiMaxNum := uiMax;

// Call FB 
fbRnd(xReloadSeed := xReloadSeed);

// Capture output
uiOut := fbRnd.uiRandomValue;

Timer1(IN:= NOT Timer1.Q, PT:= T#1000MS);

// Demo: generate single random on first cycle (creates a rising edge then clears)
IF timer1.Q THEN
    bTrigCmd := TRUE;   // create a rising edge for demo
ELSE
    // clear trigger after first cycle so next rising edge can be generated externally
    bTrigCmd := FALSE;
END_IF

The above code was initially created using the new WAGO PLC_Agent AI Add-On package and then I modified it to be a bit more to my liking.

I tested by generating random numbers between 1 and 20 and counted how many times each value appeared when I left it running for several hours. At the end of the test, each output value appeared between 5300 and 5600 times, which seemed random enough for me.

Comments and improvements are welcome!

1 Like