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