diff --git a/components/modules/ws2812.c b/components/modules/ws2812.c index 76a84523..1f80f823 100644 --- a/components/modules/ws2812.c +++ b/components/modules/ws2812.c @@ -11,6 +11,13 @@ #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 500 typedef struct { int size; @@ -33,6 +40,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 +64,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 +191,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 786e9ab5..e1eab272 100644 --- a/components/platform/include/platform.h +++ b/components/platform/include/platform.h @@ -210,7 +210,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 67fa562d..1a7dabb9 100644 --- a/components/platform/ws2812.c +++ b/components/platform/ws2812.c @@ -30,122 +30,183 @@ #include "driver/gpio.h" #include "esp_log.h" #include "soc/periph_defs.h" +#include "soc/rmt_reg.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; + bool sendreset; + rmt_item32_t reset; + rmt_item32_t bits[2]; const uint8_t *data; size_t len; - size_t tx_offset; + uint8_t bitpos; } 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 ws2812_fill_memory_encoded( rmt_channel_t channel, const uint8_t *data, size_t len, size_t tx_offset ) +// Callback function to convert GRB data to pulse lengths. +static void ws2812_convert(const void *src, rmt_item32_t *dest, size_t src_size, size_t wanted_num, size_t *translated_size, size_t *item_num) { - for (size_t idx = 0; idx < len; idx++) { - uint8_t byte = data[idx]; + 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; - 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; + 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; - byte <<= 1; - } - } -} - - -static void 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]); -#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S2) - uint32_t data_sub_len = RMT.tx_lim_ch[channel].limit/8; -#elif defined(CONFIG_IDF_TARGET_ESP32S3) - uint32_t data_sub_len = RMT.chn_tx_lim[channel].tx_lim_chn/8; -#else - uint32_t data_sub_len = RMT.tx_lim[channel].limit/8; -#endif - - 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*8].val = 0; - chain->data += chain->len; - chain->len = 0; + if( ptContext->sendreset==true ) + { + dest[cnt_out++] = ptContext->reset; + ptContext->sendreset = false; } - if (chain->tx_offset == 0) { - chain->tx_offset = data_sub_len * 8; - } else { - chain->tx_offset = 0; + 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 = 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->tx_offset = 0; + chain->bitpos = 0; + + // Send a reset if "reset" is not 0. + chain->sendreset = (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); @@ -192,6 +253,7 @@ int platform_ws2812_send( void ) rmt_tx.tx_config.idle_level = 0; rmt_tx.tx_config.idle_output_en = true; rmt_tx.rmt_mode = RMT_MODE_TX; + rmt_tx.flags = 0; // configure selected RMT channels for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX && res == PLATFORM_OK; channel++) { @@ -202,26 +264,38 @@ int platform_ws2812_send( void ) res = PLATFORM_ERR; break; } - if (rmt_driver_install( channel, 0, PLATFORM_RMT_INTR_FLAGS ) != ESP_OK) { + if (rmt_driver_install( channel, 0, 0 /*PLATFORM_RMT_INTR_FLAGS*/ ) != ESP_OK) { + res = PLATFORM_ERR; + break; + } + if (rmt_translator_init( channel, ws2812_convert) != ESP_OK) { + res = PLATFORM_ERR; + break; + } + if (rmt_translator_set_context( channel, &(ws2812_chains[channel])) != ESP_OK) { res = PLATFORM_ERR; break; } } } - - // hook-in our shared ISR - esp_intr_alloc( ETS_RMT_INTR_SOURCE, PLATFORM_RMT_INTR_FLAGS, ws2812_isr, NULL, &ws2812_intr_handle ); - +#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) { - // 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, + if (rmt_write_sample( channel, + ws2812_chains[channel].data, + ws2812_chains[channel].len, false ) != ESP_OK) { res = PLATFORM_ERR; break; @@ -236,10 +310,16 @@ int platform_ws2812_send( void ) } } - - // un-hook our ISR - esp_intr_free( ws2812_intr_handle ); - +#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 c408b096..c448d24e 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 500 which generates a reset of 50us. +- `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 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.