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` +