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