Remote Control Transceiver (RMT)

The Rmt class provides a wrapper around the ESP32 RMT peripheral. It allows you to send infrared signals with the ESP32. See the esp-idf documentation for more information about the RMT peripheral. (https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/rmt.html)

The RmtEncoder class provides a wrapper around the ESP32 rmt encoder functionality. It allows you to encode infrared signals with the ESP32. See the esp-idf documentation for more information about the RMT encoder (https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/rmt.html#rmt-encoder)

The main functionality of the Rmt / RmtEncoder classes beyond what is provided by the esp-idf is to allow the use of the RMT peripheral with c++ functions (such as with bound functions, functionals, etc.). It also provides a simpler wrapper / interface to the user.

API Reference

Header File

Classes

class Rmt : public espp::BaseComponent

Class wrapping the RMT peripheral on the ESP32.

The RMT (Remote Control Transceiver) peripheral is used to generate precise timing pulses on a GPIO pin. It can be used to drive a WS2812B or similar LED strip which uses a 1-wire protocol such as the WS2812B. The RMT peripheral is also used by the ESP32 to drive the IR transmitter.

Example 1: Transmitting data

    // create the rmt object
    espp::Rmt rmt(espp::Rmt::Config{
        .gpio_num = 18, // WS2812B data pin on the TinyS3
        .resolution_hz = WS2812_FREQ_HZ,
        .log_level = espp::Logger::Verbosity::INFO,
    });

    // tell the RMT object to use the led_encoder (espp::RmtEncoder) that's
    // defined above
    rmt.set_encoder(std::move(led_encoder));

    // create a task to cycle through rainbow colors and send them to the
    // WS2812B LED using the RMT peripheral
    auto task_fn = [&rmt](std::mutex &m, std::condition_variable &cv) {
      static auto start = std::chrono::high_resolution_clock::now();
      auto now = std::chrono::high_resolution_clock::now();
      float t = std::chrono::duration<float>(now - start).count();
      // rotate through rainbow colors in hsv based on time, hue is 0-360
      float hue = (cos(t) * 0.5f + 0.5f) * 360.0f;
      espp::Hsv hsv(hue, 1.0f, 1.0f);
      espp::Rgb rgb = hsv.rgb();
      uint8_t green = std::clamp(int(rgb.g * 255), 0, 255);
      uint8_t blue = std::clamp(int(rgb.b * 255), 0, 255);
      uint8_t red = std::clamp(int(rgb.r * 255), 0, 255);
      // NOTE: we only have one LED so we only need to send one set of RGB data
      uint8_t data[3] = {green, blue, red};
      // now we can send the data to the WS2812B LED
      rmt.transmit(data, sizeof(data));
      fmt::print("hsv->rgb->uint: {} -> {} -> {} {} {}\n", hsv, rgb, green, blue, red);
      // NOTE: sleeping in this way allows the sleep to exit early when the
      // task is being stopped / destroyed
      {
        std::unique_lock<std::mutex> lk(m);
        cv.wait_for(lk, 50ms);
      }
      // don't want to stop the task
      return false;
    };

    auto task = espp::Task({.callback = task_fn,
                            .task_config =
                                {
                                    .name = "Rmt Task",
                                    .stack_size_bytes = 5 * 1024,
                                },
                            .log_level = espp::Logger::Verbosity::WARN});
    task.start();

Public Functions

inline explicit Rmt(const Config &config)

Constructor.

Parameters

config – Configuration for this class

inline ~Rmt()

Destructor.

This function disables the RMT peripheral and frees the RMT channel.

inline bool transmit(const uint8_t *data, size_t length)

Transmit a buffer of data using the RMT peripheral.

Note

This function blocks until the data has been transmitted.

Parameters
  • data – Pointer to the data to transmit

  • length – Length of the data to transmit

Returns

True if the data was successfully transmitted, false otherwise

inline const std::string &get_name() const

Get the name of the component

Note

This is the tag of the logger

Returns

A const reference to the name of the component

inline void set_log_tag(const std::string_view &tag)

Set the tag for the logger

Parameters

tag – The tag to use for the logger

inline espp::Logger::Verbosity get_log_level() const

Get the log level for the logger

Returns

The verbosity level of the logger

inline void set_log_level(espp::Logger::Verbosity level)

Set the log level for the logger

Parameters

level – The verbosity level to use for the logger

inline void set_log_verbosity(espp::Logger::Verbosity level)

Set the log verbosity for the logger

See also

set_log_level

Note

This is a convenience method that calls set_log_level

Parameters

level – The verbosity level to use for the logger

inline espp::Logger::Verbosity get_log_verbosity() const

Get the log verbosity for the logger

See also

get_log_level

Note

This is a convenience method that calls get_log_level

Returns

The verbosity level of the logger

inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)

Set the rate limit for the logger

Note

Only calls to the logger that have _rate_limit suffix will be rate limited

Parameters

rate_limit – The rate limit to use for the logger

struct Config

Configuration for the RMT class.

Public Members

int gpio_num

GPIO pin to use for the RMT peripheral.

rmt_clock_source_t clock_src = RMT_CLK_SRC_DEFAULT

Clock source for the RMT peripheral.

bool dma_enabled = false

Whether to use DMA for the RMT peripheral.

int block_size = 64

Memory block size (e.g. 64 * 4 = 256 bytes) for the RMT peripheral. Note: this has different meaning depending on whether DMA is configured or not. Suggested size without DMA is >= 64, with DMA is >= 1024.

size_t resolution_hz = 10000000

Resolution of the RMT peripheral.

int transaction_queue_depth = 1

Depth of the RMT transaction queue (number of transactions that can be queued)

Logger::Verbosity log_level = Logger::Verbosity::WARN

Log level for this class.

Header File

Classes

class RmtEncoder

Class representing an RMT encoder.

This class is used to encode data for the RMT peripheral. It is used by the Rmt class to encode data for transmission.

Example 1: WS2812 encoder

    //
    // The RmtEncoder provides a way to encode data into the RMT peripheral.
    // This code is a custom encoder that encodes WS2812B data. It uses two
    // encoders, a bytes encoder and a copy encoder. The bytes encoder encodes
    // the RGB data into the RMT peripheral and the copy encoder encodes the
    // reset code. The reset code is a special code that is sent after the RGB
    // data to reset the WS2812B LEDs. The reset code is a 50us low pulse
    // followed by a 50us high pulse. The reset code is sent after the RGB data
    // to ensure that the WS2812B LEDs latch the RGB data. The reset code is
    // sent after the RGB data because the WS2812B LEDs latch the RGB data on
    // the rising edge of the reset code.
    //
    // This code is copied from the led_stip example in the esp-idf
    // (https://github.com/espressif/esp-idf/tree/master/examples/peripherals/rmt/led_strip/main)
    int led_encoder_state = 0;
    static constexpr int WS2812_FREQ_HZ = 10000000;
    static constexpr int MICROS_PER_SEC = 1000000;
    auto led_encoder = std::make_unique<espp::RmtEncoder>(espp::RmtEncoder::Config{
        // NOTE: since we're using the 10MHz RMT clock, we can use the pre-defined
        //       ws2812_10mhz_bytes_encoder_config
        .bytes_encoder_config = espp::RmtEncoder::ws2812_10mhz_bytes_encoder_config,
        .encode = [&led_encoder_state](auto channel, auto *copy_encoder, auto *bytes_encoder,
                                       const void *data, size_t data_size,
                                       rmt_encode_state_t *ret_state) -> size_t {
          // divide by 2 since we have both duration0 and duration1 in the reset code
          static uint16_t reset_ticks =
              WS2812_FREQ_HZ / MICROS_PER_SEC * 50 / 2; // reset code duration defaults to 50us
          static rmt_symbol_word_t led_reset_code = (rmt_symbol_word_t){
              .duration0 = reset_ticks,
              .level0 = 0,
              .duration1 = reset_ticks,
              .level1 = 0,
          };
          rmt_encode_state_t session_state = RMT_ENCODING_RESET;
          int state = RMT_ENCODING_RESET;
          size_t encoded_symbols = 0;
          switch (led_encoder_state) {
          case 0: // send RGB data
            encoded_symbols +=
                bytes_encoder->encode(bytes_encoder, channel, data, data_size, &session_state);
            if (session_state & RMT_ENCODING_COMPLETE) {
              led_encoder_state = 1; // switch to next state when current encoding session finished
            }
            if (session_state & RMT_ENCODING_MEM_FULL) {
              state |= RMT_ENCODING_MEM_FULL;
              goto out; // yield if there's no free space for encoding artifacts
            }
            // fall-through
          case 1: // send reset code
            encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_reset_code,
                                                    sizeof(led_reset_code), &session_state);
            if (session_state & RMT_ENCODING_COMPLETE) {
              led_encoder_state = RMT_ENCODING_RESET; // back to the initial encoding session
              state |= RMT_ENCODING_COMPLETE;
            }
            if (session_state & RMT_ENCODING_MEM_FULL) {
              state |= RMT_ENCODING_MEM_FULL;
              goto out; // yield if there's no free space for encoding artifacts
            }
          }
        out:
          *ret_state = static_cast<rmt_encode_state_t>(state);
          return encoded_symbols;
        },
        .del = [](auto *base_encoder) -> esp_err_t {
          // we don't have any extra resources to free, so just return ESP_OK
          return ESP_OK;
        },
        .reset = [&led_encoder_state](auto *base_encoder) -> esp_err_t {
          // all we have is some extra state to reset
          led_encoder_state = 0;
          return ESP_OK;
        },
    });

Public Types

typedef std::function<size_t(rmt_channel_handle_t channel, rmt_encoder_t *copy_encoder, rmt_encoder_t *bytes_encoder, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)> encode_fn

Function to encode data for the RMT peripheral.

Note

This function is called by the Rmt class to encode data for transmission. It is called repeatedly until all data has been encoded.

Note

This function should return the number of bytes encoded.

Note

This function should set the RMT encoder state to the next state to be used for encoding.

Param channel

RMT channel to use for encoding

Param copy_encoder

RMT encoder to use for copying data

Param bytes_encoder

RMT encoder to use for encoding bytes

Param primary_data

Pointer to the primary data to encode

Param data_size

Size of the primary data to encode

Param ret_state

Pointer to the RMT encoder state to return

Return

Number of bytes encoded

typedef std::function<esp_err_t(rmt_encoder_t*)> delete_fn

Function to delete an RMT encoder.

Param encoder

RMT encoder to delete

Return

ESP_OK if the encoder was successfully deleted, an error code

typedef std::function<esp_err_t(rmt_encoder_t*)> reset_fn

Function to reset an RMT encoder.

Param encoder

RMT encoder to reset

Return

ESP_OK if the encoder was successfully reset, an error code

Public Functions

inline explicit RmtEncoder(const Config &config)

Constructor.

Parameters

config – Configuration for this class

inline ~RmtEncoder()

Destructor.

inline rmt_encoder_handle_t handle() const

Get the RMT encoder handle.

Returns

RMT encoder handle

Public Static Attributes

static constexpr rmt_bytes_encoder_config_t sk6805_10mhz_bytes_encoder_config  = {.bit0 ={.duration0 = static_cast<uint16_t>(0.3 * 10000000 / 1000000),.level0 = 1,.duration1 = static_cast<uint16_t>(0.9 * 10000000 / 1000000),.level1 = 0,},.bit1 ={.duration0 = static_cast<uint16_t>(0.6 * 10000000 / 1000000),.level0 = 1,.duration1 = static_cast<uint16_t>(0.6 * 10000000 / 1000000),.level1 = 0,},.flags ={.msb_first = 1},}

Configuration for the byte encoding for SK6805 LEDs.

This configuration is used to encode bytes for SK6085 LEDs.

See also

Config

Note

This configuration can be provided to the configuration for this class. These values are based on the timing values provided in the SK6805 datasheet (https://cdn-shop.adafruit.com/product-files/3484/3484_Datasheet.pdf)

static constexpr rmt_bytes_encoder_config_t ws2812_10mhz_bytes_encoder_config  = {.bit0 ={.duration0 = static_cast<uint16_t>(0.3 * 10000000 / 1000000),.level0 = 1,.duration1 = static_cast<uint16_t>(0.9 * 10000000 / 1000000),.level1 = 0,},.bit1 ={.duration0 = static_cast<uint16_t>(0.9 * 10000000 / 1000000),.level0 = 1,.duration1 = static_cast<uint16_t>(0.3 * 10000000 / 1000000),.level1 = 0,},.flags ={.msb_first = 1},}

Configuration for the byte encoding for WS2812 LEDs.

This configuration is used to encode bytes for WS2812 LEDs.

See also

Config

Note

This configuration can be provided to the configuration for this class.

struct Config

Configuration for this class.

Public Members

rmt_bytes_encoder_config_t bytes_encoder_config

Configuration for the RMT bytes encoder.

encode_fn encode

Function to encode data for the RMT peripheral.

delete_fn del

Function to delete an RMT encoder.

reset_fn reset

Function to reset an RMT encoder.