Connecting LCD to Microcontroller Tutorial

Microcontroller Topics
Post Reply
User avatar
Reginald
Sergeant Major
Sergeant Major
Posts: 29
Joined: Mon Aug 03, 2009 9:54 pm
Location: Mount Lavinia

Connecting LCD to Microcontroller Tutorial

Post by Reginald » Sun Sep 20, 2009 3:48 am

Introduction

The most commonly used Character based LCDs are based on Hitachi's HD44780 controller or other which are compatible with HD44580. In this tutorial, we will discuss about character based LCDs, their interfacing with various microcontrollers, various interfaces (8-bit/4-bit), programming, special stuff and tricks you can do with these simple looking LCDs which can give a new look to your application.

For Specs and technical information HD44780 controller Click Here

Pin Description

The most commonly used LCDs found in the market today are 1 Line, 2 Line or 4 Line LCDs which have only 1 controller and support at most of 80 charachers, whereas LCDs supporting more than 80 characters make use of 2 HD44780 controllers.

Most LCDs with 1 controller has 14 Pins and LCDs with 2 controller has 16 Pins (two pins are extra in both for back-light LED connections). Pin description is shown in the table below.
lcdpins.jpg
lcdpins.jpg (14.01 KiB) Viewed 4043 times
Figure 1: Character LCD type HD44780 Pin diagram
Pin NumberNameDescription
1VSSPower supply (GND)
2VCCPower supply (+5V)
3VEEContrast adjust
4RS0 = Instruction input, 1 = Data input
5R/W0 = Write to LCD module, 1 = Read from LCD module
6ENEnable signal
7D0Data bus line 0 (LSB)
8D1Data bus line 1
9D2Data bus line 2
10D3Data bus line 3
11D4Data bus line 4
12D5Data bus line 5
13D6Data bus line 6
14D7Data bus line 7 (MSB)
Table 1: Character LCD pins with 1 Controller
Pin NumberNameDescription
1D7Data bus line 7 (MSB)
2D6Data bus line 6
3D5Data bus line 5
4D4Data bus line 4
5D3Data bus line 3
6D2Data bus line 2
7D1Data bus line 1
8D0Data bus line 0 (LSB)
9EN1Enable signal for row 0 and 1 (1stcontroller)
10R/W0 = Write to LCD module, 1 = Read from LCD module
11RS0 = Instruction input, 1 = Data input
12VEEContrast adjust
13VSSPower supply (GND)
14VCCPower supply (+5V)
15EN2Enable signal for row 2 and 3 (2ndcontroller)
16NCNot Connected
Table 2: Character LCD pins with 2 Controller

Usually these days you will find single controller LCD modules are used more in the market. So in the tutorial we will discuss more about the single controller LCD, the operation and everything else is same for the double controller too. Lets take a look at the basic information which is there in every LCD.

DDRAM - Display Data RAM
Display data RAM (DDRAM) stores display data represented in 8-bit character codes. Its extended capacity is 80 X 8 bits, or 80 characters. The area in display data RAM (DDRAM) that is not used for display can be used as general data RAM. So whatever you send on the DDRAM is actually displayed on the LCD. For LCDs like 1x16, only 16 characters are visible, so whatever you write after 16 chars is written in DDRAM but is not visible to the user.

Figures below will show you the DDRAM addresses of 1 Line, 2 Line and 4 Line LCDs.
lcd-1l.png
lcd-1l.png (1.25 KiB) Viewed 4042 times
Figure 2: DDRAM Address for 1 Line LCD
lcd-2l.png
lcd-2l.png (1.66 KiB) Viewed 4042 times
Figure 3: DDRAM Address for 2 Line LCD
lcd-4l.gif
lcd-4l.gif (3.6 KiB) Viewed 4042 times
Figure 4: DDRAM Address for 4 Line LCD

CGROM - Character Generator ROM
Now you might be thinking that when you send an ASCII value to DDRAM, how the character is displayed on LCD? so the answer is CGROM. The character generator ROM generates 5 x 8 dot or 5 x 10 dot character patterns from 8-bit character codes (see Figure 5 and Figure 6 for more details). It can generate 208 5 x 8 dot character patterns and 32 5 x 10 dot character patterns. User-defined character patterns are also available by mask-programmed ROM.
cgrom1.png
cgrom1.png (7.35 KiB) Viewed 4042 times
Figure 5: LCD characters code map for 5x8 dots
cgrom2.png
cgrom2.png (7.81 KiB) Viewed 4042 times
Figure 6: LCD characters code map for 5x10 dots

As you can see in both the code maps, the character code from 0x00 to 0x07 is occupied by the CGRAM characters or the user defined characters. If user want to display the fourth custom character then the code to display it is 0x03 i.e. when user send 0x03 code to the LCD DDRAM then the fourth user created charater or patteren will be displayed on the LCD.

CGRAM - Character Generator RAM
As clear from the name, CGRAM area is used to create custom characters in LCD. In the character generator RAM, the user can rewrite character patterns by program. For 5 x 8 dots, eight character patterns can be written, and for 5 x 10 dots, four character patterns can be written. Later in this tutorial i will explain how to use CGRAM area to make custom character and also making animations to give nice effects to your application.

BF - Busy Flag
Busy Flag is an status indicator flag for LCD. When we send a command or data to the LCD for processing, this flag is set (i.e BF =1) and as soon as the instruction is executed successfully this flag is cleared (BF = 0). This is helpful in producing and exact amount of delay. for the LCD processing.

To read Busy Flag, the condition RS = 0 and R/W = 1 must be met and The MSB of the LCD data bus (D7) act as busy flag. When BF = 1 means LCD is busy and will not accept next command or data and BF = 0 means LCD is ready for the next command or data to process.

Instruction Register (IR) and Data Register (DR)
There are two 8-bit registers in HD44780 controller Instruction and Data register. Instruction register corresponds to the register where you send commands to LCD e.g LCD shift command, LCD clear, LCD address etc. and Data register is used for storing data which is to be displayed on LCD. when send the enable signal of the LCD is asserted, the data on the pins is latched in to the data register and data is then moved automatically to the DDRAM and hence is displayed on the LCD.
Data Register is not only used for sending data to DDRAM but also for CGRAM, the address where you want to send the data, is decided by the instruction you send to LCD. We will discuss more on LCD instruction set further in this tutorial.

Commands and Instruction set
Only the instruction register (IR) and the data register (DR) of the LCD can be controlled by the MCU. Before starting the internal operation of the LCD, control information is temporarily stored into these registers to allow interfacing with various MCUs, which operate at different speeds, or various peripheral control devices. The internal operation of the LCD is determined by signals sent from the MCU. These signals, which include register selection signal (RS), read/write signal (R/W), and the data bus (DB0 to DB7), make up the LCD instructions (Table 3). There are four categories of instructions that:
  • Designate LCD functions, such as display format, data length, etc.
  • Set internal RAM addresses
  • Perform data transfer with internal RAM
  • Perform miscellaneous functions
commands.gif
commands.gif (28.95 KiB) Viewed 4042 times
Table 3: Command and Instruction set for LCD type HD44780

Although looking at the table you can make your own commands and test them. Below is a brief list of useful commands which are used frequently while working on the LCD.
NumberInstructionHexDecimal
1Function Set: 8-bit, 1 Line, 5x7 Dots0x3048
2Function Set: 8-bit, 2 Line, 5x7 Dots0x3856
3Function Set: 4-bit, 1 Line, 5x7 Dots0x2032
4Function Set: 4-bit, 2 Line, 5x7 Dots0x2840
5Entry Mode0x066
6Display off Cursor off (clearing display without clearing DDRAM content)0x088
7Display on Cursor on0x0E14
8Display on Cursor off0x0C12
9Display on Cursor blinking0x0F15
10Shift entire display left0x1824
12Shift entire display right0x1C30
13Move cursor left by one character0x1016
14Move cursor right by one character0x1420
15Clear Display (also clear DDRAM content)0x011
16Set DDRAM address or coursor position on display0x80+add*128+add*
17Set CGRAM address or set pointer to CGRAM location0x40+add**64+add**
Table 4: Frequently used commands and instructions for LCD

* DDRAM address given in LCD basics section see Figure 2,3,4
** CGRAM address from 0x00 to 0x3F, 0x00 to 0x07 for char1 and so on..

The table above will help you while writing programs for LCD. But after you are done testing with the table 4, i recommend you to use table 3 to get more grip on working with LCD and trying your own commands. In the next section of the tutorial we will see the initialization with some of the coding examples in C as well as assembly.

LCD Initialization
Before using the LCD for display purpose, LCD has to be initialized either by the internal reset circuit or sending set of commands to initialize the LCD. It is the user who has to decide whether an LCD has to be initialized by instructions or by internal reset circuit. we will dicuss both ways of initialization one by one.

Initialization by internal Reset Circuit
An internal reset circuit automatically initializes the HD44780U when the power is turned on. The following instructions are executed during the initialization. The busy flag (BF) is kept in the busy state until the initialization ends (BF = 1). The busy state lasts for 10 ms after VCC rises to 4.5 V.
  • Display clear
  • Function set:
    DL = 1; 8-bit interface data
    N = 0; 1-line display
    F = 0; 5 x 8 dot character font
  • Display on/off control:
    D = 0; Display off
    C = 0; Cursor off
    B = 0; Blinking off
  • Entry mode set:
    I/D = 1; Increment by 1
    S = 0; No shift
Note: If the electrical characteristics conditions listed under the table Power Supply Conditions Using Internal Reset Circuit are not met, the internal reset circuit will not operate normally and will fail to initialize the HD44780U. For such a case, initialization must be performed by the MCU as explained in the section, Initializing by Instruction.
As mentioned in the Note, there are certain conditions that has to be met, if user want to use initialization by internal reset circuit. These conditions are shown in the Table 5 below.
intreset.gif
intreset.gif (2.75 KiB) Viewed 4042 times
Table 5: Power Supply condition for Internal Reset circuit

Figure 7 shows the test condition which are to be met for internal reset circuit to be active.
testcond.gif
testcond.gif (7.57 KiB) Viewed 4042 times
Figure 7: Internal Power Supply reset

Now the problem with the internal reset circuit is, it is highly dependent on power supply, to meet this critical power supply conditions is not hard but are difficult to achieve when you are making a simple application. So usually the second method i.e. Initialization by instruction is used and is recommended most of the time.

Initialization by instructions
Initializing LCD with instructions is really simple. Given below is a flowchart that describes the step to follow, to initialize the LCD.
initlcd.gif
initlcd.gif (15.66 KiB) Viewed 4042 times
Figure 8: Flow chart for LCD initialization

As you can see from the flow chart, the LCD is initialized in the following sequence...
  1. Send command 0x30 - Using 8-bit interface
  2. Delay 20ms
  3. Send command 0x30 - 8-bit interface
  4. Delay 20ms
  5. Send command 0x30 - 8-bit interface
  6. Delay 20ms
  7. Send Function set - see Table 4 for more information
  8. Display Clear command
  9. Set entry mode command - explained below
The first 3 commands are usually not required but are recommended when you are using 4-bit interface. So you can program the LCD starting from step 7 when working with 8-bit interface. Function set command depends on what kind of LCD you are using and what kind of interface you are using (see Table 4 in LCD Command section).

LCD Entry mode
From Table 3 in command section, you can see that the two bits decide the entry mode for LCD, these bits are:
a) I/D - Increment/Decrement bit
b) S - Display shift.
With these two bits we get four combinations of entry mode which are 0x04, 0x05, 0x06, 0x07 (see table 3 in LCD Command section). So we get different results with these different entry modes. Normally entry mode 0x06 is used which is No shift and auto increment. I recommend you to try all the possible entry modes and see the results, I am sure you will be surprised.

Programming example for LCD Initialization

ASM Code

Code: Select all

LCD_data equ P2    ;LCD Data port
LCD_D7   equ P2.7  ;LCD D7/Busy Flag
LCD_rs   equ P1.0  ;LCD Register Select
LCD_rw   equ P1.1  ;LCD Read/Write
LCD_en   equ P1.2  ;LCD Enable

LCD_init:
         mov   LCD_data,#38H  ;Function set: 2 Line, 8-bit, 5x7 dots
         clr   LCD_rs         ;Selected command register
         clr   LCD_rw         ;We are writing in instruction register
         setb  LCD_en         ;Enable H->L
         clr   LCD_en
         acall LCD_busy       ;Wait for LCD to process the command
         mov   LCD_data,#0FH  ;Display on, Curson blinking command
         clr   LCD_rs         ;Selected instruction register
         clr   LCD_rw         ;We are writing in instruction register
         setb  LCD_en         ;Enable H->L
         clr   LCD_en
         acall LCD_busy       ;Wait for LCD to process the command
         mov   LCD_data,#01H  ;Clear LCD
         clr   LCD_rs         ;Selected command register
         clr   LCD_rw         ;We are writing in instruction register
         setb  LCD_en         ;Enable H->L
         clr   LCD_en
         acall LCD_busy       ;Wait for LCD to process the command
         mov   LCD_data,#06H  ;Entry mode, auto increment with no shift
         clr   LCD_rs         ;Selected command register
         clr   LCD_rw         ;We are writing in instruction register
         setb  LCD_en         ;Enable H->L
         clr   LCD_en
         acall LCD_busy       ;Wait for LCD to process the command
         ret                  ;Return from routine
C Code

Code: Select all

#include <AT89X51.H>.
#define LCD_data P2
#define LCD_D7   P2_7
#define LCD_rs   P1_0
#define LCD_rw   P1_1
#define LCD_en   P1_2

void LCD_init()
{
     LCD_data = 0x38;     //Function set: 2 Line, 8-bit, 5x7 dots
     LCD_rs   = 0;        //Selected command register
     LCD_rw   = 0;        //We are writing in data register
     LCD_en   = 1;        //Enable H->L
     LCD_en   = 0;
     LCD_busy();          //Wait for LCD to process the command
     LCD_data = 0x0F;     //Display on, Curson blinking command
     LCD_rs   = 0;        //Selected command register
     LCD_rw   = 0;        //We are writing in data register
     LCD_en   = 1;        //Enable H->L
     LCD_en   = 0;
     LCD_busy();          //Wait for LCD to process the command
     LCD_data = 0x01;     //Clear LCD
     LCD_rs   = 0;        //Selected command register
     LCD_rw   = 0;        //We are writing in data register
     LCD_en   = 1;        //Enable H->L
     LCD_en   = 0;
     LCD_busy();          //Wait for LCD to process the command
     LCD_data = 0x06;     //Entry mode, auto increment with no shift
     LCD_rs   = 0;        //Selected command register
     LCD_rw   = 0;        //We are writing in data register
     LCD_en   = 1;        //Enable H->L
     LCD_busy();
}
With the help of the above code, you are able to initialize the LCD. Now there is a function/subroutine coming in the code i.e. LCD_busy, which is used to put delay for LCD so that there should not be any command or data sent to the LCD until it finish executing the command. More on this delay routine is explained in the next section.

Reading the busy Flag
As discussed in the previous section, there must be some delay which is needed to be there for LCD to successfully process the command or data. So this delay can be made either with a delay loop of specified time more than that of LCD process time or we can read the busy flag, which is recommended. The reason to use busy flag is that delay produced is almost for the exact amount of time for which LCD need to process the time. So is best suited for every application.

Steps to read busy flag
when we send the command, the BF or D7 bit of the LCD becomes 1 and as soon as the command is processed the BF = 0. Following are the steps to be kept in mind while reading the Busy flag.
  • Select command register
  • Select read operation
  • Send enable signal
  • Read the flag
So following the above steps we can write the code in assembly as below...

ASM Code

Code: Select all

;Ports used are same as the previous example

LCD_busy:
         setb   LCD_D7        ;Make D7th bit of LCD data port as i/p
         setb   LCD_en        ;Make port pin as o/p
         clr    LCD_rs        ;Select command register
         setb   LCD_rw        ;we are reading
check:
         clr    LCD_en        ;Enable H->L
         setb   LCD_en
         jb     LCD_D7,check  ;read busy flag again and again till it becomes 0
         ret                  ;Return from busy routine
C Code

Code: Select all

void LCD_busy()
{
     LCD_D7   = 1;           //Make D7th bit of LCD as i/p
     LCD_en   = 1;           //Make port pin as o/p
     LCD_rs   = 0;           //Selected command register
     LCD_rw   = 1;           //We are reading
     while(LCD_D7){          //read busy flag again and again till it becomes 0
           LCD_en   = 0;     //Enable H->L
           LCD_en   = 1;
     }
}
The above routine will provide the necessary delay for the instructions to complete. If you don't want to read the busy flag you can simply use a delay routine to provide the a specific amount of delay. A simple delay routine for the LCD is given below.

ASM Code

Code: Select all

LCD_busy:
         mov  r7,#50H
back:
         mov  r6,#FFH
         djnz r6,$
         djnz r7,back
         ret                  ;Return from busy routine
C Code

Code: Select all

void LCD_busy()
{
         unsigned char i,j;
         for(i=0;i<50;i++)        //A simple for loop for delay
            for(j=0;j<255;j++);
}
Now we are ready with the initialization routine and the busy routine for LCD. In the next section we will see how to send data and command to the LCD.

Sending Commands to LCD
To send commands we simply need to select the command register. Everything is same as we have done in the initialization routine. But we will summarize the common steps and put them in a single subroutine. Following are the steps:
  • Move data to LCD port
  • select command register
  • select write operation
  • send enable signal
  • wait for LCD to process the command
Keeping these steps in mind we can write LCD command routine as.

ASM Code

Code: Select all

;Ports used are same as the previous example
;Routine to send command to LCD

LCD_command:
         mov   LCD_data,A     ;Move the command to LCD port
         clr   LCD_rs         ;Selected command register
         clr   LCD_rw         ;We are writing in instruction register
         setb  LCD_en         ;Enable H->L
         clr   LCD_en
         acall LCD_busy       ;Wait for LCD to process the command
         ret                  ;Return from busy routine

; Usage of the above routine
; A will carry the command for LCD
; e.g. we want to send clear LCD command
;
; mov   a,#01H         ;01H is command for clearing LCD
; acall LCD_command    ;Send the command
C Code

Code: Select all

void LCD_command(unsigned char var)
{
     LCD_data = var;      //Function set: 2 Line, 8-bit, 5x7 dots
     LCD_rs   = 0;        //Selected command register
     LCD_rw   = 0;        //We are writing in instruction register
     LCD_en   = 1;        //Enable H->L
     LCD_en   = 0;
     LCD_busy();          //Wait for LCD to process the command
}
// Using the above function is really simple
// var will carry the command for LCD
// e.g.
//
// LCD_command(0x01);
Setting cursor position on LCD
To set the cursor position on LCD, we need to send the DDRAM address...
Bit76543210
1AD6AD5AD4AD3AD2AD1AD0
The seventh bit is always 1, and bit 0 to 7 are DDRAM address (See the introduction section of LCD). so if you want to put the cursor on first position the address will be '0000000B' in binary and 7th bit is 1. so address will be 0x80, so for DDRAM all address starts from 0x80.

For 2 line and 16 character LCD. The address from 0x80 to 0x8F are visible on first line and 0xC0 to 0xCF is visible on second line, rest of the DDRAM area is still available but is not visible on the LCD, if you want to check this thing, then simply put a long sting greater than 16 character and shift the entire display, you will see all the missing character coming from the back.. this way you can make scrolling line on LCD (see more on shifting display in commands section).

Below is an example for setting cursor position on LCD.

ASM Code

Code: Select all

;We are placing the cursor on the 4th position
;so the DDRAM address will be 0x03
;and the command will be 0x80+0x03 = 0x83
mov a,#83H           ;load the command
acall LCD_command    ;send command to LCD
C Code

Code: Select all

// to do the same thing is C
// as we done before
LCD_command(0x83);
Sending Data to LCD
To send data we simply need to select the data register. Everything is same as the command routine. Following are the steps:
  • Move data to LCD port
  • select data register
  • select write operation
  • send enable signal
  • wait for LCD to process the data
Keeping these steps in mind we can write LCD command routine as.

ASM Code

Code: Select all

;Ports used are same as the previous example
;Routine to send data (single character) to LCD

LCD_senddata:
         mov   LCD_data,A     ;Move the command to LCD port
         setb  LCD_rs         ;Selected data register
         clr   LCD_rw         ;We are writing
         setb  LCD_en         ;Enable H->L
         clr   LCD_en
         acall LCD_busy       ;Wait for LCD to process the data
         ret                  ;Return from busy routine

; Usage of the above routine
; A will carry the character to display on LCD
; e.g. we want to print A on LCD
;
; mov   a,#'A'         ;Ascii value of 'A' will be loaded in accumulator
; acall LCD_senddata   ;Send data
C Code

Code: Select all

void LCD_senddata(unsigned char var)
{
     LCD_data = var;      //Function set: 2 Line, 8-bit, 5x7 dots
     LCD_rs   = 1;        //Selected data register
     LCD_rw   = 0;        //We are writing
     LCD_en   = 1;        //Enable H->L
     LCD_en   = 0;
     LCD_busy();          //Wait for LCD to process the command
}
// Using the above function is really simple
// we will pass the character to display as argument to function
// e.g.
//
// LCD_senddata('A');
Now you have seen that its really easy to send command and data to LCD. Now what if we have a string to send to LCD? how we are going to do that?

Is simple, we will store the LCD string in the ROM of controller and call the string character by character. A simple example is shown below.

ASM Code

Code: Select all

;Sending string to LCD Example

LCD_sendstring:
         clr   a                 ;clear Accumulator for any previous data
         movc  a,@a+dptr         ;load the first character in accumulator
         jz    exit              ;go to exit if zero
         acall lcd_senddata      ;send first char
         inc   dptr              ;increment data pointer
         sjmp  LCD_sendstring    ;jump back to send the next character
exit:
         ret                     ;End of routine

; Usage of the above routine
; DPTR(data pointer) will carry the address
; of string to send to LCD.
; e.g. we want to print "LCD Tutorial" on LCD then
;
; mov   dptr,#my_string   ;my_string is the label where the string is stored
; acall LCD_sendstring    ;Send string
;
; To store a string..
; my_string:
; DB   "LCD Tutorial", 00H
; 00H indicate that string is finished.
C Code

Code: Select all

void LCD_sendstring(unsigned char *var)
{
     while(*var)              //till string ends
       LCD_senddata(*var++);  //send characters one by one
}
// Using the above function is really simple
// we will pass the string directly to the function
// e.g.
//
// LCD_sendstring("LCD Tutorial");
Now we are ready with sending data and sending command to LCD. Now the last and final section which is creating custom characters or patterns to display on LCD. Please proceed to the next section to read more.

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 address starting from 0x40. If you are wondering why it starts from 0x40? Then the answer is given below.
cgram.gif
cgram.gif (4.35 KiB) Viewed 4042 times
Bit 7 is 0 and Bit 6 is 1, due to which the CGRAM address 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 building 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 explanation in a better way. I am going to take an example. Lets make a "Bell" pattern as shown below.
custom.png
custom.png (4.66 KiB) Viewed 4042 times
Now we get the values for each row as shown.
Bit43210Hex
Row1001000x04
Row2011100x0E
Row3011100x0E
Row4011100x0E
Row5111110x1F
Row6000000x00
Row7001000x04
Row8000000x00
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)
10x00 - 0x07
20x08 - 0x0F
30x10 - 0x17
40x18 - 0x1F
50x20 - 0x27
60x28 - 0x2F
70x30 - 0x37
80x38 - 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.

ASM Code

Code: Select all

;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.

C Code

Code: Select all

//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: Select all

//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 ]);
     }
}
So the above example shows how to simplify most of your work.

LCD in 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 amount 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.

LCD connections in 4-bit Mode
lcd.png
lcd.png (5.09 KiB) Viewed 4042 times
Above is the connection diagram of LCD in 4-bit mode, where we only need 6 pins to interface an LCD. D4-D7 are the data pins connection and Enable and Register select are for LCD control pins. We are not using Read/Write (RW) Pin of the LCD, as we are only writing on the LCD so we have made it grounded permanently. If you want to use it.. then you may connect it on your controller but that will only increase another pin and does not make any big difference. Potentiometer RV1 is used to control the LCD contrast. The unwanted data pins of LCD i.e. D0-D3 are connected to ground.

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 separate 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 in 4-bit Mode - Programming

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...

ASM Code

Code: Select all

;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 Code

Code: Select all

//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 Command/Data to LCD in 4-bit mode

ASM Code

Code: Select all

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 Code

Code: Select all

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);
}
Post Reply

Return to “Microcontrollers”