Handle two WS2812 strips in parallel using the two UARTs through GPIO2 and TXD0 (#1310)

This commit is contained in:
Thomas Soëte 2016-07-20 22:28:47 +02:00 committed by Marcel Stör
parent 56839fb20b
commit 854ad7c80f
2 changed files with 160 additions and 50 deletions

View File

@ -9,6 +9,8 @@
#include "osapi.h" #include "osapi.h"
#define CANARY_VALUE 0x32383132 #define CANARY_VALUE 0x32383132
#define MODE_SINGLE 0
#define MODE_DUAL 1
#define FADE_IN 1 #define FADE_IN 1
#define FADE_OUT 0 #define FADE_OUT 0
@ -23,15 +25,27 @@ typedef struct {
uint8_t values[0]; uint8_t values[0];
} ws2812_buffer; } ws2812_buffer;
// Init UART1 to be able to stream WS2812 data // Init UART1 to be able to stream WS2812 data to GPIO2 pin
// We use GPIO2 as output pin // If DUAL mode is selected, init UART0 to stream to TXD0 as well
static void ws2812_init() { // 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 // Configure UART1
// Set baudrate of UART1 to 3200000 // Set baudrate of UART1 to 3200000
WRITE_PERI_REG(UART_CLKDIV(1), UART_CLK_FREQ / 3200000); WRITE_PERI_REG(UART_CLKDIV(1), UART_CLK_FREQ / 3200000);
// Set UART Configuration No parity / 6 DataBits / 1 StopBits / Invert TX // 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)); 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 // Pull GPIO2 down
platform_gpio_mode(4, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT); platform_gpio_mode(4, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT);
platform_gpio_write(4, 0); platform_gpio_write(4, 0);
@ -46,12 +60,11 @@ static void ws2812_init() {
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK); PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_U1TXD_BK);
} }
// Stream data using UART1 routed to GPIO2 // Stream data using UART1 routed to GPIO2
// ws2812.init() should be called first // ws2812.init() should be called first
// //
// NODE_DEBUG should not be activated because it also uses UART1 // 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 // 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 // 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 // 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 }; 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 { 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 // Fill the buffer
// (Less than 124 bytes in the buffer) WRITE_PERI_REG(UART_FIFO(1), _uartData[(value >> 6) & 3]);
while (((READ_PERI_REG(UART_STATUS(1)) >> UART_TXFIFO_CNT_S) & UART_TXFIFO_CNT) > 124); WRITE_PERI_REG(UART_FIFO(1), _uartData[(value >> 4) & 3]);
WRITE_PERI_REG(UART_FIFO(1), _uartData[(value >> 2) & 3]);
// Fill the buffer WRITE_PERI_REG(UART_FIFO(1), _uartData[(value >> 0) & 3]);
WRITE_PERI_REG(UART_FIFO(1), _uartData[(value >> 6) & 3]); }
WRITE_PERI_REG(UART_FIFO(1), _uartData[(value >> 4) & 3]); // Same for the second buffer
WRITE_PERI_REG(UART_FIFO(1), _uartData[(value >> 2) & 3]); if (pixels2 < end2 && (((READ_PERI_REG(UART_STATUS(0)) >> UART_TXFIFO_CNT_S) & UART_TXFIFO_CNT) <= 124)) {
WRITE_PERI_REG(UART_FIFO(1), _uartData[(value >> 0) & 3]); uint8_t value = *pixels2++;
} while(pixels < end);
// 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") // 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, 255, 0)) sets the first LED red.
// ws2812.write(string.char(0, 0, 255):rep(10)) sets ten LEDs blue. // 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. // 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; // In DUAL mode 'ws2812.init(ws2812.DUAL)', you may pass a second string as parameter
const char *values = luaL_checklstring(L, 1, &length); // 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 // First mandatory parameter
ws2812_write((uint8_t*) values, length); 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; return 0;
} }
@ -172,7 +246,7 @@ static int ws2812_buffer_fade(lua_State* L) {
{ {
*p++ /= fade; *p++ /= fade;
} }
else else
{ {
// as fade in can result in value overflow, an int is used to perform the check afterwards // as fade in can result in value overflow, an int is used to perform the check afterwards
val = *p * fade; 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"); luaL_argcheck(L, shiftValue > 0-buffer->size && shiftValue < buffer->size, 2, "shifting more elements than buffer size");
int shift = shiftValue >= 0 ? shiftValue : -shiftValue; int shift = shiftValue >= 0 ? shiftValue : -shiftValue;
// check if we want to shift at all // check if we want to shift at all
if (shift == 0) if (shift == 0)
{ {
return 0; return 0;
} }
uint8_t * tmp_pixels = luaM_malloc(L, buffer->colorsPerLed * sizeof(uint8_t) * shift); uint8_t * tmp_pixels = luaM_malloc(L, buffer->colorsPerLed * sizeof(uint8_t) * shift);
int i,j; int i,j;
size_t shift_len, remaining_len; size_t shift_len, remaining_len;
// calculate length of shift section and remaining section // calculate length of shift section and remaining section
shift_len = shift*buffer->colorsPerLed; shift_len = shift*buffer->colorsPerLed;
remaining_len = (buffer->size-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) // 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); 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"); luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected");
// Send the buffer // Send the buffer
ws2812_write(buffer->values, buffer->colorsPerLed*buffer->size); ws2812_write_data(buffer->values, buffer->colorsPerLed*buffer->size, 0, 0);
return 0; return 0;
} }
@ -336,25 +410,27 @@ static int ws2812_buffer_write(lua_State* L) {
static const LUA_REG_TYPE ws2812_buffer_map[] = static const LUA_REG_TYPE ws2812_buffer_map[] =
{ {
{ LSTRKEY( "fade" ), LFUNCVAL( ws2812_buffer_fade )}, { LSTRKEY( "fade" ), LFUNCVAL( ws2812_buffer_fade )},
{ LSTRKEY( "shift" ), LFUNCVAL( ws2812_buffer_shift )},
{ LSTRKEY( "fill" ), LFUNCVAL( ws2812_buffer_fill )}, { LSTRKEY( "fill" ), LFUNCVAL( ws2812_buffer_fill )},
{ LSTRKEY( "get" ), LFUNCVAL( ws2812_buffer_get )}, { LSTRKEY( "get" ), LFUNCVAL( ws2812_buffer_get )},
{ LSTRKEY( "set" ), LFUNCVAL( ws2812_buffer_set )}, { LSTRKEY( "set" ), LFUNCVAL( ws2812_buffer_set )},
{ LSTRKEY( "size" ), LFUNCVAL( ws2812_buffer_size )}, { LSTRKEY( "size" ), LFUNCVAL( ws2812_buffer_size )},
{ LSTRKEY( "shift" ), LFUNCVAL( ws2812_buffer_shift )},
{ LSTRKEY( "write" ), LFUNCVAL( ws2812_buffer_write )}, { LSTRKEY( "write" ), LFUNCVAL( ws2812_buffer_write )},
{ LSTRKEY( "__index" ), LROVAL ( ws2812_buffer_map )}, { LSTRKEY( "__index" ), LROVAL( ws2812_buffer_map )},
{ LNILKEY, LNILVAL} { LNILKEY, LNILVAL}
}; };
static const LUA_REG_TYPE ws2812_map[] = static const LUA_REG_TYPE ws2812_map[] =
{ {
{ LSTRKEY( "write" ), LFUNCVAL( ws2812_writegrb )}, { LSTRKEY( "init" ), LFUNCVAL( ws2812_init )},
{ LSTRKEY( "newBuffer" ), LFUNCVAL( ws2812_new_buffer )}, { LSTRKEY( "newBuffer" ), LFUNCVAL( ws2812_new_buffer )},
{ LSTRKEY( "init" ), LFUNCVAL( ws2812_init )}, { LSTRKEY( "write" ), LFUNCVAL( ws2812_write )},
{ LSTRKEY( "FADE_IN" ), LNUMVAL( FADE_IN ) }, { LSTRKEY( "FADE_IN" ), LNUMVAL( FADE_IN ) },
{ LSTRKEY( "FADE_OUT" ),LNUMVAL( FADE_OUT ) }, { LSTRKEY( "FADE_OUT" ), LNUMVAL( FADE_OUT ) },
{ LSTRKEY( "SHIFT_LOGICAL" ),LNUMVAL( SHIFT_LOGICAL ) }, { LSTRKEY( "MODE_SINGLE" ), LNUMVAL( MODE_SINGLE ) },
{ LSTRKEY( "SHIFT_CIRCULAR" ),LNUMVAL( SHIFT_CIRCULAR ) }, { LSTRKEY( "MODE_DUAL" ), LNUMVAL( MODE_DUAL ) },
{ LSTRKEY( "SHIFT_LOGICAL" ), LNUMVAL( SHIFT_LOGICAL ) },
{ LSTRKEY( "SHIFT_CIRCULAR" ), LNUMVAL( SHIFT_CIRCULAR ) },
{ LNILKEY, LNILVAL} { LNILKEY, LNILVAL}
}; };

View File

@ -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). 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 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() **WARNING**: In dual mode, you will loose access to the Lua's console
Initialize UART1 and GPIO2, should be called once and before write() 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 #### 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 #### Returns
`nil` `nil`
## ws2812.write() ## 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. and Green,Red,Blue,White for RGBW strips.
#### Syntax #### Syntax
`ws2812.write(string)` `ws2812.write(data1, [data2])`
#### Parameters #### 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 #### Returns
`nil` `nil`
@ -34,12 +49,22 @@ and Green,Red,Blue,White for RGBW strips.
#### Example #### Example
```lua ```lua
ws2812.init() 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 ```lua
ws2812.init() 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 # Buffer module
@ -51,11 +76,12 @@ For this purpose, the ws2812 library offers a read/write buffer.
#### Example #### Example
Led chaser with a RGBW strip Led chaser with a RGBW strip
```lua ```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 i=i+1
b:fade(2) b:fade(2)
b:set(i%b:size()+1, 0, 0, 0, 255) b:set(i%b:size()+1, 0, 0, 0, 255)
b:write() ws2812.write(b)
end) end)
``` ```
@ -105,6 +131,15 @@ Set the value at the given position
```lua ```lua
buffer:set(1, 255, 0, 0) -- set the first led green for a RGB strip 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() ## ws2812.buffer:size()
Return the size of the buffer in number of leds Return the size of the buffer in number of leds
@ -181,4 +216,3 @@ none
#### Returns #### Returns
`nil` `nil`