ESP32 Embedded Rust at the HAL: Remote Control Peripheral
Tags: rust, tutorial, embedded, esp32
Published: 2023-06-22 14:08:43

> ***This blog post is the tenth of a multi-part series of posts where I explore various peripherals in the ESP32C3 using embedded Rust at the HAL level. Please be aware that certain concepts in newer posts could depend on concepts in prior posts.***

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

The Remote Control Transceiver (RMT) peripheral in the ESP32C3 microcontroller provides a simple and efficient way to transmit and receive remote control signals. It is commonly used in applications such as infrared (IR) remote control systems. The RMT can produce programmable pulse patterns, has built-in carrier modulation, and supports multiple channels for both transmit and receive.

While the RMT peripheral in the ESP32C3 microcontroller is primarily designed for remote control applications, it can also be used for other purposes that involve generating and receiving pulse train signals. This includes but is not limited to applications like; sensor interfacing, LED control, signal generation, and timing and synchronization.

In this post, I'm going to build an application that uses the RMT to generate two different waveforms on two different pins. I will also be leveraging the Wokwi logic analyzer to verify/monitor the output of the pins.

### 📚 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.
    

### 💾 Software Setup

All the code presented in this post is available on the [**apollolabs ESP32C3**](https://github.com/apollolabsdev/ESP32C3) 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.

Additionally, the full project (code and simulation) is available on Wokwi [**here**](https://wokwi.com/projects/367671402643650561).

For this post, you need to download [**PulseView**](https://sigrok.org/wiki/Downloads) from the Sigrok website. PulseView is an open-source logic analyzer software that is going to be used to view the output signals. If you are a first-time user of PulseView and the logic analyzer function on Wokwi, I recommend you read my earlier blog post with a step-by-step setup [here](https://apollolabsblog.hashnode.dev/esp32-embedded-rust-at-the-hal-spi-communication).

### 🛠 Hardware Setup

#### **Materials**

* [**ESP32-C3-DevKitM**](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/hw-reference/esp32c3/user-guide-devkitm-1.html)
    
![ESP32C3 Devkit](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cpdvlnnr68y03933nz12.jpg)
    

#### [**The Wokwi Logic Analyzer**](https://docs.wokwi.com/parts/wokwi-logic-analyzer)

![Logic Analyzer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qaui5bdl2silye5jvw7k.png)

#### 🔌 Connections

Connections include the following:

* Gpio6 to D0 on the Wokwi logic analyzer
    
* Gpio5 to D1 on the Wokwi logic analyzer
    

## 👨‍🎨 Software Design

Before delving into what the software will do, understanding the working of the RMT would be useful to understand how it'll be configured. As such, the figure below extracted from the [ESP reference manual](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/rmt.html) shows a block diagram of of a single RMT transmitter channel in the ESP32-C3:

![RMT Tx](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/49y82rjcelswbd52j5i8.png)

In the upper path, known as the data path, an input is provided by the user (in the program) where the RMT transmitter, in turn, will generate the waveforms on the pin. The lower path, known as the control path, generates the carrier signal required for modulation purposes. For the purpose of this post, we are not doing any modulation so the lower path (control path) will be disabled.

The RMT hardware employs a pattern known as the RMT symbol to define data shown below. Each symbol comprises two pairs of values. The first value within a pair, spanning 15 bits, represents the duration of the signal in RMT ticks. The second value, encapsulated within a single bit, denotes the signal level, distinguishing between high and low. This arrangement ensures an efficient and concise representation of the signal characteristics, facilitating precise communication and interpretation within the RMT system.

![RMT Symbol](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ayvy0er18c1ieqz0qfb3.png)

**📝 *Note***

> The image above for the RMT symbol comes from the ESP reference, however, you need to be careful as it might be confusing. The numbers on top, do not reflect bit positions like normally expected in many other manuals.

In the code introduced in this post, the application will configure two RMT channels and send a different pulse code to each. We will use the logic analyzer to verify that the correct pulses were generated on each pin.

## 👨‍💻 Code Implementation

### 📥 Crate Imports

In this implementation the crates required are as follows:

* The `esp32c3_hal` crate to import the ESP32C3 device hardware abstractions.
    
* The `esp_backtrace` crate to define the panicking behavior.
    
* The `esp_println` crate to provide `println!` implementation.
    

```rust
use esp32c3_hal::{
    clock::ClockControl,
    esp_riscv_rt::entry,
    peripherals::Peripherals,
    prelude::*,
    pulse_control::{ClockSource, ConfiguredChannel, OutputChannel, PulseCode, RepeatMode},
    timer::TimerGroup,
    PulseControl, Rtc, IO,
};
use esp_backtrace as _;
use esp_println::println;
```

### 🎛 Peripheral Configuration Code

1️⃣ **Obtain a handle for the device peripherals**: In embedded Rust, as part of the singleton design pattern, we first have to take the PAC-level device peripherals. This is done using the `take()` method. Here I create a device peripheral handler named `dp` as follows:

```rust
let peripherals = Peripherals::take();
```

**2️⃣ Disable the Watchdogs:** The ESP32C3 has watchdogs enabled by default and they need to be disabled. If they are not disabled then the device would keep on resetting. I'm not going to go into much detail, however, watchdogs require the application software to periodically "kick" them to avoid resets. This is out of the scope of this example, though to avoid this issue, the following code needs to be included:

```rust
let mut system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

let mut rtc = Rtc::new(peripherals.RTC_CNTL);
let timer_group0 = TimerGroup::new(
        peripherals.TIMG0,
        &clocks,
        &mut system.peripheral_clock_control,
);
let mut wdt0 = timer_group0.wdt;
let timer_group1 = TimerGroup::new(
        peripherals.TIMG1,
        &clocks,
        &mut system.peripheral_clock_control,
);
let mut wdt1 = timer_group1.wdt;

rtc.swd.disable();
rtc.rwdt.disable();
wdt0.disable();
wdt1.disable();
```

3️⃣ **Configure and Obtain Handle for the RMT**: To create an instance of the RMT, we need to start with creating an instance of the `PulseControl` peripheral (that's the way the RMT is named in the hal) in the esp32c3-hal. In `PulseControl` there is a `new()` instance method that has the following signature:

```rust
pub fn new(
    instance: impl Peripheral<P = RMT> + 'd,
    peripheral_clock_control: &mut PeripheralClockControl,
    clk_source: ClockSource,
    div_abs: u8,
    div_frac_a: u8,
    div_frac_b: u8
) -> Result<PulseControl<'d>, SetupError>
```

The first three parameters are more or less expected where the RMT would require an instance of the peripheral to be used, a `peripheral_clock_control` struct, and a `clk_source` struct. However, for the last three parameters, although the HAL documentation does not provide much detail, it can be found in the reference manual ([page 828](https://www.espressif.com/sites/default/files/documentation/esp32-c3_technical_reference_manual_en.pdf)). Here we aren't going to need any clock division, so all the last three values will remain zero. As such, I create an instance for the `pulse` handle as follows:

```rust
    // Configure RMT peripheral
    let pulse = PulseControl::new(
        peripherals.RMT,
        &mut system.peripheral_clock_control,
        ClockSource::APB,
        0,
        0,
        0,
    )
    .unwrap();
```

Be aware that these settings, mainly to do with clocks, will apply to all the RMT channels within the peripheral. Nevertheless, we will be able to configure individual channel settings in the next step after we divide the channels.

4️⃣ **Obtain handle and set up the channels**: In the ESP32C3 there are two RMT transmitter channels that are encoded as members of the `PluseControl` struct; `channel0` and `channel1` . We can create a handle for each separately as follows:

```rust
    // Get reference to channel
    let mut rmt_channel0 = pulse.channel0;
    let mut rmt_channel1 = pulse.channel1;
```

At this point, however, the individual channels are not fully configured yet. There are several methods that would enable us to do so. You can find the applicable methods in the `Channel0` and `Channel1` struct [documentation](https://docs.rs/esp32c3-hal/latest/esp32c3_hal/pulse_control/struct.Channel0.html) in the esp32c3-hal. Here we use the `set_idle_output_level` to configure the default output level when the channel is idle, `set_carrier_modulation` to deactivate the carrier modulation, `set_channel_divider` to set the channel clock divider value, and `set_idle_output` to enable the output while the channel is idle:

```rust
    // Set up channel
    rmt_channel0
        .set_idle_output_level(false)
        .set_carrier_modulation(false)
        .set_channel_divider(1)
        .set_idle_output(true);

    rmt_channel1
        .set_idle_output_level(false)
        .set_carrier_modulation(false)
        .set_channel_divider(1)
        .set_idle_output(true);
```

5️⃣ **Assign Pins and Obtain Handles for the Configured Channels**:

All of the methods in the previous step return a `Channel0` or `Channel1` type. Neither of which is possible to send a sequence to. Instead, we need a `ConfiguredChannel0` or `ConfiguredChannel1` type. Only the `assign_pin` method returns a configured channel. As such, we assign the pins and create handles for the configured channels as follows:

```rust
    let mut rmt_channel0 = rmt_channel0.assign_pin(io.pins.gpio6);
    let mut rmt_channel1 = rmt_channel1.assign_pin(io.pins.gpio5);
```

quite frankly I found this pattern a bit different from what I expected compared to other peripherals.

Now that the channels are configured, all we have to do is create the application.

### 📱 Application Code

In the application, first thing that needs to be done is create the patterns that we want to generate on the pins. In order to do that, we'd have to use the `PulseCode` type. `PulseCode` is an object that represents the state of one RMT symbol explained earlier. Here I will create two arrays each with three pulse codes representing a different pattern. The two different patterns will be sent to two different channels:

```rust
let mut seq = [PulseCode {
    level1: true,
    length1: 10u32.nanos(),
    level2: false,
    length2: 90u32.nanos(),
}; 3];

let mut seq1 = [PulseCode {
    level1: true,
    length1: 50u32.nanos(),
    level2: false,
    length2: 50u32.nanos(),
}; 3];
```

In the `seq` array three pulse codes will be sent. For each `PulseCode`, the first level of the symbol is set to high (true) for 10ns and the second part of the symbol to low (false) for 90ns. The `seq1` array does the same though with different timings of 50ns for both on and off times.

### 🏃 Result

Now we can monitor the outputs with the Wokwi logic analyzer. Looking at the zoomed-out view, with the signals of both channels, you can see that the first channel is generating the three pulse codes followed by the second channel. Exactly like desired in the code.

![LA Output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g2i2evukshznwh7dw8lm.png)

Next, we can zoom in to see the timing. I zoomed in to the second channel and viewed the following:

![LA Zoom in](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1b0pr9g8jnfhzlhjgj1w.png)

Notice the on and off times are equal like we intended them to be. However, when measuring the timing it is 620ns rather than the 50ns that we expect. This is an issue that I am still investigating and will update this post as soon as it's clear what is occurring.

## 📱 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 [**apollolabs ESP32C3**](https://github.com/apollolabsdev/ESP32C3) git repo. Also the Wokwi project can be accessed [**here**](https://wokwi.com/projects/366942397702382593).

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

use esp32c3_hal::{
    clock::ClockControl,
    esp_riscv_rt::entry,
    peripherals::Peripherals,
    prelude::*,
    pulse_control::{ClockSource, ConfiguredChannel, OutputChannel, PulseCode, RepeatMode},
    timer::TimerGroup,
    PulseControl, Rtc, IO,
};
use esp_backtrace as _;
use esp_println::println;

#[entry]
fn main() -> ! {
    // Take Peripherals, Initialize Clocks, and Create a Handle for Each
    let peripherals = Peripherals::take();
    let mut system = peripherals.SYSTEM.split();
    let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

    // Instantiate and Create Handles for the RTC and TIMG watchdog timers
    let mut rtc = Rtc::new(peripherals.RTC_CNTL);
    let timer_group0 = TimerGroup::new(
        peripherals.TIMG0,
        &clocks,
        &mut system.peripheral_clock_control,
    );
    let mut wdt0 = timer_group0.wdt;
    let timer_group1 = TimerGroup::new(
        peripherals.TIMG1,
        &clocks,
        &mut system.peripheral_clock_control,
    );
    let mut wdt1 = timer_group1.wdt;

    // Disable the RTC and TIMG watchdog timers
    rtc.swd.disable();
    rtc.rwdt.disable();
    wdt0.disable();
    wdt1.disable();

    // Instantiate and Create Handle for IO
    let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);

    // Configure RMT peripheral
    let pulse = PulseControl::new(
        peripherals.RMT,
        &mut system.peripheral_clock_control,
        ClockSource::APB,
        0,
        0,
        0,
    )
    .unwrap();

    // Get reference to channel
    let mut rmt_channel0 = pulse.channel0;
    let mut rmt_channel1 = pulse.channel1;

    // Set up channel
    rmt_channel0
        .set_idle_output_level(false)
        .set_carrier_modulation(false)
        .set_channel_divider(1)
        .set_idle_output(true);

    rmt_channel1
        .set_idle_output_level(false)
        .set_carrier_modulation(false)
        .set_channel_divider(1)
        .set_idle_output(true);

    // Assign GPIO pin where pulses should be sent to
    let mut rmt_channel0 = rmt_channel0.assign_pin(io.pins.gpio6);
    let mut rmt_channel1 = rmt_channel1.assign_pin(io.pins.gpio5);

    // Create pulse sequence
    let mut seq = [PulseCode {
        level1: true,
        length1: 10u32.nanos(),
        level2: false,
        length2: 90u32.nanos(),
    }; 3];

        let mut seq1 = [PulseCode {
        level1: true,
        length1: 50u32.nanos(),
        level2: false,
        length2: 50u32.nanos(),
    }; 3];

            rmt_channel0
            .send_pulse_sequence(RepeatMode::SingleShot, &seq)
            .unwrap();

            rmt_channel1
            .send_pulse_sequence(RepeatMode::SingleShot, &seq1)
            .unwrap();

    // Application Loop
    loop {
    }
}
```

## Conclusion

In this post, an application was created leveraging the remote control peripheral (RMT) for the ESP32C3 to generate different pulse waveforms. The RMT code was created at the HAL level using the Rust esp32c3-hal. 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%}