STM32F4 Embedded Rust at the PAC: UART Communication
Tags: rust, beginners, programming, embedded
Published: 2023-03-07 20:35:30

## 🎬 Introduction

UART (Universal Asynchronous Receiver/Transmitter) is a communication protocol used for serial communication between two devices. In embedded systems, UART is still commonly used to communicate with other devices such as sensors, displays, and other microcontrollers. In this post, I will work through implementing a simple UART transmitter in Rust at the PAC (peripheral access crate) level. The application will simply send a character repeatedly to a connected host PC.

#### If you find this post useful, and if Embedded Rust interests you, stay in the know and skyrocket your learning curve by subscribing to The Embedded Rustacean newsletter:

{% cta  http://www.theembeddedrustacean.com %} Subscribe Now to The Embedded Rustacean{% endcta%} 

### **📚 Knowledge Pre-requisites**

To understand the content of this post, you need the following:

* Basic knowledge of coding in Rust.
    
* Familiarity with the basic template for creating embedded applications in Rust.
    
* Familiarity with [**UART communication basics**](https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter).
    

### **💾 Software Setup**

All the code presented in this post in addition to instructions for the environment and toolchain setup is available on the [**apollolabsdev Nucleo-F401RE**](https://github.com/apollolabsdev/stm32-nucleo-f401re) git repo. Note that if the code on the git repo is slightly different then it means that it was modified to enhance the code quality or accommodate any HAL/Rust updates.

In addition to the above, you would need to install some sort of serial communication terminal on your host PC. Some recommendations include:

**For Windows**:

* [**PuTTy**](https://www.putty.org/)
    
* [**Teraterm**](https://ttssh2.osdn.jp/index.html.en)
    

**For Mac and Linux**:

* [**minicom**](https://wiki.emacinc.com/wiki/Getting_Started_With_Minicom)
    

Some installation instructions for the different operating systems are available in the [**Discovery Book**](https://docs.rust-embedded.org/discovery/microbit/06-serial-communication/index.html).

### 🛠 Hardware Setup

#### 🧰 Materials

* [**Nucleo-F401RE board**](https://amzn.to/3yn6AIb)
    

![Nucleo Board](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tbn7stgyuy79xi94g8hu.jpg)

#### **🔌 Connections**

There will be no need for external connections. On-board Nucleo-F401RE connections will be utilized and include the following:

* The UART Tx line that connects to the PC through the onboard USB bridge is via pin PA2 on the microcontroller. This is a hardwired pin, meaning you cannot use any other for this setup. Unless you are using a different board other than the Nucleo-F401RE, you have to check the relevant documentation (reference manual or datasheet) to determine the number of the pin.
    

## **👨‍🎨 Software Design**

The application in this post is a simple UART transmitter that repeatedly sends the same letter. However, since we are developing at the PAC level, I'd like to cover the steps of configuration and operation ahead of the implementation. Luckily, the [reference manual](https://www.st.com/resource/en/reference_manual/dm00096844-stm32f401xb-c-and-stm32f401xd-e-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf) for the STM32F4 provides us with the steps to transmit a character:

1. Enable the USART by writing the UE bit in USART\_CR1 register to 1.
    
2. Program the M bit in USART\_CR1 to define the word length.
    
3. Program the number of stop bits in USART\_CR2.
    
4. Select the desired baud rate using the USART\_BRR register.
    
5. Set the TE bit in USART\_CR1 to enable the transmitter (sends an idle frame as the first transmission).
    
6. Write the data to send in the USART\_DR register (this clears the TXE bit). Repeat this for each data to be transmitted.
    
7. After writing the last data into the USART\_DR register, wait until TC=1. This indicates that the transmission of the last frame is complete.
    

> **📝 Note**: I omit steps that are unneeded for the application in this post. This includes setting up a DMA and interrupts.

However, ahead of the above, we need to make sure that we configure the clocks and the pins correctly.

## **👨‍💻 Code Implementation**

### 📥 Crate Imports

In this implementation, three crates are required as follows:

* The `cortex_m_rt` crate for startup code and minimal runtime for Cortex-M microcontrollers.
    
* The `cortex_m` crate to import the Cortex-m peripheral API.
    
* The `panic_halt` crate to define the panicking behavior to halt on panic.
    
* The `stm32f4xx_pac` crate to import the STM32F401 microcontroller device PAC API that was created in the [**first post**](https://apollolabsblog.hashnode.dev/stm32f4-embedded-rust-at-the-pac-svd2rust) in this series.
    

```rust
use cortex_m_rt::entry;
use cortex_m;
use panic_halt as _;
use stm32f401_pac as pac;
```

### **🎛 Peripheral Configuration Code**

**1- Obtain a Handle for the Device and Core Peripherals**: As we always do and part of the singleton pattern in embedded Rust, this needs to be done before accessing any peripheral or core register. Here I create a device peripheral handler named `dp` and core peripheral handler named `cp` as follows:

```rust
let dp = pac::Peripherals::take().unwrap();
```

Using this handle, I will be accessing the peripherals of the device.

**2- Configure System Clocks:** I simplified the clock configuration from the [**last post**](https://apollolabsblog.hashnode.dev/stm32f4-embedded-rust-at-the-pac-system-clock-configuration) where I only use the internal HSI oscillator. The internal HSI oscillator has a value of 16 MHz.

```rust
// Enable HSI Oscillator
dp.RCC.cr.modify(|_, w| w.hsion().set_bit());

// Wait for HSI clock to become ready
while dp.RCC.cr.read().hsirdy().bit() {}
```

**3- Enable Clocks to GPIO and USART Peripherals:** Ahead of using any peripherals, their clocks need to be enabled. This applies to both GPIO and UART. In the earlier [GPIO post](https://apollolabsblog.hashnode.dev/stm32f4-embedded-rust-at-the-pac-gpio-control), we've seen that the GPIOA clock is enabled through the `gpioaen` field in the `RCC_AHB1ENR` register. On the other hand, USART2 is enabled through the `usart2en` field in the `RCC_APB1ENR` register.

```rust
// Enable Clock to GPIOA
dp.RCC.ahb1enr.write(|w| w.gpioaen().set_bit());

// Enable Clock to USART2
dp.RCC.apb1enr.write(|w| w.usart2en().set_bit());
```

**4- Configure the USART Tx Output Pin:** To operate PA2 as a UART transmit pin, it needs to be configured as an alternate output. Additionally, the alternate function needs to be configured to connect to USART2. The PA2 pin is configured as an alternate output in the `GPIOA_MODER` register. Further, to select the alternate function, we need to configure the `afrl2` field in the `GPIOA_AFRL` alternate function register. According to the datasheet, afrl2 needs to be configured with a value of 7 to connect it to USART2.

```rust
// Configure PA2 as Alternate Output
dp.GPIOA.moder.modify(|_, w| unsafe { w.moder2().bits(2) });
// Select Alternate Function for PA2
dp.GPIOA.afrl.modify(|_, w| unsafe { w.afrl2().bits(7) });
```

> **📝 Note**: In cases where `modify` is used instead of `write`, this is because `write` resets the fields that are not explicitly set. This means if you want to chnage particular bits in a regsiter without affecting others, you need to use `modify` rather than `write`.

**4- Enable USART2:** This is done by asserting the UE bit in the `USART2_CR1` register.

![CR1 regsiter](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fzjst5iqnzj9l5w9j2to.png)

```rust
// Enable USART2 by setting the UE bit in USART_CR1 register
dp.USART2.cr1.modify(|_, w| { w.ue().set_bit()});
```

**5- Define the Word Length** **in USART\_CR1:** The M-bit in the CR1 provides two options for word length either a 1 start bit, 8 data bits, n stop bits option (the default) or a 1 start bit, 9 data bits, n stop bits. Since I want 8 bits of data, nothing needs to be changed here.

**6- Program the Number of Stop Bits in USART\_CR2:** This is done through the STOP field in CR2 which is 2-bits wide. The default configuration `0b00` reflects 1 stop-bit operation. Again, nothing needs to be changed here.

**7- Select the Desired Baud Rate:** this is done using the `USART_BRR` register and is a bit more involved. The BRR register actually has two fields that represent the USART divide factor `USARTDIV`; one is the `DIV_Mantissa` which is 12-bits wide and the other is the `DIV_Fraction` which is 4-bits wide.


![brr register](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e1plxlx65miwwqkyoebi.png)

The reference manual actually provides a formula to calculate these values for a desired baud rate:

{% katex %} TxRx_{baud} = \frac{f_{clk}}{8 \times (2 - \text{OVER8}) \times \text{USARTDIV}} {% endkatex %}

where fclk is the USART clock frequency, `OVER8` indicates the setting for oversampling. Since `USARTDIV` has 4 fractional bits, the equation can be adjusted as follows:

{% katex %}TxRx_{baud} = \frac{f_{clk} \times 16}{8 \times (2 - \text{OVER8}) \times \text{USARTDIV}}{% endkatex %}

`OVER8` will be 1 in our case indicating oversampling by a factor of 16. In that case, the common factor can be eliminated and we end up with the following:

```rust
// Program the UART Baud Rate
dp.USART2.brr.write(|w| unsafe { w.bits(FREQ / BAUD) });
```

I defined `FREQ` and `BAUD` earlier in the code as constants representing the USART clock frequency (supplied by PCLK1 through HSI) and the desired baud rate, respectively.

```rust
const FREQ: u32 = 16_000_000;
const BAUD: u32 = 115_200;
```

**8- Enable the Transmitter:** This is done by setting the TE bit in USART2\_CR1 as follows:

```rust
// Enable the Transmitter
dp.USART2.cr1.modify(|_, w| w.te().set_bit());
```

### **📱 Application Code**

In the application code, we're going to set up a loop that will send the character 'A' repeatedly. For that, we need to write the data we want to send in the `USART_DR` register. After that, we need to wait for the `TC` field in the `USART_SR` status register to go high indicating transmission completion, then loop.

```rust
loop {
     // Put Data in Data Register
     dp.USART2.dr.write(|w| unsafe { w.dr().bits(b'A' as u16) });
     // Wait for data to get transmitted
     while dp.USART2.sr.read().tc().bit_is_clear() {}
}
```

## **📀 Full Application Code**

Here is the full code for the implementation described in this post. You can additionally find the full project and others available on the [**apollolabsdev Nucleo-F401RE**](https://github.com/apollolabsdev/stm32-nucleo-f401re) git repo.

```rust
#![no_std]
#![no_main]

// Imports
use cortex_m_rt::entry;
use panic_halt as _;
use stm32f401_pac as pac;

const FREQ: u32 = 16_000_000;
const BAUD: u32 = 115_200;

#[entry]
fn main() -> ! {
    // Setup handler for device peripherals
    let dp = pac::Peripherals::take().unwrap();

    // Enable HSI Oscillator
    dp.RCC.cr.modify(|_, w| w.hsion().set_bit());

    // Wait for HSI clock to become ready
    while dp.RCC.cr.read().hsirdy().bit() {}

    // Enable Clock to GPIOA
    dp.RCC.ahb1enr.write(|w| w.gpioaen().set_bit());

    // Enable Clock to USART2
    dp.RCC.apb1enr.write(|w| w.usart2en().set_bit());

    // Select Alternate Function for PA2
    dp.GPIOA.afrl.modify(|_, w| unsafe { w.afrl2().bits(7) });

    // Configure PA2 as Alternate Output
    dp.GPIOA.moder.modify(|_, w| unsafe { w.moder2().bits(2) });

    // Enable USART2 by setting the UE bit in USART_CR1 register
    dp.USART2.cr1.reset();
    dp.USART2.cr1.modify(|_, w| {
        w.ue().set_bit() // USART enabled
    });

    // Program the UART Baud Rate
    dp.USART2.brr.write(|w| unsafe { w.bits(FREQ / BAUD) });

    // Enable the Transmitter
    dp.USART2.cr1.modify(|_, w| w.te().set_bit());

    // Wait until TXE flag is set
    while dp.USART2.sr.read().txe().bit_is_clear() {}

    loop {
        // Put Data in Data Register
        dp.USART2.dr.write(|w| unsafe { w.dr().bits(b'A' as u16) });
        // Wait for data to get transmitted
        while dp.USART2.sr.read().tc().bit_is_clear() {}
    }
}
```

## 🔬 Further Experimentation/Ideas

* Configure the board to receive a character from the host PC and echo it back.
    

## Conclusion

In this post, Rust code was developed to transmit a letter using the STM32 device UART peripheral exclusively at the peripheral access crate (PAC) level. The application was developed for an STM32F401RE microcontroller deployed on the Nucleo-F401RE development board. Have any questions? Share your thoughts in the comments below 👇.

#### If you found this post useful, and if Embedded Rust interests you, stay in the know and skyrocket your learning curve by subscribing to The Embedded Rustacean newsletter:

{% cta  http://www.theembeddedrustacean.com %} Subscribe Now to The Embedded Rustacean{% endcta%}