Maker.io main logo

Random Number Generation in Arduino and Embedded Designs

2025-11-10 | By Maker.io Staff

Arduino

Image of Random Number Generation in Arduino and Embedded Designs

Randomness plays a key role in many algorithms and applications. You can choose between numerous techniques, and you may be left wondering why multiple approaches exist for solving the same problem. Read on to discover why computers can’t know true randomness, how they work around it, and ways to introduce entropy into deterministic code.

How Computers Handle Randomness

In practice, computers cannot comprehend randomness, since they are fundamentally deterministic machines. Given a computer’s complete current state (the program counter, instructions, memory, registers, etc.), it’s always possible to predict the machine’s next action, memory contents, and so on. In theory, you could build a complete table containing all possible states and use it to find all transitions to every future state. Therefore, a CPU can typically not generate true random numbers on its own.

In practice, computers work around this limitation by generating so-called pseudo-random numbers. They are calculated by deterministic algorithms using different starting values as an input (the so-called seed). The output appears random, but relies on fluctuations in the seed value. It’s crucial to keep in mind that using the same starting value will always produce the same pseudo-random number sequence.

Therefore, it’s important to choose a seed with high uncertainty—often referred to as entropy. The more entropy a seed or input has, the harder it is to predict the next random number.

The Need for Random Numbers

The use of pseudo-random values in software is multifaceted. Some algorithms, like stochastic gradient descent and machine-learning methods, often rely on random starting values. Games also typically require uncertainty, like simulating dice rolls. In addition, randomness plays a vital role in cryptographic applications, such as in secret key generation.

The first two applications usually do not benefit much from more complex random numbers. However, making secret values as hard to predict as possible is vital in cryptography to ensure confidential data. The better the random number generator (RNG), the more resilient the encryption.

Generating Simple Pseudo-Random Numbers

The simplest way to generate pseudo-random numbers is to use the target platform’s built-in random number generator, for example, using an Arduino:

Copy Code
void setup() {
 Serial.begin(9600);
 }
void loop() {
 long r = random(0, 100); // Get a random number between 0-99
 Serial.println(r); 
 delay(500); 
}

Using this simple approach provides seemingly random numbers between zero and 99. But since the code does not specify a seed, the sequence repeats the same “random” numbers after each reboot:

Image of Random Number Generation in Arduino and Embedded Designs This screenshot illustrates how the Arduino always generates the same “random” series when not supplied with a seed value.

Sources of Entropy

Providing the RNG algorithm with an uncertain starting value after each boot results in different random sequences that are harder to predict. The seed is typically set only once after startup, which helps improve the quality of the randomness. This is especially true when the entropy source has a limited range of values.

Numerous approaches exist that introduce entropy into the RNG process. Examples include user interaction timing, electronic noise, timers, and event-based entropy. While most of these sources are still inherently deterministic, they typically lead to the generated numbers fluctuating between reboots, which is usually sufficient for simple applications.

Electronic Noise and User-Timing Entropy

Using electrical noise as the source of entropy is one of the simplest techniques available. In this approach, the seed is derived from the system’s initial state, which is inherently uncertain and influenced by external factors such as electrical noise.

On Arduino, for example, the initial value of a floating analog pin is commonly used:

Copy Code
void setup() { 
Serial.begin(9600);
 randomSeed(analogRead(A0)); // Set the seed at start-up
 }
void loop() { 
long r = random(0, 100);
 Serial.println(r);
 delay(500); 
}

Although this approach helps prevent the Arduino from generating the same sequence of pseudo-random numbers on every boot, the number of possible seeds is still limited and depends on the ADC resolution. Since most Arduino boards use a 10-bit ADC, the Arduino picks one of 1024 possible sequences rather than the same one each time.

A slightly better way to introduce entropy is to take advantage of human unpredictability, like measuring how long it takes a user to press a button after the program starts. Most humans find it difficult to press the button with consistent timing, which produces a starting value that is hard to predict:

Copy Code
#define BUTTON_PIN 2
#define MAX_WAITS 20

unsigned long seed = 0UL;
volatile bool pressed = false;
volatile unsigned long pressMillis = 0UL;

void buttonPressed() {
  pressed = true;
  pressMillis = millis();
}

void initializeButton(int buttonPin) {
  pinMode(buttonPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(buttonPin), buttonPressed, FALLING);
}

unsigned long getSeed(int maxWaits) {
  int timesWaited = 0;
  while (!pressed && timesWaited++ <= maxWaits) {
    delay(250);
  }
  return pressed ? pressMillis : analogRead(A0);
}

void setup() {
  Serial.begin(9600);
  initializeButton(BUTTON_PIN);
  seed = getSeed(MAX_WAITS);
  randomSeed(seed);
}

void loop() {
  long r = random(0, 100);
  Serial.println(r);
  delay(500);
}

Despite its length, the program is straightforward. The initializeButton function sets up an interrupt that calls the buttonPressed function when the button pin goes low. The ISR sets a volatile flag and records the milliseconds since boot. The getSeed function periodically checks for a button press and uses the time as the seed, or falls back to the analog noise method introduced earlier to prevent the program from indefinitely stalling.

User-timing entropy is an effective way to introduce randomness since humans are harder to predict than machines or electronic fluctuations. However, this approach requires more complex code, additional external hardware, and user interactions—potentially rendering it unsuitable for some projects.

Sensor-Based Entropy and Networking Events

Instead of relying on electronic noise for entropy, you can use variations in sensor readings as a random seed. This technique relies on environmental factors, such as temperature, pressure, or light levels, changing between boot cycles. Sensors further introduce measurement uncertainty and noise, adding another layer of uncertainty. This approach works particularly well for devices with a warm-up period, since their initial readings tend to be unreliable and noisy.

Similarly, networking events can replace user-timing entropy. For example, the time it takes a development board to connect to a WiFi network or receive its first message can serve as the random seed. These timings naturally vary due to external factors, such as the router’s response time, which are difficult to consistently predict.

Hardware Random Number Generators and True Entropy

Some microcontrollers, such as the ESP32 and higher-end STM32 series, have dedicated hardware random number generators that do not rely on pseudo-random algorithms. Instead, they utilize physical sources like thermal noise, avalanche diodes, or oscillator jitter to introduce entropy, thus producing better random numbers. Platform-specific SDKs allow you to access the hardware RNG seamlessly. For example, on the ESP32:

Copy Code
#include "esp_system.h"

void setup() {
  Serial.begin(9600);
}

void loop() {
  uint32_t r = esp_random();
  Serial.println(r);
  delay(500);
}

Although more difficult to predict than the simple approaches discussed above, hardware RNGs ultimately still rely on measuring physical processes, such as electrical noise or oscillator jitter. After sampling, the MCU converts the result to a finite set of values it can process. Still, hardware RNGs provide more than enough randomness for security-sensitive applications. To achieve true randomness in the strict theoretical sense, a device would need to rely on quantum phenomena, since only those are fundamentally unpredictable.

Summary

Random numbers are essential in many algorithms, games, and cryptography, but CPUs are fundamentally deterministic and cannot generate true randomness on their own. Pseudo-random generators use algorithms seeded with an uncertain starting value to produce sequences that appear random.

In the context of RNG, uncertainty is often referred to as entropy. Common sources of entropy include analog noise, sensor readings, user timing, or network events, each introducing variability that influences the random number sequence. Some MCUs have hardware RNGs that draw from physical processes like electrical noise or oscillator jitter. These can produce harder-to-predict random values.

Truly unpredictable values in the strictest sense require quantum phenomena. Setting a different seed on each boot suffices for most maker projects, and hardware RNGs provide sufficient randomness for typical security applications.

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.