Skip to content

Add support for true random number generator (RNG) peripheral #224

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 13, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ stm32-usbd = { version = "0.7.0", optional = true }
fixed = { version = "1.28.0", optional = true }
embedded-io = "0.6"
stm32-hrtim = { version = "0.1.0", optional = true }
rand = { version = "0.9", default-features = false }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use rand_core instead of rand?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed to rand_core in dependencies, and moved rand to dev-dependencies which is required for the example.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Thanks


[dependencies.cortex-m]
version = "0.7.7"
Expand Down
77 changes: 77 additions & 0 deletions examples/rand.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! Example of using the [`Rng`] peripheral.
//!
//! This example demonstrates common use cases of the [`rand::Rng`] trait using the G4 TRNG.
//!
//! ```DEFMT_LOG=debug cargo run --release --example rand --features stm32g431,defmt -- --chip STM32G431KBTx```

#![deny(warnings)]
#![deny(unsafe_code)]
#![no_main]
#![no_std]

use hal::prelude::*;
use hal::rng::RngExt;
use hal::stm32;
use rand::distr::Bernoulli;
use rand::distr::Distribution;
use rand::distr::Uniform;
use rand::seq::IndexedRandom;
use rand::{Rng, TryRngCore};
use stm32g4xx_hal as hal;

use cortex_m_rt::entry;

#[macro_use]
mod utils;

use utils::logger::info;

#[entry]
fn main() -> ! {
utils::logger::init();

info!("start");
let dp = stm32::Peripherals::take().expect("cannot take peripherals");
let cp = cortex_m::Peripherals::take().expect("cannot take core peripherals");

let mut rcc = dp.RCC.constrain();

let mut delay = cp.SYST.delay(&rcc.clocks);

// Constrain and start the random number generator peripheral
let rng = dp.RNG.constrain(&mut rcc).start();

// Create a Uniform distribution sampler between 100 and 1000
let between = Uniform::try_from(100..1000).unwrap();

// Create a Bernoulli distribution sampler with a 20% probability of returning true
let bernoulli = Bernoulli::new(0.2).unwrap();

// A slice of values for IndexedRandom::choose
let slice = ["foo", "bar", "baz"];

loop {
let random_float = rng.unwrap_err().random::<f32>();
info!("Random float: {}", random_float);

let random_u8 = rng.unwrap_err().random::<u8>();
info!("Random u8: {}", random_u8);

let random_array: [f32; 8] = rng.unwrap_err().random();
info!("Random array {}", &random_array);

let random_dist = between.sample(&mut rng.unwrap_err());
info!("Random dist: {}", random_dist);

let random_range = rng.unwrap_err().random_range(-10..10);
info!("Random range: {}", random_range);

let random_choice = slice.choose(&mut rng.unwrap_err());
info!("Random choice: {}", random_choice);

let random_bernoulli = bernoulli.sample(&mut rng.unwrap_err());
info!("Random bernoulli: {}", random_bernoulli);

delay.delay_ms(1000);
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ pub mod syscfg;
pub mod time;
pub mod timer;
// pub mod watchdog;
pub mod rng;

#[cfg(all(
feature = "hrtim",
Expand Down
181 changes: 181 additions & 0 deletions src/rng.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
//! True Random Number Generator (TRNG)

use rand::TryRngCore;

use crate::{rcc::Rcc, stm32::RNG};
use core::{fmt::Formatter, marker::PhantomData};

pub enum RngError {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind implementing defmt::Format when the defmt feature is enabled?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a conditional impl of defmt::Format

NotReady,
SeedError,
ClockError,
}

impl core::fmt::Debug for RngError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
RngError::NotReady => write!(f, "RNG Not ready"),
RngError::SeedError => write!(f, "RNG Seed error"),
RngError::ClockError => write!(f, "RNG Clock error"),
}
}
}

impl core::fmt::Display for RngError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
core::fmt::Debug::fmt(self, f)
}
}

/// Extension trait for the RNG register block
pub trait RngExt {
fn constrain(self, rcc: &mut Rcc) -> Rng<Stopped>;
}

impl RngExt for RNG {
/// Constrain the RNG register block and return an Rng<Stopped>.
/// Enables the RNG peripheral in the AHB2ENR register.
/// Enables the HSI48 clock used by RNG
fn constrain(self, rcc: &mut Rcc) -> Rng<Stopped> {
// Enable RNG in AHB2ENR
rcc.ahb2enr().modify(|_, w| w.rngen().set_bit());

// Enable HSI48 clock used by the RNG
rcc.enable_hsi48();

Rng {
_state: PhantomData,
}
}
}

/// Type states for the RNG peripheral
pub struct Stopped;
pub struct Running;

/// True Random Number Generator (TRNG)
pub struct Rng<State> {
_state: core::marker::PhantomData<State>,
}

impl Rng<Stopped> {
/// Start the RNG peripheral
///
/// Enables clock error detection and starts random number generation.
///
/// Retrurns an [`Rng`] in the `Running` state
pub fn start(self) -> Rng<Running> {
unsafe {
(*RNG::ptr())
.cr()
.modify(|_, w| w.rngen().set_bit().ced().clear_bit())
};

Rng {
_state: PhantomData,
}
}
}

impl Rng<Running> {
/// Stop the RNG peripheral
///
/// Returns an [`Rng`] in the `Stopped` state
pub fn stop(self) -> Rng<Stopped> {
unsafe { (*RNG::ptr()).cr().modify(|_, w| w.rngen().clear_bit()) };

Rng {
_state: PhantomData,
}
}

/// Check if the RNG is ready
#[inline(always)]
pub fn is_ready(&self) -> bool {
unsafe { (*RNG::ptr()).sr().read().drdy().bit_is_set() }
}

/// Check if the seed error flag is set
pub fn is_seed_error(&self) -> bool {
unsafe { (*RNG::ptr()).sr().read().seis().bit_is_set() }
}

/// Check if the clock error flag is set
pub fn is_clock_error(&self) -> bool {
unsafe { (*RNG::ptr()).sr().read().ceis().bit_is_set() }
}

/// Blocking read of a random u32 from the RNG in polling mode.
///
/// Returns an [`RngError`] if the RNG reports an error condition.
/// Polls the data ready flag until a random word is ready to be read.
///
/// For non-blocking operation use [`read_non_blocking()`]
pub fn read_blocking(&self) -> Result<u32, RngError> {
loop {
match self.read_non_blocking() {
Ok(value) => return Ok(value),
Err(RngError::NotReady) => continue,
Err(e) => return Err(e),
}
}
}

/// Non blocking read of a random u32 from the RNG in polling mode.
///
/// Returns an [`RngError`] if the RNG is not ready or reports an error condition.
///
/// For blocking reads use [`read_blocking()`]
pub fn read_non_blocking(&self) -> Result<u32, RngError> {
// Read the SR register to check if there is an error condition,
// and if the DRDY bit is set to indicate a valid random number is available.
let status = unsafe { (*RNG::ptr()).sr().read() };

// Check if the seed or clock error bits are set
if status.seis().bit_is_set() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not know about G4, but I believe for the H5 you have to reset the bit to ensure the peripheral is restored to a valid state. See stm32-rs/stm32h5xx-hal#37

Copy link
Contributor Author

@boondocklabs boondocklabs Aug 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a seed_error_recovery() method that performs the recovery sequence from a seed error. The clock error resets itself when the clock condition (fRNGCLOCK > fHCLK/32) is satisfied per RM0440.

This requires the caller calls seed_error_recovery() if a read returns RngError::SeedError. I considered making reads self-recover - but I figured in some cases it would be useful to know there was a seed error so I've left it explicit.

return Err(RngError::SeedError);
}

if status.ceis().bit_is_set() {
return Err(RngError::ClockError);
}

if status.drdy().bit_is_set() {
// Data is ready. Read the DR register and return the value.
Ok(unsafe { (*RNG::ptr()).dr().read().bits() })
} else {
Err(RngError::NotReady)
}
}
}

/// Implement [`rand::TryRngCore`] for the RNG peripheral.
///
/// Since this is a fallible trait, to use this as a [`rand::RngCore`] with [`rand::Rng`],
/// the [`unwrap_err`] method can be used for compatibility with [`rand::Rng`] but will panic on error.
///
/// See https://docs.rs/rand/latest/rand/trait.TryRngCore.html
impl TryRngCore for &Rng<Running> {
type Error = RngError;

fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
self.read_blocking()
}

fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
let mut result = 0u64;
for _ in 0..2 {
result |= self.try_next_u32()? as u64;
result <<= 32;
}
Ok(result)
}

fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> {
for chunk in dst.chunks_mut(size_of::<u32>()) {
let value = self.try_next_u32()?;
chunk.copy_from_slice(&value.to_ne_bytes());
}
Ok(())
}
}
Loading