Skip to content
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