Graphics LCD Interface

A while ago I was considering making a capacitance meter using the PIC.  I did some research on the web and checked out various designs.  Then I made the mistake of checking eBay to see how much one would cost.  The single board versions I found varied, but most of them also included capabilities to check resistance, inductance, and even diodes and transistors.  All at a price that would be hard to beat by building my own.  The one that really caught my eye was one that used a graphics display to show the symbol for the part and the pin outs (handy for unknown transistors).  So I bought it.  No, I didn’t reverse engineer it and that is not the subject of this post.  Sorry for the red herring.  The actual subject is how to interface a PIC to a cool and inexpensive graphics display.

Graphics LCDs

LCD Front2

LCD Back2

The graphics LCD we will be using here is referred to as the 12864 LCD.  Like the 1602 (which means 16 characters, 2 lines) the 12864 means 128×64 pixels.  Like the 1602, the displays come in a variety of colors but the most common are white on a blue background or black on a yellow background.  The 12864 can operate as a simple text display with 4 lines of 16 characters and, in this mode, it works almost identical to the 1604 (16 characters, 4 lines) LCD.  But we aren’t here for just another text display, are we?  What sets the 12864 apart is that you can put it into graphics mode and control every one of those 8192 pixels.  That’s not much compared to the typical computer screen but it can still provide a lot of fun for not a lot of money.  I picked one up online from a China source for less than $8 delivered.  I also bought one online from a USA source for less than $11 delivered.

Before you run out and buy one of these modules you need to know that there are actually a variety of them out there and that they interface in different ways.  Some of them already have cables attached but they are usually attached to a serial interface.  What we need for this project is a module that has the 0.1 inch spacing holes like the 1602 LCDs we have been using.  Within that category there seem to be two varieties that actually use different chips and interface methods.  The one I used for this project is shown in the pictures above.  The easy visible difference between this version and the other version is the labeling of pins 15 and 16.  Here we see that pin 15 is labeled as “PSB” which means “Parallel/Serial Bus”.  That allows us to select either a parallel or serial interface.  Pin 16 is not used.  In the other common version pins 15 and 16 are CS1 and CS2 respectively.  They are used to select one of two LCD interface chips inside the module.  The version we are using here uses the ST7920 chip.  Copies of the chip specification and the LCD User Manual are provided below.



Graphics LCD 8-bit

There are a couple of things obvious in the wiring diagram above.  First, we are using a different PIC than our old favorites from the previous posts.  The 16F1847 is used for three primary reasons.  First, we are going to interface to the LCD using a full 8-bit parallel interface so we need an I/O port with that many pins.  Second, each graphics table consumes 1k of Flash memory so we need something with plenty of Flash if we want multiple displays.  Third, we will access the pixel data in the graphics table using PIC indirect addressing so we want the appropriate registers to allow more than an 8-bit address.

One other thing that is obvious in the wiring diagram above is that there are more pins (20) for the LCD interface than the 14 or 16 we are used to with the 1602 display.  Fortunately, pins 1-14 are exactly the same as those for the 1602 LCD so that makes our wiring task pretty easy.  I wired up a 14-pin header on the LCD to make it compatible with my 1602 LCD test circuits and then hardwired pins 15, 17, 19, and 20 to the correct voltages on the LCD itself.

The switch shown on PIC pin 4 is used to select either text mode or graphics mode in the software for our test setup.  It could be a jumper instead of a switch and probably wouldn’t be included for any real application unless you wanted to use a similar method for selecting between different graphics screens.   The program memory in this version of PIC is 8k so it can hold up to seven complete graphics screen definitions.

The software section includes source code for both the 8-bit parallel version of the hardware shown here and also source code modified to work with the shift register interface I detailed in Episode 1.  In the shift register version the PIC PORTB connections go away and PORTA output 0 (PIC pin 17) is added for the data sent to the shift register.

Making Graphics Images

For my example displays I just use the Paint program built into Microsoft Windows to create or edit a graphics display.  Once you set it up, you can easily edit on a pixel by pixel basis.  It is rather tedious to do it that way but it does give you full control of every pixel.  For those who may not be familiar with how to do this, here are the basic steps:

  1. Open Paint and click on the tab on the upper left.
  2. Select “Properties”.
  3. Set the Image Properties for “Pixels”, “Color”, Width = 128, and Height = 64.
  4. Although we select “Color” mode, the only colors that should be used are black and white.  Even gray scale shades can cause problems with the final picture.
  5. Click on the “View” tab.
  6. Click on “Zoom in” until the drawing window stops increasing in size.
  7. Click on the “Home” tab.
  8. You can select pre-defined shapes or draw free form or use the text tool.
  9. To edit pixel by pixel select the pencil tool.  A single click of the pencil creates one pixel.
  10. To erase a pixel change “Color 1” to white and single click on the pixel to erase.
  11. When the drawing is done, save it as a Monochrome Bitmap file.

Converting Graphics Images

I found a couple of free software downloads on the web that convert a 128×64 pixel BMP file into the hex data needed for our software.  The one I use is called “LCD Assistant” and it is very simple to use.  You do need to make sure that the image is exactly 128×64 pixels and that it is monochrome.  The other program I found is called “BMP-LCD” but it only creates data in the Vertical format so we can’t use it with our version of the graphics LCD.

When setting up “LCD Assistant”, make sure to select “Horizontal” for the “Byte Orientation”.  The size parameters will automatically get set when you load a BMP file.  It should then show 128 for the width and 64 for the height.  “Size Endianness” should be “Little” and “Pixels/Byte” should be 8.  The table name is not relevant.  When you save the output you will need to name the file and specifically make sure that you add “.txt” (without quotes) to the file name.  For instance: MyFile.txt.  The “Save as type” window in the popup window does not have any options, thus requiring the explicit naming.

The output of LCD Assistant is in a format that is directly usable if you are programming in C but it needs a little editing for use in our assembly language program.  The output consists 64 lines and each line consists of 16 bytes.  A sample line format is shown below.

0x00, 0x00, … 0x60, 0x60, 0xCF,
The editing required is to remove the last comma on each line and to add a “ data ” directive at the start of each line.  The space characters before and after the word “data” are required.  A sample edited line is shown below.

data 0x00, 0x00, … 0x60, 0x60, 0xCF

You can see two complete table examples in the software listings.  A copy of the “LCD Assistant” tool is included below.



The software links are listed below.  While they are targeted for the 16F1847, they are easily ported to other versions of the PIC.  You will need to change the line that identifies the PIC version (LIST=) and the INCLUDE file but those are intuitive changes.  The __CONFIG lines may also need tweaking just because one or two of the labels used are spelled differently in some of the INCLUDE files.  The biggest requirements are enough I/O ports, enough program memory, and 16-bit indirect addressing capability.

As we saw in the description of the LCD it has the capability of being addressed in either a serial mode or a parallel mode.  In the parallel mode it can be either a 4-bit or an 8-bit interface just like the 1602 LCD.  Since I needed to upgrade the PIC to get the necessary indirect addressing capability I ended up with enough I/O lines to easily do an 8-bit parallel interface.  I also created a version that works with the 3-wire, 8-bit interface I detailed in an earlier post.  Both assembly files are included below.

There are a few other significant differences between the 16F1847 and the older PICs we have been using.  First, the internal oscillator in the old PICs would only go up to 8-Mhz.  That has been more than fast enough for these little projects but it’s always nice to have a little extra horsepower available if you ever need it.  The 16F1847’s internal oscillator can go up to 16-Mhz on its own and up to 32-Mhz by sacrificing one of the internal capabilities.  Another significant difference is that there are a ton of new registers in the 16F1847 which means that some of the ones we need are not in Bank0 or Bank1.  That makes it more important to ensure that we have the correct bank selected when accessing registers.  One new set of registers that we don’t use here but which can be very handy are the LATA and LATB registers.  These are latch registers which hold the last value that was written to either PORTA or PORTB.  You can read the LAT registers directly if you want to do a read-modify-write operation.  You will also notice that there are two “_CONFIG” lines.  That is because all of the new options require two configuration registers.  It’s very important to make sure that you get the right options associated with the right configuration register because MPLAB will not flag this as an error.  I learned that the hard way

The selector switch shown in the schematic gets read at initialization time to determine if text should be displayed or the graphics image.  Changing the switch while power is on will not switch to the other display.  You need to power down, change the switch, then power on again.  Remember, this is just a demo setup.  The routines called for displaying text are basically the same as we used for the 1602 LCD.  The first difference is that we have four routines for four lines instead of two.  The second difference is that the starting addresses for the lines are slightly different.

The graphics mode requires that we use the “extended instruction set”.  Sounds scary, but it really isn’t.  What is actually a little more difficult is how we address the graphics memory.  The first difference is that we are addressing the memory as a matrix so we need to send the vertical and horizontal coordinates of the memory to write to.  The specification talks about vertical and horizontal addresses but that may be a bit confusing.  In effect, vertical equates to row and horizontal equates to column.  Since we are sending one byte at a time, there are 16 bytes per row which equates to 128 bits (pixels).  There are a total of 64 rows but the display memory is not totally contiguous.  It is divided into upper and lower halves, each having 32 rows of 16 columns.  In the upper half the columns always start with address 80h.  In the lower half the columns always start with address 88h.  The rows address from 80h to 9Fh in both halves.  You can get into trouble trying to send a column address other than 80h so the safe method is to always write a complete row of 16 bytes.  If you do try to write just a portion of a row you will need to send two data bytes per address.  The LCD logic stores 16 bits per memory location.

Internally, the LCD will automatically increment the horizontal (column) address every time it receives data and will automatically reset back to either 80h or 88h depending on which half of the screen we are writing to.  The vertical (row) address will not automatically increment.  The software needs to keep track of the actual row address but it also tracks the number of bytes written per row so it knows when to update the row address.  In our example we send a complete row of data and then send an address update when we change to a new row.  We also use the row address to know when to reinitialize the addresses for the lower half of the display.

We saw earlier that we defined each hex data line with the directive “data”.  In this case the data will actually be written into program memory when we program the chip.  This is a little different than our previous methods of using lookup tables where we used “RETLW” commands to return the desired value.  In our current example we simply want to read a specific memory location so we will use a different form of indirect addressing to get there.  That is one of the other reasons for using a newer PIC chip.  It provides more than just 8-bit indirect addressing.  The sample software actually has two graphics tables in it, each requiring 1k byte of program memory space.  To change the graphics display you will need to actually change two lines of code in the “Do_Graph” routine to point to either “Picture1” or “Picture2”.

One important thing to keep in mind when creating the graphics data tables in our example program is that they must start on a page boundary (xx00h) and must contain exactly 1024 bytes.  Each table is labeled separately and given a specific starting address (the CODE statement).  While each CODE definition has a label, that is not the label that we use in the program.  The labels used in the program are “Picture1” and “Picture2” which directly precede their respective lines of data.


Display Examples



Here are a couple of display examples. One is in text mode and the other one is in graphics mode.  I found the Chinese characters one online and just converted it to the right file type.  You can also use the Paint program in Windows to create your own graphics.  They aren’t exactly hi-res but still a lot cooler than plain text.  That’s it for this post.  Check out my other electronics projects.