How to Generate Random Values with the ESP32-C6's Hardware RNG
When developing firmware for an embedded device, you’ll eventually need a truly random number to calculate a delay before a retry, a number used once (nonce) for an encrypted message, or a fresh Bluetooth Low Energy (BLE) address. The ESP32-C6-DevKitC-1 has a hardware random number generator (RNG) built-in for exactly that, but it produces truly random numbers only when its entropy source is enabled. In this guide, you’ll learn how to read the RNG on the ESP32-C6, switch its entropy source on and off in the right order, and measure the time cost of each read to evaluate whether it will work for your use case.
Processors: Pseudo-random
A standard processor can only perform deterministic computations, so any computation that starts from the same state will produce the same output every time. Therefore, if your firmware asks the processor to generate a random number, two boards running the same firmware will produce the same numbers in the same order. This kind of output is called pseudo-random.
Many problems can arise if you substitute a pseudo-random number for a truly random one. For example, if you use a pseudo-random number to calculate a retry delay, devices running the same firmware that lose power together may retry together and collide again. In another case, using a pseudo-random number to generate a cryptographic key means anyone who knows the firmware can generate the key themselves.
Hardware RNG: Truly random
Every ESP32-C6 has a built-in hardware RNG block. You can read from it one 32-bit value at a time with esp_random(). The RNG block gets its randomness from thermal noise, the tiny random voltage that every resistor and transistor produces from heat. An analog-to-digital converter (ADC), the circuit that turns a voltage into a number, samples that noise and feeds the result into the RNG. According to Espressif’s random number generation guide, the ADC sampling circuit is also called the entropy source.
The ESP32-C6 has two ADCs, and thus two entropy sources. One source is the high-speed ADC inside the radio. It belongs to the receive path of the Wi-Fi, BLE, and 802.15.4 radio, and the radio switches it on whenever any of those stacks is running. When it’s on, it automatically feeds noise into the RNG, so your firmware will never touch it directly.
The other entropy source is the general-purpose successive approximation (SAR) ADC, which is the same converter you would use to read a battery voltage or a sensor on an analog pin. It has no owner by default once your application starts. To let the SAR ADC feed noise into the RNG, you must manually call bootloader_random_enable() in your firmware.
Truly Random Conditions
While esp_random() always reads from the hardware RNG, it is not guaranteed to output a truly random number. Instead, at least one of the following conditions must be true for the output to be truly random:
- The radio is running.
- Your code has called
bootloader_random_enable(), which switches on the SAR ADC, and has not yet calledbootloader_random_disable(), which switches it off.
By default, neither condition will be true when your application starts. Thus, a plain application that calls esp_random() will only get pseudo-random numbers.
Tip: If your application already runs Wi-Fi or BLE, the radio’s own ADC will become the entropy source, so leave out the calls to
bootloader_random_enable()andbootloader_random_disable()entirely. Calling them while the radio is running will reconfigure the SAR ADC underneath the radio and potentially break the radio’s calibration.
Read the RNG
Before you begin, make sure you have ESP-IDF v6.0 or later installed on your PC.
Create a new project within ESP-IDF and set the target chip to esp32c6. Then, add the bootloader_support and esp_timer components to main/CMakeLists.txt to enable the entropy source controls and the microsecond timer in your firmware.
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ""
REQUIRES bootloader_support esp_timer)Add the following code in your project’s main.c. It switches the SAR ADC entropy source on, measures the time for 1,000 reads, fills a buffer, switches the source off, and prints the results.
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_random.h"
#include "bootloader_random.h"
#include "esp_timer.h"
#include "esp_log.h"
static const char *TAG = "rng";
#define SAMPLE_BYTES 4096
#define CALLS 1000
// File scope keeps the 4 KB buffer off app_main()'s stack, which is
// only a few kilobytes by default. A local array this size overflows it.
static uint8_t sample[SAMPLE_BYTES];
void app_main(void)
{
// No radio runs in this program, so switch the SAR ADC entropy source on.
// Without this call, every value below will become pseudo-random
bootloader_random_enable();
// Time a thousand reads. esp_random() waits between reads so the
// hardware can gather fresh noise, and this loop measures that duration.
// volatile stops the compiler from deleting a loop whose result it
// thinks nobody uses.
volatile uint32_t sink = 0;
int64_t start_us = esp_timer_get_time();
for (int i = 0; i < CALLS; i++) {
sink = esp_random();
}
int64_t rng_us = esp_timer_get_time() - start_us;
// Fill a buffer in one call. Use this for keys, nonces, and anything
// longer than 32 bits. It calls esp_random() once for every 4 bytes.
esp_fill_random(sample, sizeof(sample));
// Release the SAR ADC before any radio or ADC code runs. The radio
// and the ADC driver each reconfigure this same converter.
bootloader_random_disable();
ESP_LOGI(TAG, "%d esp_random() calls: %" PRId64 " us (%" PRId64 " us each)",
CALLS, rng_us, rng_us / CALLS);
ESP_LOGI(TAG, "first bytes: %02x %02x %02x %02x %02x %02x %02x %02x",
sample[0], sample[1], sample[2], sample[3],
sample[4], sample[5], sample[6], sample[7]);
(void)sink;
}Build the program, flash it, and open the serial terminal.
Results
You should see two logs after the bootloader’s startup logs in your terminal.

The first line shows how long a thousand esp_random() reads took and the average time for each read. You should expect to see each read take a few dozen microseconds because esp_random() deliberately waits between reads so the hardware can collect fresh noise. In contrast, a plain software random value generator will output in well under a microsecond.
The second line shows the first eight bytes of the 4,096-byte buffer, which the firmware filled with random values. You should see these values change after every boot (see below), proving your firmware was getting truly random values out of the RNG.

Next Steps
You now know how to generate truly random values and how much time each one takes. Each 32-bit read of the hardware RNG is fast enough for a nonce or a key, but too slow for a data stream.
When your application needs many random values quickly, Espressif recommends a software generator such as the CTR-DRBG in Mbed TLS. It computes values quickly, and you can keep them unpredictable by seeding it with a few bytes from the hardware RNG. By selecting the right random number generator, you can keep your boards performant and secure in production.
Ready to connect your devices anywhere on Earth? Get started with Hubble Network for free →