|
- static const char *TAG = "rmt_leds";
- #include <esp_log.h>
-
- #include <freertos/FreeRTOS.h>
- #include <freertos/task.h>
-
- #include "rmt_leds.hpp"
-
-
- #define WS2811_PULSE_WIDTH_NS 825
- #define WS2811_RESET_WIDTH_US 300
-
- // Each pulse is divided into thirds - two high / one low for 1, vice versa for 0
- #define SHORT_TICKS 1
- #define LONG_TICKS 2
- #define BIT_TICKS (SHORT_TICKS + LONG_TICKS)
- #define RMT_RESOLUTION_HZ ((BIT_TICKS * (uint64_t)1e9) / WS2811_PULSE_WIDTH_NS)
- #define RESET_TICKS ((RMT_RESOLUTION_HZ * WS2811_RESET_WIDTH_US) / (uint32_t)1e6)
-
- // Use RMT CHannel 3 for TX + DMA
- #define RMT_TX_CHANNEL RMT_CHANNEL_3
-
-
- static size_t IRAM_ATTR ws2811_rmt_encode(
- rmt_encoder_t *base_encoder, rmt_channel_handle_t channel,
- const void *data, size_t data_size, rmt_encode_state_t *ret_state
- ) {
- static const rmt_symbol_word_t reset_symbol = (rmt_symbol_word_t){
- .duration0 = RESET_TICKS,
- .level0 = 0,
- .duration1 = 0,
- .level1 = 0,
- };
- int state = 0;
- rmt_encode_state_t substate = (rmt_encode_state_t)0;
- size_t n_encoded = 0;
- ws2811_rmt_encoder *encoder = (ws2811_rmt_encoder*)base_encoder;
-
- if (!encoder->bytes_encoded) {
- // Encode remaining bytes
- n_encoded += encoder->bytes_encoder->encode(
- encoder->bytes_encoder, channel,
- data, data_size,
- &substate
- );
-
- if (substate & RMT_ENCODING_COMPLETE) {
- encoder->bytes_encoded = true;
- }
- }
-
- if (encoder->bytes_encoded) {
- // All bytes encoded; encode reset pulse
- n_encoded += encoder->reset_encoder->encode(
- encoder->reset_encoder, channel,
- &reset_symbol, sizeof(reset_symbol),
- &substate
- );
- if (substate & RMT_ENCODING_COMPLETE) {
- state |= RMT_ENCODING_COMPLETE;
- }
- }
-
- if (substate & RMT_ENCODING_MEM_FULL) {
- state |= RMT_ENCODING_MEM_FULL;
- }
-
- *ret_state = (rmt_encode_state_t)state;
- return n_encoded;
- }
-
- static esp_err_t ws2811_rmt_reset(rmt_encoder_t *base_encoder) {
- ws2811_rmt_encoder *encoder = (ws2811_rmt_encoder*)base_encoder;
- rmt_encoder_reset(encoder->bytes_encoder);
- rmt_encoder_reset(encoder->reset_encoder);
- encoder->bytes_encoded = false;
- return ESP_OK;
- }
-
- static esp_err_t ws2811_rmt_del(rmt_encoder_t *base_encoder) {
- ws2811_rmt_encoder *encoder = (ws2811_rmt_encoder*)base_encoder;
- rmt_del_encoder(encoder->bytes_encoder);
- rmt_del_encoder(encoder->reset_encoder);
- return ESP_OK;
- }
-
- static bool IRAM_ATTR ws2811_tx_done(
- rmt_channel_handle_t tx_chan,
- const rmt_tx_done_event_data_t *edata,
- void *user_ctx
- ) {
- BaseType_t high_task_wakeup = pdFALSE;
- QueueHandle_t queue = (QueueHandle_t)user_ctx;
- // send the transmitted RMT symbols to the parser task
- xQueueSendFromISR(queue, edata, &high_task_wakeup);
- return high_task_wakeup == pdTRUE;
- }
-
-
- RMT_LEDs::RMT_LEDs(int _gpio, int length) :
- LEDStrip(length), gpio(_gpio), channel(NULL),
- encoder({{
- .encode = ws2811_rmt_encode,
- .reset = ws2811_rmt_reset,
- .del = ws2811_rmt_del,
- },
- NULL, NULL, false
- }),
- queue(xQueueCreate(1, sizeof(rmt_tx_done_event_data_t)))
- {
- rmt_tx_channel_config_t config = {
- .gpio_num = gpio, // GPIO number
- .clk_src = RMT_CLK_SRC_DEFAULT, // select source clock
- .resolution_hz = RMT_RESOLUTION_HZ, // 3.6 MHz tick resolution -> 278 ns/tick
- .mem_block_symbols = 64, // memory block size, 64 * 4 = 256Bytes
- .trans_queue_depth = 1, // set the number of transactions that can pend in the background
- .flags = {
- .invert_out = false, // don't invert output signal
- .with_dma = true, // use DMA backend
- .io_loop_back = 0,
- .io_od_mode = 0,
- },
- };
- ESP_ERROR_CHECK(rmt_new_tx_channel(&config, &channel));
-
- rmt_tx_event_callbacks_t cbs = {
- .on_trans_done = ws2811_tx_done,
- };
- ESP_ERROR_CHECK(rmt_tx_register_event_callbacks(channel, &cbs, queue));
-
- ESP_ERROR_CHECK(rmt_enable(channel));
-
- rmt_bytes_encoder_config_t bytes_config = {
- .bit0 = {
- .duration0 = SHORT_TICKS,
- .level0 = 1,
- .duration1 = LONG_TICKS,
- .level1 = 0,
- },
- .bit1 = {
- .duration0 = LONG_TICKS,
- .level0 = 1,
- .duration1 = SHORT_TICKS,
- .level1 = 0,
- },
- .flags = {
- .msb_first = 1,
- },
- };
- ESP_ERROR_CHECK(rmt_new_bytes_encoder(&bytes_config, &encoder.bytes_encoder));
-
- rmt_copy_encoder_config_t copy_config = {};
- ESP_ERROR_CHECK(rmt_new_copy_encoder(©_config, &encoder.reset_encoder));
-
- configure(length);
- }
-
- RMT_LEDs::~RMT_LEDs() {
- ESP_ERROR_CHECK(rmt_del_encoder(&encoder));
- ESP_ERROR_CHECK(rmt_disable(channel));
- ESP_ERROR_CHECK(rmt_del_channel(channel));
- }
-
- void RMT_LEDs::configure(int n_leds) {
- // We don't own the RMT buffer, but maybe we need a threadsafe copy of pixels?
- }
-
- void RMT_LEDs::show() {
- esp_err_t err;
- static const rmt_transmit_config_t config = {
- .loop_count = 0,
- .flags = 0,
- };
-
- if (length <= 0) {
- ESP_LOGW(TAG, "Invalid length write: %d", length);
- return;
- }
-
- rmt_encoder_reset(&encoder);
-
- err = rmt_transmit(channel, &encoder, pixels, length * 3, &config);
- if (err != ESP_OK) {
- ESP_LOGE(TAG, "Failed to write sample to RMT: %d", err);
-
- } else {
- // TODO: Do a queue check prior to TX instead
- // This was an attempt to avoid clobbering pixel data,
- // but that doesn't appear to be why everything gets weird during OTA.
- rmt_tx_done_event_data_t tx_data;
- if (xQueueReceive(queue, &tx_data, portMAX_DELAY) != pdTRUE) {
- ESP_LOGE(TAG, "Failed to wait for last TX");
- }
- }
- }
|