ESPHome 2026.4.0-dev
Loading...
Searching...
No Matches
hub75.cpp
Go to the documentation of this file.
1#include "hub75_component.h"
3
4#include <cinttypes>
5
6#ifdef USE_ESP32
7
8namespace esphome::hub75 {
9
10static const char *const TAG = "hub75";
11
12// ========================================
13// Constructor
14// ========================================
15
16HUB75Display::HUB75Display(const Hub75Config &config) : config_(config) {
17 // Initialize runtime state from config
18 this->brightness_ = config.brightness;
19 this->enabled_ = (config.brightness > 0);
20}
21
22// ========================================
23// Core Component methods
24// ========================================
25
27 ESP_LOGCONFIG(TAG, "Setting up HUB75Display...");
28
29 // Create driver with pre-configured config
30 driver_ = new Hub75Driver(config_);
31 if (!driver_->begin()) {
32 ESP_LOGE(TAG, "Failed to initialize HUB75 driver!");
33 return;
34 }
35
36 this->enabled_ = true;
37}
38
40 LOG_DISPLAY("", "HUB75", this);
41
42 ESP_LOGCONFIG(TAG,
43 " Panel: %dx%d pixels\n"
44 " Layout: %dx%d panels\n"
45 " Virtual Display: %dx%d pixels",
46 config_.panel_width, config_.panel_height, config_.layout_cols, config_.layout_rows,
47 config_.panel_width * config_.layout_cols, config_.panel_height * config_.layout_rows);
48
49 ESP_LOGCONFIG(TAG,
50 " Scan Wiring: %d\n"
51 " Shift Driver: %d",
52 static_cast<int>(config_.scan_wiring), static_cast<int>(config_.shift_driver));
53
54 ESP_LOGCONFIG(TAG,
55 " Pins: R1:%i, G1:%i, B1:%i, R2:%i, G2:%i, B2:%i\n"
56 " Pins: A:%i, B:%i, C:%i, D:%i, E:%i\n"
57 " Pins: LAT:%i, OE:%i, CLK:%i",
58 config_.pins.r1, config_.pins.g1, config_.pins.b1, config_.pins.r2, config_.pins.g2, config_.pins.b2,
59 config_.pins.a, config_.pins.b, config_.pins.c, config_.pins.d, config_.pins.e, config_.pins.lat,
60 config_.pins.oe, config_.pins.clk);
61
62 ESP_LOGCONFIG(TAG,
63 " Clock Speed: %" PRIu32 " MHz\n"
64 " Latch Blanking: %i\n"
65 " Clock Phase: %s\n"
66 " Min Refresh Rate: %i Hz\n"
67 " Bit Depth: %i\n"
68 " Double Buffer: %s",
69 static_cast<uint32_t>(config_.output_clock_speed) / 1000000, config_.latch_blanking,
70 TRUEFALSE(config_.clk_phase_inverted), config_.min_refresh_rate, HUB75_BIT_DEPTH,
71 YESNO(config_.double_buffer));
72}
73
74// ========================================
75// Display/PollingComponent methods
76// ========================================
77
79 if (!driver_) [[unlikely]]
80 return;
81 if (!this->enabled_) [[unlikely]]
82 return;
83
84 this->do_update_();
85
86 if (config_.double_buffer) {
87 driver_->flip_buffer();
88 }
89}
90
92 if (!driver_) [[unlikely]]
93 return;
94 if (!this->enabled_) [[unlikely]]
95 return;
96
97 // Start with full display rect
98 display::Rect fill_rect(0, 0, this->get_width_internal(), this->get_height_internal());
99
100 // Apply clipping using Rect::shrink() to intersect
102 if (clip.is_set()) {
103 fill_rect.shrink(clip);
104 if (!fill_rect.is_set())
105 return; // Completely clipped
106 }
107
108 // Fast path: black filling entire display
109 if (!color.is_on() && fill_rect.x == 0 && fill_rect.y == 0 && fill_rect.w == this->get_width_internal() &&
110 fill_rect.h == this->get_height_internal()) {
111 driver_->clear();
112 return;
113 }
114
115 driver_->fill(fill_rect.x, fill_rect.y, fill_rect.w, fill_rect.h, color.r, color.g, color.b);
116}
117
118void HOT HUB75Display::draw_pixel_at(int x, int y, Color color) {
119 if (!driver_) [[unlikely]]
120 return;
121 if (!this->enabled_) [[unlikely]]
122 return;
123
124 if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) [[unlikely]]
125 return;
126
127 if (!this->get_clipping().inside(x, y))
128 return;
129
130 driver_->set_pixel(x, y, color.r, color.g, color.b);
131 App.feed_wdt();
132}
133
134void HOT HUB75Display::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order,
135 ColorBitness bitness, bool big_endian, int x_offset, int y_offset, 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
147 if (bitness == ColorBitness::COLOR_BITNESS_565) {
148 format = Hub75PixelFormat::RGB565;
149 bytes_per_pixel = 2;
150 } else if (bitness == ColorBitness::COLOR_BITNESS_888) {
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 // Map ESPHome ColorOrder to Hub75ColorOrder
158 // ESPHome ColorOrder is typically BGR for little-endian 32-bit
159 color_order = (order == ColorOrder::COLOR_ORDER_RGB) ? Hub75ColorOrder::RGB : Hub75ColorOrder::BGR;
160#elif LV_COLOR_DEPTH == 24
161 // 24-bit: 3 bytes per pixel, tightly packed
162 format = Hub75PixelFormat::RGB888;
163 bytes_per_pixel = 3;
164 // Note: 24-bit is always RGB order in LVGL
165#else
166 ESP_LOGE(TAG, "Unsupported LV_COLOR_DEPTH: %d", LV_COLOR_DEPTH);
167 return;
168#endif
169#else
170 // Non-LVGL mode: standard 24-bit RGB888
171 format = Hub75PixelFormat::RGB888;
172 bytes_per_pixel = 3;
173 color_order = (order == ColorOrder::COLOR_ORDER_RGB) ? Hub75ColorOrder::RGB : 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
206} // namespace esphome::hub75
207
208#endif
uint8_t h
Definition bl0906.h:2
void feed_wdt(uint32_t time=0)
bool clip(int x, int y)
Check if pixel is within region of display.
Definition display.cpp:776
Rect get_clipping() const
Get the current the clipping rectangle.
Definition display.cpp:766
int16_t x
X coordinate of corner.
Definition rect.h:12
int16_t h
Height of region.
Definition rect.h:15
int16_t y
Y coordinate of corner.
Definition rect.h:13
void shrink(Rect rect)
Definition rect.cpp:42
bool is_set() const ESPHOME_ALWAYS_INLINE
Y coordinate of corner.
Definition rect.h:22
int16_t w
Width of region.
Definition rect.h:14
void update() override
Definition hub75.cpp:78
void draw_pixel_at(int x, int y, Color color) override
Definition hub75.cpp:118
HUB75Display(const Hub75Config &config)
Definition hub75.cpp:16
void set_brightness(uint8_t brightness)
Definition hub75.cpp:198
void setup() override
Definition hub75.cpp:26
void dump_config() override
Definition hub75.cpp:39
void fill(Color color) override
Definition hub75.cpp:91
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 hub75.cpp:134
const char int const __FlashStringHelper * format
Definition log.h:74
Application App
Global storage of Application pointer - only one Application can exist.
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