diff --git a/nifs/atomvm_neopixel.c b/nifs/atomvm_neopixel.c index 08f8460b..cc98cdf2 100644 --- a/nifs/atomvm_neopixel.c +++ b/nifs/atomvm_neopixel.c @@ -19,7 +19,6 @@ #include #include -#include #include #include #include @@ -31,18 +30,8 @@ #include "trace.h" #define TAG "atomvm_neopixel" -#define NO_ALLOC_FLAGS 0 -// References -// https://docs.espressif.com/projects/esp-idf/en/v3.3.4/api-reference/peripherals/rmt.html -// - -static const char *const led_strip_atom = "\x9" "led_strip"; -static const char *const channel_0_atom = "\x9" "channel_0"; -static const char *const channel_1_atom = "\x9" "channel_1"; -static const char *const channel_2_atom = "\x9" "channel_2"; -static const char *const channel_3_atom = "\x9" "channel_3"; -// 123456789ABCDEF01 +static const char *const led_strip_atom = "\x9" "led_strip"; static inline term ptr_to_binary(void *ptr, Context* ctx) @@ -61,25 +50,6 @@ static inline void *binary_to_ptr(term binary) } -static rmt_channel_t get_rmt_channel(Context *ctx, term channel) -{ - if (channel == globalcontext_make_atom(ctx->global, channel_0_atom)) { - return RMT_CHANNEL_1; - } else if (channel == globalcontext_make_atom(ctx->global, channel_1_atom)) { - return RMT_CHANNEL_2; - } else if (channel == globalcontext_make_atom(ctx->global, channel_2_atom)) { - return RMT_CHANNEL_2; - } else if (channel == globalcontext_make_atom(ctx->global, channel_3_atom)) { - return RMT_CHANNEL_3; -#if SOC_RMT_CHANNELS_PER_GROUP > 4 - // TODO -#endif - } else { - return RMT_CHANNEL_MAX; - } -} - - static term nif_init(Context *ctx, int argc, term argv[]) { UNUSED(argc); @@ -90,44 +60,28 @@ static term nif_init(Context *ctx, int argc, term argv[]) VALIDATE_VALUE(num_pixels, term_is_integer); term channel = argv[2]; VALIDATE_VALUE(channel, term_is_atom); - - rmt_channel_t rmt_channel = get_rmt_channel(ctx, channel); + // Note: channel argument is kept for API compatibility but ignored in ESP-IDF 5.x if (UNLIKELY(memory_ensure_free(ctx, 3) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); - } else { - rmt_config_t config = RMT_DEFAULT_CONFIG_TX(term_to_int(pin), rmt_channel); - // set counter clock to 40MHz - config.clk_div = 2; - - esp_err_t err = rmt_config(&config); - if (err != ESP_OK) { - TRACE("Failed to initialize rmt config. err=%i\n", err); - term error_tuple = term_alloc_tuple(2, &ctx->heap); - term_put_tuple_element(error_tuple, 0, ERROR_ATOM); - term_put_tuple_element(error_tuple, 1, term_from_int(err)); - return error_tuple; - } - err = rmt_driver_install(config.channel, 0, NO_ALLOC_FLAGS); - if (err != ESP_OK) { - TRACE("Failed to install rmt driver. err=%i\n", err); - term error_tuple = term_alloc_tuple(2, &ctx->heap); - term_put_tuple_element(error_tuple, 0, ERROR_ATOM); - term_put_tuple_element(error_tuple, 1, term_from_int(err)); - return error_tuple; - } - led_strip_config_t strip_config = LED_STRIP_DEFAULT_CONFIG(term_to_int(num_pixels), (led_strip_dev_t) config.channel); - led_strip_t *strip = led_strip_new_rmt_ws2812(&strip_config); - if (!strip) { - TRACE("Failed to install WS2812 driver.\n"); - term error_tuple = term_alloc_tuple(2, &ctx->heap); - term_put_tuple_element(error_tuple, 0, ERROR_ATOM); - term_put_tuple_element(error_tuple, 1, globalcontext_make_atom(ctx->global, led_strip_atom)); - return error_tuple; - } - ESP_LOGI(TAG, "Installed WS2812 driver."); - return ptr_to_binary(strip, ctx); } + + led_strip_config_t strip_config = { + .max_leds = term_to_int(num_pixels), + .gpio_num = term_to_int(pin) + }; + + led_strip_t *strip = led_strip_new_rmt_ws2812(&strip_config); + if (!strip) { + TRACE("Failed to install WS2812 driver.\n"); + term error_tuple = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(error_tuple, 0, ERROR_ATOM); + term_put_tuple_element(error_tuple, 1, globalcontext_make_atom(ctx->global, led_strip_atom)); + return error_tuple; + } + + ESP_LOGI(TAG, "Installed WS2812 driver."); + return ptr_to_binary(strip, ctx); } @@ -147,12 +101,11 @@ static term nif_clear(Context *ctx, int argc, term argv[]) TRACE("Failed to clear led strip. err=%i\n", err); if (UNLIKELY(memory_ensure_free(ctx, 3) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); - } else { - term error_tuple = term_alloc_tuple(2, &ctx->heap); - term_put_tuple_element(error_tuple, 0, ERROR_ATOM); - term_put_tuple_element(error_tuple, 1, term_from_int(err)); - return error_tuple; } + term error_tuple = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(error_tuple, 0, ERROR_ATOM); + term_put_tuple_element(error_tuple, 1, term_from_int(err)); + return error_tuple; } TRACE("Cleared led strip.\n"); return OK_ATOM; @@ -175,12 +128,11 @@ static term nif_refresh(Context *ctx, int argc, term argv[]) TRACE("Failed to refresh led strip. err=%i\n", err); if (UNLIKELY(memory_ensure_free(ctx, 3) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); - } else { - term error_tuple = term_alloc_tuple(2, &ctx->heap); - term_put_tuple_element(error_tuple, 0, ERROR_ATOM); - term_put_tuple_element(error_tuple, 1, term_from_int(err)); - return error_tuple; } + term error_tuple = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(error_tuple, 0, ERROR_ATOM); + term_put_tuple_element(error_tuple, 1, term_from_int(err)); + return error_tuple; } TRACE("Refreshed led strip.\n"); return OK_ATOM; @@ -210,12 +162,11 @@ static term nif_set_pixel_rgb(Context *ctx, int argc, term argv[]) TRACE("Failed to set pixel value on index %i (r=%i g=%i b=%i). err=%i\n", i, red, green, blue, err); if (UNLIKELY(memory_ensure_free(ctx, 3) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); - } else { - term error_tuple = term_alloc_tuple(2, &ctx->heap); - term_put_tuple_element(error_tuple, 0, ERROR_ATOM); - term_put_tuple_element(error_tuple, 1, term_from_int(err)); - return error_tuple; } + term error_tuple = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(error_tuple, 0, ERROR_ATOM); + term_put_tuple_element(error_tuple, 1, term_from_int(err)); + return error_tuple; } TRACE("Set pixel %i to r=%i g=%i b=%i\n", i, term_to_int(red), term_to_int(green), term_to_int(blue)); return OK_ATOM; @@ -250,12 +201,11 @@ static term nif_set_pixel_hsv(Context *ctx, int argc, term argv[]) TRACE("Failed to set pixel value on index %i (r=%i g=%i b=%i). err=%i\n", i, red, green, blue, err); if (UNLIKELY(memory_ensure_free(ctx, 3) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); - } else { - term error_tuple = term_alloc_tuple(2, &ctx->heap); - term_put_tuple_element(error_tuple, 0, ERROR_ATOM); - term_put_tuple_element(error_tuple, 1, term_from_int(err)); - return error_tuple; } + term error_tuple = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(error_tuple, 0, ERROR_ATOM); + term_put_tuple_element(error_tuple, 1, term_from_int(err)); + return error_tuple; } TRACE("Set pixel %i to r=%i g=%i b=%i\n", i, red, green, blue); return OK_ATOM; @@ -270,6 +220,7 @@ static term nif_tini(Context *ctx, int argc, term argv[]) VALIDATE_VALUE(handle, term_is_binary); term channel = argv[1]; VALIDATE_VALUE(channel, term_is_atom); + // Note: channel argument is kept for API compatibility but ignored in ESP-IDF 5.x led_strip_t *strip = (led_strip_t *) binary_to_ptr(handle); @@ -278,28 +229,14 @@ static term nif_tini(Context *ctx, int argc, term argv[]) TRACE("Failed to delete led strip. err=%i\n", err); if (UNLIKELY(memory_ensure_free(ctx, 3) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); - } else { - term error_tuple = term_alloc_tuple(2, &ctx->heap); - term_put_tuple_element(error_tuple, 0, ERROR_ATOM); - term_put_tuple_element(error_tuple, 1, term_from_int(err)); - return error_tuple; } + term error_tuple = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(error_tuple, 0, ERROR_ATOM); + term_put_tuple_element(error_tuple, 1, term_from_int(err)); + return error_tuple; } - rmt_channel_t rmt_channel = get_rmt_channel(ctx, channel); - err = rmt_driver_uninstall(term_to_int(rmt_channel)); - if (err != ESP_OK) { - TRACE("Failed to uninstall rmt driver. err=%i\n", err); - if (UNLIKELY(memory_ensure_free(ctx, 3) != MEMORY_GC_OK)) { - RAISE_ERROR(OUT_OF_MEMORY_ATOM); - } else { - term error_tuple = term_alloc_tuple(2, &ctx->heap); - term_put_tuple_element(error_tuple, 0, ERROR_ATOM); - term_put_tuple_element(error_tuple, 1, term_from_int(err)); - return error_tuple; - } - } - TRACE("LED strip niti'd\n"); + TRACE("LED strip tini'd\n"); return OK_ATOM; } diff --git a/nifs/include/led_strip.h b/nifs/include/led_strip.h index 64e86324..5e29f70b 100644 --- a/nifs/include/led_strip.h +++ b/nifs/include/led_strip.h @@ -25,12 +25,6 @@ extern "C" { */ typedef struct led_strip_s led_strip_t; -/** -* @brief LED Strip Device Type -* -*/ -typedef void *led_strip_dev_t; - /** * @brief Declare of LED Strip Type * @@ -99,19 +93,9 @@ struct led_strip_s { */ typedef struct { uint32_t max_leds; /*!< Maximum LEDs in a single strip */ - led_strip_dev_t dev; /*!< LED strip device (e.g. RMT channel, PWM channel, etc) */ + int gpio_num; /*!< GPIO number */ } led_strip_config_t; -/** - * @brief Default configuration for LED strip - * - */ -#define LED_STRIP_DEFAULT_CONFIG(number, dev_hdl) \ - { \ - .max_leds = number, \ - .dev = dev_hdl, \ - } - /** * @brief Install a new ws2812 driver (based on RMT peripheral) * diff --git a/nifs/led_strip_rmt_ws2812.c b/nifs/led_strip_rmt_ws2812.c index 9985f4f7..e8dd43e6 100644 --- a/nifs/led_strip_rmt_ws2812.c +++ b/nifs/led_strip_rmt_ws2812.c @@ -11,15 +11,18 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + #include #include #include #include "esp_log.h" #include "esp_attr.h" +#include "driver/rmt_tx.h" +#include "driver/rmt_encoder.h" #include "led_strip.h" -#include "driver/rmt.h" static const char *TAG = "ws2812"; + #define STRIP_CHECK(a, str, goto_tag, ret_value, ...) \ do \ { \ @@ -44,53 +47,134 @@ static uint32_t ws2812_t1l_ticks = 0; typedef struct { led_strip_t parent; - rmt_channel_t rmt_channel; + rmt_channel_handle_t rmt_chan; + rmt_encoder_handle_t rmt_encoder; uint32_t strip_len; uint8_t buffer[0]; } ws2812_t; -/** - * @brief Conver RGB data to RMT format. - * - * @note For WS2812, R,G,B each contains 256 different choices (i.e. uint8_t) - * - * @param[in] src: source data, to converted to RMT format - * @param[in] dest: place where to store the convert result - * @param[in] src_size: size of source data - * @param[in] wanted_num: number of RMT items that want to get - * @param[out] translated_size: number of source data that got converted - * @param[out] item_num: number of RMT items which are converted from source data - */ -static void IRAM_ATTR ws2812_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) +// LED strip 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) { - if (src == NULL || dest == NULL) { - *translated_size = 0; - *item_num = 0; - return; + 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; + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; + } + // 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; + state |= RMT_ENCODING_COMPLETE; + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; + } } - const rmt_item32_t bit0 = {{{ ws2812_t0h_ticks, 1, ws2812_t0l_ticks, 0 }}}; //Logical 0 - const rmt_item32_t bit1 = {{{ ws2812_t1h_ticks, 1, ws2812_t1l_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++; +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; +} + +static esp_err_t rmt_new_led_strip_encoder(rmt_encoder_handle_t *ret_encoder) +{ + esp_err_t ret = ESP_OK; + rmt_led_strip_encoder_t *led_encoder = NULL; + + led_encoder = calloc(1, sizeof(rmt_led_strip_encoder_t)); + STRIP_CHECK(led_encoder, "allocate memory for led strip encoder failed", err, ESP_ERR_NO_MEM); + + 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 = { + .bit0 = { + .level0 = 1, + .duration0 = ws2812_t0h_ticks, + .level1 = 0, + .duration1 = ws2812_t0l_ticks, + }, + .bit1 = { + .level0 = 1, + .duration0 = ws2812_t1h_ticks, + .level1 = 0, + .duration1 = ws2812_t1l_ticks, + }, + .flags.msb_first = 1 + }; + + STRIP_CHECK(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder) == ESP_OK, + "create bytes encoder failed", err, ESP_FAIL); + + rmt_copy_encoder_config_t copy_encoder_config = {}; + STRIP_CHECK(rmt_new_copy_encoder(©_encoder_config, &led_encoder->copy_encoder) == ESP_OK, + "create copy encoder failed", err, ESP_FAIL); + + uint32_t reset_ticks = WS2812_RESET_US * 40; // 40MHz resolution + 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); } - size++; - psrc++; + if (led_encoder->copy_encoder) { + rmt_del_encoder(led_encoder->copy_encoder); + } + free(led_encoder); } - *translated_size = size; - *item_num = num; + return ret; } static esp_err_t ws2812_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue) @@ -98,8 +182,9 @@ static esp_err_t ws2812_set_pixel(led_strip_t *strip, uint32_t index, uint32_t r esp_err_t ret = ESP_OK; ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent); STRIP_CHECK(index < ws2812->strip_len, "index out of the maximum number of leds", err, ESP_ERR_INVALID_ARG); + uint32_t start = index * 3; - // In thr order of GRB + // In the order of GRB ws2812->buffer[start + 0] = green & 0xFF; ws2812->buffer[start + 1] = red & 0xFF; ws2812->buffer[start + 2] = blue & 0xFF; @@ -112,9 +197,16 @@ static esp_err_t ws2812_refresh(led_strip_t *strip, uint32_t timeout_ms) { esp_err_t ret = ESP_OK; ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent); - STRIP_CHECK(rmt_write_sample(ws2812->rmt_channel, ws2812->buffer, ws2812->strip_len * 3, true) == ESP_OK, + + rmt_transmit_config_t tx_config = { + .loop_count = 0, + }; + + STRIP_CHECK(rmt_transmit(ws2812->rmt_chan, ws2812->rmt_encoder, ws2812->buffer, ws2812->strip_len * 3, &tx_config) == ESP_OK, "transmit RMT samples failed", err, ESP_FAIL); - return rmt_wait_tx_done(ws2812->rmt_channel, pdMS_TO_TICKS(timeout_ms)); + STRIP_CHECK(rmt_tx_wait_all_done(ws2812->rmt_chan, timeout_ms) == ESP_OK, + "wait tx done failed", err, ESP_FAIL); + return ESP_OK; err: return ret; } @@ -122,7 +214,6 @@ static esp_err_t ws2812_refresh(led_strip_t *strip, uint32_t timeout_ms) static esp_err_t ws2812_clear(led_strip_t *strip, uint32_t timeout_ms) { ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent); - // Write zero to turn off all leds memset(ws2812->buffer, 0, ws2812->strip_len * 3); return ws2812_refresh(strip, timeout_ms); } @@ -130,6 +221,14 @@ static esp_err_t ws2812_clear(led_strip_t *strip, uint32_t timeout_ms) static esp_err_t ws2812_del(led_strip_t *strip) { ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent); + + if (ws2812->rmt_encoder) { + rmt_del_encoder(ws2812->rmt_encoder); + } + if (ws2812->rmt_chan) { + rmt_disable(ws2812->rmt_chan); + rmt_del_channel(ws2812->rmt_chan); + } free(ws2812); return ESP_OK; } @@ -137,29 +236,40 @@ static esp_err_t ws2812_del(led_strip_t *strip) led_strip_t *led_strip_new_rmt_ws2812(const led_strip_config_t *config) { led_strip_t *ret = NULL; + ws2812_t *ws2812 = NULL; + STRIP_CHECK(config, "configuration can't be null", err, NULL); - // 24 bits per led + // 24 bits per LED (3 bytes: G, R, B) uint32_t ws2812_size = sizeof(ws2812_t) + config->max_leds * 3; - ws2812_t *ws2812 = calloc(1, ws2812_size); + ws2812 = calloc(1, ws2812_size); STRIP_CHECK(ws2812, "request memory for ws2812 failed", err, NULL); - uint32_t counter_clk_hz = 0; - STRIP_CHECK(rmt_get_counter_clock((rmt_channel_t)config->dev, &counter_clk_hz) == ESP_OK, - "get rmt counter clock failed", err, NULL); - // ns -> ticks + rmt_tx_channel_config_t tx_chan_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .gpio_num = (gpio_num_t)config->gpio_num, + .mem_block_symbols = 64, + .resolution_hz = 40000000, // 40MHz + .trans_queue_depth = 4, + }; + STRIP_CHECK(rmt_new_tx_channel(&tx_chan_config, &ws2812->rmt_chan) == ESP_OK, + "create RMT TX channel failed", err, NULL); + + // Calculate timing ticks (40MHz resolution) + uint32_t counter_clk_hz = 40000000; float ratio = (float)counter_clk_hz / 1e9; ws2812_t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS); ws2812_t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS); ws2812_t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS); ws2812_t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS); - // set ws2812 to rmt adapter - rmt_translator_init((rmt_channel_t)config->dev, ws2812_rmt_adapter); + STRIP_CHECK(rmt_new_led_strip_encoder(&ws2812->rmt_encoder) == ESP_OK, + "create led strip encoder failed", err, NULL); - ws2812->rmt_channel = (rmt_channel_t)config->dev; - ws2812->strip_len = config->max_leds; + STRIP_CHECK(rmt_enable(ws2812->rmt_chan) == ESP_OK, + "enable RMT TX channel failed", err, NULL); + ws2812->strip_len = config->max_leds; ws2812->parent.set_pixel = ws2812_set_pixel; ws2812->parent.refresh = ws2812_refresh; ws2812->parent.clear = ws2812_clear; @@ -167,19 +277,20 @@ led_strip_t *led_strip_new_rmt_ws2812(const led_strip_config_t *config) return &ws2812->parent; err: + if (ws2812) { + free(ws2812); + } return ret; } void led_strip_hsv2rgb(uint32_t h, uint32_t s, uint32_t v, uint32_t *r, uint32_t *g, uint32_t *b) { - h %= 360; // h -> [0,360] + h %= 360; uint32_t rgb_max = v * 2.55f; uint32_t rgb_min = rgb_max * (100 - s) / 100.0f; uint32_t i = h / 60; uint32_t diff = h % 60; - - // RGB adjustment amount by hue uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60; switch (i) {