Custom characters on the PIC Development Board’s HD44780-type LCD.
1. Introduction.
Any LCD with the Hitachi HD44780 controller, except standard characters stored in its internal memory, can display user defined characters. Custom characters have to be written into the CG RAM memory of the LCD before they can be used. 64B of the CG RAM allows to store upto 8 characters.
2. CG RAM.
Each character displayed on the LCD is a matrix of 5×8 points. Each line is represented by one byte and each character is represented by 8 bytes. The drawing below explains how to define sample character.
Drawing 1 (large view).
3. LCD C driver.
The CCS C Compiler’s LCD driver has to be changed. For using it with the PIC-01 Development Board we need to add a few lines:
- custom characters definitions
//———————————–custom characters——————–
#define LCD_CHAR_1 0×00
#define LCD_CHAR_2 0×01
#define LCD_CHAR_3 0×02
#define LCD_CHAR_4 0×03
#define LCD_CHAR_5 0×04
#define LCD_CHAR_6 0×05
#define LCD_CHAR_7 0×06
#define LCD_CHAR_8 0×07
BYTE const LCD_CUSTOM_CHARS[64] = {0×00,0×01,0×02,0×04,0×08,0×10,0×00,0×00,
0×00,0×00,0×00,0x1F,0×00,0×00,0×00,0×00,
0×00,0×10,0×08,0×04,0×02,0×01,0×00,0×00,
0×00,0×04,0×04,0×04,0×04,0×04,0×00,0×00,
0×11,0×11,0×15,0×15,0x0A,0×00,0×00,0×00,
0x0E,0×01,0x0F,0×11,0x0F,0×00,0×00,0×00,
0×11,0×11,0x0F,0×01,0x0E,0×00,0×00,0×00,
0×00,0×04,0×02,0x1F,0×02,0×04,0×00,0×00};
- initialization function
//———————————————————–
void lcd_init_custom_chars()
{
BYTE i;
lcd_send_byte(0,LCD_CGRAM_ADDR);
for(i=0;i<=63;i++)
{
lcd_send_byte(1,LCD_CUSTOM_CHARS[i]);
delay_ms(2);
}
}
- calling custom characters
//————————————————————-
void lcd_putc(char c)
{
switch(c)
{
case ‘\f’: //clear LCD
lcd_send_byte(0,1);
delay_ms(2);
break;
case ‘\n’: //next line
lcd_gotoxy(1,2);
break;
case ‘\b’: //previous position
lcd_send_byte(0,0×10);
break;
case ‘\q’: //shift LCD
lcd_send_byte(0,0×18);
break;
case ‘\1′ : lcd_send_byte(1,LCD_CHAR_1); break; //custom
case ‘\2′ : lcd_send_byte(1,LCD_CHAR_2); break; //characters
case ‘\3′ : lcd_send_byte(1,LCD_CHAR_3); break;
case ‘\4′ : lcd_send_byte(1,LCD_CHAR_4); break;
case ‘\5′ : lcd_send_byte(1,LCD_CHAR_5); break;
case ‘\6′ : lcd_send_byte(1,LCD_CHAR_6); break;
case ‘\7′ : lcd_send_byte(1,LCD_CHAR_7); break;
case ‘\u’ : lcd_send_byte(1,LCD_CHAR_8); break;
default:
lcd_send_byte(1,c);
break;
}
}
After these changes the LCD driver should look like the code below.
//———————————————————
//
// llcd_new.h
//———————————————————
//—————pin definition——————————
#define LCD_DB4 PIN_B4
#define LCD_DB5 PIN_B5
#define LCD_DB6 PIN_B6
#define LCD_DB7 PIN_B7
#define LCD_E PIN_A1
#define LCD_RS PIN_A3
#define LCD_RW PIN_A2
#define USE_LCD_RW 1
#define LCD_CGRAM_ADDR 0×40 // Set the CGRAM address
#define LCD_DDRAM_ADDR 0×80 // Set the DDRAM address
//———custom characters—————————–
#define LCD_CHAR_1 0×00
#define LCD_CHAR_2 0×01
#define LCD_CHAR_3 0×02
#define LCD_CHAR_4 0×03
#define LCD_CHAR_5 0×04
#define LCD_CHAR_6 0×05
#define LCD_CHAR_7 0×06
#define LCD_CHAR_8 0×07
BYTE const LCD_CUSTOM_CHARS[64] = {0×00,0×01,0×02,0×04,0×08,0×10,0×00,0×00,
0×00,0×00,0×00,0x1F,0×00,0×00,0×00,0×00,
0×00,0×10,0×08,0×04,0×02,0×01,0×00,0×00,
0×00,0×04,0×04,0×04,0×04,0×04,0×00,0×00,
0×11,0×11,0×15,0×15,0x0A,0×00,0×00,0×00,
0x0E,0×01,0x0F,0×11,0x0F,0×00,0×00,0×00,
0×11,0×11,0x0F,0×01,0x0E,0×00,0×00,0×00,
0×00,0×04,0×02,0x1F,0×02,0×04,0×00,0×00};
//——————————————————-
#define lcd_type 2 // 0=5×7, 1=5×10, 2=2 lines
#define lcd_line_two 0×40 // LCD RAM address for the 2nd line
int8 const LCD_INIT_STRING[4] =
{
0×20 | (lcd_type << 2), // Func set: 4-bit, 2 lines, 5×8 dots
0xc, // Display on
1, // Clear display
6 // Increment cursor
};
//——————————————————-
void lcd_send_nibble(int8 nibble)
{
output_bit(LCD_DB4, !!(nibble & 1));
output_bit(LCD_DB5, !!(nibble & 2));
output_bit(LCD_DB6, !!(nibble & 4));
output_bit(LCD_DB7, !!(nibble & 8));
delay_cycles(1);
output_high(LCD_E);
delay_us(2);
output_low(LCD_E);
}
//——————————————————-
#ifdef USE_LCD_RW
int8 lcd_read_nibble(void)
{
int8 retval;
#bit retval_0 = retval.0
#bit retval_1 = retval.1
#bit retval_2 = retval.2
#bit retval_3 = retval.3
retval = 0;
output_high(LCD_E);
delay_cycles(1);
retval_0 = input(LCD_DB4);
retval_1 = input(LCD_DB5);
retval_2 = input(LCD_DB6);
retval_3 = input(LCD_DB7);
output_low(LCD_E);
return(retval);
}
#endif
//——————————————————-
#ifdef USE_LCD_RW
int8 lcd_read_byte(void)
{
int8 low;
int8 high;
output_high(LCD_RW);
delay_cycles(1);
high = lcd_read_nibble();
low = lcd_read_nibble();
return( (high<<4) | low);
}
#endif
//——————————————————-
void lcd_send_byte(int8 address, int8 n)
{
output_low(LCD_RS);
#ifdef USE_LCD_RW
while(bit_test(lcd_read_byte(),7)) ;
#else
delay_us(60);
#endif
if(address)
output_high(LCD_RS);
else
output_low(LCD_RS);
delay_cycles(1);
#ifdef USE_LCD_RW
output_low(LCD_RW);
delay_cycles(1);
#endif
output_low(LCD_E);
lcd_send_nibble(n >> 4);
lcd_send_nibble(n & 0xf);
}
//——————————————————
void lcd_init(void)
{
int8 i;
output_low(LCD_RS);
#ifdef USE_LCD_RW
output_low(LCD_RW);
#endif
output_low(LCD_E);
delay_ms(15);
for(i=0 ;i < 3; i++)
{
lcd_send_nibble(0×03);
delay_ms(5);
}
lcd_send_nibble(0×02);
for(i=0; i < sizeof(LCD_INIT_STRING); i++)
{
lcd_send_byte(0, LCD_INIT_STRING[i]);
#ifndef USE_LCD_RW
delay_ms(5);
#endif
}
}
//——————————————————-
void lcd_gotoxy(int8 x, int8 y)
{
int8 address;
if(y != 1)
address = lcd_line_two;
else
address=0;
address += x-1;
lcd_send_byte(0, 0×80 | address);
}
//—————————————————–
void lcd_putc(char c)
{
switch(c)
{
case ‘\f’: //clear LCD
lcd_send_byte(0,1);
delay_ms(2);
break;
case ‘\n’: //next line
lcd_gotoxy(1,2);
break;
case ‘\b’: //previous position
lcd_send_byte(0,0×10);
break;
case ‘\q’: //shift LCD
lcd_send_byte(0,0×18);
break;
case ‘\1′ : lcd_send_byte(1,LCD_CHAR_1); break; //custom
case ‘\2′ : lcd_send_byte(1,LCD_CHAR_2); break; //characters
case ‘\3′ : lcd_send_byte(1,LCD_CHAR_3); break;
case ‘\4′ : lcd_send_byte(1,LCD_CHAR_4); break;
case ‘\5′ : lcd_send_byte(1,LCD_CHAR_5); break;
case ‘\6′ : lcd_send_byte(1,LCD_CHAR_6); break;
case ‘\7′ : lcd_send_byte(1,LCD_CHAR_7); break;
case ‘\u’ : lcd_send_byte(1,LCD_CHAR_8); break;
default:
lcd_send_byte(1,c);
break;
}
}
//————————————————
#ifdef USE_LCD_RW
char lcd_getc(int8 x, int8 y)
{
char value;
lcd_gotoxy(x,y);
while(bit_test(lcd_read_byte(),7));
output_high(LCD_RS);
value = lcd_read_byte();
output_low(lcd_RS);
return(value);
}
#endif
//———————————————–
void lcd_init_custom_chars()
{
BYTE i;
lcd_send_byte(0,LCD_CGRAM_ADDR);
for(i=0;i<=63;i++)
{
lcd_send_byte(1,LCD_CUSTOM_CHARS[i]);
delay_ms(2);
}
}
4. The PIC Development Board programming example.
The listing below is an example how to use custom characters.
//———————————–custom_char.c—————-
#include<16f877a.h> //Device library
#use delay (clock=4000000) //4.00 MHz
#include”llcd_new.h” //LCD driver
#fuses xt,nowdt,protect,noput,nolvp //Fuses
int i;
display()
{
for(i=0;i<=8;i++)
{
lcd_putc(“\f \1 LOGINway \1″); // \1\2\3\4 calls
delay_ms(150); // custom characters
lcd_putc(“\f \2 LOGINway \2″); // written in ROM memory
delay_ms(150);
lcd_putc(“\f \3 LOGINway \3″);
delay_ms(150);
lcd_putc(“\f \4 LOGINway \4″);
delay_ms(150);
}
lcd_putc(“\fwww.LOGINway.com”);
delay_ms(2000);
lcd_putc(“\f”);
for(i=0;i<=39;i++) //shift LCD
{
lcd_putc(“\n\q PIC-01 Development Board”);
delay_ms(250);
}
}
main()
{
lcd_init(); //lcd start
lcd_init_custom_chars(); //write custom characters
//into LCD memory
while(1)
{
display();
delay_ms(300);
}
}
All files are available here:
llcd_new.h (.zip)
custom_char.c (.zip)
custom_char.h (.zip)
LCD Character Creator is a free software which helps in creating user defined characters.
