mirror of
https://github.com/esphome/esphome.git
synced 2024-11-21 22:48:10 +01:00
Create esp32 rmt addressable light driver (#4708)
This commit is contained in:
parent
679633245d
commit
4b664b6f09
6 changed files with 465 additions and 0 deletions
|
@ -84,6 +84,7 @@ esphome/components/esp32_ble_server/* @jesserockz
|
|||
esphome/components/esp32_camera_web_server/* @ayufan
|
||||
esphome/components/esp32_can/* @Sympatron
|
||||
esphome/components/esp32_improv/* @jesserockz
|
||||
esphome/components/esp32_rmt_led_strip/* @jesserockz
|
||||
esphome/components/esp8266/* @esphome/core
|
||||
esphome/components/ethernet_info/* @gtjadsonsantos
|
||||
esphome/components/exposure_notifications/* @OttoWinter
|
||||
|
|
0
esphome/components/esp32_rmt_led_strip/__init__.py
Normal file
0
esphome/components/esp32_rmt_led_strip/__init__.py
Normal file
207
esphome/components/esp32_rmt_led_strip/led_strip.cpp
Normal file
207
esphome/components/esp32_rmt_led_strip/led_strip.cpp
Normal file
|
@ -0,0 +1,207 @@
|
|||
#include "led_strip.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <esp_attr.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_rmt_led_strip {
|
||||
|
||||
static const char *const TAG = "esp32_rmt_led_strip";
|
||||
|
||||
static const uint8_t RMT_CLK_DIV = 2;
|
||||
|
||||
void ESP32RMTLEDStripLightOutput::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up ESP32 LED Strip...");
|
||||
|
||||
size_t buffer_size = this->get_buffer_size_();
|
||||
|
||||
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||
this->buf_ = allocator.allocate(buffer_size);
|
||||
if (this->buf_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Cannot allocate LED buffer!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->effect_data_ = allocator.allocate(this->num_leds_);
|
||||
if (this->effect_data_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Cannot allocate effect data!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
ExternalRAMAllocator<rmt_item32_t> rmt_allocator(ExternalRAMAllocator<rmt_item32_t>::ALLOW_FAILURE);
|
||||
this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8); // 8 bits per byte, 1 rmt_item32_t per bit
|
||||
|
||||
rmt_config_t config;
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.channel = this->channel_;
|
||||
config.rmt_mode = RMT_MODE_TX;
|
||||
config.gpio_num = gpio_num_t(this->pin_);
|
||||
config.mem_block_num = 1;
|
||||
config.clk_div = RMT_CLK_DIV;
|
||||
config.tx_config.loop_en = false;
|
||||
config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
|
||||
config.tx_config.carrier_en = false;
|
||||
config.tx_config.idle_level = RMT_IDLE_LEVEL_LOW;
|
||||
config.tx_config.idle_output_en = true;
|
||||
|
||||
if (rmt_config(&config) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Cannot initialize RMT!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (rmt_driver_install(config.channel, 0, 0) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Cannot install RMT driver!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high,
|
||||
uint32_t bit1_low) {
|
||||
float ratio = (float) APB_CLK_FREQ / RMT_CLK_DIV / 1e09f;
|
||||
|
||||
// 0-bit
|
||||
this->bit0_.duration0 = (uint32_t) (ratio * bit0_high);
|
||||
this->bit0_.level0 = 1;
|
||||
this->bit0_.duration1 = (uint32_t) (ratio * bit0_low);
|
||||
this->bit0_.level1 = 0;
|
||||
// 1-bit
|
||||
this->bit1_.duration0 = (uint32_t) (ratio * bit1_high);
|
||||
this->bit1_.level0 = 1;
|
||||
this->bit1_.duration1 = (uint32_t) (ratio * bit1_low);
|
||||
this->bit1_.level1 = 0;
|
||||
}
|
||||
|
||||
void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
|
||||
// protect from refreshing too often
|
||||
uint32_t now = micros();
|
||||
if (*this->max_refresh_rate_ != 0 && (now - this->last_refresh_) < *this->max_refresh_rate_) {
|
||||
// try again next loop iteration, so that this change won't get lost
|
||||
this->schedule_show();
|
||||
return;
|
||||
}
|
||||
this->last_refresh_ = now;
|
||||
this->mark_shown_();
|
||||
|
||||
ESP_LOGVV(TAG, "Writing RGB values to bus...");
|
||||
|
||||
if (rmt_wait_tx_done(this->channel_, pdMS_TO_TICKS(1000)) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "RMT TX timeout");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
delayMicroseconds(50);
|
||||
|
||||
size_t buffer_size = this->get_buffer_size_();
|
||||
|
||||
size_t size = 0;
|
||||
size_t len = 0;
|
||||
uint8_t *psrc = this->buf_;
|
||||
rmt_item32_t *pdest = this->rmt_buf_;
|
||||
while (size < buffer_size) {
|
||||
uint8_t b = *psrc;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
pdest->val = b & (1 << (7 - i)) ? this->bit1_.val : this->bit0_.val;
|
||||
pdest++;
|
||||
len++;
|
||||
}
|
||||
size++;
|
||||
psrc++;
|
||||
}
|
||||
|
||||
if (rmt_write_items(this->channel_, this->rmt_buf_, len, false) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "RMT TX error");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index) const {
|
||||
int32_t r = 0, g = 0, b = 0;
|
||||
switch (this->rgb_order_) {
|
||||
case ORDER_RGB:
|
||||
r = 0;
|
||||
g = 1;
|
||||
b = 2;
|
||||
break;
|
||||
case ORDER_RBG:
|
||||
r = 0;
|
||||
g = 2;
|
||||
b = 1;
|
||||
break;
|
||||
case ORDER_GRB:
|
||||
r = 1;
|
||||
g = 0;
|
||||
b = 2;
|
||||
break;
|
||||
case ORDER_GBR:
|
||||
r = 2;
|
||||
g = 0;
|
||||
b = 1;
|
||||
break;
|
||||
case ORDER_BGR:
|
||||
r = 2;
|
||||
g = 1;
|
||||
b = 0;
|
||||
break;
|
||||
case ORDER_BRG:
|
||||
r = 1;
|
||||
g = 2;
|
||||
b = 0;
|
||||
break;
|
||||
}
|
||||
uint8_t multiplier = this->is_rgbw_ ? 4 : 3;
|
||||
return {this->buf_ + (index * multiplier) + r,
|
||||
this->buf_ + (index * multiplier) + g,
|
||||
this->buf_ + (index * multiplier) + b,
|
||||
this->is_rgbw_ ? this->buf_ + (index * multiplier) + 3 : nullptr,
|
||||
&this->effect_data_[index],
|
||||
&this->correction_};
|
||||
}
|
||||
|
||||
void ESP32RMTLEDStripLightOutput::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ESP32 RMT LED Strip:");
|
||||
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
|
||||
ESP_LOGCONFIG(TAG, " Channel: %u", this->channel_);
|
||||
const char *rgb_order;
|
||||
switch (this->rgb_order_) {
|
||||
case ORDER_RGB:
|
||||
rgb_order = "RGB";
|
||||
break;
|
||||
case ORDER_RBG:
|
||||
rgb_order = "RBG";
|
||||
break;
|
||||
case ORDER_GRB:
|
||||
rgb_order = "GRB";
|
||||
break;
|
||||
case ORDER_GBR:
|
||||
rgb_order = "GBR";
|
||||
break;
|
||||
case ORDER_BGR:
|
||||
rgb_order = "BGR";
|
||||
break;
|
||||
case ORDER_BRG:
|
||||
rgb_order = "BRG";
|
||||
break;
|
||||
default:
|
||||
rgb_order = "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " RGB Order: %s", rgb_order);
|
||||
ESP_LOGCONFIG(TAG, " Max refresh rate: %u", *this->max_refresh_rate_);
|
||||
ESP_LOGCONFIG(TAG, " Number of LEDs: %u", this->num_leds_);
|
||||
}
|
||||
|
||||
float ESP32RMTLEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
|
||||
} // namespace esp32_rmt_led_strip
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
87
esphome/components/esp32_rmt_led_strip/led_strip.h
Normal file
87
esphome/components/esp32_rmt_led_strip/led_strip.h
Normal file
|
@ -0,0 +1,87 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/components/light/addressable_light.h"
|
||||
#include "esphome/components/light/light_output.h"
|
||||
#include "esphome/core/color.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/rmt.h>
|
||||
#include <esp_err.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace esp32_rmt_led_strip {
|
||||
|
||||
enum RGBOrder : uint8_t {
|
||||
ORDER_RGB,
|
||||
ORDER_RBG,
|
||||
ORDER_GRB,
|
||||
ORDER_GBR,
|
||||
ORDER_BGR,
|
||||
ORDER_BRG,
|
||||
};
|
||||
|
||||
class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
||||
public:
|
||||
void setup() override;
|
||||
void write_state(light::LightState *state) override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
int32_t size() const override { return this->num_leds_; }
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
if (this->is_rgbw_) {
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::RGB_WHITE});
|
||||
} else {
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB});
|
||||
}
|
||||
return traits;
|
||||
}
|
||||
|
||||
void set_pin(uint8_t pin) { this->pin_ = pin; }
|
||||
void set_num_leds(uint16_t num_leds) { this->num_leds_ = num_leds; }
|
||||
void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; }
|
||||
|
||||
/// Set a maximum refresh rate in µs as some lights do not like being updated too often.
|
||||
void set_max_refresh_rate(uint32_t interval_us) { this->max_refresh_rate_ = interval_us; }
|
||||
|
||||
void set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high, uint32_t bit1_low);
|
||||
|
||||
void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; }
|
||||
void set_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; }
|
||||
|
||||
void clear_effect_data() override {
|
||||
for (int i = 0; i < this->size(); i++)
|
||||
this->effect_data_[i] = 0;
|
||||
}
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
light::ESPColorView get_view_internal(int32_t index) const override;
|
||||
|
||||
size_t get_buffer_size_() const { return this->num_leds_ * (3 + this->is_rgbw_); }
|
||||
|
||||
uint8_t *buf_{nullptr};
|
||||
uint8_t *effect_data_{nullptr};
|
||||
rmt_item32_t *rmt_buf_{nullptr};
|
||||
|
||||
uint8_t pin_;
|
||||
uint16_t num_leds_;
|
||||
bool is_rgbw_;
|
||||
|
||||
rmt_item32_t bit0_, bit1_;
|
||||
RGBOrder rgb_order_;
|
||||
rmt_channel_t channel_;
|
||||
|
||||
uint32_t last_refresh_{0};
|
||||
optional<uint32_t> max_refresh_rate_{};
|
||||
};
|
||||
|
||||
} // namespace esp32_rmt_led_strip
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
151
esphome/components/esp32_rmt_led_strip/light.py
Normal file
151
esphome/components/esp32_rmt_led_strip/light.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import esp32, light
|
||||
from esphome.const import (
|
||||
CONF_CHIPSET,
|
||||
CONF_MAX_REFRESH_RATE,
|
||||
CONF_NUM_LEDS,
|
||||
CONF_OUTPUT_ID,
|
||||
CONF_PIN,
|
||||
CONF_RGB_ORDER,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
esp32_rmt_led_strip_ns = cg.esphome_ns.namespace("esp32_rmt_led_strip")
|
||||
ESP32RMTLEDStripLightOutput = esp32_rmt_led_strip_ns.class_(
|
||||
"ESP32RMTLEDStripLightOutput", light.AddressableLight
|
||||
)
|
||||
|
||||
rmt_channel_t = cg.global_ns.enum("rmt_channel_t")
|
||||
|
||||
RGBOrder = esp32_rmt_led_strip_ns.enum("RGBOrder")
|
||||
|
||||
RGB_ORDERS = {
|
||||
"RGB": RGBOrder.ORDER_RGB,
|
||||
"RBG": RGBOrder.ORDER_RBG,
|
||||
"GRB": RGBOrder.ORDER_GRB,
|
||||
"GBR": RGBOrder.ORDER_GBR,
|
||||
"BGR": RGBOrder.ORDER_BGR,
|
||||
"BRG": RGBOrder.ORDER_BRG,
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class LEDStripTimings:
|
||||
bit0_high: int
|
||||
bit0_low: int
|
||||
bit1_high: int
|
||||
bit1_low: int
|
||||
|
||||
|
||||
CHIPSETS = {
|
||||
"WS2812": LEDStripTimings(400, 1000, 1000, 400),
|
||||
"SK6812": LEDStripTimings(300, 900, 600, 600),
|
||||
"APA106": LEDStripTimings(350, 1360, 1360, 350),
|
||||
"SM16703": LEDStripTimings(300, 900, 1360, 350),
|
||||
}
|
||||
|
||||
|
||||
CONF_IS_RGBW = "is_rgbw"
|
||||
CONF_BIT0_HIGH = "bit0_high"
|
||||
CONF_BIT0_LOW = "bit0_low"
|
||||
CONF_BIT1_HIGH = "bit1_high"
|
||||
CONF_BIT1_LOW = "bit1_low"
|
||||
CONF_RMT_CHANNEL = "rmt_channel"
|
||||
|
||||
RMT_CHANNELS = {
|
||||
esp32.const.VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7],
|
||||
esp32.const.VARIANT_ESP32S2: [0, 1, 2, 3],
|
||||
esp32.const.VARIANT_ESP32S3: [0, 1, 2, 3],
|
||||
esp32.const.VARIANT_ESP32C3: [0, 1],
|
||||
}
|
||||
|
||||
|
||||
def _validate_rmt_channel(value):
|
||||
variant = esp32.get_esp32_variant()
|
||||
if variant not in RMT_CHANNELS:
|
||||
raise cv.Invalid(f"ESP32 variant {variant} does not support RMT.")
|
||||
if value not in RMT_CHANNELS[variant]:
|
||||
raise cv.Invalid(
|
||||
f"RMT channel {value} is not supported for ESP32 variant {variant}."
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
light.ADDRESSABLE_LIGHT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(ESP32RMTLEDStripLightOutput),
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
|
||||
cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True),
|
||||
cv.Required(CONF_RMT_CHANNEL): _validate_rmt_channel,
|
||||
cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
|
||||
cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
|
||||
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
|
||||
cv.Inclusive(
|
||||
CONF_BIT0_HIGH,
|
||||
"custom",
|
||||
): cv.positive_time_period_microseconds,
|
||||
cv.Inclusive(
|
||||
CONF_BIT0_LOW,
|
||||
"custom",
|
||||
): cv.positive_time_period_microseconds,
|
||||
cv.Inclusive(
|
||||
CONF_BIT1_HIGH,
|
||||
"custom",
|
||||
): cv.positive_time_period_microseconds,
|
||||
cv.Inclusive(
|
||||
CONF_BIT1_LOW,
|
||||
"custom",
|
||||
): cv.positive_time_period_microseconds,
|
||||
}
|
||||
),
|
||||
cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
||||
await light.register_light(var, config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_num_leds(config[CONF_NUM_LEDS]))
|
||||
cg.add(var.set_pin(config[CONF_PIN]))
|
||||
|
||||
if CONF_MAX_REFRESH_RATE in config:
|
||||
cg.add(var.set_max_refresh_rate(config[CONF_MAX_REFRESH_RATE]))
|
||||
|
||||
if CONF_CHIPSET in config:
|
||||
chipset = CHIPSETS[config[CONF_CHIPSET]]
|
||||
cg.add(
|
||||
var.set_led_params(
|
||||
chipset.bit0_high,
|
||||
chipset.bit0_low,
|
||||
chipset.bit1_high,
|
||||
chipset.bit1_low,
|
||||
)
|
||||
)
|
||||
else:
|
||||
cg.add(
|
||||
var.set_led_params(
|
||||
config[CONF_BIT0_HIGH],
|
||||
config[CONF_BIT0_LOW],
|
||||
config[CONF_BIT1_HIGH],
|
||||
config[CONF_BIT1_LOW],
|
||||
)
|
||||
)
|
||||
|
||||
cg.add(var.set_rgb_order(config[CONF_RGB_ORDER]))
|
||||
cg.add(var.set_is_rgbw(config[CONF_IS_RGBW]))
|
||||
|
||||
cg.add(
|
||||
var.set_rmt_channel(
|
||||
getattr(rmt_channel_t, f"RMT_CHANNEL_{config[CONF_RMT_CHANNEL]}")
|
||||
)
|
||||
)
|
|
@ -644,3 +644,22 @@ key_collector:
|
|||
source_id: keypad
|
||||
min_length: 4
|
||||
max_length: 4
|
||||
|
||||
light:
|
||||
- platform: esp32_rmt_led_strip
|
||||
id: led_strip
|
||||
pin: 13
|
||||
num_leds: 60
|
||||
rmt_channel: 6
|
||||
rgb_order: GRB
|
||||
chipset: ws2812
|
||||
- platform: esp32_rmt_led_strip
|
||||
id: led_strip2
|
||||
pin: 15
|
||||
num_leds: 60
|
||||
rmt_channel: 2
|
||||
rgb_order: RGB
|
||||
bit0_high: 100us
|
||||
bit0_low: 100us
|
||||
bit1_high: 100us
|
||||
bit1_low: 100us
|
||||
|
|
Loading…
Reference in a new issue