LCD Alphanumeric Display - 2020

LCD Module Library Code

LCD Alphanumeric Display

PIC18F45k50 LCD Alphanumeric Display Interface

These types of display generally come with a HD44780 controller that has two operating modes 4-bit and 8-bit modes. There are usually 4 control lines and 8 data lines that are connected to a controller in this case a PIC microcontroller. The four control lines are defining how the data lines should be interpreted. The control pins are labelled EN – Enable Signal, RS – Register Select Signal, RW – Read/Write signal. To initialize an LCD the microcontroller needs to be able to send commands to the display. This is a simple function that sets the control lines and the 8-bit command to the LCD.

Pin Name Description
RS Register Select Signal High signal - the data lines are interpreted as Command.
Low signal - the data lines are interpreted as Data.
RW Read or Write Signal High signal - read operation mode.
Low signal - write operation mode.
EN Enable Signal Low - LCD module disabled.
High - LCD module in operation mode.

Pulse high to low - the LCD interprets the command or data.
D0 ~ D7 Data/Command bits 4 Bit mode – only D0~D3 are used.

8 bit mode – All D0 ~ D7 are used.

Circuit Diagram

LCD Module - PIC18F45K50 Diagram
Sending a commands

Place the byte command on the PORTD register, set RS to high for command, set RW to low for write mode, then pulse E high to low to signal the command. The LCD module will interpret the data bits as a command.

The LCD module will take the 8 data bits from the microcontroller as a write command when the enable signal pulses.

Sending a data

Sending data such as characters to the display is the exact same method as command expect that the RS signal is high. By placing the data on the PORTD register then set RS high (for data) and RW low for write command, then finally pulse the Enable signal will trigger the LCD to interpret the data bits as incoming data.

Waiting for LCD module

When sending either a command or data to the LCD module the program must wait for the LCD to process the command or data. This can be done by using a delay between the Enable signal pulse which has to be long enough for the LCD to process the instruction. I generally use a delay of 10ms between and after the enable pulse which seems to work fine.

LCD Commands
Instruction Value Description
FIRST_LINE 0x80 Moves the cursor to the first line first character.
SECOND_LINE 0xC0 Moves the cursor to the second line first character.
THRID_LINE 0x94 Moves the cursor to the third line first character.
FOUR_LINE 0xD4 Moves the cursor to the four-line first character.
CURSOR_HOME 0x02 Moves the cursor back to the first line and character.
CURSOR_LEFT 0x10 Moves the cursor Left by one character.
CURSOR_RIGHT 0x14 Moves the Cursor Right by one character.
CURSOR_ON_BLINK_OFF 0x0E Disables the cursor from blinking but displays a solid cursor.
CURSOR_ON_BLINK_ON 0x0F Enables cursor and blinking of cursor.
CGRAM_WRITE 0x40 Enter RAM write mode, for custom characters.
Initializing LCD Module

A sequence of command must be sent to the LCD module to initialize the start-up procedure. The first command to initialize the module is to set the operating mode of the display, in this case 8-bit mode.

Source Code
                                        /* 
    * File: LCD Module  
    * Author:  Mr Steven J Baldwin
    * Comments:
    * Revision history: 
    */
    
    // This is a guard condition so that contents of this file are not included
    // more than once.  
    #ifndef LCD_MODULE_H
    #define LCD_MODULE_H
    
    #define _XTAL_FREQ 20000000  //Crystal Frequency, used in delay
    
    #include <xc.h>
    
    //#define LCD_4BIT_MODE
    
    #define LCD_DATA   LATD
    #define LCD_EN     LATEbits.LATE2
    #define LCD_RW     LATEbits.LATE1
    #define LCD_RS     LATEbits.LATE0
    
    #define FIRST_LINE      0x80
    #define SECOND_LINE     0xC0
    #define THRID_LINE      0x94
    #define FOUR_LINE       0xD4
    
    #define CURSOR_HOME     0x02
    #define CURSOR_LEFT     0x10
    #define CURSOR_RIGHT    0x14
    
    #define CURSOR_ON_BLINK_OFF     0x0E
    #define CURSOR_ON_BLINK_ON      0x0F
    #define CURSOR_OFF              0x0C
    
    #define CGRAM_WRITE             0x40
    
    //
    // Custom Characters Data Send Values.
    //
    
    #define CUSTOM_CHAR_ONE             0x00
    #define CUSTOM_CHAR_TWO             0x01
    #define CUSTOM_CHAR_THREE           0x02
    #define CUSTOM_CHAR_FOUR            0x03
    #define CUSTOM_CHAR_FIVE            0x04
    
    
    //
    // initialize LCD module using 8 Bit
    //
    
    void lcd_init(void);
    
    //
    // Resset LCD
    //
    
    void lcd_reset(void);
    
    //
    // Send Command Instruction to LCD module
    //
    
    void lcd_send_cmd(unsigned char cmd);
    
    //
    // Send data to LCD Module
    //
    
    void lcd_send_data(unsigned char data);
    
    //
    // Send String to LCD Module
    //
    
    void lcd_send_string(const char* str);
    
    //
    // move cursor
    //
    
    void lcd_move_cursor(unsigned int x, unsigned int y);
    
    //
    // Clear LCD Display
    //
    
    void lcd_clear(void);
    
    
    //
    // Move cursor left by one
    //
    
    void lcd_cursor_left(void);
    
    //
    // Move cursor right by one
    //
    
    void lcd_cursor_right(void);
    
    //
    // shift display right
    //
    
    void lcd_shift_right(void);
    
    //
    // shift display left
    //
    
    void lcd_shift_left(void);
    
    
    
    #endif	/* XC_HEADER_TEMPLATE_H */
    
                                           
                                        
                                    
                                        /* 
    * File: LCD Module  
    * Author:  Mr Steven J Baldwin
    * 
    */
    
    #include "lcd_module.h"
    
    void lcd_init(void)
    {
    #ifdef LCD_4BIT_MODE
        
        __delay_ms(100);              // Wait for LCD
        
        lcd_send_cmd(0x33);           // Function Set 
        
        __delay_ms(45);               // Wait for LCD
        
        lcd_send_cmd(0x32);           // Function Set 
        
        __delay_ms(45);               // Wait for LCD
        
        lcd_send_cmd(0x28);           // 4 bit mode (binary : 0010 1000)
        
        __delay_ms(45);               // Wait for LCD
        
        lcd_send_cmd(0x0E);           // Set Entry mode  (binary : 0000 1110)
        
        __delay_ms(45);               // Wait for LCD
        
        lcd_send_cmd(0x0C);           // Display On, Cursor Off, Blinking Off  (binary : 0000 1100)
        
        lcd_send_cmd(0x0E);           // Cursor On
        
        
    #else
        
        __delay_ms(100);            // Wait for LCD
        
        lcd_send_cmd(0x38);         // Function Set, 8-bit mode 
        
        __delay_ms(15);             // Wait for LCD
        
        lcd_send_cmd(0x38);         // Function Set 
        
        __delay_ms(15);             // Wait for LCD
        
        lcd_send_cmd(0x38);         //  Function Set 
        
        __delay_ms(15);             // Wait for LCD
        
        lcd_send_cmd(0x06);         // Increment Cursor
        
        lcd_send_cmd(0x0E);         // Set Entry mode Cursor On, non blinking
        
        __delay_ms(15);             // Wait for LCD
    #endif
    }
    
    
    //
    // Send Command Instruction to LCD module
    //
    
    void lcd_send_cmd(unsigned char cmd)
    {
    #ifdef LCD_4BIT_MODE
        unsigned char buffer;
        
        //
        // Split cmd into two sets of 4 bits
        //
        
        LCD_RS = 0;           // set register select signal to instruction
        LCD_RW = 0;           // Set Read/Write pin to Write
            
        //
        // Send High nibblet to LCD Module
        //
        
        buffer = cmd;
        buffer &= 0xF0;         // Mask Lower Bits (set lower bits to zero)
        LCD_DATA &= 0x0F;       // Only work with high nibblet
        LCD_DATA |= buffer;     // Send High nibblet to LCD module
        
        LCD_EN = 1;             // Set Enable Signal Flag    
        __delay_ms(1);          // give time for the LCD to respond to request
        LCD_EN = 0;             // Set Enable Signal Flag  
        __delay_ms(1);          // give time for the LCD to respond to request
        
        //
        // Send Lower nibblet to LCD Module
        //
        
        buffer = cmd << 4;      // Move lower nibblet to higher nibblet (shift left fgour times)
        buffer &= 0xF0;         // Mask upper bits.
        LCD_DATA &= 0x0F;       // Mask lower bits.
        LCD_DATA |= buffer;
        
        LCD_EN = 1;             // Set Enable Signal Flag    
        __delay_ms(1);          // give time for the LCD to respond to request
        LCD_EN = 0;             // Set Enable Signal Flag  
        __delay_ms(1);          // give time for the LCD to respond to request
        
    #else
        LCD_DATA = cmd;       // Data to send to module via PORTD.
        LCD_RS = 0;           // set register select signal to instruction
        LCD_RW = 0;           // Set Read/Write pin to Write
        LCD_EN = 1;           // Set Enable Signal Flag    
        
        __delay_ms(20);       // give time for the LCD to respond to request
        
        LCD_EN = 0;           // Set Enable Signal Flag to false 
        
        __delay_ms(20);       // give time for the LCD to respond to request
    #endif
    }
    
    //
    // Send data to LCD Module
    //
    
    void lcd_send_data(unsigned char data)
    {
    #ifdef LCD_4BIT_MODE
        
        unsigned char buffer;
        LCD_RS = 1;           // set register select signal to instruction
        LCD_RW = 0;           // Set Read/Write pin to Write
            
        //
        // Send High nibblet to LCD Module
        //
        
        buffer = data;
        buffer &= 0xF0;           // Mask Lower Bits (set lower bits to zero)
        LCD_DATA &= 0x0F;         // Only work with high nibblet
        LCD_DATA |= buffer;       // Send High nibblet to LCD module
        
        LCD_EN = 1;               // Set Enable Signal Flag    
        __delay_ms(1);            // give time for the LCD to respond to request
        LCD_EN = 0;               // Set Enable Signal Flag  
        __delay_ms(1);            // give time for the LCD to respond to request
        
        //
        // Send Lower nibblet to LCD Module
        //
        
        buffer = data << 4;      // Move lower nibblet to higher nibblet (shift left fgour times)
        buffer &= 0xF0;          // Mask lower bits.
        LCD_DATA &= 0x0F;        // 
        LCD_DATA |= buffer;
        
        LCD_EN = 1;              // Set Enable Signal Flag    
        __delay_ms(1);           // give time for the LCD to respond to request
        LCD_EN = 0;              // Set Enable Signal Flag  
        __delay_ms(1);           // give time for the LCD to respond to request
        
    #else
        LCD_RS = 1;           // set register select signal to instruction
        LCD_DATA = data;       // Data to send to module via PORTD.
        LCD_RW = 0;           // Set Read/Write pin to Write
        LCD_EN = 1;           // Set Enable Signal Flag    
        
        __delay_ms(20);          // give time for the LCD to respond to request
        
        LCD_EN = 0;           // Set Enable Signal Flag to false 
        
        __delay_ms(20);          // give time for the LCD to respond to request
        
        LCD_EN = 1;           // Set Enable Signal Flag to false 
        
    #endif
    }
    
    
    //
    // Send String to LCD Module
    //
    
    void lcd_send_string(const char* str)
    {
        // loop trough each character in string
        while(*str)
        {
            //  Send Character data to LCD
            lcd_send_data(*str++);
        }
    }
    
    
    //
    // move cursor to given column and line number
    //
    
    void lcd_move_cursor(unsigned int x, unsigned int y)
    {
        if(y == 0)
        {
            lcd_send_cmd(FIRST_LINE + x);
        }else if(y == 1)
        {
            lcd_send_cmd(SECOND_LINE + x);
        }else if(y == 2)
        {
            lcd_send_cmd(THRID_LINE + x);
        }else if(y == 3)
        {
            lcd_send_cmd(FOUR_LINE + x);
        }
    }
    
    //
    // Clear LCD Display
    //
    
    void lcd_clear(void)
    {
        lcd_send_cmd(0x01);
    }
    
    
    
    //
    // Move Cursor Left by one character
    //
    
    void lcd_cursor_left(void)
    {
        lcd_send_cmd(CURSOR_LEFT);
    }
    
    //
    // Move Cursor Right by one character
    //
    
    void lcd_cursor_right(void)
    {
        lcd_send_cmd(0x14);
    }
    
    //
    // shift display right
    //
    
    void lcd_shift_right(void)
    {
        lcd_send_cmd(CURSOR_RIGHT);
    }
    
    //
    // shift display right
    //
    
    void lcd_shift_left(void)
    {
        lcd_send_cmd(CURSOR_LEFT);
    }
    
    //
    //  Clear and send string in one function
    //
    
    void lcd_update(const char* str){
        lcd_clear();
        lcd_send_string(str);
        __delay_ms(750);
    }
                                    
                                        /*
    * File:   main.c
    * Author: Mr Steven J Baldwin
    *
    * Created on 28 August 2020, 11:50
    */
    
    // CONFIG1L
    #pragma config PLLSEL = PLL4X   // PLL Selection (4x clock multiplier)
    #pragma config CFGPLLEN = OFF   // PLL Enable Configuration bit (PLL Disabled (firmware controlled))
    #pragma config CPUDIV = NOCLKDIV// CPU System Clock Postscaler (CPU uses system clock (no divide))
    #pragma config LS48MHZ = SYS24X4// Low Speed USB mode with 48 MHz system clock (System clock at 24 MHz, USB clock divider is set to 4)
    
    // CONFIG1H
    #pragma config FOSC = LP        // Oscillator Selection (LP oscillator)
    #pragma config PCLKEN = OFF     // Primary Oscillator Shutdown (Primary oscillator shutdown firmware controlled)
    #pragma config FCMEN = OFF      // Fail-Safe Clock Monitor (Fail-Safe Clock Monitor disabled)
    #pragma config IESO = OFF       // Internal/External Oscillator Switchover (Oscillator Switchover mode disabled)
    
    // CONFIG2L
    #pragma config nPWRTEN = ON     // Power-up Timer Enable (Power up timer enabled)
    #pragma config BOREN = OFF      // Brown-out Reset Enable (BOR disabled in hardware (SBOREN is ignored))
    #pragma config BORV = 285       // Brown-out Reset Voltage (BOR set to 2.85V nominal)
    #pragma config nLPBOR = ON      // Low-Power Brown-out Reset (Low-Power Brown-out Reset enabled)
    
    // CONFIG2H
    #pragma config WDTEN = OFF      // Watchdog Timer Enable bits (WDT disabled in hardware (SWDTEN ignored))
    #pragma config WDTPS = 1        // Watchdog Timer Postscaler (1:1)
    
    // CONFIG3H
    #pragma config CCP2MX = RB3     // CCP2 MUX bit (CCP2 input/output is multiplexed with RB3)
    #pragma config PBADEN = OFF     // PORTB A/D Enable bit (PORTB<5:0> pins are configured as digital I/O on Reset)
    #pragma config T3CMX = RB5      // Timer3 Clock Input MUX bit (T3CKI function is on RB5)
    #pragma config SDOMX = RC7      // SDO Output MUX bit (SDO function is on RC7)
    #pragma config MCLRE = OFF      // Master Clear Reset Pin Enable (RE3 input pin enabled; external MCLR disabled)
    
    // CONFIG4L
    #pragma config STVREN = OFF     // Stack Full/Underflow Reset (Stack full/underflow will not cause Reset)
    #pragma config LVP = OFF        // Single-Supply ICSP Enable bit (Single-Supply ICSP disabled)
    #pragma config ICPRT = OFF      // Dedicated In-Circuit Debug/Programming Port Enable (ICPORT disabled)
    #pragma config XINST = OFF      // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled)
    
    // CONFIG5L
    #pragma config CP0 = ON         // Block 0 Code Protect (Block 0 is code-protected)
    #pragma config CP1 = ON         // Block 1 Code Protect (Block 1 is code-protected)
    #pragma config CP2 = ON         // Block 2 Code Protect (Block 2 is code-protected)
    #pragma config CP3 = ON         // Block 3 Code Protect (Block 3 is code-protected)
    
    // CONFIG5H
    #pragma config CPB = ON         // Boot Block Code Protect (Boot block is code-protected)
    #pragma config CPD = ON         // Data EEPROM Code Protect (Data EEPROM is code-protected)
    
    // CONFIG6L
    #pragma config WRT0 = ON        // Block 0 Write Protect (Block 0 (0800-1FFFh) is write-protected)
    #pragma config WRT1 = ON        // Block 1 Write Protect (Block 1 (2000-3FFFh) is write-protected)
    #pragma config WRT2 = ON        // Block 2 Write Protect (Block 2 (04000-5FFFh) is write-protected)
    #pragma config WRT3 = ON        // Block 3 Write Protect (Block 3 (06000-7FFFh) is write-protected)
    
    // CONFIG6H
    #pragma config WRTC = ON        // Configuration Registers Write Protect (Configuration registers (300000-3000FFh) are write-protected)
    #pragma config WRTB = ON        // Boot Block Write Protect (Boot block (0000-7FFh) is write-protected)
    #pragma config WRTD = ON        // Data EEPROM Write Protect (Data EEPROM is write-protected)
    
    // CONFIG7L
    #pragma config EBTR0 = ON       // Block 0 Table Read Protect (Block 0 is protected from table reads executed in other blocks)
    #pragma config EBTR1 = ON       // Block 1 Table Read Protect (Block 1 is protected from table reads executed in other blocks)
    #pragma config EBTR2 = ON       // Block 2 Table Read Protect (Block 2 is protected from table reads executed in other blocks)
    #pragma config EBTR3 = ON       // Block 3 Table Read Protect (Block 3 is protected from table reads executed in other blocks)
    
    // CONFIG7H
    #pragma config EBTRB = ON       // Boot Block Table Read Protect (Boot block is protected from table reads executed in other blocks)
    
    // #pragma config statements should precede project file includes.
    // Use project enums instead of #define for ON and OFF.
    
    #include <xc.h>
    #include "lcd_module.h"
    
    //Crystal Frequency in herz (20Mhz) this macro used for the xc8 __delay functions.
    #define _XTAL_FREQ 20000000
    
    
    /**
    * 
    *  Main program routine
    * 
    */
    
    void main(void) {
        
        /*
        * ANSELx registers
        */
        
        ANSELD = 0x00;      //  Set as digital
        ANSELC = 0x00;      //  Set as digital
        ANSELB = 0x00;      //  Set as digital
        ANSELE = 0x00;      //  Set as digital
        ANSELA = 0x00;      //  Set as digital
        
        ADCON1 = 0x0F;      //  Disable all Analog Inputs
        
        TRISA = 0x00;       //  Set Port A as output
        TRISB = 0x00;       //  Set Port B as output
        TRISC = 0x00;       //  Set Port C as output
        TRISD = 0x00;       //  Set Port D as output
        TRISE = 0x00;       //  Set Port E as output
        
        LATA = 0x00;        //  Clear Port A
        LATB = 0x00;        //  Clear Port B
        LATC = 0x00;        //  Clear Port C
        LATD = 0x00;        //  Clear Port D
        LATE = 0x00;        //  Clear Port E
        
        //	Initialize LCD Module
        lcd_init();
        
        // Send String to Display
        lcd_send_string("Hello World.");
            
        __delay_ms(1000);               //  Wait 1 second
            
        //  Execution Loop
        while(1){
            __delay_ms(1000);           //  Wait 1 second
            lcd_clear();				//	Clear Display
            
            //	Display on first line
            lcd_send_string("First Line");
            
            //	Move to the second line
            lcd_move_cursor(0, 1);
            
            lcd_send_string("Second Line");
            
            //	Move to first line
            lcd_move_cursor(0, 0);
        }
        
        return;
    }