Display Custom Character on 16X2 LCD using PIC Microcontroller

Display Custom Character on 16X2 LCD using PIC Microcontroller

In the previous chapter, we have discussed how a character LCD is interfaced with a PIC microcontroller in 8-bit mode, where we used predefined characters stored in the LCD to display our data. In this article, we will learn more about the LCD and how we can create and use custom characters.

LCD Storage Memory

The LCD that we use has a Hitachi HD44780 LCD controller. It has three main storage locations:

  1. DDRAM
  2. CGROM
  3. CGRAM

DDRAM or “Data Display Random Access Memory” is the working data buffer of the display. Each character on the display has a corresponding DDRAM location and the byte loaded in DDRAM controls which character is displayed.

CGROM or “Character Generation Read Only Memory” holds all the standard patterns for the 5 x 7 dot matrix characters. For instance, if you want to display character “A”, you would send ASCII code 65 (decimal) to the DDRAM. The display controller looks up the pattern of dots to display for this code in the CGROM and lights up the ones appropriate for “A”. The CGROM contents depend on the particular character set and model of display, US, Chinese etc. and cannot be changed.

CGRAM or “Character Generation Random Access Memory” allows the user to define special supplementary non-standard character types that are not in the CGROM. You can load your own dot pattern shapes and call these up for display.

Predefined characters in CGROM

Pre-defined characters in CGROM with corresponding binary ASCII value

Prerequisites

Before starting with the custom character building, we require the following functions that we covered in our previous chapter.

  • void lcd_cmd ( const char ) ; /*Function to write command on Lcd*/
  • void lcd_data ( const char ) ; /*Function to display message on Lcd*/

We also need an LCD initialising function to start the LCD according to our requirement.

Note: To use a bigger pattern we are keeping the cursor OFF, which is specified in the initialisation function.

Example firmware:

/*Initialise the LCD*/
void lcd_init(void) 
{ 
lcd_cmd(0x38); //Function Set: 8-bit, 2 Line, 5x7 Dots
lcd_cmd(0x01); //Clear LCD
lcd_cmd(0x06); //Entry Mode
lcd_cmd(0x0c); //Display on Cursor off
}

We can also use the same circuit that we used before.

Custom Character Building

For making custom patterns we need to write values to the CGRAM area defining which pixel to glow. These values are to be written in the CGRAM address starting from 0x40. CGRAM has a total of 64 Bytes. For LCD using 8×5 dots for each character, you can define a total of 8 user defined patterns (1 Byte for each row and 8 rows for each pattern).

To build a custom character we have to make a pixel-map of 8×5 and get the decimal value or hex value for each row. The bit value is 1 if the pixel is glowing and the bit value is 0 if the pixel is off. The final 8 values are loaded to the CGRAM one by one. The last row is usually left blank (0x00) for the cursor. If you are not using cursor then you can make use of that 8th row to get a bigger pattern.

For example, let’s design a “Man” pattern as shown below

Display Custom Character on LCD

Now we get the binary values for each row

binary values for Display Custom Character on LCD

To store these values lets put these values in an array of 8 elements.

Example firmware:

unsigned char Pattern1 [ ] = { 0x0e, 0x0e, 0x04, 0x04, 0x1f, 0x04, 0x0a, 0x0a } ;

We need to put these values in CGRAM. The CGRAM address starts from 0x40. We can store the data in any of the 8 available positions. To store this pattern in CGRAM we need to use a pointer containing the address of the pattern and the location at which we want to store the pattern.

Example firmware:

void CreateCustomCharacter (unsigned char *Pattern, const char Location)
{ 
int i=0; 
lcd_cmd (0x40+(Location*8));     //Send the Address of CGRAM
for (i=0; i<8; i++)
lcd_data (Pattern [ i ] );         //Pass the bytes of pattern on LCD 
}

Let’s say we want to write the Man pattern at second pattern location (At address location 0x48). We need to call the above function specifying the pattern to store and the decided position.

Example firmware:

CreateCustomCharacter(Pattern1,1);     /*Create Man pattern at 2nd */

Displaying Custom Character

Custom characters are assigned fixed display codes from 0 to 7 for pattern stored in the location pointed by CGRAM address 0x40, 0x48, 0x56… and so on. So, if the user wants to display second pattern (pattern stored at CGRAM address 0x48), simply call the data function with value 1 as an argument at a desired location in the LCD.

Example firmware:

lcd_cmd ( 0x86 ) ;    //Place cursor at 6th position of first row 
lcd_data ( 1 ) ;      //Display Pattern1

Creating a sequence

In a similar fashion, we can also program the LCD to display a particular sequence with one or more custom characters.
Let’s create the following sequence.

Display Custom Character on LCD

To make the sequence let’s first design some custom patterns.

Example firmware:

unsigned char Pattern1[]= {0x0e,0x0e,0x04,0x04,0x1f,0x04,0x0a,0x0a}; 
unsigned char Pattern2[]= {0x0e,0x0e,0x15,0x0e,0x04,0x04,0x0a,0x11};
unsigned char Pattern3[]= {0x00,0x00,0x0a,0x15,0x11,0x0a,0x04,0x00};
unsigned char Pattern4[]= {0x00,0x00,0x0a,0x1f,0x1f,0x0e,0x04,0x00};

Now let’s initialize the LCD and create the patterns, saving it to different locations in the CGRAM.

Example firmware:

lcd_init();  /*Lcd Initialize*/

CreateCustomCharacter(Pattern1,1); /*Create Man pattern at location 1 of CGRAM*/
CreateCustomCharacter(Pattern2,3); 
CreateCustomCharacter(Pattern3,0); 
CreateCustomCharacter(Pattern4,2);

To display the sequence in the LCD, we need to specify the position on LCD and which pattern to display at the position. Provide adequate delay in between frames to observe the sequence distinctly.

Example firmware:

lcd_cmd(0x01);          //Clear the LCD

while(1)
{
    lcd_cmd(0x88);      //Place cursor at 8th position of first row 
    lcd_data(0);        //Display Pattern3
    lcd_cmd(0x86);      //Place cursor at 6th position of first row 
    lcd_data(1);        //Display Pattern1
    __delay_ms(500);    //Suitable delay

    lcd_cmd(0x88);      //Place cursor at 8th position of first row 
    lcd_data(2);        //Display Pattern4
    lcd_cmd(0x86);      //Place cursor at 6th position of first row 
    lcd_data(3);        //Display Pattern2
    __delay_ms(500);    //Suitable delay
}