ESPHome 2026.8.0-dev
Loading...
Searching...
No Matches
pixoo.cpp
Go to the documentation of this file.
1#include "pixoo.h"
2
3#include "esphome/core/log.h"
4
5#include <cmath>
6#include <cstring>
7#include <utility>
8
9namespace esphome::pixoo {
10
11static const char *const TAG = "pixoo";
12
13// Divoom LED-board packet protocol.
14static constexpr uint8_t PACKET_HEAD = 0xAA;
15static constexpr uint8_t PACKET_TAIL = 0xBB;
16static constexpr uint8_t CMD_DATA = 0x00;
17static constexpr uint8_t CMD_LIGHT = 0x01;
18static constexpr uint8_t CMD_UNUSED = 0x21;
19static constexpr uint8_t CMD_SET_RGB_IOUT = 0x22;
20static constexpr size_t PACKET_HEADER_LEN = 4; // head + len(2) + cmd
21static constexpr size_t PACKET_STATIC_LEN = 5; // header + tail
22static constexpr uint8_t DEFAULT_IOUT = 75; // per-channel LED current / white balance default
23
24// Pack a `0xAA len cmd data 0xBB` packet into buf; returns the packet length.
25static inline size_t build_packet(uint8_t *buf, uint8_t cmd, const uint8_t *data, uint16_t len) {
26 buf[0] = PACKET_HEAD;
27 buf[1] = static_cast<uint8_t>(len & 0xFF);
28 buf[2] = static_cast<uint8_t>((len >> 8) & 0xFF);
29 buf[3] = cmd;
30 if (data != nullptr && len > 0)
31 std::memcpy(buf + PACKET_HEADER_LEN, data, len);
32 buf[PACKET_HEADER_LEN + len] = PACKET_TAIL;
33 return len + PACKET_STATIC_LEN;
34}
35
36// Fill `total` bytes at buf with a single UNUSED padding packet.
37static inline void pad_unused(uint8_t *buf, size_t total) {
38 const uint16_t len = static_cast<uint16_t>(total - PACKET_STATIC_LEN);
39 buf[0] = PACKET_HEAD;
40 buf[1] = static_cast<uint8_t>(len & 0xFF);
41 buf[2] = static_cast<uint8_t>((len >> 8) & 0xFF);
42 buf[3] = CMD_UNUSED;
43 buf[total - 1] = PACKET_TAIL;
44}
45
47
49 const uint32_t num_pixels = static_cast<uint32_t>(this->model_) * this->model_;
50 this->data_size_ = num_pixels * 3;
51 // The frame is a DATA packet (header + RGB888 + tail) followed by a DMA-chunk-sized UNUSED
52 // packet, so the LED board completes its final DMA block.
53 this->frame_size_ = this->data_size_ + PACKET_STATIC_LEN + DMA_CHUNK;
54
55 if (!this->buffer_.init(this->data_size_)) {
56 this->mark_failed(LOG_STR("Failed to allocate draw buffer"));
57 return;
58 }
59
60 // The frame is shipped in one SPI transfer, so keep it in DMA-capable internal RAM.
62 this->frame_buffer_ = allocator.allocate(this->frame_size_);
63 if (this->frame_buffer_ == nullptr) {
64 this->buffer_.free();
65 this->mark_failed(LOG_STR("Failed to allocate frame buffer"));
66 return;
67 }
68 std::memset(this->frame_buffer_, 0, this->frame_size_);
69 // Pre-build the constant DATA-packet framing; only the RGB888 payload changes per frame.
70 this->frame_buffer_[0] = PACKET_HEAD;
71 this->frame_buffer_[1] = static_cast<uint8_t>(this->data_size_ & 0xFF);
72 this->frame_buffer_[2] = static_cast<uint8_t>((this->data_size_ >> 8) & 0xFF);
73 this->frame_buffer_[3] = CMD_DATA;
74 this->frame_buffer_[PACKET_HEADER_LEN + this->data_size_] = PACKET_TAIL;
75 pad_unused(this->frame_buffer_ + this->data_size_ + PACKET_STATIC_LEN, DMA_CHUNK);
76
77 this->spi_setup();
78
79 this->buffer_.fill(0x00);
80
81 // Set the per-channel LED current. Brightness is controlled separately via the light platform.
82 const uint8_t iout[3] = {DEFAULT_IOUT, DEFAULT_IOUT, DEFAULT_IOUT};
83 this->send_command_(CMD_SET_RGB_IOUT, iout, 3);
84
85 // Frames are pushed synchronously inside update(), so there is no loop() work to do and the
86 // component is idle between updates. Marking it done (LOOP_DONE) lets LVGL's
87 // update_when_display_idle option treat the panel as idle and drive frames on demand.
88 this->disable_loop();
89}
90
91void Pixoo::send_command_(uint8_t cmd, const uint8_t *data, uint16_t len) {
92 std::memset(this->cmd_buffer_, 0, DMA_CHUNK);
93 const size_t used = build_packet(this->cmd_buffer_, cmd, data, len);
94 if (DMA_CHUNK - used >= PACKET_STATIC_LEN)
95 pad_unused(this->cmd_buffer_ + used, DMA_CHUNK - used);
96 this->enable();
97 this->write_array(this->cmd_buffer_, DMA_CHUNK);
98 this->disable();
99}
100
101void Pixoo::set_panel_brightness(float brightness) {
102 const uint8_t pct = static_cast<uint8_t>(lroundf(clamp(brightness, 0.0f, 1.0f) * 100.0f));
103 this->send_command_(CMD_LIGHT, &pct, 1);
104}
105
107 this->do_update_();
108 for (size_t i = 0; i < this->data_size_; i++)
109 this->frame_buffer_[PACKET_HEADER_LEN + i] = this->buffer_[i];
110 this->enable();
111 this->write_array(this->frame_buffer_, this->frame_size_);
112 this->disable();
113}
114
115void Pixoo::set_pixel_(uint32_t index, Color color) {
116 const size_t off = static_cast<size_t>(index) * 3;
117 this->buffer_[off] = color.r;
118 this->buffer_[off + 1] = color.g;
119 this->buffer_[off + 2] = color.b;
120}
121
122void HOT Pixoo::draw_pixel_at(int x, int y, Color color) {
123 if (!this->get_clipping().inside(x, y))
124 return;
125 const int side = static_cast<int>(this->model_);
126 switch (this->rotation_) {
128 break;
130 std::swap(x, y);
131 x = side - x - 1;
132 break;
134 x = side - x - 1;
135 y = side - y - 1;
136 break;
138 std::swap(x, y);
139 y = side - y - 1;
140 break;
141 }
142 if (x < 0 || x >= side || y < 0 || y >= side)
143 return;
144 this->set_pixel_(static_cast<uint32_t>(y) * side + x, color);
145}
146
147void Pixoo::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
148 display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
149 // Fast path for the common LVGL/image blit: RGB565, RGB order, no rotation, no active clipping.
150 // Anything else defers to the base implementation, which decodes per pixel and routes through
151 // draw_pixel_at() so rotation, clipping and other color formats stay correct.
152 // NOTE: the stride/index math and 565->888 expansion below mirror Display::draw_pixels_at (the
153 // source of truth) -- keep them in sync if the base ever changes its source layout or decoding.
154 if (bitness != display::COLOR_BITNESS_565 || order != display::COLOR_ORDER_RGB ||
156 display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
157 x_pad);
158 return;
159 }
160 const int side = static_cast<int>(this->model_);
161 const size_t line_stride = static_cast<size_t>(x_offset) + w + x_pad;
162 for (int y = 0; y != h; y++) {
163 const int dst_y = y_start + y;
164 if (dst_y < 0 || dst_y >= side)
165 continue;
166 size_t source_idx = (static_cast<size_t>(y_offset) + y) * line_stride + x_offset;
167 for (int x = 0; x != w; x++, source_idx++) {
168 const int dst_x = x_start + x;
169 if (dst_x < 0 || dst_x >= side)
170 continue;
171 const size_t byte_idx = source_idx * 2;
172 const uint16_t rgb565 =
173 big_endian ? (ptr[byte_idx] << 8) | ptr[byte_idx + 1] : ptr[byte_idx] | (ptr[byte_idx + 1] << 8);
174 const uint8_t r5 = (rgb565 >> 11) & 0x1F;
175 const uint8_t g6 = (rgb565 >> 5) & 0x3F;
176 const uint8_t b5 = rgb565 & 0x1F;
177 this->set_pixel_(static_cast<uint32_t>(dst_y) * side + dst_x,
178 Color((r5 << 3) | (r5 >> 2), (g6 << 2) | (g6 >> 4), (b5 << 3) | (b5 >> 2)));
179 }
180 }
181}
182
183void Pixoo::fill(Color color) {
184 if (this->is_clipping()) {
186 return;
187 }
188 for (size_t i = 0; i < this->data_size_; i += 3) {
189 this->buffer_[i] = color.r;
190 this->buffer_[i + 1] = color.g;
191 this->buffer_[i + 2] = color.b;
192 }
193}
194
196 LOG_DISPLAY("", "Divoom Pixoo", this);
197 ESP_LOGCONFIG(TAG, " Model: %ux%u", (unsigned) this->model_, (unsigned) this->model_);
198 LOG_UPDATE_INTERVAL(this);
199}
200
201} // namespace esphome::pixoo
uint8_t h
Definition bl0906.h:2
void mark_failed()
Mark this component as failed.
void disable_loop()
Disable this component's loop.
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:2067
T * allocate(size_t n)
Definition helpers.h:2094
bool is_clipping() const
Definition display.h:759
virtual void fill(Color color)
Fill the entire screen with the given color.
Definition display.cpp:14
Rect get_clipping() const
Get the current the clipping rectangle.
Definition display.cpp:766
DisplayRotation rotation_
Definition display.h:789
virtual void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order, ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad)
Given an array of pixels encoded in the nominated format, draw these into the display's buffer.
Definition display.cpp:54
uint8_t cmd_buffer_[DMA_CHUNK]
Definition pixoo.h:61
void send_command_(uint8_t cmd, const uint8_t *data, uint16_t len)
Definition pixoo.cpp:91
void fill(Color color) override
Definition pixoo.cpp:183
void setup() override
Definition pixoo.cpp:48
split_buffer::SplitBuffer buffer_
Definition pixoo.h:59
PixooModel model_
Definition pixoo.h:54
void set_panel_brightness(float brightness)
Definition pixoo.cpp:101
void set_pixel_(uint32_t index, Color color)
Definition pixoo.cpp:115
void draw_pixel_at(int x, int y, Color color) override
Definition pixoo.cpp:122
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override
Definition pixoo.cpp:147
static constexpr size_t DMA_CHUNK
Definition pixoo.h:52
float get_setup_priority() const override
Definition pixoo.cpp:46
uint8_t * frame_buffer_
Definition pixoo.h:60
void update() override
Definition pixoo.cpp:106
void dump_config() override
Definition pixoo.cpp:195
bool init(size_t total_length)
void fill(uint8_t value) const
Fill the entire buffer with a single byte value.
@ DISPLAY_ROTATION_0_DEGREES
Definition display.h:134
@ DISPLAY_ROTATION_270_DEGREES
Definition display.h:137
@ DISPLAY_ROTATION_180_DEGREES
Definition display.h:136
@ DISPLAY_ROTATION_90_DEGREES
Definition display.h:135
constexpr float PROCESSOR
For components that use data from sensors like displays.
Definition component.h:47
const void size_t len
Definition hal.h:64
static void uint32_t
uint8_t g
Definition color.h:34
uint8_t b
Definition color.h:38
uint8_t r
Definition color.h:30
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6