Improved auto-selection of LED hardware support (RMT, SPI) (#22618)

This commit is contained in:
s-hadinger 2024-12-09 23:22:52 +01:00 committed by GitHub
parent c1834bd898
commit bdf880cf84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 105 additions and 106 deletions

View File

@ -18,6 +18,7 @@ All notable changes to this project will be documented in this file.
- Command `SetOption162 1` to disable adding export energy to energy today (#22578)
- ESP32 support for WPA2/3 Enterprise conditional in core v3.1.0.241206 (#22600)
- Support for Sonoff POWCT Energy Export Active (#22596)
- Improved auto-selection of LED hardware support (RMT, SPI)
### Breaking Changed
- ESP32 ArtNet switches from GRB to RGB encoding (#22556)

View File

@ -44,10 +44,10 @@ enum TasmotaLEDTypesEncoding : uint16_t {
enum TasmotaLEDHardware : uint32_t {
// low-order bits are reserved for channels numbers and hardware flags - currenlty not useds
// bits 16..23
TasmotaLed_HW_Default = 0x0 << 16,
TasmotaLed_RMT = 1 << 16,
TasmotaLed_SPI = 2 << 16,
TasmotaLed_I2S = 3 << 16,
TasmotaLed_HW_Default = 0x000000,
TasmotaLed_RMT = (1 << 0) << 16,
TasmotaLed_SPI = (1 << 1) << 16,
TasmotaLed_I2S = (1 << 2) << 16,
TasmotaLed_HW_None = 0xFF << 16, // indicates that the specified HW is not supported
};

View File

@ -30,40 +30,33 @@ enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_D
// convert to the appropriate hardware acceleration based on capacities of the SOC
uint32_t TasmotaLEDPusher::ResolveHardware(uint32_t hw) {
uint32_t hw_orig = hw;
uint32_t TasmotaLEDPusher::ResolveHardware(uint32_t hw_input) {
// Step 1. discard any unsupported hardware, and replace with TasmotaLed_HW_Default
uint32_t hw_type = hw & 0xFF0000; // discard bits 0..15
uint32_t hw = hw_input & 0xFF0000; // discard bits 0..15
#if !TASMOTALED_HARDWARE_RMT
if (hw_type == TasmotaLed_RMT) {
hw = TasmotaLed_HW_None;
}
hw &= ~TasmotaLed_RMT; // remove RMT flag if not supported by hardware
#endif // TASMOTALED_HARDWARE_RMT
#if !TASMOTALED_HARDWARE_SPI
if (hw_type == TasmotaLed_SPI) {
hw = TasmotaLed_HW_None;
}
hw &= ~TasmotaLed_SPI; // remove SPI flag if not supported by hardware
#endif // TASMOTALED_HARDWARE_SPI
#if !TASMOTALED_HARDWARE_I2S
if (hw_type == TasmotaLed_I2S) {
hw = TasmotaLed_HW_None;
}
hw &= ~TasmotaLed_I2S; // remove I2S flag if not supported by hardware
#endif // TASMOTALED_HARDWARE_I2S
// Step 2. If TasmotaLed_HW_Default, find a suitable scheme, RMT preferred
#if TASMOTALED_HARDWARE_RMT
if ((hw & 0xFF0000) == TasmotaLed_HW_Default) {
hw = TasmotaLed_RMT;
if (hw == TasmotaLed_HW_Default) {
hw |= TasmotaLed_RMT;
}
#endif // TASMOTALED_HARDWARE_RMT
#if TASMOTALED_HARDWARE_I2S
if ((hw & 0xFF0000) == TasmotaLed_HW_Default) {
hw = TasmotaLed_I2S;
if (hw == TasmotaLed_HW_Default) {
hw |= TasmotaLed_I2S;
}
#endif // TASMOTALED_HARDWARE_I2S
#if TASMOTALED_HARDWARE_SPI
if ((hw & 0xFF0000) == TasmotaLed_HW_Default) {
hw = TasmotaLed_SPI;
if (hw == TasmotaLed_HW_Default) {
hw |= TasmotaLed_SPI;
}
#endif // TASMOTALED_HARDWARE_SPI
return hw;
@ -75,22 +68,30 @@ TasmotaLEDPusher * TasmotaLEDPusher::Create(uint32_t hw, int8_t gpio) {
hw = TasmotaLEDPusher::ResolveHardware(hw);
switch (hw & 0XFF0000) {
#if TASMOTALED_HARDWARE_RMT
case TasmotaLed_RMT:
pusher = new TasmotaLEDPusherRMT(gpio);
if (pusher == nullptr && (hw & TasmotaLed_RMT)) {
pusher = new TasmotaLEDPusherRMT(gpio);
if (pusher->Initialized()) {
AddLog(LOG_LEVEL_DEBUG, "LED: RMT gpio %i", gpio);
break;
} else {
AddLog(LOG_LEVEL_INFO, "LED: Error create %s bus failed %i err=%i", "RMT", gpio, pusher->Error());
delete pusher;
pusher = nullptr;
}
}
#endif // TASMOTALED_HARDWARE_RMT
#if TASMOTALED_HARDWARE_SPI
case TasmotaLed_SPI:
pusher = new TasmotaLEDPusherSPI(gpio);
if (pusher == nullptr && (hw & TasmotaLed_SPI)) {
pusher = new TasmotaLEDPusherSPI(gpio);
if (pusher->Initialized()) {
AddLog(LOG_LEVEL_DEBUG, "LED: SPI gpio %i", gpio);
break;
#endif // TASMOTALED_HARDWARE_SPI
default:
break;
} else {
AddLog(LOG_LEVEL_INFO, "LED: Error create %s bus failed %i err=%i", "SPI", gpio, pusher->Error());
delete pusher;
pusher = nullptr;
}
}
#endif // TASMOTALED_HARDWARE_SPI
return pusher;
}

View File

@ -80,9 +80,11 @@ typedef struct TasmotaLED_Timing {
\*******************************************************************************************/
class TasmotaLEDPusher {
public:
TasmotaLEDPusher() : _pixel_count(0), _pixel_size(0), _led_timing(nullptr) {};
TasmotaLEDPusher() : _initialized(false), _err(ESP_OK), _pixel_count(0), _pixel_size(0), _led_timing(nullptr) {};
virtual ~TasmotaLEDPusher() {};
bool Initialized(void) const { return _initialized; }
esp_err_t Error(void) const { return _err; }
virtual bool Begin(uint16_t pixel_count, uint16_t pixel_size, const TasmotaLED_Timing * led_timing) {
_pixel_count = pixel_count;
_pixel_size = pixel_size;
@ -96,6 +98,8 @@ public:
static TasmotaLEDPusher * Create(uint32_t hw, int8_t gpio); // create instance for the provided type, or nullptr if failed
protected:
bool _initialized; // did the hardware got correctly initialized
esp_err_t _err;
uint16_t _pixel_count;
uint16_t _pixel_size;
const TasmotaLED_Timing * _led_timing;
@ -110,7 +114,7 @@ protected:
#include "driver/rmt_tx.h"
class TasmotaLEDPusherRMT : public TasmotaLEDPusher {
public:
TasmotaLEDPusherRMT(int8_t pin) : _pin(pin) {};
TasmotaLEDPusherRMT(int8_t pin);
~TasmotaLEDPusherRMT();
bool Begin(uint16_t pixel_count, uint16_t pixel_size, const TasmotaLED_Timing * led_timing) override;
@ -144,7 +148,7 @@ typedef struct led_strip_spi_obj_t {
class TasmotaLEDPusherSPI : public TasmotaLEDPusher {
public:
TasmotaLEDPusherSPI(int8_t pin) : _pin(pin), _spi_strip({}) {};
TasmotaLEDPusherSPI(int8_t pin);
~TasmotaLEDPusherSPI();
bool Begin(uint16_t pixel_count, uint16_t pixel_size, const TasmotaLED_Timing * led_timing) override;
@ -154,7 +158,8 @@ public:
protected:
int8_t _pin;
struct led_strip_spi_obj_t _spi_strip;
struct led_strip_spi_obj_t _spi_strip = {};;
const bool _with_dma = true;
};
#endif // TASMOTALED_HARDWARE_SPI

View File

@ -155,9 +155,7 @@ TasmotaLEDPusherRMT::~TasmotaLEDPusherRMT() {
}
}
bool TasmotaLEDPusherRMT::Begin(uint16_t pixel_count, uint16_t pixel_size, const TasmotaLED_Timing * led_timing) {
TasmotaLEDPusher::Begin(pixel_count, pixel_size, led_timing);
TasmotaLEDPusherRMT::TasmotaLEDPusherRMT(int8_t pin) : _pin(pin) {
esp_err_t ret = ESP_OK;
rmt_tx_channel_config_t config = {};
config.clk_src = RMT_CLK_SRC_DEFAULT;
@ -168,11 +166,15 @@ bool TasmotaLEDPusherRMT::Begin(uint16_t pixel_count, uint16_t pixel_size, const
config.flags.invert_out = false; // do not invert output signal
config.flags.with_dma = false; // do not need DMA backend
ret = rmt_new_tx_channel(&config, &_channel);
if (ret != ESP_OK) {
AddLog(LOG_LEVEL_INFO, "RMT: cannot initialize Gpio %i err=%i", _pin, ret);
return false;
_err = rmt_new_tx_channel(&config, &_channel);
if (_err == ESP_OK) {
_initialized = true;
}
}
bool TasmotaLEDPusherRMT::Begin(uint16_t pixel_count, uint16_t pixel_size, const TasmotaLED_Timing * led_timing) {
if (!_initialized) { return false; }
TasmotaLEDPusher::Begin(pixel_count, pixel_size, led_timing);
led_strip_encoder_config_t encoder_config = {
.resolution = RMT_LED_STRIP_RESOLUTION_HZ,
};
@ -198,13 +200,13 @@ bool TasmotaLEDPusherRMT::Begin(uint16_t pixel_count, uint16_t pixel_size, const
.level1 = 1,
};
// AddLog(LOG_LEVEL_INFO, "RMT: RmtBit0 0x%08X RmtBit1 0x%08X RmtReset 0x%08X", RmtBit0.val, RmtBit1.val, RmtReset.val);
ret = rmt_new_led_strip_encoder(&encoder_config, &_led_encoder, RmtBit0, RmtBit1, RmtReset);
if (ret != ESP_OK) {
_err = rmt_new_led_strip_encoder(&encoder_config, &_led_encoder, RmtBit0, RmtBit1, RmtReset);
if (_err != ESP_OK) {
// AddLog(LOG_LEVEL_INFO, "RMT: cannot initialize led strip encoder err=%i", ret);
return false;
}
ret = rmt_enable(_channel);
if (ret != ESP_OK) {
_err = rmt_enable(_channel);
if (_err != ESP_OK) {
// AddLog(LOG_LEVEL_INFO, "RMT: cannot enable channel err=%i", ret);
return false;
}
@ -212,7 +214,7 @@ bool TasmotaLEDPusherRMT::Begin(uint16_t pixel_count, uint16_t pixel_size, const
}
bool TasmotaLEDPusherRMT::CanShow(void) {
if (_channel) {
if (_channel && _initialized) {
return (ESP_OK == rmt_tx_wait_all_done(_channel, 0));
} else {
return false;
@ -220,6 +222,7 @@ bool TasmotaLEDPusherRMT::CanShow(void) {
}
bool TasmotaLEDPusherRMT::Push(uint8_t *buf) {
if (!_initialized) { return false; }
// wait for not actively sending data
// this will time out at 1 second, an arbitrarily long period of time

View File

@ -95,31 +95,9 @@ TasmotaLEDPusherSPI::~TasmotaLEDPusherSPI() {
}
}
bool TasmotaLEDPusherSPI::Begin(uint16_t pixel_count, uint16_t pixel_size, const TasmotaLED_Timing * led_timing) {
TasmotaLEDPusher::Begin(pixel_count, pixel_size, led_timing);
_spi_strip.bytes_per_pixel = _pixel_size;
_spi_strip.strip_len = _pixel_count;
esp_err_t err = ESP_OK;
uint32_t mem_caps = MALLOC_CAP_DEFAULT;
// spi_clock_source_t clk_src = SPI_CLK_SRC_DEFAULT;
spi_bus_config_t spi_bus_cfg;
spi_device_interface_config_t spi_dev_cfg;
TasmotaLEDPusherSPI::TasmotaLEDPusherSPI(int8_t pin) : _pin(pin) {
spi_host_device_t spi_host = SPI2_HOST;
bool with_dma = true; /// TODO: pass value or compute based on pixelcount
int clock_resolution_khz = 0;
if (with_dma) { // TODO
// DMA buffer must be placed in internal SRAM
mem_caps |= MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA;
}
_spi_strip.pixel_buf = (uint8_t *)heap_caps_calloc(1, _pixel_count * _pixel_size * SPI_BYTES_PER_COLOR_BYTE, mem_caps);
if (_spi_strip.pixel_buf == nullptr) {
AddLog(LOG_LEVEL_INFO, PSTR("LED: Error no mem for spi strip"));
goto err;
}
spi_bus_cfg = {
spi_bus_config_t spi_bus_cfg = {
.mosi_io_num = _pin,
//Only use MOSI to generate the signal, set -1 when other pins are not used.
.miso_io_num = -1,
@ -128,32 +106,55 @@ bool TasmotaLEDPusherSPI::Begin(uint16_t pixel_count, uint16_t pixel_size, const
.quadhd_io_num = -1,
.max_transfer_sz = _pixel_count * _pixel_size * SPI_BYTES_PER_COLOR_BYTE,
};
err = spi_bus_initialize(spi_host, &spi_bus_cfg, with_dma ? SPI_DMA_CH_AUTO : SPI_DMA_DISABLED);
if (err != ESP_OK) {
AddLog(LOG_LEVEL_INFO, PSTR("LED: Error create SPI bus failed"));
_err = spi_bus_initialize(spi_host, &spi_bus_cfg, _with_dma ? SPI_DMA_CH_AUTO : SPI_DMA_DISABLED);
if (_err == ESP_OK) {
_spi_strip.spi_host = spi_host; // confirmed working, so keep it's value to free it later
_initialized = true;
}
}
bool TasmotaLEDPusherSPI::Begin(uint16_t pixel_count, uint16_t pixel_size, const TasmotaLED_Timing * led_timing) {
if (!_initialized) {
return false;
}
TasmotaLEDPusher::Begin(pixel_count, pixel_size, led_timing);
_spi_strip.bytes_per_pixel = _pixel_size;
_spi_strip.strip_len = _pixel_count;
uint32_t mem_caps = MALLOC_CAP_DEFAULT;
// spi_clock_source_t clk_src = SPI_CLK_SRC_DEFAULT;
spi_device_interface_config_t spi_dev_cfg;
int clock_resolution_khz = 0;
if (_with_dma) { // TODO
// DMA buffer must be placed in internal SRAM
mem_caps |= MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA;
}
_spi_strip.pixel_buf = (uint8_t *)heap_caps_calloc(1, _pixel_count * _pixel_size * SPI_BYTES_PER_COLOR_BYTE, mem_caps);
if (_spi_strip.pixel_buf == nullptr) {
AddLog(LOG_LEVEL_INFO, PSTR("LED: Error no mem for spi strip"));
goto err;
}
_spi_strip.spi_host = spi_host; // confirmed working, so keep it's value to free it later
spi_dev_cfg = {
.command_bits = 0,
.address_bits = 0,
.dummy_bits = 0,
.mode = 0,
//set -1 when CS is not used
.clock_source = SPI_CLK_SRC_DEFAULT, // clk_src,
.clock_speed_hz = LED_STRIP_SPI_DEFAULT_RESOLUTION,
.spics_io_num = -1,
.queue_size = LED_STRIP_SPI_DEFAULT_TRANS_QUEUE_SIZE,
.command_bits = 0,
.address_bits = 0,
.dummy_bits = 0,
.mode = 0,
//set -1 when CS is not used
.clock_source = SPI_CLK_SRC_DEFAULT, // clk_src,
.clock_speed_hz = LED_STRIP_SPI_DEFAULT_RESOLUTION,
.spics_io_num = -1,
.queue_size = LED_STRIP_SPI_DEFAULT_TRANS_QUEUE_SIZE,
};
err = spi_bus_add_device(_spi_strip.spi_host, &spi_dev_cfg, &_spi_strip.spi_device);
if (err != ESP_OK) {
_err = spi_bus_add_device(_spi_strip.spi_host, &spi_dev_cfg, &_spi_strip.spi_device);
if (_err != ESP_OK) {
// AddLog(LOG_LEVEL_INFO, "LED: Error failed to add spi device");
goto err;
}
spi_device_get_actual_freq(_spi_strip.spi_device, &clock_resolution_khz);
if (err != ESP_OK) {
_err = spi_device_get_actual_freq(_spi_strip.spi_device, &clock_resolution_khz);
if (_err != ESP_OK) {
// AddLog(LOG_LEVEL_INFO, "LED: Error failed to get spi frequency");
goto err;
}
@ -163,7 +164,6 @@ bool TasmotaLEDPusherSPI::Begin(uint16_t pixel_count, uint16_t pixel_size, const
// AddLog(LOG_LEVEL_INFO, "LED: Error unsupported clock resolution: %dKHz", clock_resolution_khz);
goto err;
}
return true;
err:
if (_spi_strip.spi_device) {
@ -172,15 +172,16 @@ err:
if (_spi_strip.spi_host) {
spi_bus_free(_spi_strip.spi_host);
}
_initialized = false;
return false;
}
bool TasmotaLEDPusherSPI::CanShow(void) {
return true; // TODO
return _initialized; // TODO
}
bool TasmotaLEDPusherSPI::Push(uint8_t *buf) {
if (!_initialized) { return false; }
if (CanShow()) {
led_strip_transmit_buffer(&_spi_strip, buf);
}

View File

@ -95,20 +95,8 @@ const uint16_t kTasLed_PixelWhite = TasmotaLed_xxxW;
const uint16_t kTasLed_Type = kTasLed_PixelSize | kTasLed_PixelOrder | kTasLed_PixelWhite | kTasLed_Timing;
// select hardware acceleration - bitbanging is not supported on ESP32 due to interference of interrupts
#if CONFIG_IDF_TARGET_ESP32C2
const uint32_t kTasLed_Hardware = TasmotaLed_SPI; // no I2S for the C2
#else // all other ESP32 variants
#if defined(USE_WS2812_DMA)
const uint32_t kTasLed_Hardware = TasmotaLed_RMT; // default DMA to RMT
#elif defined(USE_WS2812_RMT)
const uint32_t kTasLed_Hardware = TasmotaLed_RMT; // default DMA to RMT
#elif defined(USE_WS2812_I2S)
const uint32_t kTasLed_Hardware = TasmotaLed_I2S; // I2S
#else
const uint32_t kTasLed_Hardware = TasmotaLed_RMT; // default DMA to RMT
#endif
#endif
// select hardware acceleration - bitbanging is not supported on ESP32 due to interference of interrupts - use default
const uint32_t kTasLed_Hardware = TasmotaLed_HW_Default; // use whatever is available
#if (USE_WS2812_HARDWARE == NEO_HW_P9813)
#error "P9813 is not supported by this library"