From 854ad7c80fb80c0efec683db5c275f01f60b078a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20So=C3=ABte?= Date: Wed, 20 Jul 2016 22:28:47 +0200 Subject: [PATCH] Handle two WS2812 strips in parallel using the two UARTs through GPIO2 and TXD0 (#1310) --- app/modules/ws2812.c | 152 ++++++++++++++++++++++++++++---------- docs/en/modules/ws2812.md | 58 ++++++++++++--- 2 files changed, 160 insertions(+), 50 deletions(-) diff --git a/app/modules/ws2812.c b/app/modules/ws2812.c index 65a67360..af3faa8d 100644 --- a/app/modules/ws2812.c +++ b/app/modules/ws2812.c @@ -9,6 +9,8 @@ #include "osapi.h" #define CANARY_VALUE 0x32383132 +#define MODE_SINGLE 0 +#define MODE_DUAL 1 #define FADE_IN 1 #define FADE_OUT 0 @@ -23,15 +25,27 @@ typedef struct { uint8_t values[0]; } ws2812_buffer; -// Init UART1 to be able to stream WS2812 data -// We use GPIO2 as output pin -static void ws2812_init() { +// Init UART1 to be able to stream WS2812 data to GPIO2 pin +// If DUAL mode is selected, init UART0 to stream to TXD0 as well +// You HAVE to redirect LUA's output somewhere else +static void ws2812_init(lua_State* L) { + const int mode = luaL_optinteger(L, 1, MODE_SINGLE); + luaL_argcheck(L, mode == MODE_SINGLE || mode == MODE_DUAL, 1, "ws2812.SINGLE or ws2812.DUAL expected"); + // 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)); + if (mode == MODE_DUAL) { + // Configure UART0 + // Set baudrate of UART0 to 3200000 + WRITE_PERI_REG(UART_CLKDIV(0), UART_CLK_FREQ / 3200000); + // Set UART Configuration No parity / 6 DataBits / 1 StopBits / Invert TX + WRITE_PERI_REG(UART_CONF0(0), 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); @@ -46,12 +60,11 @@ static void ws2812_init() { 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) { +static void ICACHE_RAM_ATTR ws2812_write_data(const uint8_t *pixels, uint32_t length, const uint8_t *pixels2, uint32_t length2) { // 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 @@ -62,23 +75,33 @@ static void ICACHE_RAM_ATTR ws2812_write(uint8_t *pixels, uint32_t length) { // 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; + const uint8_t *end = pixels + length; + const uint8_t *end2 = pixels2 + length2; do { - uint8_t value = *pixels++; + // If something to send for first buffer and enough room + // in FIFO buffer (we wants to write 4 bytes, so less than + // 124 in the buffer) + if (pixels < end && (((READ_PERI_REG(UART_STATUS(1)) >> UART_TXFIFO_CNT_S) & UART_TXFIFO_CNT) <= 124)) { + uint8_t value = *pixels++; - // Wait enough space in the FIFO buffer - // (Less than 124 bytes in the buffer) - while (((READ_PERI_REG(UART_STATUS(1)) >> UART_TXFIFO_CNT_S) & UART_TXFIFO_CNT) > 124); - - // Fill the buffer - WRITE_PERI_REG(UART_FIFO(1), _uartData[(value >> 6) & 3]); - WRITE_PERI_REG(UART_FIFO(1), _uartData[(value >> 4) & 3]); - WRITE_PERI_REG(UART_FIFO(1), _uartData[(value >> 2) & 3]); - WRITE_PERI_REG(UART_FIFO(1), _uartData[(value >> 0) & 3]); - - } while(pixels < end); + // Fill the buffer + WRITE_PERI_REG(UART_FIFO(1), _uartData[(value >> 6) & 3]); + WRITE_PERI_REG(UART_FIFO(1), _uartData[(value >> 4) & 3]); + WRITE_PERI_REG(UART_FIFO(1), _uartData[(value >> 2) & 3]); + WRITE_PERI_REG(UART_FIFO(1), _uartData[(value >> 0) & 3]); + } + // Same for the second buffer + if (pixels2 < end2 && (((READ_PERI_REG(UART_STATUS(0)) >> UART_TXFIFO_CNT_S) & UART_TXFIFO_CNT) <= 124)) { + uint8_t value = *pixels2++; + // Fill the buffer + WRITE_PERI_REG(UART_FIFO(0), _uartData[(value >> 6) & 3]); + WRITE_PERI_REG(UART_FIFO(0), _uartData[(value >> 4) & 3]); + WRITE_PERI_REG(UART_FIFO(0), _uartData[(value >> 2) & 3]); + WRITE_PERI_REG(UART_FIFO(0), _uartData[(value >> 0) & 3]); + } + } while(pixels < end || pixels2 < end2); // Until there is still something to send } // Lua: ws2812.write("string") @@ -89,12 +112,63 @@ static void ICACHE_RAM_ATTR ws2812_write(uint8_t *pixels, uint32_t length) { // 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 *values = luaL_checklstring(L, 1, &length); +// +// In DUAL mode 'ws2812.init(ws2812.DUAL)', you may pass a second string as parameter +// It will be sent through TXD0 in parallel +static int ws2812_write(lua_State* L) { + size_t length1, length2; + const char *buffer1, *buffer2; - // Send the buffer - ws2812_write((uint8_t*) values, length); + // First mandatory parameter + int type = lua_type(L, 1); + if (type == LUA_TNIL) + { + buffer1 = 0; + length1 = 0; + } + else if(type == LUA_TSTRING) + { + buffer1 = lua_tolstring(L, 1, &length1); + } + else if (type == LUA_TUSERDATA) + { + ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1); + luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected"); + + buffer1 = buffer->values; + length1 = buffer->colorsPerLed*buffer->size; + } + else + { + luaL_argerror(L, 1, "ws2812.buffer or string expected"); + } + + // Second optionnal parameter + type = lua_type(L, 2); + if (type == LUA_TNONE || type == LUA_TNIL) + { + buffer2 = 0; + length2 = 0; + } + else if (type == LUA_TSTRING) + { + buffer2 = lua_tolstring(L, 2, &length2); + } + else if (type == LUA_TUSERDATA) + { + ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 2); + luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 2, "ws2812.buffer expected"); + + buffer2 = buffer->values; + length2 = buffer->colorsPerLed*buffer->size; + } + else + { + luaL_argerror(L, 2, "ws2812.buffer or string expected"); + } + + // Send the buffers + ws2812_write_data(buffer1, length1, buffer2, length2); return 0; } @@ -172,7 +246,7 @@ static int ws2812_buffer_fade(lua_State* L) { { *p++ /= fade; } - else + else { // as fade in can result in value overflow, an int is used to perform the check afterwards val = *p * fade; @@ -194,21 +268,21 @@ static int ws2812_buffer_shift(lua_State* L) { luaL_argcheck(L, shiftValue > 0-buffer->size && shiftValue < buffer->size, 2, "shifting more elements than buffer size"); int shift = shiftValue >= 0 ? shiftValue : -shiftValue; - + // check if we want to shift at all if (shift == 0) { return 0; } - + uint8_t * tmp_pixels = luaM_malloc(L, buffer->colorsPerLed * sizeof(uint8_t) * shift); int i,j; size_t shift_len, remaining_len; // calculate length of shift section and remaining section shift_len = shift*buffer->colorsPerLed; remaining_len = (buffer->size-shift)*buffer->colorsPerLed; - - if (shiftValue > 0) + + if (shiftValue > 0) { // Store the values which are moved out of the array (last n pixels) c_memcpy(tmp_pixels, &buffer->values[(buffer->size-shift)*buffer->colorsPerLed], shift_len); @@ -328,7 +402,7 @@ static int ws2812_buffer_write(lua_State* L) { luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected"); // Send the buffer - ws2812_write(buffer->values, buffer->colorsPerLed*buffer->size); + ws2812_write_data(buffer->values, buffer->colorsPerLed*buffer->size, 0, 0); return 0; } @@ -336,25 +410,27 @@ static int ws2812_buffer_write(lua_State* L) { static const LUA_REG_TYPE ws2812_buffer_map[] = { { LSTRKEY( "fade" ), LFUNCVAL( ws2812_buffer_fade )}, - { LSTRKEY( "shift" ), LFUNCVAL( ws2812_buffer_shift )}, { LSTRKEY( "fill" ), LFUNCVAL( ws2812_buffer_fill )}, { LSTRKEY( "get" ), LFUNCVAL( ws2812_buffer_get )}, { LSTRKEY( "set" ), LFUNCVAL( ws2812_buffer_set )}, { LSTRKEY( "size" ), LFUNCVAL( ws2812_buffer_size )}, + { LSTRKEY( "shift" ), LFUNCVAL( ws2812_buffer_shift )}, { LSTRKEY( "write" ), LFUNCVAL( ws2812_buffer_write )}, - { LSTRKEY( "__index" ), LROVAL ( ws2812_buffer_map )}, + { LSTRKEY( "__index" ), LROVAL( ws2812_buffer_map )}, { LNILKEY, LNILVAL} }; static const LUA_REG_TYPE ws2812_map[] = { - { LSTRKEY( "write" ), LFUNCVAL( ws2812_writegrb )}, - { LSTRKEY( "newBuffer" ), LFUNCVAL( ws2812_new_buffer )}, - { LSTRKEY( "init" ), LFUNCVAL( ws2812_init )}, - { LSTRKEY( "FADE_IN" ), LNUMVAL( FADE_IN ) }, - { LSTRKEY( "FADE_OUT" ),LNUMVAL( FADE_OUT ) }, - { LSTRKEY( "SHIFT_LOGICAL" ),LNUMVAL( SHIFT_LOGICAL ) }, - { LSTRKEY( "SHIFT_CIRCULAR" ),LNUMVAL( SHIFT_CIRCULAR ) }, + { LSTRKEY( "init" ), LFUNCVAL( ws2812_init )}, + { LSTRKEY( "newBuffer" ), LFUNCVAL( ws2812_new_buffer )}, + { LSTRKEY( "write" ), LFUNCVAL( ws2812_write )}, + { LSTRKEY( "FADE_IN" ), LNUMVAL( FADE_IN ) }, + { LSTRKEY( "FADE_OUT" ), LNUMVAL( FADE_OUT ) }, + { LSTRKEY( "MODE_SINGLE" ), LNUMVAL( MODE_SINGLE ) }, + { LSTRKEY( "MODE_DUAL" ), LNUMVAL( MODE_DUAL ) }, + { LSTRKEY( "SHIFT_LOGICAL" ), LNUMVAL( SHIFT_LOGICAL ) }, + { LSTRKEY( "SHIFT_CIRCULAR" ), LNUMVAL( SHIFT_CIRCULAR ) }, { LNILKEY, LNILVAL} }; diff --git a/docs/en/modules/ws2812.md b/docs/en/modules/ws2812.md index 0c7ce3da..21f671aa 100644 --- a/docs/en/modules/ws2812.md +++ b/docs/en/modules/ws2812.md @@ -7,26 +7,41 @@ ws2812 is a library to handle ws2812-like led strips. It works at least on WS2812, WS2812b, APA104, SK6812 (RGB or RGBW). The library uses UART1 routed on GPIO2 (Pin D4 on NodeMCU DEVKIT) to -generate the bitstream. +generate the bitstream. It can use UART0 routed to TXD0 as well to +handle two led strips at the same time. -## ws2812.init() -Initialize UART1 and GPIO2, should be called once and before write() +**WARNING**: In dual mode, you will loose access to the Lua's console +through the serial port (it will be reconfigured to support WS2812-like +protocol). If you want to keep access to Lua's console, you will have to +use an other input channel like a TCP server (see [example](https://github.com/nodemcu/nodemcu-firmware/blob/master/examples/telnet.lua)) + +## ws2812.init(mode) +Initialize UART1 and GPIO2, should be called once and before write(). +Initialize UART0 (TXD0) too if `ws2812.MODE_DUAL` is set. #### Parameters -none +- `mode` (optional) either `ws2812.MODE_SINGLE` (default if omitted) or `ws2812.MODE_DUAL`. +In `ws2812.MODE_DUAL` mode you will be able to handle two strips in parallel but will lose access +to Lua's serial console as it shares the same UART and PIN. #### Returns `nil` ## ws2812.write() -Send data to a led strip using its native format which is generally Green,Red,Blue for RGB strips +Send data to one or two 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.write(string)` +`ws2812.write(data1, [data2])` #### Parameters -- `string` payload to be sent to one or more WS2812 like leds. +- `data1` payload to be sent to one or more WS2812 like leds through GPIO2 +- `data2` (optional) payload to be sent to one or more WS2812 like leds through TXD0 (`ws2812.MODE_DUAL` mode required) + +Payload type could be: +- `nil` nothing is done +- `string` representing bytes to send +- `ws2812.buffer` see [Buffer module](#buffer-module) #### Returns `nil` @@ -34,12 +49,22 @@ and Green,Red,Blue,White for RGBW strips. #### Example ```lua ws2812.init() -ws2812.write(string.char(255,0,0,255,0,0) -- turn the two first RGB leds to green +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 +ws2812.write(string.char(0, 0, 0, 255, 0, 0, 0, 255)) -- turn the two first RGBW leds to white +``` + +```lua +ws2812.init(ws2812.MODE_DUAL) +ws2812.write(string.char(255, 0, 0, 255, 0, 0), string.char(0, 255, 0, 0, 255, 0)) -- turn the two first RGB leds to green on the first strip and red on the second strip +``` + +```lua +ws2812.init(ws2812.MODE_DUAL) +ws2812.write(nil, string.char(0, 255, 0, 0, 255, 0)) -- turn the two first RGB leds to red on the second strip, do nothing on the first ``` # Buffer module @@ -51,11 +76,12 @@ 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() +ws2812.init() +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() + ws2812.write(b) end) ``` @@ -105,6 +131,15 @@ Set the value at the given position ```lua buffer:set(1, 255, 0, 0) -- set the first led green for a RGB strip ``` + +```lua +buffer:set(1, {255, 0, 0}) -- set the first led green for a RGB strip +``` + +```lua +buffer:set(1, string.char(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 @@ -181,4 +216,3 @@ none #### Returns `nil` -