The Raspberry Pi Pico is an incredible little microcontroller. While it does not have a video output interface built-in, like it’s bigger Raspberry Pi Zero / 1 / 2 / 3 / 4 / 400 siblings (HDMI / double HDMI in their case), it is possible to add a video output to the Pico! (As VGA or DVI over an HDMI connector, read on for details)
In this post, we’ll explain why a video out is a very special feature for microcontrollers, and which types of video out you can build or purchase for your Pico. We’ll also show you some sample code – already precompiled for you, so you can download it directly, and then we’ll talk about what you need to modify to make it run.
Note: the Pico does not have a Linux operating system like the Raspberry Pi Zero W for example. So you’ll most likely have to dive a bit deeper into learning how to code & upload your applications to the Pico. If that’s not your thing, the Pi Zero W might be a better fit to start playing around 🙂
If you’ve succeeded in doing your own video project with the Raspberry Pi Pico, let us know in the comments!
Why is adding a Raspberry Pi Pico Video output a challenge?
The Raspberry Pi 400, for example, is based on the BCM2711 SoC. This system-on-a-chip has specialized hardware which takes care of the video output, perfectly preparing it in the format which is specified for the particular video interface(s), two HDMI ports in the case of the Pi 400. It also has a massive memory (of 4 GB), in which to store the video output data.
To understand this better we need to look at some fundamentals of computer video output:
Video display and transmission
We see the display on the screen “at once”. Our brain has massively parallel wiring, in which it ingests information from all available retina cells at once (cone cells and rod cells).
(* the mechanism of transmission into the brain integrates information across several retina cells, but the fact remains that a lot of information is transmitted in parallel. Look into bipolar cells if you’re interested to know more)
The brain, however has latencies – it is not able to resolve changes in visual stimuli faster than 13 ms. (which is about 1 in 75 frames per second).
For us this means that if we want to display a really smooth animation, we need to show about 60 different static pictures per second. Our brain will interpret these static pictures as smooth and life-like playback.
Typically, we want these pictures to be in color.
We have three different photoreceptors for color, so the screen displays only these three different colors with different brightness. Again, our brain will synthesize the colors in between from this information it gets. (On a sidenote, purple does not really exist as it’s own wavelength – it’s a mixture of red and blue).
There are three primary colors:
Together, they are known as RGB. When you add all three of them, you get white. Adding red to green yields yellow, adding green to blue yields cyan, adding blue to red yields magenta.
So what we really want to output are:
- three different colors,
- each with a different brightness value
- ideally about 60 different values per second (60 Hz)
- a certain screen resolution – e.g. 640 x 480
Let’s calculate how much data this is per second:
- 3 colors
- x 8 bit color depth
- x 60
- x 640 x 480
= 442.368.000 bits per second = ca. 422 MBit/s
(Consider that for example the USB v1.1 interface on the Pico has about 10 Mbit/s – about 40 times less throughput!)
To output this information, ideally you would also want to hold this information as a bitmap in your RAM – one part of your application will update the image in the RAM, while another will take care of delivering the data in a format which a monitor can work with. This needs to be RAM, since we are operating at high speeds, and need to be able to read this reliably with small latencies – which Flash memory would not provide.
Let’s calculate how much RAM we need:
- 640 x 480 (resolution)
- x 3 (colors)
- x 8 bits (color depth)
= 7372800 bits = 900 kBytes (note 1 Byte = 8 Bits)
While 900 kBytes will fit many times over into the RAM of a Raspberry Pi 400, the Pico only has 264KB RAM.
As you see, we will need to reduce some of the parameters (e.g. resolution, color depth, …) if we want to fit a bitmap into the RAM, or we need to come up with some clever ideas of how to make it work without storing everything!
Pico’s RP2040 PIO allows to output video data
Finally, while the Raspberry Pi 400 and other Raspberry Pi models have dedicated hardware circuitry to process all this information and output it in a reliable manner, the Pico doesn’t have special circuits dedicated just for video output.
But it has one trick up its sleeve! The RP2040 supports PIO (programmable IO). The PIO is dedicated to emulating different interfaces with precise timing, and it is very very powerful! It can be programmed to read from the RAM and output at high speeds.
We will be using the PIO to output video to some GPIO pins, and will use some additional circuitry (resistors) to bring the signal into the desired condition, depending on the video output interface we want to connect the Pico to.
Video output format history
Before getting into the meat of how you can add a video output to your Raspberry Pi Pico, let’s have a look at a little history of video output formats.
As discussed already, the human eye is not capable of detecting changes in images faster than about 13 ms. One of the first approaches to build computer monitors was therefore the CRT (cathode ray tube) monitor.
The CRT has three ray guns, which sweep pixel by pixel, line by line. (and then need some time to return to the starting point). The screen has colorful phosphors, which will continue to emit light for a while after the ray has passed through it. On the next sweep of the ray gun over this particular pixel the intensity of the ray might have changed, which we’ll see as a darker pixel. Our brain fuses the adjacent colorful phosphor pixels into one pixel, and is not able to notice the brightness changes between ray sweeps.
This way the illusion of a moving image is created.
In effect they don’t need to have all the data present at the start of the image – but just the current pixel brigthness value. The intesity of the ray will be modified according to this. We can use an analogue signal for this – for example, increasing the voltage will increase the brightness.
We need three different wires for the different colors (to drive each ray gun individually), and we need to have a way to let the monitor know when a new line needs to be started, and when a new image has to be started (when all lines have been shown).
VGA (video graphics array)
VGA was designed with these CRT monitors in mind. It is still reasonably common as an input on monitors, even though it’s becoming increasingly obsolete as we shift to completely digital transmission (more on that later).
This is also the easiest video output standard to get working on the Pico.
Next to a specification (with default resolution modes), it specified a connector, the VGA connector:
It has 15 pins:
- RED (red video)
- GREEN (green video)
- BLUE (blue video)
- ID2/RES (reserved)
- GND (Ground HSync)
- RED_RTN (red return, analog ground for red)
- GREEN_RTN (Green return, analog ground for green)
- BLUE_RTN (Blue return, analog ground for blue)
- KEY/PWR (+5 V DC powers EDID EEPROM chip on some monitors)
- GND (Ground VSync, DDC)
- ID0/RES (reserved)
- ID1/SDA (I2C data since DDC2)
- HSync (Horizontal Sync)
- VSync (Vertical Sync)
- ID3/SCL (I2C clock since DDC2)
Note: VGA cables can support different resolutions, color depths and refresh frequencies, whereas the word “VGA” when referring to the resolution usually means 640 x 480.
As you see, there are three wires carrying the image data, one for each color. The signal is transmitted with a peak (maximum) voltage of 0.7 V. The signals transmitted for the colors are analog in nature – higher voltages increase the brightness, a voltage of 0 means the pixel is dark / off.
Making VGA output work on the Pico
This means that the Pico, with it’s 3.3V digital output has high enough voltages to be able to drive these RGB pins into the VGA cable (which expects voltages of 0 – 0.7V). We actually need to reduce the voltage by using resistors.
We can build a simple DAC (digital analog converter) by combining several resistors and GPIO pins. Depending on the combination of GPIO pins which are active at any given moment, we have different voltage levels (= levels of brightness):
As you can see in the picture above, five GPIOs (0 -4) drive one channel (red in this case), which gives us a five-bit depth. The resistors are weighted 1:2:4:8:16, for example the least significant bit (LSB) of red has a 8.06K resistor.
When trying to build this circuit, you should go for 1 % tolerance resistors to get a good picture.
Please refer to “Hardware design with the RP2040” to understand how the resistor values were calculated. In short, if we drive all of them at once, we’ll have a voltage of 0.74 V which is OK for our purposes.
More specifically, what this reference design suggests is to support a commonly used 16-bit RGB data format (RGB-565), which uses 5 bits for red and blue, and 6 for green. We can reduce the actual physical output to the green to 5 GPIO pins as for the other colors, to save one pin.
In addition to that 2 more pins are needed for horizontal and vertical blanking timing (HSYNC and VSYNC)
This brings us to a total of 17 GPIO pins to drive a VGA output. Luckily, the Pico has 26 available GPIO pins, which allows us to drive the VGA output.
As mentioned previously, the Pico is also capable of driving these pins at the necessary frequencies and with precise timing, thanks to the RP2040 PIO feature (programmable I/O).
Raspberry Pi Pico VGA video output hardware
Raspberry Pi has designed and open-sourced a carrier board for the Pico which shows off different features:
- VGA output
- microSD slot
- audio outputs (analog PWM, digital I2S)
Purchase the Pico VGA / Audio / microSD Board
We (buyzero.de) are currently in the process of having this reference design board built and assembled for us. Contact us if you want to be notified, once the board can be purchased!
In the meantime, Pimoroni has also created a version of this board, they call it the Pimoroni Pico VGA Demo Base.
Side note: each pin of the 15 rgb output pins will still need to be driven at around 17.58 Mbit/s, which still is an impressive – but much more manageable number!
Software required to drive the VGA output
Since we’re working on the Pico, there are no “graphics drivers” we can simply install. We need to write the code ourselves… or not 🙂
Luckily, the people who designed the hardware board for us already supply some code we can use, so that we’re able to focus on our project.
Example code you can use can be found in the pico-playground Repository:
In this repository, there is a simple movie player called popcorn (which plays movies in a custom format). A big buck bunny, 1.6 GB in size can be downloaded here. Note that these are raw disk images to be written to an SD card – this example assumes you have the VGA demo board, which has an SD slot. Instructions for converting movies are also given.
- pico_scanvideo – includes extensive documentation!
The API outputs parallel RGB data and sync signal on pins for DPI VGA (using resistor DACs, as described above).
A neat point:
- The default PIO scanline program accepts run-length-encoded data – this means that you can save RAM to generate flat color areas (I’m thinking games here!)
In general you would want to use the pico_scanvideo library instead of developing VGA output code from scratch 🙂good coding practice
Check out the scanvideo directory of the pico-playground repository:
- mandelbrot: a mandelbrot generator using a 320x240x16 framebuffer
- sprite_demo: bouncing Eben heads (video at the top of our page!)
- test_pattern: Display color bars
Using the VGA Board
You need to pass an additional parameter to CMake while compiling:
TBD: Add step-by-step guides
DVI: Digital Visual Interface
Technology moves on. Life moves on. CRTs became more and more obsolete, superseded by more modern flat screens, which have digital interfaces. No moving beams, just pixels.
For a while, signals continued to be analog – but this is not very desirable, since we have to take something digital, transform it into something analog, and then transform it back into something digital again. The image will be less precise, and we have additional circuitry which we could dispense with.
Enter DVI. It cleverly offered an option to transmit analog signals as well, so simple DVI to VGA adapters / cables could be built. Of course, the graphics card would need to output both analog and digital data. But this helped to have the standard accepted and for it to become widespread.
We’re interested in the digital signals here (DVI-D), as we would like to bit-bang them from the Raspberry Pi Pico.
With DVI-D, the image data is transmitted in a serial way.
A “single link” (most basic) DVI connection consists of four so-called TMDS links (transition minimized differential signaling):
- pixel clock
Differential signalling is used to minimize interference (since
We have a total of 24 bits per pixel (8 bits x 3 colors), and encode the data using 8b10b encoding (8 bits are mapped to 10 bit symbols on the actual physical line to achieve DC balance amongst other things).
DVI treats the pixels similarly to VGA: everything is re-transmitted every time the image “starts” again, and the data is clocked precisely. This is like a telephone line, where the line is constantly in use when two people talk.
Note: in contrast to this, DisplayPort treats the data as packets – which has a number of advantages.
In contrast to the VGA example discussed above, since the data is sent digitally, instead of analog brightness values, this means much higher data volumes.
Luke Wren, an engineer with Raspberry Pi, believed that the RP2040 (at the heart of the Pico) would also be capable of driving DVI output, again using PIO.
Luke Wren calculated that about 252 Mbps serial data has to be driven through the GPIO digital pads – differential serial, which is emulated with two single-ended pads.
Above you see the circuit used to drive the DVI output (using an HDMI connector, more on this below) – are simply several 270 Ohm resistors.
Luke Wren has even gone further and added dual DVI output to his PicoDVI layout by a plugin board:
HDMI: downwards compatible with DVI
HDMI is the next evolution of connectors (and in competition with DisplayPort). It is fully downwards compatible with the digital DVI signals – thus you can have simple, purely passive DVI / HDMI converters.
Pico DVI Sock
The Pico DVI Sock is an easy and low-cost solution to add digital video output to your Pi. It has been designed by Luke Wren (see description above). It is a DVI output with an HDMI connector. As HDMI is downward-compatible to DVI, you can use an HDMI cable to connect your Pico to HDMI displays:
Programming the Pico DVI Sock
You can download our picodvi-test.zip and get started playing around with the .UF2 example compiles in it. It also includes a PDF which gives you a step-by-step guide.
Luke Wren provides code examples in his repository. However, to use them with the Pico DVI Sock, you’ll need to set the correct config to use. We’ll show you how in this mini-tutorial.
sudo apt install cmake gcc-arm-none-eabi libnewlib-arm-none-eabi build-essential
Clone PicoDVI repo from Luke Wren:
git clone https://github.com/raspberrypi/pico-sdk
git submodule update --init
git clone https://github.com/Wren6991/PicoDVI.git
To use the code examples with the Pico DVI Sock, you’ll need to set the correct pin config to use. Add the following line to common_dvi_pin_configs.h just before the first #ifndef
#define DEFAULT_DVI_SERIAL_CONFIG pico_sock_cfg
Building the examples
Install the examples on the Pico
The built examples will be in the software/build/apps folder.
Press and hold the BOOTSEL key on the Pico, then connect the board to your PC using the microUSB connector. Copy and paste the appropriate .uf2 file you want to try – e.g. sprite_bounce.uf2 – to the Pico.
The Pico will automatically restart, and you should be able to see output on the HDMI connector (remember it’s DVI really :-)).
You can download our picodvi-test.zip and get started playing around with the .UF2 example compiles in it. It also includes a PDF which gives you a step-by-step guide. If the sprite_bounce.uf2 example doesn’t work for you, try another monitor – possibly not all monitors are compatible with this video output.
If you’ve come so far, let us know in the comments how it worked for you & what project ideas you’ve come up with!
Purchase a Pico DVI Sock
Our sister page, buyzero.de, stocks the Pico DVI Sock in two variants:
- Pico DVI Sock only (for self-soldering) @ 6,58 € currently
- Pico DVI Sock on Pico, with pre-soldered headers @ 17,89 € currently
Sidenote: DBI & DSI displays
As a side note, the Raspberry Pi Pico Extras repository has placeholders for DBI (16 bit MIPI DBI displays – with data being passed in parallel) and DSI (MIPI serial displays), so we might see support for these displays in the future as well.