LCD interfacing with Microcontrollers tutorial - 4-bit Mode

►Introduction

Till now whatever we discussed in the previous part of ths LCD tutorial, we were dealing with 8-bit mode. Now we are going to learn how to use LCD in 4-bit mode. There are many reasons why sometime we prefer to use LCD in 4-bit mode instead of 8-bit. One basic reason is lesser number of pins are needed to interface LCD.

In 4-bit mode the data is sent in nibbles, first we send the higher nibble and then the lower nibble. To enable the 4-bit mode of LCD, we need to follow special sequence of initialization that tells the LCD controller that user has selected 4-bit mode of operation. We call this special sequence as resetting the LCD. Following is the reset sequence of LCD.
 Wait for abour 20mS
 Send the first init value (0x30)
 Wait for about 10mS
 Send second init value (0x30)
 Wait for about 1mS
 Send third init value (0x30)
 Wait for 1mS
 Select bus width (0x30 - for 8-bit and 0x20 for 4-bit)
 Wait for 1mS

The busy flag will only be valid after the above reset sequence. Usually we do not use busy flag in 4-bit mode as we have to write code for reading two nibbles from the LCD. Instead we simply put a certain ammount of delay usually 300 to 600uS. This delay might vary depending on the LCD you are using, as you might have a different crystal frequency on which LCD controller is running. So it actually depends on the LCD module you are using. So if you feel any problem running the LCD, simply try to increase the delay. This usually works. For me about 400uS works perfect.

►Sending data/command in 4-bit Mode

We will now look into the common steps to send data/command to LCD when working in 4-bit mode. As i already explained in 4-bit mode data is sent nibble by nibble, first we send higher nibble and then lower nibble. This means in both command and data sending function we need to saperate the higher 4-bits and lower 4-bits.


The common steps are:
 Mask lower 4-bits
 Send to the LCD port
 Send enable signal
 Mask higher 4-bits
 Send to LCD port
 Send enable signal

We are done with the theory part now, In the next section we will take a look at the programming microcontroller to control LCD in 4-bit mode.

LCD interfacing with Microcontrollers tutorial - 4-bit Mode

►4-bit Initialization

Initialization of LCD is completed only after the reset sequence and basic initialization commands. We have already discussed about the reset sequence of the lcd in the previous section. So lets look at the programming now...


►Assembly Program
CODE:
;In this whole 4-bit tutorial LCD is connected to
;my controller in following way...
;D4 - P3.0
;D5 - P3.1
;D6 - P3.2
;D7 - P3.3
;EN - P3.7
;RS - P3.5

lcd_port equ P3 ;LCD connected to Port3
en equ P3.7 ;Enable connected to P3.7
rs equ P3.5 ;Register select to P3.5

lcd_reset: ;LCD reset sequence
mov lcd_port, #0FFH
mov delay,#20 ;20mS delay
acall delayms
mov lcd_port, #83H ;Data = 30H, EN = 1, First Init
mov lcd_port, #03H ;Data = 30H, EN = 0
mov delay,#15 ;Delay 15mS
acall delayms
mov lcd_port, #83H ;Second Init, Data = 30H, EN = 1
mov lcd_port, #03H ;Data = 30H, EN = 0
mov delay,#5 ;Delay 5mS
acall delayms
mov lcd_port, #83H ;Third Init
mov lcd_port, #03H
mov delay,#5 ;Delay 5mS
acall delayms
mov lcd_port, #82H ;Select Data width (20H for 4bit)
mov lcd_port, #02H ;Data = 20H, EN = 0
mov delay,#5 ;Delay 5mS
acall delayms
ret

lcd_init:
acall lcd_reset ;Call LCD Reset sequence
mov a,#28H ;4-bit, 2 line, 5x7 dots
acall lcd_cmd ;Call LCD command
mov a,#0CH ;Display ON cursor OFF
acall lcd_cmd ;Call LCD command
mov a,#06H ;Set entry mode (Auto increment)
acall lcd_cmd ;Call LCD command
mov a,#80H ;Bring cursor to line 1
acall lcd_cmd ;Call LCD command
ret




►C Program
CODE:
//The pins used are same as explained earlier
#define lcd_port P3

//LCD Registers addresses
#define LCD_EN 0x80
#define LCD_RS 0x20

void lcd_reset()
{
lcd_port = 0xFF;
delayms(20);
lcd_port = 0x03+LCD_EN;
lcd_port = 0x03;
delayms(10);
lcd_port = 0x03+LCD_EN;
lcd_port = 0x03;
delayms(1);
lcd_port = 0x03+LCD_EN;
lcd_port = 0x03;
delayms(1);
lcd_port = 0x02+LCD_EN;
lcd_port = 0x02;
delayms(1);
}

void lcd_init ()
{
lcd_reset(); // Call LCD reset
lcd_cmd(0x28); // 4-bit mode - 2 line - 5x7 font.
lcd_cmd(0x0C); // Display no cursor - no blink.
lcd_cmd(0x06); // Automatic Increment - No Display shift.
lcd_cmd(0x80); // Address DDRAM with 0 offset 80h.
}




►Sending Dommand/Data to LCD in 4-bit mode

►Assembly Program
CODE:
lcd_cmd: ;LCD command Routine
mov temp,a ;Save a copy of command to temp
swap a ;Swap to use higher nibble
anl a,#0FH ;Mask the first four bits
add a,#80H ;Enable = 1, RS = 0
mov lcd_port,a ;Move it to lcd port
anl a,#0FH ;Enable = 0, RS = 0
mov lcd_port,a ;Move to lcd port

mov a,temp ;Reload the command from temp
anl a,#0FH ;Mask first four bits
add a,#80H ;Enable = 1
mov lcd_port,a ;Move to port
anl a,#0FH ;Enable = 0
mov lcd_port,a ;Move to lcd port

mov delay,#1 ;delay 1 ms
acall delayms
ret

lcd_dat: ;LCD data Routine
mov temp,a ;Keep copy of data in temp
swap a ;We need higher nibble
anl a,#0FH ;Mask first four bits
add a,#0A0H ;Enable = 1, RS = 1
mov lcd_port,a ;Move to lcd port
nop
clr en ;Enable = 0

mov a,temp ;Reload the data from temp
anl a,#0FH ;we need lower nibble now
add a,#0A0H ;Enable = 1, RS = 1
mov lcd_port,a ;Move to lcd port
nop
clr en ;Enable = 0

mov delay,#1 ;Delay 1mS
acall delayms
ret




►C Program
CODE:
void lcd_cmd (char cmd)
{
lcd_port = ((cmd >> 4) & 0x0F)|LCD_EN;
lcd_port = ((cmd >> 4) & 0x0F);

lcd_port = (cmd & 0x0F)|LCD_EN;
lcd_port = (cmd & 0x0F);

delayus(200);
delayus(200);
}

void lcd_data (unsigned char dat)
{
lcd_port = (((dat >> 4) & 0x0F)|LCD_EN|LCD_RS);
lcd_port = (((dat >> 4) & 0x0F)|LCD_RS);

lcd_port = ((dat & 0x0F)|LCD_EN|LCD_RS);
lcd_port = ((dat & 0x0F)|LCD_RS);

delayus(200);
delayus(200);
}

LCD Programming - Creating custom character


CGRAM and Character Building

As already explained, all character based LCD of type HD44780 has CGRAM area to create user defined patterns. 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 adress starting from 0x40. If you are wondering why it starts from 0x40? Then the answer is given below.



Bit 7 is 0 and Bit 6 is 1, due to which the CGRAM adress command starts from 0x40, where the address of CGRAM (Acg) starts from 0x00. CGRAM has a total of 64 Bytes. When you are using LCD as 5x8 dots in function set then you can define a total of 8 user defined patterns (1 Byte for each row and 8 rows for each pattern), where as when LCD is working in 5x10 dots, you can define 4 user defined patterns.

Lets take an of bulding a custom pattern. All we have to do is make a pixel-map of 7x5 and get the hex or decimal value or hex value for each row, bit value is 1 if pixel is glowing and bit value is 0 if pixel is off. The final 7 values are loaded to the CGRAM one by one. As i said there are 8 rows for each pattern, so 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 also. so you get a bigger pattern.


To explain the above explaination in a better way. I am going to take an example. Lets make a "Bell" pattern as shown below.



Now we get the values for each row as shown.
Bit: 4 3 2 1 0 - Hex
Row1: 0 0 1 0 0 - 0x04
Row2: 0 1 1 1 0 - 0x0E
Row3: 0 1 1 1 0 - 0x0E
Row4: 0 1 1 1 0 - 0x0E
Row5: 1 1 1 1 1 - 0x1F
Row6: 0 0 0 0 0 - 0x00
Row7: 0 0 1 0 0 - 0x04
Row8: 0 0 0 0 0 - 0x00

We are not using row 8 as in our pattern it is not required. if you are using cursor then it is recommended not to use the 8th row. Now as we have got the values. We just need to put these values in the CGRAM. You can decided which place you want to store in. Following is the memory map for custom patterns in CGRAM.
Memory Map
Pattern No. CGRAM Address (Acg)
1 0x00 - 0x07
2 0x08 - 0x0F
3 0x10 - 0x17
4 0x18 - 0x1F
5 0x20 - 0x27
6 0x28 - 0x2F
7 0x30 - 0x37
8 0x38 - 0x3F

We can point the cursor to CGRAM address by sending command, which is 0x40 + CGRAM address (For more information please see Table 4 in commands section). Lets say we want to write the Bell pattern at second pattern location. So we send the command as 0x48 (0x40 + 0x08), and then we send the pattern data. Below is a small programming example to do this.
CODE:
;LCD Ports are same as discussed in previous sections

LCD_build:
mov A,#48H ;Load the location where we want to store
acall LCD_command ;Send the command
mov A,#04H ;Load row 1 data
acall LCD_senddata ;Send the data
mov A,#0EH ;Load row 2 data
acall LCD_senddata ;Send the data
mov A,#0EH ;Load row 3 data
acall LCD_senddata ;Send the data
mov A,#0EH ;Load row 4 data
acall LCD_senddata ;Send the data
mov A,#1FH ;Load row 5 data
acall LCD_senddata ;Send the data
mov A,#00H ;Load row 6 data
acall LCD_senddata ;Send the data
mov A,#04H ;Load row 7 data
acall LCD_senddata ;Send the data
mov A,#00H ;Load row 8 data
acall LCD_senddata ;Send the data
ret ;Return from routine


The above routine will create bell character at pattern location 2. To display the above generated pattern on LCD, simply load the pattern location (0,1,2,...7) and call the LCD_senddata subroutine. Now we can also write the above routine in C as...
CODE:
//LCD Ports are same as discussed in previous sections
void LCD_build(){
LCD_command(0x48); //Load the location where we want to store
LCD_senddata(0x04); //Load row 1 data
LCD_senddata(0x0E); //Load row 2 data
LCD_senddata(0x0E); //Load row 3 data
LCD_senddata(0x0E); //Load row 4 data
LCD_senddata(0x1F); //Load row 5 data
LCD_senddata(0x00); //Load row 6 data
LCD_senddata(0x04); //Load row 7 data
LCD_senddata(0x00); //Load row 8 data
}


I think now most of you find programing in C more simple than assembly. We can also summarize the above in a simple small routine so that you can simply call the build routine providing a pointer to array containing the build data. Below example shows how to do it.
CODE:
//Input:
// location: location where you want to store
// 0,1,2,....7
// ptr: Pointer to pattern data
//
//Usage:
// pattern[8]={0x04,0x0E,0x0E,0x0E,0x1F,0x00,0x04,0x00};
// LCD_build(1,pattern);
//
//LCD Ports are same as discussed in previous sections

void LCD_build(unsigned char location, unsigned char *ptr){
unsigned char i;
if(location<8){
LCD_command(0x40+(location*8));
for(i=0;i<8;i++)
LCD_senddata(ptr[ i ]);
}
}