Add ws2812 module.

This commit is contained in:
devsaurus 2017-04-06 22:59:19 +02:00
parent 61bd4821b3
commit 1866e6f457
6 changed files with 1102 additions and 0 deletions

View File

@ -135,4 +135,10 @@ config LUA_MODULE_WIFI
help help
Includes the WiFi module (recommended). Includes the WiFi module (recommended).
config LUA_MODULE_WS2812
bool "WS2812 module"
default "n"
help
Includes the ws2812 module.
endmenu endmenu

533
components/modules/ws2812.c Normal file
View File

@ -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);

View File

@ -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 ); 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 // Internal flash erase/write functions
uint32_t platform_flash_get_sector_of_address( uint32_t addr ); uint32_t platform_flash_get_sector_of_address( uint32_t addr );

View File

@ -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;
}

317
docs/en/modules/ws2812.md Normal file
View File

@ -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})
```

View File

@ -43,4 +43,5 @@ pages:
- 'tmr': 'en/modules/tmr.md' - 'tmr': 'en/modules/tmr.md'
- 'uart': 'en/modules/uart.md' - 'uart': 'en/modules/uart.md'
- 'wifi': 'en/modules/wifi.md' - 'wifi': 'en/modules/wifi.md'
- 'ws2812': 'en/modules/ws2812.md'