From 1a2b49eae998c4cea9697fcd9ff72f2119f2393b Mon Sep 17 00:00:00 2001 From: Christoph Thelen Date: Sun, 4 Feb 2024 10:14:36 +0100 Subject: [PATCH 1/6] Choose the number of RMT buffers in the ws2812 module. The number of buffers required for optimal operation should be selected by the ws2812 module, not the caller. --- components/modules/ws2812.c | 2 +- components/platform/include/platform.h | 2 +- components/platform/ws2812.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/modules/ws2812.c b/components/modules/ws2812.c index 76a84523d..c0c33a4a3 100644 --- a/components/modules/ws2812.c +++ b/components/modules/ws2812.c @@ -83,7 +83,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, (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..885059116 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, 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..ceda5e77c 100644 --- a/components/platform/ws2812.c +++ b/components/platform/ws2812.c @@ -126,11 +126,11 @@ static void ws2812_sample_to_rmt(const void *src, rmt_item32_t *dest, size_t src // ESP_DRAM_LOGW("ws2812", "src bytes consumed: %u total rmt items: %u", *translated_size, *item_num); } -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, 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]); chain->valid = true; From 4369308523fdea7b194055b7e636bee703b0d9d4 Mon Sep 17 00:00:00 2001 From: Christoph Thelen Date: Sun, 4 Feb 2024 10:42:32 +0100 Subject: [PATCH 2/6] Add parameters for RGB LED bit times. This patch adds compatibility for different RGB LEDS besides the WS2812. ESP evaluation boards like the ESP32-C3-DevKitM-1 use an SK68XXMINI-HS RGB LED which does not respond to the timings of this module. The patch adds optional parameters for the bit timings to the write function. If the new parameters are not supplied, the old values are used. An example for the SK68XXMINI-HS is provided in the documentation. --- components/modules/ws2812.c | 89 +++++++++++++++++++++++++- components/platform/include/platform.h | 2 +- components/platform/ws2812.c | 78 ++++++++++++++++------ docs/modules/ws2812.md | 13 +++- 4 files changed, 158 insertions(+), 24 deletions(-) diff --git a/components/modules/ws2812.c b/components/modules/ws2812.c index c0c33a4a3..7f5817ffd 100644 --- a/components/modules/ws2812.c +++ b/components/modules/ws2812.c @@ -11,6 +11,12 @@ #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 + typedef struct { int size; @@ -33,6 +39,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 +63,86 @@ static int ws2812_write( lua_State* L ) int gpio_num = luaL_checkint( L, -1 ); 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 +170,7 @@ static int ws2812_write( lua_State* L ) lua_pop( L, 1 ); // prepare channel - if (platform_ws2812_setup( gpio_num, (const uint8_t *)data, length ) != PLATFORM_OK) { + if (platform_ws2812_setup( gpio_num, 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 885059116..dc66d4bf0 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, const uint8_t *data, size_t len ); +int platform_ws2812_setup( uint8_t gpio_num, 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 ceda5e77c..2ab6e12be 100644 --- a/components/platform/ws2812.c +++ b/components/platform/ws2812.c @@ -44,27 +44,8 @@ _Static_assert(SOC_RMT_MEM_WORDS_PER_CHANNEL >= 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 }; @@ -73,6 +54,7 @@ typedef struct { bool valid; bool needs_reset; uint8_t gpio; + rmt_item32_t bits[2]; const uint8_t *data; size_t len; } ws2812_chain_t; @@ -84,6 +66,8 @@ static ws2812_chain_t ws2812_chains[RMT_CHANNEL_MAX]; 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) { + uint8_t ucBit; + // 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. @@ -116,7 +100,9 @@ static void ws2812_sample_to_rmt(const void *src, rmt_item32_t *dest, size_t src 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; + /* Get the current bit. */ + ucBit = (byte & 0x80U) >> 7U; + dest[idx * 8 + i] = chain->bits[ucBit]; byte <<= 1; } } @@ -126,12 +112,13 @@ static void ws2812_sample_to_rmt(const void *src, rmt_item32_t *dest, size_t src // ESP_DRAM_LOGW("ws2812", "src bytes consumed: %u total rmt items: %u", *translated_size, *item_num); } -int platform_ws2812_setup( uint8_t gpio_num, const uint8_t *data, size_t len ) +int platform_ws2812_setup( uint8_t gpio_num, 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( 1, RMT_MODE_TX )) >= 0) { ws2812_chain_t *chain = &(ws2812_chains[channel]); + rmt_item32_t tRmtItem; chain->valid = true; chain->gpio = gpio_num; @@ -139,6 +126,55 @@ int platform_ws2812_setup( uint8_t gpio_num, const uint8_t *data, size_t len ) chain->data = data; chain->needs_reset = true; + // 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); #endif diff --git a/docs/modules/ws2812.md b/docs/modules/ws2812.md index c408b096f..57d282840 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,13 @@ 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: + +- `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 +51,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, t0h = 3, t0l = 9, t1h = 6, t1l = 6, data = string.char(1, 0, 0)}) -- turn the SK6812 RGB 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. From a0d311aa2dbcb64494f182ccefc51b8aa3dcbe96 Mon Sep 17 00:00:00 2001 From: Christoph Thelen Date: Sun, 4 Feb 2024 11:47:10 +0100 Subject: [PATCH 3/6] Remove restrictions from RTM translator. The old RMT translator was not able to split the bits of the source data into the size requested by the RMT transmitter. Either all 8 bits of an input byte were translated or none. The new routine removes the restriction by delivering exactly the requested amount of data to the transmitter, which results in a more balanced buffering of translated data under load. --- components/platform/ws2812.c | 110 +++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 51 deletions(-) diff --git a/components/platform/ws2812.c b/components/platform/ws2812.c index 2ab6e12be..3d30b85ff 100644 --- a/components/platform/ws2812.c +++ b/components/platform/ws2812.c @@ -35,19 +35,11 @@ #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_RESET (50000 / 100) -// 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 }; +// This is 512 * 100ns, which is a bit above the requisite 50us +const rmt_item32_t ws2812_rmt_reset = { .level0 = 0, .duration0 = 256, .level1 = 0, .duration1 = 256 }; // descriptor for a ws2812 chain typedef struct { @@ -57,59 +49,74 @@ typedef struct { 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) { + 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; - // 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; - } + 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; - // 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++) { - /* Get the current bit. */ - ucBit = (byte & 0x80U) >> 7U; - dest[idx * 8 + i] = chain->bits[ucBit]; - byte <<= 1; + if( ptContext->needs_reset==true ) + { + dest[cnt_out++] = ws2812_rmt_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, uint32_t t0h, uint32_t t0l, uint32_t t1h, uint32_t t1l, const uint8_t *data, size_t len ) @@ -125,6 +132,7 @@ int platform_ws2812_setup( uint8_t gpio_num, uint32_t t0h, uint32_t t0l, uint32_ chain->len = len; chain->data = data; chain->needs_reset = true; + chain->bitpos = 0; // Limit the bit times to the available 15 bits. // The values must not be 0. From f31bb9a46f76d49b6c488c26159462cfd03cd3f2 Mon Sep 17 00:00:00 2001 From: Christoph Thelen Date: Sun, 4 Feb 2024 12:21:02 +0100 Subject: [PATCH 4/6] Add a parameter for the RGB LED reset time. This patch introduces a new optional parameter for the reset time in the RGB LED communication. The default is 51.2 microseconds. A value of 0 sends no reset signal, which allows a small optimisation for consecutive write commands. Please note that the reset time of the old code should be 50 microseconds, as the define WS2812_DURATION_RESET suggested. Due to the restrictions of the old RMT translator routine, it was slightly increased to 51.2 microseconds. This patch keeps the value of 51.2 microseconds to be as compatible as possible. --- components/modules/ws2812.c | 24 +++++++++++++++++- components/platform/include/platform.h | 2 +- components/platform/ws2812.c | 35 +++++++++++++++++++++----- docs/modules/ws2812.md | 3 ++- 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/components/modules/ws2812.c b/components/modules/ws2812.c index 7f5817ffd..fd7e9492e 100644 --- a/components/modules/ws2812.c +++ b/components/modules/ws2812.c @@ -16,6 +16,8 @@ #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 { @@ -63,6 +65,26 @@ 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. @@ -170,7 +192,7 @@ static int ws2812_write( lua_State* L ) lua_pop( L, 1 ); // prepare channel - if (platform_ws2812_setup( gpio_num, t0h, t0l, t1h, t1l, (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 dc66d4bf0..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, uint32_t bit0h, uint32_t bit0l, uint32_t bit1h, uint32_t bit1l, 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 3d30b85ff..c48515516 100644 --- a/components/platform/ws2812.c +++ b/components/platform/ws2812.c @@ -38,14 +38,12 @@ // divider to generate 100ns base period from 80MHz APB clock #define WS2812_CLKDIV (100 * 80 /1000) -// This is 512 * 100ns, which is a bit above the requisite 50us -const rmt_item32_t ws2812_rmt_reset = { .level0 = 0, .duration0 = 256, .level1 = 0, .duration1 = 256 }; - // 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; @@ -78,7 +76,7 @@ static void ws2812_sample_to_rmt(const void *src, rmt_item32_t *dest, size_t src if( ptContext->needs_reset==true ) { - dest[cnt_out++] = ws2812_rmt_reset; + dest[cnt_out++] = ptContext->reset; ptContext->needs_reset = false; } if( src!=NULL && src_size>0 ) @@ -119,21 +117,46 @@ static void ws2812_sample_to_rmt(const void *src, rmt_item32_t *dest, size_t src *item_num = cnt_out; } -int platform_ws2812_setup( uint8_t gpio_num, uint32_t t0h, uint32_t t0l, uint32_t t1h, uint32_t t1l, 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( 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 ) diff --git a/docs/modules/ws2812.md b/docs/modules/ws2812.md index 57d282840..e5cfd8800 100644 --- a/docs/modules/ws2812.md +++ b/docs/modules/ws2812.md @@ -24,6 +24,7 @@ Variable number of tables, each describing a single strip. Required elements are 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. @@ -52,7 +53,7 @@ ws2812.write({pin = 4, data = string.char(255, 0, 0, 255, 0, 0)}, ``` ```lua -ws2812.write({pin = 8, t0h = 3, t0l = 9, t1h = 6, t1l = 6, data = string.char(1, 0, 0)}) -- turn the SK6812 RGB led on the ESP32-C3-DevKitM-1 to green +ws2812.write({pin = 8, reset = 800, t0h = 3, t0l = 9, t1h = 6, t1l = 6, data = string.char(1, 0, 0)}) -- turn the SK6812 RGB led on the ESP32-C3-DevKitM-1 to green ``` # Buffer module From 96a6410596875d50420b045d12ba00b9ca4986e8 Mon Sep 17 00:00:00 2001 From: Christoph Thelen Date: Sun, 4 Feb 2024 12:36:07 +0100 Subject: [PATCH 5/6] Minimize the time drift between RMT channels. Place all RMT channels in a group to minimize the time drift between the signals. Please note that this feature is not available on all platforms. --- components/platform/ws2812.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/components/platform/ws2812.c b/components/platform/ws2812.c index c48515516..21c14a4cf 100644 --- a/components/platform/ws2812.c +++ b/components/platform/ws2812.c @@ -32,6 +32,7 @@ #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 @@ -277,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) { @@ -296,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; } From 504e268570dba6fe15e393f7b4febe89ab6091fe Mon Sep 17 00:00:00 2001 From: Christoph Thelen Date: Wed, 7 Feb 2024 09:36:14 +0100 Subject: [PATCH 6/6] Fix the description of the SK6812 LED in the example code. The SK6812 expects the data for the green LED first, then red and finally blue. It should be described as a GRB LED. --- docs/modules/ws2812.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ws2812.md b/docs/modules/ws2812.md index e5cfd8800..c4ae1c4c2 100644 --- a/docs/modules/ws2812.md +++ b/docs/modules/ws2812.md @@ -53,7 +53,7 @@ ws2812.write({pin = 4, data = string.char(255, 0, 0, 255, 0, 0)}, ``` ```lua -ws2812.write({pin = 8, reset = 800, t0h = 3, t0l = 9, t1h = 6, t1l = 6, data = string.char(1, 0, 0)}) -- turn the SK6812 RGB led on the ESP32-C3-DevKitM-1 to green +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