LCD Programming Example using Assembly Language

Four-bit interface using software time delays.

This is the version that you will most likely want to use for most of your applications. It uses only six I/O lines and since you can use any available I/O pin for any of the signals it allows you to avoid using special purpose pins that may be needed needed for other devices.

The source code is here.
A similar program written in 'C' is here.
A similar program written for the Arduino IDE is here.
The list of all LCD programming examples is here.

; *************************************************************************** ; LCD-AVR-4d.asm - Use an HD44780U based LCD with an Atmel ATmega processor ; ; Copyright (C) 2013 Donald Weiman (weimandn@alfredstate.edu) ; ; This program is free software: you can redistribute it and/or modify ; it under the terms of the GNU General Public License as published by ; the Free Software Foundation, either version 3 of the License, or ; (at your option) any later version. ; ; This program is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU General Public License for more details. ; ; You should have received a copy of the GNU General Public License ; along with this program. If not, see <http://www.gnu.org/licenses/>. ; ; *************************************************************************** ; File: LCD-AVR-4d.asm ; Date: September 8, 2013 ; ; Target: ATmega328 ; Assembler: Atmel AvrAssembler2 (AVR Studio 6) ; Author: Donald Weiman ; ; Hardware: Arduino, Boarduino, or equivalent, LCD Module ; ; Summary: 4-bit data interface, busy flag not implemented. ; Any LCD pin can be connected to any available I/O port. ; Includes a simple write string routine. ; ; ****************************** Program Notes ****************************** ; ; This program uses a 4-bit data interface but does not use the ; busy flag to determine when the LCD controller is ready. The ; LCD RW line (pin 5) is not connected to the uP and it must be ; connected to GND for the program to function. ; ; All time delays are longer than those specified in most datasheets ; in order to accommodate slower than normal LCD modules. This ; requirement is well documented but almost always ignored. The ; information is in a note at the bottom of the right hand ; (Execution Time) column of the instruction set. ; ; *************************************************************************** ; ; The four data lines as well as the two control lines may be ; implemented on any available I/O pin of any port. These are ; the connections used for this program: ; ; ----------- ---------- ; | ATmega328 | | LCD | ; | | | | ; | PD7|---------------->|D7 | ; | PD6|---------------->|D6 | ; | PD5|---------------->|D5 | ; | PD4|---------------->|D4 | ; | | |D3 | ; | | |D2 | ; | | |D1 | ; | | |D0 | ; | | | | ; | PB1|---------------->|E | ; | | GND --->|RW | ; | PB0|---------------->|RS | ; ----------- ---------- ; ; *************************************************************************** /* ; Studio 6 handles this by default ;.nolist ;.include "m328def.inc" ;.list */ .equ fclk = 16000000 ; system clock frequency (for delays) ; register usage .def temp = R16 ; temporary storage ; LCD interface (should agree with the diagram above) ; make sure that the LCD RW pin is connected to GND .equ lcd_D7_port = PORTD ; lcd D7 connection .equ lcd_D7_bit = PORTD7 .equ lcd_D7_ddr = DDRD .equ lcd_D6_port = PORTD ; lcd D6 connection .equ lcd_D6_bit = PORTD6 .equ lcd_D6_ddr = DDRD .equ lcd_D5_port = PORTD ; lcd D5 connection .equ lcd_D5_bit = PORTD5 .equ lcd_D5_ddr = DDRD .equ lcd_D4_port = PORTD ; lcd D4 connection .equ lcd_D4_bit = PORTD4 .equ lcd_D4_ddr = DDRD .equ lcd_E_port = PORTB ; lcd Enable pin .equ lcd_E_bit = PORTB1 .equ lcd_E_ddr = DDRB .equ lcd_RS_port = PORTB ; lcd Register Select pin .equ lcd_RS_bit = PORTB0 .equ lcd_RS_ddr = DDRB ; LCD module information .equ lcd_LineOne = 0x00 ; start of line 1 .equ lcd_LineTwo = 0x40 ; start of line 2 ;.equ lcd_LineThree = 0x14 ; start of line 3 (20x4) ;.equ lcd_lineFour = 0x54 ; start of line 4 (20x4) ;.equ lcd_LineThree = 0x10 ; start of line 3 (16x4) ;.equ lcd_lineFour = 0x50 ; start of line 4 (16x4) ; LCD instructions .equ lcd_Clear = 0b00000001 ; replace all characters with ASCII 'space' .equ lcd_Home = 0b00000010 ; return cursor to first position on first line .equ lcd_EntryMode = 0b00000110 ; shift cursor from left to right on read/write .equ lcd_DisplayOff = 0b00001000 ; turn display off .equ lcd_DisplayOn = 0b00001100 ; display on, cursor off, don't blink character .equ lcd_FunctionReset = 0b00110000 ; reset the LCD .equ lcd_FunctionSet4bit = 0b00101000 ; 4-bit data, 2-line display, 5 x 7 font .equ lcd_SetCursor = 0b10000000 ; set cursor position ; ****************************** Reset Vector ******************************* .org 0x0000 jmp start ; jump over Interrupt Vectors, Program ID etc. ;******************************* Program ID ********************************* .org INT_VECTORS_SIZE program_author: .db "Donald Weiman",0 program_version: .db "LCD-AVR-4d (asm)",0,0 program_date: .db "Sep 8, 2013",0 ; ****************************** Main Program Code ************************** start: ; initialize the stack pointer to the highest RAM address ldi temp,low(RAMEND) out SPL,temp ldi temp,high(RAMEND) out SPH,temp ; configure the microprocessor pins for the data lines sbi lcd_D7_ddr, lcd_D7_bit ; 4 data lines - output sbi lcd_D6_ddr, lcd_D6_bit sbi lcd_D5_ddr, lcd_D5_bit sbi lcd_D4_ddr, lcd_D4_bit ; configure the microprocessor pins for the control lines sbi lcd_E_ddr, lcd_E_bit ; E line - output sbi lcd_RS_ddr, lcd_RS_bit ; RS line - output ; initialize the LCD controller as determined by the equates (LCD instructions) call lcd_init_4d ; initialize the LCD display for a 4-bit interface ; display the first line of information ldi ZH, high(program_author) ; point to the information that is to be displayed ldi ZL, low(program_author) ldi temp, lcd_LineOne ; point to where the information should be displayed call lcd_write_string_4d ; display the second line of information ldi ZH, high(program_version) ; point to the information that is to be displayed ldi ZL, low(program_version) ldi temp, lcd_LineTwo ; point to where the information should be displayed call lcd_write_string_4d ; endless loop here: rjmp here ; ****************************** End of Main Program Code ******************* ; ============================== 4-bit LCD Subroutines ====================== ; Name: lcd_init_4d ; Purpose: initialize the LCD module for a 4-bit data interface ; Entry: equates (LCD instructions) set up for the desired operation ; Exit: no parameters ; Notes: uses time delays instead of checking the busy flag lcd_init_4d: ; Power-up delay ldi temp, 100 ; initial 40 mSec delay call delayTx1mS ; IMPORTANT - At this point the LCD module is in the 8-bit mode and it is expecting to receive ; 8 bits of data, one bit on each of its 8 data lines, each time the 'E' line is pulsed. ; ; Since the LCD module is wired for the 4-bit mode, only the upper four data lines are connected to ; the microprocessor and the lower four data lines are typically left open. Therefore, when ; the 'E' line is pulsed, the LCD controller will read whatever data has been set up on the upper ; four data lines and the lower four data lines will be high (due to internal pull-up circuitry). ; ; Fortunately the 'FunctionReset' instruction does not care about what is on the lower four bits so ; this instruction can be sent on just the four available data lines and it will be interpreted ; properly by the LCD controller. The 'lcd_write_4' subroutine will accomplish this if the ; control lines have previously been configured properly. ; Set up the RS and E lines for the 'lcd_write_4' subroutine. cbi lcd_RS_port, lcd_RS_bit ; select the Instruction Register (RS low) cbi lcd_E_port, lcd_E_bit ; make sure E is initially low ; Reset the LCD controller. ldi temp, lcd_FunctionReset ; first part of reset sequence call lcd_write_4 ldi temp, 10 ; 4.1 mS delay (min) call delayTx1mS ldi temp, lcd_FunctionReset ; second part of reset sequence call lcd_write_4 ldi temp, 200 ; 100 uS delay (min) call delayTx1uS ldi temp, lcd_FunctionReset ; third part of reset sequence call lcd_write_4 ldi temp, 200 ; this delay is omitted in the data sheet call delayTx1uS ; Preliminary Function Set instruction - used only to set the 4-bit mode. ; The number of lines or the font cannot be set at this time since the controller is still in the ; 8-bit mode, but the data transfer mode can be changed since this parameter is determined by one ; of the upper four bits of the instruction. ldi temp, lcd_FunctionSet4bit ; set 4-bit mode call lcd_write_4 ldi temp, 80 ; 40 uS delay (min) call delayTx1uS ; Function Set instruction ldi temp, lcd_FunctionSet4bit ; set mode, lines, and font call lcd_write_instruction_4d ldi temp, 80 ; 40 uS delay (min) call delayTx1uS ; The next three instructions are specified in the data sheet as part of the initialization routine, ; so it is a good idea (but probably not necessary) to do them just as specified and then redo them ; later if the application requires a different configuration. ; Display On/Off Control instruction ldi temp, lcd_DisplayOff ; turn display OFF call lcd_write_instruction_4d ldi temp, 80 ; 40 uS delay (min) call delayTx1uS ; Clear Display instruction ldi temp, lcd_Clear ; clear display RAM call lcd_write_instruction_4d ldi temp, 4 ; 1.64 mS delay (min) call delayTx1mS ; Entry Mode Set instruction ldi temp, lcd_EntryMode ; set desired shift characteristics call lcd_write_instruction_4d ldi temp, 80 ; 40 uS delay (min) call delayTx1uS ; This is the end of the LCD controller initialization as specified in the data sheet, but the display ; has been left in the OFF condition. This is a good time to turn the display back ON. ; Display On/Off Control instruction ldi temp, lcd_DisplayOn ; turn the display ON call lcd_write_instruction_4d ldi temp, 80 ; 40 uS delay (min) call delayTx1uS ret ; --------------------------------------------------------------------------- ; Name: lcd_write_string_4d ; Purpose: display a string of characters on the LCD ; Entry: ZH and ZL pointing to the start of the string ; (temp) contains the desired DDRAM address at which to start the display ; Exit: no parameters ; Notes: the string must end with a null (0) ; uses time delays instead of checking the busy flag lcd_write_string_4d: ; preserve registers push ZH ; preserve pointer registers push ZL ; fix up the pointers for use with the 'lpm' instruction lsl ZL ; shift the pointer one bit left for the lpm instruction rol ZH ; set up the initial DDRAM address ori temp, lcd_SetCursor ; convert the plain address to a set cursor instruction call lcd_write_instruction_4d ; set up the first DDRAM address ldi temp, 80 ; 40 uS delay (min) call delayTx1uS ; write the string of characters lcd_write_string_4d_01: lpm temp, Z+ ; get a character cpi temp, 0 ; check for end of string breq lcd_write_string_4d_02 ; done ; arrive here if this is a valid character call lcd_write_character_4d ; display the character ldi temp, 80 ; 40 uS delay (min) call delayTx1uS rjmp lcd_write_string_4d_01 ; not done, send another character ; arrive here when all characters in the message have been sent to the LCD module lcd_write_string_4d_02: pop ZL ; restore pointer registers pop ZH ret ; --------------------------------------------------------------------------- ; Name: lcd_write_character_4d ; Purpose: send a byte of information to the LCD data register ; Entry: (temp) contains the data byte ; Exit: no parameters ; Notes: does not deal with RW (busy flag is not implemented) lcd_write_character_4d: sbi lcd_RS_port, lcd_RS_bit ; select the Data Register (RS high) cbi lcd_E_port, lcd_E_bit ; make sure E is initially low call lcd_write_4 ; write the upper 4-bits of the data swap temp ; swap high and low nibbles call lcd_write_4 ; write the lower 4-bits of the data ret ; --------------------------------------------------------------------------- ; Name: lcd_write_instruction_4d ; Purpose: send a byte of information to the LCD instruction register ; Entry: (temp) contains the data byte ; Exit: no parameters ; Notes: does not deal with RW (busy flag is not implemented) lcd_write_instruction_4d: cbi lcd_RS_port, lcd_RS_bit ; select the Instruction Register (RS low) cbi lcd_E_port, lcd_E_bit ; make sure E is initially low call lcd_write_4 ; write the upper 4-bits of the instruction swap temp ; swap high and low nibbles call lcd_write_4 ; write the lower 4-bits of the instruction ret ; --------------------------------------------------------------------------- ; Name: lcd_write_4 ; Purpose: send a nibble (4-bits) of information to the LCD module ; Entry: (temp) contains a byte of data with the desired 4-bits in the upper nibble ; (RS) is configured for the desired LCD register ; (E) is low ; (RW) is low ; Exit: no parameters ; Notes: use either time delays or the busy flag lcd_write_4: ; set up D7 sbi lcd_D7_port, lcd_D7_bit ; assume that the D7 data is '1' sbrs temp, 7 ; check the actual data value cbi lcd_D7_port, lcd_D7_bit ; arrive here only if the data was actually '0' ; set up D6 sbi lcd_D6_port, lcd_D6_bit ; repeat for each data bit sbrs temp, 6 cbi lcd_D6_port, lcd_D6_bit ; set up D5 sbi lcd_D5_port, lcd_D5_bit sbrs temp, 5 cbi lcd_D5_port, lcd_D5_bit ; set up D4 sbi lcd_D4_port, lcd_D4_bit sbrs temp, 4 cbi lcd_D4_port, lcd_D4_bit ; write the data ; 'Address set-up time' (40 nS) sbi lcd_E_port, lcd_E_bit ; Enable pin high call delay1uS ; implement 'Data set-up time' (80 nS) and 'Enable pulse width' (230 nS) cbi lcd_E_port, lcd_E_bit ; Enable pin low call delay1uS ; implement 'Data hold time' (10 nS) and 'Enable cycle time' (500 nS) ret ; ============================== End of 4-bit LCD Subroutines =============== ; ============================== Time Delay Subroutines ===================== ; Name: delayYx1mS ; Purpose: provide a delay of (YH:YL) x 1 mS ; Entry: (YH:YL) = delay data ; Exit: no parameters ; Notes: the 16-bit register provides for a delay of up to 65.535 Seconds ; requires delay1mS delayYx1mS: call delay1mS ; delay for 1 mS sbiw YH:YL, 1 ; update the the delay counter brne delayYx1mS ; counter is not zero ; arrive here when delay counter is zero (total delay period is finished) ret ; --------------------------------------------------------------------------- ; Name: delayTx1mS ; Purpose: provide a delay of (temp) x 1 mS ; Entry: (temp) = delay data ; Exit: no parameters ; Notes: the 8-bit register provides for a delay of up to 255 mS ; requires delay1mS delayTx1mS: call delay1mS ; delay for 1 mS dec temp ; update the delay counter brne delayTx1mS ; counter is not zero ; arrive here when delay counter is zero (total delay period is finished) ret ; --------------------------------------------------------------------------- ; Name: delay1mS ; Purpose: provide a delay of 1 mS ; Entry: no parameters ; Exit: no parameters ; Notes: chews up fclk/1000 clock cycles (including the 'call') delay1mS: push YL ; [2] preserve registers push YH ; [2] ldi YL, low (((fclk/1000)-18)/4) ; [1] delay counter ldi YH, high(((fclk/1000)-18)/4) ; [1] delay1mS_01: sbiw YH:YL, 1 ; [2] update the the delay counter brne delay1mS_01 ; [2] delay counter is not zero ; arrive here when delay counter is zero pop YH ; [2] restore registers pop YL ; [2] ret ; [4] ; --------------------------------------------------------------------------- ; Name: delayTx1uS ; Purpose: provide a delay of (temp) x 1 uS with a 16 MHz clock frequency ; Entry: (temp) = delay data ; Exit: no parameters ; Notes: the 8-bit register provides for a delay of up to 255 uS ; requires delay1uS delayTx1uS: call delay1uS ; delay for 1 uS dec temp ; decrement the delay counter brne delayTx1uS ; counter is not zero ; arrive here when delay counter is zero (total delay period is finished) ret ; --------------------------------------------------------------------------- ; Name: delay1uS ; Purpose: provide a delay of 1 uS with a 16 MHz clock frequency ; Entry: no parameters ; Exit: no parameters ; Notes: add another push/pop for 20 MHz clock frequency delay1uS: push temp ; [2] these instructions do nothing except consume clock cycles pop temp ; [2] push temp ; [2] pop temp ; [2] ret ; [4] ; ============================== End of Time Delay Subroutines ==============