Add ws2812 module.
This commit is contained in:
parent
61bd4821b3
commit
1866e6f457
|
@ -135,4 +135,10 @@ config LUA_MODULE_WIFI
|
|||
help
|
||||
Includes the WiFi module (recommended).
|
||||
|
||||
config LUA_MODULE_WS2812
|
||||
bool "WS2812 module"
|
||||
default "n"
|
||||
help
|
||||
Includes the ws2812 module.
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -0,0 +1,533 @@
|
|||
|
||||
#include "module.h"
|
||||
#include "lauxlib.h"
|
||||
#include "lmem.h"
|
||||
#include "platform.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define FADE_IN 1
|
||||
#define FADE_OUT 0
|
||||
#define SHIFT_LOGICAL 0
|
||||
#define SHIFT_CIRCULAR 1
|
||||
|
||||
|
||||
typedef struct {
|
||||
int size;
|
||||
uint8_t colorsPerLed;
|
||||
uint8_t values[0];
|
||||
} ws2812_buffer;
|
||||
|
||||
static void ws2812_cleanup( lua_State *L, int pop )
|
||||
{
|
||||
if (pop)
|
||||
lua_pop( L, pop );
|
||||
platform_ws2812_release();
|
||||
}
|
||||
|
||||
// Lua: ws2812.write("string")
|
||||
// Byte triples in the string are interpreted as G R B values.
|
||||
//
|
||||
// ws2812.write({pin = 4, data = string.char(0, 255, 0)}) sets the first LED red.
|
||||
// ws2812.write({pin = 4, data = string.char(0, 0, 255):rep(10)}) sets ten LEDs blue.
|
||||
// ws2812.write({pin = 4, data = string.char(255, 0, 0, 255, 255, 255)}) first LED green, second LED white.
|
||||
static int ws2812_write( lua_State* L )
|
||||
{
|
||||
int top = lua_gettop( L );
|
||||
|
||||
for (int stack = 1; stack <= top; stack++) {
|
||||
if (lua_type( L, stack ) != LUA_TTABLE) {
|
||||
ws2812_cleanup( L, 0 );
|
||||
luaL_checktype( L, stack, LUA_TTABLE ); // trigger error
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
// retrieve pin
|
||||
//
|
||||
lua_getfield( L, stack, "pin" );
|
||||
if (!lua_isnumber( L, -1 )) {
|
||||
ws2812_cleanup( L, 1 );
|
||||
return luaL_argerror( L, stack, "invalid pin" );
|
||||
}
|
||||
int gpio_num = luaL_checkint( L, -1 );
|
||||
lua_pop( L, 1 );
|
||||
|
||||
//
|
||||
// retrieve data
|
||||
//
|
||||
lua_getfield( L, stack, "data" );
|
||||
|
||||
const char *data;
|
||||
size_t length;
|
||||
int type = lua_type( L, -1 );
|
||||
if (type == LUA_TSTRING)
|
||||
{
|
||||
data = lua_tolstring( L, -1, &length );
|
||||
}
|
||||
else if (type == LUA_TUSERDATA)
|
||||
{
|
||||
ws2812_buffer *buffer = (ws2812_buffer*)luaL_checkudata( L, -1, "ws2812.buffer" );
|
||||
|
||||
data = (const char *)buffer->values;
|
||||
length = buffer->colorsPerLed*buffer->size;
|
||||
}
|
||||
else
|
||||
{
|
||||
ws2812_cleanup( L, 1 );
|
||||
return luaL_argerror(L, stack, "ws2812.buffer or string expected");
|
||||
}
|
||||
lua_pop( L, 1 );
|
||||
|
||||
// prepare channel
|
||||
if (platform_ws2812_setup( gpio_num, 1, (const uint8_t *)data, length ) != PLATFORM_OK) {
|
||||
ws2812_cleanup( L, 0 );
|
||||
return luaL_argerror( L, stack, "can't set up chain" );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// send all channels at once
|
||||
//
|
||||
if (platform_ws2812_send() != PLATFORM_OK) {
|
||||
ws2812_cleanup( L, 0 );
|
||||
return luaL_error( L, "sending failed" );
|
||||
}
|
||||
|
||||
ws2812_cleanup( L, 0 );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ptrdiff_t posrelat (ptrdiff_t pos, size_t len) {
|
||||
/* relative string position: negative means back from end */
|
||||
if (pos < 0) pos += (ptrdiff_t)len + 1;
|
||||
return (pos >= 0) ? pos : 0;
|
||||
}
|
||||
|
||||
static ws2812_buffer *allocate_buffer(lua_State *L, int leds, int colorsPerLed) {
|
||||
// Allocate memory
|
||||
size_t size = sizeof(ws2812_buffer) + colorsPerLed*leds*sizeof(uint8_t);
|
||||
ws2812_buffer * buffer = (ws2812_buffer*)lua_newuserdata(L, size);
|
||||
|
||||
// Associate its metatable
|
||||
luaL_getmetatable(L, "ws2812.buffer");
|
||||
lua_setmetatable(L, -2);
|
||||
|
||||
// Save led strip size
|
||||
buffer->size = leds;
|
||||
buffer->colorsPerLed = colorsPerLed;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Handle a buffer where we can store led values
|
||||
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");
|
||||
|
||||
ws2812_buffer * buffer = allocate_buffer(L, leds, colorsPerLed);
|
||||
|
||||
memset(buffer->values, 0, colorsPerLed * leds);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ws2812_buffer_fill(lua_State* L) {
|
||||
ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
|
||||
|
||||
// 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];
|
||||
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 ws2812_buffer_fade(lua_State* L) {
|
||||
ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
|
||||
const int fade = luaL_checkinteger(L, 2);
|
||||
unsigned direction = luaL_optinteger( L, 3, FADE_OUT );
|
||||
|
||||
luaL_argcheck(L, fade > 0, 2, "fade value should be a strict positive number");
|
||||
|
||||
uint8_t * p = &buffer->values[0];
|
||||
int val = 0;
|
||||
int i;
|
||||
for(i = 0; i < buffer->size * buffer->colorsPerLed; i++)
|
||||
{
|
||||
if (direction == FADE_OUT)
|
||||
{
|
||||
*p++ /= fade;
|
||||
}
|
||||
else
|
||||
{
|
||||
// as fade in can result in value overflow, an int is used to perform the check afterwards
|
||||
val = *p * fade;
|
||||
if (val > 255) val = 255;
|
||||
*p++ = val;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int ws2812_buffer_shift(lua_State* L) {
|
||||
ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
|
||||
const int shiftValue = luaL_checkinteger(L, 2);
|
||||
const unsigned shift_type = luaL_optinteger( L, 3, SHIFT_LOGICAL );
|
||||
|
||||
ptrdiff_t start = posrelat(luaL_optinteger(L, 4, 1), buffer->size);
|
||||
ptrdiff_t end = posrelat(luaL_optinteger(L, 5, -1), buffer->size);
|
||||
if (start < 1) start = 1;
|
||||
if (end > (ptrdiff_t)buffer->size) end = (ptrdiff_t)buffer->size;
|
||||
|
||||
start--;
|
||||
int size = end - start;
|
||||
size_t offset = start * buffer->colorsPerLed;
|
||||
|
||||
luaL_argcheck(L, shiftValue > 0-size && shiftValue < 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 || size <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t * tmp_pixels = luaM_malloc(L, buffer->colorsPerLed * sizeof(uint8_t) * shift);
|
||||
size_t shift_len, remaining_len;
|
||||
// calculate length of shift section and remaining section
|
||||
shift_len = shift*buffer->colorsPerLed;
|
||||
remaining_len = (size-shift)*buffer->colorsPerLed;
|
||||
|
||||
if (shiftValue > 0)
|
||||
{
|
||||
// Store the values which are moved out of the array (last n pixels)
|
||||
memcpy(tmp_pixels, &buffer->values[offset + (size-shift)*buffer->colorsPerLed], shift_len);
|
||||
// Move pixels to end
|
||||
memmove(&buffer->values[offset + shift*buffer->colorsPerLed], &buffer->values[offset], remaining_len);
|
||||
// Fill beginning with temp data
|
||||
if (shift_type == SHIFT_LOGICAL)
|
||||
{
|
||||
memset(&buffer->values[offset], 0, shift_len);
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(&buffer->values[offset], tmp_pixels, shift_len);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Store the values which are moved out of the array (last n pixels)
|
||||
memcpy(tmp_pixels, &buffer->values[offset], shift_len);
|
||||
// Move pixels to end
|
||||
memmove(&buffer->values[offset], &buffer->values[offset + shift*buffer->colorsPerLed], remaining_len);
|
||||
// Fill beginning with temp data
|
||||
if (shift_type == SHIFT_LOGICAL)
|
||||
{
|
||||
memset(&buffer->values[offset + (size-shift)*buffer->colorsPerLed], 0, shift_len);
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(&buffer->values[offset + (size-shift)*buffer->colorsPerLed], tmp_pixels, shift_len);
|
||||
}
|
||||
}
|
||||
// Free memory
|
||||
luaM_free(L, tmp_pixels);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ws2812_buffer_dump(lua_State* L) {
|
||||
ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
|
||||
|
||||
lua_pushlstring(L, (char *)buffer->values, buffer->size * buffer->colorsPerLed);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ws2812_buffer_replace(lua_State* L) {
|
||||
ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
|
||||
size_t l = buffer->size;
|
||||
ptrdiff_t start = posrelat(luaL_optinteger(L, 3, 1), l);
|
||||
|
||||
uint8_t *src;
|
||||
size_t srcLen;
|
||||
|
||||
if (lua_type(L, 2) == LUA_TSTRING) {
|
||||
size_t length;
|
||||
|
||||
src = (uint8_t *) lua_tolstring(L, 2, &length);
|
||||
srcLen = length / buffer->colorsPerLed;
|
||||
} else {
|
||||
ws2812_buffer * rhs = (ws2812_buffer*)luaL_checkudata(L, 2, "ws2812.buffer");
|
||||
src = rhs->values;
|
||||
srcLen = rhs->size;
|
||||
luaL_argcheck(L, rhs->colorsPerLed == buffer->colorsPerLed, 2, "Buffers have different colors");
|
||||
}
|
||||
|
||||
luaL_argcheck(L, srcLen + start - 1 <= buffer->size, 2, "Does not fit into destination");
|
||||
|
||||
memcpy(buffer->values + (start - 1) * buffer->colorsPerLed, src, srcLen * buffer->colorsPerLed);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// buffer:mix(factor1, buffer1, ..)
|
||||
// factor is 256 for 100%
|
||||
// uses saturating arithmetic (one buffer at a time)
|
||||
static int ws2812_buffer_mix(lua_State* L) {
|
||||
ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
|
||||
|
||||
int pos = 2;
|
||||
size_t cells = buffer->size * buffer->colorsPerLed;
|
||||
|
||||
int n_sources = (lua_gettop(L) - 1) / 2;
|
||||
|
||||
struct {
|
||||
int factor;
|
||||
const uint8_t *values;
|
||||
} source[n_sources];
|
||||
|
||||
int src;
|
||||
for (src = 0; src < n_sources; src++, pos += 2) {
|
||||
int factor = luaL_checkinteger(L, pos);
|
||||
ws2812_buffer *src_buffer = (ws2812_buffer*) luaL_checkudata(L, pos + 1, "ws2812.buffer");
|
||||
|
||||
luaL_argcheck(L, src_buffer->size == buffer->size && src_buffer->colorsPerLed == buffer->colorsPerLed, pos + 1, "Buffer not same shape");
|
||||
|
||||
source[src].factor = factor;
|
||||
source[src].values = src_buffer->values;
|
||||
}
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < cells; i++) {
|
||||
int val = 0;
|
||||
for (src = 0; src < n_sources; src++) {
|
||||
val += ((int)(source[src].values[i] * source[src].factor) >> 8);
|
||||
}
|
||||
|
||||
if (val < 0) {
|
||||
val = 0;
|
||||
} else if (val > 255) {
|
||||
val = 255;
|
||||
}
|
||||
buffer->values[i] = val;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns the total of all channels
|
||||
static int ws2812_buffer_power(lua_State* L) {
|
||||
ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
|
||||
|
||||
size_t cells = buffer->size * buffer->colorsPerLed;
|
||||
|
||||
size_t i;
|
||||
int total = 0;
|
||||
for (i = 0; i < cells; i++) {
|
||||
total += buffer->values[i];
|
||||
}
|
||||
|
||||
lua_pushnumber(L, total);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ws2812_buffer_get(lua_State* L) {
|
||||
ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
|
||||
const int led = luaL_checkinteger(L, 2) - 1;
|
||||
|
||||
luaL_argcheck(L, led >= 0 && led < buffer->size, 2, "index out of range");
|
||||
|
||||
int i;
|
||||
for (i = 0; i < buffer->colorsPerLed; i++)
|
||||
{
|
||||
lua_pushnumber(L, buffer->values[buffer->colorsPerLed*led+i]);
|
||||
}
|
||||
|
||||
return buffer->colorsPerLed;
|
||||
}
|
||||
|
||||
static int ws2812_buffer_set(lua_State* L) {
|
||||
ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
|
||||
const int led = luaL_checkinteger(L, 2) - 1;
|
||||
|
||||
luaL_argcheck(L, led >= 0 && led < buffer->size, 2, "index out of range");
|
||||
|
||||
int type = lua_type(L, 3);
|
||||
if(type == LUA_TTABLE)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < buffer->colorsPerLed; i++)
|
||||
{
|
||||
// Get value and push it on stack
|
||||
lua_rawgeti(L, 3, i+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, buffer->colorsPerLed);
|
||||
}
|
||||
else if(type == LUA_TSTRING)
|
||||
{
|
||||
size_t len;
|
||||
const char * buf = lua_tolstring(L, 3, &len);
|
||||
|
||||
// Overflow check
|
||||
if( buffer->colorsPerLed*led + len > buffer->colorsPerLed*buffer->size )
|
||||
{
|
||||
return luaL_error(L, "string size will exceed strip length");
|
||||
}
|
||||
|
||||
memcpy(&buffer->values[buffer->colorsPerLed*led], buf, len);
|
||||
}
|
||||
else
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < buffer->colorsPerLed; i++)
|
||||
{
|
||||
buffer->values[buffer->colorsPerLed*led+i] = luaL_checkinteger(L, 3+i);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ws2812_buffer_size(lua_State* L) {
|
||||
ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
|
||||
|
||||
lua_pushnumber(L, buffer->size);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ws2812_buffer_sub(lua_State* L) {
|
||||
ws2812_buffer * lhs = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
|
||||
size_t l = lhs->size;
|
||||
ptrdiff_t start = posrelat(luaL_checkinteger(L, 2), l);
|
||||
ptrdiff_t end = posrelat(luaL_optinteger(L, 3, -1), l);
|
||||
if (start < 1) start = 1;
|
||||
if (end > (ptrdiff_t)l) end = (ptrdiff_t)l;
|
||||
if (start <= end) {
|
||||
ws2812_buffer *result = allocate_buffer(L, end - start + 1, lhs->colorsPerLed);
|
||||
memcpy(result->values, lhs->values + lhs->colorsPerLed * (start - 1), lhs->colorsPerLed * (end - start + 1));
|
||||
} else {
|
||||
ws2812_buffer *result = allocate_buffer(L, 0, lhs->colorsPerLed);
|
||||
(void)result;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ws2812_buffer_concat(lua_State* L) {
|
||||
ws2812_buffer * lhs = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
|
||||
ws2812_buffer * rhs = (ws2812_buffer*)luaL_checkudata(L, 2, "ws2812.buffer");
|
||||
|
||||
luaL_argcheck(L, lhs->colorsPerLed == rhs->colorsPerLed, 1, "Can only concatenate buffers with same colors");
|
||||
|
||||
int colorsPerLed = lhs->colorsPerLed;
|
||||
int leds = lhs->size + rhs->size;
|
||||
|
||||
ws2812_buffer * buffer = allocate_buffer(L, leds, colorsPerLed);
|
||||
|
||||
memcpy(buffer->values, lhs->values, lhs->colorsPerLed * lhs->size);
|
||||
memcpy(buffer->values + lhs->colorsPerLed * lhs->size, rhs->values, rhs->colorsPerLed * rhs->size);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ws2812_buffer_tostring(lua_State* L) {
|
||||
ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer");
|
||||
|
||||
luaL_Buffer result;
|
||||
luaL_buffinit(L, &result);
|
||||
|
||||
luaL_addchar(&result, '[');
|
||||
int i;
|
||||
int p = 0;
|
||||
for (i = 0; i < buffer->size; i++) {
|
||||
int j;
|
||||
if (i > 0) {
|
||||
luaL_addchar(&result, ',');
|
||||
}
|
||||
luaL_addchar(&result, '(');
|
||||
for (j = 0; j < buffer->colorsPerLed; j++, p++) {
|
||||
if (j > 0) {
|
||||
luaL_addchar(&result, ',');
|
||||
}
|
||||
char numbuf[5];
|
||||
sprintf(numbuf, "%d", buffer->values[p]);
|
||||
luaL_addstring(&result, numbuf);
|
||||
}
|
||||
luaL_addchar(&result, ')');
|
||||
}
|
||||
|
||||
luaL_addchar(&result, ']');
|
||||
luaL_pushresult(&result);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const LUA_REG_TYPE ws2812_buffer_map[] =
|
||||
{
|
||||
{ LSTRKEY( "dump" ), LFUNCVAL( ws2812_buffer_dump )},
|
||||
{ LSTRKEY( "fade" ), LFUNCVAL( ws2812_buffer_fade )},
|
||||
{ LSTRKEY( "fill" ), LFUNCVAL( ws2812_buffer_fill )},
|
||||
{ LSTRKEY( "get" ), LFUNCVAL( ws2812_buffer_get )},
|
||||
{ LSTRKEY( "replace" ), LFUNCVAL( ws2812_buffer_replace )},
|
||||
{ LSTRKEY( "mix" ), LFUNCVAL( ws2812_buffer_mix )},
|
||||
{ LSTRKEY( "power" ), LFUNCVAL( ws2812_buffer_power )},
|
||||
{ LSTRKEY( "set" ), LFUNCVAL( ws2812_buffer_set )},
|
||||
{ LSTRKEY( "shift" ), LFUNCVAL( ws2812_buffer_shift )},
|
||||
{ LSTRKEY( "size" ), LFUNCVAL( ws2812_buffer_size )},
|
||||
{ LSTRKEY( "sub" ), LFUNCVAL( ws2812_buffer_sub )},
|
||||
{ LSTRKEY( "__concat" ),LFUNCVAL( ws2812_buffer_concat )},
|
||||
{ LSTRKEY( "__index" ), LROVAL( ws2812_buffer_map )},
|
||||
{ LSTRKEY( "__tostring" ), LFUNCVAL( ws2812_buffer_tostring )},
|
||||
{ LNILKEY, LNILVAL}
|
||||
};
|
||||
|
||||
static const LUA_REG_TYPE ws2812_map[] =
|
||||
{
|
||||
{ LSTRKEY( "newBuffer" ), LFUNCVAL( ws2812_new_buffer )},
|
||||
{ LSTRKEY( "write" ), LFUNCVAL( ws2812_write )},
|
||||
{ LSTRKEY( "FADE_IN" ), LNUMVAL( FADE_IN ) },
|
||||
{ LSTRKEY( "FADE_OUT" ), LNUMVAL( FADE_OUT ) },
|
||||
{ LSTRKEY( "SHIFT_LOGICAL" ), LNUMVAL( SHIFT_LOGICAL ) },
|
||||
{ LSTRKEY( "SHIFT_CIRCULAR" ), LNUMVAL( SHIFT_CIRCULAR ) },
|
||||
{ LNILKEY, LNILVAL}
|
||||
};
|
||||
|
||||
int luaopen_ws2812(lua_State *L) {
|
||||
// TODO: Make sure that the GPIO system is initialized
|
||||
luaL_rometatable(L, "ws2812.buffer", (void *)ws2812_buffer_map); // create metatable for ws2812.buffer
|
||||
return 0;
|
||||
}
|
||||
|
||||
NODEMCU_MODULE(WS2812, "ws2812", ws2812_map, luaopen_ws2812);
|
|
@ -172,6 +172,14 @@ uint16_t platform_onewire_crc16( const uint8_t* input, uint16_t len, uint16_t cr
|
|||
int platform_dht_read( uint8_t gpio_num, uint8_t wakeup_ms, uint8_t *data );
|
||||
|
||||
|
||||
// *****************************************************************************
|
||||
// WS2812 platform interface
|
||||
|
||||
int platform_ws2812_setup( uint8_t gpio_num, uint8_t num_mem, const uint8_t *data, size_t len );
|
||||
int platform_ws2812_release( void );
|
||||
int platform_ws2812_send( void );
|
||||
|
||||
|
||||
// Internal flash erase/write functions
|
||||
|
||||
uint32_t platform_flash_get_sector_of_address( uint32_t addr );
|
||||
|
|
|
@ -0,0 +1,237 @@
|
|||
/* ****************************************************************************
|
||||
*
|
||||
* ESP32 platform interface for WS2812 LEDs.
|
||||
*
|
||||
* Copyright (c) 2017, Arnim Laeuger
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
* ****************************************************************************/
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#include "driver/rmt.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
|
||||
#undef WS2812_DEBUG
|
||||
|
||||
|
||||
// divider to generate 100ns base period from 80MHz APB clock
|
||||
#define WS2812_CLKDIV (100 * 80 /1000)
|
||||
// bit H & L durations in multiples of 100ns
|
||||
#define WS2812_DURATION_T0H 4
|
||||
#define WS2812_DURATION_T0L 7
|
||||
#define WS2812_DURATION_T1H 8
|
||||
#define WS2812_DURATION_T1L 6
|
||||
#define WS2812_DURATION_RESET (50000 / 100)
|
||||
|
||||
// 0 bit in rmt encoding
|
||||
const rmt_item32_t ws2812_rmt_bit0 = {
|
||||
.level0 = 1,
|
||||
.duration0 = WS2812_DURATION_T0H,
|
||||
.level1 = 0,
|
||||
.duration1 = WS2812_DURATION_T0L
|
||||
};
|
||||
// 1 bit in rmt encoding
|
||||
const rmt_item32_t ws2812_rmt_bit1 = {
|
||||
.level0 = 1,
|
||||
.duration0 = WS2812_DURATION_T1H,
|
||||
.level1 = 0,
|
||||
.duration1 = WS2812_DURATION_T1L
|
||||
};
|
||||
|
||||
#define ws2812_rmt_reset {.level0 = 0, .duration0 = 4, .level1 = 0, .duration1 = 4}
|
||||
// reset signal, spans one complete buffer block
|
||||
const rmt_item32_t ws2812_rmt_reset_block[64] = { [0 ... 63] = ws2812_rmt_reset };
|
||||
|
||||
|
||||
// descriptor for a ws2812 chain
|
||||
typedef struct {
|
||||
bool valid;
|
||||
uint8_t gpio;
|
||||
const uint8_t *data;
|
||||
size_t len;
|
||||
size_t tx_offset;
|
||||
} ws2812_chain_t;
|
||||
|
||||
|
||||
// chain descriptor array
|
||||
static ws2812_chain_t ws2812_chains[RMT_CHANNEL_MAX];
|
||||
|
||||
// interrupt handler for ws2812 ISR
|
||||
static intr_handle_t ws2812_intr_handle;
|
||||
|
||||
|
||||
static void IRAM_ATTR ws2812_fill_memory_encoded( rmt_channel_t channel, const uint8_t *data, size_t len, size_t tx_offset )
|
||||
{
|
||||
for (size_t idx = 0; idx < len; idx++) {
|
||||
uint8_t byte = data[idx];
|
||||
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
RMTMEM.chan[channel].data32[tx_offset + idx*8 + i].val = byte & 0x80 ? ws2812_rmt_bit1.val : ws2812_rmt_bit0.val;
|
||||
|
||||
byte <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void IRAM_ATTR ws2812_isr(void *arg)
|
||||
{
|
||||
uint32_t intr_st = RMT.int_st.val;
|
||||
|
||||
for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX; channel++) {
|
||||
|
||||
if ((intr_st & (BIT(channel+24))) && (ws2812_chains[channel].valid)) {
|
||||
RMT.int_clr.val = BIT(channel+24);
|
||||
|
||||
ws2812_chain_t *chain = &(ws2812_chains[channel]);
|
||||
uint32_t data_sub_len = RMT.tx_lim_ch[channel].limit/8;
|
||||
|
||||
if (chain->len >= data_sub_len) {
|
||||
ws2812_fill_memory_encoded( channel, chain->data, data_sub_len, chain->tx_offset );
|
||||
chain->data += data_sub_len;
|
||||
chain->len -= data_sub_len;
|
||||
} else if (chain->len == 0) {
|
||||
RMTMEM.chan[channel].data32[chain->tx_offset].val = 0;
|
||||
} else {
|
||||
ws2812_fill_memory_encoded( channel, chain->data, chain->len, chain->tx_offset );
|
||||
RMTMEM.chan[channel].data32[chain->tx_offset + chain->len].val = 0;
|
||||
chain->data += chain->len;
|
||||
chain->len = 0;
|
||||
}
|
||||
if (chain->tx_offset == 0) {
|
||||
chain->tx_offset = data_sub_len * 8;
|
||||
} else {
|
||||
chain->tx_offset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int platform_ws2812_setup( uint8_t gpio_num, uint8_t num_mem, const uint8_t *data, size_t len )
|
||||
{
|
||||
int channel;
|
||||
|
||||
if ((channel = platform_rmt_allocate( num_mem )) >= 0) {
|
||||
ws2812_chain_t *chain = &(ws2812_chains[channel]);
|
||||
|
||||
chain->valid = true;
|
||||
chain->gpio = gpio_num;
|
||||
chain->len = len;
|
||||
chain->data = data;
|
||||
chain->tx_offset = 0;
|
||||
|
||||
#ifdef WS2812_DEBUG
|
||||
ESP_LOGI("ws2812", "Setup done for gpio %d on RMT channel %d", gpio_num, channel);
|
||||
#endif
|
||||
|
||||
return PLATFORM_OK;
|
||||
}
|
||||
|
||||
return PLATFORM_ERR;
|
||||
}
|
||||
|
||||
int platform_ws2812_release( void )
|
||||
{
|
||||
for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX; channel++) {
|
||||
ws2812_chain_t *chain = &(ws2812_chains[channel]);
|
||||
|
||||
if (chain->valid) {
|
||||
rmt_driver_uninstall( channel );
|
||||
|
||||
platform_rmt_release( channel );
|
||||
|
||||
// attach GPIO to pin, driving 0
|
||||
gpio_set_level( chain->gpio, 0 );
|
||||
gpio_set_direction( chain->gpio, GPIO_MODE_DEF_OUTPUT );
|
||||
gpio_matrix_out( chain->gpio, SIG_GPIO_OUT_IDX, 0, 0 );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return PLATFORM_OK;
|
||||
}
|
||||
|
||||
int platform_ws2812_send( void )
|
||||
{
|
||||
rmt_config_t rmt_tx;
|
||||
int res = PLATFORM_OK;
|
||||
|
||||
// common settings
|
||||
rmt_tx.mem_block_num = 1;
|
||||
rmt_tx.clk_div = WS2812_CLKDIV;
|
||||
rmt_tx.tx_config.loop_en = false;
|
||||
rmt_tx.tx_config.carrier_en = false;
|
||||
rmt_tx.tx_config.idle_level = 0;
|
||||
rmt_tx.tx_config.idle_output_en = true;
|
||||
rmt_tx.rmt_mode = RMT_MODE_TX;
|
||||
|
||||
// configure selected RMT channels
|
||||
for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX && res == PLATFORM_OK; channel++) {
|
||||
if (ws2812_chains[channel].valid) {
|
||||
rmt_tx.channel = channel;
|
||||
rmt_tx.gpio_num = ws2812_chains[channel].gpio;
|
||||
if (rmt_config( &rmt_tx ) != ESP_OK) {
|
||||
res = PLATFORM_ERR;
|
||||
break;
|
||||
}
|
||||
if (rmt_driver_install( channel, 0, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_SHARED ) != ESP_OK) {
|
||||
res = PLATFORM_ERR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// hook-in our shared ISR
|
||||
esp_intr_alloc( ETS_RMT_INTR_SOURCE, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_SHARED, ws2812_isr, NULL, &ws2812_intr_handle );
|
||||
|
||||
|
||||
// start selected channels one by one
|
||||
for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX && res == PLATFORM_OK; channel++) {
|
||||
if (ws2812_chains[channel].valid) {
|
||||
// we just feed a single block for generating the reset to the rmt driver
|
||||
// the actual payload data is handled by our private shared ISR
|
||||
if (rmt_write_items( channel,
|
||||
(rmt_item32_t *)ws2812_rmt_reset_block,
|
||||
64,
|
||||
false ) != ESP_OK) {
|
||||
res = PLATFORM_ERR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wait for all channels to finish
|
||||
for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX; channel++) {
|
||||
if (ws2812_chains[channel].valid) {
|
||||
rmt_wait_tx_done( channel );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// un-hook our ISR
|
||||
esp_intr_free( ws2812_intr_handle );
|
||||
|
||||
|
||||
return res;
|
||||
}
|
|
@ -0,0 +1,317 @@
|
|||
# WS2812 Module
|
||||
| Since | Origin / Contributor | Maintainer | Source |
|
||||
| :----- | :-------------------- | :---------- | :------ |
|
||||
| 2015-02-05 | [Till Klocke](https://github.com/dereulenspiegel), [Thomas Soëte](https://github.com/Alkorin) | [Arnim Läuger](https://github.com/devsaurus) | [ws2812.c](../../../components/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 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(array)`
|
||||
|
||||
#### Parameters
|
||||
Array containing an element for each chain (maximum of 8 elements):
|
||||
- `pin` 0 ~ 33, I/O index
|
||||
- `data` payload to be sent to one or more WS2812 like leds through GPIO2
|
||||
|
||||
Payload type could be:
|
||||
- `string` representing bytes to send
|
||||
- `ws2812.buffer` see [Buffer module](#buffer-module)
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
||||
#### Example
|
||||
```lua
|
||||
ws2812.write({pin = 4, data = string.char(255, 0, 0, 255, 0, 0)}) -- turn the two first RGB leds to green
|
||||
```
|
||||
|
||||
```lua
|
||||
ws2812.write({pin = 4, string.char(0, 0, 0, 255, 0, 0, 0, 255)}) -- turn the two first RGBW leds to white
|
||||
```
|
||||
|
||||
```lua
|
||||
ws2812.write({pin = 4, data = string.char(255, 0, 0, 255, 0, 0)},
|
||||
{pin = 14, data = 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
|
||||
```
|
||||
|
||||
# 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. This buffer has a `__tostring` method so that it can be printed. This is useful for debugging.
|
||||
|
||||
#### Example
|
||||
Led chaser with a RGBW strip
|
||||
```lua
|
||||
local i, buffer = 0, ws2812.newBuffer(300, 4); buffer:fill(0, 0, 0, 0); tmr.create():alarm(50, 1, function()
|
||||
i = i + 1
|
||||
buffer:fade(2)
|
||||
buffer:set(i % buffer:size() + 1, 0, 0, 0, 255)
|
||||
ws2812.write({pin = 4, data = buffer})
|
||||
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 = ws2812.newBuffer(32, 4)
|
||||
print(buffer:get(1))
|
||||
0 0 0 0
|
||||
```
|
||||
|
||||
## 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` payload of the color
|
||||
|
||||
Payload could be:
|
||||
- `number, number, ...` you should pass as many arguments as `bytesPerLed`
|
||||
- `table` should contains `bytesPerLed` numbers
|
||||
- `string` should contains `bytesPerLed` bytes
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
||||
#### Example
|
||||
```lua
|
||||
buffer = ws2812.newBuffer(32, 3)
|
||||
buffer:set(1, 255, 0, 0) -- set the first led green for a RGB strip
|
||||
```
|
||||
|
||||
```lua
|
||||
buffer = ws2812.newBuffer(32, 4)
|
||||
buffer:set(1, {0, 0, 0, 255}) -- set the first led white for a RGBW strip
|
||||
```
|
||||
|
||||
```lua
|
||||
buffer = ws2812.newBuffer(32, 3)
|
||||
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
|
||||
|
||||
#### 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, you should pass as many arguments as `bytesPerLed`
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
||||
#### Example
|
||||
```lua
|
||||
buffer:fill(0, 0, 0) -- fill the buffer with black for a RGB strip
|
||||
```
|
||||
|
||||
## ws2812.buffer:dump()
|
||||
Returns the contents of the buffer (the pixel values) as a string. This can then be saved to a file or sent over a network.
|
||||
|
||||
#### Syntax
|
||||
`buffer:dump()`
|
||||
|
||||
#### Returns
|
||||
A string containing the pixel values.
|
||||
|
||||
#### Example
|
||||
```lua
|
||||
local s = buffer:dump()
|
||||
```
|
||||
|
||||
## ws2812.buffer:replace()
|
||||
Inserts a string (or a buffer) into another buffer with an offset.
|
||||
The buffer must have the same number of colors per led or an error will be thrown.
|
||||
|
||||
#### Syntax
|
||||
`buffer:replace(source[, offset])`
|
||||
|
||||
#### Parameters
|
||||
- `source` the pixel values to be set into the buffer. This is either a string or a buffer.
|
||||
- `offset` the offset where the source is to be placed in the buffer. Default is 1. Negative values can be used.
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
||||
#### Example
|
||||
```lua
|
||||
buffer:replace(anotherbuffer:dump()) -- copy one buffer into another via a string
|
||||
buffer:replace(anotherbuffer) -- copy one buffer into another
|
||||
newbuffer = buffer.sub(1) -- make a copy of a buffer into a new buffer
|
||||
```
|
||||
|
||||
## ws2812.buffer:mix()
|
||||
This is a general method that loads data into a buffer that is a linear combination of data from other buffers. It can be used to copy a buffer or,
|
||||
more usefully, do a cross fade. The pixel values are computed as integers and then range limited to [0, 255]. This means that negative
|
||||
factors work as expected, and that the order of combining buffers does not matter.
|
||||
|
||||
#### Syntax
|
||||
`buffer:mix(factor1, buffer1, ...)`
|
||||
|
||||
#### Parameters
|
||||
- `factor1` This is the factor that the contents of `buffer1` are multiplied by. This factor is scaled by a factor of 256. Thus `factor1` value of 256 is a factor of 1.0.
|
||||
- `buffer1` This is the source buffer. It must be of the same shape as the destination buffer.
|
||||
|
||||
There can be any number of factor/buffer pairs.
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
||||
#### Example
|
||||
```lua
|
||||
-- loads buffer with a crossfade between buffer1 and buffer2
|
||||
buffer:mix(256 - crossmix, buffer1, crossmix, buffer2)
|
||||
|
||||
-- multiplies all values in buffer by 0.75
|
||||
-- This can be used in place of buffer:fade
|
||||
buffer:mix(192, buffer)
|
||||
```
|
||||
|
||||
## ws2812.buffer:power()
|
||||
Computes the total energy requirement for the buffer. This is merely the total sum of all the pixel values (which assumes that each color in each
|
||||
pixel consumes the same amount of power). A real WS2812 (or WS2811) has three constant current drivers of 20mA -- one for each of R, G and B. The
|
||||
pulse width modulation will cause the *average* current to scale linearly with pixel value.
|
||||
|
||||
#### Syntax
|
||||
`buffer:power()`
|
||||
|
||||
#### Returns
|
||||
An integer which is the sum of all the pixel values.
|
||||
|
||||
#### Example
|
||||
```lua
|
||||
-- Dim the buffer to no more than the PSU can provide
|
||||
local psu_current_ma = 1000
|
||||
local led_current_ma = 20
|
||||
local led_sum = psu_current_ma * 255 / led_current_ma
|
||||
|
||||
local p = buffer:power()
|
||||
if p > led_sum then
|
||||
buffer:mix(256 * led_sum / p, buffer) -- power is now limited
|
||||
end
|
||||
```
|
||||
|
||||
## ws2812.buffer:fade()
|
||||
Fade in or out. Defaults to out. Multiply or divide each byte of each led with/by the given value. Useful for a fading effect.
|
||||
|
||||
#### Syntax
|
||||
`buffer:fade(value [, direction])`
|
||||
|
||||
#### Parameters
|
||||
- `value` value by which to divide or multiply each byte
|
||||
- `direction` ws2812.FADE\_IN or ws2812.FADE\_OUT. Defaults to ws2812.FADE\_OUT
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
||||
#### Example
|
||||
```lua
|
||||
buffer:fade(2)
|
||||
buffer:fade(2, ws2812.FADE_IN)
|
||||
```
|
||||
## ws2812.buffer:shift()
|
||||
Shift the content of (a piece of) the buffer in positive or negative direction. This allows simple animation effects. A slice of the buffer can be specified by using the
|
||||
standard start and end offset Lua notation. Negative values count backwards from the end of the buffer.
|
||||
|
||||
#### Syntax
|
||||
`buffer:shift(value [, mode[, i[, j]]])`
|
||||
|
||||
#### Parameters
|
||||
- `value` number of pixels by which to rotate the buffer. Positive values rotate forwards, negative values backwards.
|
||||
- `mode` is the shift mode to use. Can be one of `ws2812.SHIFT_LOGICAL` or `ws2812.SHIFT_CIRCULAR`. In case of SHIFT\_LOGICAL, the freed pixels are set to 0 (off). In case of SHIFT\_CIRCULAR, the buffer is treated like a ring buffer, inserting the pixels falling out on one end again on the other end. Defaults to SHIFT\_LOGICAL.
|
||||
- `i` is the first offset in the buffer to be affected. Negative values are permitted and count backwards from the end. Default is 1.
|
||||
- `j` is the last offset in the buffer to be affected. Negative values are permitted and count backwards from the end. Default is -1.
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
||||
#### Example
|
||||
```lua
|
||||
buffer:shift(3)
|
||||
```
|
||||
|
||||
## ws2812.buffer:sub()
|
||||
This implements the extraction function like `string.sub`. The indexes are in leds and all the same rules apply.
|
||||
|
||||
#### Syntax
|
||||
`buffer1:sub(i[, j])`
|
||||
|
||||
#### Parameters
|
||||
- `i` This is the start of the extracted data. Negative values can be used.
|
||||
- `j` this is the end of the extracted data. Negative values can be used. The default is -1.
|
||||
|
||||
#### Returns
|
||||
A buffer containing the extracted piece.
|
||||
|
||||
#### Example
|
||||
```
|
||||
b = buffer:sub(1,10)
|
||||
```
|
||||
|
||||
## ws2812.buffer:__concat()
|
||||
This implements the `..` operator to concatenate two buffers. They must have the same number of colors per led.
|
||||
|
||||
#### Syntax
|
||||
`buffer1 .. buffer2`
|
||||
|
||||
#### Parameters
|
||||
- `buffer1` this is the start of the resulting buffer
|
||||
- `buffer2` this is the end of the resulting buffer
|
||||
|
||||
#### Returns
|
||||
The concatenated buffer.
|
||||
|
||||
#### Example
|
||||
```
|
||||
ws2812.write({pin = 4, data = buffer1 .. buffer2})
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -43,4 +43,5 @@ pages:
|
|||
- 'tmr': 'en/modules/tmr.md'
|
||||
- 'uart': 'en/modules/uart.md'
|
||||
- 'wifi': 'en/modules/wifi.md'
|
||||
- 'ws2812': 'en/modules/ws2812.md'
|
||||
|
||||
|
|
Loading…
Reference in New Issue