TinyGPU
Loading...
Searching...
No Matches
LVGLDriver.h
Go to the documentation of this file.
1
2#include <type_traits>
3
4#include "TinyGPU/Color/GammaTable.h"
5#include "TinyGPU.h"
6#include "TinyGPU/Input/TouchDriver.h"
7#include "TinyGPU/ThreeD/Vector.h"
8#include "lvgl.h"
9
10/**
11 * @brief LVGLDriver is a helper class to initialize LVGL v9 with a TinyGPU
12 * Output driver. It sets up the display buffer and flush callback for LVGL.
13 *
14 * @tparam RGB_T The pixel format LVGL's rendered output is converted to
15 * before transmission. Defaults to RGB565, matching LVGL's own internal
16 * RGB565 color format (convertBuffer() below still has to undo RGB565's
17 * own byte-swapped storage - see RGB565.h - even in that default case).
18 */
19template <typename RGB_T = RGB565>
21 static_assert(sizeof(RGB_T) == 2,
22 "LVGLDriver currently hardcodes LV_COLOR_FORMAT_RGB565 (a "
23 "16-bit format) for LVGL's own rendering, so RGB_T must also "
24 "be a 16-bit pixel type.");
25
26 public:
27 LVGLDriver(DisplayDriver<RGB_T>& driver, size_t x, size_t y,
28 size_t bufferSize = 0) {
29 this->driver = &driver;
30 this->disp_x = x;
31 this->disp_y = y;
32 // Default buffer size in bytes (v9 buffer size is expressed in bytes)
33 this->dispBufferSize =
34 bufferSize > 0 ? bufferSize : disp_x * sizeof(RGB_T);
35 }
36
37 // Initializes LVGL and registers the TinyGPU Output driver with LVGL.
38 bool begin() {
39 if (!driver) {
40 return false;
41 }
42
43 lv_init();
44 lv_tick_set_cb(my_tick);
45
46 driver->begin();
47
48 // Create display object in LVGL v9
49 disp = lv_display_create(disp_x, disp_y);
50 if (!disp) {
51 return false;
52 }
53 // This is LVGL's own internal rendering format, independent of RGB_T -
54 // see the note on display() below.
55 lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
56
57 // Allocate draw buffer (byte vector for raw pixel memory)
58 buf_1.resize(dispBufferSize);
59
60 // Register display buffer with LVGL v9
61 lv_display_set_buffers(disp, buf_1.data(), nullptr, dispBufferSize,
62 LV_DISPLAY_RENDER_MODE_PARTIAL);
63
64 // Set user data and flush callback. LVGLDriver<RGB_T>::display is a
65 // static, non-capturing member function, so its address is a plain
66 // function pointer LVGL's C API can call directly - no free-standing
67 // forwarding function needed.
68 lv_display_set_user_data(disp, this);
69 lv_display_set_flush_cb(disp, &LVGLDriver::display);
70
71 // setupTouch() registers an LVGL input device against `disp`, so it
72 // must run after disp exists - it silently does nothing otherwise.
73 if (touch_driver) {
74 // Calibration is only meaningful for controllers that report raw,
75 // uncalibrated ADC counts (e.g. resistive XPT2046 panels). Capacitive
76 // controllers (CST816S, FT6236, ...) already report coordinates in
77 // native screen-pixel space, so forcing a canned calibration profile
78 // here would corrupt their output. If your touch driver needs
79 // calibration, call touchDriver.setCalibration(...) yourself before
80 // passing it to setTouchDriver().
81 if (!touch_driver->hasCalibration()) {
82 Serial.println(
83 "LVGLDriver: touch driver has no calibration set. If it reports "
84 "raw ADC counts (e.g. a resistive XPT2046 panel) rather than "
85 "screen-pixel coordinates, touch input will be wrong until you "
86 "call touchDriver.setCalibration(...) before setTouchDriver().");
87 }
88 touch_driver->begin();
89 setupTouch();
90 }
91
92 return true;
93 }
94
95 void end() {
96 if (disp) {
97 lv_display_delete(disp);
98 disp = nullptr;
99 }
100 lv_tick_set_cb(nullptr);
101 lv_deinit();
102 buf_1.clear();
103 }
104
105 // Displays the given area of the screen with the provided color data.
106 static void display(lv_display_t* disp, const lv_area_t* area,
107 uint8_t* px_map) {
108 assert(disp != nullptr);
109 const size_t w = static_cast<size_t>(area->x2 - area->x1 + 1);
110 const size_t h = static_cast<size_t>(area->y2 - area->y1 + 1);
111
112 LVGLDriver* p_driver =
113 static_cast<LVGLDriver*>(lv_display_get_user_data(disp));
114
115 // px_map is packed by LVGL itself according to LV_COLOR_FORMAT_RGB565,
116 // which is LVGL's own fixed (native-order) bit layout - unrelated to
117 // RGB_T's own storage convention. Re-pack each pixel into RGB_T's
118 // layout (and apply gamma correction, if set) before handing the
119 // buffer to the driver.
120 convertBuffer(px_map, w * h);
121
122 // SurfaceWithExternalBuffer's default font argument is hardcoded to
123 // Font5x7<RGB565>, which doesn't match IFont<RGB_T>& for any other
124 // RGB_T, so an explicit RGB_T-typed font has to be passed here.
125 static Font5x7<RGB_T> font;
126 SurfaceWithExternalBuffer<RGB_T> surface(w, h, font);
127 surface.setExternalBuffer(px_map, w * h * sizeof(RGB_T));
128 surface.resizeBuffer(w, h);
129
130 p_driver->getDriver().writeData(surface, area->x1, area->y1);
131
132 lv_display_flush_ready(disp);
133 }
134
135 void delay(uint32_t ms) {
136 lv_tick_inc(ms);
137 // Unqualified delay(ms) here would resolve to this very member
138 // function (C++ name lookup stops at the first enclosing scope with a
139 // matching name, regardless of parameter types), recursing forever
140 // instead of calling Arduino's delay(). :: forces the global one.
141 ::delay(ms);
142 }
143
144 /// Defines the touch driver to be used with LVGL. This is optional and can be
145 /// set if a touch driver is available.
146 void setTouchDriver(TouchDriver& touch) { touch_driver = &touch; }
147
148 /// Checks if a touch driver has been set for this LVGLDriver.
149 bool hasTouchDriver() const { return touch_driver != nullptr; }
150
151 /// Provides a pointer to the touch driver, if one has been set. Returns
152 /// nullptr if no touch driver is available.
153 TouchDriver& touchDriver() { return *touch_driver; }
154
155 /// Provides a reference to the underlying DisplayDriver instance used by
156 /// this LVGLDriver (an SPI panel driver on ESP32, DisplayDriverSDL on
157 /// desktop, or any other DisplayDriver<RGB_T> implementation).
158 DisplayDriver<RGB_T>& getDriver() { return *driver; }
159
160 /// Applies independent per-channel gamma correction to every color LVGL
161 /// renders, before RGB_T's own field layout/compensation is applied.
162 /// gammaR=gammaG=gammaB=1.0 (the default) applies no correction.
163 ///
164 /// This is deliberately per-channel rather than one shared curve: some
165 /// panels show a visible color tint in near-black/grey tones even with
166 /// correct field compensation, because the panel's red/green/blue
167 /// subpixels have different non-linear responses at low brightness. A
168 /// single gamma applied equally to R, G and B can only ever change
169 /// overall brightness - if R=G=B going in, the same curve on all three
170 /// still gives R=G=B going out, so it mathematically cannot fix a hue
171 /// tint in grey tones. Independent per-channel curves can, by
172 /// deliberately un-balancing a grey input to counteract the panel's own
173 /// imbalance. There's no single correct set of values for an unknown/
174 /// undocumented panel - try values on both sides of 1.0 per channel and
175 /// compare on real hardware. See GammaTable.h.
176 static void setGamma(float gammaR, float gammaG, float gammaB) {
177 gammaR_().setGamma(gammaR);
178 gammaG_().setGamma(gammaG);
179 gammaB_().setGamma(gammaB);
180 }
181
182 protected:
183 Vector<uint8_t> buf_1;
186 lv_display_t* disp = nullptr;
190
191 // Use Arduino's millis() as tick source
192 static uint32_t my_tick(void) { return millis(); }
193
194 static GammaTable& gammaR_() {
195 static GammaTable table;
196 return table;
197 }
198 static GammaTable& gammaG_() {
199 static GammaTable table;
200 return table;
201 }
202 static GammaTable& gammaB_() {
203 static GammaTable table;
204 return table;
205 }
206
207 /// Converts a buffer LVGL packed as (native-order) RGB565 into RGB_T's
208 /// own byte layout, applying per-channel gamma correction (see
209 /// setGamma()), in place. Each pixel is decoded with RGB565's semantics
210 /// (what LVGL actually wrote), gamma corrected, and re-encoded via
211 /// RGB_T::fromRGB(), so RGB_T's own compensation (if any) is genuinely
212 /// applied regardless of how RGB_T's constructor happens to order its
213 /// arguments.
214 static void convertBuffer(uint8_t* px_map, size_t pixelCount) {
215 const bool sameFormat = std::is_same<RGB_T, RGB565>::value;
216 const bool needsGamma = (gammaR_().gamma() != 1.0f) ||
217 (gammaG_().gamma() != 1.0f) ||
218 (gammaB_().gamma() != 1.0f);
219 uint16_t* pixels = reinterpret_cast<uint16_t*>(px_map);
220 if (sameFormat && !needsGamma) {
221 // No channel repacking or gamma to apply, but RGB565 stores bytes
222 // swapped from LVGL's native order (see RGB565.h) - a raw swap is
223 // still needed.
224 for (size_t i = 0; i < pixelCount; ++i) {
225 pixels[i] = RGB565::swapBytes(pixels[i]);
226 }
227 return;
228 }
229
230 const GammaTable& gr = gammaR_();
231 const GammaTable& gg = gammaG_();
232 const GammaTable& gb = gammaB_();
233 for (size_t i = 0; i < pixelCount; ++i) {
234 // pixels[i] is LVGL's native-order pixel; byte-swap it into
235 // RGB565's stored (wire) order before decoding.
236 const RGB565 src(RGB565::swapBytes(pixels[i]));
237 uint8_t r = src.getRed();
238 uint8_t g = src.getGreen();
239 uint8_t b = src.getBlue();
240 if (needsGamma) {
241 r = gr.apply(r);
242 g = gg.apply(g);
243 b = gb.apply(b);
244 }
245 const RGB_T dst = RGB_T::fromRGB(r, g, b);
246 pixels[i] = dst.getValue();
247 }
248 }
249
250 /**
251 * @brief Registers a touch driver with this LVGL display instance.
252 */
254 if (!disp) return nullptr;
255
256 lv_indev_t* indev = lv_indev_create();
257 if (!indev) return nullptr;
258
259 lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
260 lv_indev_set_display(indev, disp); // Binds touch to this specific display
261 lv_indev_set_user_data(indev, &touchDriver());
262
263 // Set static touch read callback. isTouched() must be called before
264 // getPoint() - per TouchDriver's documented contract (see
265 // TouchDriver.h), isTouched() is where a controller read/event-pump
266 // happens for drivers that need one (e.g. TouchDriverSDL polls SDL's
267 // event queue there to update its mouse state; getPoint() alone would
268 // never see a fresh position). Every other TouchDriver consumer in
269 // TinyGPU (e.g. FrameBuffer::processTouch()) follows this same
270 // isTouched()-then-getPoint() order.
271 lv_indev_set_read_cb(indev, [](lv_indev_t* indev, lv_indev_data_t* data) {
272 auto* touch =
273 static_cast<TouchDriver*>(lv_indev_get_user_data(indev));
274 Point p;
275 if (touch && touch->isTouched() && touch->getPoint(p)) {
276 data->point.x = p.x;
277 data->point.y = p.y;
278 data->state = LV_INDEV_STATE_PRESSED;
279 } else {
280 data->state = LV_INDEV_STATE_RELEASED;
281 }
282 });
283
284 return indev;
285 }
286
287 void dumpBuffer(const uint8_t* data, size_t len) {
288 char lineHeader[10];
289 char byteStr[4];
290 for (size_t i = 0; i < len; ++i) {
291 if ((i % 16) == 0) {
292 snprintf(lineHeader, sizeof(lineHeader), "\n%06u: ", (unsigned)i);
293 Serial.print(lineHeader);
294 }
295 snprintf(byteStr, sizeof(byteStr), "%02X ", data[i]);
296 Serial.print(byteStr);
297 }
298
299 Serial.println();
300 }
301};
LVGLDriver is a helper class to initialize LVGL v9 with a TinyGPU Output driver. It sets up the displ...
Definition: LVGLDriver.h:20
static GammaTable & gammaG_()
Definition: LVGLDriver.h:198
TouchDriver * touch_driver
Definition: LVGLDriver.h:185
void dumpBuffer(const uint8_t *data, size_t len)
Definition: LVGLDriver.h:287
DisplayDriver< RGB_T > & getDriver()
Definition: LVGLDriver.h:158
size_t disp_y
Definition: LVGLDriver.h:188
DisplayDriver< RGB_T > * driver
Definition: LVGLDriver.h:184
static void convertBuffer(uint8_t *px_map, size_t pixelCount)
Definition: LVGLDriver.h:214
static void setGamma(float gammaR, float gammaG, float gammaB)
Definition: LVGLDriver.h:176
static GammaTable & gammaB_()
Definition: LVGLDriver.h:202
bool begin()
Definition: LVGLDriver.h:38
void setTouchDriver(TouchDriver &touch)
Definition: LVGLDriver.h:146
bool hasTouchDriver() const
Checks if a touch driver has been set for this LVGLDriver.
Definition: LVGLDriver.h:149
lv_display_t * disp
Definition: LVGLDriver.h:186
static GammaTable & gammaR_()
Definition: LVGLDriver.h:194
size_t dispBufferSize
Definition: LVGLDriver.h:189
void delay(uint32_t ms)
Definition: LVGLDriver.h:135
size_t disp_x
Definition: LVGLDriver.h:187
void end()
Definition: LVGLDriver.h:95
TouchDriver & touchDriver()
Definition: LVGLDriver.h:153
lv_indev_t * setupTouch()
Registers a touch driver with this LVGL display instance.
Definition: LVGLDriver.h:253
static void display(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
Definition: LVGLDriver.h:106
static uint32_t my_tick(void)
Definition: LVGLDriver.h:192