mirror of https://github.com/arendst/Tasmota.git
Improved auto-selection of LED hardware support (RMT, SPI) (#22618)
This commit is contained in:
parent
c1834bd898
commit
bdf880cf84
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue