Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

set(ATOMVM_NEOPIXEL_COMPONENT_SRCS
"nifs/atomvm_neopixel.c"
"nifs/led_strip_rmt_ws2812.c"
"nifs/led_strip_driver.c"
)

# WHOLE_ARCHIVE option is supported only with esp-idf 5.x
Expand All @@ -34,7 +34,7 @@ endif()
idf_component_register(
SRCS ${ATOMVM_NEOPIXEL_COMPONENT_SRCS}
INCLUDE_DIRS "nifs/include"
PRIV_REQUIRES "libatomvm" "avm_sys" "driver"
PRIV_REQUIRES "libatomvm" "avm_sys" "driver" "led_strip"
${OPTIONAL_WHOLE_ARCHIVE}
)

Expand Down
36 changes: 36 additions & 0 deletions Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,41 @@ config AVM_NEOPIXEL_ENABLE
default y
help
Use this parameter to enable or disable the AtomVM NEOPIXEL driver.

This driver uses the ESP-IDF led_strip component which provides:
- Automatic DMA support on ESP32-S3/C6
- SPI backend fallback for better WiFi coexistence
- Maintained by Espressif for optimal performance

choice AVM_NEOPIXEL_BACKEND
prompt "LED strip backend"
default AVM_NEOPIXEL_BACKEND_AUTO
depends on AVM_NEOPIXEL_ENABLE
help
Select the backend peripheral for driving the LED strip.

config AVM_NEOPIXEL_BACKEND_AUTO
bool "Auto (recommended)"
help
Automatically select the best backend for your chip:
- ESP32: SPI with DMA (best WiFi coexistence)
- ESP32-S3/C6/P4: RMT with DMA
- Others: RMT, fallback to SPI

config AVM_NEOPIXEL_BACKEND_SPI
bool "Force SPI"
help
Always use SPI backend with DMA.
Best for WiFi coexistence on all chips.
Note: Uses entire SPI bus (SPI2_HOST).

config AVM_NEOPIXEL_BACKEND_RMT
bool "Force RMT"
help
Always use RMT backend.
May cause flickering with WiFi on ESP32 (no DMA).
Works well on ESP32-S3/C6/P4 with DMA.

endchoice

endmenu
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
# AtomVM NeoPixel Library

This AtomVM Erlang library and Nif can be used to control WS2812 LED strips using the ESP32 SoC for any Erlang/Elixir programs targeted for AtomVM on the ESP32 platform.
This AtomVM Erlang library and Nif can be used to control WS2812 and SK6812 LED strips using the ESP32 SoC for any Erlang/Elixir programs targeted for AtomVM on the ESP32 platform.

## Features

- **RGB LED support** (WS2812, WS2812B) - 24-bit color
- **RGBW LED support** (SK6812) - 32-bit color with dedicated white channel
- **Global brightness control** - Hardware-efficient brightness scaling (0-255)
- **Multiple color spaces** - RGB, RGBW, HSV, and HSVW
- **Batch operations** - Fill entire strip or set multiple pixels in a single call
- **ESP-IDF 5.x compatible** - Uses the new RMT driver API

This Nif is included as an add-on to the AtomVM base image. In order to use this Nif in your AtomVM program, you must be able to build the AtomVM virtual machine, which in turn requires installation of the Espressif IDF SDK and tool chain.

Expand Down
37 changes: 36 additions & 1 deletion examples/neopixel_example/src/neopixel_example.erl
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@
%%
-module(neopixel_example).

-export([start/0]).
-export([start/0, demo_fill/0, demo_pattern/0]).

-define(NEOPIXEL_PIN, 18).
-define(NUM_PIXELS, 4).

-define(SATURATION, 100).
-define(VALUE, 15).

%% @doc Main example - rainbow cycle on each pixel
start() ->
{ok, NeoPixel} = neopixel:start(?NEOPIXEL_PIN, ?NUM_PIXELS),
ok = neopixel:clear(NeoPixel),
Expand All @@ -35,6 +36,40 @@ start() ->
),
timer:sleep(infinity).

%% @doc Demo: Fill entire strip with solid colors
demo_fill() ->
{ok, NeoPixel} = neopixel:start(?NEOPIXEL_PIN, ?NUM_PIXELS),
ok = neopixel:clear(NeoPixel),
%% Cycle through red, green, blue
fill_loop(NeoPixel, [{255, 0, 0}, {0, 255, 0}, {0, 0, 255}]).

fill_loop(NeoPixel, []) ->
fill_loop(NeoPixel, [{255, 0, 0}, {0, 255, 0}, {0, 0, 255}]);
fill_loop(NeoPixel, [{R, G, B} | Rest]) ->
ok = neopixel:fill_rgb(NeoPixel, R, G, B),
ok = neopixel:refresh(NeoPixel),
timer:sleep(1000),
fill_loop(NeoPixel, Rest).

%% @doc Demo: Set multiple pixels with a pattern using set_pixels_rgb
demo_pattern() ->
{ok, NeoPixel} = neopixel:start(?NEOPIXEL_PIN, ?NUM_PIXELS),
ok = neopixel:clear(NeoPixel),
%% Create a rainbow pattern
Pattern = [{255, 0, 0}, {255, 127, 0}, {0, 255, 0}, {0, 0, 255}],
pattern_loop(NeoPixel, Pattern, 0).

pattern_loop(NeoPixel, Pattern, Offset) ->
%% Rotate the pattern by Offset positions
RotatedPattern = rotate_list(Pattern, Offset),
ok = neopixel:set_pixels_rgb(NeoPixel, RotatedPattern),
ok = neopixel:refresh(NeoPixel),
timer:sleep(200),
pattern_loop(NeoPixel, Pattern, (Offset + 1) rem length(Pattern)).

rotate_list(List, 0) -> List;
rotate_list([H | T], N) -> rotate_list(T ++ [H], N - 1).

loop(NeoPixel, I, Hue, SleepMs) ->
ok = neopixel:set_pixel_hsv(NeoPixel, I, Hue, ?SATURATION, ?VALUE),
ok = neopixel:refresh(NeoPixel),
Expand Down
5 changes: 5 additions & 0 deletions idf_component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/led_strip: "^3.0.2"
idf:
version: ">=5.0"
Loading