Commit 0577c8af authored by Thomas Soëte's avatar Thomas Soëte Committed by Marcel Stör
Browse files

Finalize work on ws2812 module

* Fix bug on first write
	Pin is 'HIGH' at reset, so we need to pull it down and generate a
	reset.

* Move init code to flash section, not needed to be in iram.

* Remove pin choice in API

* Remove lock in ws2812_buffer_write

* Remove naked malloc

* Drop ws2812_writergb

* Drop support of ws2812.buffers to ws2812_writegrb should use ws2812.buffers:write

* Add support for <>3 colors per leds strips (RGBW)

* Remove ICACHE_FLASH_ATTR

* Add static const on _uartData to avoid initialization penalty
parent 216c69f1
#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.
//
// 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);
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;
}
// 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.
// Lua: ws2812.write("string")
// Byte triples in the string 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);
// 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 *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);
}
const char *values = luaL_checklstring(L, 1, &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));
for (i = 0; i < buffer->colorsPerLed; i++)
{
colors[i] = luaL_checkinteger(L, 2+i);
}
// Fill buffer
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->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}
};
......
# 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).
The library uses UART1 routed on GPIO2 (Pin D4 on NodeMCU DEVKIT) to
generate the bitstream.
## ws2812.init()
Initialize UART1 and GPIO2, should be called once and before write()
#### Parameters
none
#### Returns
`nil`
## ws2812.write()
Send GRB data in 8 bits to a WS2812 chain.
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.writegrb(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 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)<br />
... You can connect a lot of WS2812 ...
- `G2`, `R2`, `B2` are the next WS2812's Green, Red, and Blue channel parameters
- `string` payload to be sent to one or more WS2812 like leds.
#### Returns
`nil`
#### Example
```lua
ws2812.init()
ws2812.write(string.char(255,0,0,255,0,0) -- turn the two first RGB leds to green
```
```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.init()
ws2812.write(string.char(0,0,0,255,0,0,0,255) -- turn the two first RGBW leds to white
```
## ws2812.writergb()
Send GRB data in 8bits to a WS2812 chain.
# 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
`ws2812.writergb(pin, string)`
`buffer:set(index, color)`
#### 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)<br />
... You can connect a lot of WS2812 ...
- `R2`, `G2`, `B2` are the next WS2812's Red, Green, and Blue channel parameters
- `index` position in the buffer (1 for the first led)
- `color` bytes of the color
#### 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
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`
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment