diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index b02accf..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "esp-idf-lib"] - path = esp-idf-lib - url = https://github.com/UncleRus/esp-idf-lib.git diff --git a/esp-idf-lib b/esp-idf-lib deleted file mode 160000 index c6da185..0000000 --- a/esp-idf-lib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c6da1853214c6979d21655b57b04cd039e6503d0 diff --git a/main/include/led_strip_encoder.h b/main/include/led_strip_encoder.h new file mode 100644 index 0000000..db5ef07 --- /dev/null +++ b/main/include/led_strip_encoder.h @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "driver/rmt_encoder.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of led strip encoder configuration + */ +typedef struct { + uint32_t resolution; /*!< Encoder resolution, in Hz */ +} led_strip_encoder_config_t; + +/** + * @brief Create RMT encoder for encoding LED strip pixels into RMT symbols + * + * @param[in] config Encoder configuration + * @param[out] ret_encoder Returned encoder handle + * @return + * - ESP_ERR_INVALID_ARG for any invalid arguments + * - ESP_ERR_NO_MEM out of memory when creating led strip encoder + * - ESP_OK if creating encoder successfully + */ +esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder); + +#ifdef __cplusplus +} +#endif diff --git a/main/include/led_task.h b/main/include/led_task.h index 74527cc..c05c663 100644 --- a/main/include/led_task.h +++ b/main/include/led_task.h @@ -2,14 +2,12 @@ #include #include -#include #include #define LED_Q_SIZE 4 #define LED_PATTERN_LEN 4 extern QueueHandle_t led_queue; -#define LED_COLOR_HEX(hex) ((rgb_t){ .r = ((hex) >> 16) & 0xff, .g = ((hex) >> 8) & 0xff, .b = (hex) & 0xff }) typedef enum { led_status_error = 0, @@ -20,7 +18,7 @@ typedef enum { } led_status_t; typedef struct { - rgb_t color; + uint32_t color; uint32_t time_ms; bool final; } led_pattern_step_t; diff --git a/main/led_strip_encoder.c b/main/led_strip_encoder.c new file mode 100644 index 0000000..de8638c --- /dev/null +++ b/main/led_strip_encoder.c @@ -0,0 +1,124 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_check.h" +#include "led_strip_encoder.h" + +static const char *TAG = "led_encoder"; + +typedef struct { + rmt_encoder_t base; + rmt_encoder_t *bytes_encoder; + rmt_encoder_t *copy_encoder; + int state; + rmt_symbol_word_t reset_code; +} rmt_led_strip_encoder_t; + +static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder; + rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder; + rmt_encode_state_t session_state = RMT_ENCODING_RESET; + rmt_encode_state_t state = RMT_ENCODING_RESET; + size_t encoded_symbols = 0; + switch (led_encoder->state) { + case 0: // send RGB data + encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data, data_size, &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + led_encoder->state = 1; // switch to next state when current encoding session finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space for encoding artifacts + } + // fall-through + case 1: // send reset code + encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code, + sizeof(led_encoder->reset_code), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + led_encoder->state = RMT_ENCODING_RESET; // back to the initial encoding session + state |= RMT_ENCODING_COMPLETE; + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space for encoding artifacts + } + } +out: + *ret_state = state; + return encoded_symbols; +} + +static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_del_encoder(led_encoder->bytes_encoder); + rmt_del_encoder(led_encoder->copy_encoder); + free(led_encoder); + return ESP_OK; +} + +static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_encoder_reset(led_encoder->bytes_encoder); + rmt_encoder_reset(led_encoder->copy_encoder); + led_encoder->state = RMT_ENCODING_RESET; + return ESP_OK; +} + +esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder) +{ + esp_err_t ret = ESP_OK; + rmt_led_strip_encoder_t *led_encoder = NULL; + ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + led_encoder = rmt_alloc_encoder_mem(sizeof(rmt_led_strip_encoder_t)); + ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for led strip encoder"); + led_encoder->base.encode = rmt_encode_led_strip; + led_encoder->base.del = rmt_del_led_strip_encoder; + led_encoder->base.reset = rmt_led_strip_encoder_reset; + // different led strip might have its own timing requirements, following parameter is for WS2812 + rmt_bytes_encoder_config_t bytes_encoder_config = { + .bit0 = { + .level0 = 1, + .duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us + .level1 = 0, + .duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us + }, + .bit1 = { + .level0 = 1, + .duration0 = 0.9 * config->resolution / 1000000, // T1H=0.9us + .level1 = 0, + .duration1 = 0.3 * config->resolution / 1000000, // T1L=0.3us + }, + .flags.msb_first = 1 // WS2812 transfer bit order: G7...G0R7...R0B7...B0 + }; + ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder), err, TAG, "create bytes encoder failed"); + rmt_copy_encoder_config_t copy_encoder_config = {}; + ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &led_encoder->copy_encoder), err, TAG, "create copy encoder failed"); + + uint32_t reset_ticks = config->resolution / 1000000 * 50 / 2; // reset code duration defaults to 50us + led_encoder->reset_code = (rmt_symbol_word_t) { + .level0 = 0, + .duration0 = reset_ticks, + .level1 = 0, + .duration1 = reset_ticks, + }; + *ret_encoder = &led_encoder->base; + return ESP_OK; +err: + if (led_encoder) { + if (led_encoder->bytes_encoder) { + rmt_del_encoder(led_encoder->bytes_encoder); + } + if (led_encoder->copy_encoder) { + rmt_del_encoder(led_encoder->copy_encoder); + } + free(led_encoder); + } + return ret; +} diff --git a/main/led_task.c b/main/led_task.c index c5cb3a1..9cff4f6 100644 --- a/main/led_task.c +++ b/main/led_task.c @@ -1,35 +1,36 @@ #include #include #include -#include -#include -#include +#include +#include #include #include #include "common.h" #include "led_task.h" #include "config.h" +#include "led_strip_encoder.h" #define TAG "led_task" +#define RMT_LED_STRIP_RESOLUTION_HZ 10000000 static const led_pattern_step_t _led_patterns[][LED_PATTERN_LEN] = { /* error */ { - { LED_COLOR_HEX(0xff0000), 500, 0 }, - { LED_COLOR_HEX(0), 500, 1 } + { 0xff0000, 500, 0 }, + { 0x000000, 500, 1 } }, /* wait */ { - { LED_COLOR_HEX(0xffff00), 10000, 1 } + { 0xffff00, 10000, 1 } }, /* idle */ { - { LED_COLOR_HEX(0x00ffff), 20, 0 }, - { LED_COLOR_HEX(0), 1980, 1 } + { 0x00ffff, 20, 0 }, + { 0x000000, 1980, 1 } }, /* granted */ { - { LED_COLOR_HEX(0x00ff00), 10000, 1} + { 0x00ff00, 10000, 1} }, /* refused */ { - { LED_COLOR_HEX(0xff0000), 10000, 1} + { 0xff0000, 10000, 1} }, }; @@ -37,14 +38,24 @@ QueueHandle_t led_queue; void led_task(void* _arg) { // initialize LED strip - led_strip_t strip = { - .type = LED_STRIP_WS2812, - .brightness = 255, - .length = 1, - .gpio = LED_PIN, - .channel = RMT_CHANNEL_0, + rmt_channel_handle_t led_chan = NULL; + rmt_tx_channel_config_t tx_chan_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .gpio_num = LED_PIN, + .mem_block_symbols = 64, + .resolution_hz = RMT_LED_STRIP_RESOLUTION_HZ, + .trans_queue_depth = 4, + }; + ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &led_chan)); + rmt_encoder_handle_t led_encoder = NULL; + led_strip_encoder_config_t encoder_config = { + .resolution = RMT_LED_STRIP_RESOLUTION_HZ, + }; + ESP_ERROR_CHECK(rmt_new_led_strip_encoder(&encoder_config, &led_encoder)); + ESP_ERROR_CHECK(rmt_enable(led_chan)); + rmt_transmit_config_t tx_config = { + .loop_count = 0, }; - ESP_ERROR_CHECK(led_strip_init(&strip)); // sequencer state led_status_t status = led_status_wait; @@ -59,11 +70,17 @@ void led_task(void* _arg) { } // refresh led according to next step + // discuss: does this macro trick improve or hurt readability? #define step _led_patterns[status][step_idx] if(step_idx < 0 || esp_timer_get_time() - step_started >= step.time_ms * 1000) { step_idx = (step_idx < 0 || step.final) ? 0 : (step_idx + 1); - led_strip_set_pixel(&strip, 0, step.color); - led_strip_flush(&strip); + uint8_t grb[3] = { + (step.color & 0x00ff00) >> 8, + (step.color & 0xff0000) >> 16, + step.color & 0x0000ff + }; + ESP_ERROR_CHECK(rmt_transmit(led_chan, led_encoder, grb, sizeof(grb), &tx_config)); + ESP_ERROR_CHECK(rmt_tx_wait_all_done(led_chan, portMAX_DELAY)); step_started = esp_timer_get_time(); } #undef step @@ -72,7 +89,6 @@ void led_task(void* _arg) { void led_init(void) { led_queue = xQueueCreate(LED_Q_SIZE, sizeof(led_status_t)); - led_strip_install(); } void led_set_status(led_status_t status) { diff --git a/sdkconfig b/sdkconfig index 98409c7..b8a38c3 100644 --- a/sdkconfig +++ b/sdkconfig @@ -1718,94 +1718,6 @@ CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30 CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y # CONFIG_WIFI_PROV_STA_FAST_SCAN is not set # end of Wi-Fi Provisioning Manager - -# -# Button -# -CONFIG_BUTTON_MAX=5 -CONFIG_BUTTON_POLL_TIMEOUT=10 -CONFIG_BUTTON_LONG_PRESS_TIMEOUT=1000 -CONFIG_BUTTON_AUTOREPEAT_TIMEOUT=500 -CONFIG_BUTTON_AUTOREPEAT_INTERVAL=250 -# end of Button - -# -# DPS310 driver -# -CONFIG_DPS310_PROTOCOL_USING_I2C=y -# end of DPS310 driver - -# -# Rotary encoders -# -CONFIG_RE_MAX=1 -CONFIG_RE_INTERVAL_US=1000 -CONFIG_RE_BTN_DEAD_TIME_US=10000 -CONFIG_RE_BTN_PRESSED_LEVEL_0=y -# CONFIG_RE_BTN_PRESSED_LEVEL_1 is not set -CONFIG_RE_BTN_LONG_PRESS_TIME_US=500000 -# end of Rotary encoders - -# -# Example -# -CONFIG_EXAMPLE_USING_FOO=y -# CONFIG_EXAMPLE_USING_BAR is not set -CONFIG_EXAMPLE_NUMBER=1 -# end of Example - -# -# HMC5883L -# -CONFIG_HMC5883L_MEAS_TIMEOUT=6000 -# end of HMC5883L - -# -# I2C -# -CONFIG_I2CDEV_TIMEOUT=1000 -# CONFIG_I2CDEV_NOLOCK is not set -# end of I2C - -# -# LED strip -# -CONFIG_LED_STRIP_FLUSH_TIMEOUT=1000 -CONFIG_LED_STRIP_PAUSE_LENGTH=50 -# end of LED strip - -# -# LED strip SPI -# -CONFIG_LED_STRIP_SPI_USING_SK9822=y -CONFIG_LED_STRIP_SPI_MUTEX_TIMEOUT_MS=1 -# end of LED strip SPI - -# -# MAX1704X Battery Fuel Gauge -# -CONFIG_MAX1704X_MODEL_3_4=y -# CONFIG_MAX1704X_MODEL_8_9 is not set -# end of MAX1704X Battery Fuel Gauge - -# -# MCP23x17 -# -CONFIG_MCP23X17_IFACE_I2C=y -# CONFIG_MCP23X17_IFACE_SPI is not set -# end of MCP23x17 - -# -# MPU6050 -# -# CONFIG_AUTO_CONFIG_ADDR is not set -# end of MPU6050 - -# -# OneWire -# -# CONFIG_ONEWIRE_CRC8_TABLE is not set -# end of OneWire # end of Component config # CONFIG_IDF_EXPERIMENTAL_FEATURES is not set