mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 08:28:12 +01:00
fix rp2040_pio_led flicker and proper multi-strip support (#6194)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
8952719045
commit
ebfccc64c7
3 changed files with 122 additions and 38 deletions
|
@ -6,6 +6,7 @@
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
#include <hardware/clocks.h>
|
#include <hardware/clocks.h>
|
||||||
|
#include <hardware/dma.h>
|
||||||
#include <hardware/pio.h>
|
#include <hardware/pio.h>
|
||||||
#include <pico/stdlib.h>
|
#include <pico/stdlib.h>
|
||||||
|
|
||||||
|
@ -14,6 +15,15 @@ namespace rp2040_pio_led_strip {
|
||||||
|
|
||||||
static const char *TAG = "rp2040_pio_led_strip";
|
static const char *TAG = "rp2040_pio_led_strip";
|
||||||
|
|
||||||
|
static uint8_t num_instance_[2] = {0, 0};
|
||||||
|
static std::map<Chipset, uint> chipset_offsets_ = {
|
||||||
|
{CHIPSET_WS2812, 0}, {CHIPSET_WS2812B, 0}, {CHIPSET_SK6812, 0}, {CHIPSET_SM16703, 0}, {CHIPSET_CUSTOM, 0},
|
||||||
|
};
|
||||||
|
static std::map<Chipset, bool> conf_count_ = {
|
||||||
|
{CHIPSET_WS2812, false}, {CHIPSET_WS2812B, false}, {CHIPSET_SK6812, false},
|
||||||
|
{CHIPSET_SM16703, false}, {CHIPSET_CUSTOM, false},
|
||||||
|
};
|
||||||
|
|
||||||
void RP2040PIOLEDStripLightOutput::setup() {
|
void RP2040PIOLEDStripLightOutput::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up RP2040 LED Strip...");
|
ESP_LOGCONFIG(TAG, "Setting up RP2040 LED Strip...");
|
||||||
|
|
||||||
|
@ -34,24 +44,71 @@ void RP2040PIOLEDStripLightOutput::setup() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize the PIO program
|
||||||
|
|
||||||
// Select PIO instance to use (0 or 1)
|
// Select PIO instance to use (0 or 1)
|
||||||
this->pio_ = pio0;
|
|
||||||
if (this->pio_ == nullptr) {
|
if (this->pio_ == nullptr) {
|
||||||
ESP_LOGE(TAG, "Failed to claim PIO instance");
|
ESP_LOGE(TAG, "Failed to claim PIO instance");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the assembled program into the PIO and get its location in the PIO's instruction memory
|
// if there are multiple strips, we can reuse the same PIO program and save space
|
||||||
uint offset = pio_add_program(this->pio_, this->program_);
|
// but there are only 4 state machines on each PIO so we can only have 4 strips per PIO
|
||||||
|
uint offset = 0;
|
||||||
|
|
||||||
|
if (num_instance_[this->pio_ == pio0 ? 0 : 1] > 4) {
|
||||||
|
ESP_LOGE(TAG, "Too many instances of PIO program");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// keep track of how many instances of the PIO program are running on each PIO
|
||||||
|
num_instance_[this->pio_ == pio0 ? 0 : 1]++;
|
||||||
|
|
||||||
|
// if there are multiple strips of the same chipset, we can reuse the same PIO program and save space
|
||||||
|
if (this->conf_count_[this->chipset_]) {
|
||||||
|
offset = chipset_offsets_[this->chipset_];
|
||||||
|
} else {
|
||||||
|
// Load the assembled program into the PIO and get its location in the PIO's instruction memory and save it
|
||||||
|
offset = pio_add_program(this->pio_, this->program_);
|
||||||
|
chipset_offsets_[this->chipset_] = offset;
|
||||||
|
conf_count_[this->chipset_] = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Configure the state machine's PIO, and start it
|
// Configure the state machine's PIO, and start it
|
||||||
this->sm_ = pio_claim_unused_sm(this->pio_, true);
|
this->sm_ = pio_claim_unused_sm(this->pio_, true);
|
||||||
if (this->sm_ < 0) {
|
if (this->sm_ < 0) {
|
||||||
|
// in theory this code should never be reached
|
||||||
ESP_LOGE(TAG, "Failed to claim PIO state machine");
|
ESP_LOGE(TAG, "Failed to claim PIO state machine");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initalize the DMA channel (Note: There are 12 DMA channels and 8 state machines so we won't run out)
|
||||||
|
|
||||||
|
this->dma_chan_ = dma_claim_unused_channel(true);
|
||||||
|
if (this->dma_chan_ < 0) {
|
||||||
|
ESP_LOGE(TAG, "Failed to claim DMA channel");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->dma_config_ = dma_channel_get_default_config(this->dma_chan_);
|
||||||
|
channel_config_set_transfer_data_size(
|
||||||
|
&this->dma_config_,
|
||||||
|
DMA_SIZE_8); // 8 bit transfers (could be 32 but the pio program would need to be changed to handle junk data)
|
||||||
|
channel_config_set_read_increment(&this->dma_config_, true); // increment the read address
|
||||||
|
channel_config_set_write_increment(&this->dma_config_, false); // don't increment the write address
|
||||||
|
channel_config_set_dreq(&this->dma_config_,
|
||||||
|
pio_get_dreq(this->pio_, this->sm_, true)); // set the DREQ to the state machine's TX FIFO
|
||||||
|
|
||||||
|
dma_channel_configure(this->dma_chan_, &this->dma_config_,
|
||||||
|
&this->pio_->txf[this->sm_], // write to the state machine's TX FIFO
|
||||||
|
this->buf_, // read from memory
|
||||||
|
this->is_rgbw_ ? num_leds_ * 4 : num_leds_ * 3, // number of bytes to transfer
|
||||||
|
false // don't start yet
|
||||||
|
);
|
||||||
|
|
||||||
this->init_(this->pio_, this->sm_, offset, this->pin_, this->max_refresh_rate_);
|
this->init_(this->pio_, this->sm_, offset, this->pin_, this->max_refresh_rate_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,16 +125,8 @@ void RP2040PIOLEDStripLightOutput::write_state(light::LightState *state) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// assemble bits in buffer to 32 bit words with ex for GBR: 0bGGGGGGGGRRRRRRRRBBBBBBBB00000000
|
// the bits are already in the correct order for the pio program so we can just copy the buffer using DMA
|
||||||
for (int i = 0; i < this->num_leds_; i++) {
|
dma_channel_transfer_from_buffer_now(this->dma_chan_, this->buf_, this->get_buffer_size_());
|
||||||
uint8_t multiplier = this->is_rgbw_ ? 4 : 3;
|
|
||||||
uint8_t c1 = this->buf_[(i * multiplier) + 0];
|
|
||||||
uint8_t c2 = this->buf_[(i * multiplier) + 1];
|
|
||||||
uint8_t c3 = this->buf_[(i * multiplier) + 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 {
|
light::ESPColorView RP2040PIOLEDStripLightOutput::get_view_internal(int32_t index) const {
|
||||||
|
|
|
@ -9,9 +9,11 @@
|
||||||
#include "esphome/components/light/addressable_light.h"
|
#include "esphome/components/light/addressable_light.h"
|
||||||
#include "esphome/components/light/light_output.h"
|
#include "esphome/components/light/light_output.h"
|
||||||
|
|
||||||
|
#include <hardware/dma.h>
|
||||||
#include <hardware/pio.h>
|
#include <hardware/pio.h>
|
||||||
#include <hardware/structs/pio.h>
|
#include <hardware/structs/pio.h>
|
||||||
#include <pico/stdio.h>
|
#include <pico/stdio.h>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace rp2040_pio_led_strip {
|
namespace rp2040_pio_led_strip {
|
||||||
|
@ -25,6 +27,15 @@ enum RGBOrder : uint8_t {
|
||||||
ORDER_BRG,
|
ORDER_BRG,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum Chipset : uint8_t {
|
||||||
|
CHIPSET_WS2812,
|
||||||
|
CHIPSET_WS2812B,
|
||||||
|
CHIPSET_SK6812,
|
||||||
|
CHIPSET_SM16703,
|
||||||
|
CHIPSET_APA102,
|
||||||
|
CHIPSET_CUSTOM = 0xFF,
|
||||||
|
};
|
||||||
|
|
||||||
inline const char *rgb_order_to_string(RGBOrder order) {
|
inline const char *rgb_order_to_string(RGBOrder order) {
|
||||||
switch (order) {
|
switch (order) {
|
||||||
case ORDER_RGB:
|
case ORDER_RGB:
|
||||||
|
@ -69,6 +80,7 @@ class RP2040PIOLEDStripLightOutput : public light::AddressableLight {
|
||||||
void set_program(const pio_program_t *program) { this->program_ = program; }
|
void set_program(const pio_program_t *program) { this->program_ = program; }
|
||||||
void set_init_function(init_fn init) { this->init_ = init; }
|
void set_init_function(init_fn init) { this->init_ = init; }
|
||||||
|
|
||||||
|
void set_chipset(Chipset chipset) { this->chipset_ = chipset; };
|
||||||
void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; }
|
void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; }
|
||||||
void clear_effect_data() override {
|
void clear_effect_data() override {
|
||||||
for (int i = 0; i < this->size(); i++) {
|
for (int i = 0; i < this->size(); i++) {
|
||||||
|
@ -92,14 +104,22 @@ class RP2040PIOLEDStripLightOutput : public light::AddressableLight {
|
||||||
|
|
||||||
pio_hw_t *pio_;
|
pio_hw_t *pio_;
|
||||||
uint sm_;
|
uint sm_;
|
||||||
|
uint dma_chan_;
|
||||||
|
dma_channel_config dma_config_;
|
||||||
|
|
||||||
RGBOrder rgb_order_{ORDER_RGB};
|
RGBOrder rgb_order_{ORDER_RGB};
|
||||||
|
Chipset chipset_{CHIPSET_CUSTOM};
|
||||||
|
|
||||||
uint32_t last_refresh_{0};
|
uint32_t last_refresh_{0};
|
||||||
float max_refresh_rate_;
|
float max_refresh_rate_;
|
||||||
|
|
||||||
const pio_program_t *program_;
|
const pio_program_t *program_;
|
||||||
init_fn init_;
|
init_fn init_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
inline static int num_instance_[2];
|
||||||
|
inline static std::map<Chipset, bool> conf_count_;
|
||||||
|
inline static std::map<Chipset, int> chipset_offsets_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rp2040_pio_led_strip
|
} // namespace rp2040_pio_led_strip
|
||||||
|
|
|
@ -68,12 +68,15 @@ static inline void rp2040_pio_led_strip_driver_{id}_init(PIO pio, uint sm, uint
|
||||||
|
|
||||||
pio_sm_config c = rp2040_pio_led_strip_{id}_program_get_default_config(offset);
|
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_set_pins(&c, pin, 1);
|
||||||
sm_config_set_out_shift(&c, false, true, {32 if rgbw else 24});
|
sm_config_set_out_shift(&c, false, true, 8);
|
||||||
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||||
|
|
||||||
int cycles_per_bit = 69;
|
// target frequency is 57.5MHz
|
||||||
float div = 2.409;
|
long clk = clock_get_hz(clk_sys);
|
||||||
sm_config_set_clkdiv(&c, div);
|
long target_freq = 57500000;
|
||||||
|
int n = 2;
|
||||||
|
int f = round(((clk / target_freq) - n ) * 256);
|
||||||
|
sm_config_set_clkdiv_int_frac(&c, n, f);
|
||||||
|
|
||||||
|
|
||||||
pio_sm_init(pio, sm, offset, &c);
|
pio_sm_init(pio, sm, offset, &c);
|
||||||
|
@ -86,8 +89,9 @@ static inline void rp2040_pio_led_strip_driver_{id}_init(PIO pio, uint sm, uint
|
||||||
.wrap_target
|
.wrap_target
|
||||||
awaiting_data:
|
awaiting_data:
|
||||||
; Wait for data in FIFO queue
|
; Wait for data in FIFO queue
|
||||||
|
; out null, 24 ; discard the byte lane replication of the FIFO since we only need 8 bits (not needed????)
|
||||||
pull block ; this will block until there is data in the FIFO queue and then it will pull it into the shift register
|
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)
|
set y, 7 ; set y to the number of bits to write counting 0, (always 7 because we are doing one word at a time)
|
||||||
|
|
||||||
mainloop:
|
mainloop:
|
||||||
; go through each bit in the shift register and jump to the appropriate label
|
; go through each bit in the shift register and jump to the appropriate label
|
||||||
|
@ -95,7 +99,15 @@ mainloop:
|
||||||
|
|
||||||
out x, 1
|
out x, 1
|
||||||
jmp !x, writezero
|
jmp !x, writezero
|
||||||
jmp writeone
|
|
||||||
|
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
|
||||||
|
|
||||||
writezero:
|
writezero:
|
||||||
; Write T0H and T0L bits to the output pin
|
; Write T0H and T0L bits to the output pin
|
||||||
|
@ -106,14 +118,7 @@ writezero:
|
||||||
jmp y--, mainloop
|
jmp y--, mainloop
|
||||||
jmp awaiting_data
|
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"""
|
.wrap"""
|
||||||
|
|
||||||
|
@ -139,7 +144,15 @@ RP2040PIOLEDStripLightOutput = rp2040_pio_led_strip_ns.class_(
|
||||||
|
|
||||||
RGBOrder = rp2040_pio_led_strip_ns.enum("RGBOrder")
|
RGBOrder = rp2040_pio_led_strip_ns.enum("RGBOrder")
|
||||||
|
|
||||||
Chipsets = rp2040_pio_led_strip_ns.enum("Chipset")
|
Chipset = rp2040_pio_led_strip_ns.enum("Chipset")
|
||||||
|
|
||||||
|
CHIPSETS = {
|
||||||
|
"WS2812": Chipset.CHIPSET_WS2812,
|
||||||
|
"WS2812B": Chipset.CHIPSET_WS2812B,
|
||||||
|
"SK6812": Chipset.CHIPSET_SK6812,
|
||||||
|
"SM16703": Chipset.CHIPSET_SM16703,
|
||||||
|
"CUSTOM": Chipset.CHIPSET_CUSTOM,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -159,10 +172,10 @@ RGB_ORDERS = {
|
||||||
"BRG": RGBOrder.ORDER_BRG,
|
"BRG": RGBOrder.ORDER_BRG,
|
||||||
}
|
}
|
||||||
|
|
||||||
CHIPSETS = {
|
CHIPSET_TIMINGS = {
|
||||||
"WS2812": LEDStripTimings(20, 43, 41, 31),
|
"WS2812": LEDStripTimings(20, 40, 46, 34),
|
||||||
"WS2812B": LEDStripTimings(23, 46, 46, 23),
|
"WS2812B": LEDStripTimings(23, 49, 46, 26),
|
||||||
"SK6812": LEDStripTimings(17, 52, 31, 31),
|
"SK6812": LEDStripTimings(17, 52, 34, 34),
|
||||||
"SM16703": LEDStripTimings(17, 52, 52, 17),
|
"SM16703": LEDStripTimings(17, 52, 52, 17),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +205,7 @@ CONFIG_SCHEMA = cv.All(
|
||||||
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
|
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
|
||||||
cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True),
|
cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True),
|
||||||
cv.Required(CONF_PIO): cv.one_of(0, 1, int=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_CHIPSET): cv.enum(CHIPSETS, upper=True),
|
||||||
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
|
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
|
||||||
cv.Inclusive(
|
cv.Inclusive(
|
||||||
CONF_BIT0_HIGH,
|
CONF_BIT0_HIGH,
|
||||||
|
@ -238,7 +251,8 @@ async def to_code(config):
|
||||||
|
|
||||||
key = f"led_strip_{id}"
|
key = f"led_strip_{id}"
|
||||||
|
|
||||||
if CONF_CHIPSET in config:
|
if chipset := config.get(CONF_CHIPSET):
|
||||||
|
cg.add(var.set_chipset(chipset))
|
||||||
_LOGGER.info("Generating PIO assembly code")
|
_LOGGER.info("Generating PIO assembly code")
|
||||||
rp2040.add_pio_file(
|
rp2040.add_pio_file(
|
||||||
__name__,
|
__name__,
|
||||||
|
@ -246,13 +260,14 @@ async def to_code(config):
|
||||||
generate_assembly_code(
|
generate_assembly_code(
|
||||||
id,
|
id,
|
||||||
config[CONF_IS_RGBW],
|
config[CONF_IS_RGBW],
|
||||||
CHIPSETS[config[CONF_CHIPSET]].T0H,
|
CHIPSET_TIMINGS[chipset].T0H,
|
||||||
CHIPSETS[config[CONF_CHIPSET]].T0L,
|
CHIPSET_TIMINGS[chipset].T0L,
|
||||||
CHIPSETS[config[CONF_CHIPSET]].T1H,
|
CHIPSET_TIMINGS[chipset].T1H,
|
||||||
CHIPSETS[config[CONF_CHIPSET]].T1L,
|
CHIPSET_TIMINGS[chipset].T1L,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
cg.add(var.set_chipset(Chipset.CHIPSET_CUSTOM))
|
||||||
_LOGGER.info("Generating custom PIO assembly code")
|
_LOGGER.info("Generating custom PIO assembly code")
|
||||||
rp2040.add_pio_file(
|
rp2040.add_pio_file(
|
||||||
__name__,
|
__name__,
|
||||||
|
|
Loading…
Reference in a new issue