static const char *TAG = "rmt_leds"; #include #include #include #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"); } } }