The Deterministic Nature of Microcontrollers

At their core, microcontrollers are entirely deterministic machines. When makers search for an arduino random number solution, they often hit a wall: the board simply cannot generate true randomness on its own without external entropy. Understanding the difference between Pseudo-Random Number Generators (PRNGs) and True Random Number Generators (TRNGs) is the first step toward building reliable, unpredictable systems for cryptography, gaming, or randomized sensor polling.

The Standard Approach: random() and randomSeed()

The Arduino IDE provides the random(min, max) function. Under the hood, the standard AVR core uses a Linear Congruential Generator (LCG). According to the official Arduino reference for random(), this algorithm is fast and uses minimal memory, but it is entirely predictable if the starting seed is known.

The avr-libc standard library implements this using the formula X_{n+1} = (a * X_n + c) mod m, specifically with multiplier a = 1103515245 and increment c = 12345. This mathematical reality means if an attacker captures just two consecutive 32-bit outputs, they can reverse-engineer the exact seed and predict every future number your microcontroller will generate.

To vary the sequence, developers use randomSeed(seed). But where does that seed come from? This is where most tutorials lead you astray.

The 'Floating Pin' Fallacy

A ubiquitous piece of advice in beginner forums is to seed the PRNG using an unconnected analog pin:

randomSeed(analogRead(A0));

Why this fails in production: An unconnected ATmega328P ADC pin does not read pure white noise. Due to internal parasitic capacitance and electromagnetic coupling from the board's own 16MHz crystal, the reading typically hovers between 512 and 540. You are only getting 4 to 5 bits of actual entropy, and the lower-order bits are highly correlated. If your project requires secure token generation or unbiased statistical sampling, the floating pin method will result in overlapping, predictable sequences across multiple device reboots.

Method 1: Watchdog Timer Jitter (Software Entropy)

If you are constrained to an ATmega328P (Arduino Uno/Nano) and cannot add external hardware, you can extract true entropy from the microcontroller's own Watchdog Timer (WDT). The WDT relies on an independent, uncalibrated internal RC oscillator. By measuring the phase drift between the WDT oscillator and the main system clock, you can harvest genuine hardware jitter.

Implementing the Entropy Library

The most robust implementation of this technique is the Entropy library originally detailed by PJRC. It generates 32 bits of true hardware entropy without external components.

#include <Entropy.h>

void setup() {
  Serial.begin(115200);
  // Initialize the WDT entropy harvesting
  Entropy.initialize();
}

void loop() {
  // Generate a true random 32-bit integer
  uint32_t trueRandom = Entropy.random();
  Serial.println(trueRandom);
  delay(100);
}

Performance Note: Harvesting 32 bits of entropy via WDT jitter takes approximately 40-50 milliseconds. Do not call this inside a high-frequency interrupt service routine (ISR) or a fast sensor-polling loop.

Method 2: Hardware True Random Number Generators (TRNG)

For modern IoT deployments, cryptographic key generation, or high-speed randomized dithering, software jitter is too slow. You need dedicated hardware entropy sources.

The ESP32 Built-in Hardware RNG

If you have migrated from the AVR architecture to the ESP32, you have a massive advantage. The ESP32 features a dedicated hardware RNG based on SAR ADC noise and Wi-Fi/Bluetooth RF interference. According to the Espressif ESP-IDF Random Number Generator API, the hardware RNG produces true random numbers at high speeds, provided the Wi-Fi or Bluetooth radios are enabled (which introduces the necessary RF noise into the ADC).

// ESP32 Hardware TRNG implementation
#include 'esp_random.h'

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

void loop() {
  // esp_random() returns a 32-bit true random number instantly
  uint32_t hwRandom = esp_random();
  Serial.println(hwRandom);
}

External Entropy ICs and DIY Avalanche Noise

If you are using a Raspberry Pi Pico (RP2040) or an Arduino Uno and require cryptographic-grade randomness, integrate an external secure element. The Microchip ATECC608B (approx. $0.85 - $1.20 per unit in low volumes) communicates via I2C and features a FIPS-compliant internal TRNG.

For makers building custom PCBs who need high-speed entropy without an ESP32 or expensive crypto chips, a reverse-biased Zener diode avalanche circuit is the gold standard of DIY TRNGs. By running a 5.1V Zener diode (like the 1N4733A) in reverse breakdown at roughly 4.5V, the quantum avalanche effect generates wideband white noise. AC-couple this noise through a 100nF capacitor into a high-gain op-amp (like the LM358), and feed the output to a comparator connected to an Arduino digital interrupt pin. This yields thousands of true random bits per second for less than $0.30 in BOM cost.

Comparison Matrix: Arduino Random Number Methods

Method Entropy Quality Execution Time (32-bit) Hardware Required Best Use Case
randomSeed(analogRead(A0)) Poor (4-5 bits) ~104 µs None (Floating Pin) Simple LED flickering, basic games
WDT Jitter (Entropy Lib) High (True Entropy) ~45 ms None (AVR Internal) Secure tokens, non-blocking lotteries
ESP32 esp_random() Very High (TRNG) < 1 µs ESP32 SoC TLS handshakes, IoT crypto, fast dithering
ATECC608B I2C IC Cryptographic (FIPS) ~15 ms (I2C overhead) External IC (~$1.00) Commercial IoT, secure boot, key storage
Zener Avalanche Circuit High (Analog Noise) ~10 µs (per bit) Diode, Op-Amp, Caps Custom PCBs, high-speed statistical sampling

Real-World Edge Cases and Memory Constraints

1. The Blocking Code Trap

When using the WDT Entropy library on an Arduino Uno, calling Entropy.random() blocks the main thread for roughly 45 milliseconds. If your sketch is reading a rotary encoder or managing WS2812B NeoPixel timing (which requires strict microsecond interrupts), the entropy harvesting will cause missed steps or LED flicker. Solution: Generate a pool of random numbers during the setup() phase or during known idle states, storing them in a circular buffer array.

2. The EEPROM Seed Chaining Mistake

A dangerous anti-pattern found in older forums is reading a seed from EEPROM, using it, and then writing the next seed back to EEPROM to 'chain' the randomness across reboots. This causes an EEPROM write cycle on every single boot. The ATmega328P EEPROM is rated for only 100,000 write cycles. If your device reboots hourly, you will brick the EEPROM sector in under 12 years, and potentially much sooner if subjected to high temperatures. Instead, rely on WDT jitter or hardware TRNGs on every boot.

3. Modulo Bias in Range Mapping

If you need an arduino random number between 0 and 99, using random() % 100 introduces modulo bias because 4,294,967,296 (the 32-bit max) is not perfectly divisible by 100. The lower numbers will appear slightly more often. For high-stakes statistical fairness, use rejection sampling or the built-in random(0, 100) function which handles the mapping math internally to minimize bias.

Pro-Tip for Production Firmware: Never rely on a single entropy source for cryptographic keys. Best practice dictates hashing your WDT entropy together with a unique hardware identifier (like the Microchip MAC address or EEPROM serial) using SHA-256 before using it to seed your PRNG.

Summary

Generating a reliable arduino random number sequence requires moving past the basic analogRead() tutorials. By leveraging Watchdog Timer jitter on legacy AVRs, utilizing the ESP32's native RF-noise TRNG, or integrating dedicated secure elements and avalanche circuits, you can guarantee the statistical integrity and security your project demands.