From 0b9f6929de61a7601b84a1a1a248ad789337d6f5 Mon Sep 17 00:00:00 2001 From: Andrey Aleksandrov Date: Tue, 30 Dec 2025 21:48:50 +0200 Subject: [PATCH] Initial commit --- CHANGELOG.md | 12 ++ led_manager.c | 348 ++++++++++++++++++++++++++++++++++++++++ led_manager.h | 97 +++++++++++ led_strip.h | 108 +++++++++++++ led_strip_api.c | 94 +++++++++++ led_strip_interface.h | 95 +++++++++++ led_strip_rmt.h | 47 ++++++ led_strip_rmt_dev.c | 184 +++++++++++++++++++++ led_strip_rmt_encoder.c | 146 +++++++++++++++++ led_strip_rmt_encoder.h | 38 +++++ led_strip_spi.h | 47 ++++++ led_strip_spi_dev.c | 230 ++++++++++++++++++++++++++ led_strip_types.h | 69 ++++++++ library.json | 16 ++ 14 files changed, 1531 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 led_manager.c create mode 100644 led_manager.h create mode 100644 led_strip.h create mode 100644 led_strip_api.c create mode 100644 led_strip_interface.h create mode 100644 led_strip_rmt.h create mode 100644 led_strip_rmt_dev.c create mode 100644 led_strip_rmt_encoder.c create mode 100644 led_strip_rmt_encoder.h create mode 100644 led_strip_spi.h create mode 100644 led_strip_spi_dev.c create mode 100644 led_strip_types.h create mode 100644 library.json diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8625ef8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [v1.0.0] - 2024-12-30 + +### Added + +- Repository diff --git a/led_manager.c b/led_manager.c new file mode 100644 index 0000000..35aaa5d --- /dev/null +++ b/led_manager.c @@ -0,0 +1,348 @@ +#include "led_manager.h" +#include + +static const char *TAG = "LED_MANAGER"; +static led_strip_handle_t led_strip = NULL; +static bool is_initialized = false; + +// Blinking functionality +static TaskHandle_t blink_task_handle = NULL; +static bool is_blinking = false; +static uint8_t current_red = 0; +static uint8_t current_green = 0; +static uint8_t current_blue = 0; +static uint32_t blink_rate_ms = 500; +static bool blink_state = false; + +// Forward declaration for blink task +static void led_blink_task(void *pvParameter); + +// RGB color definitions +static const struct +{ + uint8_t red; + uint8_t green; + uint8_t blue; + const char *name; + led_color_t enum_val; +} rgb_colors[] = { + {200, 0, 0, "Red", LED_COLOR_RED}, + {200, 40, 0, "Orange", LED_COLOR_ORANGE}, + {200, 160, 0, "Yellow", LED_COLOR_YELLOW}, + {0, 180, 0, "Green", LED_COLOR_GREEN}, + {0, 0, 160, "Blue", LED_COLOR_BLUE}, +}; + +// LED blink task +static void led_blink_task(void *pvParameter) +{ + while (is_blinking) + { + if (blink_state) + { + // Turn on with current color + led_strip_set_pixel(led_strip, 0, current_red, current_green, current_blue); + led_strip_refresh(led_strip); + } + else + { + // Turn off + led_strip_set_pixel(led_strip, 0, 0, 0, 0); + led_strip_refresh(led_strip); + } + + blink_state = !blink_state; + vTaskDelay(pdMS_TO_TICKS(blink_rate_ms)); + } + + // Clean exit - restore solid color + led_strip_set_pixel(led_strip, 0, current_red, current_green, current_blue); + led_strip_refresh(led_strip); + + blink_task_handle = NULL; + vTaskDelete(NULL); +} + +esp_err_t led_manager_init(int gpio_pin) +{ + if (is_initialized) + { + ESP_LOGW(TAG, "LED manager already initialized"); + return ESP_OK; + } + + ESP_LOGI(TAG, "Initializing LED manager on GPIO %d", gpio_pin); + + // Configure GPIO + gpio_reset_pin(gpio_pin); + gpio_set_direction(gpio_pin, GPIO_MODE_OUTPUT); + gpio_set_level(gpio_pin, 0); + + // LED strip general configuration + led_strip_config_t strip_config = { + .strip_gpio_num = gpio_pin, + .max_leds = 1, + .led_model = LED_MODEL_WS2812, + .color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB, + .flags = { + .invert_out = false, + }}; + + // LED strip RMT configuration + led_strip_rmt_config_t rmt_config = { +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + .rmt_channel = 0, +#else + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 10 * 1000 * 1000, // 10MHz resolution + .mem_block_symbols = 64, +#endif + .flags = { + .with_dma = false, + }}; + + // Create the LED strip + esp_err_t ret = led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "Failed to create LED strip: %s", esp_err_to_name(ret)); + return ret; + } + + // Clear the LED strip + ret = led_strip_clear(led_strip); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "Failed to clear LED strip: %s", esp_err_to_name(ret)); + led_strip_del(led_strip); + led_strip = NULL; + return ret; + } + + is_initialized = true; + ESP_LOGI(TAG, "LED manager initialized successfully"); + return ESP_OK; +} + +esp_err_t led_manager_set_color_by_name(const char *color_name, bool blink) +{ + if (!is_initialized || led_strip == NULL) + { + ESP_LOGE(TAG, "LED manager not initialized"); + return ESP_ERR_INVALID_STATE; + } + + if (color_name == NULL) + { + ESP_LOGE(TAG, "Color name is NULL"); + return ESP_ERR_INVALID_ARG; + } + + int color_index = -1; + + // Find the color index by name + for (int i = 0; i < sizeof(rgb_colors) / sizeof(rgb_colors[0]); i++) + { + if (strcmp(rgb_colors[i].name, color_name) == 0) + { + color_index = i; + break; + } + } + + // If color not found, default to first color (Red) + if (color_index == -1) + { + ESP_LOGW(TAG, "Color '%s' not found, defaulting to Red", color_name); + color_index = 0; + } + + // Stop any existing blinking + if (is_blinking) + { + led_manager_stop_blinking(); + } + + // Set the color + esp_err_t ret = led_manager_set_rgb(rgb_colors[color_index].red, + rgb_colors[color_index].green, + rgb_colors[color_index].blue); + if (ret != ESP_OK) + { + return ret; + } + + if (blink) + { + // Start blinking + is_blinking = true; + blink_state = true; + if (blink_task_handle == NULL) + { + xTaskCreate(led_blink_task, "led_blink", 2048, NULL, 5, &blink_task_handle); + } + } + + return ESP_OK; +} + +esp_err_t led_manager_set_color(led_color_t color, bool blink) +{ + if (!is_initialized || led_strip == NULL) + { + ESP_LOGE(TAG, "LED manager not initialized"); + return ESP_ERR_INVALID_STATE; + } + + // Find the color by enum value + for (int i = 0; i < sizeof(rgb_colors) / sizeof(rgb_colors[0]); i++) + { + if (rgb_colors[i].enum_val == color) + { + // Stop any existing blinking + if (is_blinking) + { + led_manager_stop_blinking(); + } + + esp_err_t ret = led_manager_set_rgb(rgb_colors[i].red, + rgb_colors[i].green, + rgb_colors[i].blue); + if (ret != ESP_OK) + { + return ret; + } + + if (blink) + { + // Start blinking + is_blinking = true; + blink_state = true; + if (blink_task_handle == NULL) + { + xTaskCreate(led_blink_task, "led_blink", 2048, NULL, 5, &blink_task_handle); + } + } + + return ESP_OK; + } + } + + ESP_LOGW(TAG, "Invalid color enum %d, defaulting to Red", color); + return led_manager_set_color(LED_COLOR_RED, blink); +} + +esp_err_t led_manager_set_rgb(uint8_t red, uint8_t green, uint8_t blue) +{ + if (!is_initialized || led_strip == NULL) + { + ESP_LOGE(TAG, "LED manager not initialized"); + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGD(TAG, "Setting LED color: R=%d, G=%d, B=%d", red, green, blue); + + // Set the LED pixel using RGB color + esp_err_t ret = led_strip_set_pixel(led_strip, 0, red, green, blue); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "Failed to set LED pixel: %s", esp_err_to_name(ret)); + return ret; + } + + // Refresh the strip to send data + ret = led_strip_refresh(led_strip); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "Failed to refresh LED strip: %s", esp_err_to_name(ret)); + return ret; + } + + // Store current color for blinking + current_red = red; + current_green = green; + current_blue = blue; + + return ESP_OK; +} + +esp_err_t led_manager_stop_blinking(void) +{ + if (is_blinking) + { + is_blinking = false; + // Task will clean itself up + vTaskDelay(pdMS_TO_TICKS(100)); // Allow task to finish + } + return ESP_OK; +} + +esp_err_t led_manager_set_blink_rate(uint32_t rate_ms) +{ + if (rate_ms < 50) + { + ESP_LOGW(TAG, "Blink rate too fast, minimum is 50ms"); + rate_ms = 50; + } + blink_rate_ms = rate_ms; + ESP_LOGI(TAG, "Blink rate set to %lu ms", rate_ms); + return ESP_OK; +} + +esp_err_t led_manager_clear(void) +{ + if (!is_initialized || led_strip == NULL) + { + ESP_LOGE(TAG, "LED manager not initialized"); + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGD(TAG, "Clearing LED"); + + esp_err_t ret = led_strip_clear(led_strip); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "Failed to clear LED strip: %s", esp_err_to_name(ret)); + return ret; + } + + return ESP_OK; +} + +esp_err_t led_manager_deinit(void) +{ + if (!is_initialized) + { + ESP_LOGW(TAG, "LED manager not initialized"); + return ESP_OK; + } + + ESP_LOGI(TAG, "Deinitializing LED manager"); + + // Stop blinking task if running + if (blink_task_handle != NULL) + { + is_blinking = false; + vTaskDelay(pdMS_TO_TICKS(100)); // Allow task to finish + if (blink_task_handle != NULL) + { + vTaskDelete(blink_task_handle); + blink_task_handle = NULL; + } + } + + if (led_strip != NULL) + { + led_strip_clear(led_strip); + esp_err_t ret = led_strip_del(led_strip); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "Failed to delete LED strip: %s", esp_err_to_name(ret)); + } + led_strip = NULL; + } + + is_initialized = false; + ESP_LOGI(TAG, "LED manager deinitialized"); + return ESP_OK; +} \ No newline at end of file diff --git a/led_manager.h b/led_manager.h new file mode 100644 index 0000000..b0f2927 --- /dev/null +++ b/led_manager.h @@ -0,0 +1,97 @@ +#ifndef LED_MANAGER_H +#define LED_MANAGER_H + +#include "led_strip.h" +#include "driver/gpio.h" +#include "esp_log.h" +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * @brief LED color names available for use + */ + typedef enum + { + LED_COLOR_RED, + LED_COLOR_ORANGE, + LED_COLOR_YELLOW, + LED_COLOR_GREEN, + LED_COLOR_BLUE + } led_color_t; + + /** + * @brief Initialize the LED manager with GPIO pin + * + * @param gpio_pin GPIO pin number where the LED strip is connected + * @return esp_err_t ESP_OK on success, error code on failure + */ + esp_err_t led_manager_init(int gpio_pin); + + /** + * @brief Set LED color using color name with optional blinking + * + * @param color_name String name of the color (e.g., "Red", "Green", "Blue") + * @param blink Enable blinking (true) or solid color (false) + * @return esp_err_t ESP_OK on success, error code on failure + */ + esp_err_t led_manager_set_color_by_name(const char *color_name, bool blink); + + /** + * @brief Set LED color using color enum with optional blinking + * + * @param color Color enum value + * @param blink Enable blinking (true) or solid color (false) + * @return esp_err_t ESP_OK on success, error code on failure + */ + esp_err_t led_manager_set_color(led_color_t color, bool blink); + + /** + * @brief Set LED color using RGB values + * + * @param red Red component (0-255) + * @param green Green component (0-255) + * @param blue Blue component (0-255) + * @return esp_err_t ESP_OK on success, error code on failure + */ + esp_err_t led_manager_set_rgb(uint8_t red, uint8_t green, uint8_t blue); + + /** + * @brief Clear/turn off the LED + * + * @return esp_err_t ESP_OK on success, error code on failure + */ + esp_err_t led_manager_clear(void); + + /** + * @brief Stop blinking and keep current color + * + * @return esp_err_t ESP_OK on success, error code on failure + */ + esp_err_t led_manager_stop_blinking(void); + + /** + * @brief Set blink rate + * + * @param rate_ms Blink rate in milliseconds (default: 500ms) + * @return esp_err_t ESP_OK on success, error code on failure + */ + esp_err_t led_manager_set_blink_rate(uint32_t rate_ms); + + /** + * @brief Deinitialize the LED manager and free resources + * + * @return esp_err_t ESP_OK on success, error code on failure + */ + esp_err_t led_manager_deinit(void); + +#ifdef __cplusplus +} +#endif + +#endif // LED_MANAGER_H \ No newline at end of file diff --git a/led_strip.h b/led_strip.h new file mode 100644 index 0000000..ba0a8ba --- /dev/null +++ b/led_strip.h @@ -0,0 +1,108 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" +#include "led_strip_rmt.h" +#include "led_strip_spi.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * @brief Set RGB for a specific pixel + * + * @param strip: LED strip + * @param index: index of pixel to set + * @param red: red part of color + * @param green: green part of color + * @param blue: blue part of color + * + * @return + * - ESP_OK: Set RGB for a specific pixel successfully + * - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters + * - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred + */ + esp_err_t led_strip_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue); + + /** + * @brief Set RGBW for a specific pixel + * + * @note Only call this function if your led strip does have the white component (e.g. SK6812-RGBW) + * @note Also see `led_strip_set_pixel` if you only want to specify the RGB part of the color and bypass the white component + * + * @param strip: LED strip + * @param index: index of pixel to set + * @param red: red part of color + * @param green: green part of color + * @param blue: blue part of color + * @param white: separate white component + * + * @return + * - ESP_OK: Set RGBW color for a specific pixel successfully + * - ESP_ERR_INVALID_ARG: Set RGBW color for a specific pixel failed because of an invalid argument + * - ESP_FAIL: Set RGBW color for a specific pixel failed because other error occurred + */ + esp_err_t led_strip_set_pixel_rgbw(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white); + + /** + * @brief Set HSV for a specific pixel + * + * @param strip: LED strip + * @param index: index of pixel to set + * @param hue: hue part of color (0 - 360) + * @param saturation: saturation part of color (0 - 255, rescaled from 0 - 1. e.g. saturation = 0.5, rescaled to 127) + * @param value: value part of color (0 - 255, rescaled from 0 - 1. e.g. value = 0.5, rescaled to 127) + * + * @return + * - ESP_OK: Set HSV color for a specific pixel successfully + * - ESP_ERR_INVALID_ARG: Set HSV color for a specific pixel failed because of an invalid argument + * - ESP_FAIL: Set HSV color for a specific pixel failed because other error occurred + */ + esp_err_t led_strip_set_pixel_hsv(led_strip_handle_t strip, uint32_t index, uint16_t hue, uint8_t saturation, uint8_t value); + + /** + * @brief Refresh memory colors to LEDs + * + * @param strip: LED strip + * + * @return + * - ESP_OK: Refresh successfully + * - ESP_FAIL: Refresh failed because some other error occurred + * + * @note: + * After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip. + */ + esp_err_t led_strip_refresh(led_strip_handle_t strip); + + /** + * @brief Clear LED strip (turn off all LEDs) + * + * @param strip: LED strip + * + * @return + * - ESP_OK: Clear LEDs successfully + * - ESP_FAIL: Clear LEDs failed because some other error occurred + */ + esp_err_t led_strip_clear(led_strip_handle_t strip); + + /** + * @brief Free LED strip resources + * + * @param strip: LED strip + * + * @return + * - ESP_OK: Free resources successfully + * - ESP_FAIL: Free resources failed because error occurred + */ + esp_err_t led_strip_del(led_strip_handle_t strip); + +#ifdef __cplusplus +} +#endif diff --git a/led_strip_api.c b/led_strip_api.c new file mode 100644 index 0000000..6eb86b8 --- /dev/null +++ b/led_strip_api.c @@ -0,0 +1,94 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "esp_log.h" +#include "esp_check.h" +#include "led_strip.h" +#include "led_strip_interface.h" + +static const char *TAG = "led_strip"; + +esp_err_t led_strip_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue) +{ + ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return strip->set_pixel(strip, index, red, green, blue); +} + +esp_err_t led_strip_set_pixel_hsv(led_strip_handle_t strip, uint32_t index, uint16_t hue, uint8_t saturation, uint8_t value) +{ + ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + uint32_t red = 0; + uint32_t green = 0; + uint32_t blue = 0; + + uint32_t rgb_max = value; + uint32_t rgb_min = rgb_max * (255 - saturation) / 255.0f; + + uint32_t i = hue / 60; + uint32_t diff = hue % 60; + + // RGB adjustment amount by hue + uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60; + + switch (i) { + case 0: + red = rgb_max; + green = rgb_min + rgb_adj; + blue = rgb_min; + break; + case 1: + red = rgb_max - rgb_adj; + green = rgb_max; + blue = rgb_min; + break; + case 2: + red = rgb_min; + green = rgb_max; + blue = rgb_min + rgb_adj; + break; + case 3: + red = rgb_min; + green = rgb_max - rgb_adj; + blue = rgb_max; + break; + case 4: + red = rgb_min + rgb_adj; + green = rgb_min; + blue = rgb_max; + break; + default: + red = rgb_max; + green = rgb_min; + blue = rgb_max - rgb_adj; + break; + } + + return strip->set_pixel(strip, index, red, green, blue); +} + +esp_err_t led_strip_set_pixel_rgbw(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white) +{ + ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return strip->set_pixel_rgbw(strip, index, red, green, blue, white); +} + +esp_err_t led_strip_refresh(led_strip_handle_t strip) +{ + ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return strip->refresh(strip); +} + +esp_err_t led_strip_clear(led_strip_handle_t strip) +{ + ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return strip->clear(strip); +} + +esp_err_t led_strip_del(led_strip_handle_t strip) +{ + ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return strip->del(strip); +} diff --git a/led_strip_interface.h b/led_strip_interface.h new file mode 100644 index 0000000..3de4c27 --- /dev/null +++ b/led_strip_interface.h @@ -0,0 +1,95 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct led_strip_t led_strip_t; /*!< Type of LED strip */ + +/** + * @brief LED strip interface definition + */ +struct led_strip_t { + /** + * @brief Set RGB for a specific pixel + * + * @param strip: LED strip + * @param index: index of pixel to set + * @param red: red part of color + * @param green: green part of color + * @param blue: blue part of color + * + * @return + * - ESP_OK: Set RGB for a specific pixel successfully + * - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters + * - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred + */ + esp_err_t (*set_pixel)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue); + + /** + * @brief Set RGBW for a specific pixel. Similar to `set_pixel` but also set the white component + * + * @param strip: LED strip + * @param index: index of pixel to set + * @param red: red part of color + * @param green: green part of color + * @param blue: blue part of color + * @param white: separate white component + * + * @return + * - ESP_OK: Set RGBW color for a specific pixel successfully + * - ESP_ERR_INVALID_ARG: Set RGBW color for a specific pixel failed because of an invalid argument + * - ESP_FAIL: Set RGBW color for a specific pixel failed because other error occurred + */ + esp_err_t (*set_pixel_rgbw)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white); + + /** + * @brief Refresh memory colors to LEDs + * + * @param strip: LED strip + * @param timeout_ms: timeout value for refreshing task + * + * @return + * - ESP_OK: Refresh successfully + * - ESP_FAIL: Refresh failed because some other error occurred + * + * @note: + * After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip. + */ + esp_err_t (*refresh)(led_strip_t *strip); + + /** + * @brief Clear LED strip (turn off all LEDs) + * + * @param strip: LED strip + * @param timeout_ms: timeout value for clearing task + * + * @return + * - ESP_OK: Clear LEDs successfully + * - ESP_FAIL: Clear LEDs failed because some other error occurred + */ + esp_err_t (*clear)(led_strip_t *strip); + + /** + * @brief Free LED strip resources + * + * @param strip: LED strip + * + * @return + * - ESP_OK: Free resources successfully + * - ESP_FAIL: Free resources failed because error occurred + */ + esp_err_t (*del)(led_strip_t *strip); +}; + +#ifdef __cplusplus +} +#endif diff --git a/led_strip_rmt.h b/led_strip_rmt.h new file mode 100644 index 0000000..3b58276 --- /dev/null +++ b/led_strip_rmt.h @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" +#include "led_strip_types.h" +#include "esp_idf_version.h" +#include "driver/rmt_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LED Strip RMT specific configuration + */ +typedef struct { + rmt_clock_source_t clk_src; /*!< RMT clock source */ + uint32_t resolution_hz; /*!< RMT tick resolution, if set to zero, a default resolution (10MHz) will be applied */ + size_t mem_block_symbols; /*!< How many RMT symbols can one RMT channel hold at one time. Set to 0 will fallback to use the default size. */ + /*!< Extra RMT specific driver flags */ + struct led_strip_rmt_extra_config { + uint32_t with_dma: 1; /*!< Use DMA to transmit data */ + } flags; /*!< Extra driver flags */ +} led_strip_rmt_config_t; + +/** + * @brief Create LED strip based on RMT TX channel + * + * @param led_config LED strip configuration + * @param rmt_config RMT specific configuration + * @param ret_strip Returned LED strip handle + * @return + * - ESP_OK: create LED strip handle successfully + * - ESP_ERR_INVALID_ARG: create LED strip handle failed because of invalid argument + * - ESP_ERR_NO_MEM: create LED strip handle failed because of out of memory + * - ESP_FAIL: create LED strip handle failed because some other error + */ +esp_err_t led_strip_new_rmt_device(const led_strip_config_t *led_config, const led_strip_rmt_config_t *rmt_config, led_strip_handle_t *ret_strip); + +#ifdef __cplusplus +} +#endif diff --git a/led_strip_rmt_dev.c b/led_strip_rmt_dev.c new file mode 100644 index 0000000..4a38f4e --- /dev/null +++ b/led_strip_rmt_dev.c @@ -0,0 +1,184 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "esp_log.h" +#include "esp_check.h" +#include "driver/rmt_tx.h" +#include "led_strip.h" +#include "led_strip_interface.h" +#include "led_strip_rmt_encoder.h" + +#define LED_STRIP_RMT_DEFAULT_RESOLUTION 10000000 // 10MHz resolution +#define LED_STRIP_RMT_DEFAULT_TRANS_QUEUE_SIZE 4 +// the memory size of each RMT channel, in words (4 bytes) +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 +#define LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 64 +#else +#define LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS 48 +#endif + +static const char *TAG = "led_strip_rmt"; + +typedef struct { + led_strip_t base; + rmt_channel_handle_t rmt_chan; + rmt_encoder_handle_t strip_encoder; + uint32_t strip_len; + uint8_t bytes_per_pixel; + led_color_component_format_t component_fmt; + uint8_t pixel_buf[]; +} led_strip_rmt_obj; + +static esp_err_t led_strip_rmt_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue) +{ + led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base); + ESP_RETURN_ON_FALSE(index < rmt_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs"); + + led_color_component_format_t component_fmt = rmt_strip->component_fmt; + uint32_t start = index * rmt_strip->bytes_per_pixel; + uint8_t *pixel_buf = rmt_strip->pixel_buf; + + pixel_buf[start + component_fmt.format.r_pos] = red & 0xFF; + pixel_buf[start + component_fmt.format.g_pos] = green & 0xFF; + pixel_buf[start + component_fmt.format.b_pos] = blue & 0xFF; + if (component_fmt.format.num_components > 3) { + pixel_buf[start + component_fmt.format.w_pos] = 0; + } + + return ESP_OK; +} + +static esp_err_t led_strip_rmt_set_pixel_rgbw(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white) +{ + led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base); + led_color_component_format_t component_fmt = rmt_strip->component_fmt; + ESP_RETURN_ON_FALSE(index < rmt_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs"); + ESP_RETURN_ON_FALSE(component_fmt.format.num_components == 4, ESP_ERR_INVALID_ARG, TAG, "led doesn't have 4 components"); + + uint32_t start = index * rmt_strip->bytes_per_pixel; + uint8_t *pixel_buf = rmt_strip->pixel_buf; + + pixel_buf[start + component_fmt.format.r_pos] = red & 0xFF; + pixel_buf[start + component_fmt.format.g_pos] = green & 0xFF; + pixel_buf[start + component_fmt.format.b_pos] = blue & 0xFF; + pixel_buf[start + component_fmt.format.w_pos] = white & 0xFF; + + return ESP_OK; +} + +static esp_err_t led_strip_rmt_refresh(led_strip_t *strip) +{ + led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base); + rmt_transmit_config_t tx_conf = { + .loop_count = 0, + }; + + ESP_RETURN_ON_ERROR(rmt_enable(rmt_strip->rmt_chan), TAG, "enable RMT channel failed"); + ESP_RETURN_ON_ERROR(rmt_transmit(rmt_strip->rmt_chan, rmt_strip->strip_encoder, rmt_strip->pixel_buf, + rmt_strip->strip_len * rmt_strip->bytes_per_pixel, &tx_conf), TAG, "transmit pixels by RMT failed"); + ESP_RETURN_ON_ERROR(rmt_tx_wait_all_done(rmt_strip->rmt_chan, -1), TAG, "flush RMT channel failed"); + ESP_RETURN_ON_ERROR(rmt_disable(rmt_strip->rmt_chan), TAG, "disable RMT channel failed"); + return ESP_OK; +} + +static esp_err_t led_strip_rmt_clear(led_strip_t *strip) +{ + led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base); + // Write zero to turn off all leds + memset(rmt_strip->pixel_buf, 0, rmt_strip->strip_len * rmt_strip->bytes_per_pixel); + return led_strip_rmt_refresh(strip); +} + +static esp_err_t led_strip_rmt_del(led_strip_t *strip) +{ + led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base); + ESP_RETURN_ON_ERROR(rmt_del_channel(rmt_strip->rmt_chan), TAG, "delete RMT channel failed"); + ESP_RETURN_ON_ERROR(rmt_del_encoder(rmt_strip->strip_encoder), TAG, "delete strip encoder failed"); + free(rmt_strip); + return ESP_OK; +} + +esp_err_t led_strip_new_rmt_device(const led_strip_config_t *led_config, const led_strip_rmt_config_t *rmt_config, led_strip_handle_t *ret_strip) +{ + led_strip_rmt_obj *rmt_strip = NULL; + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(led_config && rmt_config && ret_strip, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + led_color_component_format_t component_fmt = led_config->color_component_format; + // If R/G/B order is not specified, set default GRB order as fallback + if (component_fmt.format_id == 0) { + component_fmt = LED_STRIP_COLOR_COMPONENT_FMT_GRB; + } + // check the validation of the color component format + uint8_t mask = 0; + if (component_fmt.format.num_components == 3) { + mask = BIT(component_fmt.format.r_pos) | BIT(component_fmt.format.g_pos) | BIT(component_fmt.format.b_pos); + // Check for invalid values + ESP_RETURN_ON_FALSE(mask == 0x07, ESP_ERR_INVALID_ARG, TAG, "invalid order argument"); + } else if (component_fmt.format.num_components == 4) { + mask = BIT(component_fmt.format.r_pos) | BIT(component_fmt.format.g_pos) | BIT(component_fmt.format.b_pos) | BIT(component_fmt.format.w_pos); + // Check for invalid values + ESP_RETURN_ON_FALSE(mask == 0x0F, ESP_ERR_INVALID_ARG, TAG, "invalid order argument"); + } else { + ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "invalid number of color components: %d", component_fmt.format.num_components); + } + // TODO: we assume each color component is 8 bits, may need to support other configurations in the future, e.g. 10bits per color component? + uint8_t bytes_per_pixel = component_fmt.format.num_components; + rmt_strip = calloc(1, sizeof(led_strip_rmt_obj) + led_config->max_leds * bytes_per_pixel); + ESP_GOTO_ON_FALSE(rmt_strip, ESP_ERR_NO_MEM, err, TAG, "no mem for rmt strip"); + uint32_t resolution = rmt_config->resolution_hz ? rmt_config->resolution_hz : LED_STRIP_RMT_DEFAULT_RESOLUTION; + + // for backward compatibility, if the user does not set the clk_src, use the default value + rmt_clock_source_t clk_src = RMT_CLK_SRC_DEFAULT; + if (rmt_config->clk_src) { + clk_src = rmt_config->clk_src; + } + size_t mem_block_symbols = LED_STRIP_RMT_DEFAULT_MEM_BLOCK_SYMBOLS; + // override the default value if the user sets it + if (rmt_config->mem_block_symbols) { + mem_block_symbols = rmt_config->mem_block_symbols; + } + rmt_tx_channel_config_t rmt_chan_config = { + .clk_src = clk_src, + .gpio_num = led_config->strip_gpio_num, + .mem_block_symbols = mem_block_symbols, + .resolution_hz = resolution, + .trans_queue_depth = LED_STRIP_RMT_DEFAULT_TRANS_QUEUE_SIZE, + .flags.with_dma = rmt_config->flags.with_dma, + .flags.invert_out = led_config->flags.invert_out, + }; + ESP_GOTO_ON_ERROR(rmt_new_tx_channel(&rmt_chan_config, &rmt_strip->rmt_chan), err, TAG, "create RMT TX channel failed"); + + led_strip_encoder_config_t strip_encoder_conf = { + .resolution = resolution, + .led_model = led_config->led_model + }; + ESP_GOTO_ON_ERROR(rmt_new_led_strip_encoder(&strip_encoder_conf, &rmt_strip->strip_encoder), err, TAG, "create LED strip encoder failed"); + + rmt_strip->component_fmt = component_fmt; + rmt_strip->bytes_per_pixel = bytes_per_pixel; + rmt_strip->strip_len = led_config->max_leds; + rmt_strip->base.set_pixel = led_strip_rmt_set_pixel; + rmt_strip->base.set_pixel_rgbw = led_strip_rmt_set_pixel_rgbw; + rmt_strip->base.refresh = led_strip_rmt_refresh; + rmt_strip->base.clear = led_strip_rmt_clear; + rmt_strip->base.del = led_strip_rmt_del; + + *ret_strip = &rmt_strip->base; + return ESP_OK; +err: + if (rmt_strip) { + if (rmt_strip->rmt_chan) { + rmt_del_channel(rmt_strip->rmt_chan); + } + if (rmt_strip->strip_encoder) { + rmt_del_encoder(rmt_strip->strip_encoder); + } + free(rmt_strip); + } + return ret; +} diff --git a/led_strip_rmt_encoder.c b/led_strip_rmt_encoder.c new file mode 100644 index 0000000..2e6f570 --- /dev/null +++ b/led_strip_rmt_encoder.c @@ -0,0 +1,146 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_check.h" +#include "led_strip_rmt_encoder.h" + +static const char *TAG = "led_rmt_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 = 0; + rmt_encode_state_t state = 0; + 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 = 0; // 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 = 0; + 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"); + ESP_GOTO_ON_FALSE(config->led_model < LED_MODEL_INVALID, ESP_ERR_INVALID_ARG, err, TAG, "invalid led model"); + led_encoder = calloc(1, 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; + rmt_bytes_encoder_config_t bytes_encoder_config; + if (config->led_model == LED_MODEL_SK6812) { + bytes_encoder_config = (rmt_bytes_encoder_config_t) { + .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.6 * config->resolution / 1000000, // T1H=0.6us + .level1 = 0, + .duration1 = 0.6 * config->resolution / 1000000, // T1L=0.6us + }, + .flags.msb_first = 1 // SK6812 transfer bit order: G7...G0R7...R0B7...B0(W7...W0) + }; + } else if (config->led_model == LED_MODEL_WS2812) { + // different led strip might have its own timing requirements, following parameter is for WS2812 + bytes_encoder_config = (rmt_bytes_encoder_config_t) { + .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 + }; + } else { + assert(false); + } + 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 * 280 / 2; // reset code duration defaults to 280us to accomodate WS2812B-V5 + 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/led_strip_rmt_encoder.h b/led_strip_rmt_encoder.h new file mode 100644 index 0000000..ba71e60 --- /dev/null +++ b/led_strip_rmt_encoder.h @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "driver/rmt_encoder.h" +#include "led_strip_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of led strip encoder configuration + */ +typedef struct { + uint32_t resolution; /*!< Encoder resolution, in Hz */ + led_model_t led_model; /*!< LED model */ +} 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/led_strip_spi.h b/led_strip_spi.h new file mode 100644 index 0000000..cd66e7a --- /dev/null +++ b/led_strip_spi.h @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" +#include "driver/spi_master.h" +#include "led_strip_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LED Strip SPI specific configuration + */ +typedef struct { + spi_clock_source_t clk_src; /*!< SPI clock source */ + spi_host_device_t spi_bus; /*!< SPI bus ID. Which buses are available depends on the specific chip */ + struct { + uint32_t with_dma: 1; /*!< Use DMA to transmit data */ + } flags; /*!< Extra driver flags */ +} led_strip_spi_config_t; + +/** + * @brief Create LED strip based on SPI MOSI channel + * + * @note Although only the MOSI line is used for generating the signal, the whole SPI bus can't be used for other purposes. + * + * @param led_config LED strip configuration + * @param spi_config SPI specific configuration + * @param ret_strip Returned LED strip handle + * @return + * - ESP_OK: create LED strip handle successfully + * - ESP_ERR_INVALID_ARG: create LED strip handle failed because of invalid argument + * - ESP_ERR_NOT_SUPPORTED: create LED strip handle failed because of unsupported configuration + * - ESP_ERR_NO_MEM: create LED strip handle failed because of out of memory + * - ESP_FAIL: create LED strip handle failed because some other error + */ +esp_err_t led_strip_new_spi_device(const led_strip_config_t *led_config, const led_strip_spi_config_t *spi_config, led_strip_handle_t *ret_strip); + +#ifdef __cplusplus +} +#endif diff --git a/led_strip_spi_dev.c b/led_strip_spi_dev.c new file mode 100644 index 0000000..81e0259 --- /dev/null +++ b/led_strip_spi_dev.c @@ -0,0 +1,230 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "esp_log.h" +#include "esp_check.h" +#include "esp_rom_gpio.h" +#include "soc/spi_periph.h" +#include "led_strip.h" +#include "led_strip_interface.h" + +#define LED_STRIP_SPI_DEFAULT_RESOLUTION (2.5 * 1000 * 1000) // 2.5MHz resolution +#define LED_STRIP_SPI_DEFAULT_TRANS_QUEUE_SIZE 4 + +#define SPI_BYTES_PER_COLOR_BYTE 3 +#define SPI_BITS_PER_COLOR_BYTE (SPI_BYTES_PER_COLOR_BYTE * 8) + +static const char *TAG = "led_strip_spi"; + +typedef struct { + led_strip_t base; + spi_host_device_t spi_host; + spi_device_handle_t spi_device; + uint32_t strip_len; + uint8_t bytes_per_pixel; + led_color_component_format_t component_fmt; + uint8_t pixel_buf[]; +} led_strip_spi_obj; + +// please make sure to zero-initialize the buf before calling this function +static void __led_strip_spi_bit(uint8_t data, uint8_t *buf) +{ + // Each color of 1 bit is represented by 3 bits of SPI, low_level:100 ,high_level:110 + // So a color byte occupies 3 bytes of SPI. + *(buf + 2) |= data & BIT(0) ? BIT(2) | BIT(1) : BIT(2); + *(buf + 2) |= data & BIT(1) ? BIT(5) | BIT(4) : BIT(5); + *(buf + 2) |= data & BIT(2) ? BIT(7) : 0x00; + *(buf + 1) |= BIT(0); + *(buf + 1) |= data & BIT(3) ? BIT(3) | BIT(2) : BIT(3); + *(buf + 1) |= data & BIT(4) ? BIT(6) | BIT(5) : BIT(6); + *(buf + 0) |= data & BIT(5) ? BIT(1) | BIT(0) : BIT(1); + *(buf + 0) |= data & BIT(6) ? BIT(4) | BIT(3) : BIT(4); + *(buf + 0) |= data & BIT(7) ? BIT(7) | BIT(6) : BIT(7); +} + +static esp_err_t led_strip_spi_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue) +{ + led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base); + ESP_RETURN_ON_FALSE(index < spi_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs"); + // 3 pixels take 72bits(9bytes) + uint32_t start = index * spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE; + uint8_t *pixel_buf = spi_strip->pixel_buf; + led_color_component_format_t component_fmt = spi_strip->component_fmt; + memset(pixel_buf + start, 0, spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE); + + __led_strip_spi_bit(red, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * component_fmt.format.r_pos]); + __led_strip_spi_bit(green, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * component_fmt.format.g_pos]); + __led_strip_spi_bit(blue, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * component_fmt.format.b_pos]); + if (component_fmt.format.num_components > 3) { + __led_strip_spi_bit(0, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * component_fmt.format.w_pos]); + } + + return ESP_OK; +} + +static esp_err_t led_strip_spi_set_pixel_rgbw(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue, uint32_t white) +{ + led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base); + led_color_component_format_t component_fmt = spi_strip->component_fmt; + ESP_RETURN_ON_FALSE(index < spi_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs"); + ESP_RETURN_ON_FALSE(component_fmt.format.num_components == 4, ESP_ERR_INVALID_ARG, TAG, "led doesn't have 4 components"); + + // LED_PIXEL_FORMAT_GRBW takes 96bits(12bytes) + uint32_t start = index * spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE; + uint8_t *pixel_buf = spi_strip->pixel_buf; + memset(pixel_buf + start, 0, spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE); + + __led_strip_spi_bit(red, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * component_fmt.format.r_pos]); + __led_strip_spi_bit(green, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * component_fmt.format.g_pos]); + __led_strip_spi_bit(blue, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * component_fmt.format.b_pos]); + __led_strip_spi_bit(white, &pixel_buf[start + SPI_BYTES_PER_COLOR_BYTE * component_fmt.format.w_pos]); + + return ESP_OK; +} + +static esp_err_t led_strip_spi_refresh(led_strip_t *strip) +{ + led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base); + spi_transaction_t tx_conf; + memset(&tx_conf, 0, sizeof(tx_conf)); + + tx_conf.length = spi_strip->strip_len * spi_strip->bytes_per_pixel * SPI_BITS_PER_COLOR_BYTE; + tx_conf.tx_buffer = spi_strip->pixel_buf; + tx_conf.rx_buffer = NULL; + ESP_RETURN_ON_ERROR(spi_device_transmit(spi_strip->spi_device, &tx_conf), TAG, "transmit pixels by SPI failed"); + + return ESP_OK; +} + +static esp_err_t led_strip_spi_clear(led_strip_t *strip) +{ + led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base); + //Write zero to turn off all leds + memset(spi_strip->pixel_buf, 0, spi_strip->strip_len * spi_strip->bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE); + uint8_t *buf = spi_strip->pixel_buf; + for (int index = 0; index < spi_strip->strip_len * spi_strip->bytes_per_pixel; index++) { + __led_strip_spi_bit(0, buf); + buf += SPI_BYTES_PER_COLOR_BYTE; + } + + return led_strip_spi_refresh(strip); +} + +static esp_err_t led_strip_spi_del(led_strip_t *strip) +{ + led_strip_spi_obj *spi_strip = __containerof(strip, led_strip_spi_obj, base); + + ESP_RETURN_ON_ERROR(spi_bus_remove_device(spi_strip->spi_device), TAG, "delete spi device failed"); + ESP_RETURN_ON_ERROR(spi_bus_free(spi_strip->spi_host), TAG, "free spi bus failed"); + + free(spi_strip); + return ESP_OK; +} + +esp_err_t led_strip_new_spi_device(const led_strip_config_t *led_config, const led_strip_spi_config_t *spi_config, led_strip_handle_t *ret_strip) +{ + led_strip_spi_obj *spi_strip = NULL; + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(led_config && spi_config && ret_strip, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + led_color_component_format_t component_fmt = led_config->color_component_format; + // If R/G/B order is not specified, set default GRB order as fallback + if (component_fmt.format_id == 0) { + component_fmt = LED_STRIP_COLOR_COMPONENT_FMT_GRB; + } + // check the validation of the color component format + uint8_t mask = 0; + if (component_fmt.format.num_components == 3) { + mask = BIT(component_fmt.format.r_pos) | BIT(component_fmt.format.g_pos) | BIT(component_fmt.format.b_pos); + // Check for invalid values + ESP_RETURN_ON_FALSE(mask == 0x07, ESP_ERR_INVALID_ARG, TAG, "invalid order argument"); + } else if (component_fmt.format.num_components == 4) { + mask = BIT(component_fmt.format.r_pos) | BIT(component_fmt.format.g_pos) | BIT(component_fmt.format.b_pos) | BIT(component_fmt.format.w_pos); + // Check for invalid values + ESP_RETURN_ON_FALSE(mask == 0x0F, ESP_ERR_INVALID_ARG, TAG, "invalid order argument"); + } else { + ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "invalid number of color components: %d", component_fmt.format.num_components); + } + // TODO: we assume each color component is 8 bits, may need to support other configurations in the future, e.g. 10bits per color component? + uint8_t bytes_per_pixel = component_fmt.format.num_components; + uint32_t mem_caps = MALLOC_CAP_DEFAULT; + if (spi_config->flags.with_dma) { + // DMA buffer must be placed in internal SRAM + mem_caps |= MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA; + } + spi_strip = heap_caps_calloc(1, sizeof(led_strip_spi_obj) + led_config->max_leds * bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE, mem_caps); + + ESP_GOTO_ON_FALSE(spi_strip, ESP_ERR_NO_MEM, err, TAG, "no mem for spi strip"); + + spi_strip->spi_host = spi_config->spi_bus; + // for backward compatibility, if the user does not set the clk_src, use the default value + spi_clock_source_t clk_src = SPI_CLK_SRC_DEFAULT; + if (spi_config->clk_src) { + clk_src = spi_config->clk_src; + } + + spi_bus_config_t spi_bus_cfg = { + .mosi_io_num = led_config->strip_gpio_num, + //Only use MOSI to generate the signal, set -1 when other pins are not used. + .miso_io_num = -1, + .sclk_io_num = -1, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .max_transfer_sz = led_config->max_leds * bytes_per_pixel * SPI_BYTES_PER_COLOR_BYTE, + }; + ESP_GOTO_ON_ERROR(spi_bus_initialize(spi_strip->spi_host, &spi_bus_cfg, spi_config->flags.with_dma ? SPI_DMA_CH_AUTO : SPI_DMA_DISABLED), err, TAG, "create SPI bus failed"); + + if (led_config->flags.invert_out == true) { + esp_rom_gpio_connect_out_signal(led_config->strip_gpio_num, spi_periph_signal[spi_strip->spi_host].spid_out, true, false); + } + + spi_device_interface_config_t spi_dev_cfg = { + .clock_source = clk_src, + .command_bits = 0, + .address_bits = 0, + .dummy_bits = 0, + .clock_speed_hz = LED_STRIP_SPI_DEFAULT_RESOLUTION, + .mode = 0, + //set -1 when CS is not used + .spics_io_num = -1, + .queue_size = LED_STRIP_SPI_DEFAULT_TRANS_QUEUE_SIZE, + }; + + ESP_GOTO_ON_ERROR(spi_bus_add_device(spi_strip->spi_host, &spi_dev_cfg, &spi_strip->spi_device), err, TAG, "Failed to add spi device"); + //ensure the reset time is enough + esp_rom_delay_us(10); + int clock_resolution_khz = 0; + spi_device_get_actual_freq(spi_strip->spi_device, &clock_resolution_khz); + // TODO: ideally we should decide the SPI_BYTES_PER_COLOR_BYTE by the real clock resolution + // But now, let's fixed the resolution, the downside is, we don't support a clock source whose frequency is not multiple of LED_STRIP_SPI_DEFAULT_RESOLUTION + // clock_resolution between 2.2MHz to 2.8MHz is supported + ESP_GOTO_ON_FALSE((clock_resolution_khz < LED_STRIP_SPI_DEFAULT_RESOLUTION / 1000 + 300) && (clock_resolution_khz > LED_STRIP_SPI_DEFAULT_RESOLUTION / 1000 - 300), ESP_ERR_NOT_SUPPORTED, err, + TAG, "unsupported clock resolution:%dKHz", clock_resolution_khz); + + spi_strip->component_fmt = component_fmt; + spi_strip->bytes_per_pixel = bytes_per_pixel; + spi_strip->strip_len = led_config->max_leds; + spi_strip->base.set_pixel = led_strip_spi_set_pixel; + spi_strip->base.set_pixel_rgbw = led_strip_spi_set_pixel_rgbw; + spi_strip->base.refresh = led_strip_spi_refresh; + spi_strip->base.clear = led_strip_spi_clear; + spi_strip->base.del = led_strip_spi_del; + + *ret_strip = &spi_strip->base; + return ESP_OK; +err: + if (spi_strip) { + if (spi_strip->spi_device) { + spi_bus_remove_device(spi_strip->spi_device); + } + if (spi_strip->spi_host) { + spi_bus_free(spi_strip->spi_host); + } + free(spi_strip); + } + return ret; +} diff --git a/led_strip_types.h b/led_strip_types.h new file mode 100644 index 0000000..3c290d5 --- /dev/null +++ b/led_strip_types.h @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of LED strip handle + */ +typedef struct led_strip_t *led_strip_handle_t; + +/** + * @brief LED strip model + * @note Different led model may have different timing parameters, so we need to distinguish them. + */ +typedef enum { + LED_MODEL_WS2812, /*!< LED strip model: WS2812 */ + LED_MODEL_SK6812, /*!< LED strip model: SK6812 */ + LED_MODEL_INVALID /*!< Invalid LED strip model */ +} led_model_t; + +/** + * @brief LED color component format + * @note The format is used to specify the order of color components in each pixel, also the number of color components. + */ +typedef union { + struct format_layout { + uint32_t r_pos: 2; /*!< Position of the red channel in the color order: 0~3 */ + uint32_t g_pos: 2; /*!< Position of the green channel in the color order: 0~3 */ + uint32_t b_pos: 2; /*!< Position of the blue channel in the color order: 0~3 */ + uint32_t w_pos: 2; /*!< Position of the white channel in the color order: 0~3 */ + uint32_t reserved: 21; /*!< Reserved */ + uint32_t num_components: 3; /*!< Number of color components per pixel: 3 or 4. If set to 0, it will fallback to 3 */ + } format; /*!< Format layout */ + uint32_t format_id; /*!< Format ID */ +} led_color_component_format_t; + +/// Helper macros to set the color component format +#define LED_STRIP_COLOR_COMPONENT_FMT_GRB (led_color_component_format_t){.format = {.r_pos = 1, .g_pos = 0, .b_pos = 2, .w_pos = 3, .reserved = 0, .num_components = 3}} +#define LED_STRIP_COLOR_COMPONENT_FMT_GRBW (led_color_component_format_t){.format = {.r_pos = 1, .g_pos = 0, .b_pos = 2, .w_pos = 3, .reserved = 0, .num_components = 4}} +#define LED_STRIP_COLOR_COMPONENT_FMT_RGB (led_color_component_format_t){.format = {.r_pos = 0, .g_pos = 1, .b_pos = 2, .w_pos = 3, .reserved = 0, .num_components = 3}} +#define LED_STRIP_COLOR_COMPONENT_FMT_RGBW (led_color_component_format_t){.format = {.r_pos = 0, .g_pos = 1, .b_pos = 2, .w_pos = 3, .reserved = 0, .num_components = 4}} + +/** + * @brief LED Strip common configurations + * The common configurations are not specific to any backend peripheral. + */ +typedef struct { + int strip_gpio_num; /*!< GPIO number that used by LED strip */ + uint32_t max_leds; /*!< Maximum number of LEDs that can be controlled in a single strip */ + led_model_t led_model; /*!< Specifies the LED strip model (e.g., WS2812, SK6812) */ + led_color_component_format_t color_component_format; /*!< Specifies the order of color components in each pixel. + Use helper macros like `LED_STRIP_COLOR_COMPONENT_FMT_GRB` to set the format */ + /*!< LED strip extra driver flags */ + struct led_strip_extra_flags { + uint32_t invert_out: 1; /*!< Invert output signal */ + } flags; /*!< Extra driver flags */ +} led_strip_config_t; + +#ifdef __cplusplus +} +#endif diff --git a/library.json b/library.json new file mode 100644 index 0000000..02a226f --- /dev/null +++ b/library.json @@ -0,0 +1,16 @@ +{ + "name": "led_manager", + "version": "1.0.0", + "description": "Complete LED control library with high-level management and low-level strip drivers for ESP32", + "keywords": "led, ws2812, neopixel, rgb, esp32, status, blink, strip, rmt, spi, driver", + "authors": { + "name": "ESP32C6-DHT22 Project" + }, + "license": "MIT", + "frameworks": [ + "espidf" + ], + "platforms": [ + "espressif32" + ] +} \ No newline at end of file