TinyGPU
Loading...
Searching...
No Matches
DisplayDriverParallel8RP2040.h
Go to the documentation of this file.
1#pragma once
2#include <initializer_list>
3#include <stdint.h>
4#include <string.h>
5
6#include "TinyGPU/Drivers/DisplayDriver.h"
7#include "TinyGPU/Emulation.h"
8
9#if defined(ARDUINO_ARCH_RP2040) || defined(PICO_RP2040)
10#include "hardware/clocks.h"
11#include "hardware/dma.h"
12#include "hardware/gpio.h"
13#include "hardware/pio.h"
14#define TINYGPU_HAS_RP2040_PIO_PARALLEL8 1
15#else
16#error
17 "DisplayDriverParallel8RP2040.h needs the RP2040 PIO/DMA peripherals (pico-sdk hardware/pio.h, hardware/dma.h) - RP2040-only (arduino-pico core, or a plain pico-sdk build). For a portable, any-Arduino-core bit-banged 8-bit parallel driver see DisplayDriverParallel8.h; for ESP32's hardware LCD_CAM/PARLIO peripherals see DisplayDriverParallel8ESP32.h."
18#endif
19
20namespace tinygpu {
21
22#ifdef TINYGPU_HAS_RP2040_PIO_PARALLEL8
23
24// clang-format off
25//
26// PIO program (assembled with pico-sdk's pioasm from the source below -
27// this is its verbatim `-o hex` output, not hand-derived - see the
28// comment above each instruction for what it does):
29//
30// .program lcd8080_write
31// .side_set 1
32// .wrap_target
33// out pins, 8 side 0 ; drive the byte onto D0-D7, WR low
34// nop side 1 ; WR high - panel latches on this edge
35// .wrap
36//
37// Runs at 2 PIO clock cycles/byte. autopull (8-bit threshold, shift
38// right) means the state machine pulls a fresh byte from the TX FIFO by
39// itself whenever the OSR runs dry - the byte just needs to sit in the
40// low 8 bits of whatever 32-bit word reaches the FIFO (from
41// pio_sm_put_blocking() or a DMA_SIZE_8 transfer into txf[]).
42//
43// clang-format on
45 0x6008, // 0: out pins, 8 side 0
46 0xb042, // 1: nop side 1
47};
48
51 .length = 2,
52 .origin = -1,
53};
54
55/**
56 * @brief RP2040 PIO/DMA-accelerated 8-bit parallel ("Intel 8080"-style)
57 * TFT display driver. D0-D7 and WR are driven by one PIO state machine
58 * (see kLcd8080WriteProgram above) instead of bit-banged via
59 * digitalWrite() - see DisplayDriverParallel8.h for the portable
60 * bit-banged version this mirrors, and DisplayDriverParallel8ESP32.h for
61 * the equivalent ESP32 LCD_CAM/PARLIO-peripheral version. DC/CS/RST stay
62 * on plain GPIO (Arduino pinMode()/digitalWrite()), same as those two.
63 *
64 * Bulk pixel data (writeData()) is fed to the state machine via a DMA
65 * channel reading directly out of the surface's buffer - the CPU issues
66 * one DMA transfer and waits for it (and the state machine's own FIFO) to
67 * drain, rather than looping a pio_sm_put_blocking() call per byte.
68 *
69 * Hardware constraint (PIO's `out pins`/`side-set` addressing): D0-D7
70 * must be 8 *consecutive* GPIOs, D0 being the lowest-numbered - this
71 * isn't a software choice, it's how the PIO block maps its 32
72 * general-purpose output-mapped pins. WR must be a separate GPIO outside
73 * that D0-D7 range.
74 */
75template <typename RGB_T = RGB565>
77 public:
78 /// @param d0 the first (lowest-numbered) of 8 consecutive data-line
79 /// GPIOs - see the class comment's hardware constraint.
80 /// @param wr write-strobe pin - the PIO program's side-set pin.
81 /// @param dc data/command ("RS") select pin.
82 /// @param cs chip-select pin, or -1 if the panel has CS permanently
83 /// tied low.
84 /// @param rst reset pin, or -1 if not wired up.
85 /// @param width/height the panel's addressable resolution - subclasses
86 /// with a rotation concept (e.g. ILI9341Driver8080RP2040) override
87 /// width()/height() themselves instead and can ignore these.
88 /// @param clockDiv PIO state machine clock divider - the program runs
89 /// at 2 PIO-clock cycles/byte, so the WR toggle rate is
90 /// (clock_sys_hz / clockDiv) / 2. Default (8.0) gives roughly 7.8MHz at
91 /// a stock 125MHz system clock - comfortably inside most 8080 panels'
92 /// timing spec; lower it for a faster panel/wiring, raise it if data
93 /// looks corrupted (a symptom of the panel not keeping up).
94 /// @param pio which PIO block to use (pio0/pio1) - pick one not
95 /// already fully claimed by another peripheral (e.g. an RP2040 audio
96 /// driver using PIO for I2S).
98 int8_t cs = -1, int8_t rst = -1,
99 size_t width = 240, size_t height = 320,
100 float clockDiv = 8.0f, PIO pio = pio0)
101 : d0_(d0),
102 wr_(wr),
103 dc_(dc),
104 cs_(cs),
105 rst_(rst),
106 width_(width),
109 pio_(pio) {}
110
111 void end() override {
112 if (dma_ >= 0) {
114 dma_ = -1;
115 }
116 if (busInitialized_) {
117 pio_sm_set_enabled(pio_, sm_, false);
120 busInitialized_ = false;
121 }
122 }
123
125
126 size_t width() const override { return width_; }
127 size_t height() const override { return height_; }
128
130 return writeData(surface, 0, 0);
131 }
132
134 static_assert(sizeof(RGB_T) == 2,
135 "writeData assumes a 16bpp RGB_T (RGB565) stored in "
136 "wire byte order");
137 if (!busInitialized_) return false;
139
142
148
150 surface.size(), true);
153
155 return true;
156 }
157
158 protected:
165 int dma_ = -1;
166 bool busInitialized_ = false;
167
169 writeCommand(0x2A);
170 writeData16(x, x + w - 1);
171 writeCommand(0x2B);
172 writeData16(y, y + h - 1);
173 }
174
177 writeCommand(0x2C); // RAMWR
178 return true;
179 }
180
181 /// Claims a state machine + DMA channel and starts the PIO program.
182 /// Subclasses call this first thing in their begin() (after
183 /// setupPinsAndReset()), then send their chip's own init register
184 /// sequence via writeCommand()/writeDataN() before returning.
185 bool beginBus() {
186 int smClaim = pio_claim_unused_sm(pio_, false);
187 if (smClaim < 0) return false;
188 sm_ = static_cast<uint>(smClaim);
189
190 if (!pio_can_add_program(pio_, &kLcd8080WriteProgram)) return false;
192
193 for (int i = 0; i < 8; ++i) {
195 }
199
202 sm_config_set_sideset(&c, 1, false, false);
205 sm_config_set_out_shift(&c, /*shift_right=*/true, /*autopull=*/true, 8);
208
211
212 const int dmaClaim = dma_claim_unused_channel(false);
213 if (dmaClaim < 0) return false;
214 dma_ = dmaClaim;
215
216 busInitialized_ = true;
217 return true;
218 }
219
221 if (dc_ >= 0) pinMode(dc_, OUTPUT);
222 if (cs_ >= 0) {
225 }
226 if (rst_ >= 0) {
229 delay(20);
231 delay(150);
232 }
233 }
234
238 writeByte(cmd);
241 }
242
246 writeByte(d1 >> 8);
247 writeByte(d1 & 0xFF);
248 writeByte(d2 >> 8);
249 writeByte(d2 & 0xFF);
252 }
253
260 }
261
262 /// Writes each byte as a separate writeData8() call - a convenience for
263 /// sending a command's multi-byte parameter list during init sequences,
264 /// matching DisplayDriverSPI::writeDataN()/DisplayDriverParallel8's
265 /// writeDataN().
267 for (uint8_t b : bytes) {
268 writeData8(b);
269 }
270 }
271
272 private:
273 void beginTransaction() {
274 if (cs_ >= 0) digitalWrite(cs_, LOW);
275 }
276
277 void endTransaction() {
278 if (cs_ >= 0) digitalWrite(cs_, HIGH);
279 }
280
281 /// Pushes one byte into the state machine's TX FIFO - the byte belongs
282 /// in the FIFO word's low 8 bits (see the program comment's note on
283 /// autopull/shift_right).
284 void writeByte(uint8_t data) {
286 }
287
288 /// Blocks until the state machine has actually shifted out everything
289 /// pushed to it - the TX FIFO alone can report empty slightly before
290 /// the OSR finishes shifting the last word out, so this also waits for
291 /// the state machine to stall (nothing left to execute against).
292 void waitForTxDrain() {
293 while (!pio_sm_is_tx_fifo_empty(pio_, sm_)) {
294 }
295 while (!(pio_->fdebug & (1u << (PIO_FDEBUG_TXSTALL_LSB + sm_)))) {
296 }
297 pio_->fdebug = 1u << (PIO_FDEBUG_TXSTALL_LSB + sm_); // write-1-to-clear
298 }
299};
300
301/**
302 * @brief Driver for ILI9341 8-bit parallel display controller, driven via
303 * RP2040's PIO/DMA (see DisplayDriverParallel8RP2040).
304 *
305 * Same power/gamma/MADCTL init sequence as DisplayDriverSPI.h's
306 * ILI9341Driver (see that class's doc comment for provenance) - only the
307 * bus underneath differs.
308 */
309template <typename RGB_T = RGB565>
311 public:
317
319
320 /// @param nativeWidth/nativeHeight the panel's physical resolution in
321 /// its native (portrait, MV bit clear) orientation - 240x320 is the
322 /// common ILI9341 module size and the default; pass your panel's real
323 /// values if it differs.
326 float clockDiv = 8.0f, PIO pio = pio0,
333
336 }
339 }
340
343 if (!beginBus()) return false;
344
345 writeCommand(0x01); // SWRESET
346 delay(150);
347
348 writeCommand(0xEF);
349 writeDataN({0x03, 0x80, 0x02});
350
351 writeCommand(0xCF);
352 writeDataN({0x00, 0xC1, 0x30});
353
354 writeCommand(0xED);
355 writeDataN({0x64, 0x03, 0x12, 0x81});
356
357 writeCommand(0xE8);
358 writeDataN({0x85, 0x00, 0x78});
359
360 writeCommand(0xCB);
361 writeDataN({0x39, 0x2C, 0x00, 0x34, 0x02});
362
363 writeCommand(0xF7);
364 writeDataN({0x20});
365
366 writeCommand(0xEA);
367 writeDataN({0x00, 0x00});
368
369 writeCommand(0xC0);
370 writeDataN({0x23});
371
372 writeCommand(0xC1);
373 writeDataN({0x10});
374
375 writeCommand(0xC5);
376 writeDataN({0x3E, 0x28});
377
378 writeCommand(0xC7);
379 writeDataN({0x86});
380
381 writeCommand(0x36); // MADCTL
383
384 writeCommand(0x3A); // Pixel format: 16bpp
385 writeDataN({0x55});
386
387 writeCommand(0xB1);
388 writeDataN({0x00, 0x13});
389
390 writeCommand(0xB6);
391 writeDataN({0x08, 0x82, 0x27});
392
393 writeCommand(0xF2);
394 writeDataN({0x00});
395
396 writeCommand(0x26);
397 writeDataN({0x01});
398
399 writeCommand(0xE0);
400 writeDataN({0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07,
401 0x10, 0x03, 0x0E, 0x09, 0x00});
402
403 writeCommand(0xE1);
404 writeDataN({0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08,
405 0x0F, 0x0C, 0x31, 0x36, 0x0F});
406
407 writeCommand(0x11); // Sleep out
408 delay(120);
409 writeCommand(0x29); // Display on
410
411 writeCommand(invertColor_ ? 0x21 : 0x20);
412
413 return true;
414 }
415
417 if (rotation == Rotation::kNone) return;
419 writeCommand(0x36);
421 }
422
423 Rotation rotation() const { return rotation_; }
424
425 /// See DisplayDriverSPI.h's ILI9341Driver::setInvertColor() - same
426 /// panel quirk, same fix, just re-sent over this bus's begin() instead.
429 writeCommand(invert ? 0x21 : 0x20);
430 }
431
432 protected:
435 bool invertColor_ = false;
436
438 switch (rotation) {
439 case Rotation::kLandscape:
440 return 0x28;
442 return 0x88;
444 return 0xE8;
445 case Rotation::kNone:
446 case Rotation::kPortrait:
447 default:
448 return 0x48;
449 }
450 }
451};
452
453/**
454 * @brief Driver for ST7789 8-bit parallel display controller, driven via
455 * RP2040's PIO/DMA (see DisplayDriverParallel8RP2040).
456 */
457template <typename RGB_T = RGB565>
459 public:
464
466 int8_t rst = -1, size_t width = 240,
467 size_t height = 320, float clockDiv = 8.0f,
468 PIO pio = pio0)
470 height, clockDiv, pio) {}
471
474 if (!beginBus()) return false;
475 writeCommand(0x01); // SWRESET
476 delay(150);
477 writeCommand(0x11); // Sleep out
478 delay(120);
479 writeCommand(0x3A);
480 writeData8(0x55); // Pixel format: 16bpp
481 writeCommand(0x29); // Display on
482 return true;
483 }
484};
485
486#endif // TINYGPU_HAS_RP2040_PIO_PARALLEL8
487
488} // namespace tinygpu
Definition: DSIBusESP32.h:19