diff --git a/app/modules/ws2812.c b/app/modules/ws2812.c
index 578c8c24..bbd166c3 100644
--- a/app/modules/ws2812.c
+++ b/app/modules/ws2812.c
@@ -1,5 +1,6 @@
#include "module.h"
#include "lauxlib.h"
+#include "lmem.h"
#include "platform.h"
#include "c_stdlib.h"
#include "c_string.h"
@@ -11,33 +12,51 @@
typedef struct {
int canary;
int size;
+ uint8_t colorsPerLed;
uint8_t values[0];
} ws2812_buffer;
-// Stream data using UART1 routed to GPIO2
-// NODE_DEBUG should not be activated because it also uses UART1
-static void ICACHE_RAM_ATTR ws2812_write(uint8_t pin, uint8_t *pixels, uint32_t length) {
-
- // Data are sent LSB first, with a start bit at 0, an end bit at 1 and all inverted
- // 0b00110111 => 110111 => [0]111011[1] => 10001000 => 00
- // 0b00000111 => 000111 => [0]111000[1] => 10001110 => 01
- // 0b00110100 => 110100 => [0]001011[1] => 11101000 => 10
- // 0b00000100 => 000100 => [0]001000[1] => 11101110 => 11
- uint8_t _uartData[4] = { 0b00110111, 0b00000111, 0b00110100, 0b00000100 };
-
+// Init UART1 to be able to stream WS2812 data
+// We use GPIO2 as output pin
+static void ws2812_init() {
// Configure UART1
// Set baudrate of UART1 to 3200000
WRITE_PERI_REG(UART_CLKDIV(1), UART_CLK_FREQ / 3200000);
// Set UART Configuration No parity / 6 DataBits / 1 StopBits / Invert TX
WRITE_PERI_REG(UART_CONF0(1), UART_TXD_INV | (1 << UART_STOP_BIT_NUM_S) | (1 << UART_BIT_NUM_S));
+ // Pull GPIO2 down
+ platform_gpio_mode(4, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT);
+ platform_gpio_write(4, 0);
+
+ // Waits 10us to simulate a reset
+ os_delay_us(10);
+
// Redirect UART1 to GPIO2
// Disable GPIO2
GPIO_REG_WRITE(GPIO_ENABLE_W1TC_ADDRESS, BIT2);
// Enable Function 2 for GPIO2 (U1TXD)
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK);
+}
+
+
+// Stream data using UART1 routed to GPIO2
+// ws2812.init() should be called first
+//
+// NODE_DEBUG should not be activated because it also uses UART1
+static void ICACHE_RAM_ATTR ws2812_write(uint8_t *pixels, uint32_t length) {
+
+ // Data are sent LSB first, with a start bit at 0, an end bit at 1 and all inverted
+ // 0b00110111 => 110111 => [0]111011[1] => 10001000 => 00
+ // 0b00000111 => 000111 => [0]111000[1] => 10001110 => 01
+ // 0b00110100 => 110100 => [0]001011[1] => 11101000 => 10
+ // 0b00000100 => 000100 => [0]001000[1] => 11101110 => 11
+ // Array declared as static const to avoid runtime generation
+ // But declared in ".data" section to avoid read penalty from FLASH
+ static const __attribute__((section(".data._uartData"))) uint8_t _uartData[4] = { 0b00110111, 0b00000111, 0b00110100, 0b00000100 };
uint8_t *end = pixels + length;
+
do {
uint8_t value = *pixels++;
@@ -52,94 +71,37 @@ static void ICACHE_RAM_ATTR ws2812_write(uint8_t pin, uint8_t *pixels, uint32_t
WRITE_PERI_REG(UART_FIFO(1), _uartData[(value >> 0) & 3]);
} while(pixels < end);
+
}
-// Lua: ws2812.writergb(pin, "string")
-// Byte triples in the string or buffer are interpreted as R G B values and sent to the hardware as G R B.
+// Lua: ws2812.write("string")
+// Byte triples in the string are interpreted as G R B values.
//
-// ws2812.writergb(4, string.char(255, 0, 0)) uses GPIO2 and sets the first LED red.
-// ws2812.writergb(3, string.char(0, 0, 255):rep(10)) uses GPIO0 and sets ten LEDs blue.
-// ws2812.writergb(4, string.char(0, 255, 0, 255, 255, 255)) first LED green, second LED white.
-static int ICACHE_FLASH_ATTR ws2812_writergb(lua_State* L)
-{
- const uint8_t pin = luaL_checkinteger(L, 1);
+// ws2812.init() should be called first
+//
+// ws2812.write(string.char(0, 255, 0)) sets the first LED red.
+// ws2812.write(string.char(0, 0, 255):rep(10)) sets ten LEDs blue.
+// ws2812.write(string.char(255, 0, 0, 255, 255, 255)) first LED green, second LED white.
+static int ws2812_writegrb(lua_State* L) {
size_t length;
- const char *rgb;
-
- // Buffer or string
- if(lua_isuserdata(L, 2)) {
- ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 2);
-
- luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 2, "ws2812.buffer expected");
-
- rgb = &buffer->values[0];
- length = 3*buffer->size;
- } else {
- rgb = luaL_checklstring(L, 2, &length);
- }
-
- // dont modify lua-internal lstring - make a copy instead
- char *buffer = (char *)c_malloc(length);
- c_memcpy(buffer, rgb, length);
-
- // Ignore incomplete Byte triples at the end of buffer:
- length -= length % 3;
-
- // Rearrange R G B values to G R B order needed by WS2812 LEDs:
- size_t i;
- for (i = 0; i < length; i += 3) {
- const char r = buffer[i];
- const char g = buffer[i + 1];
- buffer[i] = g;
- buffer[i + 1] = r;
- }
+ const char *values = luaL_checklstring(L, 1, &length);
// Send the buffer
- ws2812_write(pin_num[pin], (uint8_t*) buffer, length);
-
- c_free(buffer);
-
- return 0;
-}
-
-// Lua: ws2812.write(pin, "string")
-// Lua: ws2812.write(pin, ws2812.buffer)
-// Byte triples in the string or buffer are interpreted as G R B values.
-//
-// ws2812.write(4, string.char(0, 255, 0)) uses GPIO2 and sets the first LED red.
-// ws2812.write(3, string.char(0, 0, 255):rep(10)) uses GPIO0 and sets ten LEDs blue.
-// ws2812.write(4, string.char(255, 0, 0, 255, 255, 255)) first LED green, second LED white.
-static int ICACHE_FLASH_ATTR ws2812_writegrb(lua_State* L) {
- const uint8_t pin = luaL_checkinteger(L, 1);
- size_t length;
- const char *values;
-
- // Buffer or string
- if(lua_isuserdata(L, 2)) {
- ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 2);
-
- luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 2, "ws2812.buffer expected");
-
- values = &buffer->values[0];
- length = 3*buffer->size;
- } else {
- values = luaL_checklstring(L, 2, &length);
- }
-
- // Send the buffer
- ws2812_write(pin_num[pin], (uint8_t*) values, length);
+ ws2812_write((uint8_t*) values, length);
return 0;
}
// Handle a buffer where we can store led values
-static int ICACHE_FLASH_ATTR ws2812_new_buffer(lua_State *L) {
+static int ws2812_new_buffer(lua_State *L) {
const int leds = luaL_checkint(L, 1);
+ const int colorsPerLed = luaL_checkint(L, 2);
luaL_argcheck(L, leds > 0, 1, "should be a positive integer");
+ luaL_argcheck(L, colorsPerLed > 0, 2, "should be a positive integer");
// Allocate memory
- size_t size = sizeof(ws2812_buffer) + 3*leds*sizeof(uint8_t);
+ size_t size = sizeof(ws2812_buffer) + colorsPerLed*leds*sizeof(uint8_t);
ws2812_buffer * buffer = (ws2812_buffer*)lua_newuserdata(L, size);
// Associate its metatable
@@ -148,6 +110,7 @@ static int ICACHE_FLASH_ATTR ws2812_new_buffer(lua_State *L) {
// Save led strip size
buffer->size = leds;
+ buffer->colorsPerLed = colorsPerLed;
// Store canary for future type checks
buffer->canary = CANARY_VALUE;
@@ -155,27 +118,37 @@ static int ICACHE_FLASH_ATTR ws2812_new_buffer(lua_State *L) {
return 1;
}
-static int ICACHE_FLASH_ATTR ws2812_buffer_fill(lua_State* L) {
+static int ws2812_buffer_fill(lua_State* L) {
ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1);
luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected");
- const int g = luaL_checkinteger(L, 2);
- const int r = luaL_checkinteger(L, 3);
- const int b = luaL_checkinteger(L, 4);
+ // Grab colors
+ int i, j;
+ int * colors = luaM_malloc(L, buffer->colorsPerLed * sizeof(int));
- uint8_t * p = &buffer->values[0];
- int i;
- for(i = 0; i < buffer->size; i++) {
- *p++ = g;
- *p++ = r;
- *p++ = b;
+ for (i = 0; i < buffer->colorsPerLed; i++)
+ {
+ colors[i] = luaL_checkinteger(L, 2+i);
}
+ // Fill buffer
+ uint8_t * p = &buffer->values[0];
+ for(i = 0; i < buffer->size; i++)
+ {
+ for (j = 0; j < buffer->colorsPerLed; j++)
+ {
+ *p++ = colors[j];
+ }
+ }
+
+ // Free memory
+ luaM_free(L, colors);
+
return 0;
}
-static int ICACHE_FLASH_ATTR ws2812_buffer_fade(lua_State* L) {
+static int ws2812_buffer_fade(lua_State* L) {
ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1);
const int fade = luaL_checkinteger(L, 2);
@@ -184,32 +157,33 @@ static int ICACHE_FLASH_ATTR ws2812_buffer_fade(lua_State* L) {
uint8_t * p = &buffer->values[0];
int i;
- for(i = 0; i < buffer->size; i++) {
- *p++ /= fade;
- *p++ /= fade;
+ for(i = 0; i < buffer->size * buffer->colorsPerLed; i++)
+ {
*p++ /= fade;
}
return 0;
}
-static int ICACHE_FLASH_ATTR ws2812_buffer_get(lua_State* L) {
+static int ws2812_buffer_get(lua_State* L) {
ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1);
- const int led = luaL_checkinteger(L, 2);
+ const int led = luaL_checkinteger(L, 2) - 1;
luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected");
luaL_argcheck(L, led >= 0 && led < buffer->size, 2, "index out of range");
- lua_pushnumber(L, buffer->values[3*led+0]);
- lua_pushnumber(L, buffer->values[3*led+1]);
- lua_pushnumber(L, buffer->values[3*led+2]);
+ int i;
+ for (i = 0; i < buffer->colorsPerLed; i++)
+ {
+ lua_pushnumber(L, buffer->values[buffer->colorsPerLed*led+i]);
+ }
- return 3;
+ return buffer->colorsPerLed;
}
-static int ICACHE_FLASH_ATTR ws2812_buffer_set(lua_State* L) {
+static int ws2812_buffer_set(lua_State* L) {
ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1);
- const int led = luaL_checkinteger(L, 2);
+ const int led = luaL_checkinteger(L, 2) - 1;
luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected");
luaL_argcheck(L, led >= 0 && led < buffer->size, 2, "index out of range");
@@ -217,18 +191,18 @@ static int ICACHE_FLASH_ATTR ws2812_buffer_set(lua_State* L) {
int type = lua_type(L, 3);
if(type == LUA_TTABLE)
{
- // Get g,r,b values and push them on stash
- lua_rawgeti(L, 3, 1);
- lua_rawgeti(L, 3, 2);
- lua_rawgeti(L, 3, 3);
+ int i;
+ for (i = 0; i < buffer->colorsPerLed; i++)
+ {
+ // Get value and push it on stack
+ lua_rawgeti(L, 3, i+1);
- // Convert them as int and store them in buffer
- buffer->values[3*led+0] = lua_tonumber(L, -3);
- buffer->values[3*led+1] = lua_tonumber(L, -2);
- buffer->values[3*led+2] = lua_tonumber(L, -1);
+ // Convert it as int and store them in buffer
+ buffer->values[buffer->colorsPerLed*led+i] = lua_tonumber(L, -1);
+ }
// Clean up the stack
- lua_pop(L, 3);
+ lua_pop(L, buffer->colorsPerLed);
}
else if(type == LUA_TSTRING)
{
@@ -236,24 +210,26 @@ static int ICACHE_FLASH_ATTR ws2812_buffer_set(lua_State* L) {
const char * buf = lua_tolstring(L, 3, &len);
// Overflow check
- if( 3*led + len > 3*buffer->size )
+ if( buffer->colorsPerLed*led + len > buffer->colorsPerLed*buffer->size )
{
return luaL_error(L, "string size will exceed strip length");
}
- c_memcpy(&buffer->values[3*led], buf, len);
+ c_memcpy(&buffer->values[buffer->colorsPerLed*led], buf, len);
}
else
{
- buffer->values[3*led+0] = luaL_checkinteger(L, 3);
- buffer->values[3*led+1] = luaL_checkinteger(L, 4);
- buffer->values[3*led+2] = luaL_checkinteger(L, 5);
+ int i;
+ for (i = 0; i < buffer->colorsPerLed; i++)
+ {
+ buffer->values[buffer->colorsPerLed*led+i] = luaL_checkinteger(L, 3+i);
+ }
}
return 0;
}
-static int ICACHE_FLASH_ATTR ws2812_buffer_size(lua_State* L) {
+static int ws2812_buffer_size(lua_State* L) {
ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1);
luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected");
@@ -263,20 +239,13 @@ static int ICACHE_FLASH_ATTR ws2812_buffer_size(lua_State* L) {
return 1;
}
-static int ICACHE_FLASH_ATTR ws2812_buffer_write(lua_State* L) {
+static int ws2812_buffer_write(lua_State* L) {
ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1);
- const uint8_t pin = luaL_checkinteger(L, 2);
luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected");
- // Initialize the output pin
- platform_gpio_mode(pin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT);
- platform_gpio_write(pin, 0);
-
// Send the buffer
- ets_intr_lock();
- ws2812_write(pin_num[pin], &buffer->values[0], 3*buffer->size);
- ets_intr_unlock();
+ ws2812_write(buffer->values, buffer->colorsPerLed*buffer->size);
return 0;
}
@@ -295,9 +264,9 @@ static const LUA_REG_TYPE ws2812_buffer_map[] =
static const LUA_REG_TYPE ws2812_map[] =
{
- { LSTRKEY( "writergb" ), LFUNCVAL( ws2812_writergb )},
{ LSTRKEY( "write" ), LFUNCVAL( ws2812_writegrb )},
{ LSTRKEY( "newBuffer" ), LFUNCVAL( ws2812_new_buffer )},
+ { LSTRKEY( "init" ), LFUNCVAL( ws2812_init )},
{ LNILKEY, LNILVAL}
};
diff --git a/docs/en/modules/ws2812.md b/docs/en/modules/ws2812.md
index c73373d0..408bc505 100644
--- a/docs/en/modules/ws2812.md
+++ b/docs/en/modules/ws2812.md
@@ -1,57 +1,164 @@
# WS2812 Module
| Since | Origin / Contributor | Maintainer | Source |
| :----- | :-------------------- | :---------- | :------ |
-| 2015-02-05 | [Till Klocke](https://github.com/dereulenspiegel) | [Till Klocke](https://github.com/dereulenspiegel) | [ws2812.c](../../../app/modules/ws2812.c)|
+| 2015-02-05 | [Till Klocke](https://github.com/dereulenspiegel), [Thomas Soëte](https://github.com/Alkorin) | [Till Klocke](https://github.com/dereulenspiegel) | [ws2812.c](../../../app/modules/ws2812.c)|
+ws2812 is a library to handle ws2812-like led strips.
+It works at least on WS2812, WS2812b, APA104, SK6812 (RGB or RGBW).
-## ws2812.write()
-Send GRB data in 8 bits to a WS2812 chain.
+The library uses UART1 routed on GPIO2 (Pin D4 on NodeMCU DEVKIT) to
+generate the bitstream.
-#### Syntax
-`ws2812.writegrb(pin, string)`
+## ws2812.init()
+Initialize UART1 and GPIO2, should be called once and before write()
#### Parameters
-- `pin` is ignored. Only works with GPIO2.
-- `string` payload to be sent to one or more WS2812 LEDs.
- It should be composed from a GRB triplet per element.
- - `G1` the first pixel's Green channel (0-255)
- - `R1` the first pixel's Red channel (0-255)
- - `B1` the first pixel's Blue channel (0-255)
- ... You can connect a lot of WS2812 ...
- - `G2`, `R2`, `B2` are the next WS2812's Green, Red, and Blue channel parameters
+none
#### Returns
`nil`
-```lua
-g = 0
-r = 255
-b = 0
-leds_grb = string.char(g,r,b, g,r,b)
-ws2812.write(2, leds_grb) -- turn two WS2812Bs to red, connected to pin GPIO2
-```
-
-## ws2812.writergb()
-Send GRB data in 8bits to a WS2812 chain.
+## ws2812.write()
+Send data to a led strip using its native format which is generally Green,Red,Blue for RGB strips
+and Green,Red,Blue,White for RGBW strips.
#### Syntax
-`ws2812.writergb(pin, string)`
+`ws2812.write(string)`
#### Parameters
-- `pin` is ignored. Only works with GPIO2.
-- `string` payload to be sent to one or more WS2812 LEDs.
- It should be composed from an RGB triplet per element.
- - `R1` the first pixel's Red channel (0-255)
- - `G1` the first pixel's Green channel (0-255)
- - `B1` the first pixel's Blue channel (0-255)
- ... You can connect a lot of WS2812 ...
- - `R2`, `G2`, `B2` are the next WS2812's Red, Green, and Blue channel parameters
+- `string` payload to be sent to one or more WS2812 like leds.
#### Returns
`nil`
#### Example
```lua
-leds_rgb = string.char(255,0,0, 0,255,0, 0,0,255)
-ws2812.writergb(2, leds_rgb) -- turn three WS2812Bs to red, green, and blue respectively
+ws2812.init()
+ws2812.write(string.char(255,0,0,255,0,0) -- turn the two first RGB leds to green
```
+
+```lua
+ws2812.init()
+ws2812.write(string.char(0,0,0,255,0,0,0,255) -- turn the two first RGBW leds to white
+```
+
+# Buffer module
+For more advanced animations, it is useful to keep a "framebuffer" of the strip,
+interact with it and flush it to the strip.
+
+For this purpose, the ws2812 library offers a read/write buffer.
+
+#### Example
+Led chaser with a RGBW strip
+```lua
+local i, b = 0, ws2812.newBuffer(300, 4); b:fill(0,0,0,0); tmr.alarm(0, 50, 1, function()
+ i=i+1
+ b:fade(2)
+ b:set(i%b:size()+1, 0, 0, 0, 255)
+ b:write()
+end)
+```
+
+## ws2812.newBuffer()
+Allocate a new memory buffer to store led values.
+
+#### Syntax
+`ws2812.newBuffer(numberOfLeds, bytesPerLed)`
+
+#### Parameters
+ - `numberOfLeds` length of the led strip
+ - `bytesPerLed` 3 for RGB strips and 4 for RGBW strips
+
+#### Returns
+`ws2812.buffer`
+
+## ws2812.buffer:get()
+Return the value at the given position
+
+#### Syntax
+`buffer:get(index)`
+
+#### Parameters
+ - `index` position in the buffer (1 for first led)
+
+#### Returns
+`(color)`
+
+#### Example
+```lua
+buffer:get(2) -- return the color of the second led
+```
+## ws2812.buffer:set()
+Set the value at the given position
+
+#### Syntax
+`buffer:set(index, color)`
+
+#### Parameters
+ - `index` position in the buffer (1 for the first led)
+ - `color` bytes of the color
+
+#### Returns
+`nil`
+
+#### Example
+```lua
+buffer:set(1, 255, 0, 0) -- set the first led green for a RGB strip
+```
+## ws2812.buffer:size()
+Return the size of the buffer in number of leds
+
+#### Syntax
+`buffer:size()`
+
+#### Parameters
+none
+
+#### Returns
+`int`
+
+## ws2812.buffer:fill()
+Fill the buffer with the given color.
+The number of given bytes must match the number of bytesPerLed of the buffer
+
+#### Syntax
+`buffer:fill(color)`
+
+#### Parameters
+ - `color` bytes of the color
+
+#### Returns
+`nil`
+
+#### Example
+```lua
+buffer:fill(0, 0, 0) -- fill the buffer with black for a RGB strip
+```
+## ws2812.buffer:fade()
+Divide each byte of each led by the given value. Useful for a fading effect
+
+#### Syntax
+`buffer:fade(value)`
+
+#### Parameters
+ - `value` value by which divide each byte
+
+#### Returns
+`nil`
+
+#### Example
+```lua
+buffer:fade(2)
+```
+## ws2812.buffer:write()
+Output the buffer to the led strip
+
+#### Syntax
+`buffer:write()`
+
+#### Parameters
+none
+
+#### Returns
+`nil`
+