# PicoDVI Arduino Library: Video Out for RP2040 Boards

## Overview

![](https://cdn-learn.adafruit.com/assets/assets/000/119/884/medium800thumb/hacks_picodvi-on-tv.jpg?1680039064)

**IMPORTANT: the software documented here pushes the RP2040 microcontroller _far_ beyond its design specifications.&nbsp;Just like PC overclocking, there’s some risk of reduced component lifespan,&nbsp;though the extent (if any) can’t be precisely quantified and could vary from one chip to another.&nbsp;Proceed at your own discretion.**

 **_PicoDVI_** is a remarkable project by Luke Wren of the Raspberry Pi Foundation, capable of squeezing **digital video** _directly_ from an RP2040 microcontroller. Imagine the creative applications for having these inexpensive devices linked to a display…not a tiny LCD, but the big bright screen in your living room or connected to your computer!

In its natural habitat, PicoDVI works with the _Raspberry Pi Pico C/C++ SDK_ — a command-line development system — and projects using it involve careful planning and work to generate an image. What we’ve done is wrap up PicoDVI in an Arduino-flavoured wrapper: programmed within the familiar **Arduino IDE** , using graphics operations from the **Adafruit\_GFX** library — draw lines, draw circles, some text and so forth. Novice coders can then develop video-based projects with minimal fuss.

## Compatible Hardware
- PicoDVI can work on most **RP2040** -based microcontroller boards. It uses features unique to this chip and _will not work anywhere else,_ no ESP32, no SAMD, etc.
- **8 GPIO pins** are used for output. They don’t need to _all_ be consecutive, but does require four _pairs_ of consecutive pins.
- An **HDMI\* breakout** board can be used, or a bare HDMI connector and some passive components. Or **better yet…**
- Several boards now have this all **baked in:** Adafruit’s [Feather RP2040 DVI](https://www.adafruit.com/product/5710) (shown below), Pi Cowbell DVI, Pimoroni’s [Pico DV Demo Base](https://www.adafruit.com/product/5674), or Luke Wren’s own [Pico DVI Sock](https://www.hackster.io/news/luke-wren-s-surface-mount-pico-dvi-sock-brings-video-output-capabilities-to-the-raspberry-pi-pico-afc04e0874dd) design.

![Adafruit Feather RP2040 DVI board](https://cdn-learn.adafruit.com/assets/assets/000/119/665/medium800/hdmi_feather-rp2040-dvi.jpg?1679682142)

To perform its magic, the PicoDVI code sometimes takes liberties with video timing. _Most_ monitors can successfully sync with this, but there’s _no guarantee_ that every monitor can latch on to every PicoDVI mode. You will need to experiment.

\* Although an HDMI _connector_ and _cable_ are involved, strictly speaking this is _DVI_ video. True and Proper HDMI™ is a proprietary, licensable format bringing together several technologies — video, audio, content protection, sometimes networking. HDMI’s baseline non-content-protected video format happens to use DVI-compatible signaling, which is what the library generates.&nbsp;_Nobody will fault you for casually calling it “HDMI out” (do younger folks even remember DVI monitors?), but if you’re making a product or publishing a tutorial, best to say “DVI” to avoid potential licensing drama._

To reiterate an opening point: the PicoDVI code works by&nbsp;_extreme_ overclocking. We’ve yet to witness failures from this, but _it is a possibility._ Thankfully the chips are affordable…the assumed risk is _nowhere near_ the magnitude of overclocking a high-end PC CPU or graphics card.

# PicoDVI Arduino Library: Video Out for RP2040 Boards

## Installation

PicoDVI relies on the **_Earle Philhower III Arduino core_** for programming — an optional package that makes most RP2040 boards work in the Arduino environment. If you’ve previously followed any guides for our RP2040-based boards, you likely already have this installed…just check that you’re up to date with the latest ( **3.1.0 or newer** ).

If that sounds unfamiliar, [this guide walks through the process](https://learn.adafruit.com/rp2040-arduino-with-the-earlephilhower-core/overview).

![](https://cdn-learn.adafruit.com/assets/assets/000/119/447/medium800/hdmi_boards-manager.png?1678812706)

Once installed, the Arduino IDE **Tools→Board** menu will include a rollover for “Raspberry Pi RP2040 Boards,” and you can find and select whatever board type you’re using (e.g. Feather RP2040 DVI or Raspberry Pi Pico).

Next, the **PicoDVI library** can be installed from the **Arduino Library Manager**. From the Sketch menu…

**Sketch→Include Library→Manage Libraries…**

Enter “ **picodvi** ” in the search field and look for **PicoDVI - Adafruit Fork** in the results. Click “Install,” then “Close.”

Our version of PicoDVI depends on the **Adafruit\_GFX** library. The Library Manager _should_ install this automatically if not already present, but if using an older version of the Arduino IDE you might need to search for and install it manually.

![](https://cdn-learn.adafruit.com/assets/assets/000/119/415/medium800/hdmi_picodvi-install.png?1678732354)

This is our “fork” of the original PicoDVI project,&nbsp;meaning as much of the original code is preserved with minimal changes. What we’ve done is add an Arduino-compliant C++ wrapper to make this command-line library work with the friendlier Arduino IDE, and implemented simple raster framebuffers for drawing.&nbsp;All the original stuff is there if you want to dig in and learn, though the original examples as written won’t build in the Arduino IDE.

[Here’s our fork on GitHub](https://github.com/adafruit/PicoDVI), and [Luke Wren’s original project](https://github.com/Wren6991/PicoDVI).

# PicoDVI Arduino Library: Video Out for RP2040 Boards

## Examples

We've created several examples to showcase the library in use. Each tends to highlight _one_ new capability of PicoDVI, none too complex or technical. With core and library installed, you'll find these in the **Files→Examples→PicoDVI - Adafruit Fork** menu in alphabetical order, but let's walk through from simple to fancy. Even if you don't plan on trying any or all of these examples, please skim through, some key concepts are explained…

# PicoDVI Arduino Library: Video Out for RP2040 Boards

## 16bit_hello

 **Please read through _at least_ this example.** Most of the concepts covered here apply to _all_ the examples and to using the PicoDVI library in general, and it would be a tedious read with all these details reiterated on every page.

![](https://cdn-learn.adafruit.com/assets/assets/000/119/491/medium800thumb/hdmi_picodvi-16bit.jpg?1678928831)

Although this example is a _lot_ of code,&nbsp;it’s also the _most familiar_ if you’ve previously used the Adafruit\_GFX library with any of our color TFT or OLED displays…in fact, it’s the _same_ demo we now use for all our [EYESPI screens](https://www.adafruit.com/?q=eyespi), with only a slight adjustment for DVI output.&nbsp;If you _haven’t_ used Adafruit\_GFX, [we have a whole guide explaining the groundwork](https://learn.adafruit.com/adafruit-gfx-graphics-library).

The GFX parts are well-commented and we won’t go through the whole example here, just mentioning the most important lines in this example that set it apart from the usual TFT projects…

Near the very top of the code:

```cpp
#include &lt;PicoDVI.h&gt;
```

This _header file_ establishes all that needs known about the PicoDVI library. In projects that use TFT displays, the equivalent line might reference `Adafruit_ST7789.h` or `Adafruit_ILI9341.h`&nbsp;— headers for different display controllers — and it’s the same idea here, but for the DVI display.

A little further down, a global declaration is made before the `setup()` function:

```cpp
DVIGFX16 display(DVI_RES_320x240p60, adafruit_feather_dvi_cfg);
```

This creates a global object, `display` (it’s _global_ — usable _anywhere in the sketch_ — because it’s declared outside any function), of type `DVIGFX16`, which is a _ **16-bit color framebuffer** _ (a section of memory dedicated for graphical display).&nbsp;[16-bit colors are described further in the GFX tutorial](https://learn.adafruit.com/adafruit-gfx-graphics-library/coordinate-system-and-units). The two arguments passed to this function define the resolution and what GPIO pins are used…more on those in a moment, but first…

Then, inside the `setup()` function (which runs once at startup), there’s a call like this:

```cpp
display.begin()
```

`display` is the previously-declared global object, `.begin()` is the function that actually starts up the DVI output (without this, you’ll just get a _No Signal_ message on most monitors).

In the example, you’ll notice the **return value is tested** …and if **false** , the program just stops there, blinking the LED to indicate there’s a problem. This can happen due to a lack of resources…usually insufficient RAM or other finite resources of the chip (three of the chip’s four PIO state machines are needed for DVI).

While the RP2040 chip has a generous 264 kilobytes of RAM, a full-color screenful like this uses a majority of it…about **150 KB** (320&nbsp;×&nbsp;240&nbsp;×&nbsp;2&nbsp;bytes/pixel). On a typical TFT or OLED display, that would be offloaded to a display controller chip, but with DVI it occupies main RAM. The tradeoff is _stunning_ performance…toggling bits in memory is _orders of magnitude_ faster than pushing pixels even over a fast SPI connection.

Let’s look at the _constructor_ again, that global declaration for the display, and explore the _arguments_ passed:

```cpp
DVIGFX16 display(DVI_RES_320x240p60, adafruit_feather_dvi_cfg);
```

Looking at the _second_ argument first, simpler to explain —&nbsp;`adafruit_feather_dvi_cfg` — this is the name of a structure tucked away in the header files, and configures the code to work on the **Adafruit Feather RP2040 DVI** board. There are others! You might change this to&nbsp;`pimoroni_demo_hdmi_cfg` if using a Pimoroni Pico DV Demo Base board, or `pico_sock_cfg` for a Pico DVI sock. Each of these boards adopted a different set of DVI output pins in their design. (The complete set of ready-made pin configurations can be found in the library file&nbsp;PicoDVI/software/include/common\_dvi\_pin\_configs.h — or, if using an HDMI breakout and your own custom pinout, declare a `dvi_serialiser_cfg` structure of your own and pass it to the constructor).

The _first_ argument, `DVI_RES_320x240p60`, selects the output resolution and refresh rate: 320×240 pixels at 60 Hz. Only **a very limited set of resolutions are supported** , and this is one of them. 320×240 is a 4:3 aspect ratio and might look best on an older DVI monitor, or with “pillarboxing” (left and right black bars) enabled on a more modern 16:9 HDMI monitor…most will have a menu selection somewhere for pillarboxing or if you’d prefer the image “stretched” to the full display width…pixels are then _rectangular_ rather than square, but it does use the whole display.

Some monitors will report this as&nbsp;640×480 resolution, but it’s really only half that. The library is doubling up pixels on both the horizontal and vertical axes, and there wouldn’t be enough RAM to buffer a full image at the larger size anyway.

There’s a line, commented out by default, that selects a wider mode. You can enable this (and comment out the first one instead)…but read on for extra steps this entails:

```cpp
DVIGFX16 display(DVI_RES_400x240p60, adafruit_feather_dvi_cfg);
```

This is _closer_ to 16:9 aspect ratio (not exactly, but less visibly stretched) at the expense of some extra RAM…now using 192K, leaving only about a Commodore 64’s worth for your own project to do its thing.

**BEFORE YOU ATTEMPT THIS RESOLUTION THOUGH, there’s a compile-time setting that _MUST_ be manually enabled:**

With most Adafruit RP2040 boards, including the Feather DVI, access to flash memory must be explicitly slowed down…the CPU would otherwise _outpace it and lock up,_ it’s running so fast in this mode!

From the Arduino **Tools** menu, look for the **Boot&nbsp;Stage&nbsp;2** rollover and select **W25Q080&nbsp;QSPI/4**.

![hdmi_qspi-clkdiv.png](https://cdn-learn.adafruit.com/assets/assets/000/119/435/medium640/hdmi_qspi-clkdiv.png?1678753594)

This is wild frontier engineering, running the RP2040 at an absolutely bonkers clock rate, and _might not even work_ across every chip. And when it doesn’t, locks up _so hard_ that not even the LED error blink will happen, and your computer may get slow and choppy as it tries to understand this weird thing connected to USB.&nbsp; **If this happens, unplug the board, hold down the BOOT button while re-connecting, and set the code back to the milder**  **320×240 resolution.** Sorry about that.

There’s a secret optional _third_ argument to the constructor that can run the RP2040 chip at a slightly higher internal voltage. Extreme overclocking (such as at higher resolutions) may require this. Normally the chip operates internally at 1.1 Volts DC, and by default the library boosts this slightly to 1.2V. That’s beyond the chip’s “official” rated specification, but just like PC overclocking, can keep the system more stable. (Or, if you find the approved 1.1V sufficiently stable, can select that.) Just tack it on as an extra argument like so:

```auto
DVIGFX16 display(DVI_RES_400x240p60, adafruit_feather_dvi_cfg, VREG_VOLTAGE_1_25);
```

The available voltage selections include:

`VREG_VOLTAGE_0_85` (0.85 Volts)  
`VREG_VOLTAGE_0_90` (0.9V)  
`VREG_VOLTAGE_0_95` (0.95V)  
`VREG_VOLTAGE_1_00` (1.0V)  
`VREG_VOLTAGE_1_05` (1.05V)  
`VREG_VOLTAGE_1_10` **(1.1V, normal system default)**  
`VREG_VOLTAGE_1_15` (1.15V)  
`VREG_VOLTAGE_1_20` **(1.2V, library default if not specified)**  
`VREG_VOLTAGE_1_25` (1.25V)  
`VREG_VOLTAGE_1_30` (1.3V, maximum possible from internal regulator)

Over-volting does carry with it associated risks, as mentioned on the opening page.

Once everything is set up, the code then goes into some straightforward GFX demos, just as it would with a TFT or OLED display…

## Source Code
https://github.com/adafruit/PicoDVI/blob/master/examples/16bit_hello/16bit_hello.ino

# PicoDVI Arduino Library: Video Out for RP2040 Boards

## 8bit_single_buffer and 8bit_double_buffer

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…

## 8bit\_single\_buffer
![hdmi_picodvi-8bit-single.gif](https://cdn-learn.adafruit.com/assets/assets/000/119/486/medium640thumb/hdmi_picodvi-8bit-single.jpg?1678928470)

This starts out the same as before, by #including the&nbsp;`PicoDVI.h`&nbsp;header file. And then the global `display` declaration is ever-so-slightly different:

```cpp
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`&nbsp;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`&nbsp;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:

```cpp
for (int i=1; i&lt;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.

```cpp
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.

## 8bit\_double\_buffer
![hdmi_picodvi-8bit-double.gif](https://cdn-learn.adafruit.com/assets/assets/000/119/487/medium640thumb/hdmi_picodvi-8bit-double.jpg?1678928549)

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:

```auto
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):

```cpp
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:

```cpp
display.swap();
```

All that clearing and drawing takes place in a _background_&nbsp;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:

```cpp
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&nbsp;`swap(false, true)` once after colors are initialized, so both the front and back buffers are _using the same palette._

## Source Code: 8bit\_single\_buffer
https://github.com/adafruit/PicoDVI/blob/master/examples/8bit_single_buffer/8bit_single_buffer.ino

## Source Code: 8bit\_double\_buffer
https://github.com/adafruit/PicoDVI/blob/master/examples/8bit_double_buffer/8bit_double_buffer.ino

# PicoDVI Arduino Library: Video Out for RP2040 Boards

## 1bit_single_buffer and 1bit_double_buffer

Sometimes _a sharp image_ is more important that a variety of colors. PicoDVI has a setting offering double the resolution on each axis — 640×480, or 800×480 in wide mode — if you’re fine with **strictly black-and-white** graphics. Even with all the extra pixels, this uses only _half_ the RAM of the 8-bit modes, or one fourth the 16-bit mode: about 38K for single-buffered 640×480 graphics, 75K double-buffered (and 47 or 94K for the widescreen variants).

# 1bit\_single\_buffer
![hdmi_picodvi-1bit-single.gif](https://cdn-learn.adafruit.com/assets/assets/000/119/488/medium640thumb/hdmi_picodvi-1bit-single.jpg?1678928643)

The global `display` declaration is very similar to the 8-bit version:

```auto
DVIGFX1 display(DVI_RES_640x480p60, false, adafruit_feather_dvi_cfg);
```

The display type is `DVIGFX1` now (instead of `DVIGFX8`), and the resolution doubled by requesting&nbsp;`DVI_RES_640x480p60`. Second argument is `false` because this example is single-buffered, and the remaining arguments (pinout and optional voltage setting) are the same as before. Remember that you may need to adjust the flash memory timing if using the widescreen `DVI_RES_800x480p60`&nbsp;mode.

The remaining code in `loop()` then just draws lines between random endpoints, randomly selecting color 0 (black) or color 1 (white). _Any_ GFX function could be used here — circles, text (including fonts) and so forth — using just those two color values. That’s all there is to it!

# 1bit\_double\_buffer
![hdmi_picodvi-1bit-double.gif](https://cdn-learn.adafruit.com/assets/assets/000/119/493/medium640thumb/hdmi_picodvi-1bit-double.jpg?1678931053)

This is the bouncing-balls demo again, but strictly black-and-white. You can probably predict how this will all go…

```cpp
DVIGFX1 display(DVI_RES_640x480p60, true, adafruit_feather_dvi_cfg);
```

Second argument is `true` now for double buffering, and the rest is all the same. No color palette is set up this time because 1-bit mode is always just black (0) or white (1).

`loop()` then works similarly to the 8-bit version: clear the whole screen, draw circles (outlined this time), update their positions for next time, and swap front/back buffers. `swap()` can optionally accept a single `true`/`false` argument (rather than two in the 8-bit case) whether to copy the front framebuffer contents to the back. It’s not used in this case because the full screen is redrawn; `false` is implied if not provided.

## Source Code: 1bit\_single\_buffer
https://github.com/adafruit/PicoDVI/blob/master/examples/1bit_single_buffer/1bit_single_buffer.ino

## Source Code: 1bit\_double\_buffer
https://github.com/adafruit/PicoDVI/blob/master/examples/1bit_double_buffer/1bit_double_buffer.ino

# PicoDVI Arduino Library: Video Out for RP2040 Boards

## 1bit_text

![](https://cdn-learn.adafruit.com/assets/assets/000/119/490/medium800thumb/hdmi_picodvi-text-mode.jpg?1678928738)

Adafruit\_GFX has some nice text and font support, but occasionally you may want something _minimalist_ and _fast,_ like the text mode of old PC’s. PicoDVI offers a fixed-width pure character mode that uses far less RAM than even the 1-bit graphics modes, and conveniently scrolls upward when text reaches the bottom.

Looking at the `display` declaration for this example:

```cpp
DVItext1 display(DVI_RES_640x240p60, adafruit_feather_dvi_cfg);
```

The object type is now `DVItext1` (optimistically named with the “1” in case we add color text mode(s) in the future). The arguments should be familiar by now, but the resolution setting — `DVI_RES_640x240p60` — is a new one.

The character cells in this text mode are **8 by 8 pixels**. A **640** pixel width will thus yield **80 columns** of text. The peculiar **240** pixel height used here makes things _vertically stretched;_ each character cell is tall and narrow, and this mode yields **80×30** characters overall.

The result looks _incredibly_ similar to vintage 80 column PC text — in fact the shapes are based on the IBM VGA font. This is also known as the “[Code Page 437](https://en.wikipedia.org/wiki/Code_page_437)” character set. Characters are 8 bits…0–255…there’s a variety of symbols and accented letters, but no Unicode support, nor any alternate fonts.

A 100-column wide mode is possible (following the same compilation rules as explained in prior examples), and up to 60 rows if one foregoes the vertical stretch:

```cpp
DVItext1 display(DVI_RES_800x480p60, adafruit_feather_dvi_cfg);
```

There’s little to say here about use other than it supports the same `print()` and `println()` calls as Arduino Serial or Adafruit\_GFX:

```cpp
display.print("Hello World!  ");
```

It’s pretty basic, there’s no word wrap at the right edge…but when text reaches the bottom of the screen, everything will scroll upward automatically.

## Source Code
https://github.com/adafruit/PicoDVI/blob/master/examples/1bit_text/1bit_text.ino

# PicoDVI Arduino Library: Video Out for RP2040 Boards

## virtual_spitft

This is **not a self-contained example**. It works in conjunction with another microcontroller that _thinks_ it’s connected to an ST7789 or ILI9341 TFT display, issuing those graphics to the DVI output instead (or, if a TFT screen is present, it can display on _both_). This requires additional hardware and code…and it probably can’t keep up with the fastest devices…but seems to work well enough with midrange parts like SAMD21 (e.g. Metro Express).

This taps into the **SPI output** of the host device and a couple extra control signals. These can be routed to most any input pins on the RP2040 running the PicoDVI code, but **three of these pins — SPI MOSI, TFT D/C, SPI CLK —&nbsp;_must_ be sequential** (e.g. pins 9–11, or A1–A3, etc.). The fourth — TFT CS — can go to any available pin, and of course **ground should be connected between both boards**.

The input pins are defined near the top of the example code:

```auto
#define PIN_DATA 9 // 3 contiguous pins start here: data, DC, clk
#define PIN_CS   6 // Chip-select need not be contiguous
```

Here is _one possible_ wiring setup, for the Adafruit\_ST7789&nbsp;graphicstest.ino example running on an Adafruit Metro M0 board:

![](https://cdn-learn.adafruit.com/assets/assets/000/119/672/medium800/hdmi_virtual-tft-wiring-diagram.png?1679681397)

The “host” device could be just about anything, really. A Metro M0 board was chosen because it uses 3.3V logic — it can connect directly to the Feather without a logic level shifter, making the diagram a little simpler.

The wires shown happen to be the defaults for the “graphicstest” example on that board, but in reality might connect elsewhere…you’d need to look through the code. Also, there _could_ also be a TFT physically attached, with the signals _split_ both to the TFT and the Feather RP2040 DVI board.

If the host device uses **5 Volt logic** (Metro Mini, Arduino Nano, etc.), a logic-level shifter such as the [74LVC245 IC](https://www.adafruit.com/product/735) should be added between to bring these signals down to a safe 3.3V for the RP2040. This is not shown in the wiring diagram, but the connections can be easily derived from the chip’s [datasheet](https://cdn-shop.adafruit.com/datasheets/sn74lvc245a.pdf). Vcc connects to 3.3V from the RP2040 board, OE to ground, and DIR to either 3.3V or ground depending which side you’re using for “in” vs. “out.”

 **This example is an interesting _concept_ but not especially robust.** The RP2040 board running PicoDVI should be powered on first, a fast host microcontroller might outpace the code running on the RP2040, and if the two get out of sync, it may or may not re-synchronize successfully.

## Source Code
https://github.com/adafruit/PicoDVI/blob/master/examples/virtual_spitft/virtual_spitft.ino

https://github.com/adafruit/PicoDVI/blob/master/examples/virtual_spitft/fourwire.pio

# PicoDVI Arduino Library: Video Out for RP2040 Boards

## Screensavers

In the_&nbsp;olde days™&nbsp;_of CRT monitors, good computer hygiene meant installing a _screensaver_ to prevent image burn-in. Early operating systems didn’t even provide this…such programs had to be _purchased!_ We’ve replicated the nostalgic essence of a few popular classics…only this time, the code’s all _free._

With the PicoDVI library [installed as previously described](https://learn.adafruit.com/picodvi-arduino-library-video-out-for-rp2040-boards/installation), you’ll find these in Arduino’s _Examples_ menu…

**File→Examples→PicoDVI&nbsp;-&nbsp;Adafruit Fork→screensavers**

If using DVI PiCowbell, use this output line in your Arduino code:

`DVIGFX8 display(DVI_RES_320x240p60, true, adafruit_dvibell_cfg);`

For the DVI Sock, use this:

`DVIGFX8 display(DVI_RES_320x240p60, true, pico_sock_cfg);`

&nbsp;

For the 1-bit DVi Logo Bounce on the DVI PiCowbell, use this:

`DVIGFX1 display(DVI_RES_640x480p60, true, adafruit_dvibell_cfg);`

For the DVI Sock, use this:

`DVIGFX1 display(DVI_RES_640x480p60, true, pico_sock_cfg);`

&nbsp;

If you’d prefer to just mess around with the demos, we also have pre-compiled UF2 files for the **Feather RP2040 DVI** boards at 400x240 resolution and for the **Pico with the DVI PiCowbell** at 320x240 for each example below.

The examples are all written for the **Feather RP2040 DVI** using 320x240 pixels, but easily adapted for other RP2040 boards, and for 400x240 pixel resolution with a little extra work. See the earlier 8bit\_double\_buffer example for an explanation.

We won’t be showing the code for these examples “inline” here because the embedded graphics data makes them _huge…_you’d be scrolling for weeks. There are comments in the code and some additional notes at the end of this page.

## Flying Toasters
The classic-est of the classics! _This_ is what made folks want to _buy_ a screensaver.

Not emulated, just an _approximation_ of the original.

![hacks_toasters.gif](https://cdn-learn.adafruit.com/assets/assets/000/120/320/medium640thumb/hacks_toasters.jpg?1681852970)

[toasters.uf2](https://cdn-learn.adafruit.com/assets/assets/000/120/321/original/toasters.uf2?1681852991)
[toasters_dvibell.ino.uf2](https://cdn-learn.adafruit.com/assets/assets/000/128/929/original/toasters_dvibell.ino.uf2?1711463008)
## Aquarium
The _other_ classic. Not as zany™ as the toasters, but soothing to watch.

![hacks_aquarium.gif](https://cdn-learn.adafruit.com/assets/assets/000/120/325/medium640thumb/hacks_aquarium.jpg?1681853967)

[aquarium.uf2](https://cdn-learn.adafruit.com/assets/assets/000/120/326/original/aquarium.uf2?1681853983)
[aquarium_dvibell.ino.uf2](https://cdn-learn.adafruit.com/assets/assets/000/128/930/original/aquarium_dvibell.ino.uf2?1711463018)
## TV Host
Also not a classic PC screensaver but it _had_ to be done.

Inspired by the trash-heap screens in _Max Headroom: 20 Minutes into the Future_ (1985).&nbsp;Been noticing more&nbsp;_working_ flatscreen TVs and monitors left out by the curb lately.&nbsp;_Free pixels!_ What could we make of them? This was one such idea.

![hacks_tvhost.gif](https://cdn-learn.adafruit.com/assets/assets/000/120/319/medium640thumb/hacks_tvhost.jpg?1681852445)

![hacks_trash-tvs.jpg](https://cdn-learn.adafruit.com/assets/assets/000/120/327/medium640/hacks_trash-tvs.jpg?1681854953)

[tv-host.uf2](https://cdn-learn.adafruit.com/assets/assets/000/120/975/original/tv-host.uf2?1683928240)
[tvhost_DVIBELL.ino.uf2](https://cdn-learn.adafruit.com/assets/assets/000/128/931/original/tvhost_DVIBELL.ino.uf2?1711463027)
Here’s a version that’s _just_ the rotating cube background:

[cube-background.uf2](https://cdn-learn.adafruit.com/assets/assets/000/120/976/original/cube-background.uf2?1683928257)
## Boing!
_The_ Amiga demo that started it all.

(This UF2 file is compiled at 320x240 for aspect ratio authenticity.)

![hacks_boing.gif](https://cdn-learn.adafruit.com/assets/assets/000/120/935/medium640thumb/hacks_boing.jpg?1683765162)

[boing.uf2](https://cdn-learn.adafruit.com/assets/assets/000/120/934/original/boing.uf2?1683764522)
[boing_dvibell.ino.uf2](https://cdn-learn.adafruit.com/assets/assets/000/128/932/original/boing_dvibell.ino.uf2?1711463037)
## Logo Bounce
Okay, it’s not a classic PC screensaver, but had to include this as the code’s the simplest of the lot, and also it shows crisp high-resolution (640x480 or 800x480) monochrome animation. This example is too high a resolution to run on the Pico.

Hi-res monochrome would be ideal for making retro vector-style games like _Asteroids_ or _Battlezone._

![hacks_logobounce.gif](https://cdn-learn.adafruit.com/assets/assets/000/120/322/medium640thumb/hacks_logobounce.jpg?1681853469)

Here’s a ready-made UF2 file for Feather RP2040 DVI:

[logobounce.uf2](https://cdn-learn.adafruit.com/assets/assets/000/120/323/original/logobounce.uf2?1681853506)
## Notes

All of these examples do something similar: graphics “sprites” are embedded as tables in the code, and then bitmap-drawing functions in Adafruit GFX “blit” these to the screen. Double-buffered animation prevents flicker; everything appears buttery smooth.

The “TV Host” example is distinct in that it also lays down some _vector-style_ graphics first, then overlays large sprites atop this. The backdrop is doing some actual 3D perspective but only in the barest sense.

None of these examples is especially “crafty” — the approach is brute-force, using large sprite tables as a tradeoff to keep the code simpler to read. The demos are nonetheless impressive for a modestly-priced microcontroller board. If slapdash code can do this, then advanced programmers will likely be creating some stunning work!

The sprite tables were generated using a mix of ImageMagick and/or Python. We don’t have a well-written universal tool for this because _sometimes that’s just the nature of Python…_quickly writing a sloppy one-off program for a special task.

ImageMagick was mostly used for the 1-bit sprite masks, for example:

```terminal
magick mask.png -define h:format=gray -depth 1 mask.h
```

The output needs a little editing (see the sprite.h/sprites.h header file that’s a part of each example), but ImageMagick does _most_ of the work.

Here’s a little Python code (not CircuitPython, but “desktop” Python) that extracts the color palette from an 8-bit PNG or GIF and converts it to the “RGB565” color format used by PicoDVI and Adafruit GFX, then follows up with a table of sprite pixel values:

```python
from PIL import Image, ImagePalette

with Image.open("dragon.png") as img:

    pal = img.getpalette()
    print("const uint16_t palette[] = {")
    for i in range(len(pal) // 3):
        r = pal[i * 3 + 0] &gt;&gt; 3
        g = pal[i * 3 + 1] &gt;&gt; 2
        b = pal[i * 3 + 2] &gt;&gt; 3
        rgb = (r &lt;&lt; 11) | (g &lt;&lt; 5) | b;
        print("0x%04X, " % (rgb), end="")
    print("};\n")

    print("const uint8_t sprite[] = {")
    for y in range(img.size[1]):
        for x in range(img.size[0]):
            p = img.getpixel((x, y))
            print("0x%02X, " % (p), end="")
    print("};\n")
```

The output of this code is then redirected to a file, such as:

```terminal
python convert.py &gt; sprite.h
```

That file’s contents will be a sloppy mess! To keep this simple, rather than formatting nicely in the Python code, the output is run through the _clang-format_ tool to clean it up:

```terminal
clang-format -i sprite.h
```

The mask data (for programs that need it) was then appended.

Sometimes _ImageMagick_ can be used for the sprite conversion as well, but it tends to be headstrong about optimizing and reordering colors. There’s probably settings to disable those behaviors, but doing the conversion in Python was simple enough.

The _boing_ example is a little different because the code was adapted from an earlier project for PyPortal. That one uses “packed” data, two pixels per byte.

# PicoDVI Arduino Library: Video Out for RP2040 Boards

## file_access

Yeehaw!

Notice how all the screensaver examples embed their graphics data _as tables within the code._ Any _change_ in graphics requires _new tables._ What if we could load graphics…or any other data our program might need…from files instead?

Installing **CircuitPython** initializes a board’s flash storage like a small USB drive. We then have **Arduino** libraries that can read and write files in this space.

Combining this with PicoDVI is _possible,_ but is _experimental and failure-prone._ Either one of these tasks — DVI video or USB storage — is pretty demanding, and _combining_ the two is asking a lot. In no case will it damage the board, but at worst there’s a small chance of wiping out the flash storage and having to re-load everything. Understand **this is _wild west_ stuff.** &nbsp;We’ll walk through some best practices to make this less perilous…

First, you’ll need up-to-date versions of several packages:

**Tools→Board→Boards Manager…**

- The RP2040 Arduino “core” must be version 3.3.0 or later. Setup and installation is explained in the [Feather RP2040 DVI guide](https://learn.adafruit.com/adafruit-feather-rp2040-dvi/arduino-ide-setup).

**Sketch→Include Library→Library Manager…**

- PicoDVI library 1.1.0 or later.
- Adafruit\_SPIFlash 4.2.0 or later.
- Adafruit\_CPFS 1.1.0 or later.

Then head to the [CircuitPython downloads page](https://circuitpython.org/downloads) and grab the current version for your board. Keep this file around. As mentioned above, there’s a small chance of things going awry and needing to re-initialize a board’s flash storage.

## Prepare Hardware

Install CircuitPython as you world normally. On most RP2040 boards, this involves holding down the BOOT button while connecting USB or tapping the reset button. A drive called RPI-RP2 will appear on your computer. Drag the CircuitPython .UF2 file to this drive and let it work. After several seconds, RPI-RP2 is replaced with a CIRCUITPY drive. Success! Now there’s a viable filesystem to hold files.

Drag files for your project to the CIRCUITPY drive and organize to your liking. It’s prudent to **keep a clean copy of everything organized on your computer, should a reinstall be necessary.**

The file\_access example doesn’t require any additional files. All this example does is print a directory listing. But even a fresh CircuitPython install will have a few items present.

## Compile and Upload Sketch

Open the file\_access sketch in the Arduino IDE. Select your board type from the “Tools” menu rollover…

**Tools→Board→Raspberry Pi RP2040 Boards→** (board name)

Then…important detail or the sketch won’t compile…select the TinyUSB stack, which helps make the CIRCUITPY drive accessible to Arduino code and the host computer…

**Tools→USB Stack→Adafruit TinyUSB**

Then you can compile and upload to the board as you would any other sketch.

## In Action

To keep the code brief and instructive, all this example does is print a listing of the CIRCUITPY root directory to a connected DVI display. But…selecting or previewing different files from the attached host computer, you’ll see it prints the listing again. Selecting a file updates the last “touch” time for that file, which constitutes a change. You can also try dragging files to or removing files from the CIRCUITPY drive, and should get an updated listing with each change.

This example uses a text mode, but the same principles can be applied to graphical programs as well. Most won’t even need the change-detect behavior…whatever data is loaded on start-up, that’s what you get in that case.

## Code Explainer

The PicoDVI parts are already explained in other examples and won’t be covered here. Only the file-specific additions are mentioned.

First up, an extra library header and global object are declared:

```cpp
#include &lt;Adafruit_CPFS.h&gt; // For accessing the CIRCUITPY drive

FatVolume *fs = NULL; // CIRCUITPY flash filesystem, as a FAT pointer
```

Adafruit\_CPFS ( **C** ircuit **P** ython **F** ile **S** ystem) is an Arduino library that helps make the CIRCUITPY flash filesystem available both to our Arduino sketch and presents it to a host computer over USB. `fs` in this case is a global pointer to that filesystem object; initially NULL as we’ve not set it up yet.

A little later, in the `setup()` function, `fs` is then initialized by calling `Adafruit_CPFS::``begin` like so:

```cpp
fs = Adafruit_CPFS::begin(true, -1, NULL, false);
```

This is an unusual syntax in that all Adafruit\_CPFS functions are “static” — they do not require declaring a special object type (though one can if desired), the flipside being that all function calls to that library begin with `Adafruit_CPFS::`, with the two colons.

More of interest here are all the arguments passed to the `begin()` function:

- The initial `true` argument tells the library to make the filesystem available _both_ to our sketch and to a host computer over USB.
- The `-1` and `NULL` arguments have no meaning here. The Adafruit\_CPFS library works on a variety of different hardware, and these are specific to other devices. Necessary but ignored here.
- The trailing `false` argument is _critical_ when combining Adafruit\_CPFS and PicoDVI on an RP2040 microcontroller. This keeps the second core running at all times (where the DVI-generating code resides), where CPFS would normally want to pause it during writes.

If this were a non-PicoDVI project, all those arguments would be unnecessary, and `begin()` would just be called like so:

```cpp
fs = Adafruit_CPFS::begin();
```

A few lines down, after the DVI display has been initialized, the value of the `fs` variable is checked to see whether the flash filesystem is present and working:

```cpp
if (fs == NULL) { // If CIRCUITPY filesystem is missing or malformed...
    // Show error message &amp; fast blink LED to indicate problem. Full stop.
    display.println("Can't access board's CIRCUITPY drive.");
    display.println("Has CircuitPython been previously installed?");
    for (;;) digitalWrite(LED_BUILTIN, (millis() / 250) &amp; 1);
  } // else valid CIRCUITPY drive, proceed...
```

And, after that point, `fs` can be treated like a normal filesystem object, say if you’ve used the Arduino SD library before. Only difference is, because it’s a _pointer_ to an object, one uses C’s _indirection_ syntax `->` rather than `.`:

```cpp
fs-&gt;open("settings.json", FILE_READ);
```

## Avoiding _The Crash_
The prospect of sometimes re-loading a board is frustrating but also interesting how it came about. The RP2040 chip has no internal flash memory; code is read from external flash, which _also_ holds the CIRCUITPY flash filesystem. It’s _not possible_ to simultaneously write or erase one part of flash while executing code from another…normally, all activity has to stop for this, which PicoDVI (generating time-critical signals) abhors. Getting all these to even _sort of_ play nice together took some work.

Here are some steps one can take to avoid The Crash:

- Consider having your Arduino sketch treat the CIRCUITPY drive as **read-only** ; steer clear of writing or creating new files, and open files in `FILE_READ` mode only. There’s nothing _preventing_ writing files, and you’re welcome to try and see how it goes, but you take your chances. This is why the [CP/M emulator project uses a microSD card](https://learn.adafruit.com/rp2040-runcpm-emulator-with-usb-keyboard-hdmi-screen) rather than the internal flash; there’s no contention for resources that way.

- For a crucial situation or live demo, don’t power the board off a computer’s USB port, and instead use a passive source like a USB power supply or a LiPoly battery. If there’s nothing trying to mount the drive and touch files, operation should be reliable.

- Consider adding a **switch or button** to your circuit that can be **polled on startup** , and only present the filesystem over USB in one state or the other using the first argument to `Adafruit_CPFS::begin``()` (see code snippet above). An initial `true` value makes the drive computer-writable, while `false` makes it so _only_ the Arduino code sees the drive.

- Perhaps take the switch idea one step further and _don’t even start the PicoDVI display_ when the drive is mounted. With the switch one way, the computer sees the CIRCUITPY drive and you can load up new files, but there’s no display. Switched the other way, video works but the computer won’t see the drive. This should be fairly robust.


## Featured Products

### Adafruit Feather RP2040 with DVI Output Port - Works with HDMI

[Adafruit Feather RP2040 with DVI Output Port - Works with HDMI](https://www.adafruit.com/product/5710)
Wouldn't it be cool if you could display images and graphics from a microcontroller directly to an HDMI monitor or television? We think so! So we designed this RP2040 Feather that has a digital video output (a.k.a DVI) that will work with any HDMI monitor or display. Note it doesn't do...

In Stock
[Buy Now](https://www.adafruit.com/product/5710)
[Related Guides to the Product](https://learn.adafruit.com/products/5710/guides)
### Raspberry Pi Pico RP2040

[Raspberry Pi Pico RP2040](https://www.adafruit.com/product/4864)
The Raspberry Pi foundation changed single-board computing [when they released the Raspberry Pi computer](https://www.raspberrypi.org/archives/723), now they're ready to do the same for microcontrollers with the release of the brand new **Raspberry Pi Pico**. This...

In Stock
[Buy Now](https://www.adafruit.com/product/4864)
[Related Guides to the Product](https://learn.adafruit.com/products/4864/guides)
### Adafruit DVI Breakout Board - For HDMI Source Devices

[Adafruit DVI Breakout Board - For HDMI Source Devices](https://www.adafruit.com/product/4984)
We designed this little breakout board after seeing [some cool demos for the Raspberry Pi Pico driving an HDMI display](https://github.com/Wren6991/PicoDVI). By using some fun 'abuse' of overclocking and the RP2040's PIO system a low-cost microcontroller can have great...

In Stock
[Buy Now](https://www.adafruit.com/product/4984)
[Related Guides to the Product](https://learn.adafruit.com/products/4984/guides)
### Pimoroni Pico DV Demo Base - RP2040 DVI Multimedia Board

[Pimoroni Pico DV Demo Base - RP2040 DVI Multimedia Board](https://www.adafruit.com/product/5674)
Intrigued by the possibilities of&nbsp;[VGA Demo Base](https://shop.pimoroni.com/products/pimoroni-pico-vga-demo-base)&nbsp;but no longer own a D-sub cable? No problem! This board is an all-digital conversion of Raspberry Pi's VGA reference design, great if you...

No Longer Stocked
[Buy Now](https://www.adafruit.com/product/5674)
[Related Guides to the Product](https://learn.adafruit.com/products/5674/guides)
### HDMI Cable - 1 meter

[HDMI Cable - 1 meter](https://www.adafruit.com/product/608)
Connect two HDMI devices together with this basic HDMI cable. It has nice molded grips for easy installation, and is 1 meter long (about 3 feet). This is a HDMI 1.3 cable.

We're now stocking a very fancy Official Raspberry Pi cable with overmolding and a Pi logo. Please note...

In Stock
[Buy Now](https://www.adafruit.com/product/608)
[Related Guides to the Product](https://learn.adafruit.com/products/608/guides)

## Related Guides

- [Adafruit DVI Breakout Board](https://learn.adafruit.com/adafruit-dvi-breakout-board.md)
- [Adafruit Feather RP2040 with DVI Output Port](https://learn.adafruit.com/adafruit-feather-rp2040-dvi.md)
- [Vintage computer to HDMI with Feather DVI & CircuitPython](https://learn.adafruit.com/vintage-computer-to-dvi-with-feather-dvi-circuitpython.md)
- [RP2040 RunCPM Emulator with USB Keyboard & HDMI screen](https://learn.adafruit.com/rp2040-runcpm-emulator-with-usb-keyboard-hdmi-screen.md)
- [Feather RP2040 DVI CircuitPython Day 2024 Countdown Clock](https://learn.adafruit.com/feather-rp2040-dvi-circuitpython-day-2024-countdown-clock.md)
- [NES Emulator for RP2040 & RP2350 DVI Boards](https://learn.adafruit.com/nes-emulator-for-rp2040-dvi-boards.md)
- [Feather RP2040 DVI Video Synth](https://learn.adafruit.com/feather-rp2040-dvi-video-synth.md)
- [Case for Feather RP2040 DVI](https://learn.adafruit.com/case-for-feather-rp2040-dvi.md)
- [USB-PD Hacks](https://learn.adafruit.com/usb-pd-hacks.md)
- [Commodore Keyboard to USB HID with CircuitPython](https://learn.adafruit.com/commodore-keyboard-to-usb-hid-with-circuitpython.md)
- [Adafruit WINC1500 WiFi Shield for Arduino](https://learn.adafruit.com/adafruit-winc1500-wifi-shield-for-arduino.md)
- [PicoDVI Adafruit IO Feed Dashboard](https://learn.adafruit.com/dvi-io.md)
- [Halo Energy Sword RP2040](https://learn.adafruit.com/halo-energy-sword-rp2040.md)
- [Crayola Scented Candle](https://learn.adafruit.com/crayola-scented-candle.md)
- [How to Set Up 2-Factor Authentication on Adafruit](https://learn.adafruit.com/how-to-set-up-2-factor-authentication-on-adafruit.md)
