ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
hub75.cpp
Go to the documentation of this file.
1#include "hub75_component.h"
2
3#include <cinttypes>
4
5#ifdef USE_ESP32
6
7namespace esphome::hub75 {
8
9ESPHOME_LOG_TAG(TAG, "hub75");
10
11// ========================================
12// Constructor
13// ========================================
14
15HUB75Display::HUB75Display(const Hub75Config &config) : config_(config) {
16 // Initialize runtime state from config
17 this->brightness_ = config.brightness;
18 this->enabled_ = (config.brightness > 0);
19}
20
21// ========================================
22// Core Component methods
23// ========================================
24
26 ESP_LOGCONFIG(TAG, "Setting up HUB75Display...");
27
28 // Create driver with pre-configured config
29 driver_ = new Hub75Driver(config_);
30 if (!driver_->begin()) {
31 ESP_LOGE(TAG, "Failed to initialize HUB75 driver!");
32 return;
33 }
34
35 this->enabled_ = true;
36}
37
39 LOG_DISPLAY("", "HUB75", this);
40
41 ESP_LOGCONFIG(TAG,
42 " Panel: %dx%d pixels\n"
43 " Layout: %dx%d panels\n"
44 " Virtual Display: %dx%d pixels",
45 config_.panel_width, config_.panel_height, config_.layout_cols, config_.layout_rows,
46 config_.panel_width * config_.layout_cols, config_.panel_height * config_.layout_rows);
47
48 ESP_LOGCONFIG(TAG,
49 " Scan Wiring: %d\n"
50 " Shift Driver: %d",
51 static_cast<int>(config_.scan_wiring), static_cast<int>(config_.shift_driver));
52
53 ESP_LOGCONFIG(TAG,
54 " Pins: R1:%i, G1:%i, B1:%i, R2:%i, G2:%i, B2:%i\n"
55 " Pins: A:%i, B:%i, C:%i, D:%i, E:%i\n"
56 " Pins: LAT:%i, OE:%i, CLK:%i",
57 config_.pins.r1, config_.pins.g1, config_.pins.b1, config_.pins.r2, config_.pins.g2, config_.pins.b2,
58 config_.pins.a, config_.pins.b, config_.pins.c, config_.pins.d, config_.pins.e, config_.pins.lat,
59 config_.pins.oe, config_.pins.clk);
60
61 ESP_LOGCONFIG(TAG,
62 " Clock Speed: %" PRIu32 " MHz\n"
63 " Latch Blanking: %i\n"
64 " Clock Phase: %s\n"
65 " Min Refresh Rate: %i Hz\n"
66 " Bit Depth: %i\n"
67 " Double Buffer: %s",
68 static_cast<uint32_t>(config_.output_clock_speed) / 1000000, config_.latch_blanking,
69 TRUEFALSE(config_.clk_phase_inverted), config_.min_refresh_rate, HUB75_BIT_DEPTH,
70 YESNO(config_.double_buffer));
71}
72
73// ========================================
74// Display/PollingComponent methods
75// ========================================
76
78 if (!driver_) [[unlikely]]
79 return;
80 if (!this->enabled_) [[unlikely]]
81 return;
82
83 this->do_update_();
84
85 if (config_.double_buffer) {
86 driver_->flip_buffer();
87 }
88}
89
91 if (!driver_) [[unlikely]]
92 return;
93 if (!this->enabled_) [[unlikely]]
94 return;
95
96 // Start with full display rect
97 display::Rect fill_rect(0, 0, this->get_width_internal(), this->get_height_internal());
98
99 // Apply clipping using Rect::shrink() to intersect
101 if (clip.is_set()) {
102 fill_rect.shrink(clip);
103 if (!fill_rect.is_set())
104 return; // Completely clipped
105 }
106
107 // Fast path: black filling entire display
108 if (!color.is_on() && fill_rect.x == 0 && fill_rect.y == 0 && fill_rect.w == this->get_width_internal() &&
109 fill_rect.h == this->get_height_internal()) {
110 driver_->clear();
111 return;
112 }
113
114 driver_->fill(fill_rect.x, fill_rect.y, fill_rect.w, fill_rect.h, color.r, color.g, color.b);
115}
116
117void HOT HUB75Display::draw_pixel_at(int x, int y, Color color) {
118 if (!driver_) [[unlikely]]
119 return;
120 if (!this->enabled_) [[unlikely]]
121 return;
122
123 if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) [[unlikely]]
124 return;
125
126 if (this->is_point_clipped(x, y))
127 return;
128
129 driver_->set_pixel(x, y, color.r, color.g, color.b);
130 this->feed_wdt_per_pixel_();
131}
132
133void HOT HUB75Display::draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr,
134 ColorBitness bitness, bool big_endian, int x_offset, int y_offset,
135 int x_pad) {
136 if (!driver_) [[unlikely]]
137 return;
138 if (!this->enabled_) [[unlikely]]
139 return;
140
141 // Map ESPHome enums to hub75 enums
142 Hub75PixelFormat format;
143 Hub75ColorOrder color_order = Hub75ColorOrder::RGB;
144 int bytes_per_pixel;
145
146 // Determine format based on bitness
148 format = Hub75PixelFormat::RGB565;
149 bytes_per_pixel = 2;
151#ifdef USE_LVGL
152#if LV_COLOR_DEPTH == 32
153 // 32-bit: 4 bytes per pixel with padding byte (LVGL mode)
154 format = Hub75PixelFormat::RGB888_32;
155 bytes_per_pixel = 4;
156
157 color_order = Hub75ColorOrder::RGB;
158#elif LV_COLOR_DEPTH == 24
159 // 24-bit: 3 bytes per pixel, tightly packed
160 format = Hub75PixelFormat::RGB888;
161 bytes_per_pixel = 3;
162 // 888 is b,g,r in memory, which hub75 calls BGR
163 color_order = Hub75ColorOrder::BGR;
164#else
165 ESP_LOGE(TAG, "Unsupported LV_COLOR_DEPTH: %d", LV_COLOR_DEPTH);
166 return;
167#endif
168#else
169 // Non-LVGL mode: standard 24-bit RGB888
170 format = Hub75PixelFormat::RGB888;
171 bytes_per_pixel = 3;
172 // 888 is b,g,r in memory, which hub75 calls BGR
173 color_order = Hub75ColorOrder::BGR;
174#endif
175 } else {
176 ESP_LOGE(TAG, "Unsupported bitness: %d", static_cast<int>(bitness));
177 return;
178 }
179
180 // Check if buffer is tightly packed (no stride)
181 const int stride_px = x_offset + w + x_pad;
182 const bool is_packed = (x_offset == 0 && x_pad == 0 && y_offset == 0);
183
184 if (is_packed) {
185 // Tightly packed buffer - single bulk call for best performance
186 driver_->draw_pixels(x_start, y_start, w, h, ptr, format, color_order, big_endian);
187 } else {
188 // Buffer has stride (padding between rows) - draw row by row
189 for (int yy = 0; yy < h; ++yy) {
190 const size_t row_offset = ((y_offset + yy) * stride_px + x_offset) * bytes_per_pixel;
191 const uint8_t *row_ptr = ptr + row_offset;
192
193 driver_->draw_pixels(x_start, y_start + yy, w, 1, row_ptr, format, color_order, big_endian);
194 }
195 }
196}
197
198void HUB75Display::set_brightness(uint8_t brightness) {
199 this->brightness_ = brightness;
200 this->enabled_ = (brightness > 0);
201 if (this->driver_ != nullptr) {
202 this->driver_->set_brightness(brightness);
203 }
204}
205
206static Hub75Rotation to_hub75_rotation(display::DisplayRotation rotation) {
207 switch (rotation) {
209 return Hub75Rotation::ROTATE_90;
211 return Hub75Rotation::ROTATE_180;
213 return Hub75Rotation::ROTATE_270;
214 default:
215 return Hub75Rotation::ROTATE_0;
216 }
217}
218
219static display::DisplayRotation to_display_rotation(Hub75Rotation rotation) {
220 switch (rotation) {
221 case Hub75Rotation::ROTATE_90:
223 case Hub75Rotation::ROTATE_180:
225 case Hub75Rotation::ROTATE_270:
227 default:
229 }
230}
231
232// Before setup() creates the driver the configuration holds the rotation, afterwards the driver does
234 const Hub75Rotation hub75_rotation = to_hub75_rotation(rotation);
235 if (this->driver_ == nullptr) {
236 this->config_.rotation = hub75_rotation;
237 return;
238 }
239 this->driver_->set_rotation(hub75_rotation);
240}
241
243 return to_display_rotation(this->driver_ == nullptr ? this->config_.rotation : this->driver_->get_rotation());
244}
245
246} // namespace esphome::hub75
247
248#endif
uint8_t h
Definition bl0906.h:2
bool clip(int x, int y)
Check if pixel is within region of display.
Definition display.cpp:838
int int int const uint8_t ColorOrder ColorBitness bitness
Definition display.h:389
void ESPHOME_ALWAYS_INLINE feed_wdt_per_pixel_()
Watchdog feed for per pixel loops.
Definition display.h:842
int int int const uint8_t ColorOrder ColorBitness bool int int y_offset
Definition display.h:389
int int int const uint8_t ColorOrder ColorBitness bool int int int x_pad
Definition display.h:389
int int int const uint8_t * ptr
Definition display.h:388
bool ESPHOME_ALWAYS_INLINE is_point_clipped(int x, int y) const
Whether (x, y) falls outside the active clipping rectangle.
Definition display.h:807
int int int const uint8_t ColorOrder ColorBitness bool big_endian
Definition display.h:389
Rect get_clipping() const
Get the current the clipping rectangle.
Definition display.cpp:826
int int int const uint8_t ColorOrder ColorBitness bool int x_offset
Definition display.h:389
int16_t x
X coordinate of corner.
Definition rect.h:11
int16_t h
Height of region.
Definition rect.h:14
int16_t y
Y coordinate of corner.
Definition rect.h:12
void shrink(Rect rect)
Definition rect.cpp:41
bool is_set() const ESPHOME_ALWAYS_INLINE
Y coordinate of corner.
Definition rect.h:21
int16_t w
Width of region.
Definition rect.h:13
void update() override
Definition hub75.cpp:77
void draw_pixel_at(int x, int y, Color color) override
Definition hub75.cpp:117
HUB75Display(const Hub75Config &config)
Definition hub75.cpp:15
void set_brightness(uint8_t brightness)
Definition hub75.cpp:198
void setup() override
Definition hub75.cpp:25
display::DisplayRotation get_rotation() const override
Definition hub75.cpp:242
void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override
Definition hub75.cpp:133
void dump_config() override
Definition hub75.cpp:38
void fill(Color color) override
Definition hub75.cpp:90
void set_rotation(display::DisplayRotation rotation) override
Definition hub75.cpp:233
const char * format
@ DISPLAY_ROTATION_0_DEGREES
Definition display.h:136
@ DISPLAY_ROTATION_270_DEGREES
Definition display.h:139
@ DISPLAY_ROTATION_180_DEGREES
Definition display.h:138
@ DISPLAY_ROTATION_90_DEGREES
Definition display.h:137
ESPHOME_LOG_TAG(TAG, "hub75")
static void uint32_t
uint8_t g
Definition color.h:34
uint8_t b
Definition color.h:38
bool is_on() ESPHOME_ALWAYS_INLINE
Definition color.h:70
uint8_t r
Definition color.h:30
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6