Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rp2040 pio ledstrip #4818

Merged
merged 44 commits into from May 21, 2023
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
3832f3c
LED Strip component using PIO
Papa-DMan May 11, 2023
34e64ad
Merge branch 'esphome:dev' into rp2040_ledstrip
Papa-DMan May 12, 2023
61cf7b7
update clock divider and more logging
Papa-DMan May 12, 2023
a410385
fixed write_state to maybe actually write the state
Papa-DMan May 12, 2023
063f4ca
Debugged the PIO Assembly. Now has correct Behavior
Papa-DMan May 12, 2023
a715216
forgor to update the init function
Papa-DMan May 12, 2023
478b512
don't reset the led strip before write | pioasm
Papa-DMan May 12, 2023
669b7ca
forcing sm clock to mcu speed
Papa-DMan May 12, 2023
69766cd
Re-enabled Clock divider
Papa-DMan May 12, 2023
2c4e7ce
did the math to figure out the clock divider
Papa-DMan May 13, 2023
4d1c490
Classic missing ";"
Papa-DMan May 13, 2023
cd6f16f
Merge branch 'esphome:dev' into rp2040_ledstrip
Papa-DMan May 13, 2023
648219d
Adjust Clock division - protocol a little too fast
Papa-DMan May 13, 2023
c252f4e
wrong direction :)
Papa-DMan May 13, 2023
4a7c234
Units are also important when doing math :)
Papa-DMan May 13, 2023
c8fdde4
Added Assembly for common chipsets + updated cg
Papa-DMan May 14, 2023
2ef65a5
can have default required.
Papa-DMan May 14, 2023
17d1f63
spelling is hard
Papa-DMan May 14, 2023
75dd12c
build_codeowners.py
Papa-DMan May 14, 2023
6768908
linting + missing include
Papa-DMan May 14, 2023
5036366
missing ; moment and const being cringe :)
Papa-DMan May 14, 2023
5979db5
fix includes and missing '='
Papa-DMan May 14, 2023
df08e52
added #ifdef platform guards and formatting
Papa-DMan May 15, 2023
7d42ab1
Merge branch 'dev' into rp2040_ledstrip
Papa-DMan May 15, 2023
e835f21
clang-format
Papa-DMan May 15, 2023
3e386ee
Merge branch 'dev' into rp2040_ledstrip
Papa-DMan May 15, 2023
f614c91
Merge branch 'dev' into rp2040_ledstrip
Papa-DMan May 15, 2023
68e7709
auto-generation and custom timing support
Papa-DMan May 15, 2023
ebd9ccd
Write pio files to CORE data to be generated by the cpp writer.
jesserockz May 16, 2023
ffcad01
changed api call for pioasm to use new package
Papa-DMan May 16, 2023
5655578
Generate pio files into their own directory using the component id
jesserockz May 16, 2023
60461fd
Fix CONF variables
jesserockz May 16, 2023
6faf47d
f-string
jesserockz May 16, 2023
2fbda4e
Use write_file helper
jesserockz May 16, 2023
99381b9
format
jesserockz May 16, 2023
56f3576
Actually send pixel data
jesserockz May 16, 2023
05b2516
Updates
jesserockz May 16, 2023
198ad7d
Logging takes too much time causing a reset timing
Papa-DMan May 17, 2023
707ee5d
Merge branch 'dev' into rp2040_ledstrip
Papa-DMan May 17, 2023
d66043c
the bit counter in the asm should be (total - 1)
Papa-DMan May 17, 2023
35dce19
Make pio function more robust
jesserockz May 17, 2023
17d5e7d
Remove unnecessary check
jesserockz May 17, 2023
b6d40de
Fix lint issue
jesserockz May 18, 2023
875ba33
Merge branch 'dev' into rp2040_ledstrip
Papa-DMan May 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Expand Up @@ -220,6 +220,7 @@ esphome/components/restart/* @esphome/core
esphome/components/rf_bridge/* @jesserockz
esphome/components/rgbct/* @jesserockz
esphome/components/rp2040/* @jesserockz
esphome/components/rp2040_pio_led_strip/* @Papa-DMan
esphome/components/rp2040_pwm/* @jesserockz
esphome/components/rtttl/* @glmnet
esphome/components/safe_mode/* @jsuanet @paulmonigatti
Expand Down
66 changes: 63 additions & 3 deletions esphome/components/rp2040/__init__.py
@@ -1,4 +1,7 @@
import logging
import os

from string import ascii_letters, digits

import esphome.codegen as cg
import esphome.config_validation as cv
Expand All @@ -12,9 +15,11 @@
KEY_TARGET_FRAMEWORK,
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
from .gpio import rp2040_pin_to_code # noqa
Expand All @@ -33,6 +38,8 @@ def set_core_data(config):
)
CORE.data[KEY_RP2040][KEY_BOARD] = config[CONF_BOARD]

CORE.data[KEY_RP2040][KEY_PIO_FILES] = {}

return config


Expand Down Expand Up @@ -148,7 +155,10 @@ async def to_code(config):
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
cg.add_platformio_option(
"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")
Expand All @@ -159,3 +169,53 @@ async def to_code(config):
"USE_ARDUINO_VERSION_CODE",
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:
raise EsphomeError(
f"[{component}] Invalid PIO key: {key}. Allowed characters: [{ascii_letters}{digits}_]\nPlease report an issue https://github.com/esphome/issues"
)
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()
1 change: 1 addition & 0 deletions esphome/components/rp2040/const.py
Expand Up @@ -2,5 +2,6 @@

KEY_BOARD = "board"
KEY_RP2040 = "rp2040"
KEY_PIO_FILES = "pio_files"

rp2040_ns = cg.esphome_ns.namespace("rp2040")
1 change: 1 addition & 0 deletions esphome/components/rp2040_pio_led_strip/__init__.py
@@ -0,0 +1 @@
CODEOWNERS = ["@Papa-DMan"]
139 changes: 139 additions & 0 deletions esphome/components/rp2040_pio_led_strip/led_strip.cpp
@@ -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 changes: 108 additions & 0 deletions esphome/components/rp2040_pio_led_strip/led_strip.h
@@ -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