small coat of arms
Back to the lab

80 column PIC video terminal

You can get this 80 character by 25 line video display out of a PIC18F25K20 microcontroller using only 2 resistors and a crystal*

Click here for a complete PIC based video terminal with RS-232 port and PS/2 keyboard interface.

80 x 25 character display on $99 Coby 12" LCD television using composite video input.

Minimal Hardware Required


  1. PS/2 Keyboard (they're practically free)
  2. Any composite video display (monitor, TV, projector)
  3. PIC18F26K22 MCU
  4. 14.31818 mHz crystal (4x NTSC colorburst)
  5. two 18pF capacitors
  6. a 330 ohm resistor
  7. a 680 ohm resistor
  8. a 3.3 volt power supply (or LM1117-33 chip)
  9. two 4.7k ohm pullup resistors

Optional Hardware

  1. MAX3232 RS-232 voltage level converter plus capacitors and DB9 connector
  2. 23K256 SRAM
  3. 2.2K ohm and two 470 ohm resistors for audio
  4. USB connector for power supply (use a spare cellphone charger)
  5. Switchcraft RCA connectors for video and audio
  6. MINI-DIN6 connector for PS/2 keyboard
  7. Headers for auxiliary I/O and SPI I/O.

The complete hookup:

  1. Run the PIC at 3.3 volts
  2. Hook the crystal
  3. Connect pin C2 to a video output connector via a 680 ohm resistor.
  4. Connect pin C7 to a video output connector via a 330 ohm resistor.
  5. Connect the video output connector to the composite input of a TV or monitor. Make sure you have both signal and ground connections.
  6. Connect PS/2 keyboard to +5V and ground
  7. Connect PS/2 keyboard clock line to SCL () with a pullup to +5v.
  8. Connect PS/2 keyboard data line to will a pullup to +5v.

Theory of Operation

R2 and R3 form a primitive 2-bit DAC. The television is an important part of the DAC because it provides 75 ohm termination. If you are measuring the voltage output without a TV attached be sure to add a 75 ohm resistor between video output and ground. The resistor values were chosen to get as close to the standard video levels as possible: 0.0 volts (sync), 0.3 volts (black), and 1.0 volts (white). If you substitute a 5.0 volt PIC you will need to change the resistor values.

Pin C2 is the sync pin.When it's high the video signal is ~0.3 volts (black). When it's low the video signal is ~0 volts (sync). Pin C7 is output from the video shift register. When the sync pin is high a high on C7 produces ~1.0 volt which is our white level. When it's low the output is ~0.3 volts which is our black level. It is possible to get gray scale pixels by raising C7 and lowering C2 but we do not use this combination.

Timer2 is programmed to trigger an interrupt at the beginning of each scan line. With a 14.31818 mHz crystal it fires every 908 instruction clocks (it would be perfect if it fired every 909.09 clocks).

The PIC's EUSART is used in master synchronous transmit mode as a high speed video shift register. The baud rate generator is set to zero which give us one pixel per instruction cycle (14.31818 megapixels/second). A tight loop written in assembly language loads the character code from RAM, converts it to a bitmap via a second lookup from the character generator (in ROM), and writes the byte into the transmit register. Each pass through the loop takes 16 instruction cycles and outputs two characters.

Because the user program only runs when the interrupt handler is not busy we try to spend as little time inside the interrupt as possible. Upon entering the ISR we start our 4 microsecond sync pulse before we save the CPU registers. Once the registers are saved we turn the sync off. Right now we are not doing any useful work during the backporch (8 microseconds per line) so this would be a good time to do something like service a PS/2 keyboard port.

I originally tried to implement 40 column mode by using a lower baud rate. I found that the EUSART would leave a one pixel gap between characters when I did that. As a workaround I created a lookup table which translates 8-bit wide font data into double pixel 16-bit wide output. This consumes 512 bytes of precious RAM. Another solution would be to use a 27mHz (or 28.636)crystal in HS mode (no PLL) This would give a pixel clock of 6.75 mHz (or 7.159). This would save the 512 bytes wasted on lookup table RAM and allow you to use almost any PIC without overclocking. The downside is that user programs would only have half as many CPU cycles available. Maybe I'll post a 40 column version of the code for a 27mHz crystal.

I did try to use the MSSP (SPI & I²C module) instead of the EUSART but it is not capable of continuous transmission and it left a gap between characters. If you don't "mind the gap" you could use the MSSP and free up the EUSART for communications.


  1. The PIC18F26K20 is rated at 64mHz. With a 4X PLL we run it at 57.3 mHz which (div 4) gives an instruction cycle of 69.84 nanoseconds. You might be able to overclock a slower PIC if you don't have an 18F26K20 handy.
  2. Only the industrial version of the 18F26K20 is rated at 64mHz. The extended temperature version is only rated at 48 mHz.
  3. Change to #define CRYSTAL13 if you have a 13.5 mHz crystal. This gives you CCIR-601 square pixels.
  4. If all you have is a 16mHz crystal you can #define CRYSTAL16. You will have a large overscan area if you run at 16 mHz. It is also possible to use the PIC's internal 16mhz oscillator instead of a crystal but the display will be wavy.
  5. You need a PIC with a full ~3800 bytes of RAM because an 80 column video display takes 2000 bytes.
  6. For better results use the luma channel of an S-video connection.
  7. For best results use the component video "Y" input on your HDTV (the green plug).
  8. Uncomment the line #define COLUMN40 if you only want a 40 column video display. 40 column mode still takes more than 1536 bytes (it won't fit an 18F25K20) because it uses a 512 byte lookup table to double stretch the pixels.
  9. All the video is generated from inside a global interrupt handler leaving the processor free to run your own code without polling or timing restrictions. The TIMER2 interrupt runs at 15.750 kHz which is the horizontal scan rate of NTSC video.
  10. The code uses the EUSART in master synchronous mode as a video shift register. This means no UART is available for communication. If you need a serial port you could try get get your hands on a PIC 18F26J22 which has a second EUSART.
  11. The PIC 18F26K20 has hardware slew rate control of its output pins. Changing the slew rate affects the quality of the video. The best setting depends on the display device you are using. A high slew rate works well on an HDTV. A lower slew rate works well on an analog CRT television.
  12. This was compiled using version 4.112 of the CCS compiler. It will not work correctly on version 4.095, so make sure you have the latest version of the compiler.
  13. The ROM font is 8X8 pixel ASCII. The upper 128 characters are inverse video. There are some special symbols in the lower 32 characters. I used the freeware CosmicVoid CFE_64 Font Editor to create the custom font and a quick and dirty program I wrote with Microsoft Visual C++ Express to convert the font file into interleaved ROM data.



This project was inspired in part by Rickard Gunée's PIC video PONG and Tetris and partly by Don Lancaster's 1978 classic The Cheap Video Cookbook.

The next step is to finish support for a PS/2 keyboard interface. Once that's done and the PIC18F26K22 becomes available (later this quarter) we can use the second EUSART as standard serial port, creating a full fledged ANSI video terminal using only a single chip. Stay tuned!

If you have questions or comments send me some email:

Single layer board was build using PCB-GCODE for Eagle and a Tormach PCNC1100 milling machine.


PIC Source code80columnvideo.c
80 column HEX file80columnvideo.hex
Eagle PCB Schematic80column.sch
Eagle PCB Board80column.brd
*OK, so you actually need two capacitors for the crystal and you probably want a pull-up resistor on the reset line, but it will work without them.

September 5, 2010