mirror of
https://github.com/esphome/esphome.git
synced 2024-11-22 06:58:11 +01:00
Rp2040 pio ledstrip (#4818)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
784cc3bc29
commit
a15ac06771
9 changed files with 610 additions and 3 deletions
|
@ -220,6 +220,7 @@ esphome/components/restart/* @esphome/core
|
||||||
esphome/components/rf_bridge/* @jesserockz
|
esphome/components/rf_bridge/* @jesserockz
|
||||||
esphome/components/rgbct/* @jesserockz
|
esphome/components/rgbct/* @jesserockz
|
||||||
esphome/components/rp2040/* @jesserockz
|
esphome/components/rp2040/* @jesserockz
|
||||||
|
esphome/components/rp2040_pio_led_strip/* @Papa-DMan
|
||||||
esphome/components/rp2040_pwm/* @jesserockz
|
esphome/components/rp2040_pwm/* @jesserockz
|
||||||
esphome/components/rtttl/* @glmnet
|
esphome/components/rtttl/* @glmnet
|
||||||
esphome/components/safe_mode/* @jsuanet @paulmonigatti
|
esphome/components/safe_mode/* @jsuanet @paulmonigatti
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from string import ascii_letters, digits
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
|
@ -12,9 +15,11 @@ from esphome.const import (
|
||||||
KEY_TARGET_FRAMEWORK,
|
KEY_TARGET_FRAMEWORK,
|
||||||
KEY_TARGET_PLATFORM,
|
KEY_TARGET_PLATFORM,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority, EsphomeError
|
||||||
|
from esphome.helpers import mkdir_p, write_file
|
||||||
|
import esphome.platformio_api as api
|
||||||
|
|
||||||
from .const import KEY_BOARD, KEY_RP2040, rp2040_ns
|
from .const import KEY_BOARD, KEY_PIO_FILES, KEY_RP2040, rp2040_ns
|
||||||
|
|
||||||
# force import gpio to register pin schema
|
# force import gpio to register pin schema
|
||||||
from .gpio import rp2040_pin_to_code # noqa
|
from .gpio import rp2040_pin_to_code # noqa
|
||||||
|
@ -33,6 +38,8 @@ def set_core_data(config):
|
||||||
)
|
)
|
||||||
CORE.data[KEY_RP2040][KEY_BOARD] = config[CONF_BOARD]
|
CORE.data[KEY_RP2040][KEY_BOARD] = config[CONF_BOARD]
|
||||||
|
|
||||||
|
CORE.data[KEY_RP2040][KEY_PIO_FILES] = {}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@ -148,7 +155,10 @@ async def to_code(config):
|
||||||
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
|
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
|
||||||
cg.add_platformio_option(
|
cg.add_platformio_option(
|
||||||
"platform_packages",
|
"platform_packages",
|
||||||
[f"earlephilhower/framework-arduinopico@{conf[CONF_SOURCE]}"],
|
[
|
||||||
|
f"earlephilhower/framework-arduinopico@{conf[CONF_SOURCE]}",
|
||||||
|
"earlephilhower/tool-pioasm-rp2040-earlephilhower",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
cg.add_platformio_option("board_build.core", "earlephilhower")
|
cg.add_platformio_option("board_build.core", "earlephilhower")
|
||||||
|
@ -159,3 +169,53 @@ async def to_code(config):
|
||||||
"USE_ARDUINO_VERSION_CODE",
|
"USE_ARDUINO_VERSION_CODE",
|
||||||
cg.RawExpression(f"VERSION_CODE({ver.major}, {ver.minor}, {ver.patch})"),
|
cg.RawExpression(f"VERSION_CODE({ver.major}, {ver.minor}, {ver.patch})"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_pio_file(component: str, key: str, data: str):
|
||||||
|
try:
|
||||||
|
cv.validate_id_name(key)
|
||||||
|
except cv.Invalid as e:
|
||||||
|
raise EsphomeError(
|
||||||
|
f"[{component}] Invalid PIO key: {key}. Allowed characters: [{ascii_letters}{digits}_]\nPlease report an issue https://github.com/esphome/issues"
|
||||||
|
) from e
|
||||||
|
CORE.data[KEY_RP2040][KEY_PIO_FILES][key] = data
|
||||||
|
|
||||||
|
|
||||||
|
def generate_pio_files() -> bool:
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
shutil.rmtree(CORE.relative_build_path("src/pio"), ignore_errors=True)
|
||||||
|
|
||||||
|
includes: list[str] = []
|
||||||
|
files = CORE.data[KEY_RP2040][KEY_PIO_FILES]
|
||||||
|
if not files:
|
||||||
|
return False
|
||||||
|
for key, data in files.items():
|
||||||
|
pio_path = CORE.relative_build_path(f"src/pio/{key}.pio")
|
||||||
|
mkdir_p(os.path.dirname(pio_path))
|
||||||
|
write_file(pio_path, data)
|
||||||
|
_LOGGER.info("Assembling PIO assembly code")
|
||||||
|
retval = api.run_platformio_cli(
|
||||||
|
"pkg",
|
||||||
|
"exec",
|
||||||
|
"--package",
|
||||||
|
"earlephilhower/tool-pioasm-rp2040-earlephilhower",
|
||||||
|
"--",
|
||||||
|
"pioasm",
|
||||||
|
pio_path,
|
||||||
|
pio_path + ".h",
|
||||||
|
)
|
||||||
|
includes.append(f"pio/{key}.pio.h")
|
||||||
|
if retval != 0:
|
||||||
|
raise EsphomeError("PIO assembly failed")
|
||||||
|
|
||||||
|
write_file(
|
||||||
|
CORE.relative_build_path("src/pio_includes.h"),
|
||||||
|
"#pragma once\n" + "\n".join([f'#include "{include}"' for include in includes]),
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# Called by writer.py
|
||||||
|
def copy_files() -> bool:
|
||||||
|
return generate_pio_files()
|
||||||
|
|
|
@ -2,5 +2,6 @@ import esphome.codegen as cg
|
||||||
|
|
||||||
KEY_BOARD = "board"
|
KEY_BOARD = "board"
|
||||||
KEY_RP2040 = "rp2040"
|
KEY_RP2040 = "rp2040"
|
||||||
|
KEY_PIO_FILES = "pio_files"
|
||||||
|
|
||||||
rp2040_ns = cg.esphome_ns.namespace("rp2040")
|
rp2040_ns = cg.esphome_ns.namespace("rp2040")
|
||||||
|
|
1
esphome/components/rp2040_pio_led_strip/__init__.py
Normal file
1
esphome/components/rp2040_pio_led_strip/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
CODEOWNERS = ["@Papa-DMan"]
|
139
esphome/components/rp2040_pio_led_strip/led_strip.cpp
Normal file
139
esphome/components/rp2040_pio_led_strip/led_strip.cpp
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
#include "led_strip.h"
|
||||||
|
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <hardware/clocks.h>
|
||||||
|
#include <hardware/pio.h>
|
||||||
|
#include <pico/stdlib.h>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace rp2040_pio_led_strip {
|
||||||
|
|
||||||
|
static const char *TAG = "rp2040_pio_led_strip";
|
||||||
|
|
||||||
|
void RP2040PIOLEDStripLightOutput::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up RP2040 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, "Failed to allocate buffer of size %u", buffer_size);
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->effect_data_ = allocator.allocate(this->num_leds_);
|
||||||
|
if (this->effect_data_ == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Failed to allocate effect data of size %u", this->num_leds_);
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select PIO instance to use (0 or 1)
|
||||||
|
this->pio_ = pio0;
|
||||||
|
if (this->pio_ == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Failed to claim PIO instance");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the assembled program into the PIO and get its location in the PIO's instruction memory
|
||||||
|
uint offset = pio_add_program(this->pio_, this->program_);
|
||||||
|
|
||||||
|
// Configure the state machine's PIO, and start it
|
||||||
|
this->sm_ = pio_claim_unused_sm(this->pio_, true);
|
||||||
|
if (this->sm_ < 0) {
|
||||||
|
ESP_LOGE(TAG, "Failed to claim PIO state machine");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->init_(this->pio_, this->sm_, offset, this->pin_, this->max_refresh_rate_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RP2040PIOLEDStripLightOutput::write_state(light::LightState *state) {
|
||||||
|
ESP_LOGVV(TAG, "Writing state...");
|
||||||
|
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGW(TAG, "Light is in failed state, not writing state.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->buf_ == nullptr) {
|
||||||
|
ESP_LOGW(TAG, "Buffer is null, not writing state.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// assemble bits in buffer to 32 bit words with ex for GBR: 0bGGGGGGGGRRRRRRRRBBBBBBBB00000000
|
||||||
|
for (int i = 0; i < this->num_leds_; i++) {
|
||||||
|
uint8_t c1 = this->buf_[(i * 3) + 0];
|
||||||
|
uint8_t c2 = this->buf_[(i * 3) + 1];
|
||||||
|
uint8_t c3 = this->buf_[(i * 3) + 2];
|
||||||
|
uint8_t w = this->is_rgbw_ ? this->buf_[(i * 4) + 3] : 0;
|
||||||
|
uint32_t color = encode_uint32(c1, c2, c3, w);
|
||||||
|
pio_sm_put_blocking(this->pio_, this->sm_, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
light::ESPColorView RP2040PIOLEDStripLightOutput::get_view_internal(int32_t index) const {
|
||||||
|
int32_t r = 0, g = 0, b = 0, w = 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 RP2040PIOLEDStripLightOutput::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "RP2040 PIO LED Strip Light Output:");
|
||||||
|
ESP_LOGCONFIG(TAG, " Pin: GPIO%d", this->pin_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Number of LEDs: %d", this->num_leds_);
|
||||||
|
ESP_LOGCONFIG(TAG, " RGBW: %s", YESNO(this->is_rgbw_));
|
||||||
|
ESP_LOGCONFIG(TAG, " RGB Order: %s", rgb_order_to_string(this->rgb_order_));
|
||||||
|
ESP_LOGCONFIG(TAG, " Max Refresh Rate: %f Hz", this->max_refresh_rate_);
|
||||||
|
}
|
||||||
|
|
||||||
|
float RP2040PIOLEDStripLightOutput::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
|
} // namespace rp2040_pio_led_strip
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif
|
108
esphome/components/rp2040_pio_led_strip/led_strip.h
Normal file
108
esphome/components/rp2040_pio_led_strip/led_strip.h
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef USE_RP2040
|
||||||
|
|
||||||
|
#include "esphome/core/color.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
#include "esphome/components/light/addressable_light.h"
|
||||||
|
#include "esphome/components/light/light_output.h"
|
||||||
|
|
||||||
|
#include <hardware/pio.h>
|
||||||
|
#include <hardware/structs/pio.h>
|
||||||
|
#include <pico/stdio.h>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace rp2040_pio_led_strip {
|
||||||
|
|
||||||
|
enum RGBOrder : uint8_t {
|
||||||
|
ORDER_RGB,
|
||||||
|
ORDER_RBG,
|
||||||
|
ORDER_GRB,
|
||||||
|
ORDER_GBR,
|
||||||
|
ORDER_BGR,
|
||||||
|
ORDER_BRG,
|
||||||
|
};
|
||||||
|
|
||||||
|
inline const char *rgb_order_to_string(RGBOrder order) {
|
||||||
|
switch (order) {
|
||||||
|
case ORDER_RGB:
|
||||||
|
return "RGB";
|
||||||
|
case ORDER_RBG:
|
||||||
|
return "RBG";
|
||||||
|
case ORDER_GRB:
|
||||||
|
return "GRB";
|
||||||
|
case ORDER_GBR:
|
||||||
|
return "GBR";
|
||||||
|
case ORDER_BGR:
|
||||||
|
return "BGR";
|
||||||
|
case ORDER_BRG:
|
||||||
|
return "BRG";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using init_fn = void (*)(PIO pio, uint sm, uint offset, uint pin, float freq);
|
||||||
|
|
||||||
|
class RP2040PIOLEDStripLightOutput : 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();
|
||||||
|
this->is_rgbw_ ? traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::RGB_WHITE})
|
||||||
|
: traits.set_supported_color_modes({light::ColorMode::RGB});
|
||||||
|
return traits;
|
||||||
|
}
|
||||||
|
void set_pin(uint8_t pin) { this->pin_ = pin; }
|
||||||
|
void set_num_leds(uint32_t num_leds) { this->num_leds_ = num_leds; }
|
||||||
|
void set_is_rgbw(bool is_rgbw) { this->is_rgbw_ = is_rgbw; }
|
||||||
|
|
||||||
|
void set_max_refresh_rate(float interval_us) { this->max_refresh_rate_ = interval_us; }
|
||||||
|
|
||||||
|
void set_pio(int pio_num) { pio_num ? this->pio_ = pio1 : this->pio_ = pio0; }
|
||||||
|
void set_program(const pio_program_t *program) { this->program_ = program; }
|
||||||
|
void set_init_function(init_fn init) { this->init_ = init; }
|
||||||
|
|
||||||
|
void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; }
|
||||||
|
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};
|
||||||
|
|
||||||
|
uint8_t pin_;
|
||||||
|
uint32_t num_leds_;
|
||||||
|
bool is_rgbw_;
|
||||||
|
|
||||||
|
pio_hw_t *pio_;
|
||||||
|
uint sm_;
|
||||||
|
|
||||||
|
RGBOrder rgb_order_{ORDER_RGB};
|
||||||
|
|
||||||
|
uint32_t last_refresh_{0};
|
||||||
|
float max_refresh_rate_;
|
||||||
|
|
||||||
|
const pio_program_t *program_;
|
||||||
|
init_fn init_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rp2040_pio_led_strip
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_RP2040
|
267
esphome/components/rp2040_pio_led_strip/light.py
Normal file
267
esphome/components/rp2040_pio_led_strip/light.py
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from esphome import pins
|
||||||
|
from esphome.components import light, rp2040
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_CHIPSET,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_NUM_LEDS,
|
||||||
|
CONF_OUTPUT_ID,
|
||||||
|
CONF_PIN,
|
||||||
|
CONF_RGB_ORDER,
|
||||||
|
)
|
||||||
|
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
|
||||||
|
from esphome.util import _LOGGER
|
||||||
|
|
||||||
|
|
||||||
|
def get_nops(timing):
|
||||||
|
"""
|
||||||
|
Calculate the number of NOP instructions required to wait for a given amount of time.
|
||||||
|
"""
|
||||||
|
time_remaining = timing
|
||||||
|
nops = []
|
||||||
|
if time_remaining < 32:
|
||||||
|
nops.append(time_remaining - 1)
|
||||||
|
return nops
|
||||||
|
nops.append(31)
|
||||||
|
time_remaining -= 32
|
||||||
|
while time_remaining > 0:
|
||||||
|
if time_remaining >= 32:
|
||||||
|
nops.append("nop [31]")
|
||||||
|
time_remaining -= 32
|
||||||
|
else:
|
||||||
|
nops.append("nop [" + str(time_remaining) + " - 1 ]")
|
||||||
|
time_remaining = 0
|
||||||
|
return nops
|
||||||
|
|
||||||
|
|
||||||
|
def generate_assembly_code(id, rgbw, t0h, t0l, t1h, t1l):
|
||||||
|
"""
|
||||||
|
Generate assembly code with the given timing values.
|
||||||
|
"""
|
||||||
|
nops_t0h = get_nops(t0h)
|
||||||
|
nops_t0l = get_nops(t0l)
|
||||||
|
nops_t1h = get_nops(t1h)
|
||||||
|
nops_t1l = get_nops(t1l)
|
||||||
|
|
||||||
|
t0h = nops_t0h.pop(0)
|
||||||
|
t0l = nops_t0l.pop(0)
|
||||||
|
t1h = nops_t1h.pop(0)
|
||||||
|
t1l = nops_t1l.pop(0)
|
||||||
|
|
||||||
|
nops_t0h = "\n".join(" " * 4 + nop for nop in nops_t0h)
|
||||||
|
nops_t0l = "\n".join(" " * 4 + nop for nop in nops_t0l)
|
||||||
|
nops_t1h = "\n".join(" " * 4 + nop for nop in nops_t1h)
|
||||||
|
nops_t1l = "\n".join(" " * 4 + nop for nop in nops_t1l)
|
||||||
|
|
||||||
|
const_csdk_code = f"""
|
||||||
|
% c-sdk {{
|
||||||
|
#include "hardware/clocks.h"
|
||||||
|
|
||||||
|
static inline void rp2040_pio_led_strip_driver_{id}_init(PIO pio, uint sm, uint offset, uint pin, float freq) {{
|
||||||
|
pio_gpio_init(pio, pin);
|
||||||
|
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
|
||||||
|
|
||||||
|
pio_sm_config c = rp2040_pio_led_strip_{id}_program_get_default_config(offset);
|
||||||
|
sm_config_set_set_pins(&c, pin, 1);
|
||||||
|
sm_config_set_out_shift(&c, false, true, {32 if rgbw else 24});
|
||||||
|
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||||
|
|
||||||
|
int cycles_per_bit = 69;
|
||||||
|
float div = 2.409;
|
||||||
|
sm_config_set_clkdiv(&c, div);
|
||||||
|
|
||||||
|
|
||||||
|
pio_sm_init(pio, sm, offset, &c);
|
||||||
|
pio_sm_set_enabled(pio, sm, true);
|
||||||
|
}}
|
||||||
|
%}}"""
|
||||||
|
|
||||||
|
assembly_template = f""".program rp2040_pio_led_strip_{id}
|
||||||
|
|
||||||
|
.wrap_target
|
||||||
|
awaiting_data:
|
||||||
|
; Wait for data in FIFO queue
|
||||||
|
pull block ; this will block until there is data in the FIFO queue and then it will pull it into the shift register
|
||||||
|
set y, {31 if rgbw else 23} ; set y to the number of bits to write counting 0, (23 if RGB, 31 if RGBW)
|
||||||
|
|
||||||
|
mainloop:
|
||||||
|
; go through each bit in the shift register and jump to the appropriate label
|
||||||
|
; depending on the value of the bit
|
||||||
|
|
||||||
|
out x, 1
|
||||||
|
jmp !x, writezero
|
||||||
|
jmp writeone
|
||||||
|
|
||||||
|
writezero:
|
||||||
|
; Write T0H and T0L bits to the output pin
|
||||||
|
set pins, 1 [{t0h}]
|
||||||
|
{nops_t0h}
|
||||||
|
set pins, 0 [{t0l}]
|
||||||
|
{nops_t0l}
|
||||||
|
jmp y--, mainloop
|
||||||
|
jmp awaiting_data
|
||||||
|
|
||||||
|
writeone:
|
||||||
|
; Write T1H and T1L bits to the output pin
|
||||||
|
set pins, 1 [{t1h}]
|
||||||
|
{nops_t1h}
|
||||||
|
set pins, 0 [{t1l}]
|
||||||
|
{nops_t1l}
|
||||||
|
jmp y--, mainloop
|
||||||
|
jmp awaiting_data
|
||||||
|
|
||||||
|
.wrap"""
|
||||||
|
|
||||||
|
return assembly_template + const_csdk_code
|
||||||
|
|
||||||
|
|
||||||
|
def time_to_cycles(time_us):
|
||||||
|
cycles_per_us = 57.5
|
||||||
|
cycles = round(float(time_us) * cycles_per_us)
|
||||||
|
return cycles
|
||||||
|
|
||||||
|
|
||||||
|
CONF_PIO = "pio"
|
||||||
|
|
||||||
|
CODEOWNERS = ["@Papa-DMan"]
|
||||||
|
DEPENDENCIES = ["rp2040"]
|
||||||
|
|
||||||
|
rp2040_pio_led_strip_ns = cg.esphome_ns.namespace("rp2040_pio_led_strip")
|
||||||
|
RP2040PIOLEDStripLightOutput = rp2040_pio_led_strip_ns.class_(
|
||||||
|
"RP2040PIOLEDStripLightOutput", light.AddressableLight
|
||||||
|
)
|
||||||
|
|
||||||
|
RGBOrder = rp2040_pio_led_strip_ns.enum("RGBOrder")
|
||||||
|
|
||||||
|
Chipsets = rp2040_pio_led_strip_ns.enum("Chipset")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LEDStripTimings:
|
||||||
|
T0H: int
|
||||||
|
T0L: int
|
||||||
|
T1H: int
|
||||||
|
T1L: int
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
CHIPSETS = {
|
||||||
|
"WS2812": LEDStripTimings(20, 43, 41, 31),
|
||||||
|
"WS2812B": LEDStripTimings(23, 46, 46, 23),
|
||||||
|
"SK6812": LEDStripTimings(17, 52, 31, 31),
|
||||||
|
"SM16703": LEDStripTimings(17, 52, 52, 17),
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_timing(value):
|
||||||
|
# if doesn't end with us, raise error
|
||||||
|
if not value.endswith("us"):
|
||||||
|
raise cv.Invalid("Timing must be in microseconds (us)")
|
||||||
|
value = float(value[:-2])
|
||||||
|
nops = get_nops(value)
|
||||||
|
nops.pop(0)
|
||||||
|
if len(nops) > 3:
|
||||||
|
raise cv.Invalid("Timing is too long, please try again.")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
light.ADDRESSABLE_LIGHT_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(RP2040PIOLEDStripLightOutput),
|
||||||
|
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_PIO): cv.one_of(0, 1, int=True),
|
||||||
|
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",
|
||||||
|
): _validate_timing,
|
||||||
|
cv.Inclusive(
|
||||||
|
CONF_BIT0_LOW,
|
||||||
|
"custom",
|
||||||
|
): _validate_timing,
|
||||||
|
cv.Inclusive(
|
||||||
|
CONF_BIT1_HIGH,
|
||||||
|
"custom",
|
||||||
|
): _validate_timing,
|
||||||
|
cv.Inclusive(
|
||||||
|
CONF_BIT1_LOW,
|
||||||
|
"custom",
|
||||||
|
): _validate_timing,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
||||||
|
id = config[CONF_ID].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]))
|
||||||
|
|
||||||
|
cg.add(var.set_rgb_order(config[CONF_RGB_ORDER]))
|
||||||
|
cg.add(var.set_is_rgbw(config[CONF_IS_RGBW]))
|
||||||
|
|
||||||
|
cg.add(var.set_pio(config[CONF_PIO]))
|
||||||
|
cg.add(var.set_program(cg.RawExpression(f"&rp2040_pio_led_strip_{id}_program")))
|
||||||
|
cg.add(
|
||||||
|
var.set_init_function(
|
||||||
|
cg.RawExpression(f"rp2040_pio_led_strip_driver_{id}_init")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
key = f"led_strip_{id}"
|
||||||
|
|
||||||
|
if CONF_CHIPSET in config:
|
||||||
|
_LOGGER.info("Generating PIO assembly code")
|
||||||
|
rp2040.add_pio_file(
|
||||||
|
__name__,
|
||||||
|
key,
|
||||||
|
generate_assembly_code(
|
||||||
|
id,
|
||||||
|
config[CONF_IS_RGBW],
|
||||||
|
CHIPSETS[config[CONF_CHIPSET]].T0H,
|
||||||
|
CHIPSETS[config[CONF_CHIPSET]].T0L,
|
||||||
|
CHIPSETS[config[CONF_CHIPSET]].T1H,
|
||||||
|
CHIPSETS[config[CONF_CHIPSET]].T1L,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_LOGGER.info("Generating custom PIO assembly code")
|
||||||
|
rp2040.add_pio_file(
|
||||||
|
__name__,
|
||||||
|
key,
|
||||||
|
generate_assembly_code(
|
||||||
|
id,
|
||||||
|
config[CONF_IS_RGBW],
|
||||||
|
time_to_cycles(config[CONF_BIT0_HIGH]),
|
||||||
|
time_to_cycles(config[CONF_BIT0_LOW]),
|
||||||
|
time_to_cycles(config[CONF_BIT1_HIGH]),
|
||||||
|
time_to_cycles(config[CONF_BIT1_LOW]),
|
||||||
|
),
|
||||||
|
)
|
|
@ -299,6 +299,16 @@ def copy_src_tree():
|
||||||
|
|
||||||
copy_files()
|
copy_files()
|
||||||
|
|
||||||
|
elif CORE.is_rp2040:
|
||||||
|
from esphome.components.rp2040 import copy_files
|
||||||
|
|
||||||
|
(pio) = copy_files()
|
||||||
|
if pio:
|
||||||
|
write_file_if_changed(
|
||||||
|
CORE.relative_src_path("esphome.h"),
|
||||||
|
ESPHOME_H_FORMAT.format(include_s + '\n#include "pio_includes.h"'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def generate_defines_h():
|
def generate_defines_h():
|
||||||
define_content_l = [x.as_macro for x in CORE.defines]
|
define_content_l = [x.as_macro for x in CORE.defines]
|
||||||
|
|
|
@ -37,6 +37,26 @@ switch:
|
||||||
output: pin_4
|
output: pin_4
|
||||||
id: pin_4_switch
|
id: pin_4_switch
|
||||||
|
|
||||||
|
light:
|
||||||
|
- platform: rp2040_pio_led_strip
|
||||||
|
id: led_strip
|
||||||
|
pin: GPIO13
|
||||||
|
num_leds: 60
|
||||||
|
pio: 0
|
||||||
|
rgb_order: GRB
|
||||||
|
chipset: WS2812
|
||||||
|
- platform: rp2040_pio_led_strip
|
||||||
|
id: led_strip_custom_timings
|
||||||
|
pin: GPIO13
|
||||||
|
num_leds: 60
|
||||||
|
pio: 1
|
||||||
|
rgb_order: GRB
|
||||||
|
bit0_high: .1us
|
||||||
|
bit0_low: 1.2us
|
||||||
|
bit1_high: .69us
|
||||||
|
bit1_low: .4us
|
||||||
|
|
||||||
|
|
||||||
sensor:
|
sensor:
|
||||||
- platform: internal_temperature
|
- platform: internal_temperature
|
||||||
name: Internal Temperature
|
name: Internal Temperature
|
||||||
|
|
Loading…
Reference in a new issue