Embedded Rust & Embassy: UART Serial Communication
Tags: rust, tutorial, beginners, iot
Published: 2022-11-28 17:04:43

> This blog post is the second of a multi-part series where I will explore various peripherals of the STM32 microcontroller using the embedded Rust embassy framework. In this post, I will be exploring the usage of UART.

#### 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%} 

## Introduction
Setting up UART serial communication is useful for any type of device-to-device (point-to-point) communication. On of the common examples is printing output to a PC. In this post, I will be configuring and setting up UART communication with a PC terminal using embassy for an STM32 device. I will build on the application from the [GPIO button-controlled blinking post](https://apollolabsblog.hashnode.dev/embedded-rust-embassy-gpio-button-controlled-blinking) to print to the output how many times a GPIO button has been pressed.  

> 📝 For those interested in seeing how embassy compares to other HAL frameworks, you can check out how I created the same application using the stm32f4xx-hal [here](https://apollolabsblog.hashnode.dev/stm32f4-embedded-rust-at-the-hal-uart-serial-communication).

### 📚 Knowledge Pre-requisites
To understand the content of this post, you need the following:
- Basic knowledge of coding in Rust.
- Knowledge of `async/await` and `Futures`.
- Familiarity with the basic template for creating embedded applications in Rust.
- Familiarity with interrupts in Cortex-M processors.
- Familiarity with [UART communication basics](https://www.analog.com/en/analog-dialogue/articles/uart-a-hardware-communication-protocol.html).

### 💾 Software Setup
All the code presented in this post in addition to instructions for the environment and toolchain setup are 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)
- [Serial Studio](https://serial-studio.github.io/)

**For Mac and Linux**:
- [minicom](https://wiki.emacinc.com/wiki/Getting_Started_With_Minicom)
- [Serial Studio](https://serial-studio.github.io/)

Apart from Serial Studio, some detailed instructions for the different operating systems are available in the [Discovery Book](https://docs.rust-embedded.org/discovery/microbit/06-serial-communication/index.html).

For me, Serial Studio comes highly recommended. I personally came across [Serial Studio](https://serial-studio.github.io/) recently and found it to be awesome for two main reasons. First is that you can skip many of those instructions for other tools, especially in Mac and Linux systems. Second, if you are you want to graph data over UART, it has a really nice and easy-to-configure setup. It's also open-source and free to use.

### 🛠 Hardware Setup
#### Materials
- [Nucleo-F401RE board](https://s.click.aliexpress.com/e/_DdEhurV)

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

#### 🔌 Connections
There will be no need for external connections. On-board connections will be utilized and include the following:
- LED is connected to pin PA5 on the microcontroller. The pin will be used as an output.
- User button connected to pin PC13 on the microcontroller. The pin will be used as input.
- 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

For the purposes of this post, there isn't much to change from an algorithmic perspective compared to the [last post](https://apollolabsblog.hashnode.dev/embedded-rust-embassy-gpio-button-controlled-blinking). However, the tasks done would differ in one of the state. Meaning, I will still cycle through several LED blinking frequencies based on a button press but do something different in the button press state. As such, the application algorithm representation looks the same:

![State Machine](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w4k5k8w1s69hzw70yl88.png)

Although this can be reflected on the chart, the button press behavior can be contained in the button press state. Consequently, the button press state behaviour looks something like this:

![Flow Chart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tmtjhqce3p7s4q6xddvj.png)

The difference is that now there is a count maintained every time we enter the button press state. Also before exiting the button press state, the count is transmitted over UART.

Let's now jump into implementing this algorithm.

## 👨‍💻 Code Implementation

📝 **Setting up an Embassy Project**: 
> Although I followed the embassy documentation to set up my first project, it wasn't all smooth sailing. Issues mainly had to do with configurations/settings of configuration (toml) files. It wasn't all too clear what had to be done without needing to ask. I figure this has to do with embassy still being considered unstable. If using the STM32F401RE-Nucleo, the easiest path would be to clone the project from the apollolabsdev [git repo](https://github.com/apollolabsdev/stm32-nucleo-f401re). 

### 📥 Crate Imports 
In this implementation the crates required are as follows:

- The `core::fmt::Write` crate will allow us to use the `writeln!` macro for easy printing.
- The `core::sync::atomic` crate to import the `Atomic` sharing type.
- The `embassy_executor` crate to import the embassy embedded async/await executor.
- The `embassy_time` crate to import timekeeping capabilities.
- The `embassy_stm32` crate to import the embassy STM32 series microcontroller device hardware abstractions. The needed abstractions are imported accordingly.
- The `panic_halt` crate to define the panicking behavior to halt on panic.

```rust
use core::fmt::Write;
use core::sync::atomic::{AtomicU32, Ordering};
use embassy_executor::Spawner;
use embassy_stm32::dma::NoDma;
use embassy_stm32::exti::ExtiInput;
use embassy_stm32::gpio::{AnyPin, Input, Level, Output, Pin, Pull, Speed};
use embassy_stm32::usart::{Config, UartTx};
use embassy_time::{Duration, Timer};
use panic_halt as _;
```
#### 🌍 Global Variables 

This part remains the same as [before](https://apollolabsblog.hashnode.dev/embedded-rust-embassy-gpio-button-controlled-blinking). The `AtomicU32` type is used to safely share the delay value `BLINK_MS` among several tasks:

```rust
static BLINK_MS: AtomicU32 = AtomicU32::new(0);
```

### 💡 The LED Blinking Task
This task is the one that manages the LED blinking (switches between the LED blinking states). It is a task that is spawned by the main task in the device config/setup phase, ahead of the main task entering its continuous loop. As indicated in the software design section, the only changes in the code affect the button press state. The steps taken previously in this task included:

1. Create a blinking task and handle for the LED GPIO pin.
2. Define the task loop.

As such, nothing needs to be changed here and the code remains the same as before: 

```rust
#[embassy_executor::task]
async fn led_task(led: AnyPin) {
    // Configure the LED pin as a low-speed output and obtain a handler
    // Initialize the LED output to Low
    let mut led = Output::new(led, Level::Low, Speed::Low);

    loop {
        let del = BLINK_MS.load(Ordering::Relaxed);
        Timer::after(Duration::from_millis(del.into())).await;
        led.toggle();
    }
}
```

### 📱 The Main Task (Button Press Task)
#### 🎛 Peripheral Configuration & Task Spawning

The start of the main task is marked by the following code:

```rust
#[embassy_executor::main]
async fn main(spawner: Spawner) 
```
As indicated before, the main task will be where we manage the button press and its count. The following steps will mark the tasks performed in the main task:

1️⃣ **Initialize MCU and obtain a handle for the device peripherals**: A device peripheral handler `p` is created:

```rust
 let p = embassy_stm32::init(Default::default());
```
Here we're just passing the default value for the `Config` type.

2️⃣ **Obtain a handle and configure the input button**: The on-board user push button on the Nucleo-F401RE is connected to pin PC13 (Pin 13 Port C) as stated earlier. A `button` handle for the input button is created with the following line of code:

```rust
let button = Input::new(p.PC13, Pull::None);
``` 

3️⃣ **Attach the interrupt to the input button**: 
At this point, the `button` instance is just an input pin. In order to make the device respond to push button interrupts, `button` needs to be attached to one of the device's external interrupt inputs. Using the `EXTI` [external interrupt input driver](https://docs.embassy.dev/embassy-stm32/git/stm32f030c6/exti/struct.ExtiInput.html) and knowing from the datasheet that PC13 attaches to EXTI13, the interrupt is attached as follows:

```rust
let mut button = ExtiInput::new(button, p.EXTI13);
```

4️⃣ **Publish Delay Value and Spawn LED Blinking Task**: before entering the button press loop, we're going to need to kick off our LED blinking task. Though ahead of that, we need to also provide an initial value for the delay and update it to the global context: 

```rust
let mut del_var = 2000;
BLINK_MS.store(del_var, Ordering::Relaxed);
```
Then `led_task` is kicked off using the `spawn` method:

```rust
spawner.spawn(led_task(p.PA5.degrade())).unwrap();
```

5️⃣ **Configure UART and obtain handle**: Looking into the Nucleo-F401RE board pinout, the Tx line pin PA2 connects to the USART2 peripheral in the microcontroller device. As such, this means we need to configure USART2 and somehow pass it to the handle of the pin we want to use. Under `embassy_stm32::usart::UartTx` there exists a `new` method to configure UART with the following signature:

```rust
pub fn new(
    _inner: impl Peripheral<P = T> + 'd,
    tx: impl Peripheral<P = impl TxPin<T>> + 'd,
    tx_dma: impl Peripheral<P = TxDma> + 'd,
    config: Config
) -> Self
```
Where `inner` expects an argument passing in a UART peripheral instance, `tx` a `Pin` instance for the UART Tx pin, `tx_dma` an instance for a DMA channel, and `config` a `Config` configuration struct to configure UART.

This results in the following line of code:

```rust
let mut usart = UartTx::new(p.USART2, p.PA2, NoDma, Config::default());
```

Here `USART2` is passed since it corresponds in the STM32F401 to pin `PA2` which is also passed as a second parameter. `PA2` is also the pin that connects to the onboard USB bridge.

Finally, I create a `value` variable to track the number of counts, and a `String` type `msg` handle to store the formatted text that will be transmitted over UART.

```rust
let mut value: u8 = 0;
let mut msg: String<8> = String::new();
```

Next, we can move on to the application Loop.

📝 **UART with Interrupts**: 
> The UART can actually be configured easily with interrupts. This would enable us to later use `await` with the UART handle. However, in embassy, it seems to require that UART is attached to a DMA channel an interrupt is going to be attached. Setting up DMA in embassy turns out to be quite smooth actually. THough I leave the DMA topic to a different post.  


#### 🔁 Main Task Loop

Following the design described earlier in the state machine, the button press will be the only event that is interrupt-based. However, using embassy, events from interrupts or otherwise are managed through `Futures`. This means that execution can be yielded to allow other code to progress until the event attached to a `Future` occurs. There is an executor that manages all of this in the background and more detail about it is provided [in the documentation](https://embassy.dev/dev/runtime.html). 

Given that, in the main task loop all we need to do is wait for a button press to occur. This is done through the following line: 

```rust
button.wait_for_rising_edge().await;
```

`wait_for_rising_edge` is an [`ExtiInput` method](https://docs.embassy.dev/embassy-stm32/git/stm32f030c6/exti/struct.ExtiInput.html) that returns a `Future` as well. This is a `Future` that is attached to the interrupt event of a button press. This can be read as the task yielding execution to the executor until a rising edge occurs on the button. 

This means that once a button press occurs, the code following the above line will execute. The following code adjusts the delay value and updates the global context:

```rust
del_var = del_var - 300_u32;
   if del_var < 500_u32 {
       del_var = 2000_u32;
   }
BLINK_MS.store(del_var, Ordering::Relaxed);
```

Next, the counter message is prepared using the `writeln!` macro, and the message is transmitted using the `blocking_write` method from `embassy_stm32::usart::UartTx`:

```rust
        core::writeln!(&mut msg, "{:02}", value).unwrap();
        usart.blocking_write(msg.as_bytes()).unwrap();
```

> 📝 Notice that here that this is a blocking write for UART. This means that this line will block operations, not allowing any others to execute until it completes. For non-blocking UART, there is an `async` method, however it requires that interrupts are configured for UART.
 
Finally, the push button counter value is recorded by incrementing `value` and `msg` is cleared for the next transmit.

```rust
        value = value.wrapping_add(1);
        msg.clear();
```

This concludes the code for the full application.

## 📱 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 git repo](https://github.com/apollolabsdev/stm32-nucleo-f401re).


```rust
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]

use core::fmt::Write;
use core::sync::atomic::{AtomicU32, Ordering};

use heapless::String;

use embassy_executor::Spawner;
use embassy_stm32::dma::NoDma;
use embassy_stm32::exti::ExtiInput;
use embassy_stm32::gpio::{AnyPin, Input, Level, Output, Pin, Pull, Speed};
use embassy_stm32::usart::{Config, UartTx};
use embassy_time::{Duration, Timer};
use panic_halt as _;

static BLINK_MS: AtomicU32 = AtomicU32::new(0);

#[embassy_executor::task]
async fn led_task(led: AnyPin) {
    // Configure the LED pin as a low -speed output and obtain a handler
    // Initialize LED output to Low
    // On the Nucleo FR401 theres an on-board LED connected to pin PA5
    let mut led = Output::new(led, Level::Low, Speed::Low);

    loop {
        let del = BLINK_MS.load(Ordering::Relaxed);
        Timer::after(Duration::from_millis(del.into())).await;
        led.toggle();
    }
}

#[embassy_executor::main]
async fn main(spawner: Spawner) {
    // Initialize and create handle for devicer peripherals
    let p = embassy_stm32::init(Default::default());

    // Configure the button pin (if needed) and obtain handler.
    // On the Nucleo FR401 there is a button connected to pin PC13.
    let button = Input::new(p.PC13, Pull::None);
    let mut button = ExtiInput::new(button, p.EXTI13);

    //Configure UART
    let mut usart = UartTx::new(p.USART2, p.PA2, NoDma, Config::default());

    // Create and initialize a delay variable to manage delay loop
    let mut del_var = 2000;

    // Publish blink duration value to global context
    BLINK_MS.store(del_var, Ordering::Relaxed);

    // Spawn LED blinking task
    spawner.spawn(led_task(p.PA5.degrade())).unwrap();

    // Variable to keep track of how many button presses occured
    let mut value: u8 = 0;

    // Create empty String for message
    let mut msg: String<8> = String::new();

    loop {
        // Check if button got pressed
        button.wait_for_rising_edge().await;

        // If button pressed decrease the delay value
        del_var = del_var - 300;
        // If updated delay value drops below 300 then reset it back to starting value
        if del_var < 500 {
            del_var = 2000;
        }
        // Publish updated delay value to global context
        BLINK_MS.store(del_var, Ordering::Relaxed);

        // Format Message
        core::writeln!(&mut msg, "{:02}", value).unwrap();

        // Transmit Message
        usart.blocking_write(msg.as_bytes()).unwrap();

        // Update Value Parameter
        value = value.wrapping_add(1);

        // Clear String for next message
        msg.clear();
    }
}

``` 

## Conclusion
In this post, a UART-based application was created using Rust on the Nucleo-F401RE development board. All code was created leveraging the embassy framework for STM32. It is also shown in this post how global variables can be created and shared among tasks. It still remains that leveraging embassy provides for a smoother sail compared to other existing approaches. Have any questions/comments? 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%}