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_LN882X
4
5#include <cinttypes>
6#include <cstring>
7
8#include "esphome/core/hal.h"
9#include "esphome/core/log.h"
10
11extern "C" {
12// Skip the SDK register headers: their unused *_unpack inline functions copy volatile unions, which C++ rejects.
13// Same as LibreTiny's sdk_private.h; the HAL headers below use nothing from them.
14// NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
15#define __REG_DMA_H__
16#define __REG_GPIO_H__
17#define __REG_SPI_H__
18#define __REG_SYSC_AWO_H__
19#define __REG_SYSC_CMP_H__
20// NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
21#include "hal/hal_clock.h"
22#include "hal/hal_dma.h"
23#include "hal/hal_gpio.h"
24#include "hal/hal_spi.h"
25}
26
28
29ESPHOME_LOG_TAG(TAG, "ln882x_spi_led_strip");
30
31// light.py derives the SPI encoding from this APB0 clock, which the SDK's SystemInit() sets.
32static constexpr uint32_t APB0_HZ = 40000000;
33// Each LED data bit is sent as one SPI byte, MSB first: the leading 1-bits are the high time.
34static constexpr uint8_t SPI_BITS_PER_LED_BIT = 8;
35// SPI0 TX is hard-wired to DMA channel 4 (see hal_dma.h).
36static constexpr uint32_t LED_STRIP_DMA_CH = DMA_CH_4;
37
38static uint8_t spi_pattern(uint8_t high_bits) { return uint8_t(0xFF00 >> high_bits); }
39
40static void spi_dma_init(uint8_t pin, uint8_t prescaler) {
41 // LibreTiny numbers pins as (port << 4) | index. Every pin the LN882x boards expose
42 // (PA0-PA12, PB3-PB9) can be muxed to SPI0 MOSI.
43 const uint32_t gpio_base = (pin >> 4) ? GPIOB_BASE : GPIOA_BASE;
44 const auto gpio_pin = static_cast<gpio_pin_t>(1U << (pin & 0xF));
45 hal_gpio_pin_afio_select(gpio_base, gpio_pin, SPI0_MOSI);
46 hal_gpio_pin_afio_en(gpio_base, gpio_pin, HAL_ENABLE);
47
48 // Only MOSI is routed to a pin; NSS is left unrouted with its output disabled.
49 spi_init_type_def spi_init{};
50 spi_init.spi_direction = SPI_DIRECTION_2LINES_FULLDUPLEX;
51 spi_init.spi_mode = SPI_MODE_MASTER;
52 spi_init.spi_data_size = SPI_DATASIZE_8B;
53 spi_init.spi_cpol = SPI_CPOL_LOW;
54 spi_init.spi_cpha = SPI_CPHA_1EDGE;
55 spi_init.spi_nss_mode = SPI_NSS_HARD;
56 spi_init.spi_baud_rate_prescaler = static_cast<spi_baud_rate_precaler_t>(prescaler);
57 spi_init.spi_first_bit = SPI_FIRST_BIT_MSB;
58 hal_spi_init(SPI0_BASE, &spi_init);
59 hal_spi_en(SPI0_BASE, HAL_ENABLE);
60 hal_spi_ssoe_en(SPI0_BASE, HAL_DISABLE);
61
62 dma_init_t_def dma_init{};
63 dma_init.dma_mem_to_mem_en = DMA_MEM_TO_MEM_DIS;
64 dma_init.dma_pri_lev = DMA_PRI_LEV_HIGH;
65 dma_init.dma_mem_size = DMA_MEM_SIZE_8_BIT;
66 dma_init.dma_p_size = DMA_P_SIZE_8_BIT;
67 dma_init.dma_mem_inc_en = DMA_MEM_INC_EN;
68 dma_init.dma_p_inc_en = DMA_P_INC_DIS;
69 dma_init.dma_circ_mode_en = DMA_CIRC_MODE_DIS;
70 dma_init.dma_dir = DMA_READ_FORM_MEM;
71 dma_init.dma_p_addr = SPI0_DATA_REG;
72 hal_dma_init(LED_STRIP_DMA_CH, &dma_init);
73 hal_dma_en(LED_STRIP_DMA_CH, HAL_DISABLE);
74}
75
76static void spi_dma_transmit(const uint8_t *data, uint16_t len) {
77 hal_dma_en(LED_STRIP_DMA_CH, HAL_DISABLE);
78 hal_spi_dma_en(SPI0_BASE, SPI_DMA_TX_EN, HAL_DISABLE);
79 hal_dma_set_mem_addr(LED_STRIP_DMA_CH, reinterpret_cast<uint32_t>(data));
80 hal_dma_set_data_num(LED_STRIP_DMA_CH, len);
81 hal_spi_dma_en(SPI0_BASE, SPI_DMA_TX_EN, HAL_ENABLE);
82 hal_dma_en(LED_STRIP_DMA_CH, HAL_ENABLE);
83}
84
85// The count reaches zero once the last byte is in the SPI data register. The tail of every
86// transfer is the reset gap, so starting the next one while that byte shifts out is harmless.
87static bool spi_dma_busy() { return hal_dma_get_data_num(LED_STRIP_DMA_CH) != 0; }
88
90 light::ChannelColors channel_colors, uint8_t prescaler,
91 uint8_t bit0_high_bits, uint8_t bit1_high_bits,
92 uint16_t reset_bytes)
93 : color_data_bytes_(num_leds * channel_colors.bytes_per_led()),
94 // The tail of the DMA buffer stays zero: it is the reset gap.
95 dma_data_bytes_(color_data_bytes_ * SPI_BITS_PER_LED_BIT + reset_bytes),
96 channel_colors_(channel_colors),
97 num_leds_(num_leds),
98 pin_(pin),
99 prescaler_(prescaler),
100 bit0_high_bits_(bit0_high_bits),
101 bit1_high_bits_(bit1_high_bits) {}
102
104 const uint32_t apb0_hz = hal_clock_get_apb0_clk();
105 if (apb0_hz != APB0_HZ) {
106 ESP_LOGE(TAG, "APB0 clock is %" PRIu32 " Hz", apb0_hz);
107 this->mark_failed(LOG_STR("APB0 clock is not 40 MHz"));
108 return;
109 }
110
111 // One block for the color, effect and DMA data.
112 RAMAllocator<uint8_t> allocator;
113 uint8_t *buffer = allocator.allocate(this->color_data_bytes_ + this->num_leds_ + this->dma_data_bytes_);
114 if (buffer == nullptr) {
115 this->mark_failed(LOG_STR("Allocation error"));
116 return;
117 }
118 memset(buffer, 0, this->color_data_bytes_ + this->num_leds_ + this->dma_data_bytes_);
119 this->color_data_ = buffer;
120 this->effect_data_ = buffer + this->color_data_bytes_;
121 this->dma_data_ = this->effect_data_ + this->num_leds_;
122
123 spi_dma_init(this->pin_, this->prescaler_);
124}
125
127 const uint32_t spi_bit_ns = (1000000000 / APB0_HZ) << this->prescaler_;
128 char channel_colors[5];
129 ESP_LOGCONFIG(TAG,
130 "LN882x SPI LED Strip:\n"
131 " Pin: %u\n"
132 " Channel colors: %s\n"
133 " Number of LEDs: %" PRIu16 "\n"
134 " Max refresh rate: %" PRIu32 " us\n"
135 " SPI frequency: %" PRIu32 " Hz\n"
136 " Bit 0 / bit 1 high: %" PRIu32 " / %" PRIu32 " ns of %" PRIu32 " ns",
137 this->pin_, this->channel_colors_.to_string(channel_colors), this->num_leds_, this->max_refresh_rate_,
138 APB0_HZ >> this->prescaler_, this->bit0_high_bits_ * spi_bit_ns, this->bit1_high_bits_ * spi_bit_ns,
139 SPI_BITS_PER_LED_BIT * spi_bit_ns);
140}
141
143
145 if (this->is_failed())
146 return;
147
148 const uint32_t now = micros();
149 if (this->throttle_show_(now, this->last_refresh_, this->max_refresh_rate_))
150 return;
151
152 if (spi_dma_busy()) {
153 // Busy sending the last frame, try again later.
154 this->schedule_show();
155 return;
156 }
157
158 this->last_refresh_ = now;
159 this->mark_shown_();
160
161 const uint8_t bit0 = spi_pattern(this->bit0_high_bits_);
162 const uint8_t bit1 = spi_pattern(this->bit1_high_bits_);
163 const uint8_t *color_data = this->color_data_;
164 uint8_t *dma_data = this->dma_data_;
165 for (size_t i = 0; i < this->color_data_bytes_; i++) {
166 const uint8_t color_byte = *(color_data++);
167 for (uint8_t mask = 0x80; mask != 0; mask >>= 1) {
168 *(dma_data++) = (color_byte & mask) ? bit1 : bit0;
169 }
170 }
171
172 spi_dma_transmit(this->dma_data_, this->dma_data_bytes_);
173}
174
175} // namespace esphome::ln882x_spi_led_strip
176
177#endif // USE_LN882X
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:274
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:2324
T * allocate(size_t n)
Definition helpers.h:2351
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
LN882xSPILEDStripLightOutput(uint8_t pin, uint16_t num_leds, light::ChannelColors channel_colors, uint8_t prescaler, uint8_t bit0_high_bits, uint8_t bit1_high_bits, uint16_t reset_bytes)
The SPI encoding comes from light.py: SPI clock APB0 / 2^prescaler, the number of high SPI bits for a...
Definition led_strip.cpp:89
void write_state(light::LightState *state) override
bool state
Definition fan.h:2
uint32_t buffer
Definition gif.cpp:50
ESPHOME_LOG_TAG(TAG, "ln882x_spi_led_strip")
constexpr float HARDWARE
For components that deal with hardware and are very important like GPIO switch.
Definition component.h:43
const void size_t len
Definition hal.h:68
uint32_t IRAM_ATTR HOT micros()
Definition hal.cpp:43
static void uint32_t
Which byte of an addressable LED's data carries each colour.
const char * to_string(char *buf) const
Write the order back out as text, e.g.