Przeglądaj źródła

Add RMT DMA support

S3
jrhoffa 3 lat temu
rodzic
commit
0370e2b6e1
3 zmienionych plików z 165 dodań i 64 usunięć
  1. +1
    -1
      main/main.cpp
  2. +150
    -59
      main/rmt_leds.cpp
  3. +14
    -4
      main/rmt_leds.hpp

+ 1
- 1
main/main.cpp Wyświetl plik

@@ -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) {


+ 150
- 59
main/rmt_leds.cpp Wyświetl plik

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

+ 14
- 4
main/rmt_leds.hpp Wyświetl plik

@@ -1,20 +1,30 @@
#pragma once

#include <driver/rmt.h>
#include <freertos/queue.h>

#include <driver/rmt_tx.h>

#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);
};

Ładowanie…
Anuluj
Zapisz