DEV Community

Cover image for ESP32 Embedded Rust at the HAL: Random Number Generator
Omar Hiari
Omar Hiari

Posted on • Edited on

ESP32 Embedded Rust at the HAL: Random Number Generator

This blog post is the ninth of a multi-part series of posts where I explore various peripherals in the ESP32C3 using embedded Rust at the HAL level. Please be aware that certain concepts in newer posts could depend on concepts in prior posts.

If you find this post useful, and if Embedded Rust interests you, stay in the know and skyrocket your learning curve by subscribing to The Embedded Rustacean newsletter:

Subscribe Now to The Embedded Rustacean

Introduction

The random number generator (RNG) in the ESP32C3 offers a significant advantage over pseudo-random number generators (PRNGs) due to its true randomness. Unlike PRNGs, which generate sequences of numbers based on a predetermined algorithm, the ESP32C3's RNG utilizes physical processes to produce genuinely unpredictable and statistically random numbers. This inherent randomness makes the ESP32C3 RNG ideal for applications requiring high levels of security, such as cryptographic key generation and secure communication protocols. Additionally, the ESP32C3 RNG ensures fair outcomes in simulations, statistical analysis, and gaming scenarios, where unbiased randomness is crucial.

Using the RNG in the ESP32C3 does not take a lot of code. In this post I'm going to build a simple application that reads in a file statically and prints out lines randomly. Which line will be printed will be determined by the number generated by the RNG.

📚 Knowledge Pre-requisites

To understand the content of this post, you need the following:

  • Basic knowledge of coding in Rust.

  • Familiarity with the basic template for creating embedded applications in Rust.

💾 Software Setup

All the code presented in this post is available on the apollolabs ESP32C3 git repo. Note that if the code on the git repo is slightly different then it means that it was modified to enhance the code quality or accommodate any HAL/Rust updates.

Additionally, the full project (code and simulation) is available on Wokwi here.

🛠 Hardware Setup

Materials

ESP32C3 Devkit

🔌 Connections

No connections are necessary for this application.

👨‍🎨 Software Design

In the code introduced in this post, the application will grab a random number from the RNG every 2 seconds. The number will be used to print out an NMEA sentence from a file that is read statically.

👨‍💻 Code Implementation

📥 Crate Imports

In this implementation the crates required are as follows:

  • The esp32c3_hal crate to import the ESP32C3 device hardware abstractions.

  • The esp_backtrace crate to define the panicking behavior.

  • The esp_println crate to provide println! implementation.

use esp32c3_hal::{
    clock::ClockControl, 
    esp_riscv_rt::entry, 
    peripherals::Peripherals, 
    prelude::*,
    timer::TimerGroup, 
    Delay, Rng, Rtc,
};
use esp_backtrace as _;
use esp_println::{print, println};
Enter fullscreen mode Exit fullscreen mode

🌍 Global Static Variables

For this application, we're going to need a file with a list of mock sentences. In Rust, there is the include_str macro that would allow us to read a file statically at compile time as an str. The macro will yield a &'static str type that includes the contents of the file:

static MOCK_SENTENCES: &'static str = include_str!("nmea.rs");
Enter fullscreen mode Exit fullscreen mode

📝 Note

The nmea.rs file had a different extension of .log , however I had to change the extension for Wokwi so that it would recognize the file. The extension doesn't really matter for the include_str macro however as long as the contents of the file are UTF-8 encoded.

🎛 Peripheral Configuration Code

1️⃣ Obtain a handle for the device peripherals: In embedded Rust, as part of the singleton design pattern, we first have to take the PAC-level device peripherals. This is done using the take() method. Here I create a device peripheral handler named dp as follows:

let peripherals = Peripherals::take();
Enter fullscreen mode Exit fullscreen mode

2️⃣ Disable the Watchdogs: The ESP32C3 has watchdogs enabled by default and they need to be disabled. If they are not disabled then the device would keep on resetting. I'm not going to go into much detail, however, watchdogs require the application software to periodically "kick" them to avoid resets. This is out of the scope of this example, though to avoid this issue, the following code needs to be included:

let mut system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

let mut rtc = Rtc::new(peripherals.RTC_CNTL);
let timer_group0 = TimerGroup::new(
        peripherals.TIMG0,
        &clocks,
        &mut system.peripheral_clock_control,
);
let mut wdt0 = timer_group0.wdt;
let timer_group1 = TimerGroup::new(
        peripherals.TIMG1,
        &clocks,
        &mut system.peripheral_clock_control,
);
let mut wdt1 = timer_group1.wdt;

rtc.swd.disable();
rtc.rwdt.disable();
wdt0.disable();
wdt1.disable();
Enter fullscreen mode Exit fullscreen mode

3️⃣ Instantiate and Create Handle for the RNG: To create an instance of the RNG, all we need to do is call the new() instance method on the RNG struct as follows:

let mut rng = Rng::new(peripherals.RNG);
Enter fullscreen mode Exit fullscreen mode

Note how the new method requires passing the RNG peripheral.

4️⃣ Obtain a handle for the delay: I'll be using the hal Delay type to create a delay handle as follows:

let mut delay = Delay::new(&clocks);
Enter fullscreen mode Exit fullscreen mode

📱 Application Code

🔁 Application Loop

The code below represents the full application:

loop {
    let num = rng.random() as u8;
    let sentence = MOCK_SENTENCES.lines().nth(num as usize).unwrap();
    println!("{}", sentence);
    delay.delay_ms(2000_u32);
}
Enter fullscreen mode Exit fullscreen mode

In the first line of the code, a random number is obtained using the rng handle and the random method. As it turns out in the time being, the random interface returns a u32. Additionally, there aren't any interfaces that allow the control of the value range. Since my file does not have a number of lines that requires a u32 I need to scale the number down. As a result, I cast the number as a u8.

In the second line, I use the lines method on the file read in statically. lines in Rust returns an iterator over the lines of a string, this would allow us to perform various operations on each line. Additionally, the nth method is used allowing us to read back a particular line number.

Finally, the third and fourth lines print the read sentence to the console and delay for 2 secs, respectively.

📱 Full Application Code

Here is the full code for the implementation described in this post. You can additionally find the full project and others available on the apollolabs ESP32C3 git repo. Also the Wokwi project can be accessed here.

#![no_std]
#![no_main]

use esp32c3_hal::{
    clock::ClockControl, esp_riscv_rt::entry, peripherals::Peripherals, prelude::*,
    timer::TimerGroup, Delay, Rng, Rtc,
};
use esp_backtrace as _;
use esp_println::{print, println};

static MOCK_SENTENCES: &'static str = include_str!("nmea.rs");

#[entry]
fn main() -> ! {
    // Take Peripherals, Initialize Clocks, and Create a Handle for Each
    let peripherals = Peripherals::take();
    let mut system = peripherals.SYSTEM.split();
    let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

    // Instantiate and Create Handles for the RTC and TIMG watchdog timers
    let mut rtc = Rtc::new(peripherals.RTC_CNTL);
    let timer_group0 = TimerGroup::new(
        peripherals.TIMG0,
        &clocks,
        &mut system.peripheral_clock_control,
    );
    let mut wdt0 = timer_group0.wdt;
    let timer_group1 = TimerGroup::new(
        peripherals.TIMG1,
        &clocks,
        &mut system.peripheral_clock_control,
    );
    let mut wdt1 = timer_group1.wdt;

    // Disable the RTC and TIMG watchdog timers
    rtc.swd.disable();
    rtc.rwdt.disable();
    wdt0.disable();
    wdt1.disable();

    let mut delay = Delay::new(&clocks);

    // Configure terminal: enable newline conversion
    print!("\x1b[20h");

    let mut rng = Rng::new(peripherals.RNG);

    // Application Loop
    loop {
        let num = rng.random() as u8;
        let sentence = MOCK_SENTENCES.lines().nth(num as usize).unwrap();
        println!("{}", sentence);
        delay.delay_ms(2000_u32);
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this post, an application was created leveraging the random number generator (RNG) peripheral for the ESP32C3 to read random lines from a file. The RNG code was created at the HAL level using the Rust esp32c3-hal. Have any questions/comments? Share your thoughts in the comments below 👇.

If you found this post useful, and if Embedded Rust interests you, stay in the know and skyrocket your learning curve by subscribing to The Embedded Rustacean newsletter:

Subscribe Now to The Embedded Rustacean

Top comments (0)