Interfacing 16X2 LCD with LPC2148 tutorial


Introduction
Interfacing a 16X2 or 16X4 LCD module with a 3.3V MCU is not same as interfacing with MCUs like AVR which operate on 5Volts. As per request by some of the readers of previous articles on lpc2148 this article is on Interfacing a 5V LCD Module with LPC2148 MCU and in general for any ARM or 3.3V MCU .

Whats next? : A simple Library for LCD interfacing with LPC214x and LPC176x MCUs. And more tutorials on Timer , PWM , UART .. for lpc214x , lpc176x.

For this article I’ve used the readily available Chinese 16X2 LCD Module : JHD-162A. The Data sheet for JHD-162A 16X2 LCD Module is located at : http://www.egochina.net.cn/eBay/Download/JHD162A.pdf I would strongly suggest that you keep that datasheet open when reading this article to avoid any sort of confusion.

Before Starting the Tutorial , as a motivation , I would like to show the final outcome of this tutorial. The pic below shows the LPC2148 development board interfaced with JHD162A LCD Module via 2x HCF4050B ICs. I’ll come to HCF4050B IC shortly .. at the moment lets get started with the basics.

16X2 LCD Basics :

This particular chinese LCD i.e JHD162A has KS0066U controller or similar to the famous HD44780U. It Consists of 2 Rows with 16 Characters on each. It has a 16 pin Interface. Operates on 5V and has LED backlight. Works in 2 Modes :

  • 1) Instruction Mode : Used for initializing and configuring LCD before we can use it & during operation.
  • 2) Data Mode : Displays the respective characters for codes supplied to it via Data Pins.

Standard Pinout is as follows :

To keep things simple lets group the pins into 3 :
1) Power Pins : Pin 1,2,3,15,16
2) Control Pins : Pin 4,5,6
3) Data Pins : Pin 7 to 14

Now lets see some of the important pins before we actually start writing programs for LCD:

  • Contrast Voltage (VEE) : Instead of using a trim pot just connect a 1K resistor to VEE in series and ground it. That gives the best contrast of what I’ve seen.
  • RS – short for Register select (Control Pin) : Used to switch been Instruction and Data Mode. RS = High for Instruction Mode and RS = Low for Data mode.
  • R/W – Read or Write (Control Pin): R/W = High for Read Mode and R/W = Low for Write. Since we are going to use Write Mode only we will permanently ground it using a pull-down resistor of 4.7K Ohms. Caution : If you are planning to use Read Mode with 3.3V MCUs you’ll need a Bi-directional level shifter which can shift 5V to 3.3V and Vice-Versa.
  • Enable (Control Pin) : This is similar to a trigger pin. Each Data/Instruction is executed by the LCD module only when a pulse is applied to Enable pin. More specifically the it is executed at the falling edge of the pulse.

If you want to know the fundamentals and internal operation of LCD Modules I would recommend visiting these links :

Interfacing 5V LCD Module with lpc214x:

5V LCD wont operate on 3.3V and also since lpc214x runs on 3.3V and 5Volts might fuse some of its GPIO pin we are gonna need a level shifter or translator that can shift 3.3Volts to 5Volts for LCD Module to be safe. Vikram Sharma M. has successfully interfaced JHD162A with MSP430 MCU using CD4050B IC as explained in his post @ http://msharmavikram.wordpress.com/2012/08/14/3-mistakes-of-lcd-with-msp430-solved/ .

Some of the buffers / level shifters than can be used are : SN74AHC244N , SN74AC241N , CD74AC244E , CD4050B , etc.. out of which CD4050B is readily available. I purchased a few HCF4050 which is same as CD4050B except it is manufactured by ST Microelectronics and not Texas Instruments.

HCF4050B is a non-inverting Hex Buffer. In our case we’ll be needing 2 of them at minimum since we are going to use 8+2=10 pins from MCU which need to be shifted to 5Volts. There are going to be 8 Data pins and 2 control pins connected from MCU to the LCD Module using 2x HCF4050B.

NOTE : As far as possible use the 1st eight consecutive pins(at any cost!) from any port of the MCU as Data Pins which will make sending character data or commands(Instructions) to LCD very easy. For e.g. in our case we will use P0.0 to P0.7 as data pins and P1.16 as RS pin and P1.17 as Enable pin.

Connections from lpc214x to LCD module will be as shown below. I’ve also made a PCB for the schematic if bread-board is not your cup of tea.(Frankly , since I was running out of time I did it using a bread-board. If some one is having any problems with PCB please let me know.)

Pin Connections from MCU->HFC4050B->LCD:

1)Pin Connections from MCU to HCF4050B ICs :
IC1 : P1.16->Pin3 , P1.17->Pin5 , P0.0->Pin7 , P0.1->Pin9 , P0.2->Pin11 , P0.3->Pin14
IC2 : P0.4->Pin3 , P0.5->Pin5 , P0.6->Pin7 , P0.7->Pin9 (Pins 11,14 of IC2 are NC)

2)Pin Connections from HCF4050B to LCD Module :
IC1 : Pin2->RS , Pin4->Enable , Pin6->DB0 , Pin10->DB1 , Pin12->DB2 , Pin15->DB3
IC2 : Pin2->DB4 , Pin4->DB5 , Pin6->DB6 , Pin10->DB7 (Pin 12,15 of IC2 are NC)

Download links to PCB pdf and Eagle 6.2+ (Schematic+Board) Design Files:

1) lcd_interface_arm_mcu_PCB-pdf.rar
2) lcd_interface_arm_mcu-EAGLE-6.2+design-files.rar

Components required at bare minimum :

  • LPC214x Development board
  • 16X2 LCD Module
  • 2x HFC4050B or CD4050B
  • 5V Supply for LCD
  • Resistors : 1x 1K , 1x 4.4K , 1x 47 Ohms
  • Bread-Board / Self-made PCB
  • Berg Strips , Jumper wires
NOTE : You need to connect the ‘GND’ from MCU Board to ‘LCD’s GND’ else its not gonna work. Diode D1 and Decoupling Capacitors C1 & C2 are optional. D1 can be replaced by a short and C1,C2 can be ignored.

Initializing LCD Module :

After you have cross-checked all your connections from MCU to HFC4050 to LCD Module its time now to Display text on LCD. But before that we need to initialize the LCD properly. Also NOTE that : as per the Datasheet – before initializing LCD we need to wait for a minimum time of about 15ms after the input voltage supply is stable and >4.5Volts.

The 1st thing required for this is that RS must be held LOW and Enable also LOW in the beginning. Now we must supply some commands using the Data Pins to LCD. But this command wont be executed until we supply a pulse to Enable Pin. After supplying the command we make enable High and then Low after a short delay(i.e a Pulse) and the command is executed.

Codes for various instructions can be obtained from below table : (Refer to page 12 of the Datasheet)

For beginners we are only interested in following commands (which are also executed in given order during initialization):

  1. Function Set
  2. Display Switch
  3. Input Set
  4. Screen Clear Command
  5. DDRAM AD Set – Resposition Cursor Command
  • In our case we are going to use 8Bit Mode with 2Rows and 5×10 Font style which gives Function set as 0x3C.
  • Next we issue Display On , Cursor On , Blink On which gives Display Switch as 0x0F.
  • Next we select Increment Mode which gives Input Set as 0×06.
  • Now we clear the screen and set cursor at Home which gives Screen Clear as 0×01.
  • Also the command to reposition the cursor at Home at anytime is 0×80 and to reposition it at the 1st column of the second row is 0xC0.

To cut-short or in simple words: We need to supply 5 commands in given order to the Data Pins with a small amount of delay in between to initialize the LCD properly. These commands are : 0x3C , 0x0F , 0×06 , 0×01 , 0×80(actually not required). Now since we have used the the 1st eight pins of port 0 on lpc214x we can directly assign these values to the IO0PIN register. Note that Bit 0 i.e LSB in IO0PIN is towards extreme right and the bit number increases as we move towards the left approaching the MSB. Also since we have connected P0.0 to Data Bit 0 pin , P0.1 to Data bit 1 and so on … we have a one to one consecutive mapping between them MCU pins and LCD Data pins.

NOTE: During Initialization , issuing 0×80 command to reposition the cursor at home will be of no use since it would already be at Home after issuing Screen Clear Command. Also we need to Supply a Pulse to Enable after issuing each command and wait for a short while for the LCD controller to process it. Datasheet says a delay of 40us is required at minimum. But even a delay of 1ms wont hurt us to be on the safer side.

Correct Sequence for Initializing LCD module (8 Bit Interface) is as given :

  1. After LCD Module is powered on and MCU boots wait for 20ms (Specifically >15ms)
  2. Now make RS and Enable LOW making sure that R/W is permanently grounded.
  3. Issue Function set command – 0x3C and Pulse Enable (wait for >40us after pulsing).
  4. Issue Display Switch – 0x0F and Pulse Enable (wait for >40us after pulsing).
  5. Issue Input Set – 0×06 and Pulse Enable (wait for >40us after pulsing).
  6. Issue Screen Clear – 0×01 and Pulse Enable (wait for >1.64ms after pulsing).
  7. Issue DDRAM AD Set – 0×80 and Pulse Enable (wait for >40us after pulsing).

Now LCD is ready for Printing!

After Initializing the LCD you would see a Cursor Blinking at in 1st column at row 1 :

Printing Characters on LCD Module :

Now that we have initialized LCD correctly its time to send some characters. For this we enter into Data Mode by making RS High. After that any number / code applied to Data pins will be converted into corresponding Character and displayed. The codes for the characters are ASCII Codes given by the font table as show below :

Hence to print a character on LCD we just need to apply the 8bit ASCII code to Data pins. This can be done easily by type casting the character into an integer (which is done automatically by the compiler – but we are doing it here explicitly). Refer to Page 14 of datasheet for font table.

Now , after printing 16 characters you might wanna print more characters in 2nd Row. This is done by entering into Instruction Mode by holding RS=LOW and giving command 0xC0 which repositions the cursor at 2nd Row & 1st column. Needless to say that we need to again give a Pulse on Enable pin to process it. After that we put RS to high and get back in Data mode.

Note on Repositioning the Cursor : As we have seen , the command for repositioning the cursor at home is 0×80. Think of 0×80 as the base address of Display Data Ram(DDRAM). Hence 0×80 will set cursor at 1st Row, 1st Column. If you add 0×01 to 0×80 the cursor will go to 2nd column in 1st row. Likewise if you add 0x0F to 0×80 you’ll end up at 1st row , last column. Similar to this the base address for 2nd Row is 0×40 and not 0×10! 0×10 is the base address of 3rd row and 0×50 is base address for 4th row in the case of 16×4 LCD Modules. So to position the cursor at 2nd row, 1st column we must add 0×40 to 0×80 which gives 0×80+0×40=0xC0. Hence after giving command ’0xC0′ cursor comes down to 2nd row & 1st col.

More on DDRAM and CGROM @ http://www.8051projects.net/lcd-interfacing/basics.php

Programs for Lpc2148 to print Characters on LCD module :

1st Let me explain a few functions that I’ve made in the programs below:

  • initLCD() : Initializes the LCD.
  • enable() : To generate a pulse on Enable pin.
  • LCD_Cmd(int) : To issue LCD commands.
  • LCD_WriteChar(char) : Print a Single Character.
  • LCD_WriteString(string) : Print a String.
  • delay() :Generate required delay.(For program1 Only!)
  • delayMS(int) : Generate specificed delay.(For program2 Only!)

Program #1 : Basic Program without precise Timing & no PLL setup.

/*
 (C) OCFreaks! | Umang Gajera 2012-13.
 More Embedded tutorials @ www.ocfreaks.com/cat/embedded
 >>Program for Interfacing 16X2 LCD Module with LPC2148
*/


#include <lpc214x.h>

/*
 Connections from LPC2148 to LCD Module:
 P0.0 to P0.7 used as Data bits.
 P1.16 connected to pin4 i.e. RS    - Command / Data
 P1.17 connected to pin6 i.e. E - Enable
 Pin5 of LCD Module i.e. 'R/W' connected to ground
*/
 

void initLCD(void);
void enable(void);
void LCD_WriteChar(char c);
void LCD_WriteString(char * string);
void LCD_Cmd(unsigned int cmd);
void delay(void);


int main(void)
{
    initLCD(); //LCD Now intialized and ready to Print!
    LCD_WriteString(".: Welcome to :.");
    LCD_Cmd(0x80 + 0x40); //Come to 2nd Row
    LCD_WriteString("www.OCFreaks.com");
    while(1); // Loop forever    
    return 0; //This won't execute :P
}

void initLCD(void)
{
    IO0DIR = 0xFF; //P0.0 to P0.7 configured as Output - Using 8 Bit mode
    IO1DIR |= (1<<16) | (1<<17); //P1.16 and P1.17 configured as Output - Control Pins
    IO0PIN = 0x0; //Reset Port0 to 0.  
    IO1PIN = 0x0; //Reset Port1 to 0 - Which also makes RS and Enable LOW.

    //LCD Initialization Sequence Now starts
    delay(); //Initial Delay
    LCD_Cmd(0x3C); //Function Set Command : 8 Bit Mode , 2 Rows , 5x10 Font Style
    LCD_Cmd(0x0F); //Display Switch Command : Display on , Cursor on , Blink on
    LCD_Cmd(0x06); //Input Set : Increment Mode
    LCD_Cmd(0x01); //Screen Clear Command , Cursor at Home
    LCD_Cmd(0x80); //Not required the 1st time but needed to reposition the cursor at home after Clearing Screen
    //Done!
}

void enable(void)
{
    delay();
    IO1PIN |=  (1<<17);//Enable=High
    delay();
    IO1PIN &= ~(1<<17);//Enable=Low
    delay();
}

void LCD_WriteChar(char c)
{
    IO1PIN |= (1<<16); //Switch to Data Mode
    IO0PIN = (int) c; //Supply Character Code
    enable(); //Pulse Enable to process it
}

void LCD_WriteString(char * string)
{
    int c=0;
    while (string[c]!='\0')
    {
        LCD_WriteChar(string[c]);
        c++;
    }
}
       
void LCD_Cmd(unsigned int cmd)
{
    IO1PIN = 0x0; //Enter Instruction Mode
    IO0PIN = cmd; //Supply Instruction/Command Code
    enable(); //Pulse Enable to process it
}

void delay(void)
{
    int i=0,x=0;
    for(i=0; i<19999; i++){ x++; }
}

Program #2 : Program with PLL* setup and precise Timing**. (Xtal=12Mhz , CCLK=60Mhz)

*PLL tutorial for LPC2148 @ http://www.ocfreaks.com/lpc214x-pll-tutorial-for-cpu-and-peripheral-clock/
**Tutorial on Timer for LPC2148 will be posted soon.

/*
 (C) OCFreaks! | Umang Gajera 2012-13.
 More Embedded tutorials @ www.ocfreaks.com/cat/embedded
 >>Program2 for Interfacing 16X2 LCD Module with LPC2148
 >>using Timer0 and CPU @ 60Mhz
*/



#include <lpc214x.h>

/*
 XTAL Freq=12 Mhz , CCLK=60Mhz , PCLK=60Mhz
 Using common delay of 2ms for all operations
   
 Connections from LPC2148 to LCD Module:
 P0.0 to P0.7 used as Data bits.
 P1.16 connected to pin4 i.e. RS    - Command / Data
 P1.17 connected to pin6 i.e. E - Enable
 Pin5 of LCD Module i.e. 'R/W' connected to ground
*/


#define PLOCK 0x00000400

void initLCD(void);
void enable(void);
void LCD_WriteChar(char c);
void LCD_WriteString(char * string);
void LCD_Cmd(unsigned int cmd);
void delayMS(unsigned int milliseconds);

void setupPLL0(void);
void feedSeq(void);
void connectPLL0(void);


int main(void)
{
    setupPLL0();
    feedSeq(); //sequence for locking PLL to desired freq.
    connectPLL0();
    feedSeq(); //sequence for connecting the PLL as system clock
   
    //SysClock is now ticking @ 60Mhz!
       
    VPBDIV = 0x01; // PCLK is same as CCLK i.e 60Mhz
   
    //PLL0 Now configured!

    initLCD(); //LCD Now intialized and ready to Print!
    LCD_WriteString(".: Welcome to :.");
    LCD_Cmd(0x80 + 0x40); //Come to 2nd Row
    LCD_WriteString("www.OCFreaks.com");
    while(1); // Loop forever    
    return 0; //This won't execute :P
}

void initLCD(void)
{
    IO0DIR = 0xFF; //P0.0 to P0.7 configured as Output - Using 8 Bit mode
    IO1DIR |= (1<<16) | (1<<17); //P1.16 and P1.17 configured as Output - Control Pins
    IO0PIN = 0x0; //Reset Port0 to 0.  
    IO1PIN = 0x0; //Reset Port1 to 0 - Which also makes RS and Enable LOW.

    //LCD Initialization Sequence Now starts
    delayMS(20); //Initial Delay
    LCD_Cmd(0x3C); //Function Set Command : 8 Bit Mode , 2 Rows , 5x10 Font Style
    LCD_Cmd(0x0F); //Display Switch Command : Display on , Cursor on , Blink on
    LCD_Cmd(0x06); //Input Set : Increment Mode
    LCD_Cmd(0x01); //Screen Clear Command , Cursor at Home
    LCD_Cmd(0x80); //Not required the 1st time but needed to reposition the cursor at home after Clearing Screen
    //Done!
}

void enable(void)
{
    //Using common delay of 2ms
    delayMS(2);
    IO1PIN |=  (1<<17);//Enable=High
    delayMS(2);
    IO1PIN &= ~(1<<17);//Enable=Low
    delayMS(2);
}

void LCD_WriteChar(char c)
{
    IO1PIN |= (1<<16); //Switch to Data Mode
    IO0PIN = (int) c; //Supply Character Code
    enable(); //Pulse Enable to process it
}

void LCD_WriteString(char * string)
{
    int c=0;
    while (string[c]!='\0')
    {
        LCD_WriteChar(string[c]);
        c++;
    }
}
       
void LCD_Cmd(unsigned int cmd)
{
    IO1PIN = 0x0; //Enter Instruction Mode
    IO0PIN = cmd; //Supply Instruction/Command Code
    enable(); //Pulse Enable to process it
}

void delayMS(unsigned int milliseconds)
{
//Timers will be explained in detail in the very next tutorial @ www.ocfreaks.com/cat/embedded/
   
    T0IR = 0;
    T0CTCR = 0;
    T0PR = 60000; //60000 clock cycles @60Mhz = 1 mS
    T0PC = 0;
    T0TC = 0;
    T0TCR = 0x01; //enable timer
   
    //wait until timer counter reaches the desired dela1y  
    while(T0TC < milliseconds);
   
    T0TCR = 0x00; //disable timer
}

//---------PLL Related Functions :---------------

void setupPLL0(void)
{
    PLL0CON = 0x01; // PPLE=1 & PPLC=0 so it will be enabled
                    // but not connected after FEED sequence
    PLL0CFG = 0x24; // set the multipler to 5 (i.e actually 4)
                    // i.e 12x5 = 60 Mhz (M - 1 = 4)!!!
                    // Set P=2 since we want FCCO in range!!!
                    // So , Assign PSEL =01 in PLL0CFG as per the table.
}

void feedSeq(void)
{
    PLL0FEED = 0xAA;
    PLL0FEED = 0x55;
}

void connectPLL0(void)
{
    // check whether PLL has locked on to the  desired freq by reading the lock bit
    // in the PPL0STAT register

    while( !( PLL0STAT & PLOCK ));

    // now enable(again) and connect
    PLL0CON = 0x03;
}

Output after flashing any of the above code to MCU (Successfully Tested on Lpc2148 and Lpc1768) :

Download Links to Source Code using KEIL UV4 Project IDE :

1) Program #1 : lpc2148_lcd_interfacing.rar
2) Program #2 : lpc2148_lcd_interfacing_precise.rar

Silence please : 2 LCDs were Harmed in the making
While working on this article 2 of my Green LCDs were damaged. One got completely useless and other got partially damaged. In any case both of them are useless now.

LCD #1 : Display completely gone kaput..

LCD #2 : Display partially gone kaput..



Filed Under: Embedded

Tags: , , , , , , , , , ,

About the Author

The Author seems to be into Robotics , Gaming , Mechanics , Embedded stuff , PC Overclocking , CNCs n 3D-printers , RC Hobby , DIYing , and God knows what kinda other crap and keeps on getting regularly trolled by self xD

Comments (10)

Trackback URL | Comments RSS Feed

  1. Ananda says:

    I am eagerly waiting for the tutorial “Interrupts in LPC214x” since after GPIO I have started programming on interrupts. Please post it soon, else please reply me with some links which I can go through until you post interrupt article.

    Also is it true that Fast Interrupt requests will work only on those MCUs having speed > 60MHz.

    Thank you.

  2. Rogerio says:

    I don’t understand. The CD4050 is a high-to-low level shifter. The CD4050 VIH (HIGH level input voltage) is 3.15V to 4.5V suply. Identical to JHD162A the input VIH is 0.7VCC.

    Thanks.

  3. sethupathy says:

    sir, tried with 1st lcd coding i got output as p p with some symbol.
    p’ls help me to solve the issues asap.

    • Umang Gajera says:

      Hi Sethupathy , Which development board are you using ? Are you getting a blinking cursor after initializing the module ? I need some more details from your side to pin down the root cause of error.

      -Regards

  4. Gopi M says:

    Hai,

    this is the tutorial link of oc freaks lcd 16X2 interface program with ARM lpc2148 , i follow this program …

    Interfacing 16X2 LCD with LPC2148 tutorial : OCFreaks!

    first i followed the oc freaks program for interfacing of lcd 16X2 with ARM lpc2148. i can understand the coding and successfully execute the program in my ARM lpc2148 development board. what the program in oc freak tutorial followed is they using port 1 (RS = P1.16 & EN = P1.17) for control pins and port 0 for data pins( D0 t0 D7 ….. P0.0 to P0.7 respectively ) but what i am trying to implement is i want to configure both data pins and control pin in port one itself( port 0) . i wrote program for that task but what the problem i faced is , the command is clearly executing , i saw the cursor in the LCD display but the letter are displayed like junk values.

    so here i mention what all the pins i used and my program.

    Register Select = P0.10
    Enable = P0.13

    D0 = P0.16
    D1 = P0.17
    D2 = P0.18
    D3 = P0.19
    D4 = P0.20
    D5 = P0.21
    D6 = P0.22
    D7 = P0.23

    #include
    void initLCD(void);
    void enable(void);
    void LCD_WriteChar(char c);
    void LCD_WriteString( char * string);
    void LCD_Cmd(unsigned int cmd);
    void delay(void);
    #define RS (1<<10)
    #define EN (1<<13)
    int main(void)
    {
    initLCD();
    LCD_WriteString("GOPI EINSTEIN");
    }

    void initLCD(void)
    {
    IO0DIR = 0xFFFFFFFF;
    IO0PIN = 0×00;
    delay();
    LCD_Cmd(0×38);
    LCD_Cmd(0x0e);
    LCD_Cmd(0×06);
    LCD_Cmd(0×01);
    LCD_Cmd(0×80);
    }

    void enable(void)
    {
    delay();
    IO0PIN |=EN;
    delay();
    IO0PIN &=~EN;
    delay();
    }

    void LCD_WriteChar(char c)
    {
    IO0PIN |=RS;
    IO0PIN |=(((int)c)<<16);
    enable();
    }

    void LCD_WriteString ( char * string)
    {
    while (* string)
    {
    LCD_WriteChar(* string++);
    }
    }

    void LCD_Cmd(unsigned int cmd)
    {
    IO0PIN =0×0;
    IO0PIN |= (cmd<<16);
    enable();
    }

    void delay(void)
    {
    int i=0;
    for(i=0; i<19999; i++);

    }

    this is the program, what a wrote.

    please clear my doubt……

  5. PRAVEEN says:

    Actually when i started studying with ARM, i felt that it is too tough .
    But when i undergone your tutorials named as ,
    http://www.ocfreaks.com/lpc2148-gpio-programming-tutorial/
    http://www.ocfreaks.com/interfacing-16×2-lcd-with-lpc2148-tutorial/
    am really feeling good and i understood lot of things ,Thank you very much for your guidance

Leave a Reply




If you want a picture to show with your comment, go get a Gravatar.