diff --git a/main/main.cpp b/main/main.cpp index acf27cf..8dd4b60 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -107,7 +107,7 @@ extern "C" void app_main(void) { // ScreenLEDs() // SPI_LEDs(39); // RMT_LEDs(GPIO_NUM_17); - LEDStrip *LEDs = new RMT_LEDs(GPIO_NUM_17); + LEDStrip *LEDs = new RMT_LEDs(17); int frequency = 30; while (true) { diff --git a/main/rmt_leds.cpp b/main/rmt_leds.cpp index 9e4b75f..8b19c18 100644 --- a/main/rmt_leds.cpp +++ b/main/rmt_leds.cpp @@ -7,98 +7,189 @@ static const char *TAG = "rmt_leds"; #include "rmt_leds.hpp" -#define WS2811_PULSE_WIDTH_NS 1250 +#define WS2811_PULSE_WIDTH_NS 833 +#define WS2811_RESET_WIDTH_US 300 -#define RMT_TX_CHANNEL RMT_CHANNEL_0 +// 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 int bit_ticks; - -static void IRAM_ATTR ws2811_rmt_adapter( - const void *src, rmt_item32_t *dest, size_t src_size, - size_t wanted_num, size_t *translated_size, size_t *item_num +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 ) { - if (src == NULL || dest == NULL) { - *translated_size = 0; - *item_num = 0; - return; + 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; + } } - const uint32_t short_ticks = bit_ticks / 3; - const uint32_t long_ticks = bit_ticks - short_ticks; - const rmt_item32_t bit0 = {{{ short_ticks, 1, long_ticks, 0 }}}; // Logical 0 - const rmt_item32_t bit1 = {{{ long_ticks, 1, short_ticks, 0 }}}; // Logical 1 - size_t size = 0; - size_t num = 0; - uint8_t *psrc = (uint8_t *)src; - rmt_item32_t *pdest = dest; - while (size < src_size && num < wanted_num) { - for (int i = 0; i < 8; i++) { - // MSB first - if (*psrc & (1 << (7 - i))) { - pdest->val = bit1.val; - } else { - pdest->val = bit0.val; - } - num++; - pdest++; + 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; } - size++; - psrc++; } - *translated_size = size; - *item_num = num; -} - + if (substate & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + } -RMT_LEDs::RMT_LEDs(gpio_num_t _gpio, int length) : - LEDStrip(length), gpio(_gpio), channel(RMT_CHANNEL_MAX) { - rmt_config_t config = RMT_DEFAULT_CONFIG_TX(gpio, RMT_TX_CHANNEL); - // set counter clock to 40MHz - config.clk_div = 2; + *ret_state = (rmt_encode_state_t)state; + return n_encoded; +} - ESP_ERROR_CHECK(rmt_config(&config)); - channel = config.channel; - ESP_ERROR_CHECK(rmt_driver_install(channel, 0, 0)); +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; +} - uint32_t counter_clk_hz = 0; - int err = rmt_get_counter_clock(channel, &counter_clk_hz); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to read RMT counter: %d", err); - return; - } +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; +} - rmt_translator_init(channel, ws2811_rmt_adapter); +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; +} - // ns -> ticks - bit_ticks = ((uint64_t)counter_clk_hz * WS2811_PULSE_WIDTH_NS) / 1e9; - ESP_LOGI(TAG, "Each bit is %d RMT ticks", bit_ticks); +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_driver_uninstall(channel)); + 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 + // 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, + }; - err = rmt_wait_tx_done(channel, 0); - if (err != ESP_OK) { - ESP_LOGE(TAG, "RMT TX incomplete: %d", err); + if (length <= 0) { + ESP_LOGW(TAG, "Invalid length write: %d", length); return; } - err = rmt_write_sample(channel, (uint8_t*)pixels, length * 3, false); + 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"); + } } } diff --git a/main/rmt_leds.hpp b/main/rmt_leds.hpp index d6924a4..42cc410 100644 --- a/main/rmt_leds.hpp +++ b/main/rmt_leds.hpp @@ -1,20 +1,30 @@ #pragma once -#include +#include + +#include #include "leds.hpp" +struct ws2811_rmt_encoder : rmt_encoder_t { + rmt_encoder_t *bytes_encoder; + rmt_encoder_t *reset_encoder; + bool bytes_encoded; +}; + class RMT_LEDs : public LEDStrip { public: - RMT_LEDs(gpio_num_t gpio, int length = 0); + RMT_LEDs(int gpio, int length = 0); ~RMT_LEDs(); void show(); void length_changing(int len) { configure(len); } private: - gpio_num_t gpio; - rmt_channel_t channel; + int gpio; + rmt_channel_handle_t channel; + ws2811_rmt_encoder encoder; + QueueHandle_t queue; void configure(int length); };