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
🔌 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 provideprintln!
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};
🌍 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");
📝 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 theinclude_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();
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();
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);
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);
📱 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);
}
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);
}
}
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 👇.
Top comments (0)