Since I’ve always liked to understand technology from first principle, I’ve embarked on a small project to generate a VGA signal from scratch on an Arduino Uno. (On the other hand, it could also be that after all Big Data work, a small data project in the 2 KB of RAM the Uno offers sounded quite appealing.)
There are a number of related projects. For starters, there is the most impressive Craft demo built around a 20 MHz Atmega88 that sports impressive visuals and sound. For Arduino, there is for example the VGAx library (allowing output of 120x60 pixels in 4 colors) or the TVout library.
The Arduino Uno is based on a 16 MHz Atmega328 processor, so there are a number of important constraints. The most convenient way to handle getting graphics on the screen is to use a framebuffer, i.e. a region in memory that has all the information on what is being displayed and that the program can manipulate. However, with only 2 KB of memory available, one has to trade off both resolution and color depth for that convenience. An alternative is to “race the beam” and have code generate output data in real-time (in Atari 2600 fashion), which makes it hard to generate complex pictures since computation competes with outputting pixels. I’ve opted for a compromise by using a line buffer that I update after it is drawn, which allows for some interesting effects.
With 16 MHz we are limited in the numbers of pixels per frame we can write out in time. First, we need to use a VGA mode with a pixel clock that is a multiple of the Arduino’s clock. I picked the VESA 640x480 @ 75 Hz mode since its pixel clock of 31.5 MHz is roughly twice the clock of the Arduino and since the monitor I am going to use explicitly supports this mode. The Arduino’s clock is only half the speed, and therefore we can only render half the pixels per scanline and get a maximum effective resolution of 320x480. To get the monitor to display a picture, we need to output data to the VGA port with the right timings for this video mode. Each pixel corresponds to 2 cycles, so we need to write 320 cycles worth of color information to the red, green, and blue lines of the VGA port. Then we send 8 cycles of nothing (the horizontal front porch) followed by 32 cycles with the horizontal sync output set to one. 60 cycles of back porch (all outputs zero) follow, then the next scanline begins. At the end of the frame we draw 19 black lines that the monitor will use to sync to the next frame. During the second to forth lines, we set the vertical sync output to one. And that’s basically it — as long as the timings are (roughly) correct, the monitor will render a picture.1
And here is the result:
There are three effects in this program. First and foremost, the central element are Kefrens bars (vertical raster bars). For this effect, I am using a single line buffer of 106 pixels for each scanline. Every four scanlines, new blue, white, and green pixels are added to the buffer. Since the contents from the previous change persist (the same line is drawn over and over again), we get more and more vertical bars towards the bottom of the screen. After drawing a frame during the vertical sync period, the line buffer is cleared.
The sine wave motion is based on a table of 128 sine values plus some code to have a larger amplitude towards the bottom. Some twiddling was required to get this to look right with 8 bit integer math.
It takes 3 cycles to draw a pixel from the line buffer to screen, two cycles to read from memory and increment the line buffer pointer (conveniently done in a single AVR instruction) and one cycle to output the value. The line buffer is updated every 4 scanlines (for no technical but just visual reasons), which yields an effective resolution of 106x120. The color of each pixel is encoded as 7 bits: 1 for red, 3 for green, and 3 for blue. The 8th bit is used to enable the background color (more about that in moment).
The text and graphics below the Kefrens bars are hardcoded in assembly, which lets us update the color in a single cycle allowing for the full resolution of 320x480 pixels (if it’d span the full screen instead of just the bottom 6 scanlines). Any of the 7 bits of color can be used, but the color value must already be available in a register to achieve the maximum resolution. Otherwise loading a different color value takes an additional cycle (and therefore pixel). Movement is accomplished using a variable delay driven by the sine table. Since the graphics are all hardcoded using output instructions, they also do not take up any of the sparse 2 KB of RAM. Instead, they end up in the slightly more spacious 32 KB of flash memory of the Atmega328.
Lastly, the horizontal raster bars in the background are implemented over the background color bit. If the background color is enabled, an additional 5 bit red signal and a 5 bit green signal are added to the final VGA signal. Depending on the scanline, both the red and green outputs are adjusted. The gradient is achieved by reading from a table of color values, and the movement uses the same table of sine values as the previous two effects. Both the red and the green channels are adjusted independently, so when the bars overlap, they combine to yellow. When the vertical bars are added to the line buffer, the background bit is switched so no background color appears where vertical bars are drawn.
The primary 7 bit color plus the background bit use one of the Atmega328 ports (Arduino pins 0 to 7). For the 3 bit green and blue color, I am using resistor ladders while the 1 bit red line is directly connected via a resistor to the red VGA line.
A second port (Arduino pins 8 to 13) is used for the horizontal sync signal plus 5 bits for the red background channel. The third port (Arduino pins A0 to A5) contains the vertical sync and 5 bits for the green background channel.
Two PNP transistors switch the red and green background channels onto the respective VGA lines when the background bit is set to zero.
As it turns out, most parts are required for the background colors: over 20 resistors (mostly for two resistor ladders to get two channels of 5 bit color) plus the two transistors to switch the signal on and off as needed. But hey, the gradient with 32 shades does look nice…
Per the VGA standard, the peak voltage for the color signals should be 0.7 V over an impedance of 75 Ω. With the background color there are two paths to the output, so there’s a risk to get over 0.7 V (for example, by applying bright green both on the primary channel and the background channel). Therefore, care must be taken in the code to not generate this type of output.
Furthermore, the transistors don’t switch instantly, so there is a risk for background color leaking rightwards into the vertical bars. As a precaution, the background color in this demo runs into an area of blue to which it does not contribute.
The code is written to be cycle-correct, so a lot of counting was involved. It does not use timer interrupts. Instead, I’ve squeezed in calculations manually into the blanking periods, which makes it a tad easier to reason about timings. As far as timings go, my monitor appears to be forgiving about an extra cycle here and there, but things start to get jittery if it is unbalanced and varying from line to line. If timings are too far off, the monitor stays black (and debugging that without an oscilloscope involves a lot more counting).
If you’re curious, my code’s available over at GitHub.
There is also the issue of sync polarity for both the horizontal and vertical sync signals, but most modern monitors do not care (so neither did I).↩