Simplex version of uart communication works in C but not on rust

I'm trying to configure my stm32f446 nucleo board to transmit a simple message to the pc over usart2. I've read the manual and configured it to work with a single buffer. However, the rust code compiles but does not print anything to the putty console. The C version works fine. I was trying to step through the code while looking at the disassembly view, but code was behaving unpredictably.

This is the original C code written by controller's tech:



/**
  ******************************************************************************

  CLOCK Setup For STM32F446RE
  Author:   ControllersTech
  Updated:  2nd Aug 2020

  ******************************************************************************
  Copyright (C) 2017 ControllersTech.com

  This is a free software under the GNU license, you can redistribute it and/or modify it under the terms
  of the GNU General Public License version 3 as published by the Free Software Foundation.
  This software library is shared with public for educational purposes, without WARRANTY and Author is not liable for any damages caused directly
  or indirectly by this software, read more about this on the GNU General Public License.

  ******************************************************************************
*/

/**
  * @brief  System Clock Configuration
  *         The system Clock is configured as follow : 
  *            System Clock source            = PLL (HSE)
  *            SYSCLK(Hz)                     = 180000000
  *            HCLK(Hz)                       = 180000000
  *            AHB Prescaler                  = 1
  *            APB1 Prescaler                 = 4
  *            APB2 Prescaler                 = 2
  *            HSE Frequency(Hz)              = 8000000
  *            PLL_M                          = 4
  *            PLL_N                          = 180
  *            PLL_P                          = 2
  *            VDD(V)                         = 3.3
  *            Main regulator output voltage  = Scale1 mode
  *            Flash Latency(WS)              = 5
  * @param  None
  * @retval None
  */

#include "RccConfig.h"

void SysClockConfig (void)
{
		/*************>>>>>>> STEPS FOLLOWED <<<<<<<<************
	
	1. ENABLE HSE and wait for the HSE to become Ready
	2. Set the POWER ENABLE CLOCK and VOLTAGE REGULATOR
	3. Configure the FLASH PREFETCH and the LATENCY Related Settings
	4. Configure the PRESCALARS HCLK, PCLK1, PCLK2
	5. Configure the MAIN PLL
	6. Enable the PLL and wait for it to become ready
	7. Select the Clock Source and wait for it to be set
	
	********************************************************/
	
	
	#define PLL_M 	4
	#define PLL_N 	180
	#define PLL_P 	0  // PLLP = 2

	// 1. ENABLE HSE and wait for the HSE to become Ready
	RCC->CR |= RCC_CR_HSEON;  
	while (!(RCC->CR & RCC_CR_HSERDY));
	
	// 2. Set the POWER ENABLE CLOCK and VOLTAGE REGULATOR
	RCC->APB1ENR |= RCC_APB1ENR_PWREN;
	PWR->CR |= PWR_CR_VOS; 
	
	
	// 3. Configure the FLASH PREFETCH and the LATENCY Related Settings
	FLASH->ACR = FLASH_ACR_ICEN | FLASH_ACR_DCEN | FLASH_ACR_PRFTEN | FLASH_ACR_LATENCY_5WS;
	
	// 4. Configure the PRESCALARS HCLK, PCLK1, PCLK2
	// AHB PR
	RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
	
	// APB1 PR
	RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;
	
	// APB2 PR
	RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;
	
	
	// 5. Configure the MAIN PLL
	RCC->PLLCFGR = (PLL_M <<0) | (PLL_N << 6) | (PLL_P <<16) | (RCC_PLLCFGR_PLLSRC_HSE);

	// 6. Enable the PLL and wait for it to become ready
	RCC->CR |= RCC_CR_PLLON;
	while (!(RCC->CR & RCC_CR_PLLRDY));
	
	// 7. Select the Clock Source and wait for it to be set
	RCC->CFGR |= RCC_CFGR_SW_PLL;
	while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
}


this is the ported rust code that I wrote:

#![no_std]
#![no_main]
pub mod stm32_lib;

// pick a panicking behavior
use panic_halt as _; // you can put a breakpoint on `rust_begin_unwind` to catch panics
                     // use panic_abort as _; // requires nightly
                     // use panic_itm as _; // logs messages over ITM; requires ITM support
                     // use panic_semihosting as _; // logs messages to the host stderr; requires a debugger

use cortex_m_rt::entry;
use stm32_lib::rcc;
use stm32f4::stm32f446;

pub fn uart_send_data(data: &str, uart: &mut stm32f446::USART2) {
    //Write the data to send in the USART DATA register
    for &byte in data.as_bytes() {
        uart.dr.write(|w| w.dr().bits(u16::from(byte)));
        while !(uart.sr.read().tc().bit()) {}
    }
}

#[entry]
fn main() -> ! {
    let peripherals = stm32f446::Peripherals::take().unwrap();
    let mut rcc = peripherals.RCC;
    let mut timer = peripherals.TIM11; //used for the delay
    let mut flash = peripherals.FLASH;
    let mut gpio = peripherals.GPIOA; //general purpose pin normally used as output.
    let mut pwr = peripherals.PWR;
    let mut uart = peripherals.USART2;

    rcc::initialize_clock(&mut rcc, &mut pwr, &mut flash);
    rcc::config_timer(&mut rcc, &mut timer);
    //Enable the UART CLOCK and GPIO CLOCK
    rcc.apb1enr.modify(|_r, w| w.usart2en().set_bit());
    rcc.ahb1enr.modify(|_r, w| w.gpioaen().set_bit());

    //Configure the alternate mode register for the gpio
    gpio.moder.write(|w| w.moder2().bits(0b10));
    gpio.moder.modify(|_r, w| w.moder3().bits(0b10));

    gpio.ospeedr.modify(|_r, w| w.ospeedr2().bits(3));
    gpio.ospeedr.modify(|_r, w| w.ospeedr3().bits(0b11));

    gpio.afrl.modify(|_r, w| w.afrl2().bits(0b0111));
    gpio.afrl.modify(|_r, w| w.afrl3().bits(0b0111));

    //Enable the UART by writing the UE bit in the USART_CR1 register
    uart.cr1.write(|w| w.ue().enabled());

    //Program the M bit to define the word length
    uart.cr1.modify(|_r, w| w.m().clear_bit());

    //Select the desired baud rate using USART BRR register
    uart.brr.write(|w| w.div_fraction().bits(7));
    uart.brr.modify(|_r, w| w.div_mantissa().bits(24));

    //Enable the transmitter/Reciver by Setting the TE and RE bits in USART_CR1 Register
    uart.cr1.modify(|_r, w| w.te().set_bit()); // TE = 1;
    uart.cr1.modify(|_r, w| w.re().set_bit()); // RE = 1;

    uart_send_data("ok", &mut uart);

    loop {
        //
    }
}

Just need some direction I could take in debugging the issue. thanks for reading

you are using the pac crate directly, is there any reason why you don't use the hal library crate?

if for some reason, you have to write your own abstractions and bypass the hal drivers, I suggest you use the hal crate as your start point and reference implementation, so you are not completely at dark. for the stm32f446 chip, the corresponding hal driver can be found here:

BTW, just in case you are new to rust and not familiar with the terms:

  • pac stands for "peripheral access crate", which contains low level register mapping definitions, and is typically auto-generated from vendors' spec sheets;
  • hal stands for "hardware abstraction layer", which usually implements somewhat higher level abstract APIs on top of pac for certain chips
    • there are also board level hal libraries too

most of the time, you use hal crates in you application, oh, and typically you also need the so-called "micro-architecture (or runtime) crates" for device initialization and basic runtime support -- the cortex_m_rt::entry attribute over main() in your code is one such example.

Thanks for the reply. I've been around the space some time now, im 29 and I've been super interested in electronics since I was a kid.
However, I'm not necessarily the best or most talented (in some regards). I do have a degree in electrical engineering, but my math skills mostly saved me there; but i digress sorry... I thought the closer I got to the hardware (excluding Asm in some cases) the better off I'd be, done some copy paste embedded C but found C hard to really learn (pointers and memory management), even tho I was kinda good in it. When I found out about rust I originally started out with hal but found the "hidden layers" to be annoying when something was not working like I'd expected it to. I need to be able to control the stm32f4 like I can my own brain but finding an interface that I can learn well is proving difficult.

I know I shouldn't be recreating the wheel so I will consider learning the hal method. I was just wondering, what if I wanted to build custom PCB's wouldn't a pac level mastery be preferred over hal (or any higher level of abstraction, except for production scenarios)?

it's ok to use pac directly if you need to, but then it's no real difference than C. the power of rust type system cannot help at htis low level of abstraction.

this is likely due to lack of familiarity with the rust patterns and idioms. it's the same for many people when using C++ in embedded environment. even some C compiler optimizations is scary to some poeple so they always use DEBUG so they have the "confidence" of their code.

there's no real solution to such problems, you just need time to get yourself confortable with the language.

the hals are really very thin layer on top of pacs, I rarely have the need to use the pacs directly, so I would not say it's preferred, but there's nothing wrong to write your own drivers if you don't want to use the existing hal crates.

there are chip hal crates and board hal crates, custom boards can create their own board hals, which typically would use the chip hals. for example, you can find a list of boards using the rp2040 chip:

it's ok to use pac directly if you need to, but then it's no real difference than C. the power of rust type system cannot help at htis low level of abstraction.

I didnt see it this way to be honest and your right, it would mean I would be writting everything myself without relying on crates which wouldnt make much sense. I found the documentation of the crates a bit hard to decipher but that was probably because I rushed ahead without fully knowing rust (still haven't even learnt traits, which seems to be ubiquitous in the language.

I just need to slow down and learn rust thoroughly

thanks for taking the time to reply to my post, it was very much appreciated and now I know the direction I must go. Thanks for everything, have a good one.

yeah, the libraries make heavy usage (and thus take great advantage) of language features, this is one major selling point for embedded rust, but it's also the main hurdle for many new users.

it's no doubt that rust has a much deeper learning curve (in general, not specific to embedded platforms), compared to C/C++, and also go, zig, etc, due to the complexity of the type system.

and I think the situation is even tougher for embedded software developers, as many of them have electronics engineering background, it takes them even longer to grasp the complicated type system and many of the functional programming concepts.

for example, most rust code heavily use generics, closures, higher order functions, etc, yet from my personal experiences, I have rarely used even function pointers in embedded C.

on the other hand, I think the ownership and linearity of types are much easier to grasp, once you have seen examples how they represent hardware resources.

Is there any chance that delay loop was optimized out? It shouldn't be, but check.

Having used C++ quite a bit in embedded systems I'm inclined to take issue with that statement for reasons outlined here: Feeling down about Rust for serious projects - #17 by ZiCog

C is of course a much simpler language, but that just moves much difficulty into being sure whatever you have created actually works properly.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.