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