cancel
Showing results for 
Search instead for 
Did you mean: 

USART stm32h5 rust

HardFaultHero
Associate

Hi everyone,

I’m bringing up USART2 on an STM32 using bare-metal Rust (#![no_std]), direct register access (no HAL, no CubeMX).

The CODE looks correctly

However, the data itself is wrong.

Instead of transmitting 'A' (0x41), the terminal consistently receives values like:

  • 0xCE

  • 0x05

(always stable, but never 0x41).

#![no_std]
#![no_main]

use core::ptr::{read_volatile, write_volatile};
use core::panic::PanicInfo;
use cortex_m_rt::entry;

#[panic_handler]
fn panic(_: &PanicInfo) -> ! {
loop {}
}

#[inline(always)]
fn r(addr: u32) -> u32 {
unsafe { read_volatile(addr as *const u32) }
}

#[inline(always)]
fn w(addr: u32, val: u32) {
unsafe { write_volatile(addr as *mut u32, val) }
}

/* ================= BASE ================= */
const RCC: u32 = 0x4402_0C00;
const GPIOA: u32 = 0x4202_0000;
const USART2: u32 = 0x4000_4400;

/* ================= RCC ================= */
const RCC_AHB2ENR: u32 = RCC + 0x08C;
const RCC_APB1LENR: u32 = RCC + 0x09C;
const RCC_CCIPR2: u32 = RCC + 0x0DC;

/* ================= GPIO ================= */
const GPIOA_MODER: u32 = GPIOA + 0x00;
const GPIOA_AFRL: u32 = GPIOA + 0x20;

/* ================= USART ================= */
const USART_CR1: u32 = USART2 + 0x00;
const USART_BRR: u32 = USART2 + 0x0C;
const USART_ISR: u32 = USART2 + 0x1C;
const USART_TDR: u32 = USART2 + 0x28;
const USART_PRESC: u32 = USART2 + 0x2C;

/* ================= BITS ================= */
const USART_CR1_UE: u32 = 1 << 0;
const USART_CR1_RE: u32 = 1 << 2;
const USART_CR1_TE: u32 = 1 << 3;

const USART_ISR_TXFNF: u32 = 1 << 7;
const USART_ISR_TEACK: u32 = 1 << 21;
const USART_ISR_REACK: u32 = 1 << 22;

#[entry]
fn main() -> ! {

/* -------- CLOCK ENABLE -------- */

// GPIOA enable
w(RCC_AHB2ENR, r(RCC_AHB2ENR) | (1 << 0));

// USART2 enable
w(RCC_APB1LENR, r(RCC_APB1LENR) | (1 << 17));

// USART2 clock = PCLK1
w(RCC_CCIPR2, r(RCC_CCIPR2) & !(0b111 << 3));

/* -------- GPIO -------- */

// PA2, PA3 → AF
w(GPIOA_MODER,
(r(GPIOA_MODER) & !(0b1111 << 4)) | (0b1010 << 4)
);

// AF7
w(GPIOA_AFRL,
(r(GPIOA_AFRL) & !(0xFF << 8)) | (0x77 << 8)
);

/* -------- USART -------- */

// Disable
w(USART_CR1, 0);
while (r(USART_ISR) & (USART_ISR_TEACK | USART_ISR_REACK)) != 0 {}

// Prescaler /1
w(USART_PRESC, 0);

// 115200 @ 16 MHz
w(USART_BRR, 16_000_000 / 115_200);

// Enable TX RX
w(USART_CR1, USART_CR1_TE | USART_CR1_RE);

// Enable USART
w(USART_CR1, r(USART_CR1) | USART_CR1_UE);

while (r(USART_ISR) & USART_ISR_TEACK) == 0 {}

/* -------- TX LOOP -------- */
loop {
while (r(USART_ISR) & USART_ISR_TXFNF) == 0 {}
w(USART_TDR, b'A' as u32);
}
}

If anyone has run into “stable baud but wrong characters” on STM32 USART, I’d really appreciate pointers on what to double-check.

Thanks!



6 REPLIES 6
Bob S
Super User

How do you know the baud rate is correct?  Have you looked at the TX line with a scope?

I don't know specifically for the H5 for sure, but with other STM32 families, if you want to send 1 byte you need to write 1 byte, not a 32-bit value (which will actually send 2 bytes - in your code that would be 0x41 0x00, or the other way around, I don't recall).

How do you have USART2 connected to to your PC?  Is this a Nucleo board and USART2 is the port connected through the on-board STLink?

You can always use CubeMX to generate a simple program, or use one of the sample UART projects, run that and see what the register values are, then compare to your register values.

Duc
Senior

Hi @HardFaultHero ,

Could you please share your Rust project so I can try debugging it on my side?


@Bob S wrote:

if you want to send 1 byte you need to write 1 byte, not a 32-bit value


Well, the USART TDR register is a 32-bit register:

AndrewNeil_0-1767713136779.png

https://www.st.com/resource/en/reference_manual/rm0492-stm32h503-line-armbased-32bit-mcus-stmicroelectronics.pdf#page=1421

 


A complex system that works is invariably found to have evolved from a simple system that worked.
A complex system designed from scratch never works and cannot be patched up to make it work.
Pavel A.
Super User

 the terminal consistently receives values like:

Which terminal? Does it expect specific parity (odd or even)? Number of bits?

You seem to use the FIFO mode (TXFNF) - have you enabled it?

> Well, the USART TDR register is a 32-bit register:

Right-o, I was thinking of the SPI data registers, that can take 8/16/32 bit writes.

HardFaultHero
Associate

STM32H533-NUCLEO
After debugging, the baud rate and clock configuration were correct.
The real issue I had was in my RX code, not in the hardware or clock tree.

Below is a minimal working Rust project that continuously transmits the character 'A' at 115200 baud.

Hopefully this helps someone else.

 

.
├── .cargo
│   └── config.toml
├── Cargo.toml
├── memory.x
└── src
    └── main.rs

this is the directory configuration
[package]
name = "stm32h5tls"
version = "0.1.0"
edition = "2024"

[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"

this is the Cargo.toml
#![no_std]
#![no_main]

use core::ptr::{read_volatile, write_volatile};
use core::panic::PanicInfo;
use cortex_m_rt::entry;

#[panic_handler]
fn panic(_: &PanicInfo) -> ! {
    loop {}
}

#[inline(always)]
fn r(addr: u32) -> u32 {
    unsafe { read_volatile(addr as *const u32) }
}

#[inline(always)]
fn w(addr: u32, val: u32) {
    unsafe { write_volatile(addr as *mut u32, val) }
}

/* ================= BASE ================= */
const RCC:    u32 = 0x4402_0C00;
const GPIOA:  u32 = 0x4202_0000;
const USART2: u32 = 0x4000_4400;

/* ================= RCC ================= */
const RCC_CR:        u32 = RCC + 0x000;
const RCC_CFGR1:     u32 = RCC + 0x01C;
const RCC_CFGR2:     u32 = RCC + 0x020;
const RCC_AHB2ENR:   u32 = RCC + 0x08C;
const RCC_APB1LENR:  u32 = RCC + 0x09C;
const RCC_CCIPR1:    u32 = RCC + 0x0D8;

/* ================= GPIO ================= */
const GPIOA_MODER: u32 = GPIOA + 0x00;
const GPIOA_AFRL:  u32 = GPIOA + 0x20;

/* ================= USART ================= */
const USART_CR1:   u32 = USART2 + 0x00;
const USART_BRR:   u32 = USART2 + 0x0C;
const USART_ISR:   u32 = USART2 + 0x1C;
const USART_TDR:   u32 = USART2 + 0x28;
const USART_PRESC: u32 = USART2 + 0x2C;

/* ================= BITS ================= */
const USART_ISR_TXFNF: u32 = 1 << 7;
const USART_ISR_TEACK: u32 = 1 << 21;

#[entry]
fn main() -> ! {

    /* ============================================================
     * CLOCK — HSI 64 MHz / 4 = 16 MHz
     * ============================================================ */

    /* HSI ON + HSIDIV=/4 */
    w(
        RCC_CR,
        (r(RCC_CR) & !(0b11 << 3)) |
        (0b10 << 3) |   // HSIDIV = /4
        (1 << 0)        // HSION
    );

    /* wait HSIRDY */
    while r(RCC_CR) & (1 << 1) == 0 {}

    /* SYSCLK = HSI */
    w(RCC_CFGR1, r(RCC_CFGR1) & !0b11);

    /* wait SWS = HSI */
    while (r(RCC_CFGR1) >> 3) & 0b11 != 0 {}

    /* CFGR2: bus ON, no prescaler */
    w(RCC_CFGR2, 0x0000_0000);

    /* ============================================================
     * PERIPHERAL CLOCK ENABLE
     * ============================================================ */

    /* GPIOA clock */
    w(RCC_AHB2ENR, r(RCC_AHB2ENR) | (1 << 0));

    /* USART2 clock */
    w(RCC_APB1LENR, r(RCC_APB1LENR) | (1 << 17));

    /* USART2 kernel clock = HSI */
    w(
        RCC_CCIPR1,
        (r(RCC_CCIPR1) & !(0b111 << 3)) |
        (0b011 << 3)
    );

    /* ============================================================
     * GPIO — PA2 / PA3 AF7
     * ============================================================ */

    /* PA2, PA3 → Alternate Function */
    w(
        GPIOA_MODER,
        (r(GPIOA_MODER) & !((0b11 << 4) | (0b11 << 6))) |
        ((0b10 << 4) | (0b10 << 6))
    );

    /* PA2, PA3 → AF7 */
    w(
        GPIOA_AFRL,
        (r(GPIOA_AFRL) & !((0b1111 << 8) | (0b1111 << 12))) |
        ((0b0111 << 8) | (0b0111 << 12))
    );

    /* ============================================================
     * USART2 INIT — TX attivo, RX pronto
     * ============================================================ */

    /* Disable USART */
    w(USART_CR1, 0x0000_0000);

    /* Prescaler /1 */
    w(USART_PRESC, 0x0000_0000);

    /* BRR = 16 MHz / 115200 ≈ 139 */
    w(USART_BRR, 139);

    /* UE + TE + RE */
    w(USART_CR1, (1 << 3) | (1 << 2) | (1 << 0));

    /* wait TEACK */
    while r(USART_ISR) & USART_ISR_TEACK == 0 {}

    /* ============================================================
     * TX LOOP — transmit only 'A'
     * ============================================================ */
    loop {
        while r(USART_ISR) & USART_ISR_TXFNF == 0 {}
        w(USART_TDR, b'A' as u32);
    }
}
[build]
target = "thumbv8m.main-none-eabi"

[target.thumbv8m.main-none-eabi]
rustflags = [
  "-C", "link-arg=-Tlink.x",
]
this is the config.toml
MEMORY
{
  /* Flash non-secure: Bank1 + Bank2 = 512 KB */
  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K

  /* SRAM1 non-secure = 128 KB */
  RAM   (rwx): ORIGIN = 0x20000000, LENGTH = 128K
}
this is the memory.x