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!