Random numbers are useful for simulations, to create a sense of arbitrariness in games that might make them more “interesting”, i.e. generally speaking: to introduce a level of unpredicatability in the oh-so-deterministic world of today’s computers.

There is an alternative, if you take randomness with a grain of salt: pseudorandomness. This is a technique which generates numbers that appear random and totally unrelated, but are in fact produced by carefully designed algorithmic computations.

It’s easy to generate pseudorandom sequences in software. For simple 8-bit sequences, something like this will generate a pretty erratic series (unless xPrev is zero!):

uint8_t xNext = xPrev * 211;

Variations on that theme will produce longer sequences of larger values. Entire books have been written about this topic, a web search will turn up many effective algorithms. But let’s be clear about it: this approach leads to anything BUT unpredictable sequences. If you know the previous state and the algorithm, you can fully predict all future values.

Randomness via an ADC

A much higher level of randomness can be obtained by observing some aspect of the real physical world, and capturing a noisy signal (“noise” == “random” in physics … sort of).

With an ADC, this is easy to do: leave one ADC input pin floating, and measure its analog voltage. The least-significant bit will be the noisiest, always fluctuating due to electrical noise, e.g. the omni-present thermal brownian motion. If we repeat this 32 times and keep only the lower bit, we should get nicely random results:

ADC<0> adc;
PinA<0> floatingPin;

uint32_t randomNumber () {
    uint32_t r = 0;
    for (int i = 0; i < 32; ++i)
        r = (r << 1) | (adc.read(floatingPin) & 1);
    return r;
}

Randomness hardware

In many STM32 µC’s, there’s also a Random Number Generator (RNG) hardware device. This uses multiple ring oscillators, combined via XOR to maximise the noise content.

A ring oscillator can be built with an odd number of logical inverter gates:

In actual use, there will be a lot more than three, but the mechanism is the same: inverters produce the opposite logic level on their output, after a very slight delay. If you tie an odd number of stages into a ring, you get a configuration which has no stable state: if the input is “0”, the output will be “1”, but that output is tied back to the input, so it turns to “1” as well, flipping the output to “0”. And the cycle repeats - ad nauseam, and ad infinitum.

The key is the logic gate delay, especially with a large numbers of inverters. This delay is temperature and thermal-noise dependent, and changes all the time. The resulting signal is very high frequency and very irregular and (hopefully) independent of the µC’s system clock. So whenever you look at its output pin, you’ll get to see either “0” or “1”, and it’ll be nearly unpredicatble (unless you cool it way down to absolute zero) - i.e. nicely random.

It’s very easy to try this out on an STM32F407, for example:

#include <jee.h>

UartDev< PinA<9>, PinA<10> > console;

int printf(const char* fmt, ...) {
    va_list ap; va_start(ap, fmt); veprintf(console.putc, fmt, ap); va_end(ap);
    return 0;
}

int main() {
    console.init();

    Periph::bit(Periph::rcc+0x34, 6) = 1;  // RNGEN, p.244
    constexpr uint32_t rng = 0x50060800;

    MMIO32(rng+0x0) = (1<<2); // start the RNG

    while (true) {
        while ((MMIO32(rng+0x4) & (1<<0)) == 0) {}
        printf("%08x\n", MMIO32(rng+0x8));
    }
}

Here’s some output, this will go on forever, evidently:

2E54C269
2395B09C
9B1AB45C
3993E287
32FCF0E7