Using an LCD's User Defined Character Generator for Graphics Animation A Special "Thanks" to Myke Predko for submitting the following project/article. About the Author Myke Predko is the author of " Programming and Customizing the PIC Microcontroller", the "Handbook of Microcontrollers" and "Programming and Customizing the 8051 Microcontroller" as well as the soon to be released "PC PhD" and "PC Interfacing Pocketbook" which are all published by McGraw-Hill.As well as writing books on electronics and programming, Myke works for Celestica, Inc. in the area of New Products Test Engineering. His wife, Patience, and he have three children, Joel, Elliot and Marya. This project uses Myke's 2-Wire LCD
Interface from last week Last week, I showed how the PICMicro could be connected up to a Hitachi 44780 LCD using only two wires. This week, I wanted to use this circuit and show how simple graphic animation can be displayed on an alpha-numeric LCD display very easily. The technique used is "Character Rotation", in which motion is simulated in a manner that is very similar to cartoon animation. In cartoon animation, a series of pictures are photographed in which each one is slightly different from the previous one in the sequence. When these pictures are displayed rapidly enough (usually greater than 15 times per second), the human eye perceives that only one picture is displayed and the characters on the picture are actually moving. By photographing these pictures onto movie film and running them at more than 15 frames per second, the average human can be convinced that a road runner can dupe a coyote into running into his own leg hold trap and then over a cliff where a cannon ball will crush him into an accordion. A similar process and result can be accomplished with a Hitachi 44780 based LCD Display. The basic 44780 has an approximate ASCII character set built in. When an ASCII character is sent to the display, it is converted into a seven high by five wide series of "pixels" (dots) which appear, to the user as a character. I say that the character set "approximates" the ASCII character set because some characters (most notably the backward slash - "\") are not present and the ASCII Control characters (everything less than 0x020) show up as characters and do not perform their assigned actions. Part of the area reserved by ASCII for Control Characters, from 0x000 to 0x007, can be used by an application to display custom ("user defined") characters. These eight characters can be set up and used by an application very easily. The user defined characters have their pixel patterns stored in a separate memory area within the Hitachi 44780 known as the "CGRAM". This is an acronym for "Character Generator Random Access Memory". Inside the 44780, character pixel patterns are generated from memories. For the most part (248 characters), this is a "ROM" ("Read Only Memory") character generator which has been predefined and cannot be changed or defined by the user. The remaining eight characters have a "RAM" character generator which can be written to and when the representative character is to be displayed, the user defined pixel patterns is used instead of a predefined one. This aspect of user or application pixel definition is why I usually refer to these eight characters as the "user defined characters". If no pixel pattern is written to the 44780 and a character from 0x000 to 0x007 is displayed, then a random pixel pattern will be displayed. If you power down the LCD display and display the character again, chances are the pixel pattern will be different. This is because the RAM does not power up to a set value and means that if you want to use the user defined characters, you have to make sure they are properly defined before they are displayed. Each byte within the CGRAM is used to display a line of pixels on the LCD display. The actual character definition is shown in the diagram below.
As can be seen in the diagram, the first line (or row) of pixels is at the character starting address in CGRAM with each line, going down, at an incrementing offset. When the 44780 was designed, the engineers took into account how people think and made it easy to figure out which bits are displayed for each row. Bit 0 of the row's byte is on the right hand side of the eight by five character block; so to define a row, each pixel is represented as a bit in the byte and if the pixel is to be dark a "1" is placed in the byte that defines the row. This is probably a bit hard to understand, to make it easier, I want to go through an example character that I created for the application that is presented later in this article. To define a character, I usually take a piece of graph paper, outline an eight by five square box and draw the character inside it as I've shown in the diagram below:
Once the character is designed, I then figure out what each byte will be by converting each dark pixel to a "1" and each light pixel to a "0". As can be seen in the diagram above, I usually do this down the right side of the diagram and convert each row into a hex number. The number itself can be anything from 0x000 to 0x01F. Defining the characters is actually quite straightforward and can be done quite easily following the method I have outlined here. If you try to scribble something down or create characters in your head (or in the application source code), you'll probably find that you will make some pretty horrendous mistakes. The nice thing about the character graphics is, even if you make a mistake they're pretty easy to fix later. The "cursor" inside the 44780 can point to the display RAM (which contains the ASCII characters that are mapped to the LCD display output) or the CGRAM. To move between the two memory areas, the "Move Cursor to CGRAM" and "Move Cursor to Display" instructions are used. To load characters, the "Move Cursor to CGRAM" instruction is sent to the 44780. The lower six bits of this instruction is the row address of the CGRAM to start writing to. After the first byte is written, then all subsequent bytes will increment to the next row address. Normally when writing to the CGRAM, you should start at the top row (offset equal to zero) of the character. This is calculated by multiplying the character number by eight. This means that character 0x000 will have its character definition bytes starting at offset 0x000 in the CGRAM, for character 1 the character definition bytes will start at offset 0x008, character 2's character definition bytes will start at offset 0x010 and so on. To load the CGRAM for Character 0, the following code sequence is used:
SendINS( 0x048 ); // Move 44780's internal cursor to CGRAM Address 1
SendCHAR( TopRow_Value ); // Send the eight character definition bytes
SendCHAR( SecondTopRow_Value );
SendCHAR( ThirdTopRow_Value );
SendCHAR( FourthTopRow_Value );
SendCHAR( FifthTopRow_Value );
SendCHAR( SixthTopRow_Value );
SendCHAR( SeventhTopRow_Value );
SendCHAR( BottomRow_Value );
SendINS( 0x080 ); // Move 44780's internal cursor to display RAM
// Address 0
Before going on to using user defined characters for animation, I want to make three points about creating character graphics. The first is the displayed character size. In the text above, I note that each character is usually seven pixels by five pixels, but in the user defined character above and in the two diagrams, I discuss that each character has eight rows and not seven. In actuality, each character does have eight rows which can be used for the user defined character pixel pattern. By convention, only seven rows are normally used with the eighth (bottom) row reserved for the LCD's cursor. Normally when I define a custom character, I make the bottom row's byte zero (0x000) to allow the cursor to be displayed. This is not critical, especially if the cursor is turned "off" by resetting the "C" bit of the "Enable/Display Cursor" instruction byte of the 44780, but something to be aware of because it may result in a character looking different depending on whether or not the cursor is below it or if the cursor is hidden by the character. The last point is understanding how the conventions in your application work. Normally when I program, I define the end of a string to be a 0x000 or "Null" character. If you look back over what I have written, you should realize that this character can be a user defined character. To prevent this from being a problem, when I create applications which have user defined graphic characters, I try to avoid using character "0" unless I absolutely have to. If character "0" is needed for a unique character, then the string end is changed to something like "$" (which is the MS-DOS string termination character). The "Animate" application presented in this article uses the same hardware as I demonstrated in my first article; The Two Wire LCD Interface. Other interfaces can be used by keeping the code the same except for the "SendINS" and "SendCHAR" subroutines which have to be specific to the LCD interface used.At the start of this article, I noted that animation was usually a series of pictures displayed in such a way to present the illusion of movement. This can be done by changing what a character on an LCD display is showing the user. One way of doing this is to rewrite the user defined character each time the display is to change. To do this, the code shown above would have to be repeated each time the character was to move. This probably seems inefficient in terms of time and a potential problem with the user seeing the change, but the LCD is able to be updated quickly enough that this is not an issue. The real problem with this method is the amount of code that is required to update the display repeatedly. Instead of the data being saved into the CGRAM once and all together, which is quite efficient in terms of space used, it is put in piecemeal throughout the code which is quite wasteful for devices like microcontrollers which have limited program storage to begin with. A better way of doing this is to load the CGRAM with each character to display and then change the byte on the display. Using this method, the 44780's Cursor has to be moved followed by writing the new display character which takes a lot less space than the previous method. To show what I mean, for the "Animate" application that I present in this article, I created a series of pictures of a stick man walking and make it appear like the stick man is actually walking by using the code:
while ( 1 == 1 ) { // Loop forever
Dlay200(); // Delay 200 msecs
SendINS( 0x080 ); // Move Cursor to Start of the Display
SendCHAR( 1 ); // Send the First Character
Dlay200(); // Delay 200 msecs
SendINS( 0x080 ); // Move Cursor to Start of the Display
SendCHAR( 2 ); // Send the Second Character
Dlay200(); // Delay 200 msecs
SendINS( 0x080 ); // Move Cursor to Start of the Display
SendCHAR( 3 ); // Send the Third Character
Dlay200(); // Delay 200 msecs
SendINS( 0x080 ); // Move Cursor to Start of the Display
SendCHAR( 4 ); // Send the Fourth Character
Dlay200(); // Delay 200 msecs
SendINS( 0x080 ); // Move Cursor to Start of the Display
SendCHAR( 5 ); // Send the Fifth Character
}
I delay 200 msecs to prevent the PICMicro from running so quickly that the user cannot see the movement of the stick man on the LCD display. This gives a "walking rate" of about one step per second which is adequate to follow the movement and get some idea of what is supposed to be represented. In "Animate", I have placed an "A" character beside the walking stick man. The reason for this was to show that the single character write does not overwrite any other characters on the display. When you run this application, you'll probably be disappointed in the quality of the animation; the stick man is hard to discern and probably looks like a contortion skeleton key. I wanted to leave in this animation because it really shows how limiting the eight by five pixel character box can be. The user defined characters should be used for just that, special characters that can be programmed by the user. Now, having said this, there is one animation application which the user defined characters are well suited and that is for "Bar Graphs". By creating characters with varying numbers of lines blacked out, simple analog displays can be made for applications which are very effective. Using the character methods I outline in this article, displays can work at the full speed of the sensors or other data generators and are often more effective than volt meters or other analog displays. The code was written for MPLAB Version 4.00. If you are porting the code to a different assembler, I would appreciate it if you would let me know as I would like to make this code available to anyone else who uses this assembler. If you have any problems with the code, please let me know and I'll see what I can do to help you publish the fixes so other people can benefit. Click HERE to download the code for this project. Myke Copyright and Warranty Statement This article is presented on an "AS IS" basis. I have tested the circuit and code that I have presented here and I am confident that it works on the hardware that I have used. Different hardware may result in different results. The information contained here cannot be reproduced without the author's permission. Program the PIC in simple BASIC using the PicBasic Compiler. Visit: http://www.rentron.com/PicBasic2.htm
|