feat(rmt): Add simple callback encoder

This commit is contained in:
Jeroen Domburg
2024-03-26 23:12:47 +08:00
parent 19700a57e6
commit 0078680e88
7 changed files with 479 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(led_strip_simple_encoder)

View File

@@ -0,0 +1,56 @@
| Supported Targets | ESP32 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
# RMT Transmit Example -- LED Strip
(See the README.md file in the upper level 'examples' directory for more information about examples.)
Almost any waveform can be generated by RMT peripheral, as long as a proper encoder is implemented. In this example, the simple callback RMT encoder is used to convert RGB pixels into format that can be recognized by hardware.
This example shows how to drive an addressable LED strip [WS2812](https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf) by implementing a callback that can be used by the simple callback RMT encoder.
## How to Use Example
### Hardware Required
* A development board with any supported Espressif SOC chip (see `Supported Targets` table above)
* A USB cable for Power supply and programming
* A WS2812 LED strip
Connection :
```
--- 5V
|
+
RMT_LED_STRIP_GPIO_NUM +------ +---|>| (WS2812 LED strip)
DI +
|
--- GND
```
The GPIO number used in this example can be changed according to your board, by the macro `RMT_LED_STRIP_GPIO_NUM` defined in the [source file](main/led_strip_example_main.c). The number of LEDs can be changed as well by `EXAMPLE_LED_NUMBERS`.
### Build and Flash
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Console Output
```
I (302) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (323) example: Create RMT TX channel
I (343) example: Create simple callback-based encoder
I (353) example: Start LED rainbow chase
```
After you seeing this log, you should see a rainbow chasing demonstration pattern. To change the chasing speed, you can update the `EXAMPLE_ANGLE_INC_FRAME` value in [source file](main/led_strip_example_main.c). To change the density of colors, you can change `EXAMPLE_ANGLE_INC_LED` in the same file.
## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "led_strip_example_main.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,134 @@
/*
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/rmt_tx.h"
#define RMT_LED_STRIP_RESOLUTION_HZ 10000000 // 10MHz resolution, 1 tick = 0.1us (led strip needs a high resolution)
#define RMT_LED_STRIP_GPIO_NUM 8
#define EXAMPLE_LED_NUMBERS 24
#define EXAMPLE_FRAME_DURATION_MS 20
#define EXAMPLE_ANGLE_INC_FRAME 0.02
#define EXAMPLE_ANGLE_INC_LED 0.3
static const char *TAG = "example";
static uint8_t led_strip_pixels[EXAMPLE_LED_NUMBERS * 3];
static const rmt_symbol_word_t ws2812_zero = {
.level0 = 1,
.duration0 = 0.3 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000, // T0H=0.3us
.level1 = 0,
.duration1 = 0.9 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000, // T0L=0.9us
};
static const rmt_symbol_word_t ws2812_one = {
.level0 = 1,
.duration0 = 0.9 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000, // T1H=0.9us
.level1 = 0,
.duration1 = 0.3 * RMT_LED_STRIP_RESOLUTION_HZ / 1000000, // T1L=0.3us
};
//reset defaults to 50uS
static const rmt_symbol_word_t ws2812_reset = {
.level0 = 1,
.duration0 = RMT_LED_STRIP_RESOLUTION_HZ / 1000000 * 50 / 2,
.level1 = 0,
.duration1 = RMT_LED_STRIP_RESOLUTION_HZ / 1000000 * 50 / 2,
};
static size_t encoder_callback(const void *data, size_t data_size,
size_t symbols_written, size_t symbols_free,
rmt_symbol_word_t *symbols, bool *done, void *arg)
{
// We need a minimum of 8 symbol spaces to encode a byte. We only
// need one to encode a reset, but it's simpler to simply demand that
// there are 8 symbol spaces free to write anything.
if (symbols_free < 8) {
return 0;
}
// We can calculate where in the data we are from the symbol pos.
// Alternatively, we could use some counter referenced by the arg
// parameter to keep track of this.
size_t data_pos = symbols_written / 8;
uint8_t *data_bytes = (uint8_t*)data;
if (data_pos < data_size) {
// Encode a byte
size_t symbol_pos = 0;
for (int bitmask = 0x80; bitmask != 0; bitmask >>= 1) {
if (data_bytes[data_pos]&bitmask) {
symbols[symbol_pos++] = ws2812_one;
} else {
symbols[symbol_pos++] = ws2812_zero;
}
}
// We're done; we should have written 8 symbols.
return symbol_pos;
} else {
//All bytes already are encoded.
//Encode the reset, and we're done.
symbols[0] = ws2812_reset;
*done = 1; //Indicate end of the transaction.
return 1; //we only wrote one symbol
}
}
void app_main(void)
{
ESP_LOGI(TAG, "Create RMT TX channel");
rmt_channel_handle_t led_chan = NULL;
rmt_tx_channel_config_t tx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, // select source clock
.gpio_num = RMT_LED_STRIP_GPIO_NUM,
.mem_block_symbols = 64, // increase the block size can make the LED less flickering
.resolution_hz = RMT_LED_STRIP_RESOLUTION_HZ,
.trans_queue_depth = 4, // set the number of transactions that can be pending in the background
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &led_chan));
ESP_LOGI(TAG, "Create simple callback-based encoder");
rmt_encoder_handle_t simple_encoder = NULL;
const rmt_simple_encoder_config_t simple_encoder_cfg = {
.callback = encoder_callback
//Note we don't set min_chunk_size here as the default of 64 is good enough.
};
ESP_ERROR_CHECK(rmt_new_simple_encoder(&simple_encoder_cfg, &simple_encoder));
ESP_LOGI(TAG, "Enable RMT TX channel");
ESP_ERROR_CHECK(rmt_enable(led_chan));
ESP_LOGI(TAG, "Start LED rainbow chase");
rmt_transmit_config_t tx_config = {
.loop_count = 0, // no transfer loop
};
float offset = 0;
while (1) {
for (int led = 0; led < EXAMPLE_LED_NUMBERS; led++) {
// Build RGB pixels. Each color is an offset sine, which gives a
// hue-like effect.
float angle = offset + (led * EXAMPLE_ANGLE_INC_LED);
const float color_off = (M_PI * 2) / 3;
led_strip_pixels[led * 3 + 0] = sin(angle + color_off * 0) * 127 + 128;
led_strip_pixels[led * 3 + 1] = sin(angle + color_off * 1) * 127 + 128;
led_strip_pixels[led * 3 + 2] = sin(angle + color_off * 2) * 117 + 128;;
}
// Flush RGB values to LEDs
ESP_ERROR_CHECK(rmt_transmit(led_chan, simple_encoder, led_strip_pixels, sizeof(led_strip_pixels), &tx_config));
ESP_ERROR_CHECK(rmt_tx_wait_all_done(led_chan, portMAX_DELAY));
vTaskDelay(pdMS_TO_TICKS(EXAMPLE_FRAME_DURATION_MS));
//Increase offset to shift pattern
offset += EXAMPLE_ANGLE_INC_FRAME;
if (offset > 2 * M_PI) {
offset -= 2 * M_PI;
}
}
}