From f761ae91ee037c2ba3de517a2e4554042787b54e Mon Sep 17 00:00:00 2001 From: boondockenergy Date: Fri, 8 Aug 2025 10:55:26 -0700 Subject: [PATCH 1/6] Add support for true random number generator (RNG) peripheral - Adds `RngExt` trait to constrain and enable RNG peripheral with HSI48 clock - Implements state machine with `Rng` and `Rng` types - Provides blocking and non-blocking read methods for 32-bit random values - Implements `TryRngCore` from `rand` for interoperability with rand traits - Includes error handling for seed and clock error conditions --- Cargo.toml | 1 + examples/rand.rs | 77 ++++++++++++++++++++ src/lib.rs | 1 + src/rng.rs | 181 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 260 insertions(+) create mode 100644 examples/rand.rs create mode 100644 src/rng.rs diff --git a/Cargo.toml b/Cargo.toml index cfe5af67..e4c5f20a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } [dependencies.cortex-m] version = "0.7.7" diff --git a/examples/rand.rs b/examples/rand.rs new file mode 100644 index 00000000..7e7953a2 --- /dev/null +++ b/examples/rand.rs @@ -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::(); + info!("Random float: {}", random_float); + + let random_u8 = rng.unwrap_err().random::(); + 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); + } +} diff --git a/src/lib.rs b/src/lib.rs index c8b663b4..aeef1e46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,7 @@ pub mod syscfg; pub mod time; pub mod timer; // pub mod watchdog; +pub mod rng; #[cfg(all( feature = "hrtim", diff --git a/src/rng.rs b/src/rng.rs new file mode 100644 index 00000000..bba0ad0d --- /dev/null +++ b/src/rng.rs @@ -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 { + 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; +} + +impl RngExt for RNG { + /// Constrain the RNG register block and return an Rng. + /// Enables the RNG peripheral in the AHB2ENR register. + /// Enables the HSI48 clock used by RNG + fn constrain(self, rcc: &mut Rcc) -> Rng { + // 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: core::marker::PhantomData, +} + +impl Rng { + /// 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 { + unsafe { + (*RNG::ptr()) + .cr() + .modify(|_, w| w.rngen().set_bit().ced().clear_bit()) + }; + + Rng { + _state: PhantomData, + } + } +} + +impl Rng { + /// Stop the RNG peripheral + /// + /// Returns an [`Rng`] in the `Stopped` state + pub fn stop(self) -> Rng { + 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 { + 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 { + // 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() { + 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 { + type Error = RngError; + + fn try_next_u32(&mut self) -> Result { + self.read_blocking() + } + + fn try_next_u64(&mut self) -> Result { + 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::()) { + let value = self.try_next_u32()?; + chunk.copy_from_slice(&value.to_ne_bytes()); + } + Ok(()) + } +} From 28bf2a9cce54af0afc05b41114ccf396377ee2e3 Mon Sep 17 00:00:00 2001 From: boondockenergy Date: Wed, 13 Aug 2025 12:31:58 -0700 Subject: [PATCH 2/6] Use rand_core in dependencies, move rand into dev-dependencies for the example. --- Cargo.toml | 3 ++- src/rng.rs | 13 ++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e4c5f20a..41328b51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +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 } +rand_core = { version = "0.9", default-features = false } [dependencies.cortex-m] version = "0.7.7" @@ -73,6 +73,7 @@ bme680 = "0.6.0" embedded-sdmmc = "0.3.0" usb-device = { version = "0.3.2", features = ["defmt"] } usbd-serial = "0.2.2" +rand = { version = "0.9", default-features = false } #TODO: Separate feature sets [features] diff --git a/src/rng.rs b/src/rng.rs index bba0ad0d..b873df38 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -1,6 +1,6 @@ //! True Random Number Generator (TRNG) -use rand::TryRngCore; +use rand_core::TryRngCore; use crate::{rcc::Rcc, stm32::RNG}; use core::{fmt::Formatter, marker::PhantomData}; @@ -27,6 +27,17 @@ impl core::fmt::Display for RngError { } } +#[cfg(feature = "defmt")] +impl defmt::Format for RngError { + fn format(&self, fmt: defmt::Formatter) { + match self { + RngError::NotReady => defmt::write!(fmt, "RNG Not ready"), + RngError::SeedError => defmt::write!(fmt, "RNG Seed error"), + RngError::ClockError => defmt::write!(fmt, "RNG Clock error"), + } + } +} + /// Extension trait for the RNG register block pub trait RngExt { fn constrain(self, rcc: &mut Rcc) -> Rng; From 3fe641d36ca50720531c2f1a52d6f92519734a0d Mon Sep 17 00:00:00 2001 From: boondockenergy Date: Wed, 13 Aug 2025 12:43:51 -0700 Subject: [PATCH 3/6] Handle possible race between SR and DR register reads per RM0440 26.7.3 RNDATA description --- src/rng.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/rng.rs b/src/rng.rs index b873df38..a69b64a2 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -136,6 +136,12 @@ impl Rng { /// /// Returns an [`RngError`] if the RNG is not ready or reports an error condition. /// + /// Once the RNG is ready, 4 consecutive 32 bit reads can be performed without blocking, + /// at which point the internal FIFO will be refilled after 216 periods of the AHB clock + /// (RM0440 26.7.3) + /// + /// While the RNG is filling the FIFO, the function will return [`RngError::NotReady`]. + /// /// For blocking reads use [`read_blocking()`] pub fn read_non_blocking(&self) -> Result { // Read the SR register to check if there is an error condition, @@ -153,7 +159,10 @@ impl Rng { if status.drdy().bit_is_set() { // Data is ready. Read the DR register and return the value. - Ok(unsafe { (*RNG::ptr()).dr().read().bits() }) + match unsafe { (*RNG::ptr()).dr().read().bits() } { + 0 => Err(RngError::SeedError), + data => Ok(data), + } } else { Err(RngError::NotReady) } From 520992c5e80bddda433edee2a8a6c71ca958473a Mon Sep 17 00:00:00 2001 From: boondockenergy Date: Wed, 13 Aug 2025 13:01:31 -0700 Subject: [PATCH 4/6] Add seed error recovery sequence. Add more comments and documented `RngError` --- src/rng.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/rng.rs b/src/rng.rs index a69b64a2..3034c322 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -5,9 +5,15 @@ use rand_core::TryRngCore; use crate::{rcc::Rcc, stm32::RNG}; use core::{fmt::Formatter, marker::PhantomData}; +/// Error type for the RNG peripheral pub enum RngError { + /// The DRDY bit is not set in the status register. NotReady, + /// A noise source seed error was detected. [`seed_error_recovery()`] + /// should be called to recover from the error. SeedError, + /// A clock error was detected. fRNGCLOCK is less than fHCLK/32 + /// The clock error condition is automatically cleared when the clock condition returns to normal. ClockError, } @@ -116,6 +122,27 @@ impl Rng { unsafe { (*RNG::ptr()).sr().read().ceis().bit_is_set() } } + /// Perform recovery sequence of a seed error from RM0440 26.3.7 + /// + /// The SEIS bit is cleared, and 12 words and read and discarded from the DR register. + /// + /// If the recovery sequence was successful, the function returns `Ok(())`. + /// If the SEIS bit is still set after the recovery sequence, [`RngError::SeedError`] is returned. + pub fn seed_error_recovery(&mut self) -> Result<(), RngError> { + // Clear SEIS bit + unsafe { (*RNG::ptr()).sr().clear_bits(|w| w.seis().clear_bit()) }; + // Read and discard 12 words from DR register + for _ in 0..12 { + unsafe { (*RNG::ptr()).dr().read() }; + } + // Confirm SEIS is still clear + if unsafe { (*RNG::ptr()).sr().read().seis().bit_is_clear() } { + Ok(()) + } else { + Err(RngError::SeedError) + } + } + /// Blocking read of a random u32 from the RNG in polling mode. /// /// Returns an [`RngError`] if the RNG reports an error condition. @@ -158,7 +185,9 @@ impl Rng { } if status.drdy().bit_is_set() { - // Data is ready. Read the DR register and return the value. + // The data ready bit is set. Read the DR register and check if it is zero. + // A zero read indicates a seed error between reading SR and DR registers + // see RM0440 26.7.3 RNDATA description. match unsafe { (*RNG::ptr()).dr().read().bits() } { 0 => Err(RngError::SeedError), data => Ok(data), From e67030d015222477bf4a760ec8726db87fa1104d Mon Sep 17 00:00:00 2001 From: boondockenergy Date: Wed, 13 Aug 2025 13:10:26 -0700 Subject: [PATCH 5/6] Add inline(always) to status register error flag accessor methods --- src/rng.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rng.rs b/src/rng.rs index 3034c322..6804c62e 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -113,11 +113,13 @@ impl Rng { } /// Check if the seed error flag is set + #[inline(always)] pub fn is_seed_error(&self) -> bool { unsafe { (*RNG::ptr()).sr().read().seis().bit_is_set() } } /// Check if the clock error flag is set + #[inline(always)] pub fn is_clock_error(&self) -> bool { unsafe { (*RNG::ptr()).sr().read().ceis().bit_is_set() } } From fe0966aa31058ffebbd7a8b755dbc2cdda0650d4 Mon Sep 17 00:00:00 2001 From: boondockenergy Date: Wed, 13 Aug 2025 14:23:23 -0700 Subject: [PATCH 6/6] Use debug format string in rand example --- examples/rand.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/rand.rs b/examples/rand.rs index 7e7953a2..4c013c4c 100644 --- a/examples/rand.rs +++ b/examples/rand.rs @@ -58,7 +58,7 @@ fn main() -> ! { info!("Random u8: {}", random_u8); let random_array: [f32; 8] = rng.unwrap_err().random(); - info!("Random array {}", &random_array); + info!("Random array {:?}", &random_array); let random_dist = between.sample(&mut rng.unwrap_err()); info!("Random dist: {}", random_dist); @@ -67,7 +67,7 @@ fn main() -> ! { info!("Random range: {}", random_range); let random_choice = slice.choose(&mut rng.unwrap_err()); - info!("Random choice: {}", random_choice); + info!("Random choice: {:?}", random_choice); let random_bernoulli = bernoulli.sample(&mut rng.unwrap_err()); info!("Random bernoulli: {}", random_bernoulli);