ESPHome 2026.10.0-dev
Loading...
Searching...
No Matches
led_strip.cpp
Go to the documentation of this file.
1#include "led_strip.h"
2
3#ifdef USE_RP2
4
6#include "esphome/core/log.h"
7
8#include <hardware/clocks.h>
9#include <hardware/dma.h>
10#include <hardware/irq.h>
11#include <hardware/pio.h>
12#include <pico/sem.h>
13#include <pico/stdlib.h>
14
16
17static const char *const TAG = "rp2040_pio_led_strip";
18
19// DMA interrupt service routine
21 uint32_t channel = dma_hw->ints0;
22 for (uint dma_chan = 0; dma_chan < 12; ++dma_chan) {
23 if (RP2040PIOLEDStripLightOutput::dma_chan_active[dma_chan] && (channel & (1u << dma_chan))) {
24 dma_hw->ints0 = (1u << dma_chan); // Clear the interrupt
25 sem_release(&RP2040PIOLEDStripLightOutput::dma_write_complete_sem[dma_chan]); // Handle the interrupt
26 }
27 }
28}
29
31 size_t buffer_size = this->get_buffer_size_();
32
33 RAMAllocator<uint8_t> allocator;
34 this->buf_ = allocator.allocate(buffer_size);
35 if (this->buf_ == nullptr) {
36 ESP_LOGE(TAG, "Failed to allocate buffer of size %u", buffer_size);
37 this->mark_failed();
38 return;
39 }
40
41 this->effect_data_ = allocator.allocate(this->num_leds_);
42 if (this->effect_data_ == nullptr) {
43 ESP_LOGE(TAG, "Failed to allocate effect data of size %u", this->num_leds_);
44 this->mark_failed();
45 return;
46 }
47
48 // Initialize the PIO program
49
50 // Select PIO instance to use (0 or 1)
51 if (this->pio_ == nullptr) {
52 ESP_LOGE(TAG, "Failed to claim PIO instance");
53 this->mark_failed();
54 return;
55 }
56
57 // if there are multiple strips, we can reuse the same PIO program and save space
58 // but there are only 4 state machines on each PIO so we can only have 4 strips per PIO
59 uint offset = 0;
60
61 if (RP2040PIOLEDStripLightOutput::num_instance[this->pio_ == pio0 ? 0 : 1] >= 4) {
62 ESP_LOGE(TAG, "Too many instances of PIO program");
63 this->mark_failed();
64 return;
65 }
66 // keep track of how many instances of the PIO program are running on each PIO
67 RP2040PIOLEDStripLightOutput::num_instance[this->pio_ == pio0 ? 0 : 1]++;
68
69 // if there are multiple strips of the same chipset, we can reuse the same PIO program and save space
70 if (RP2040PIOLEDStripLightOutput::conf_count[this->chipset_]) {
71 offset = RP2040PIOLEDStripLightOutput::chipset_offsets[this->chipset_];
72 } else {
73 // Load the assembled program into the PIO and get its location in the PIO's instruction memory and save it
74 offset = pio_add_program(this->pio_, this->program_);
75 RP2040PIOLEDStripLightOutput::chipset_offsets[this->chipset_] = offset;
76 RP2040PIOLEDStripLightOutput::conf_count[this->chipset_] = true;
77 }
78
79 // Configure the state machine's PIO, and start it
80 this->sm_ = pio_claim_unused_sm(this->pio_, true);
81 if (this->sm_ < 0) {
82 // in theory this code should never be reached
83 ESP_LOGE(TAG, "Failed to claim PIO state machine");
84 this->mark_failed();
85 return;
86 }
87
88 // Initalize the DMA channel (Note: There are 12 DMA channels and 8 state machines so we won't run out)
89
90 this->dma_chan_ = dma_claim_unused_channel(true);
91 if (this->dma_chan_ < 0) {
92 ESP_LOGE(TAG, "Failed to claim DMA channel");
93 this->mark_failed();
94 return;
95 }
96
97 // Mark the DMA channel as active
98 RP2040PIOLEDStripLightOutput::dma_chan_active[this->dma_chan_] = true;
99
100 this->dma_config_ = dma_channel_get_default_config(this->dma_chan_);
101 channel_config_set_transfer_data_size(
102 &this->dma_config_,
103 DMA_SIZE_8); // 8 bit transfers (could be 32 but the pio program would need to be changed to handle junk data)
104 channel_config_set_read_increment(&this->dma_config_, true); // increment the read address
105 channel_config_set_write_increment(&this->dma_config_, false); // don't increment the write address
106 channel_config_set_dreq(&this->dma_config_,
107 pio_get_dreq(this->pio_, this->sm_, true)); // set the DREQ to the state machine's TX FIFO
108
109 dma_channel_configure(this->dma_chan_, &this->dma_config_,
110 &this->pio_->txf[this->sm_], // write to the state machine's TX FIFO
111 this->buf_, // read from memory
112 this->get_buffer_size_(), // number of bytes to transfer
113 false // don't start yet
114 );
115
116 // Initialize the semaphore for this DMA channel
117 sem_init(&RP2040PIOLEDStripLightOutput::dma_write_complete_sem[this->dma_chan_], 1, 1);
118
119 irq_set_exclusive_handler(DMA_IRQ_0, dma_write_complete_handler); // after DMA all data, raise an interrupt
120 dma_channel_set_irq0_enabled(this->dma_chan_, true); // map DMA channel to interrupt
121 irq_set_enabled(DMA_IRQ_0, true); // enable interrupt
122
123 this->init_(this->pio_, this->sm_, offset, this->pin_, this->max_refresh_rate_);
124}
125
127 ESP_LOGVV(TAG, "Writing state");
128
129 if (this->is_failed()) {
130 ESP_LOGW(TAG, "Light is in failed state, not writing state.");
131 return;
132 }
133
134 if (this->buf_ == nullptr) {
135 ESP_LOGW(TAG, "Buffer is null, not writing state.");
136 return;
137 }
138
139 // the bits are already in the correct order for the pio program so we can just copy the buffer using DMA
140 sem_acquire_blocking(&RP2040PIOLEDStripLightOutput::dma_write_complete_sem[this->dma_chan_]);
141 dma_channel_transfer_from_buffer_now(this->dma_chan_, this->buf_, this->get_buffer_size_());
142}
143
145 const light::ChannelColors &colors = this->channel_colors_;
146 uint8_t *led = this->buf_ + (index * colors.bytes_per_led());
147 return {led + colors.r,
148 led + colors.g,
149 led + colors.b,
150 colors.has_white() ? led + colors.w : nullptr,
151 &this->effect_data_[index],
152 &this->correction_};
153}
154
156 char channel_colors[5];
157 ESP_LOGCONFIG(TAG,
158 "RP2040 PIO LED Strip Light Output:\n"
159 " Pin: GPIO%d\n"
160 " Number of LEDs: %d\n"
161 " Channel colors: %s\n"
162 " Max Refresh Rate: %f Hz",
163 this->pin_, this->num_leds_, this->channel_colors_.to_string(channel_colors), this->max_refresh_rate_);
164}
165
167
168} // namespace esphome::rp2040_pio_led_strip
169
170#endif
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:272
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:2107
T * allocate(size_t n)
Definition helpers.h:2134
This class represents the communication layer between the front-end MQTT layer and the hardware outpu...
Definition light_state.h:93
light::ESPColorView get_view_internal(int32_t index) const override
void write_state(light::LightState *state) override
bool state
Definition fan.h:2
constexpr float HARDWARE
For components that deal with hardware and are very important like GPIO switch.
Definition component.h:43
static void uint32_t
Which byte of an addressable LED's data carries each colour.
const char * to_string(char *buf) const
Write the order back out as text, e.g.