8051 + LCD + EPROM 

Last Update March 30th 2004


This is a basic configuration for the 8051 family with an external eprom and LCD.

Several other variations can be done.  If using a 8051 derivative with internal code memory as 8751 or the reduced pin count as 80C751 80C752 AT89Cx051 or other, the LCD can be attached to the same pinouts as show here.
A variant of only 4 data pins can be implemented saving 4 port pins, this is described at the end of this chapter.


About the circuit

The variable resistor connected at LCD pin 3 (VD) and ground, controls the LCD display contrast.  LCD's with extended temperature range (negative C) can need a negative voltage at the VD pin (3), so the variable resistor need to be connected in a different way - the center tap goes to pin 3 (VD), one side to +5Vdc, while the other goes to a negative voltage around -3Vdc or more. To reduce power consume over this trimpot, you can try values higher than 10k ohms. I observed several LCD's that connecting VD to Ground is enough to result in a good display contrast. When you apply a resistor or a variable resistor to VD, a small current flows trough and a voltage develops over the resistor, normally this voltage is small between zero and 1 Volt.

The crystal connected at 8051 pins 18 and 19 needs the capacitors (33pF) connected to ground. Several 8051 works well without the capacitors, but some of them have intermitent clock "no-start" or "stop oscilattion" problems. To avoid problems just install the capacitors.

The 8051 reset pin receives a resistor (8k2 ohms) to ground and an electrolitic capacitor (10uF) connected to VCC.  At power up, the capacitor is discharged, so current flows from ground to +Vcc via the resistor and the capacitor, until it charges the capacitor. This time can be calculated as aprox;   time =  1 /  RxC  or  82milliseconds.  The CMOS versions doesn't need the 8k2 resistor, but the books say it doesn't harm to install it. If you want to install a "reset" button, just install the switch in parallel to the capacitor, so when pressing the key it will apply +Vcc to the Reset pin, also discharging the capacitor.


External Memory

The 8051 uses the Port P0 in some different ways when addressing external memory;

Reading EPROM

When reading the code memory, in this case an eprom, the 8051 breaks the 16 bits address (64k) into two bytes of 8 bits each. The high order bits are posted at the port P2 bits, while the low order 8 bits are posted at port P0 bits.

The 8051 then pulses up the pin ALE (30) to signal the external circuits that the address is available at the output of P0 and P2.  This ALE up pulse tells the 74HCT573 (8 bits latch) to store the 8 low order address bits from P0.  The output of this latch goes directly to the eprom low 8 bits address, and stays there until a new ALE pulse tells it to store a different address.  At this point the eprom has the 16 address bits stable until P2 changes or ALE pulses up with a different value at P0.

The 8051 then pulses down PSEN pin 29 to get the program byte stored at the eprom, addressed by the above sequence.  This PSEN low level pulse goes directly into the /CE eprom pin 20, this selects the eprom chip.  The /OE (output enable pin 22) allows the eprom to outputs the byte addressed at pins 11-19.  At this moment the 8051 reads the port P0 pins, and it will be the eprom output byte.

While no software instruction changes the P2 value, and the external eprom address 8 bits high order still the same, the P2 value will remains unchanged.

The 8051 reads external code memory whenever the EA (pin 31) is connected to ground, even If the 8051 has internal eprom/rom.
To execute the code stored into the internal eprom/rom (if exist)  the EA pin must be connected to +5Vdc (high level).

Years ago I build an emulator that uses a 8751 with internal eprom programmed to download data from a PC serial port at high speed and store it in an external RAM memory. This data in real was a 8051 program under development, to be executed by the 8051.  After the data transfer, a manual switch side A changes the EA level from high to low and side B transfers the /CE RAM pin from the "external address decoding" to 8051 PSEN, so the 8051 "thinks" the RAM is an Eprom, and a new RESET makes the 8051 runs the downloaded program. It worked pretty well until I bought an EPROM EMULATOR, which is a much better tool.

The eprom at this example is a 27C512, so 64kbytes. Any other smaller eprom can be connected to this example, for sure reducing the high address bits.  A 27C256 addresses only 32k and has only 15 address pins, A15 not used, 27C128 doesn't use A15 neighter A14, and so on.


External RAM

External RAM is not shown in the drawing, but it can be connected exactly as the Eprom, pratically in parallel with the eprom, sharing the same 74HCT573 output, except pins /CE, /OE and /W. In this case, the /CE RAM pin is not connected to PSEN (PSEN means only reading instruction code), instead it is connected to an "external address decoding circuit" that activates low level whenever the 8051 drives /RD or /WR (pins 17 or 16) low meaning READ from or WRITE to the external RAM chip.

To write into the RAM, the 8051 post the high order address 8 bits at P2, the low order address 8 bits at P0, pulses up ALE to store P0 at the latches, then post the data to be writen at P0 and pulses /WR low.  This /WR low need to be translated by the "external address decoding circuit" into --/W+/OE and --/CE to the RAM, this stores the byte from P0 into the RAM addressed by P2 and the output of the 74HCT573.

Toread from the RAM, the 8051 does the same addressing, but now it only pulses low the /RD pin, and the "external address decoding circuit" translates it into +/W--/OE and --/CE to the RAM, this reads the RAM and its contents is posted at the RAM output, so the 8051 reads this byte available at port P0 input pins.


LCD Interface
Standard LCD 14 pins


 Pin #
 Name
 In/Out
 Description
1
 Ground - Power supply zero Volts and Signal Ground.
2
 VSS + +5Vdc power supply. Works with lower voltages.
3
 VD
 Contrast Voltage
In As described before, it needs to be close to ground level, and it can be adjusted via a variable resistor, trimpot or potentiometer, for a better view.  Some extended temperature range LCD's may require a negative value at this pin.
4
 RS In Data sent to LCD can be a Character to be displayed (RS:High) or a command (instruction) to be executed by the LCD controller (RS:Low).
5  RW In Data direction. HIGH means Read from LCD,  LOW means Write to LCD.
6  E or CE In CE:High tells LCD to initiate internal operation based on the RS, RW and Data signal levels.
7-14  Data pins (8) I/O Data to be displayed or command (Instruction) to be executed.

Programming Exercises

Suppose our assembler code has the following equates:

RS    bit    INT1
RW    bit    T0
CE    bit    T1
Busy  bit    P1.7

or

RS    bit    0B3h
RW    bit    0B5h
CE    bit    0B4h
Busy  bit    097h

Data Write
To write a character (Data)  INTO the LCD display, it is necessary to preset RS High, R/W low, character byte at Data Pins 7-14, and then pulse CE pin up once.

Suppose we have the Data to write at the LCD at the accumulator, so the routine DataWrite would be:

DataWrite:
      Mov   P1,A    ; Post Accumulator at LCD data input
      Setb  RS      ; Set RS high (Data)
      Clr   RW      ; Set RW low (Write)
      Nop           ; Wait circuit settling
      Setb  CE      ; Tells the LCD to work (receive data byte)
      Nop           ; Wait a little bit
      Nop           ;
      Clr   CE      ; Drop the selection line
      Ret           ; Return to Caller
 

Instruction Write
To write a command or address (Instruction) to the LCD, preset RS Low, R/W low, command or address at Data Pins 7-14, and then pulse CE pin up once.  Suppose we have an instruction at the accumulator, to send it to LCD it would be:

CmdWrite:
      Mov   P1,A    ; Post Accumulator at LCD data input
      Clr   RS      ; Set RS low (Instruction)
      Clr   RW      ; Set RW low (Write)
      Nop           ; Wait circuit settling
      Setb  CE      ; Tells the LCD to work (receive instruction byte)
      Nop           ; Wait a little bit
      Nop           ;
      Clr   CE      ; Drop the selection line
      Ret           ; Return to Caller
 

LCDs with a chip on the back identified as HD44780 (Hitachi),  HD44100H (Hitachi),  M5259 (OKI) or SED1300F or others, understand a popular "standard LCD command set". We will describe it later.

The most popular LCD displays are produced in 1, 2 or 4 lines, with 8, 16, 20 or 40 characters per line in this formats:
1x8, 2x8, 1x16, 2x16, 4x20 or 2x40.

At power on, the LCD needs to be programmed for proper operation.  It consists to send a series of command bytes to the LCD.
For our tests here, the sequence is 038h 038h 006h, 00Eh, 001h.  This will program the LCD for display active, new characters to the right, character normal size 5x7 dots, increment cursor position, cursor on, not blinking.

(The initialization sequence map for 8 and 4 bits, you can find at the end of this document).

To do that, we just need to post those bytes one by one at port P1 (according with the drawing), and execute the Instruction Write as described above, for each byte in the sequence.  Our assembler initialization routine would be:

LCDInitialize:
      Mov   A,#038h          ;  Data Lenght (8 bits), 2 lines, char 5x7
      Call  CmdWrite         ;  Needs 3 times this byte (1st)
      Call  Delay10ms        ;  Some delay routine 10 milliseconds
                             ;
      Call  CmdWrite         ;  (second)
      Call  Delay10ms        ;  Some delay routine 10 milliseconds
                             ;
      Call  CmdWrite         ;  (third)
      Call  Delay10ms        ;  Some delay routine 10 milliseconds
                             ;
      Mov   A,#006h          ;  Move direction and Shift Display
      Call  CmdWrite         ;
      Call  Delay10ms        ;  Some delay routine 10 milliseconds
                             ;
      Mov   A,#00Eh          ;  Display and Cursor On, not blinking
      Call  CmdWrite         ;
                             ;
      Mov   A,#001h          ;  LCD Clear
      Jmp   CmdWrite         ;  ... and return to caller

The Call Delay10ms instructions are necessary because the LCD needs some time to execute the first command sent, and the busy bit (explained later) is not working yet.

Now our LCD is ready to receive the first data, so the following instruction will write "HELLO" at the display;

Hello:
      Mov   A,#'H'           ;
      Call  DataWrite        ;
      Mov   A,#'E'           ;
      Call  DataWrite        ;
      Mov   A,#'L'           ;
      Call  DataWrite        ;
      Mov   A,#'L'           ;
      Call  DataWrite        ;
      Mov   A,#'O'           ;
      Jmp   DataWrite        ; ... and return to caller

This data "HELLO" will be at the left of the first line. This is because it is following the "CLEAR" command send by the initialization routine.  The instruction 001h is a clear command, it clears the display positioning the cursor at the left of the first line.

The first left position of the first line has the address 080h, the second 081h, third 082h...
The first left position of the second line has the address 0C0h, the second 0C1h, third 0C2h...

So, if you want to write the text "HELLO" at the second line instead the first, you need to send the address (Instruction) 0C0h to the LCD before you send the data bytes, so it would be;

      Mov   A,#0C0h          ; Position cursor at second line left
      Call  CmdWrite         ; Tell Lcd to do it
      Jmp   Hello            ; ... and return to caller
 

We can create a Message Routine to display text string at certain display positions.  To call this routine, first load DPTR with the text string address.  Each text string is ended with a "zero". We can call this a "stringz" style.

Example of stringz:
MSG001:   DB    "HELLO"
          DB    000h

Example of LCD_MSG routine to display a stringz text (Dptr must be pointing to the Stringz):
LCD_MSG:  Clr   A                ; Clear Index
          Movc  A,@A+Dptr        ; Get byte pointed by Dptr
          Inc   Dptr             ; Point to the next byte
          Jz    LCD_Msg9         ; Return if found the zero (end of stringz)
          Call  DataWrite        ; It was data, write it to Lcd
          Jmp   Lcd_Msg          ; Go get next byte from stringz
Lcd_Msg9: Ret                    ; Return to Caller

So, the following code will display the MSG001 starting at the current cursor position:
          Mov   Dptr,#MSG001     ;
          Call  Lcd_Msg          ;
 

We can implement an automatic addressing byte in the middle (initial) of the StringZ, and implement this special LCD addressing at the LCD_MSG routine:

Example of stringz to be displayed at first line, starting at position # 5:
MSG001:     DB    084h
            DB    "HELLO"
            DB    000h

Example of stringz to be displayed at second line, 8th position:
MSG002:     DB    0C7h,"HELLO",000h

Example of LCD_MSG routine to display a stringz text with automatic address recognition. New code in red.
(Dptr must be pointing to the Stringz):

LCD_Msg:
          Clr   A                ; Clear Index
          Movc  A,@A+Dptr        ; Get byte pointed by Dptr
          Inc   Dptr             ; Point to the next byte
          Jz    LCD_Msg9         ; Return if found the zero (end of stringz)
          Cjne  A,#07Fh,$+3      ; Data or Address?  If A > 7Fh is address.
          Jnc   Lcd_Msg_Data     ; Carry will be clear if A =< 7Fh (Data)
          Call  CmdWrite         ; Carry set if A > 7Fh, it is address
          Jmp   Lcd_Msg          ; Go get next byte from stringz
Lcd_Msg_Data:                    ;
          Call  DataWrite        ; It was data, write it to Lcd
          Jmp   Lcd_Msg          ; Go get next byte from stringz
Lcd_Msg9: Ret                    ; Return to Caller
 

Example to show MSG001:
          Mov   Dptr,#MSG001
          Call  LCD_Msg

Example to show MSG002:
          Mov   Dptr,#MSG002
          Call  LCD_Msg

We can implement more imbeded commands (instructions), in red,  to the stringz text, as LCD_CLEAR (001h) for example:

LCD_MSG:  Clr   A                ; Clear Index
          Movc  A,@A+Dptr        ; Get byte pointed by Dptr
          Inc   Dptr             ; Point to the next byte
          Jz    LCD_Msg9         ; Return if found the zero (end of stringz)
                                 ;
          Cjne  A,#001h,Lcd_Msg1 ; Check if is a Clear Command
          Call  CmdWrite         ; If yes, write it as command to LCD
          Jmp   Lcd_Msg          ; Go get next byte from stringz
                                 ;
Lcd_Msg1: Cjne  A,#07Fh,$+3      ; Data or Address?  If A > 7Fh then is address.
          Jnc   Lcd_Msg_Data     ; Carry will be clear if A =< 7Fh (Data)
          Call  CmdWrite         ; Carry not set if A > 7F, it is address
          Jmp   Lcd_Msg          ; Go get next byte from stringz
                                 ;
Lcd_Msg_Data:                    ;
          Call  DataWrite        ; It was data, write it to Lcd
          Jmp   Lcd_Msg          ; Go get next byte from stringz
Lcd_Msg9: Ret                    ; Return to Caller
 

Using this fashion of stringz, you can build a complex message that changes cursor pointer in the middle of the text:

MSG003:   DB    001h             ; Clear LCD
          DB    085h             ; Set cursor at 6th position 1st line
          DB    "HELLO"          ; Text
          DB    0C2h             ; Set cursor at 3rd position 2nd line
          DB    "WORLD"          ; Text
          DB    000h             ; End of Stringz
Note the ending zero only at the end.

So, now the following routine will clear the LCD, write HELLO at first line, position 6, WORLD at second line position 3;

          Mov   Dptr,#MSG003
          Call  LCD_MSG


LCD addressing values

1 x 16
80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
2 x 16
80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF
4 X 20
80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93
C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF D0 D1 D2 D3
94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7
D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7


Busy Bit

The Lcd controller chip needs some time to execute the instruction or data write, and whenever it receives something it starts this internal operation routine that takes time, during this time the controller will be busy and will not understand anything else that you send.  So, it will be necessary to wait a little bit after you send something to the LCD. How long is it?  Nobody knows, it depends on the controller type and operation, and it can varies.  You can build a "wasting time" routine to wait for some time after each write to the LCD, but it will be or too short, or too long because you would never know how long will be enough.  For example, the Clear command takes the longest time, a simple data write not too long.

To avoid guessing and testing, just use a simple way to check if the LCD controller is busy or not, there is the busy bit.
After writing to the LCD, send a "read instruction" and check bit 7, this is the busy bit, a high level means the controller still busy, a low level means the controller finished the previous operation and is available to receive more bytes.

An example of assembler routine to check the busy bit:

LCD_BUSY:   Clr   CE          ; Make sure LCD is not selected
            Setb  P1.7        ; Make sure Bit 7 will be pull up to read ok
            Setb  RW          ; Read Operation RW is up
            Clr   RS          ; Instruction Operation
LB1:        Clr   CE          ; Deselect LCD
            Setb  CE          ; Select LCD for read
            JB    P1.7,LB1    ; Still busy? keep checking
            Clr   CE          ; Not busy anymore, deselect LCD
            Ret               ; Return to Caller

The instruction above JB P1.7,LB1 can be substituted by JB Busy,LB1, since Busy was previously defined as bit P1.7 or 097h.

So, this routine needs to be inserted at InstructionWrite and DataWrite routine exit.
They will be this way:

Data Write
DataWrite:
      Mov   P1,A      ; Post Accumulator at LCD data input
      Setb  RS        ; Set RS high (Data)
      Clr   RW        ; Set RW low (Write)
      Nop             ; Wait circuit settling
      Setb  CE        ; Tells the LCD to work (receive data byte)
      Nop             ; Wait a little bit
      Nop             ;
      Clr   CE        ; Drop the selection line
      Call  LCD_Busy  ; Wait Controller not busy
      Ret             ; Return to Caller
 

Instruction Write
InstructionWrite:
      Mov   P1,A      ; Post Accumulator at LCD data input
      Clr   RS        ; Set RS low (Instruction)
      Clr   RW        ; Set RW low (Write)
      Nop             ; Wait circuit settling
      Setb  CE        ; Tells the LCD to work (receive instruction byte)
      Nop             ; Wait a little bit
      Nop             ;
      Clr   CE        ; Drop the selection line
      Call  LCD_Busy  ; Wait Controller not busy
      Ret             ; Return to Caller


Command Encoding



 
 

Instruction

RS
RW
D7
D6
D5
D4
D3
D2
D1
D0
Description
Clear Display
0
0
0
0
0
0
0
0
0
1
Clear all display and returns the cursor 
to the home position, address 080h
Return home
0
0
0
0
0
0
0
0
1
*
Returns the cursor to the home position
(address 080h). Also returns the display
being shifted to the original position. 
DD Ram contents remain unchanged.
Entry Mode
Set
0
0
0
0
0
0
0
1
I/D
S
Set the cursor move direction and 
specifies or not the shift display. These
operations are performed during data 
write and read.
Display On/
Off Control
0
0
0
0
0
0
1
D
C
B
Sets On/Off of all display (D),
Cursor On/Off (C), and blink of
cursor position character (B)
Cursor and
Display Shift
0
0
0
0
0
1
SC
RL
*
*
Moves the Cursor and shifts the
display without changing DD Ram
contents
Function Set
0
0
0
0
1
DL
N
F
*
*
Sets Interface data length (DL),
number of display lines (N) and
character font (F)
Write Data
1
0
Write Data
Writes data into DD Ram
Read Data
1
1
Read Data
Reads data from DD Ram
Set CG Ram
Address
0 0 0 1 Address CG Ram Sets the CG Ram address.  CG Ram
Data is sent and received after this.
Set DD Ram
Address
0
0
1
Address DD Ram
Sets the DD Ram address.  DD Ram 
Data is sent and received after this.
ID = 1 Increment (+1)
ID = 0 Decrement (-1)
S  = 1 Follows display shift
SC = 1 Display Shift
SC = 0 Cursor move
RL = 1 Shift to the Right
RL = 0 Shift to the Left
DL = 1 bits:8
DL = 0 bits:4
N  = 1 lines:2
N  = 0 lines:1
F  = 1 dots:5x10
F  = 0 dots:5x7
BF = 1 Controller Busy
BF = 0 Contr. Available
DDRAM = Display Data Ram
CGRAM = Character Generator user RAM


CGRAM User Configurable Characters



The first 8 ASCII characters can be configurable by the user, in a bitmap style.  After programming, the user can display those new characters writing the ASCII code 00h to 07h to DDRAM as a normal character.  For example, to display the letter "A" the user send the regular 41h, that is the ASCII value for it.  If the CGRAM character position 00h is programmed to has an inverted letter "R", for example, and the user needs to display the text  "ARAC" using the inverted "R", he needs to write this sequence to the LCD: 41h 00h, 41h, 43h.

The CGRAM accepts 64 data bytes, that will be the configurable bitmap for the first 8 ASCII characters.  It means that each configurable character will use 8 bytes from the CGRAM.  Character ASCII 00h will use from CGRAM address 00 to 07,  character ASCII 01 will use from 08h to 0Fh, and so on in blocks of 8 bytes each.

When writting to the CGRAM the internal counter increments automatically, so the user just need to set the first address and then send the bitmap data bytes in sequence, 8 bytes per programmable character.

To open the door to the CGRAM, the user needs to WRITE to the LCD with the RS bit low, as a command write, with the data byte 4Xh, while the "x" means the CGRAM address from 00 to 63 (00 - 3Fh).  To program the ascii 00h, first character, the user needs to write 40h, to program the second (ascii 01) the value would be 48h and so on.   The user can program more than one character at the same time, just sending the next 8 bytes.  64 bytes can be sent at once, and program all the 8 programable characters.  So, if the user wants to program all the 8 characters at once, just WRITE to the LCD with the RS bit low the value 40h, and follows 64 bytes with the bitmaps for the 8 chars.

Creating the bitmap for the "Inverted R":
Bits 7, 6 and 5 are not used, and the last byte is the cursor position, can be used for descending letters as "g" and "j" or special symbols that needs it, but they will interfere with the cursor at that position.
 
 

D7 D6 D5 D4 D3 D2 D1 D0  Byte to write 
 to CGRAM
 
0 0 0 0 1 1 1 1 0F
0 0 0 1 0 0 0 1 11
0 0 0 1 0 0 0 1 11
0 0 0 0 1 1 1 1 0F
0 0 0 0 0 1 0 1 05
0 0 0 0 1 0 0 1 09
0 0 0 1 0 0 0 1 11
0 0 0 0 0 0 0 0 00

To program the first ASCII (00h) programmed character with the "inverted R", the user needs to do this sequence:
 

 W pin   RS pin   Data Byte
0 0 40
0 1 0F
0 1 11
0 1 11
0 1 0F
0 1 05
0 1 09
0 1 11
0 1 00

After this, to program the next character just send the next 8 bitmap bytes, since the internal CGRAM address counter will be automatically pointing to the address 08h
 


4 Bits initialization sequence

The LCD can be programmed to use only 4 bits as Data instead 8, it can save 4 port pins. To use this, you need to change the first initialization bytes from 038h 038h 038h to 03xh 03xh 02xh, it means you only send the high order bits 7-4.  All subsequent bytes sent to LCD need to be done in two steps, first send the high order bits 7-4 then the low order 3-0, and data flow uses on LCD data bits 7-4, (pins 7-10, bits 0-3 are not used).
 

Activity
RS  RW  DB7 DB6 DB5 DB4  Busy Bit 
readable?
Power On No
Wait more than 15ms after Vcc raises to +4.5V  No
Send to LCD 0 0 0 0 1 1 No
Wait more than 4.1ms No
Send to LCD 0 0 0 0 1 1 No
Wait more than 100us No
Send to LCD 0 0 0 0 1 1 No
Send to LCD - Switchet to 4 bits mode 0 0 0 0 1 0 No
Send to LCD First Half (function set) 0 0 0 0 1 0 No
Send to LCD Second Half 0 N F * * Yes
continuous with regular initialization sequence,
two nibbles per byte...

8 Bits initialization sequence


Activity
RS  RW  DB7 DB6 DB5 DB4 DB4 DB4 DB4 DB4  Busy Bit 
readable?
Power On No
Wait more than 15ms 
after Vcc raises to +4.5V 
No
Send to LCD 0 0 0 0 1 1 * * * * No
Wait more than 4.1ms No
Send to LCD 0 0 0 0 1 1 * * * * No
Wait more than 100us No
Send to LCD 0 0 0 0 1 1 * * * * No
Send to LCD (function set) 0 0 0 0 1 1 N F * * Yes
continuous with regular 
initialization sequence...

Programming TIPS

Whenever you can't see text on LCD, suspect broken cable, bad soldering, contrast voltage adjustment (VD pin 2), or bad programming.  Powering up the LCD only with +Vcc, Ground and VD (pin 2) is enough to see the line #1 and #3 (if available) with large black rectangles (all dots black), as the unit powers up with memory full of hex 0FFh and in single line programmed. The contrast adjustment for that particular display differs from the regular operation adjustment.

If you see something wrong appearing sometimes at the LCD, suspect timming problems. Even looking at the busy flag bit before send another command or data, it is possible that your program is doing it too fast, so include one or two NOPS before the instructions that appear as to causing the problem can solve it.

Save time when writing to the LCD.  If for example you have a display with fixed text and only some part changes, you can make your program to write it all at start, and then only updates what changes.  Imagine for example if your program shows:
 

T I M E   I S   0 5 : 3 4 : 1 7
8
0
8
1
8
2
8
3
8
4
8
5
8
6
8
7
8
8
8
9
8
A
8
B
8
C
8
D
8
E
8
F
LCD DD Ram Addresses

So, changing seconds will change only bytes at address 08Eh and 08Fh, minutes at 08Bh, 08Ch, hours at 088h and 089h.
You don't need to rewrite all the text always, just what changed.  By this way you save processing and data flow time. The LCD waste some time to do internal functions, and your 8051/AVR will be waiting for the "busy" bit to return to send another byte.

Imagine you have a seconds counter located at internal 8051 ram address 015h and 016h, so, address 016h will be changing once a second, while 015h changes only every 10 seconds, and it will change whenever address 016h contains zero.

A possible assembler routine to update only seconds would be:

Update Seconds Routine

UpdateSeconds:        ;
      Mov   A,016h    ; Get seconds units to check if it is zero
      Jnz   US10      ; If seconds units is not zero, don't update seconds tens.
      Mov   A,#08Eh   ; It was zero, so need to update seconds tens.
      Call  CmdWrite  ; Position Cursor at Seconds Tens
      Mov   A,015h    ; Get seconds tens to display
      Call  ShowHex   ; Convert hex to ascii and display
US10:                 ;
      Mov   A,#08Fh   ; Position Cursor at Seconds Units
      Call  CmdWrite  ;
      Mov   A,016h    ; Get seconds units to display
      Call  ShowHex   ; Convert hex to ascii and display
      Ret             ;
                      ;
ShowHex:              ;
      Call  X2A       ; Convert single nibble to ascii
      Call  DataWrite ; Display data at LCD
      Ret             ;
X2A:                  ;
      Anl   A,#00Fh   ; Make sure it is only right nibble
      Inc   A         ; Increment +1 to bypass Ret instruction below
      Movc  A,@A+PC   ; Get ascii byte indexed by A contents
      Ret             ;
      DB    "0123456789ABCDEF"
 
 


Text produced by Wagner Lipnharski - wagner@ustr.net for www.ustr.net
Thank you to Jean-Paul Morizot for code typo corrections in March/2004.
Have fun, and good luck.
Wagner Lipnharski - UST Research Inc - November 1998
Last Revision March 30th, 2004