Here is my advice on how to implement a random number generator on Arduino, or indeed any microcontroller:
Seed the random number generator at bootup (in setup()
in Arduino) only.
Use a timer to consume random numbers, and/or consume random numbers when idle.
This way, even though the state immediately after power on is predictable, the state changes very rapidly, and becomes unpredictable (unless the microcontroller state is monitored very closely at something like microsecond intervals).
Use exclusive-OR (^
) to mix in entropy from sources that have at least some randomness, like say a hardware source based on avalanche noise.
For best results, do it only occasionally. I would personally feed the entropy to a separate pseudorandom number generator, and mix its output to the primary generator state.
Use human input (button presses et cetera, via high-resolution timers) as a source for entropy.
That is, measure the time between e.g. successive button presses at a very high resolution, and use a few least significant bits as entropy to mix into the pseudorandom number generator state.
After just a few button presses, the internal state of the pseudorandom number generator would basically be unpredictable.
Use battery-backed SRAM (often available as part of real-time clock modules) to store a seed state for the next bootup.
Technically, you could also use EEPROM or Flash for this, but since they have a limited number of write cycles, I do not recommend using those for this.
As I've mentioned in other posts, I personally do not trust the built-in pseudorandom number generators, and instead implement one of the known linear feedback shift generator based ones (a Xorshift variant, or Mersenne Twister). They are well known and their output randomness characterized.
For an Arduino-based game, I'd probably use Xorshift128 for the random number generator itself, and a Xorshift64* for obtaining entropy from hardware sources and using its output to perturb the main generator state every now and then (say, at button press, or similar occasions; less than once per second).
I would ensure that even when idle, the generators' states are advanced (by "consuming" pseudorandom numbers; just throwing them out), so that physical world timing would become a major factor in the sequences generated.
Various microcontrollers have different timers and hardware features available, so the exact code I would implement definitely depends on the hardware used. Unfortunately, that means the code is not that portable across hardware; even small, innocuous-looking changes may mean the output becomes quite predictable. I could write an example for Arduino Leonardo / Arduino Pro Micro (both based on ATmega32u4 microcontroller), because I have one at hand, but if you are using some other microcontroller, the code might be interesting, but misleading at worst if you tried to port it to another hardware architecture without knowing its details and behaviour.
To generate pseudorandom integers within a range, I recommend using the exclusion method, rather than the modulo (%
) method. The exclusion method ensures uniform distribution; the modulo method can give a small bias to some values near the smaller end of the range.
The idea of the exclusion method is simple. You grab exactly the needed number of bits to cover the range, but exclude values outside the range. On average, you may consume up to twice as many random number bits, but with a fast generator like Xorshift, that is definitely not a problem.
The general pattern of a fixed 32-bit signed integer range function is
static inline int32_t rndrange(void)
{
uint32_t u;
do {
u = random32() >> SHIFT;
} while (u > LIMIT);
return u + MINIMUM;
}
where random32()
returns uniform pseudorandom 32-bit unsigned integers, MINIMUM
is the smallest integer the function can return, MINIMUM + LIMIT
is the maximum integer the function can return (LIMIT = MAXIMUM - MINIMUM
), and SHIFT
is
31 if LIMIT == 1 ║ 15 LIMIT <= 131071
30 LIMIT <= 3 ║ 14 LIMIT <= 262143
29 LIMIT <= 7 ║ 13 LIMIT <= 524287
28 LIMIT <= 15 ║ 12 LIMIT <= 1048575
27 LIMIT <= 31 ║ 11 LIMIT <= 2097151
26 LIMIT <= 63 ║ 10 LIMIT <= 4194303
25 LIMIT <= 127 ║ 9 LIMIT <= 8388607
24 LIMIT <= 255 ║ 8 LIMIT <= 16777215
23 LIMIT <= 511 ║ 7 LIMIT <= 33554431
22 LIMIT <= 1023 ║ 6 LIMIT <= 67108863
21 LIMIT <= 2047 ║ 5 LIMIT <= 134217727
20 LIMIT <= 4095 ║ 4 LIMIT <= 268435455
19 LIMIT <= 8191 ║ 3 LIMIT <= 536870911
18 LIMIT <= 16383 ║ 2 LIMIT <= 1073741823
17 LIMIT <= 32767 ║ 1 LIMIT <= 2147483647
16 LIMIT <= 65535 ║ 0 if LIMIT <= 4294967295
In Arduino, or any C or C++ code compiled using GCC, you can use
static inline int random_intrange(const int minval,
const int maxval)
{
if (maxval > minval) {
const unsigned int limit = maxval - minval;
const unsigned char shift = __builtin_clz(limit);
unsigned int u;
do {
u = random_unsigned_int() >> shift;
} while (u > limit);
return minval + u;
} else
return minval;
}
static inline unsigned int random_uintrange(const unsigned int minval,
const unsigned int maxval)
{
if (maxval > minval) {
const unsigned int limit = maxval - minval;
const unsigned char shift = __builtin_clz(limit);
unsigned int u;
do {
u = random_unsigned_int() >> shift;
} while (u > limit);
return minval + u;
} else
return minval;
}
as long as random_unsigned_int()
is an uniform pseudorandom number generator that returns unsigned int
s, from 0
to UINT_MAX
, inclusive.