How to control a HD44780-based Character-LCD
The Industry Standard Character LCD
Visitor #
Code examples for Intel 8051 family
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.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;
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;
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;
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;
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;
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.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.
- 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;
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;
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;
(To be published)
- 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.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
Intel:
- Compiler: PLM51 V1.3
- Relocator/linker: RL51 V3.1
- Librarian: LIB51 V1.1
- Object-hex converter: OH51 V1.1
(no longer sold/supported)
-
Ashling ICE/Debugger (preferred)
- Ceibo ICE/Debugger