STM32U5 series MCUs HAL, PAC and peripheral access.

Hello

After a good 10 hours in the embedded rust world, I am satisfied to have a test program running on a NUCLEO-U545RE-Q, using probe-rs, based on the embeded rust book (wich seems to be work in progress), and this template: GitHub - knurling-rs/app-template: Quickly set up a `probe-rs` + `defmt` + `flip-link` embedded project

I modified src/bin/hello.rs to my needs by trying out the SysTick and then attempting to turn on the user LED connected to PA5 by using the PAC directly, before learning that that is not the right way (Initialize stm32 clock and write leds - #3 by nerditation). Strugling to find a HAL for the used MCU STM32U545RE before, I set out again using less specific search terms and found this: stm32_hal2 - Rust
The STM32U5 series of MCUs is not supported yet, therefore another dead end...

In src/bin/hello.rs i try to troubleshoot what I am doing by:

  1. Reading gpio bits showing what they should be. This already shows that my approach fails.
  2. Modifying MODEr to make PORTA pin 5 push-pull and and setting pin 5 high.
  3. Reading and showing what they should be again. Still futile.
  4. (Systick is intended to blink the LED.)
#![no_main]
#![no_std]

use app as _; // global logger + panicking-behavior + memory layout


// Added semihosting functions.
use cortex_m_semihosting::{debug, hprintln};
// Added PAC (Peripheral Access crate):
use stm32u5::stm32u545 as pac;
// Added general cortex PAC (Peripheral Access crate):
use cortex_m as mpac;
//
use core::ops::Deref;


#[cortex_m_rt::entry]
fn main() -> ! {
    // Cortex M Peripherals API.
    let mut cp = mpac::Peripherals::take().unwrap();
    // Peripherals API.
    let mut p = pac::Peripherals::take().unwrap();
    //defmt::println!("Hello, world!");

    // PortA (for blinking LED with PA5).
    let mut gpioa = p.GPIOA.deref();
    hprintln!("Before regmod:\n\
               moder : {:b}\n\
               should: 10101011111111111111111111111111\n\n\
               outreg: {:b}\n\
               should: 00000000000000000000000000000000\n\n\
               inreg : {:b}\n\
               should: 0000000000000000XXXXXXXXXXXXXXXX",
               gpioa.moder().read().bits(),
               gpioa.odr().read().bits(),
               gpioa.idr().read().bits()
            );

    gpioa.moder().modify(
        |r, w| unsafe { w.bits(
                r.bits() /* Existing bits. */
                & !(1 << 11) /* Set bit 11 zero. */
                | (1 << 10)) /* Set bit 10 one. */
            }
        );
    gpioa.bsrr().write(|w| unsafe { w.bits(1 << 5) } );

    hprintln!("\n\nAfter regmod:\n\
               moder : {:b}\n\
               should: 10101011111111111111011111111111\n\n\
               outreg: {:b}\n\
               should: 00000000000000000000000000100000\n\n\
               inreg : {:b}\n\
               should: 0000000000000000XXXXXXXXXX1XXXXX",
               gpioa.moder().read().bits(),
               gpioa.odr().read().bits(),
               gpioa.idr().read().bits()
            );

    // Trying out systicks.
    let mut systick = cp.SYST;
    systick.set_clock_source(mpac::peripheral::syst::SystClkSource::Core);
    systick.set_reload(10_000);
    systick.clear_current();
    systick.enable_interrupt();
    systick.enable_counter();
    
    loop {}
}

// https://docs.rust-embedded.org/book/start/exceptions.html
// "Note that the exception attribute transforms definitions of static variables
// inside the function by wrapping them into unsafe blocks and providing us with
// new appropriate variables of type &mut of the same name. Thus we can
// dereference the reference via * to access the values of the variables without
// needing to wrap them in an unsafe block."
#[cortex_m_rt::exception]
fn SysTick() {
    static mut TICK: bool = true;
    static mut COUNT: usize = 0;
    if *TICK {
        hprintln!("Tick");
        *TICK = false;
    } else {
        hprintln!("Tack");
        *TICK = true;
    }
    *COUNT += 1;
    if *COUNT > 5 {
        app::exit()
    }
}

Output from cargo run --bin hello (Omitted warnings form unused debug import and variables not needing to be mut):

Before regmod:
moder : 10101011111111111111111111111111
should: 10101011111111111111111111111111

outreg: 10101011111111111111111111111111
should: 00000000000000000000000000000000

inreg : 10101011111111111111111111111111
should: 0000000000000000XXXXXXXXXXXXXXXX


After regmod:
moder : 10101011111111111111111111111111
should: 10101011111111111111011111111111

outreg: 10101011111111111111111111111111
should: 00000000000000000000000000100000

inreg : 10101011111111111111111111111111
should: 0000000000000000XXXXXXXXXX1XXXXX
Tick
Tack
Tick
Tack
Tick
Tack
Firmware exited successfully

Does anybody know of a HAL for the STM32U5 series MCUs and I was just not able to find it?

If not, any chance of making this work with the PAC? I have the impression that stm32u5 - Rust is finished as intended (as it seems auto generated). If that is not the case, what gives the clue? The Cargo.toml of the above linked STM32-HAL2 does not list the STM32U5 series either, but that might just be a delay in timing: stm32-hal/Cargo.toml at main · David-OConnor/stm32-hal · GitHub.

If even that is not possible, I would be (until a HAL is available) happy to do this with C style unsafe pointers. Hints how to do that, would be appreciated.

The Embassy project implements a HAL for many STM32 MCUs, including yours:

I have use Embassy (the embedded OS) in the past, and it is quite nice. I have not used the HAL in isolation but, if I understand correctly, you should be able to use the HAL without the rest of Embassy OS.

1 Like

Thank you very much @pzometa very appreciated.

I can confirm that that embassy_stm32 - Rust works perfectly as a HAL, at least for turning on the user LED.

For those interested, I created a new project, reusing most of Cargo.toml and all of .cargo/config evolved from following the embedded rust book and other examples. Here is the new, standalone main.rs, doing nothing but turn on the LED on the development board using the embassy_stm32.

#![no_main]
#![no_std]

// Logger
use defmt_rtt as _; // global logger
// Panic handler
use panic_probe as _;
// HAL (Hardware Abstraction Layer):
use embassy_stm32::gpio::{Level, Output, Speed};


#[cortex_m_rt::entry]
fn main() -> ! {
    let p = embassy_stm32::init(Default::default());
    let mut led = Output::new(p.PA5, Level::High, Speed::Medium);

    semihosting::process::exit(0);
}
1 Like