Skip to content

Commit 4a717a2

Browse files
committed
samples: drivers: add espressif rmt application
This sample application demonstrates usage of the Espressif RMT driver. Signed-off-by: Joel Guittet <joelguittet@gmail.com>
1 parent bb3210a commit 4a717a2

File tree

9 files changed

+384
-0
lines changed

9 files changed

+384
-0
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright (c) 2025 Joel Guittet (joelguittet@gmail.com)
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
cmake_minimum_required(VERSION 3.20.0)
5+
6+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
7+
project(espressif_rmt)
8+
9+
target_sources(app PRIVATE src/main.c src/led_strip_encoder.c)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Private config options for espressif_rmt sample
2+
3+
# Copyright (c) 2025 Joel Guittet (joelguittet@gmail.com)
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
mainmenu "Espressif RMT sample application"
7+
8+
source "Kconfig.zephyr"
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.. zephyr:code-sample:: rmt
2+
:name: Espressif Remote Control Transceiver (RMT)
3+
:relevant-api: rmt_interface
4+
5+
Generate a WS2812 signal using the RMT driver API.
6+
7+
Overview
8+
********
9+
10+
This sample demonstrates how to use the :ref:`Espressif RMT driver API <rmt_api>`.
11+
12+
You should connect a WS2812 strip to the RMT output of the board.
13+
14+
Building and Running
15+
********************
16+
17+
The RMT output is defined in the board's devicetree and pinmux file.
18+
19+
Building and Running for esp32-devkitc
20+
======================================
21+
The sample can be built and executed for the
22+
:zephyr:board:`esp32_devkitc` as follows:
23+
24+
.. zephyr-app-commands::
25+
:zephyr-app: samples/drivers/misc/espressif_rmt
26+
:board: esp32_devkitc/esp32/procpu
27+
:goals: build flash
28+
:compact:
29+
30+
Sample output
31+
=============
32+
33+
.. code-block:: console
34+
35+
*** Booting Zephyr OS build v4.3.0-2805-g63ff14e3d8d0 ***
36+
Create RMT TX channel
37+
I (141) gpio: GPIO[12]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
38+
Install led strip encoder
39+
Enable RMT TX channel
40+
Start LED rainbow chase
41+
42+
After you seeing this log, you should see a rainbow chasing demonstration pattern.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CONFIG_ESPRESSIF_RMT=y
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
sample:
2+
name: espressif_rmt
3+
tests:
4+
sample.drivers.misc.espressif_rmt:
5+
tags:
6+
- drivers
7+
filter: dt_compat_enabled("espressif,esp32-rmt")
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright 2025 Joel Guittet (joelguittet@gmail.com)
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/ {
8+
zephyr,user {
9+
rmt-output-gpios = <&gpio0 12 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
10+
};
11+
};
12+
13+
&rmt {
14+
status = "okay";
15+
};
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include "esp_check.h"
8+
#include "led_strip_encoder.h"
9+
10+
static const char *TAG = "led_encoder";
11+
12+
typedef struct {
13+
rmt_encoder_t base;
14+
rmt_encoder_t *bytes_encoder;
15+
rmt_encoder_t *copy_encoder;
16+
int state;
17+
rmt_symbol_word_t reset_code;
18+
} rmt_led_strip_encoder_t;
19+
20+
static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
21+
{
22+
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
23+
rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder;
24+
rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder;
25+
rmt_encode_state_t session_state = RMT_ENCODING_RESET;
26+
rmt_encode_state_t state = RMT_ENCODING_RESET;
27+
size_t encoded_symbols = 0;
28+
switch (led_encoder->state) {
29+
case 0: // send RGB data
30+
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data, data_size, &session_state);
31+
if (session_state & RMT_ENCODING_COMPLETE) {
32+
led_encoder->state = 1; // switch to next state when current encoding session finished
33+
}
34+
if (session_state & RMT_ENCODING_MEM_FULL) {
35+
state |= RMT_ENCODING_MEM_FULL;
36+
goto out; // yield if there's no free space for encoding artifacts
37+
}
38+
// fall-through
39+
case 1: // send reset code
40+
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code,
41+
sizeof(led_encoder->reset_code), &session_state);
42+
if (session_state & RMT_ENCODING_COMPLETE) {
43+
led_encoder->state = RMT_ENCODING_RESET; // back to the initial encoding session
44+
state |= RMT_ENCODING_COMPLETE;
45+
}
46+
if (session_state & RMT_ENCODING_MEM_FULL) {
47+
state |= RMT_ENCODING_MEM_FULL;
48+
goto out; // yield if there's no free space for encoding artifacts
49+
}
50+
}
51+
out:
52+
*ret_state = state;
53+
return encoded_symbols;
54+
}
55+
56+
static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder)
57+
{
58+
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
59+
rmt_del_encoder(led_encoder->bytes_encoder);
60+
rmt_del_encoder(led_encoder->copy_encoder);
61+
free(led_encoder);
62+
return ESP_OK;
63+
}
64+
65+
static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder)
66+
{
67+
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
68+
rmt_encoder_reset(led_encoder->bytes_encoder);
69+
rmt_encoder_reset(led_encoder->copy_encoder);
70+
led_encoder->state = RMT_ENCODING_RESET;
71+
return ESP_OK;
72+
}
73+
74+
esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
75+
{
76+
esp_err_t ret = ESP_OK;
77+
rmt_led_strip_encoder_t *led_encoder = NULL;
78+
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
79+
led_encoder = rmt_alloc_encoder_mem(sizeof(rmt_led_strip_encoder_t));
80+
ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for led strip encoder");
81+
led_encoder->base.encode = rmt_encode_led_strip;
82+
led_encoder->base.del = rmt_del_led_strip_encoder;
83+
led_encoder->base.reset = rmt_led_strip_encoder_reset;
84+
// different led strip might have its own timing requirements, following parameter is for WS2812
85+
rmt_bytes_encoder_config_t bytes_encoder_config = {
86+
.bit0 = {
87+
.level0 = 1,
88+
.duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us
89+
.level1 = 0,
90+
.duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us
91+
},
92+
.bit1 = {
93+
.level0 = 1,
94+
.duration0 = 0.9 * config->resolution / 1000000, // T1H=0.9us
95+
.level1 = 0,
96+
.duration1 = 0.3 * config->resolution / 1000000, // T1L=0.3us
97+
},
98+
.flags.msb_first = 1 // WS2812 transfer bit order: G7...G0R7...R0B7...B0
99+
};
100+
ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder), err, TAG, "create bytes encoder failed");
101+
rmt_copy_encoder_config_t copy_encoder_config = {};
102+
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(&copy_encoder_config, &led_encoder->copy_encoder), err, TAG, "create copy encoder failed");
103+
104+
uint32_t reset_ticks = config->resolution / 1000000 * 50 / 2; // reset code duration defaults to 50us
105+
led_encoder->reset_code = (rmt_symbol_word_t) {
106+
.level0 = 0,
107+
.duration0 = reset_ticks,
108+
.level1 = 0,
109+
.duration1 = reset_ticks,
110+
};
111+
*ret_encoder = &led_encoder->base;
112+
return ESP_OK;
113+
err:
114+
if (led_encoder) {
115+
if (led_encoder->bytes_encoder) {
116+
rmt_del_encoder(led_encoder->bytes_encoder);
117+
}
118+
if (led_encoder->copy_encoder) {
119+
rmt_del_encoder(led_encoder->copy_encoder);
120+
}
121+
free(led_encoder);
122+
}
123+
return ret;
124+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
#pragma once
7+
8+
#include <stdint.h>
9+
#include <zephyr/drivers/misc/espressif_rmt/rmt_encoder.h>
10+
11+
#ifdef __cplusplus
12+
extern "C" {
13+
#endif
14+
15+
/**
16+
* @brief Type of led strip encoder configuration
17+
*/
18+
typedef struct {
19+
uint32_t resolution; /*!< Encoder resolution, in Hz */
20+
} led_strip_encoder_config_t;
21+
22+
/**
23+
* @brief Create RMT encoder for encoding LED strip pixels into RMT symbols
24+
*
25+
* @param[in] dev Pointer to the device structure for the driver instance
26+
* @param[in] config Encoder configuration
27+
* @param[out] ret_encoder Returned encoder handle
28+
* @return
29+
* - ESP_ERR_INVALID_ARG for any invalid arguments
30+
* - ESP_ERR_NO_MEM out of memory when creating led strip encoder
31+
* - ESP_OK if creating encoder successfully
32+
*/
33+
esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder);
34+
35+
#ifdef __cplusplus
36+
}
37+
#endif
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright (c) 2025 Joel Guittet (joelguittet@gmail.com)
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/kernel.h>
8+
#include <zephyr/sys/printk.h>
9+
#include <zephyr/drivers/gpio.h>
10+
#include <zephyr/drivers/misc/espressif_rmt/rmt_tx.h>
11+
#include "led_strip_encoder.h"
12+
13+
#define ZEPHYR_USER_NODE DT_PATH(zephyr_user)
14+
15+
#if DT_NODE_HAS_PROP(ZEPHYR_USER_NODE, rmt_output_gpios)
16+
const struct gpio_dt_spec rmt_gpio = GPIO_DT_SPEC_GET(DT_PATH(zephyr_user), rmt_output_gpios);
17+
#else
18+
#error "Unsupported board: see README and check /zephyr,user node"
19+
#endif
20+
21+
static const struct device *const rmt_dev = DEVICE_DT_GET_ONE(espressif_esp32_rmt);
22+
23+
#define RMT_LED_STRIP_RESOLUTION_HZ 10000000 // 10MHz resolution, 1 tick = 0.1us (led strip needs a high resolution)
24+
25+
#define EXAMPLE_LED_NUMBERS 24
26+
#define EXAMPLE_CHASE_SPEED_MS 10
27+
28+
static uint8_t led_strip_pixels[EXAMPLE_LED_NUMBERS * 3];
29+
30+
/**
31+
* @brief Simple helper function, converting HSV color space to RGB color space
32+
*
33+
* Wiki: https://en.wikipedia.org/wiki/HSL_and_HSV
34+
*
35+
*/
36+
void led_strip_hsv2rgb(uint32_t h, uint32_t s, uint32_t v, uint32_t *r, uint32_t *g, uint32_t *b)
37+
{
38+
h %= 360; // h -> [0,360]
39+
uint32_t rgb_max = v * 2.55f;
40+
uint32_t rgb_min = rgb_max * (100 - s) / 100.0f;
41+
42+
uint32_t i = h / 60;
43+
uint32_t diff = h % 60;
44+
45+
// RGB adjustment amount by hue
46+
uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60;
47+
48+
switch (i) {
49+
case 0:
50+
*r = rgb_max;
51+
*g = rgb_min + rgb_adj;
52+
*b = rgb_min;
53+
break;
54+
case 1:
55+
*r = rgb_max - rgb_adj;
56+
*g = rgb_max;
57+
*b = rgb_min;
58+
break;
59+
case 2:
60+
*r = rgb_min;
61+
*g = rgb_max;
62+
*b = rgb_min + rgb_adj;
63+
break;
64+
case 3:
65+
*r = rgb_min;
66+
*g = rgb_max - rgb_adj;
67+
*b = rgb_max;
68+
break;
69+
case 4:
70+
*r = rgb_min + rgb_adj;
71+
*g = rgb_min;
72+
*b = rgb_max;
73+
break;
74+
default:
75+
*r = rgb_max;
76+
*g = rgb_min;
77+
*b = rgb_max - rgb_adj;
78+
break;
79+
}
80+
}
81+
82+
int main(void)
83+
{
84+
uint32_t red = 0;
85+
uint32_t green = 0;
86+
uint32_t blue = 0;
87+
uint16_t hue = 0;
88+
uint16_t start_rgb = 0;
89+
90+
if (!device_is_ready(rmt_dev)) {
91+
printk("RMT device %s is not ready\n", rmt_dev->name);
92+
return 0;
93+
}
94+
95+
printk("Create RMT TX channel\n");
96+
rmt_channel_handle_t led_chan = NULL;
97+
rmt_tx_channel_config_t tx_chan_config = {
98+
.clk_src = RMT_CLK_SRC_DEFAULT, // select source clock
99+
.gpio_num = rmt_gpio.pin,
100+
.mem_block_symbols = 64, // increase the block size can make the LED less flickering
101+
.resolution_hz = RMT_LED_STRIP_RESOLUTION_HZ,
102+
.trans_queue_depth = 4, // set the number of transactions that can be pending in the background
103+
};
104+
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &led_chan));
105+
106+
printk("Install led strip encoder\n");
107+
rmt_encoder_handle_t led_encoder = NULL;
108+
led_strip_encoder_config_t encoder_config = {
109+
.resolution = RMT_LED_STRIP_RESOLUTION_HZ,
110+
};
111+
ESP_ERROR_CHECK(rmt_new_led_strip_encoder(&encoder_config, &led_encoder));
112+
113+
printk("Enable RMT TX channel\n");
114+
ESP_ERROR_CHECK(rmt_enable(led_chan));
115+
116+
printk("Start LED rainbow chase\n");
117+
rmt_transmit_config_t tx_config = {
118+
.loop_count = 0, // no transfer loop
119+
};
120+
while (1) {
121+
for (int i = 0; i < 3; i++) {
122+
for (int j = i; j < EXAMPLE_LED_NUMBERS; j += 3) {
123+
// Build RGB pixels
124+
hue = j * 360 / EXAMPLE_LED_NUMBERS + start_rgb;
125+
led_strip_hsv2rgb(hue, 100, 100, &red, &green, &blue);
126+
led_strip_pixels[j * 3 + 0] = green;
127+
led_strip_pixels[j * 3 + 1] = blue;
128+
led_strip_pixels[j * 3 + 2] = red;
129+
}
130+
// Flush RGB values to LEDs
131+
ESP_ERROR_CHECK(rmt_transmit(led_chan, led_encoder, led_strip_pixels, sizeof(led_strip_pixels), &tx_config));
132+
ESP_ERROR_CHECK(rmt_tx_wait_all_done(led_chan, K_FOREVER));
133+
k_sleep(K_MSEC(EXAMPLE_CHASE_SPEED_MS));
134+
memset(led_strip_pixels, 0, sizeof(led_strip_pixels));
135+
ESP_ERROR_CHECK(rmt_transmit(led_chan, led_encoder, led_strip_pixels, sizeof(led_strip_pixels), &tx_config));
136+
ESP_ERROR_CHECK(rmt_tx_wait_all_done(led_chan, K_FOREVER));
137+
k_sleep(K_MSEC(EXAMPLE_CHASE_SPEED_MS));
138+
}
139+
start_rgb += 60;
140+
}
141+
}

0 commit comments

Comments
 (0)