small coat of arms
Back to the lab

80 column PIC video terminal

You can get this 80 character by 25 line video terminal out of a PIC18F26J11 microcontroller using only 4 resistors and a crystal*

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

Minimum Hardware Required


  1. PS/2 Keyboard (they're practically free)
  2. Any composite video display (monitor, TV, projector)
  3. PIC18F26J11 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 (I used an LM1117-33 chip)
  9. two 4.7k ohm pullup resistors

*You need a 48mHz PIC with 3700+ bytes of RAM. For 40 column mode you only need 1536 bytes of RAM.

The complete hookup:

  1. Run the PIC at 3.3 volts
  2. Hook the crystal up to the OSC1 and OSC2 pins.
  3. Connect one 18pF capacitors from each side of the crystal to ground.
  4. Connect pin C2 to a video output connector via a 680 ohm resistor.
  5. Connect pin C7 to a video output connector via a 330 ohm resistor.
  6. Connect the video output connector to the composite input of a TV or monitor. Make sure you have both signal and ground connections.
  7. Connect the PS/2 keyboard to +5V and ground
  8. Connect the PS/2 keyboard clock line to pin C3 (SCL).
  9. Connect a 4.7k pullup resistor between C3 and +5V.
  10. Connect the PS/2 keyboard data line to pin C4 (SDI).
  11. Connect a 4.7k pullup resistor between C4 and +5V.
  12. Connect a serial port to pins

Theory of Operation

R2 and R3 form a primitive 2-bit DAC. The television itself 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 I 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.

The PS/2 keyboard interface is implemented using two pullup resistors and the SPI peripheral. Because of the start bit inherent in the PS/2 protocol this is only a 7-bit interface which leads to a couple of limitations with the scancodes. The numeric keypad zero key works but only transmits a code upon key release (or typematic repeat). The F5 and F7 keys cannot be distinguished- pressing F5 is the same as F7. Otherwise I was able to communicate with the 8-bit keyboard (plus parity) using only 7-bits. The PS/2 keyboard handler runs during the horizontal back porch. The PS/2 assembly code always take the same number of instruction cycles no matter which code path is taken in order to prevent glitching the video output. When the CAPS LOCK key is pressed we must disable interrupts briefly in order to transmit two bytes to toggle the LED. Hitting the CAPS LOCK key glitches the video output for a moment.

It is possible to use keyboard scanset-1 (XT keyboard) to restore the functionality of the F7 key and the numeric keypad zero but many modern keyboards only support the default scanset-2. If you're interested in the details take a look at this site Some legacy keyboards also support scanset-3 but it's not even mentioned in the latest Microsoft keyboard specification.


  1. *You can use almost any 48mHz PIC that has 3700+ bytes of RAM and a 4X PLL clock option . This project was designed for the PIC18F26K22 which has two UARTs and runs at 64mHz but that chip is not available yet. I used an overclocked PIC18F26J11 (48 mHz) instead. With a 4X PLL we run the part at 57.3 mHz, a mere 20% above its rate speed. If you don't like overclocking you can use a PIC18F26K20 but you won't have the second UART to communicate with a host computer. You can't really use a software UART without shutting off the video interrupt temporarily.
  2. Uncomment the line #define DOUBLE40 for a 40 column display. In 40 column mode you can use a PIC with only 1536 bytes of RAM.
  3. Only the industrial versions of the 18F26K20/22 are rated at 64mHz. The extended temperature version is only rated at 48 mHz. I'd be willing to bet a toonie that they work just fine at normal temperatures.
  4. Change to #define CRYSTAL13 if you have a 13.5 mHz crystal. This gives you CCIR-601 square pixels. You may have to fuss with the #defines for the overscan margins depending on your TV set.
  5. 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.
  6. You need a PIC with a full 3700+ bytes of RAM because an 80 column video display takes 2000 bytes.
  7. For better results use the luma channel of an S-video connection.
  8. For best results use the component video "Y" input on your HDTV (the green plug).
  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:

This board has some extra features. The MAX3232 is optional for adding RS-232 voltage levels. The 23K256 is for future use. The audio circuit has not been tested.

This single layer board was built using PCB-GCODE for Eagle PCB 5.6 and a Tormach PCNC1100 milling machine. Zero ohm resistors on schematic may be replaced with jumper wires.


Source code

( CCS C-compiler)


80 column HEX file 80terminal.hex
Eagle PCB Schematic 80terminal.sch
Eagle PCB Board 80terminal.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 12, 2010