The prior 16-bit example laid the groundwork and might be all you need as a starting point for most projects, especially for rich full-color graphics. But it does have some drawbacks…
- The full-color framebuffer consumes over half of available RAM. What’s left over is often sufficient for some good projects, but really complex tasks might suffer.
- Graphics in motion will likely flicker and/or exhibit tearing — where a moving object is briefly seen half in its old and new positions, because screen and microcontroller aren’t synchronized.
The library offers solutions for each:
- An 8-bit color mode uses just one byte per pixel instead of two; a 320×240 pixel occupies half the space, about 75K now. The tradeoff is that a maximum of 256 colors can be used.
- A double-buffered 8-bit mode prevents flicker and tearing by maintaining two screen buffers: one currently shown on the monitor, and a second where drawing occurs “in the background,” which can then alternate (synchronized to the display refresh, so no tearing). Tradeoff is that this uses just as much RAM as a full 16-bit screen.
The 8-bit examples are much shorter than the prior one. They’re not here to demonstrate every kitchen-sink feature of Adafruit_GFX, simply to show what’s unique to 8-bit modes…
This starts out the same as before, by #including the PicoDVI.h
header file. And then the global display
declaration is ever-so-slightly different:
DVIGFX8 display(DVI_RES_320x240p60, false, adafruit_feather_dvi_cfg);
It’s now a DVIGFX8
object (rather than DVIGFX16
), and there’s one extra argument in the middle there. false
here tells the library to use single-buffered mode, the more memory-friendly option described above. Double-buffered mode is covered later.
All the other constructor arguments work the same as with DVIGFX16
explained on the prior page; resolution (including the need for special flash memory settings in “wide” mode), pinout and an optional voltage selection (not specified here, using the default).
DVIGFX8
allows up to 256 colors, but you get to decide which 256 colors from the full 16-bit color space. This is done through the setColor()
function, which accepts a color index (0 to 255) and a 16-bit “RGB565” color. By default, all 256 colors are set to 0 (black)…you need to set up something for your program. This first example leaves color 0 (the default background color) unchanged — black — then fills slots 1–254 with random 16-bit values, and finally the last slot (255) with white (0xFFFF). There’s no telling what the random colors might end up being, but at least this way we know there’s two contrasting colors at the very ends:
for (int i=1; i<255; i++) display.setColor(i, random(65536)); display.setColor(255, 0xFFFF);
The loop()
function then calls the usual Adafruit_GFX drawLine()
function to draw lines between random start and end points…and each with a random color index from 0 to 255 (corresponding to the table initialized with setColor() earlier) rather than a 16-bit RGB565 color.
display.drawLine(random(display.width()), random(display.height()), random(display.width()), random(display.height()), random(256));
This illustrates the speed of PicoDVI versus an SPI-attached TFT display…lines draw so fast the screen almost resembles pure noise.
This example shows how to create flicker-free, tear-free animation, moving dozens of objects about the screen.
Code starts out the same as the prior example, with the small change of passing true
as the second argument to the constructor:
DVIGFX8 display(DVI_RES_320x240p60, true, adafruit_feather_dvi_cfg);
By enabling double buffering, this now uses just as much RAM as 16-bit color mode (about 150K, or 187K for widescreen) but permits buttery smooth effects.
As in the prior example, a random color palette is created, with minor changes this time. Black and white are still used for entries 0 and 255, but for anything in-between we’d like slightly brighter colors (25–100% rather than 0–100%)…and, rather than a single packed 16-bit value, we have the option of calling setColor()
with three 8-bit values (0–255):
display.setColor(i+1, 64 + random(192), 64 + random(192), 64 + random(192));
Although this variant of setColor()
accepts three 8-bit inputs, the stored color is not really 24-bit. Behind the scenes, the values are quantized to 5 or 6 bits and packed together into a 16-bit value. It’s offered this way because a whole lot of existing graphics code — and color pickers in programs such as Photoshop — rely extensively on 24-bit colors, not 16-bit. You can use whatever’s easiest.
Skipping ahead to the loop()
function for a moment…for each frame of animation, the program:
- Clears the entire screen
- Draws a number of filled circles
- Updates the positions of those circles (“bouncing balls”) for the next frame
It doesn’t even bother carefully erasing everything in the old positions and then drawing new ones…the whole screen is just cleared and drawn anew. Filling the screen is super fast and the code is much simpler this way.
You don’t see any flicker when the screen is cleared and the balls are drawn, because of one last step:
display.swap();
All that clearing and drawing takes place in a background framebuffer, out of sight. The call to swap()
brings that buffer to the front (synchronized to the next video refresh), and the front buffer to the back for subsequent drawing operations.
swap()
can optionally accept two boolean arguments, and backing up to the end of the setup()
function now, you can see that in action:
display.swap(false, true);
The first argument, if true
, will (after the video frame sync where the swap takes place) copy the new foreground buffer contents to the background buffer. This can be helpful for programs that generate animation incrementally; just drawing any changes over the prior frame. If false
, no copy is performed…ideal for situations like this bouncing ball example, where the whole buffer is simply cleared and drawn anew.
The second argument does the same thing for the color palette, which is independent for the front and back buffers. Sometimes programs simulate animation through “color cycling” — not redrawing any pixels, but just moving a set of colors through a loop. This too would exhibit “tearing” if not for double-buffering. The bouncing balls demo doesn’t do color cycling, but it does call swap(false, true)
once after colors are initialized, so both the front and back buffers are using the same palette.
// 8-bit Adafruit_GFX-compatible framebuffer for PicoDVI. #include <PicoDVI.h> // Here's how a 320x240 8-bit (color-paletted) framebuffer is declared. // Second argument ('false' here) means NO double-buffering; all drawing // operations are shown as they occur. Third argument is a hardware // configuration -- examples are written for Adafruit Feather RP2040 DVI, // but that's easily switched out for boards like the Pimoroni Pico DV // (use 'pimoroni_demo_hdmi_cfg') or Pico DVI Sock ('pico_sock_cfg'). DVIGFX8 display(DVI_RES_320x240p60, false, adafruit_feather_dvi_cfg); // A 400x240 mode is possible but pushes overclocking even higher than // 320x240 mode. SOME BOARDS MIGHT SIMPLY NOT BE COMPATIBLE WITH THIS. // May require selecting QSPI div4 clock (Tools menu) to slow down flash // accesses, may require further over-volting the CPU to 1.25 or 1.3 V. //DVIGFX8 display(DVI_RES_400x240p60, false, adafruit_feather_dvi_cfg); void setup() { // Runs once on startup if (!display.begin()) { // Blink LED if insufficient RAM pinMode(LED_BUILTIN, OUTPUT); for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1); } // Randomize color palette. First entry is left black, last is set white. for (int i=1; i<255; i++) display.setColor(i, random(65536)); display.setColor(255, 0xFFFF); } void loop() { // Draw random lines display.drawLine(random(display.width()), random(display.height()), random(display.width()), random(display.height()), random(256)); }
// Double-buffered 8-bit Adafruit_GFX-compatible framebuffer for PicoDVI. // Animates without redraw flicker. Requires Adafruit_GFX >= 1.11.4 #include <PicoDVI.h> // Here's how a 320x240 8-bit (color-paletted) framebuffer is declared. // Second argument ('true' here) enables double-buffering for flicker-free // animation. Third argument is a hardware configuration -- examples are // written for Adafruit Feather RP2040 DVI, but that's easily switched out // for boards like the Pimoroni Pico DV (use 'pimoroni_demo_hdmi_cfg') or // Pico DVI Sock ('pico_sock_cfg'). DVIGFX8 display(DVI_RES_320x240p60, true, adafruit_feather_dvi_cfg); // A 400x240 mode is possible but pushes overclocking even higher than // 320x240 mode. SOME BOARDS MIGHT SIMPLY NOT BE COMPATIBLE WITH THIS. // May require selecting QSPI div4 clock (Tools menu) to slow down flash // accesses, may require further over-volting the CPU to 1.25 or 1.3 V. //DVIGFX8 display(DVI_RES_400x240p60, true, adafruit_feather_dvi_cfg); #define N_BALLS 100 // Number of bouncy balls to draw, 1-254 (not 255) struct { int16_t pos[2]; // Ball position (X,Y) int8_t vel[2]; // Ball velocity (X,Y) } ball[N_BALLS]; void setup() { // Runs once on startup if (!display.begin()) { // Blink LED if insufficient RAM pinMode(LED_BUILTIN, OUTPUT); for (;;) digitalWrite(LED_BUILTIN, (millis() / 500) & 1); } // Randomize initial ball positions, velocities and colors for (int i=0; i<N_BALLS; i++) { display.setColor(i+1, 64 + random(192), 64 + random(192), 64 + random(192)); ball[i].pos[0] = 10 + random(display.width() - 20); ball[i].pos[1] = 10 + random(display.height() - 20); do { ball[i].vel[0] = 2 - random(5); ball[i].vel[1] = 2 - random(5); } while ((ball[i].vel[0] == 0) && (ball[i].vel[1] == 0)); } display.setColor(255, 0xFFFF); // Last palette entry = white display.swap(false, true); // Duplicate same palette into front & back buffers } void loop() { // Clear back framebuffer and draw balls (circles) there. display.fillScreen(0); for (int i=0; i<N_BALLS; i++) { display.fillCircle(ball[i].pos[0], ball[i].pos[1], 20, i + 1); // After drawing each one, update positions, bounce off edges. ball[i].pos[0] += ball[i].vel[0]; if ((ball[i].pos[0] <= 0) || (ball[i].pos[0] >= display.width())) ball[i].vel[0] *= -1; ball[i].pos[1] += ball[i].vel[1]; if ((ball[i].pos[1] <= 0) || (ball[i].pos[1] >= display.height())) ball[i].vel[1] *= -1; } // Swap front/back buffers, do not duplicate current screen state to next frame, // we'll draw it new from scratch each time. display.swap(); }
Text editor powered by tinymce.