mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 16:38:16 +01:00
Fix RP2040 Neopixel flickering issue (#7307)
This commit is contained in:
parent
b425912a80
commit
1ffee9c4d2
2 changed files with 36 additions and 5 deletions
|
@ -7,8 +7,10 @@
|
||||||
|
|
||||||
#include <hardware/clocks.h>
|
#include <hardware/clocks.h>
|
||||||
#include <hardware/dma.h>
|
#include <hardware/dma.h>
|
||||||
|
#include <hardware/irq.h>
|
||||||
#include <hardware/pio.h>
|
#include <hardware/pio.h>
|
||||||
#include <pico/stdlib.h>
|
#include <pico/stdlib.h>
|
||||||
|
#include <pico/sem.h>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace rp2040_pio_led_strip {
|
namespace rp2040_pio_led_strip {
|
||||||
|
@ -23,6 +25,19 @@ static std::map<Chipset, bool> conf_count_ = {
|
||||||
{CHIPSET_WS2812, false}, {CHIPSET_WS2812B, false}, {CHIPSET_SK6812, false},
|
{CHIPSET_WS2812, false}, {CHIPSET_WS2812B, false}, {CHIPSET_SK6812, false},
|
||||||
{CHIPSET_SM16703, false}, {CHIPSET_CUSTOM, false},
|
{CHIPSET_SM16703, false}, {CHIPSET_CUSTOM, false},
|
||||||
};
|
};
|
||||||
|
static bool dma_chan_active_[12];
|
||||||
|
static struct semaphore dma_write_complete_sem_[12];
|
||||||
|
|
||||||
|
// DMA interrupt service routine
|
||||||
|
void RP2040PIOLEDStripLightOutput::dma_write_complete_handler_() {
|
||||||
|
uint32_t channel = dma_hw->ints0;
|
||||||
|
for (uint dma_chan = 0; dma_chan < 12; ++dma_chan) {
|
||||||
|
if (RP2040PIOLEDStripLightOutput::dma_chan_active_[dma_chan] && (channel & (1u << dma_chan))) {
|
||||||
|
dma_hw->ints0 = (1u << dma_chan); // Clear the interrupt
|
||||||
|
sem_release(&RP2040PIOLEDStripLightOutput::dma_write_complete_sem_[dma_chan]); // Handle the interrupt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void RP2040PIOLEDStripLightOutput::setup() {
|
void RP2040PIOLEDStripLightOutput::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up RP2040 LED Strip...");
|
ESP_LOGCONFIG(TAG, "Setting up RP2040 LED Strip...");
|
||||||
|
@ -57,22 +72,22 @@ void RP2040PIOLEDStripLightOutput::setup() {
|
||||||
// but there are only 4 state machines on each PIO so we can only have 4 strips per PIO
|
// but there are only 4 state machines on each PIO so we can only have 4 strips per PIO
|
||||||
uint offset = 0;
|
uint offset = 0;
|
||||||
|
|
||||||
if (num_instance_[this->pio_ == pio0 ? 0 : 1] > 4) {
|
if (RP2040PIOLEDStripLightOutput::num_instance_[this->pio_ == pio0 ? 0 : 1] > 4) {
|
||||||
ESP_LOGE(TAG, "Too many instances of PIO program");
|
ESP_LOGE(TAG, "Too many instances of PIO program");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// keep track of how many instances of the PIO program are running on each PIO
|
// keep track of how many instances of the PIO program are running on each PIO
|
||||||
num_instance_[this->pio_ == pio0 ? 0 : 1]++;
|
RP2040PIOLEDStripLightOutput::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 there are multiple strips of the same chipset, we can reuse the same PIO program and save space
|
||||||
if (this->conf_count_[this->chipset_]) {
|
if (this->conf_count_[this->chipset_]) {
|
||||||
offset = chipset_offsets_[this->chipset_];
|
offset = RP2040PIOLEDStripLightOutput::chipset_offsets_[this->chipset_];
|
||||||
} else {
|
} else {
|
||||||
// Load the assembled program into the PIO and get its location in the PIO's instruction memory and save it
|
// 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_);
|
offset = pio_add_program(this->pio_, this->program_);
|
||||||
chipset_offsets_[this->chipset_] = offset;
|
RP2040PIOLEDStripLightOutput::chipset_offsets_[this->chipset_] = offset;
|
||||||
conf_count_[this->chipset_] = true;
|
RP2040PIOLEDStripLightOutput::conf_count_[this->chipset_] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure the state machine's PIO, and start it
|
// Configure the state machine's PIO, and start it
|
||||||
|
@ -93,6 +108,9 @@ void RP2040PIOLEDStripLightOutput::setup() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark the DMA channel as active
|
||||||
|
RP2040PIOLEDStripLightOutput::dma_chan_active_[this->dma_chan_] = true;
|
||||||
|
|
||||||
this->dma_config_ = dma_channel_get_default_config(this->dma_chan_);
|
this->dma_config_ = dma_channel_get_default_config(this->dma_chan_);
|
||||||
channel_config_set_transfer_data_size(
|
channel_config_set_transfer_data_size(
|
||||||
&this->dma_config_,
|
&this->dma_config_,
|
||||||
|
@ -109,6 +127,13 @@ void RP2040PIOLEDStripLightOutput::setup() {
|
||||||
false // don't start yet
|
false // don't start yet
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Initialize the semaphore for this DMA channel
|
||||||
|
sem_init(&RP2040PIOLEDStripLightOutput::dma_write_complete_sem_[this->dma_chan_], 1, 1);
|
||||||
|
|
||||||
|
irq_set_exclusive_handler(DMA_IRQ_0, dma_write_complete_handler_); // after DMA all data, raise an interrupt
|
||||||
|
dma_channel_set_irq0_enabled(this->dma_chan_, true); // map DMA channel to interrupt
|
||||||
|
irq_set_enabled(DMA_IRQ_0, true); // enable interrupt
|
||||||
|
|
||||||
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_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,6 +151,7 @@ void RP2040PIOLEDStripLightOutput::write_state(light::LightState *state) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// the bits are already in the correct order for the pio program so we can just copy the buffer using DMA
|
// the bits are already in the correct order for the pio program so we can just copy the buffer using DMA
|
||||||
|
sem_acquire_blocking(&RP2040PIOLEDStripLightOutput::dma_write_complete_sem_[this->dma_chan_]);
|
||||||
dma_channel_transfer_from_buffer_now(this->dma_chan_, this->buf_, this->get_buffer_size_());
|
dma_channel_transfer_from_buffer_now(this->dma_chan_, this->buf_, this->get_buffer_size_());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#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 <pico/sem.h>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -95,6 +96,8 @@ class RP2040PIOLEDStripLightOutput : public light::AddressableLight {
|
||||||
|
|
||||||
size_t get_buffer_size_() const { return this->num_leds_ * (3 + this->is_rgbw_); }
|
size_t get_buffer_size_() const { return this->num_leds_ * (3 + this->is_rgbw_); }
|
||||||
|
|
||||||
|
static void dma_write_complete_handler_();
|
||||||
|
|
||||||
uint8_t *buf_{nullptr};
|
uint8_t *buf_{nullptr};
|
||||||
uint8_t *effect_data_{nullptr};
|
uint8_t *effect_data_{nullptr};
|
||||||
|
|
||||||
|
@ -120,6 +123,8 @@ class RP2040PIOLEDStripLightOutput : public light::AddressableLight {
|
||||||
inline static int num_instance_[2];
|
inline static int num_instance_[2];
|
||||||
inline static std::map<Chipset, bool> conf_count_;
|
inline static std::map<Chipset, bool> conf_count_;
|
||||||
inline static std::map<Chipset, int> chipset_offsets_;
|
inline static std::map<Chipset, int> chipset_offsets_;
|
||||||
|
inline static bool dma_chan_active_[12];
|
||||||
|
inline static struct semaphore dma_write_complete_sem_[12];
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rp2040_pio_led_strip
|
} // namespace rp2040_pio_led_strip
|
||||||
|
|
Loading…
Reference in a new issue