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"
#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}
};

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).
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`