ESPHome 2026.6.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_BK72XX
4
6#include "esphome/core/log.h"
7
8extern "C" {
9#include "rtos_pub.h"
10// rtos_pub.h must be included before the rest of the includes
11
12#include "arm_arch.h"
13#include "general_dma_pub.h"
14#include "gpio_pub.h"
15#include "icu_pub.h"
16#include "spi.h"
17#undef SPI_DAT
18#undef SPI_BASE
19};
20
21static const uint32_t SPI_TX_DMA_CHANNEL = GDMA_CHANNEL_3;
22
23// TODO: Check if SPI_PERI_CLK_DCO depends on the chip variant
24static const uint32_t SPI_PERI_CLK_26M = 26000000;
25static const uint32_t SPI_PERI_CLK_DCO = 120000000;
26
27static const uint32_t SPI_BASE = 0x00802700;
28static const uint32_t SPI_DAT = SPI_BASE + 3 * 4;
29static const uint32_t SPI_CONFIG = SPI_BASE + 1 * 4;
30
31static const uint32_t SPI_TX_EN = 1 << 0;
32static const uint32_t CTRL_NSSMD_3 = 1 << 17;
33static const uint32_t SPI_TX_FINISH_EN = 1 << 2;
34static const uint32_t SPI_RX_FINISH_EN = 1 << 3;
35
37
38static const char *const TAG = "beken_spi_led_strip";
39
40struct spi_data_t {
41 SemaphoreHandle_t dma_tx_semaphore;
42 volatile bool tx_in_progress;
43 bool first_run;
44};
45
46static spi_data_t *spi_data = nullptr;
47
48static void set_spi_ctrl_register(unsigned long bit, bool val) {
49 uint32_t value = REG_READ(SPI_CTRL);
50 if (val == 0) {
51 value &= ~bit;
52 } else if (val == 1) {
53 value |= bit;
54 }
55 REG_WRITE(SPI_CTRL, value);
56}
57
58static void set_spi_config_register(unsigned long bit, bool val) {
59 uint32_t value = REG_READ(SPI_CONFIG);
60 if (val == 0) {
61 value &= ~bit;
62 } else if (val == 1) {
63 value |= bit;
64 }
65 REG_WRITE(SPI_CONFIG, value);
66}
67
68void spi_dma_tx_enable(bool enable) {
69 GDMA_CFG_ST en_cfg;
70 set_spi_config_register(SPI_TX_EN, enable ? 1 : 0);
71 en_cfg.channel = SPI_TX_DMA_CHANNEL;
72 en_cfg.param = enable ? 1 : 0;
73 sddev_control(GDMA_DEV_NAME, CMD_GDMA_SET_DMA_ENABLE, &en_cfg);
74}
75
76static void spi_set_clock(uint32_t max_hz) {
77 int source_clk = 0;
78 int spi_clk = 0;
79 int div = 0;
80 uint32_t param = PWD_SPI_CLK_BIT;
81 if (max_hz > 4333000) {
82 if (max_hz > 30000000) {
83 spi_clk = 30000000;
84 } else {
85 spi_clk = max_hz;
86 }
87 sddev_control(ICU_DEV_NAME, CMD_CLK_PWR_DOWN, &param);
88 source_clk = SPI_PERI_CLK_DCO;
89 param = PCLK_POSI_SPI;
90 sddev_control(ICU_DEV_NAME, CMD_CONF_PCLK_DCO, &param);
91 param = PWD_SPI_CLK_BIT;
92 sddev_control(ICU_DEV_NAME, CMD_CLK_PWR_UP, &param);
93 } else {
94 spi_clk = max_hz;
95#if CFG_XTAL_FREQUENCE
96 source_clk = CFG_XTAL_FREQUENCE;
97#else
98 source_clk = SPI_PERI_CLK_26M;
99#endif
100 param = PCLK_POSI_SPI;
101 sddev_control(ICU_DEV_NAME, CMD_CONF_PCLK_26M, &param);
102 }
103 div = ((source_clk >> 1) / spi_clk);
104 if (div < 2) {
105 div = 2;
106 } else if (div >= 255) {
107 div = 255;
108 }
109 param = REG_READ(SPI_CTRL);
110 param &= ~(SPI_CKR_MASK << SPI_CKR_POSI);
111 param |= (div << SPI_CKR_POSI);
112 REG_WRITE(SPI_CTRL, param);
113 ESP_LOGD(TAG, "target frequency: %d, actual frequency: %d", max_hz, source_clk / 2 / div);
114}
115
116void spi_dma_tx_finish_callback(unsigned int param) {
117 spi_data->tx_in_progress = false;
118 xSemaphoreGive(spi_data->dma_tx_semaphore);
120}
121
123 size_t buffer_size = this->get_buffer_size_();
124 size_t dma_buffer_size = (buffer_size * 8) + (2 * 64);
125
126 RAMAllocator<uint8_t> allocator;
127 this->buf_ = allocator.allocate(buffer_size);
128 if (this->buf_ == nullptr) {
129 ESP_LOGE(TAG, "Cannot allocate LED buffer!");
130 this->mark_failed();
131 return;
132 }
133
134 this->effect_data_ = allocator.allocate(this->num_leds_);
135 if (this->effect_data_ == nullptr) {
136 ESP_LOGE(TAG, "Cannot allocate effect data!");
137 this->mark_failed();
138 return;
139 }
140
141 this->dma_buf_ = allocator.allocate(dma_buffer_size);
142 if (this->dma_buf_ == nullptr) {
143 ESP_LOGE(TAG, "Cannot allocate DMA buffer!");
144 this->mark_failed();
145 return;
146 }
147
148 memset(this->buf_, 0, buffer_size);
149 memset(this->effect_data_, 0, this->num_leds_);
150 memset(this->dma_buf_, 0, dma_buffer_size);
151
152 uint32_t value = PCLK_POSI_SPI;
153 sddev_control(ICU_DEV_NAME, CMD_CONF_PCLK_26M, &value);
154
155 value = PWD_SPI_CLK_BIT;
156 sddev_control(ICU_DEV_NAME, CMD_CLK_PWR_UP, &value);
157
158 if (spi_data != nullptr) {
159 ESP_LOGE(TAG, "SPI device already initialized!");
160 this->mark_failed();
161 return;
162 }
163
164 spi_data = (spi_data_t *) calloc(1, sizeof(spi_data_t));
165 if (spi_data == nullptr) {
166 ESP_LOGE(TAG, "Cannot allocate spi_data!");
167 this->mark_failed();
168 return;
169 }
170
171 spi_data->dma_tx_semaphore = xSemaphoreCreateBinary();
172 if (spi_data->dma_tx_semaphore == nullptr) {
173 ESP_LOGE(TAG, "TX Semaphore init faild!");
174 this->mark_failed();
175 return;
176 }
177
178 spi_data->first_run = true;
179
180 set_spi_ctrl_register(MSTEN, 0);
181 set_spi_ctrl_register(BIT_WDTH, 0);
182 spi_set_clock(this->spi_frequency_);
183 set_spi_ctrl_register(CKPOL, 0);
184 set_spi_ctrl_register(CKPHA, 0);
185 set_spi_ctrl_register(MSTEN, 1);
186 set_spi_ctrl_register(SPIEN, 1);
187
188 set_spi_ctrl_register(TXINT_EN, 0);
189 set_spi_ctrl_register(RXINT_EN, 0);
190 set_spi_config_register(SPI_TX_FINISH_EN, 1);
191 set_spi_config_register(SPI_RX_FINISH_EN, 1);
192 set_spi_ctrl_register(RXOVR_EN, 0);
193 set_spi_ctrl_register(TXOVR_EN, 0);
194
195 value = REG_READ(SPI_CTRL);
196 value &= ~CTRL_NSSMD_3;
197 value |= (1 << 17);
198 REG_WRITE(SPI_CTRL, value);
199
200 value = GFUNC_MODE_SPI_DMA;
201 sddev_control(GPIO_DEV_NAME, CMD_GPIO_ENABLE_SECOND, &value);
202 set_spi_ctrl_register(SPI_S_CS_UP_INT_EN, 0);
203
204 GDMA_CFG_ST en_cfg;
205 GDMACFG_TPYES_ST init_cfg;
206 memset(&init_cfg, 0, sizeof(GDMACFG_TPYES_ST));
207
208 init_cfg.dstdat_width = 8;
209 init_cfg.srcdat_width = 32;
210 init_cfg.dstptr_incr = 0;
211 init_cfg.srcptr_incr = 1;
212 init_cfg.src_start_addr = this->dma_buf_;
213 init_cfg.dst_start_addr = (void *) SPI_DAT; // SPI_DMA_REG4_TXFIFO
214 init_cfg.channel = SPI_TX_DMA_CHANNEL;
215 init_cfg.prio = 0; // 10
216 init_cfg.u.type4.src_loop_start_addr = this->dma_buf_;
217 init_cfg.u.type4.src_loop_end_addr = this->dma_buf_ + dma_buffer_size;
218 init_cfg.half_fin_handler = nullptr;
219 init_cfg.fin_handler = spi_dma_tx_finish_callback;
220 init_cfg.src_module = GDMA_X_SRC_DTCM_RD_REQ;
221 init_cfg.dst_module = GDMA_X_DST_GSPI_TX_REQ; // GDMA_X_DST_HSSPI_TX_REQ
222 sddev_control(GDMA_DEV_NAME, CMD_GDMA_CFG_TYPE4, (void *) &init_cfg);
223 en_cfg.channel = SPI_TX_DMA_CHANNEL;
224 en_cfg.param = dma_buffer_size;
225 sddev_control(GDMA_DEV_NAME, CMD_GDMA_SET_TRANS_LENGTH, (void *) &en_cfg);
226 en_cfg.channel = SPI_TX_DMA_CHANNEL;
227 en_cfg.param = 0;
228 sddev_control(GDMA_DEV_NAME, CMD_GDMA_CFG_WORK_MODE, (void *) &en_cfg);
229 en_cfg.channel = SPI_TX_DMA_CHANNEL;
230 en_cfg.param = 0;
231 sddev_control(GDMA_DEV_NAME, CMD_GDMA_CFG_SRCADDR_LOOP, &en_cfg);
232
234
235 value = REG_READ(SPI_CONFIG);
236 value &= ~(0xFFF << 8);
237 value |= ((dma_buffer_size & 0xFFF) << 8);
238 REG_WRITE(SPI_CONFIG, value);
239}
240
241void BekenSPILEDStripLightOutput::set_led_params(uint8_t bit0, uint8_t bit1, uint32_t spi_frequency) {
242 this->bit0_ = bit0;
243 this->bit1_ = bit1;
244 this->spi_frequency_ = spi_frequency;
245}
246
248 // protect from refreshing too often
249 uint32_t now = micros();
250 if (*this->max_refresh_rate_ != 0 && (now - this->last_refresh_) < *this->max_refresh_rate_) {
251 // try again next loop iteration, so that this change won't get lost
252 this->schedule_show();
253 return;
254 }
255 this->last_refresh_ = now;
256 this->mark_shown_();
257
258 ESP_LOGVV(TAG, "Writing RGB values to bus");
259
260 if (spi_data == nullptr) {
261 ESP_LOGE(TAG, "SPI not initialized");
262 this->status_set_warning();
263 return;
264 }
265
266 if (!spi_data->first_run && !xSemaphoreTake(spi_data->dma_tx_semaphore, 10 / portTICK_PERIOD_MS)) {
267 ESP_LOGE(TAG, "Timed out waiting for semaphore");
268 return;
269 }
270
271 if (spi_data->tx_in_progress) {
272 ESP_LOGE(TAG, "tx_in_progress is set");
273 this->status_set_warning();
274 return;
275 }
276
277 spi_data->tx_in_progress = true;
278
279 size_t buffer_size = this->get_buffer_size_();
280 size_t size = 0;
281 uint8_t *psrc = this->buf_;
282 uint8_t *pdest = this->dma_buf_ + 64;
283 // The 64 byte padding is a workaround for a SPI DMA bug where the
284 // output doesn't exactly start at the beginning of dma_buf_
285
286 while (size < buffer_size) {
287 uint8_t b = *psrc;
288 for (int i = 0; i < 8; i++) {
289 *pdest++ = b & (1 << (7 - i)) ? this->bit1_ : this->bit0_;
290 }
291 size++;
292 psrc++;
293 }
294
295 spi_data->first_run = false;
297
298 this->status_clear_warning();
299}
300
302 int32_t r = 0, g = 0, b = 0;
303 switch (this->rgb_order_) {
304 case ORDER_RGB:
305 r = 0;
306 g = 1;
307 b = 2;
308 break;
309 case ORDER_RBG:
310 r = 0;
311 g = 2;
312 b = 1;
313 break;
314 case ORDER_GRB:
315 r = 1;
316 g = 0;
317 b = 2;
318 break;
319 case ORDER_GBR:
320 r = 2;
321 g = 0;
322 b = 1;
323 break;
324 case ORDER_BGR:
325 r = 2;
326 g = 1;
327 b = 0;
328 break;
329 case ORDER_BRG:
330 r = 1;
331 g = 2;
332 b = 0;
333 break;
334 }
335 uint8_t multiplier = this->is_rgbw_ || this->is_wrgb_ ? 4 : 3;
336 uint8_t white = this->is_wrgb_ ? 0 : 3;
337
338 return {this->buf_ + (index * multiplier) + r + this->is_wrgb_,
339 this->buf_ + (index * multiplier) + g + this->is_wrgb_,
340 this->buf_ + (index * multiplier) + b + this->is_wrgb_,
341 this->is_rgbw_ || this->is_wrgb_ ? this->buf_ + (index * multiplier) + white : nullptr,
342 &this->effect_data_[index],
343 &this->correction_};
344}
345
347 ESP_LOGCONFIG(TAG,
348 "Beken SPI LED Strip:\n"
349 " Pin: %u",
350 this->pin_);
351 const char *rgb_order;
352 switch (this->rgb_order_) {
353 case ORDER_RGB:
354 rgb_order = "RGB";
355 break;
356 case ORDER_RBG:
357 rgb_order = "RBG";
358 break;
359 case ORDER_GRB:
360 rgb_order = "GRB";
361 break;
362 case ORDER_GBR:
363 rgb_order = "GBR";
364 break;
365 case ORDER_BGR:
366 rgb_order = "BGR";
367 break;
368 case ORDER_BRG:
369 rgb_order = "BRG";
370 break;
371 default:
372 rgb_order = "UNKNOWN";
373 break;
374 }
375 ESP_LOGCONFIG(TAG,
376 " RGB Order: %s\n"
377 " Max refresh rate: %" PRIu32 "\n"
378 " Number of LEDs: %u",
379 rgb_order, *this->max_refresh_rate_, this->num_leds_);
380}
381
383
384} // namespace esphome::beken_spi_led_strip
385
386#endif // USE_BK72XX
void mark_failed()
Mark this component as failed.
void status_clear_warning()
Definition component.h:289
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:2053
T * allocate(size_t n)
Definition helpers.h:2080
void set_led_params(uint8_t bit0, uint8_t bit1, uint32_t spi_frequency)
light::ESPColorView get_view_internal(int32_t index) const override
void write_state(light::LightState *state) override
This class represents the communication layer between the front-end MQTT layer and the hardware outpu...
Definition light_state.h:93
bool state
Definition fan.h:2
mopeka_std_values val[3]
void spi_dma_tx_enable(bool enable)
Definition led_strip.cpp:68
void spi_dma_tx_finish_callback(unsigned int param)
constexpr float HARDWARE
For components that deal with hardware and are very important like GPIO switch.
Definition component.h:41
uint32_t IRAM_ATTR HOT micros()
Definition hal.cpp:43
static void uint32_t