TinyGPU
Loading...
Searching...
No Matches
DisplayDriverQSPI.h
Go to the documentation of this file.
1#pragma once
2#include <stdint.h>
3#include <string.h>
4
5#include "TinyGPU/Drivers/DisplayDriver.h"
6#include "TinyGPU/Abstractions/IQSPIBus.h"
7#include "TinyGPU/Abstractions/QSPIBusBitBang.h"
8#include "TinyGPU/Emulation.h"
9
10#if defined(ESP32)
11#include "TinyGPU/Abstractions/QSPIBusESP32.h"
12#endif
13
14namespace tinygpu {
15
16/**
17 * @brief Base driver for QSPI ("4-wire quad SPI") TFT panels: 1 clock, 1
18 * chip-select, 4 data lines, no separate D/C pin. Used by a family of
19 * compact panel controllers (NV3041A, ST77916, CO5300, SH8601,
20 * AXS15231B, ...) that all speak a de-facto-standardized QSPI wire
21 * protocol - see IQSPIBus.h for the exact 32-bit command-frame/data
22 * layout.
23 *
24 * The wire protocol is handled by an IQSPIBus (dependency-injected, see
25 * the two constructors below), not by this class - this class only turns
26 * ISurface writes into writeColor() calls and exposes writeCommand() to
27 * subclasses for their chip-specific init sequence, the same division of
28 * labor DisplayDriverSPI's setupPinsAndReset() uses for its SPI-panel
29 * subclasses. That split is what makes this family portable: adding a
30 * platform means implementing IQSPIBus once (see QSPIBusESP32.h,
31 * hardware/DMA-backed; QSPIBusBitBang.h, portable software fallback for
32 * RP2040/STM32/anything else), while every panel subclass (NV3041ADriver,
33 * ...) keeps working unchanged on every platform that has a bus.
34 */
35template <typename RGB_T = RGB565>
36class DisplayDriverQSPI : public DisplayDriver<RGB_T> {
37 public:
38 /// Pin-based constructor: builds and owns the platform's default
39 /// IQSPIBus (QSPIBusESP32 on ESP32, QSPIBusBitBang everywhere else).
40 /// pclkHz only affects the ESP32 backend - QSPIBusBitBang runs as fast
41 /// as digitalWrite() allows, uncapped.
42 DisplayDriverQSPI(int8_t cs, int8_t sclk, int8_t d0, int8_t d1, int8_t d2,
43 int8_t d3, size_t width, size_t height,
44 uint32_t pclkHz = 40000000
45#if defined(ESP32)
46 ,
48#endif
49 )
51#if defined(ESP32)
52 ownedBus_ = new QSPIBusESP32(cs, sclk, d0, d1, d2, d3,
53 width * height * sizeof(RGB_T), pclkHz, host);
54#else
55 (void)pclkHz;
56 ownedBus_ = new QSPIBusBitBang(cs, sclk, d0, d1, d2, d3);
57#endif
58 bus_ = ownedBus_;
59 }
60
61 /// Bus-injection constructor: use any IQSPIBus implementation (e.g. a
62 /// future PIO-accelerated RP2040 bus) without changing this class or
63 /// its panel subclasses. The caller keeps ownership of `bus`.
64 DisplayDriverQSPI(IQSPIBus& bus, size_t width, size_t height)
65 : bus_(&bus), width_(width), height_(height) {}
66
67 void end() override {
68 if (bus_ != nullptr) bus_->end();
69 }
70
71 ~DisplayDriverQSPI() override {
72 delete ownedBus_;
73 }
74
75 size_t width() const override { return width_; }
76 size_t height() const override { return height_; }
77
78 bool writeData(ISurface<RGB_T>& surface) override {
79 return writeData(surface, 0, 0);
80 }
81
82 bool writeData(ISurface<RGB_T>& surface, size_t x, size_t y) override {
83 static_assert(sizeof(RGB_T) == 2,
84 "writeData assumes a 16bpp RGB_T (RGB565) stored in "
85 "wire byte order");
86 if (bus_ == nullptr) return false;
87 if (!setAddressWindow(x, y, surface.width(), surface.height())) {
88 return false;
89 }
90
91 // RGB_T (RGB565) values are stored in the surface already
92 // byte-swapped to this panel's wire byte order (see RGB565.h), so no
93 // per-pixel swap is needed here - the surface's raw memory can go
94 // straight to the bus.
95 return bus_->writeColor(kCmdRamWrite, surface.data(), surface.size());
96 }
97
98 protected:
99 static constexpr uint8_t kCmdRamWrite = 0x2C;
100
101 /// Sets up the bus. Subclasses call this first thing in their begin(),
102 /// then send their chip's init register sequence via writeCommand()
103 /// before returning.
104 bool beginBus() { return bus_ != nullptr && bus_->begin(); }
105
106 bool writeCommand(uint8_t cmd, const uint8_t* param = nullptr,
107 size_t len = 0) {
108 return bus_ != nullptr && bus_->writeCommand(cmd, param, len);
109 }
110
111 bool setAddressWindow(size_t x, size_t y, size_t w, size_t h) override {
112 const uint8_t caset[4] = {
113 static_cast<uint8_t>(x >> 8), static_cast<uint8_t>(x & 0xFF),
114 static_cast<uint8_t>((x + w - 1) >> 8),
115 static_cast<uint8_t>((x + w - 1) & 0xFF)};
116 const uint8_t raset[4] = {
117 static_cast<uint8_t>(y >> 8), static_cast<uint8_t>(y & 0xFF),
118 static_cast<uint8_t>((y + h - 1) >> 8),
119 static_cast<uint8_t>((y + h - 1) & 0xFF)};
120 return writeCommand(0x2A, caset, 4) && writeCommand(0x2B, raset, 4);
121 }
122
123 IQSPIBus* bus_ = nullptr;
125
126 private:
127 IQSPIBus* ownedBus_ = nullptr;
128};
129
130/**
131 * @brief Driver for the New Vision NV3041A QSPI TFT controller (480x272
132 * panels on Guition/Sunton JC4827W543-class ESP32-S3 boards).
133 *
134 * The full vendor register-unlock/gate/source-timing/gamma init
135 * sequence, confirmed against real hardware (ported from
136 * moononournation/Arduino_GFX's Arduino_NV3041A driver - an earlier
137 * minimal sleep-out/pixel-format/MADCTL/display-on sequence produced a
138 * rotated color channel mapping: red rendered as blue, green as red,
139 * blue as green). Register 0x3A (pixel format) uses a chip-specific
140 * encoding on this part, not the standard MIPI-DCS one: 0x01 selects
141 * 16bpp RGB565 here, not the commonly-documented 0x55.
142 *
143 * Platform-independent: works over any IQSPIBus, so the same class
144 * covers ESP32 (QSPIBusESP32, hardware/DMA), RP2040 and STM32
145 * (QSPIBusBitBang, software) - see DisplayDriverQSPI's class comment.
146 * Only bring-up on ESP32 has been confirmed against real hardware; the
147 * RP2040/STM32 bit-bang path follows the same documented wire protocol
148 * but has not been run on real hardware.
149 */
150template <typename RGB_T = RGB565>
151class NV3041ADriver : public DisplayDriverQSPI<RGB_T> {
152 public:
153 using DisplayDriverQSPI<RGB_T>::beginBus;
154 using DisplayDriverQSPI<RGB_T>::writeCommand;
155
156 // 32MHz matches Arduino_GFX's Arduino_NV3041A driver, which documents
157 // it as this chip's maximum supported speed and runs clean at it on
158 // real hardware; this driver's original 40MHz default showed scattered
159 // pixel corruption on large transfers, consistent with the panel/
160 // wiring not reliably sustaining a faster clock. Only applies to the
161 // ESP32 hardware backend - QSPIBusBitBang ignores pclkHz.
162 NV3041ADriver(int8_t cs, int8_t sclk, int8_t d0, int8_t d1, int8_t d2,
163 int8_t d3, size_t width = 480, size_t height = 272,
164 uint32_t pclkHz = 32000000)
166 pclkHz) {}
167
168 /// Bus-injection constructor - see DisplayDriverQSPI's equivalent.
169 NV3041ADriver(IQSPIBus& bus, size_t width = 480, size_t height = 272)
171
172 bool begin() override {
173 static_assert(sizeof(RGB_T) == 2,
174 "NV3041ADriver assumes a 16bpp RGB_T (RGB565)");
175
176 if (!beginBus()) return false;
177
178 for (size_t i = 0; i < sizeof(kInitOps) / sizeof(kInitOps[0]); ++i) {
179 const uint8_t cmd = kInitOps[i][0];
180 const uint8_t data = kInitOps[i][1];
181 writeCommand(cmd, &data, 1);
182 if (cmd == 0x11) delay(120); // Sleep out
183 }
184 delay(100); // after display on (0x29, the last op in kInitOps)
185
186 // This panel is an IPS type: its default (uninverted) state and the
187 // vendor gamma table above only produce correct colors with display
188 // inversion turned ON - without this, colors come out wrong in a
189 // gamma-dependent, non-uniform way (not a clean photographic
190 // negative), which is why this was easy to miss.
191 writeCommand(0x21); // INVON
192
193 return true;
194 }
195
196 private:
197 // {command, data} pairs, in order. Ported verbatim from
198 // moononournation/Arduino_GFX's nv3041a_init_operations - register
199 // meanings beyond 0x3A/0x36/0x11/0x29 (gate/source timing, gamma,
200 // power) are vendor-specific and undocumented beyond that source.
201 static constexpr uint8_t kInitOps[][2] = {
202 {0xff, 0xa5}, // register unlock
203 {0x36, 0xc0}, // MADCTL
204 {0x3A, 0x01}, // pixel format: 01=565, 00=666 (chip-specific encoding)
205 {0x41, 0x03}, // 01=8bit, 03=16bit
206 {0x44, 0x15}, {0x45, 0x15}, // VBP/VFP
207 {0x7d, 0x03},
208 {0xc1, 0xbb}, {0xc2, 0x05}, {0xc3, 0x10},
209 {0xc6, 0x3e}, {0xc7, 0x25}, {0xc8, 0x11},
210 {0x7a, 0x5f}, {0x6f, 0x44}, {0x78, 0x70},
211 {0xc9, 0x00}, {0x67, 0x21},
212 {0x51, 0x0a}, {0x52, 0x76}, {0x53, 0x0a}, {0x54, 0x76}, // gate timing
213 {0x46, 0x0a}, {0x47, 0x2a}, {0x48, 0x0a}, {0x49, 0x1a}, // source timing
214 {0x56, 0x43}, {0x57, 0x42}, {0x58, 0x3c}, {0x59, 0x64},
215 {0x5a, 0x41}, {0x5b, 0x3c}, {0x5c, 0x02}, {0x5d, 0x3c},
216 {0x5e, 0x1f}, {0x60, 0x80}, {0x61, 0x3f}, {0x62, 0x21},
217 {0x63, 0x07}, {0x64, 0xe0}, {0x65, 0x02},
218 {0xca, 0x20}, {0xcb, 0x52}, {0xcc, 0x10}, {0xcD, 0x42},
219 {0xD0, 0x20}, {0xD1, 0x52}, {0xD2, 0x10}, {0xD3, 0x42},
220 {0xD4, 0x0a}, {0xD5, 0x32},
221 // gamma
222 {0x80, 0x00}, {0xA0, 0x00}, {0x81, 0x07}, {0xA1, 0x06},
223 {0x82, 0x02}, {0xA2, 0x01}, {0x86, 0x11}, {0xA6, 0x10},
224 {0x87, 0x27}, {0xA7, 0x27}, {0x83, 0x37}, {0xA3, 0x37},
225 {0x84, 0x35}, {0xA4, 0x35}, {0x85, 0x3f}, {0xA5, 0x3f},
226 {0x88, 0x0b}, {0xA8, 0x0b}, {0x89, 0x14}, {0xA9, 0x14},
227 {0x8a, 0x1a}, {0xAa, 0x1a}, {0x8b, 0x0a}, {0xAb, 0x0a},
228 {0x8c, 0x14}, {0xAc, 0x08}, {0x8d, 0x17}, {0xAd, 0x07},
229 {0x8e, 0x16}, {0xAe, 0x06}, {0x8f, 0x1B}, {0xAf, 0x07},
230 {0x90, 0x04}, {0xB0, 0x04}, {0x91, 0x0A}, {0xB1, 0x0A},
231 {0x92, 0x16}, {0xB2, 0x15},
232 {0xff, 0x00}, // close register bank
233 {0x11, 0x00}, // sleep out (triggers the 120ms delay above)
234 {0x29, 0x00}, // display on
235 };
236};
237
238} // namespace tinygpu
Base driver for QSPI ("4-wire quad SPI") TFT panels: 1 clock, 1 chip-select, 4 data lines,...
Definition: DisplayDriverQSPI.h:36
bool writeCommand(uint8_t cmd, const uint8_t *param=nullptr, size_t len=0)
Definition: DisplayDriverQSPI.h:106
~DisplayDriverQSPI() override
Definition: DisplayDriverQSPI.h:71
bool beginBus()
Definition: DisplayDriverQSPI.h:104
static constexpr uint8_t kCmdRamWrite
Definition: DisplayDriverQSPI.h:99
IQSPIBus * bus_
Definition: DisplayDriverQSPI.h:123
size_t width_
Definition: DisplayDriverQSPI.h:124
void end() override
Definition: DisplayDriverQSPI.h:67
size_t height_
Definition: DisplayDriverQSPI.h:124
size_t height() const override
Definition: DisplayDriverQSPI.h:76
DisplayDriverQSPI(int8_t cs, int8_t sclk, int8_t d0, int8_t d1, int8_t d2, int8_t d3, size_t width, size_t height, uint32_t pclkHz=40000000, spi_host_device_t host=SPI3_HOST)
Definition: DisplayDriverQSPI.h:42
DisplayDriverQSPI(IQSPIBus &bus, size_t width, size_t height)
Definition: DisplayDriverQSPI.h:64
bool writeData(ISurface< RGB_T > &surface, size_t x, size_t y) override
Definition: DisplayDriverQSPI.h:82
size_t width() const override
Definition: DisplayDriverQSPI.h:75
bool setAddressWindow(size_t x, size_t y, size_t w, size_t h) override
Definition: DisplayDriverQSPI.h:111
bool writeData(ISurface< RGB_T > &surface) override
Definition: DisplayDriverQSPI.h:78
Definition: DisplayDriver.h:49
Abstraction over the transport used by DisplayDriverQSPI (see TinyGPU/Drivers/DisplayDriverQSPI....
Definition: IQSPIBus.h:24
virtual void end()=0
virtual bool begin()=0
Sets up the bus (pins/peripheral). Returns false on failure.
Driver for the New Vision NV3041A QSPI TFT controller (480x272 panels on Guition/Sunton JC4827W543-cl...
Definition: DisplayDriverQSPI.h:151
NV3041ADriver(IQSPIBus &bus, size_t width=480, size_t height=272)
Bus-injection constructor - see DisplayDriverQSPI's equivalent.
Definition: DisplayDriverQSPI.h:169
NV3041ADriver(int8_t cs, int8_t sclk, int8_t d0, int8_t d1, int8_t d2, int8_t d3, size_t width=480, size_t height=272, uint32_t pclkHz=32000000)
Definition: DisplayDriverQSPI.h:162
bool begin() override
Definition: DisplayDriverQSPI.h:172
RGB color stored in 16-bit RGB565 format, byte-swapped from the conventional bit layout.
Definition: RGB565.h:24
Definition: DSIBusESP32.h:19
void delay(unsigned long ms)
Definition: EmulationDesktop.h:52