ESPHome 2026.5.0b1
Loading...
Searching...
No Matches
led_strip.cpp
Go to the documentation of this file.
1#include "led_strip.h"
2
3#ifdef USE_RP2040
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 *TAG = "rp2040_pio_led_strip";
18
19static uint8_t num_instance_[2] = {0, 0};
20static std::map<Chipset, uint> chipset_offsets_ = {
22};
23static std::map<Chipset, bool> conf_count_ = {
24 {CHIPSET_WS2812, false}, {CHIPSET_WS2812B, false}, {CHIPSET_SK6812, false},
25 {CHIPSET_SM16703, false}, {CHIPSET_CUSTOM, false},
26};
27static bool dma_chan_active_[12];
28static struct semaphore dma_write_complete_sem_[12];
29
30// DMA interrupt service routine
32 uint32_t channel = dma_hw->ints0;
33 for (uint dma_chan = 0; dma_chan < 12; ++dma_chan) {
34 if (RP2040PIOLEDStripLightOutput::dma_chan_active_[dma_chan] && (channel & (1u << dma_chan))) {
35 dma_hw->ints0 = (1u << dma_chan); // Clear the interrupt
36 sem_release(&RP2040PIOLEDStripLightOutput::dma_write_complete_sem_[dma_chan]); // Handle the interrupt
37 }
38 }
39}
40
42 size_t buffer_size = this->get_buffer_size_();
43
44 RAMAllocator<uint8_t> allocator;
45 this->buf_ = allocator.allocate(buffer_size);
46 if (this->buf_ == nullptr) {
47 ESP_LOGE(TAG, "Failed to allocate buffer of size %u", buffer_size);
48 this->mark_failed();
49 return;
50 }
51
52 this->effect_data_ = allocator.allocate(this->num_leds_);
53 if (this->effect_data_ == nullptr) {
54 ESP_LOGE(TAG, "Failed to allocate effect data of size %u", this->num_leds_);
55 this->mark_failed();
56 return;
57 }
58
59 // Initialize the PIO program
60
61 // Select PIO instance to use (0 or 1)
62 if (this->pio_ == nullptr) {
63 ESP_LOGE(TAG, "Failed to claim PIO instance");
64 this->mark_failed();
65 return;
66 }
67
68 // if there are multiple strips, we can reuse the same PIO program and save space
69 // but there are only 4 state machines on each PIO so we can only have 4 strips per PIO
70 uint offset = 0;
71
72 if (RP2040PIOLEDStripLightOutput::num_instance_[this->pio_ == pio0 ? 0 : 1] >= 4) {
73 ESP_LOGE(TAG, "Too many instances of PIO program");
74 this->mark_failed();
75 return;
76 }
77 // keep track of how many instances of the PIO program are running on each PIO
78 RP2040PIOLEDStripLightOutput::num_instance_[this->pio_ == pio0 ? 0 : 1]++;
79
80 // if there are multiple strips of the same chipset, we can reuse the same PIO program and save space
81 if (this->conf_count_[this->chipset_]) {
82 offset = RP2040PIOLEDStripLightOutput::chipset_offsets_[this->chipset_];
83 } else {
84 // Load the assembled program into the PIO and get its location in the PIO's instruction memory and save it
85 offset = pio_add_program(this->pio_, this->program_);
86 RP2040PIOLEDStripLightOutput::chipset_offsets_[this->chipset_] = offset;
87 RP2040PIOLEDStripLightOutput::conf_count_[this->chipset_] = true;
88 }
89
90 // Configure the state machine's PIO, and start it
91 this->sm_ = pio_claim_unused_sm(this->pio_, true);
92 if (this->sm_ < 0) {
93 // in theory this code should never be reached
94 ESP_LOGE(TAG, "Failed to claim PIO state machine");
95 this->mark_failed();
96 return;
97 }
98
99 // Initalize the DMA channel (Note: There are 12 DMA channels and 8 state machines so we won't run out)
100
101 this->dma_chan_ = dma_claim_unused_channel(true);
102 if (this->dma_chan_ < 0) {
103 ESP_LOGE(TAG, "Failed to claim DMA channel");
104 this->mark_failed();
105 return;
106 }
107
108 // Mark the DMA channel as active
109 RP2040PIOLEDStripLightOutput::dma_chan_active_[this->dma_chan_] = true;
110
111 this->dma_config_ = dma_channel_get_default_config(this->dma_chan_);
112 channel_config_set_transfer_data_size(
113 &this->dma_config_,
114 DMA_SIZE_8); // 8 bit transfers (could be 32 but the pio program would need to be changed to handle junk data)
115 channel_config_set_read_increment(&this->dma_config_, true); // increment the read address
116 channel_config_set_write_increment(&this->dma_config_, false); // don't increment the write address
117 channel_config_set_dreq(&this->dma_config_,
118 pio_get_dreq(this->pio_, this->sm_, true)); // set the DREQ to the state machine's TX FIFO
119
120 dma_channel_configure(this->dma_chan_, &this->dma_config_,
121 &this->pio_->txf[this->sm_], // write to the state machine's TX FIFO
122 this->buf_, // read from memory
123 this->is_rgbw_ ? num_leds_ * 4 : num_leds_ * 3, // number of bytes to transfer
124 false // don't start yet
125 );
126
127 // Initialize the semaphore for this DMA channel
128 sem_init(&RP2040PIOLEDStripLightOutput::dma_write_complete_sem_[this->dma_chan_], 1, 1);
129
130 irq_set_exclusive_handler(DMA_IRQ_0, dma_write_complete_handler_); // after DMA all data, raise an interrupt
131 dma_channel_set_irq0_enabled(this->dma_chan_, true); // map DMA channel to interrupt
132 irq_set_enabled(DMA_IRQ_0, true); // enable interrupt
133
134 this->init_(this->pio_, this->sm_, offset, this->pin_, this->max_refresh_rate_);
135}
136
138 ESP_LOGVV(TAG, "Writing state");
139
140 if (this->is_failed()) {
141 ESP_LOGW(TAG, "Light is in failed state, not writing state.");
142 return;
143 }
144
145 if (this->buf_ == nullptr) {
146 ESP_LOGW(TAG, "Buffer is null, not writing state.");
147 return;
148 }
149
150 // the bits are already in the correct order for the pio program so we can just copy the buffer using DMA
151 sem_acquire_blocking(&RP2040PIOLEDStripLightOutput::dma_write_complete_sem_[this->dma_chan_]);
152 dma_channel_transfer_from_buffer_now(this->dma_chan_, this->buf_, this->get_buffer_size_());
153}
154
156 int32_t r = 0, g = 0, b = 0, w = 0;
157 switch (this->rgb_order_) {
158 case ORDER_RGB:
159 r = 0;
160 g = 1;
161 b = 2;
162 break;
163 case ORDER_RBG:
164 r = 0;
165 g = 2;
166 b = 1;
167 break;
168 case ORDER_GRB:
169 r = 1;
170 g = 0;
171 b = 2;
172 break;
173 case ORDER_GBR:
174 r = 2;
175 g = 0;
176 b = 1;
177 break;
178 case ORDER_BGR:
179 r = 2;
180 g = 1;
181 b = 0;
182 break;
183 case ORDER_BRG:
184 r = 1;
185 g = 2;
186 b = 0;
187 break;
188 }
189 uint8_t multiplier = this->is_rgbw_ ? 4 : 3;
190 return {this->buf_ + (index * multiplier) + r,
191 this->buf_ + (index * multiplier) + g,
192 this->buf_ + (index * multiplier) + b,
193 this->is_rgbw_ ? this->buf_ + (index * multiplier) + 3 : nullptr,
194 &this->effect_data_[index],
195 &this->correction_};
196}
197
199 ESP_LOGCONFIG(TAG,
200 "RP2040 PIO LED Strip Light Output:\n"
201 " Pin: GPIO%d\n"
202 " Number of LEDs: %d\n"
203 " RGBW: %s\n"
204 " RGB Order: %s\n"
205 " Max Refresh Rate: %f Hz",
206 this->pin_, this->num_leds_, YESNO(this->is_rgbw_), rgb_order_to_string(this->rgb_order_),
207 this->max_refresh_rate_);
208}
209
211
212} // namespace esphome::rp2040_pio_led_strip
213
214#endif
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:284
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:2053
T * allocate(size_t n)
Definition helpers.h:2080
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:41
static void uint32_t