diff --git a/components/modules/ws2812.c b/components/modules/ws2812.c index 76a84523d..fd7e9492e 100644 --- a/components/modules/ws2812.c +++ b/components/modules/ws2812.c @@ -11,6 +11,14 @@ #define SHIFT_LOGICAL 0 #define SHIFT_CIRCULAR 1 +// The default 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 +// The default reset duration in multiples of 100ns. +#define WS2812_DURATION_RESET 512 + typedef struct { int size; @@ -33,6 +41,7 @@ static void ws2812_cleanup( lua_State *L, int pop ) // 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 type; int top = lua_gettop( L ); for (int stack = 1; stack <= top; stack++) { @@ -56,6 +65,106 @@ static int ws2812_write( lua_State* L ) int gpio_num = luaL_checkint( L, -1 ); lua_pop( L, 1 ); + // + // retrieve reset + // This is an optional parameter which defaults to WS2812_DURATION_RESET. + // + int reset = WS2812_DURATION_RESET; + type = lua_getfield( L, stack, "reset" ); + if (type!=LUA_TNIL ) + { + if (!lua_isnumber( L, -1 )) { + ws2812_cleanup( L, 1 ); + return luaL_argerror( L, stack, "invalid reset" ); + } + reset = luaL_checkint( L, -1 ); + if ((reset<0) || (reset>0xfffe)) { + ws2812_cleanup( L, 1 ); + return luaL_argerror( L, stack, "reset must be 0<=reset<=65534" ); + } + } + lua_pop( L, 1 ); + + // + // retrieve t0h + // This is an optional parameter which defaults to WS2812_DURATION_T0H. + // + int t0h = WS2812_DURATION_T0H; + type = lua_getfield( L, stack, "t0h" ); + if (type!=LUA_TNIL ) + { + if (!lua_isnumber( L, -1 )) { + ws2812_cleanup( L, 1 ); + return luaL_argerror( L, stack, "invalid t0h" ); + } + t0h = luaL_checkint( L, -1 ); + if ((t0h<1) || (t0h>0x7fff)) { + ws2812_cleanup( L, 1 ); + return luaL_argerror( L, stack, "t0h must be 1<=t0h<=32767" ); + } + } + lua_pop( L, 1 ); + + // + // retrieve t0l + // This is an optional parameter which defaults to WS2812_DURATION_T0L. + // + int t0l = WS2812_DURATION_T0L; + type = lua_getfield( L, stack, "t0l" ); + if (type!=LUA_TNIL ) + { + if (!lua_isnumber( L, -1 )) { + ws2812_cleanup( L, 1 ); + return luaL_argerror( L, stack, "invalid t0l" ); + } + t0l = luaL_checkint( L, -1 ); + if ((t0l<1) || (t0l>0x7fff)) { + ws2812_cleanup( L, 1 ); + return luaL_argerror( L, stack, "t0l must be 1<=t0l<=32767" ); + } + } + lua_pop( L, 1 ); + + // + // retrieve t1h + // This is an optional parameter which defaults to WS2812_DURATION_T1H. + // + int t1h = WS2812_DURATION_T1H; + type = lua_getfield( L, stack, "t1h" ); + if (type!=LUA_TNIL ) + { + if (!lua_isnumber( L, -1 )) { + ws2812_cleanup( L, 1 ); + return luaL_argerror( L, stack, "invalid t1h" ); + } + t1h = luaL_checkint( L, -1 ); + if ((t1h<1) || (t1h>0x7fff)) { + ws2812_cleanup( L, 1 ); + return luaL_argerror( L, stack, "t1h must be 1<=t1h<=32767" ); + } + } + lua_pop( L, 1 ); + + // + // retrieve t1l + // This is an optional parameter which defaults to WS2812_DURATION_T1L. + // + int t1l = WS2812_DURATION_T1L; + type = lua_getfield( L, stack, "t1l" ); + if (type!=LUA_TNIL ) + { + if (!lua_isnumber( L, -1 )) { + ws2812_cleanup( L, 1 ); + return luaL_argerror( L, stack, "invalid t1l" ); + } + t1l = luaL_checkint( L, -1 ); + if ((t1l<1) || (t1l>0x7fff)) { + ws2812_cleanup( L, 1 ); + return luaL_argerror( L, stack, "t1l must be 1<=t1l<=32767" ); + } + } + lua_pop( L, 1 ); + // // retrieve data // @@ -83,7 +192,7 @@ static int ws2812_write( lua_State* L ) lua_pop( L, 1 ); // prepare channel - if (platform_ws2812_setup( gpio_num, 1, (const uint8_t *)data, length ) != PLATFORM_OK) { + if (platform_ws2812_setup( gpio_num, reset, t0h, t0l, t1h, t1l, (const uint8_t *)data, length ) != PLATFORM_OK) { ws2812_cleanup( L, 0 ); return luaL_argerror( L, stack, "can't set up chain" ); } diff --git a/components/platform/include/platform.h b/components/platform/include/platform.h index 5c0929a4e..2706a9de4 100644 --- a/components/platform/include/platform.h +++ b/components/platform/include/platform.h @@ -209,7 +209,7 @@ int platform_dht_read( uint8_t gpio_num, uint8_t wakeup_ms, uint8_t *data ); // WS2812 platform interface void platform_ws2812_init( void ); -int platform_ws2812_setup( uint8_t gpio_num, uint8_t num_mem, const uint8_t *data, size_t len ); +int platform_ws2812_setup( uint8_t gpio_num, uint32_t reset, uint32_t bit0h, uint32_t bit0l, uint32_t bit1h, uint32_t bit1l, const uint8_t *data, size_t len ); int platform_ws2812_release( void ); int platform_ws2812_send( void ); diff --git a/components/platform/ws2812.c b/components/platform/ws2812.c index d00510cb0..21c14a4cf 100644 --- a/components/platform/ws2812.c +++ b/components/platform/ws2812.c @@ -32,112 +32,180 @@ #include "soc/periph_defs.h" #include "rom/gpio.h" // for gpio_matrix_out() #include "soc/gpio_periph.h" +#include "soc/rmt_reg.h" #undef WS2812_DEBUG -// If either of these fails, the reset logic in ws2812_sample_to_rmt will need revisiting. -_Static_assert(SOC_RMT_MEM_WORDS_PER_CHANNEL % 8 == 0, - "SOC_RMT_MEM_WORDS_PER_CHANNEL is assumed to be a multiple of 8"); -_Static_assert(SOC_RMT_MEM_WORDS_PER_CHANNEL >= 16, - "SOC_RMT_MEM_WORDS_PER_CHANNEL is assumed to be >= 16"); - // 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 -}; - -// This is one eighth of 512 * 100ns, ie in total a bit above the requisite 50us -const rmt_item32_t ws2812_rmt_reset = { .level0 = 0, .duration0 = 32, .level1 = 0, .duration1 = 32 }; // descriptor for a ws2812 chain typedef struct { bool valid; bool needs_reset; uint8_t gpio; + rmt_item32_t reset; + rmt_item32_t bits[2]; const uint8_t *data; size_t len; + uint8_t bitpos; } ws2812_chain_t; // chain descriptor array static ws2812_chain_t ws2812_chains[RMT_CHANNEL_MAX]; -#define MIN(a, b) ((a) < (b) ? (a) : (b)) - static void ws2812_sample_to_rmt(const void *src, rmt_item32_t *dest, size_t src_size, size_t wanted_num, size_t *translated_size, size_t *item_num) { - // Note: enabling these commented-out logs will ruin the timing so nothing - // will actually work when they're enabled. But I've kept them in as comments - // because they were useful in debugging the buffer management. - // ESP_DRAM_LOGW("ws2812", "ws2812_sample_to_rmt wanted=%u src_size=%u", wanted_num, src_size); - - void *ctx; - rmt_translator_get_context(item_num, &ctx); - ws2812_chain_t *chain = (ws2812_chain_t *)ctx; - - size_t reset_num = 0; - if (chain->needs_reset) { - // Haven't sent reset yet - - // We split the reset into 8 even though it would fit in a single - // rmt_item32_t, simply so that dest stays 8-item aligned which means we - // don't have to worry about having to split a byte of src across multiple - // blocks (assuming the static asserts at the top of this file are true). - for (int i = 0; i < 8; i++) { - dest[i] = ws2812_rmt_reset; - } - dest += 8; - wanted_num -= 8; - reset_num = 8; - chain->needs_reset = false; - } - - // Now write the actual data from src - const uint8_t *data = (const uint8_t *)src; - size_t data_num = MIN(wanted_num, src_size * 8) / 8; - for (size_t idx = 0; idx < data_num; idx++) { - uint8_t byte = data[idx]; - for (uint8_t i = 0; i < 8; i++) { - dest[idx * 8 + i] = (byte & 0x80) ? ws2812_rmt_bit1 : ws2812_rmt_bit0; - byte <<= 1; + size_t cnt_in; + size_t cnt_out; + const uint8_t *pucData; + uint8_t ucData; + uint8_t ucBitPos; + esp_err_t tStatus; + void *pvContext; + ws2812_chain_t *ptContext; + uint8_t ucBit; + + cnt_in = 0; + cnt_out = 0; + if( dest!=NULL && wanted_num>0 ) + { + tStatus = rmt_translator_get_context(item_num, &pvContext); + if( tStatus==ESP_OK ) + { + ptContext = (ws2812_chain_t *)pvContext; + + if( ptContext->needs_reset==true ) + { + dest[cnt_out++] = ptContext->reset; + ptContext->needs_reset = false; + } + if( src!=NULL && src_size>0 ) + { + ucBitPos = ptContext->bitpos; + + /* Each bit of the input data is converted into one RMT item. */ + + pucData = (const uint8_t*)src; + /* Get the current byte. */ + ucData = pucData[cnt_in] << ucBitPos; + + while( cnt_in> 7U; + /* Translate the bit to a WS2812 input code. */ + dest[cnt_out++] = ptContext->bits[ucBit]; + /* Move to the next bit. */ + ++ucBitPos; + if( ucBitPos<8U ) + { + ucData <<= 1; + } + else + { + ucBitPos = 0U; + ++cnt_in; + ucData = pucData[cnt_in]; + } + } + + ptContext->bitpos = ucBitPos; + } } } - - *translated_size = data_num; - *item_num = reset_num + data_num * 8; - // ESP_DRAM_LOGW("ws2812", "src bytes consumed: %u total rmt items: %u", *translated_size, *item_num); + *translated_size = cnt_in; + *item_num = cnt_out; } -int platform_ws2812_setup( uint8_t gpio_num, uint8_t num_mem, const uint8_t *data, size_t len ) +int platform_ws2812_setup( uint8_t gpio_num, uint32_t reset, uint32_t t0h, uint32_t t0l, uint32_t t1h, uint32_t t1l, const uint8_t *data, size_t len ) { int channel; - if ((channel = platform_rmt_allocate( num_mem, RMT_MODE_TX )) >= 0) { + if ((channel = platform_rmt_allocate( 1, RMT_MODE_TX )) >= 0) { ws2812_chain_t *chain = &(ws2812_chains[channel]); + rmt_item32_t tRmtItem; + uint32_t half; chain->valid = true; chain->gpio = gpio_num; chain->len = len; chain->data = data; - chain->needs_reset = true; + chain->bitpos = 0; + + // Send a reset if "reset" is not 0. + chain->needs_reset = (reset != 0); + + // Construct the RMT item for a reset. + tRmtItem.level0 = 0; + tRmtItem.level1 = 0; + // The reset duration must fit into one RMT item. This leaves 2*15 bit, + // which results in a maximum of 0xfffe . + if (reset>0xfffe) + { + reset = 0xfffe; + } + if (reset>0x7fff) + { + tRmtItem.duration0 = 0x7fff; + tRmtItem.duration1 = reset - 0x7fff; + } + else + { + half = reset >> 1U; + tRmtItem.duration0 = half; + tRmtItem.duration1 = reset - half; + } + chain->reset = tRmtItem; + + // Limit the bit times to the available 15 bits. + // The values must not be 0. + if( t0h==0 ) + { + t0h = 1; + } + else if( t0h>0x7fffU ) + { + t0h = 0x7fffU; + } + if( t0l==0 ) + { + t0l = 1; + } + else if( t0l>0x7fffU ) + { + t0l = 0x7fffU; + } + if( t1h==0 ) + { + t1h = 1; + } + else if( t1h>0x7fffU ) + { + t1h = 0x7fffU; + } + if( t1l==0 ) + { + t1l = 1; + } + else if( t1l>0x7fffU ) + { + t1l = 0x7fffU; + } + + // Construct the RMT item for a 0 bit. + tRmtItem.level0 = 1; + tRmtItem.duration0 = t0h; + tRmtItem.level1 = 0; + tRmtItem.duration1 = t0l; + chain->bits[0] = tRmtItem; + + // Construct the RMT item for a 1 bit. + tRmtItem.level0 = 1; + tRmtItem.duration0 = t1h; + tRmtItem.level1 = 0; + tRmtItem.duration1 = t1l; + chain->bits[1] = tRmtItem; #ifdef WS2812_DEBUG ESP_LOGI("ws2812", "Setup done for gpio %d on RMT channel %d", gpio_num, channel); @@ -210,6 +278,19 @@ int platform_ws2812_send( void ) } } + // Try to add all channels to a group. This moves the start of all RMT sequences closer + // together. +#if SOC_RMT_SUPPORT_TX_SYNCHRO + for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX && res == PLATFORM_OK; channel++) { + if (ws2812_chains[channel].valid) { + if (rmt_add_channel_to_group( channel ) != ESP_OK) { + res = PLATFORM_ERR; + break; + } + } + } +#endif + // 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) { @@ -229,6 +310,17 @@ int platform_ws2812_send( void ) } } +#if SOC_RMT_SUPPORT_TX_SYNCHRO + for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX; channel++) { + if (ws2812_chains[channel].valid) { + if (rmt_remove_channel_from_group( channel ) != ESP_OK) { + res = PLATFORM_ERR; + break; + } + } + } +#endif + return res; } diff --git a/docs/modules/ws2812.md b/docs/modules/ws2812.md index c408b096f..c4ae1c4c2 100644 --- a/docs/modules/ws2812.md +++ b/docs/modules/ws2812.md @@ -1,7 +1,7 @@ # 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)| +| 2015-02-05 | [Till Klocke](https://github.com/dereulenspiegel), [Thomas Soëte](https://github.com/Alkorin), [Christoph Thelen](https://github.com/docbacardi) | [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). @@ -22,6 +22,14 @@ Variable number of tables, each describing a single strip. Required elements are - `pin` IO index, see [GPIO Overview](gpio.md#gpio-overview) - `data` payload to be sent to one or more WS2812 like leds through GPIO2 +Optional elements are: + +- `reset` duration of the reset signal in multiples of 100ns. A duration of 0 generates no reset. The minimum possible value is 0. The maximum is 65534. The default value is 512 which generates a reset of 51.2us. +- `t0h` duration of the high period for a 0 code in multiples of 100ns. The minimum possible value is 1. The maximum is 32767. The default value is 4 which results in a high period of 400ns for each 0 code. +- `t0l` duration of the low period for a 0 code in multiples of 100ns. The minimum possible value is 1. The maximum is 32767. The default value is 7 which results in a low period of 700ns for each 0 code. +- `t1h` duration of the high period for a 1 code in multiples of 100ns. The minimum possible value is 1. The maximum is 32767. The default value is 8 which results in a high period of 800ns for each 1 code. +- `t1l` duration of the low period for a 1 code in multiples of 100ns. The minimum possible value is 1. The maximum is 32767. The default value is 6 which results in a low period of 600ns for each 1 code. + Payload type could be: - `string` representing bytes to send @@ -44,6 +52,10 @@ 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 ``` +```lua +ws2812.write({pin = 8, reset = 800, t0h = 3, t0l = 9, t1h = 6, t1l = 6, data = string.char(1, 0, 0)}) -- turn the SK6812 GRB led on the ESP32-C3-DevKitM-1 to green +``` + # 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.