How to control a HD44780-based Character-LCD
The Industry Standard Character LCD
Visitor # (unknown)
© 1995-2012 Peter Ouwehand.
Last updated on 2012-10-03
Code examples for Intel 8051 family
TOC
3. 8051 example
3.1. Basic control software
3.1.1. Requirements / features
- HD44780-based (industry-standard) character-LCD, all software in this chapter is based on it's instruction-set.
- 8051 (or derivate) running on a 11.0952MHz crystal, some code is based on this frequency.
- 8-bit interface between microcontroller and LCD-module.
- All procedures coded as seperate .P51 programs so, when compiled and put in a library, only used procedures will be linked and loaded into (E)(E)(P)ROM.
- Can be (is) used as a 'device-driver'.
3.1.2. Global declarations
To get things working.
3.1.2.1. Register declarations
/* Necessairy global declarations in source file:               */
DECLARE lcdregs     BYTE PUBLIC AT (0....H) AUXILIARY;
DECLARE lcddata     BYTE PUBLIC AT (0....H) AUXILIARY;
DECLARE lcdstat     WORD PUBLIC;
DECLARE lcdsize     BYTE PUBLIC;
/* 'lcdregs' and 'lcddata' form the interface to the display.   */
/*  must use the 'AT' attribute (hardware address decoding).    */
/* 'lcdstat' and 'lcdsize' contain status information used by   */
/* procedures in LCD.LIB and should not be changed by the user. */
3.1.2.2. Literal declarations
Purpose:
- Global literal declarations used in the library code.
Code:
DECLARE
    lcd1x16     LITERALLY '000',
    lcd2x16     LITERALLY '001',
    lcd4x16     LITERALLY '002',
    lcd1x20     LITERALLY '003',
    lcd2x20     LITERALLY '004',
    lcd4x20     LITERALLY '005',
    lcd1x24     LITERALLY '006',
    lcd2x24     LITERALLY '007',
    lcd4x24     LITERALLY '008',
    lcd1x32     LITERALLY '009',
    lcd2x32     LITERALLY '010',
    lcd4x32     LITERALLY '011',
    lcd1x40     LITERALLY '012',
    lcd2x40     LITERALLY '013',
    lcd4x40     LITERALLY '014';    /* not supported !  */
3.1.2.3. Procedure declarations / library interface
Purpose:
- Global procedure declarations to get access to the library.
Code:
lcdinit:PROCEDURE(size) EXTERNAL;
    DECLARE size        BYTE;
END lcdinit;

lcdbusy:PROCEDURE BIT EXTERNAL;
END lcdbusy;

lcdclear:PROCEDURE EXTERNAL;
END lcdclear;

lcdhome:PROCEDURE EXTERNAL;
END lcdhome;

lcddmode:PROCEDURE(function) EXTERNAL;
    DECLARE function    BYTE;
END lcddmode;

lcdemode:PROCEDURE(function) EXTERNAL;
    DECLARE function    BYTE;
END lcdemode;

lcdsdda:PROCEDURE(ramaddress) EXTERNAL;
    DECLARE ramaddress  BYTE;
END lcdsdda;

lcdscga:PROCEDURE(ramaddress) EXTERNAL;
    DECLARE ramaddress  BYTE;
END lcdscga;

lcdgaddr:PROCEDURE BYTE EXTERNAL;
END lcdgaddr;
3.1.3. Code
3.1.3.1. LCD initialisation
Purpose:
- LCD initialisiation code to be executed after power-up
  (i.e.: before any other procedures are used).
Code:
lcdinit:DO;
$include(c:\plm51\lcdsize.dcl)
DECLARE lcdregs     BYTE EXTERNAL AUXILIARY;
DECLARE lcdsize     BYTE EXTERNAL;
lcdinit:PROCEDURE(size) PUBLIC;
DECLARE size        BYTE;

IF  (size >= lcd1x16)
AND (size <= lcd4x40)
THEN
    lcdsize = size;
ELSE
    return;
CALL TIME(30);
lcdregs = 030H;
CALL TIME(60);
lcdregs = 030H;
CALL TIME(30);
lcdregs = 030H;
CALL TIME(30);
IF (lcdsize = lcd1x16)
OR (lcdsize = lcd1x20)
OR (lcdsize = lcd1x24)
OR (lcdsize = lcd1x32)
OR (lcdsize = lcd1x40)
THEN
    lcdregs = 030H;             /* 8 bits, 1 row, 5 x 7 dots        */
ELSE
    lcdregs = 038H;             /* 8 bits, 2 rows, 5 x 7 dots       */
CALL TIME(30);
lcdregs = 008H;                 /* display off,cursor off,no blink  */
CALL TIME(30);
lcdregs = 001H;                 /* clear display                    */
CALL TIME(30);
lcdregs = 0CH;                  /* display on, cursor off           */
CALL TIME(30);
lcdregs = 06H;                  /* auto-increment, shift cursor     */
CALL TIME(30);

END lcdinit;
END lcdinit;
3.1.3.2. Busy flag
Purpose:
- Tests if the LCD is busy.
- Returnvalue (bit):
  0 = LCD ready for instructions/data
  1 = LCD busy
Code:
lcdbusy:DO;
DECLARE	lcdregs	BYTE EXTERNAL AUXILIARY;
lcdbusy:PROCEDURE BIT PUBLIC;

IF (lcdregs AND 080H) <> 0              /* test busy flag           */
THEN                                    /* LCD is busy              */
    RETURN (1);
ELSE                                    /* LCD is ready             */
    RETURN (0);

END lcdbusy;
END lcdbusy;
3.1.3.3. Clear display
Purpose:
- Clears display and returns cursor to home position (upper-left corner).
Code:
lcdclear:DO;
DECLARE	lcdregs	BYTE EXTERNAL AUXILIARY;
lcdbusy:PROCEDURE BIT EXTERNAL;
END lcdbusy;
lcdclear:PROCEDURE PUBLIC;

DO WHILE lcdbusy;           /* wait till LCD ready                  */
END;
lcdregs = 001H;             /* clear display, return home           */
CALL TIME(30);              /* wait since busy flag isn't supported */
                            /* while clearing the display           */
END lcdclear;
END lcdclear;
3.1.3.4. Cursor home
Purpose:
- Returns cursor to home position.
- Returns display to original position (when shifted).
Code:
lcdhome:DO;
DECLARE	lcdregs	BYTE EXTERNAL AUXILIARY;
lcdbusy:PROCEDURE BIT EXTERNAL;
END lcdbusy;
lcdhome:PROCEDURE PUBLIC;

DO WHILE lcdbusy;                       /* wait till LCD ready      */
END;
lcdregs = 02H;                          /* home display and cursor  */

END lcdhome;
END lcdhome;
3.1.3.5. Entry mode
Purpose:
- Sets entry mode of the LCD
- b0    : 0 = no display shift, 1 = display shift
  b1    : 0 = auto-decrement, 1 = auto-increment
  b2-b7 : don't care
Code:
lcdemode:DO;
DECLARE	lcdregs     BYTE EXTERNAL AUXILIARY;
lcdbusy:PROCEDURE BIT EXTERNAL;
END lcdbusy;
lcdemode:PROCEDURE(function) PUBLIC;
DECLARE	function    BYTE;

function = (function AND 003H) + 004H;  /* strip bits and set b2    */
DO WHILE lcdbusy;                       /* wait till LCD ready      */
END;
lcdregs = function;                     /* set entry mode           */

END lcdemode;
END lcdemode;
3.1.3.6. Display mode
Purpose:
- Sets display control
- b0    : 0 = cursor blink off, 1 = cursor blink on (if b1 = 1)
  b1    : 0 = cursor off, 1 = cursor on
  b2    : 0 = display off, 1 = display on (display data remains in DD-RAM)
  b3-b7 : don't care
Code:
lcddmode:DO;
DECLARE	lcdregs     BYTE EXTERNAL AUXILIARY;
lcdbusy:PROCEDURE BIT EXTERNAL;
END lcdbusy;
lcddmode:PROCEDURE(function) PUBLIC;
DECLARE	function    BYTE;

function = (function AND 007H) + 008H;  /* strip bits and set b3    */
DO WHILE lcdbusy;                       /* wait till LCD ready      */
END;
lcdregs = function;                     /* set display mode         */

END lcddmode;
END lcddmode;
3.1.3.7. Set character generator RAM address
Purpose:
- Sets the Character-Generator-RAM address.
  CGRAM data is read/written after this setting.
Code:
lcdscga:DO;
DECLARE	lcdregs     BYTE EXTERNAL AUXILIARY;
lcdbusy:PROCEDURE BIT EXTERNAL;
END lcdbusy;
lcdsdda:PROCEDURE(ramaddress) PUBLIC;
DECLARE	ramaddress  BYTE;

DO WHILE lcdbusy;                       /* wait till LCD ready      */
END;
lcdregs = ramaddress + 40H;             /* write new CGRAM address  */

END lcdscga;
END lcdscga;
3.1.3.8. Set display data RAM address
Purpose:
- Sets the Display-Data-RAM address.
  DDRAM data is read/written after this setting.
Code:
lcdsdda:DO;
DECLARE	lcdregs     BYTE EXTERNAL AUXILIARY;
lcdbusy:PROCEDURE BIT EXTERNAL;
END lcdbusy;
lcdsdda:PROCEDURE(ramaddress) PUBLIC;
DECLARE	ramaddress  BYTE;

DO WHILE lcdbusy;                       /* wait till LCD ready      */
END;
lcdregs = ramaddress + 80H;             /* write new DDRAM address  */

END lcdsdda;
END lcdsdda;
3.1.3.9. Get address counter contents
Purpose:
- Returns address counter contents, used for both DDRAM and CGRAM.
Code:
lcdgaddr:DO;
DECLARE	lcdregs     BYTE EXTERNAL AUXILIARY;
lcdbusy:PROCEDURE BIT EXTERNAL;
END lcdbusy;
lcdgaddr:PROCEDURE BYTE PUBLIC;

DO WHILE lcdbusy;                       /* wait till LCD ready      */
END;
RETURN (lcdregs AND 07FH);              /* get current address      */

END lcdgaddr;
END lcdgaddr;
3.1.3.10. Write character
Purpose:
- Writes a character to LCD.
Code:
lcdwc:DO;
DECLARE	lcddata     BYTE EXTERNAL AUXILIARY;
lcdbusy:PROCEDURE BIT EXTERNAL;
END lcdbusy;
lcdwc:PROCEDURE(character) PUBLIC;
DECLARE	character   BYTE;

DO WHILE lcdbusy;                       /* wait till LCD ready      */
END;
lcddata = character;                    /* write char. to LCD       */

END lcdwc;
END lcdwc;
3.2. Advanced control software
3.2.1. Requirements / features
- Everything mentioned paragraph 3.1. Basic control software.
3.2.2. Global declarations
To get things working.
3.2.2.1. Procedure declarations / library interface
Purpose:
- Global procedure declarations to get access to the library.
Code:
lcduserchar:procedure(charno,sourceaddr) external;
    declare charno      byte;
    declare sourceaddr  address;
end lcduserchar;

lcdscp:PROCEDURE(row,position) EXTERNAL;
    DECLARE row         BYTE;
    DECLARE position    BYTE;
END lcdscp;

lcdwc:PROCEDURE(character) EXTERNAL;
    DECLARE character   BYTE;
END lcdwc;

lcdwsa:PROCEDURE(stringaddress,length) EXTERNAL;
    DECLARE stringaddress   ADDRESS;
    DECLARE length          BYTE;
END lcdwsa;
3.2.3. Code
3.2.3.1. User defined characters
Purpose:
- Load a user-defined character definition into the HD44780 character generator memory.

Quick explanation on how to implement user-defined characters:

First you will need to make a pixel definition for the characters
you want to use.  Below is the pixel definition for an underlined
'0' (char code 0x30) based on a 5x7 dots character definition:

       |   bits    | byte
  row  | 76543210  | value
  ------------------------
  000  |     xxx   | 0x0E
  001  |    x   x  | 0x11
  010  |    x  xx  | 0x13
  011  |    x x x  | 0x15
  100  |    xx  x  | 0x19
  101  |    x   x  | 0x11
  110  |     xxx   | 0x0E
  111  |    xxxxx  | 0x1F

The byte values need to be loaded into CGRAM address 00cccrrr
(binary), where:
-  ccc = user-defined character number (0..7)
-  rrr = row number of the user-defined character (0..7)

Once that is done you can write user-defined character codes 0..7 to the
desired LCD character position, just like you do with 'normal' characters.

User-defined character definitions may be redefined 'on-the-fly'.

While defining a 5x7 dots character:
- Character code bits (DDRAM) 2..0 correspond to CGRAM address bits 5..3
  (i.e. 8 possible user-defined characters).
While defining a 5x10 dots character:
- Character code bits (DDRAM) 2..1 correspond to CGRAM address bits 5..4
  (i.e. 4 possible user-defined characters).

It is best to switch off the cursor while writing to CGRAM.

See also 2.6. Related pages.

- Function parameters:
  charno     : the character number (0..7) to be defined
  sourceaddr : pointer to an 8-byte array in code memory which holds the character definition
Code:
lcduserchar:do;
declare	lcdregs     byte external auxiliary;
declare	lcddata     byte external auxiliary;
lcdgaddr:procedure byte external;
end lcdgaddr;
lcdbusy:procedure bit external;
end lcdbusy;
lcdsdda:procedure(ramaddress) external;
    declare	ramaddress      byte;
end lcdsdda;
lcduserchar:procedure(charno,sourceaddr) public;
declare	charno      byte;
declare	sourceaddr  address;
declare	pattern     based sourceaddr byte constant;
declare	ddrampos    byte;

if charno >= 8
then                                        /* invalid character number     */
    return;                                 /* quit                         */

ddrampos = lcdgaddr;                        /* get current DDRAM position   */

do while lcdbusy;                           /* wait till LCD ready          */
end;
lcdregs = (charno * 8) + 040h;              /* set new CGRAM address        */

do charno = 0 to 7;
    do while lcdbusy;                       /* wait till LCD ready          */
    end;
    lcddata = pattern;                      /* write bit-pattern to LCD     */
    sourceaddr = sourceaddr + 1;            /* point to next byte           */
end;

call lcdsdda(ddrampos);                     /* restore DDRAM position       */

end lcduserchar;
end lcduserchar;
3.2.3.2. Set cursor position
Purpose:
- Sets the cursor on the desired row and character position.
- Function parameters:
  row      : 0-based row number
  position : 0-based position (column)

Note: lcdsize.dcl contains the declarations mentioned in paragraph 3.1.2.2. Literal declarations.
Code:
lcdscp:DO;
$include(c:\plm51\lcdsize.dcl)
DECLARE lcdregs     BYTE EXTERNAL AUXILIARY;
DECLARE lcdsize     BYTE EXTERNAL;
lcdbusy:PROCEDURE BIT EXTERNAL;
END lcdbusy;
lcdscp:PROCEDURE(row,position) PUBLIC;
DECLARE row         BYTE;
DECLARE position    BYTE;

if lcdsize > lcd4x40
then                                            /* unknown display size     */
    return;

/*----------------------- column positioning -------------------------------*/
do case lcdsize;
    if position >= 8                            /* 1 x 16                   */
    then
        position = position + 040h;
    ;                                           /* 2 x 16                   */
    ;                                           /* 4 x 16                   */
    if position >= 10                           /* 1 x 20                   */
    then
        position = position + 040h;
    ;                                           /* 2 x 20                   */
    ;                                           /* 4 x 20                   */
    if position >= 12                           /* 1 x 24                   */
    then
        position = position + 040h;
    ;                                           /* 2 x 24                   */
    ;                                           /* 4 x 24                   */
    if position >= 16                           /* 1 x 32                   */
    then
        position = position + 040h;
    ;                                           /* 2 x 32                   */
    ;                                           /* 4 x 32                   */
    if position >= 20                           /* 1 x 40                   */
    then
        position = position + 040h;
    ;                                           /* 2 x 40                   */
    ;                                           /* 4 x 40                   */
end;

if (lcdsize = lcd2x16)
or (lcdsize = lcd2x20)
or (lcdsize = lcd2x24)
or (lcdsize = lcd2x32)
or (lcdsize = lcd2x40)
then
do case row;
    ;                                           /* row 0                    */
    position = position + 040h;                 /* row 1                    */
end;

if (lcdsize = lcd4x16)
or (lcdsize = lcd4x20)
then
do case row;
    ;                                           /* row 0                    */
    position = position + 040h;                 /* row 1                    */
    position = position + 014h;                 /* row 2                    */
    position = position + 054h;                 /* row 3                    */
end;

DO WHILE lcdbusy;                               /* wait till LCD ready      */
END;

lcdregs = position + 080h;

END lcdscp;
END lcdscp;
3.2.3.3. Write character
Purpose:
- Writes a character to the LCD at the current cursor position.
- Function parameter:
  character : character to be written
Code:
lcdwc:DO;
DECLARE lcddata     BYTE EXTERNAL AUXILIARY;
lcdbusy:PROCEDURE BIT EXTERNAL;
END lcdbusy;
lcdwc:PROCEDURE(character) PUBLIC;
DECLARE character   BYTE;

DO WHILE lcdbusy;                               /* wait till LCD ready      */
END;
lcddata = character;                            /* write char. to LCD       */

END lcdwc;
END lcdwc;
3.2.3.4. Write string
Purpose:
- Writes a string from auxiliary data memory (ext. RAM) to the LCD
  starting at the current cursor position.
- Function parameter:
  stringaddress : start address of the string in auxiliary data memory
  length        : length of the string
                  - 'length' = 0  : a $-terminated string is expected
                  - 'length' <> 0 : 'length' characters are written
Code:
lcdwsa:DO;
lcdwc:PROCEDURE(character) EXTERNAL;
    DECLARE character   BYTE;
END lcdwc;
lcdwsa:PROCEDURE(stringaddress,length) PUBLIC;
DECLARE stringaddress   ADDRESS;
DECLARE character       BASED stringaddress BYTE AUXILIARY;
DECLARE length          BYTE;
DECLARE terminator      LITERALLY '036';        /* ASCII code for '$'       */

IF length = 0
THEN                                            /* terminated string        */
DO WHILE character <> terminator;               /* while not end of string  */
    CALL lcdwc(character);                      /* write character          */
    stringaddress = stringaddress + 1;          /* adress of next char.     */
END;
ELSE                                            /* string length specified  */
DO WHILE length > 0;                            /* while not end of string  */
    CALL lcdwc(character);                      /* write character          */
    stringaddress = stringaddress + 1;          /* adress of next char.     */
    length = length - 1;                        /* decrement length counter */
END;

END lcdwsa;
END lcdwsa;
3.2.3.5. ?
Purpose:
- .
Code:

3.3. Availability
(To be published)
3.4. Target hardware
3.4.1. Controller
- A 8031 (or derivate) is used to control the LCD.
- Needs decoded LCD-READ en LCD-WRITE signals, derived from controller signals AD00..AD07, A08..A15, /RD and /WR, to map the LCD into the external DATA area.
- Address line A00 is connected to the DEMULTIPLEXED controller signal AD00 and controls which LCD register is accessed.
- Data lines AD00..AD07 are directly connected to controller signals AD00..AD07.
- Control line 'BACKLIGHT' is used to switch the LED-backlight on (logic 1) or off (logic 0) or could be controlled from a PWM output.
3.4.2. Interface
3.4.2.1. LCD interface
LCD interface
3.4.2.2. Address decoder example
An address decoder example for the schematic in 3.4.2.1. is available as 2 seperate GIF files:
1. The 8031 uC part with address demultiplexer which generates the A00 signal. View this gif.
2. The address decoder part which generates LCD-READ and LCD-WRITE signals. View this gif.
3.5. Development environment
3.5.1. Software
Intel:
- Compiler: PLM51 V1.3
- Relocator/linker: RL51 V3.1
- Librarian: LIB51 V1.1
- Object-hex converter: OH51 V1.1
(no longer sold/supported)
3.5.2. Hardware
- Ashling ICE/Debugger (preferred)
- Ceibo ICE/Debugger
Valid XHTML 1.1! Valid CSS!
Get Firefox Euro No Patents