diff --git a/nifs/atomvm_neopixel.c b/nifs/atomvm_neopixel.c index cc98cdf2..04402ac1 100644 --- a/nifs/atomvm_neopixel.c +++ b/nifs/atomvm_neopixel.c @@ -207,11 +207,63 @@ static term nif_set_pixel_hsv(Context *ctx, int argc, term argv[]) 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); + 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; } +static term nif_set_brightness(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + term handle = argv[0]; + VALIDATE_VALUE(handle, term_is_binary); + term brightness = argv[1]; + VALIDATE_VALUE(brightness, term_is_integer); + + led_strip_t *strip = (led_strip_t *) binary_to_ptr(handle); + + avm_int_t br = term_to_int(brightness); + if (br < 0 || br > 255) { + if (UNLIKELY(memory_ensure_free(ctx, 3) != MEMORY_GC_OK)) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + 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, BADARG_ATOM); + return error_tuple; + } + + esp_err_t err = strip->set_brightness(strip, (uint8_t)br); + if (err != ESP_OK) { + TRACE("Failed to set brightness to %i. err=%i\n", br, err); + if (UNLIKELY(memory_ensure_free(ctx, 3) != MEMORY_GC_OK)) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + 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 brightness to %i\n", br); + return OK_ATOM; +} + + +static term nif_get_brightness(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + term handle = argv[0]; + VALIDATE_VALUE(handle, term_is_binary); + + led_strip_t *strip = (led_strip_t *) binary_to_ptr(handle); + + uint8_t brightness = strip->get_brightness(strip); + return term_from_int(brightness); +} + + static term nif_tini(Context *ctx, int argc, term argv[]) { UNUSED(argc); @@ -266,6 +318,16 @@ static const struct Nif set_pixel_rgb_nif = .base.type = NIFFunctionType, .nif_ptr = nif_set_pixel_rgb }; +static const struct Nif set_brightness_nif = +{ + .base.type = NIFFunctionType, + .nif_ptr = nif_set_brightness +}; +static const struct Nif get_brightness_nif = +{ + .base.type = NIFFunctionType, + .nif_ptr = nif_get_brightness +}; static const struct Nif tini_nif = { .base.type = NIFFunctionType, @@ -305,6 +367,14 @@ const struct Nif *atomvm_neopixel_get_nif(const char *nifname) TRACE("Resolved platform nif %s ...\n", nifname); return &set_pixel_hsv_nif; } + if (strcmp("neopixel:nif_set_brightness/2", nifname) == 0) { + TRACE("Resolved platform nif %s ...\n", nifname); + return &set_brightness_nif; + } + if (strcmp("neopixel:nif_get_brightness/1", nifname) == 0) { + TRACE("Resolved platform nif %s ...\n", nifname); + return &get_brightness_nif; + } if (strcmp("neopixel:nif_tini/2", nifname) == 0) { TRACE("Resolved platform nif %s ...\n", nifname); return &tini_nif; diff --git a/nifs/include/led_strip.h b/nifs/include/led_strip.h index 5e29f70b..e73c04eb 100644 --- a/nifs/include/led_strip.h +++ b/nifs/include/led_strip.h @@ -85,6 +85,26 @@ struct led_strip_s { * - ESP_FAIL: Free resources failed because error occurred */ esp_err_t (*del)(led_strip_t *strip); + + /** + * @brief Set global brightness for the strip + * + * @param strip: LED strip + * @param brightness: brightness value (0-255) + * + * @return + * - ESP_OK: Set brightness successfully + */ + esp_err_t (*set_brightness)(led_strip_t *strip, uint8_t brightness); + + /** + * @brief Get current global brightness + * + * @param strip: LED strip + * + * @return current brightness value (0-255) + */ + uint8_t (*get_brightness)(led_strip_t *strip); }; /** @@ -94,6 +114,7 @@ struct led_strip_s { typedef struct { uint32_t max_leds; /*!< Maximum LEDs in a single strip */ int gpio_num; /*!< GPIO number */ + uint8_t brightness; /*!< Global brightness (0-255), default 255 */ } led_strip_config_t; /** diff --git a/nifs/led_strip_rmt_ws2812.c b/nifs/led_strip_rmt_ws2812.c index e8dd43e6..3bafbdd3 100644 --- a/nifs/led_strip_rmt_ws2812.c +++ b/nifs/led_strip_rmt_ws2812.c @@ -50,7 +50,9 @@ typedef struct { rmt_channel_handle_t rmt_chan; rmt_encoder_handle_t rmt_encoder; uint32_t strip_len; - uint8_t buffer[0]; + uint8_t brightness; + uint8_t *out_buf; // Brightness-scaled output buffer + uint8_t buffer[0]; // Raw RGB values (flexible array member) } ws2812_t; // LED strip encoder @@ -183,6 +185,7 @@ static esp_err_t ws2812_set_pixel(led_strip_t *strip, uint32_t index, uint32_t r 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); + // Store raw RGB values - brightness applied at refresh time uint32_t start = index * 3; // In the order of GRB ws2812->buffer[start + 0] = green & 0xFF; @@ -197,12 +200,27 @@ 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); + uint32_t buf_size = ws2812->strip_len * 3; + uint8_t *tx_buf; + + // Apply brightness scaling to output buffer + uint8_t br = ws2812->brightness; + if (br < 255) { + uint16_t scale = br + 1; + for (uint32_t i = 0; i < buf_size; i++) { + ws2812->out_buf[i] = (ws2812->buffer[i] * scale) >> 8; + } + tx_buf = ws2812->out_buf; + } else { + // Full brightness - transmit raw buffer directly (no copy needed) + tx_buf = ws2812->buffer; + } 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, + STRIP_CHECK(rmt_transmit(ws2812->rmt_chan, ws2812->rmt_encoder, tx_buf, buf_size, &tx_config) == ESP_OK, "transmit RMT samples failed", err, ESP_FAIL); STRIP_CHECK(rmt_tx_wait_all_done(ws2812->rmt_chan, timeout_ms) == ESP_OK, "wait tx done failed", err, ESP_FAIL); @@ -218,6 +236,19 @@ static esp_err_t ws2812_clear(led_strip_t *strip, uint32_t timeout_ms) return ws2812_refresh(strip, timeout_ms); } +static esp_err_t ws2812_set_brightness(led_strip_t *strip, uint8_t brightness) +{ + ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent); + ws2812->brightness = brightness; + return ESP_OK; +} + +static uint8_t ws2812_get_brightness(led_strip_t *strip) +{ + ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent); + return ws2812->brightness; +} + static esp_err_t ws2812_del(led_strip_t *strip) { ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent); @@ -229,6 +260,9 @@ static esp_err_t ws2812_del(led_strip_t *strip) rmt_disable(ws2812->rmt_chan); rmt_del_channel(ws2812->rmt_chan); } + if (ws2812->out_buf) { + free(ws2812->out_buf); + } free(ws2812); return ESP_OK; } @@ -237,13 +271,20 @@ led_strip_t *led_strip_new_rmt_ws2812(const led_strip_config_t *config) { led_strip_t *ret = NULL; ws2812_t *ws2812 = NULL; + uint8_t *out_buf = NULL; STRIP_CHECK(config, "configuration can't be null", err, NULL); // 24 bits per LED (3 bytes: G, R, B) - uint32_t ws2812_size = sizeof(ws2812_t) + config->max_leds * 3; + uint32_t buf_size = config->max_leds * 3; + uint32_t ws2812_size = sizeof(ws2812_t) + buf_size; ws2812 = calloc(1, ws2812_size); STRIP_CHECK(ws2812, "request memory for ws2812 failed", err, NULL); + + // Allocate output buffer for brightness-scaled data + out_buf = malloc(buf_size); + STRIP_CHECK(out_buf, "request memory for output buffer failed", err, NULL); + ws2812->out_buf = out_buf; rmt_tx_channel_config_t tx_chan_config = { .clk_src = RMT_CLK_SRC_DEFAULT, @@ -270,13 +311,19 @@ led_strip_t *led_strip_new_rmt_ws2812(const led_strip_config_t *config) "enable RMT TX channel failed", err, NULL); ws2812->strip_len = config->max_leds; + ws2812->brightness = config->brightness ? config->brightness : 255; ws2812->parent.set_pixel = ws2812_set_pixel; ws2812->parent.refresh = ws2812_refresh; ws2812->parent.clear = ws2812_clear; ws2812->parent.del = ws2812_del; + ws2812->parent.set_brightness = ws2812_set_brightness; + ws2812->parent.get_brightness = ws2812_get_brightness; return &ws2812->parent; err: + if (out_buf) { + free(out_buf); + } if (ws2812) { free(ws2812); } diff --git a/src/neopixel.erl b/src/neopixel.erl index 21f60dbd..49f6fe22 100644 --- a/src/neopixel.erl +++ b/src/neopixel.erl @@ -27,9 +27,11 @@ -module(neopixel). -export([ - start/2, start/3, stop/1, clear/1, set_pixel_rgb/5, set_pixel_hsv/5, refresh/1 + start/2, start/3, stop/1, clear/1, set_pixel_rgb/5, set_pixel_hsv/5, refresh/1, + set_brightness/2, get_brightness/1 ]). --export([nif_init/3, nif_clear/2, nif_refresh/2, nif_set_pixel_hsv/5, nif_set_pixel_rgb/5, nif_tini/2]). %% internal nif APIs +-export([nif_init/3, nif_clear/2, nif_refresh/2, nif_set_pixel_hsv/5, nif_set_pixel_rgb/5, nif_tini/2, + nif_set_brightness/2, nif_get_brightness/1]). %% internal nif APIs -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -behaviour(gen_server). @@ -41,6 +43,7 @@ -type channel() :: channel_0 | channel_1 | channel_2 | channel_3. -type color() :: 0..255. +-type brightness() :: 0..255. -type hue() :: 0..359. -type saturation() :: 0..100. -type value() :: 0..100. @@ -143,6 +146,35 @@ set_pixel_hsv(Neopixel, I, H, S, V) when is_pid(Neopixel), 0 =< H, H < 360, 0 =< set_pixel_hsv(_Neopixel, _I, _R, _G, _B) -> throw(badarg). +%%----------------------------------------------------------------------------- +%% @param Neopixel Neopixel instance +%% @param Brightness Brightness value (`0..255') +%% @returns ok | {error, Reason} +%% @doc Set global brightness for the strip. +%% +%% Brightness is applied when pixels are set. A value of 255 means full +%% brightness (no scaling), 128 means 50% brightness, 0 means off. +%% Note: You need to call refresh/1 and re-set pixels to see the effect. +%% @end +%%----------------------------------------------------------------------------- +-spec set_brightness(Neopixel::neopixel(), Brightness::brightness()) -> ok | {error, Reason::term()}. +set_brightness(Neopixel, Brightness) when is_pid(Neopixel), 0 =< Brightness, Brightness =< 255 -> + gen_server:call(Neopixel, {set_brightness, Brightness}); +set_brightness(_Neopixel, _Brightness) -> + throw(badarg). + +%%----------------------------------------------------------------------------- +%% @param Neopixel Neopixel instance +%% @returns Brightness value (`0..255') +%% @doc Get current global brightness for the strip. +%% @end +%%----------------------------------------------------------------------------- +-spec get_brightness(Neopixel::neopixel()) -> brightness(). +get_brightness(Neopixel) when is_pid(Neopixel) -> + gen_server:call(Neopixel, get_brightness); +get_brightness(_Neopixel) -> + throw(badarg). + %% %% gen_server API %% @@ -168,6 +200,10 @@ handle_call({set_pixel_rgb, I, R, G, B}, _From, State) -> {reply, ?MODULE:nif_set_pixel_rgb(State#state.nif_handle, I, R, G, B), State}; handle_call({set_pixel_hsv, I, H, S, V}, _From, State) -> {reply, ?MODULE:nif_set_pixel_hsv(State#state.nif_handle, I, H, S, V), State}; +handle_call({set_brightness, Brightness}, _From, State) -> + {reply, ?MODULE:nif_set_brightness(State#state.nif_handle, Brightness), State}; +handle_call(get_brightness, _From, State) -> + {reply, ?MODULE:nif_get_brightness(State#state.nif_handle), State}; handle_call(Request, _From, State) -> {reply, {error, {unknown_request, Request}}, State}. @@ -240,6 +276,14 @@ nif_set_pixel_rgb(_NifHandle, _Index, _Red, _Green, _Blue) -> nif_set_pixel_hsv(_NifHandle, _Index, _Hue, _Saturation, _Value) -> throw(nif_error). +%% @hidden +nif_set_brightness(_NifHandle, _Brightness) -> + throw(nif_error). + +%% @hidden +nif_get_brightness(_NifHandle) -> + throw(nif_error). + %% @hidden nif_tini(_NifHandle, _Channel) -> throw(nif_error).