ESPHome 2026.11.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
23static const uint32_t SPI_PERI_CLK_26M = 26000000;
24
25#if defined(USE_LIBRETINY_VARIANT_BK7251)
26static const uint32_t SPI_PERI_CLK_DCO = 180000000;
27#else
28static const uint32_t SPI_PERI_CLK_DCO = 120000000;
29#endif
30
31// The beken378 SDK has two SPI register layouts:
32// N-like (BK7231N, BK7238, BK7252N): CTRL +0x00, CONFIG +0x04, STAT +0x08, DAT +0x0C
33// T-like (BK7231T, BK7251): CTRL +0x00, STAT +0x04, DAT +0x08
34// The N-like variants also have TX enable / transfer length / TX finish bits in CONFIG.
35// The T-like variants have no CONFIG address.
36#if defined(USE_LIBRETINY_VARIANT_BK7231N) || defined(USE_LIBRETINY_VARIANT_BK7238)
37#define BEKEN_SPI_IS_N_LIKE
38#endif
39
40static const uint32_t SPI_BASE = 0x00802700;
41#ifdef BEKEN_SPI_IS_N_LIKE
42static const uint32_t SPI_DAT = SPI_BASE + 3 * 4;
43static const uint32_t SPI_CONFIG = SPI_BASE + 1 * 4;
44static const uint32_t SPI_TX_EN = 1 << 0;
45static const uint32_t SPI_TX_FINISH_EN = 1 << 2;
46static const uint32_t SPI_RX_FINISH_EN = 1 << 3;
47#else
48static const uint32_t SPI_DAT = SPI_BASE + 2 * 4;
49#endif
50
51static const uint32_t CTRL_NSSMD_3 = 1 << 17;
52
54
55ESPHOME_LOG_TAG(TAG, "beken_spi_led_strip");
56
57struct SpiData {
58 SemaphoreHandle_t dma_tx_semaphore;
59 volatile bool tx_in_progress;
60 bool first_run;
61};
62
63static SpiData *spi_data = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
64
65static void set_spi_ctrl_register(uint32_t bit, bool val) {
66 uint32_t value = REG_READ(SPI_CTRL);
67 if (val == 0) {
68 value &= ~bit;
69 } else if (val == 1) {
70 value |= bit;
71 }
72 REG_WRITE(SPI_CTRL, value);
73}
74
75#ifdef BEKEN_SPI_IS_N_LIKE
76static void set_spi_config_register(uint32_t bit, bool val) {
77 uint32_t value = REG_READ(SPI_CONFIG);
78 if (val == 0) {
79 value &= ~bit;
80 } else if (val == 1) {
81 value |= bit;
82 }
83 REG_WRITE(SPI_CONFIG, value);
84}
85#endif
86
87void spi_dma_tx_enable(bool enable) {
88 GDMA_CFG_ST en_cfg;
89#ifdef BEKEN_SPI_IS_N_LIKE
90 set_spi_config_register(SPI_TX_EN, enable);
91#endif
92 en_cfg.channel = SPI_TX_DMA_CHANNEL;
93 en_cfg.param = enable ? 1 : 0;
94 sddev_control(GDMA_DEV_NAME, CMD_GDMA_SET_DMA_ENABLE, &en_cfg);
95}
96
97static void spi_set_clock(uint32_t max_hz) {
98 int source_clk = 0;
99 int spi_clk = 0;
100 int div = 0;
101 uint32_t param = PWD_SPI_CLK_BIT;
102 if (max_hz > 4333000) {
103 if (max_hz > 30000000) {
104 spi_clk = 30000000;
105 } else {
106 spi_clk = max_hz;
107 }
108 sddev_control(ICU_DEV_NAME, CMD_CLK_PWR_DOWN, &param);
109 source_clk = SPI_PERI_CLK_DCO;
110 param = PCLK_POSI_SPI;
111 sddev_control(ICU_DEV_NAME, CMD_CONF_PCLK_DCO, &param);
112 param = PWD_SPI_CLK_BIT;
113 sddev_control(ICU_DEV_NAME, CMD_CLK_PWR_UP, &param);
114 } else {
115 spi_clk = max_hz;
116#if CFG_XTAL_FREQUENCE
117 source_clk = CFG_XTAL_FREQUENCE;
118#else
119 source_clk = SPI_PERI_CLK_26M;
120#endif
121 param = PCLK_POSI_SPI;
122 sddev_control(ICU_DEV_NAME, CMD_CONF_PCLK_26M, &param);
123 }
124#ifdef BEKEN_SPI_IS_N_LIKE
125 // N-like variants: spi_clk = source_clk / (2 * div)
126 div = ((source_clk >> 1) / spi_clk);
127#else
128 // T-like variants: spi_clk = source_clk / (2 * (div + 1))
129 div = ((source_clk >> 1) / spi_clk) - 1;
130#endif
131 if (div < 2) {
132 div = 2;
133 } else if (div >= 255) {
134 div = 255;
135 }
136 param = REG_READ(SPI_CTRL);
137 param &= ~(SPI_CKR_MASK << SPI_CKR_POSI);
138 param |= (div << SPI_CKR_POSI);
139 REG_WRITE(SPI_CTRL, param);
140#ifdef BEKEN_SPI_IS_N_LIKE
141 const int divisor = div;
142#else
143 const int divisor = div + 1;
144#endif
145 ESP_LOGD(TAG, "target frequency: %" PRIu32 ", actual frequency: %d", max_hz, source_clk / 2 / divisor);
146}
147
148void spi_dma_tx_finish_callback(unsigned int param) {
149 spi_data->tx_in_progress = false;
150 xSemaphoreGive(spi_data->dma_tx_semaphore);
151 spi_dma_tx_enable(false);
152}
153
155 size_t buffer_size = this->get_buffer_size_();
156 size_t dma_buffer_size = (buffer_size * 8) + (2 * 64);
157
158 RAMAllocator<uint8_t> allocator;
159 this->buf_ = allocator.allocate(buffer_size);
160 if (this->buf_ == nullptr) {
161 ESP_LOGE(TAG, "Cannot allocate LED buffer!");
162 this->mark_failed();
163 return;
164 }
165
166 this->effect_data_ = allocator.allocate(this->num_leds_);
167 if (this->effect_data_ == nullptr) {
168 ESP_LOGE(TAG, "Cannot allocate effect data!");
169 this->mark_failed();
170 return;
171 }
172
173 this->dma_buf_ = allocator.allocate(dma_buffer_size);
174 if (this->dma_buf_ == nullptr) {
175 ESP_LOGE(TAG, "Cannot allocate DMA buffer!");
176 this->mark_failed();
177 return;
178 }
179
180 memset(this->buf_, 0, buffer_size);
181 memset(this->effect_data_, 0, this->num_leds_);
182 memset(this->dma_buf_, 0, dma_buffer_size);
183
184 uint32_t value = PCLK_POSI_SPI;
185 sddev_control(ICU_DEV_NAME, CMD_CONF_PCLK_26M, &value);
186
187 value = PWD_SPI_CLK_BIT;
188 sddev_control(ICU_DEV_NAME, CMD_CLK_PWR_UP, &value);
189
190 if (spi_data != nullptr) {
191 ESP_LOGE(TAG, "SPI device already initialized!");
192 this->mark_failed();
193 return;
194 }
195
196 spi_data = (SpiData *) calloc(1, sizeof(SpiData)); // NOLINT(cppcoreguidelines-no-malloc)
197 if (spi_data == nullptr) {
198 ESP_LOGE(TAG, "Cannot allocate spi_data!");
199 this->mark_failed();
200 return;
201 }
202
203 spi_data->dma_tx_semaphore = xSemaphoreCreateBinary();
204 if (spi_data->dma_tx_semaphore == nullptr) {
205 ESP_LOGE(TAG, "TX Semaphore init faild!");
206 this->mark_failed();
207 return;
208 }
209
210 spi_data->first_run = true;
211
212 set_spi_ctrl_register(MSTEN, false);
213 set_spi_ctrl_register(BIT_WDTH, false);
214 spi_set_clock(this->spi_frequency_);
215 set_spi_ctrl_register(CKPOL, false);
216 set_spi_ctrl_register(CKPHA, false);
217 set_spi_ctrl_register(MSTEN, true);
218 set_spi_ctrl_register(SPIEN, true);
219
220 set_spi_ctrl_register(TXINT_EN, false);
221 set_spi_ctrl_register(RXINT_EN, false);
222#ifdef BEKEN_SPI_IS_N_LIKE
223 set_spi_config_register(SPI_TX_FINISH_EN, true);
224 set_spi_config_register(SPI_RX_FINISH_EN, true);
225#endif
226 set_spi_ctrl_register(RXOVR_EN, false);
227 set_spi_ctrl_register(TXOVR_EN, false);
228
229 value = REG_READ(SPI_CTRL);
230 value &= ~CTRL_NSSMD_3;
231 value |= (1 << 17);
232 REG_WRITE(SPI_CTRL, value);
233
234#ifdef BEKEN_SPI_IS_N_LIKE
235 value = GFUNC_MODE_SPI_DMA;
236#else
237 // For T-like variants, GFUNC_MODE_SPI_DMA identifies an SPI-DMA peripheral
238 // with a different base address. We want the regular SPI controller here.
239 value = GFUNC_MODE_SPI;
240#endif
241 sddev_control(GPIO_DEV_NAME, CMD_GPIO_ENABLE_SECOND, &value);
242#ifdef BEKEN_SPI_IS_N_LIKE
243 set_spi_ctrl_register(SPI_S_CS_UP_INT_EN, false);
244#endif
245
246 GDMA_CFG_ST en_cfg;
247 GDMACFG_TPYES_ST init_cfg;
248 memset(&init_cfg, 0, sizeof(GDMACFG_TPYES_ST));
249
250 init_cfg.dstdat_width = 8;
251 init_cfg.srcdat_width = 32;
252 init_cfg.dstptr_incr = 0;
253 init_cfg.srcptr_incr = 1;
254 init_cfg.src_start_addr = this->dma_buf_;
255 init_cfg.dst_start_addr = (void *) SPI_DAT; // NOLINT(performance-no-int-to-ptr) SPI_DMA_REG4_TXFIFO
256 init_cfg.channel = SPI_TX_DMA_CHANNEL;
257 init_cfg.prio = 0; // 10
258 init_cfg.u.type4.src_loop_start_addr = this->dma_buf_;
259 init_cfg.u.type4.src_loop_end_addr = this->dma_buf_ + dma_buffer_size;
260 init_cfg.half_fin_handler = nullptr;
261 init_cfg.fin_handler = spi_dma_tx_finish_callback;
262 init_cfg.src_module = GDMA_X_SRC_DTCM_RD_REQ;
263 init_cfg.dst_module = GDMA_X_DST_GSPI_TX_REQ; // GDMA_X_DST_HSSPI_TX_REQ
264 sddev_control(GDMA_DEV_NAME, CMD_GDMA_CFG_TYPE4, (void *) &init_cfg);
265 en_cfg.channel = SPI_TX_DMA_CHANNEL;
266 en_cfg.param = dma_buffer_size;
267 sddev_control(GDMA_DEV_NAME, CMD_GDMA_SET_TRANS_LENGTH, (void *) &en_cfg);
268 en_cfg.channel = SPI_TX_DMA_CHANNEL;
269 en_cfg.param = 0;
270 sddev_control(GDMA_DEV_NAME, CMD_GDMA_CFG_WORK_MODE, (void *) &en_cfg);
271 en_cfg.channel = SPI_TX_DMA_CHANNEL;
272 en_cfg.param = 0;
273 sddev_control(GDMA_DEV_NAME, CMD_GDMA_CFG_SRCADDR_LOOP, &en_cfg);
274
275 spi_dma_tx_enable(false);
276
277#ifdef BEKEN_SPI_IS_N_LIKE
278 // N-like variants: The SPI controller must be told how many bytes to clock out.
279 value = REG_READ(SPI_CONFIG);
280 value &= ~(0xFFF << 8);
281 value |= ((dma_buffer_size & 0xFFF) << 8);
282 REG_WRITE(SPI_CONFIG, value);
283#else
284 // T-like variants: No transfer-length register. The SPI controller shifts out
285 // everything the DMA writes into SPI_DAT.
286#endif
287}
288
289void BekenSPILEDStripLightOutput::set_led_params(uint8_t bit0, uint8_t bit1, uint32_t spi_frequency) {
290 this->bit0_ = bit0;
291 this->bit1_ = bit1;
292 this->spi_frequency_ = spi_frequency;
293}
294
296 uint32_t now = micros();
297 if (this->throttle_show_(now, this->last_refresh_, this->max_refresh_rate_))
298 return;
299 this->last_refresh_ = now;
300 this->mark_shown_();
301
302 ESP_LOGVV(TAG, "Writing RGB values to bus");
303
304 if (spi_data == nullptr) {
305 ESP_LOGE(TAG, "SPI not initialized");
306 this->status_set_warning();
307 return;
308 }
309
310 if (!spi_data->first_run && !xSemaphoreTake(spi_data->dma_tx_semaphore, 10 / portTICK_PERIOD_MS)) {
311 // Recover so the next update can try again instead of being stuck forever.
312 ESP_LOGE(TAG, "Timed out waiting for semaphore");
313 spi_dma_tx_enable(false);
314 spi_data->tx_in_progress = false;
315 xSemaphoreGive(spi_data->dma_tx_semaphore);
316 this->status_set_warning();
317 this->schedule_show();
318 return;
319 }
320
321#ifndef BEKEN_SPI_IS_N_LIKE
322 if (!spi_data->first_run) {
323 // For T-like variants, the GDMA finish interrupt only means that all bytes were written into
324 // the SPI TX FIFO. We must still wait for it to drain before refilling it.
325 uint32_t wait_start = micros();
326 while (!(REG_READ(SPI_STAT) & TXFIFO_EMPTY)) {
327 if (micros() - wait_start > 2000) {
328 ESP_LOGW(TAG, "SPI TX FIFO did not drain in time");
329 break;
330 }
331 }
332 }
333#endif
334
335 if (spi_data->tx_in_progress) {
336 ESP_LOGE(TAG, "tx_in_progress is set");
337 xSemaphoreGive(spi_data->dma_tx_semaphore);
338 this->status_set_warning();
339 this->schedule_show();
340 return;
341 }
342
343 spi_data->tx_in_progress = true;
344
345 size_t buffer_size = this->get_buffer_size_();
346 size_t size = 0;
347 uint8_t *psrc = this->buf_;
348 uint8_t *pdest = this->dma_buf_ + 64;
349 // The 64 byte padding is a workaround for a SPI DMA bug where the
350 // output doesn't exactly start at the beginning of dma_buf_
351
352 while (size < buffer_size) {
353 uint8_t b = *psrc;
354 for (int i = 0; i < 8; i++) {
355 *pdest++ = b & (1 << (7 - i)) ? this->bit1_ : this->bit0_;
356 }
357 size++;
358 psrc++;
359 }
360
361 spi_data->first_run = false;
362 spi_dma_tx_enable(true);
363
364 this->status_clear_warning();
365}
366
368 ESP_LOGCONFIG(TAG,
369 "Beken SPI LED Strip:\n"
370 " Pin: %u",
371 this->pin_);
372 char channel_colors[5];
373 ESP_LOGCONFIG(TAG,
374 " Channel colors: %s\n"
375 " Max refresh rate: %" PRIu32 "\n"
376 " Number of LEDs: %u",
377 this->channel_colors_.to_string(channel_colors), this->max_refresh_rate_, this->num_leds_);
378}
379
381
382} // namespace esphome::beken_spi_led_strip
383
384#endif // USE_BK72XX
void mark_failed()
Mark this component as failed.
void status_clear_warning()
Definition component.h:296
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:2324
T * allocate(size_t n)
Definition helpers.h:2351
void set_led_params(uint8_t bit0, uint8_t bit1, uint32_t spi_frequency)
void write_state(light::LightState *state) override
bool throttle_show_(uint32_t now_us, uint32_t last_us, uint32_t interval_us)
This class represents the communication layer between the front-end MQTT layer and the hardware outpu...
Definition light_state.h:68
bool state
Definition fan.h:2
mopeka_std_values val[3]
void spi_dma_tx_enable(bool enable)
Definition led_strip.cpp:87
void spi_dma_tx_finish_callback(unsigned int param)
ESPHOME_LOG_TAG(TAG, "beken_spi_led_strip")
constexpr float HARDWARE
For components that deal with hardware and are very important like GPIO switch.
Definition component.h:43
value[LAYOUT.day_index()]
uint32_t IRAM_ATTR HOT micros()
Definition hal.cpp:43
static void uint32_t
const char * to_string(char *buf) const
Write the order back out as text, e.g.