|
|
|
@@ -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"); |
|
|
|
} |
|
|
|
} |
|
|
|
} |