ESPHome 2026.8.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->is_rgbw_ ? num_leds_ * 4 : num_leds_ * 3, // 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 int32_t r = 0, g = 0, b = 0;
146 switch (this->rgb_order_) {
147 case ORDER_RGB:
148 r = 0;
149 g = 1;
150 b = 2;
151 break;
152 case ORDER_RBG:
153 r = 0;
154 g = 2;
155 b = 1;
156 break;
157 case ORDER_GRB:
158 r = 1;
159 g = 0;
160 b = 2;
161 break;
162 case ORDER_GBR:
163 r = 2;
164 g = 0;
165 b = 1;
166 break;
167 case ORDER_BGR:
168 r = 2;
169 g = 1;
170 b = 0;
171 break;
172 case ORDER_BRG:
173 r = 1;
174 g = 2;
175 b = 0;
176 break;
177 }
178 uint8_t multiplier = this->is_rgbw_ ? 4 : 3;
179 return {this->buf_ + (index * multiplier) + r,
180 this->buf_ + (index * multiplier) + g,
181 this->buf_ + (index * multiplier) + b,
182 this->is_rgbw_ ? this->buf_ + (index * multiplier) + 3 : nullptr,
183 &this->effect_data_[index],
184 &this->correction_};
185}
186
188 ESP_LOGCONFIG(TAG,
189 "RP2040 PIO LED Strip Light Output:\n"
190 " Pin: GPIO%d\n"
191 " Number of LEDs: %d\n"
192 " RGBW: %s\n"
193 " RGB Order: %s\n"
194 " Max Refresh Rate: %f Hz",
195 this->pin_, this->num_leds_, YESNO(this->is_rgbw_), rgb_order_to_string(this->rgb_order_),
196 this->max_refresh_rate_);
197}
198
200
201} // namespace esphome::rp2040_pio_led_strip
202
203#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:2073
T * allocate(size_t n)
Definition helpers.h:2100
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
const char * rgb_order_to_string(RGBOrder order)
Definition led_strip.h:39
constexpr float HARDWARE
For components that deal with hardware and are very important like GPIO switch.
Definition component.h:43
static void uint32_t