Eight-bit interface using the busy flag.
If you feel that you want to learn how to implement the use of the busy flag then
you should do this first with an eight-bit interface. This version allows you to
use any available I/O pin for any of the signals. There are now three control
lines so you will need eleven I/O pins.
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-8f.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-8f.asm
; Date: September 9, 2013
;
; Target: ATmega328
; Assembler: Atmel AvrAssembler2 (AVR Studio 6)
; Author: Donald Weiman
;
; Summary: 8-bit data interface, busy flag implemented.
; Any LCD pin can be connected to any available I/O port.
; Includes a simple write string routine.
;
; ****************************** Program Notes ******************************
;
; This program uses an 8-bit data interface and it uses the busy
; flag to determine when the LCD controller is ready. The LCD
; RW line (pin 5) must therefore be connected to the uP.
;
; The use of the busy flag does not mean that all of the software
; time delays have been eliminated. There are still several
; required in the LCD initialization routine where the busy flag
; cannot yet be used. These delays are have been implemented at
; least twice as long as called for in the data sheet to
; accommodate some of the out of spec displays that may show up.
; There are also some other software time delays required to
; implement timing requirements such as setup and hold times for
; the various control signals.
;
; ***************************************************************************
;
; The eight data lines as well as the three 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 |
; | PD3|---------------->|D3 |
; | PD2|---------------->|D2 |
; | PD1|---------------->|D1 |
; | PD0|---------------->|D0 |
; | | | |
; | PB1|---------------->|E |
; | PB2|---------------->|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)
.equ lcd_D7_port = PORTD ; lcd D7 connection
.equ lcd_D7_bit = PORTD7
.equ lcd_D7_ddr = DDRD
.equ lcd_D7_pin = PIND ; busy flag
.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_D3_port = PORTD ; lcd D3 connection
.equ lcd_D3_bit = PORTD3
.equ lcd_D3_ddr = DDRD
.equ lcd_D2_port = PORTD ; lcd D2 connection
.equ lcd_D2_bit = PORTD2
.equ lcd_D2_ddr = DDRD
.equ lcd_D1_port = PORTD ; lcd D1 connection
.equ lcd_D1_bit = PORTD1
.equ lcd_D1_ddr = DDRD
.equ lcd_D0_port = PORTD ; lcd D0 connection
.equ lcd_D0_bit = PORTD0
.equ lcd_D0_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
.equ lcd_RW_port = PORTB ; lcd Read Write pin
.equ lcd_RW_bit = PORTB2
.equ lcd_RW_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_FunctionSet8bit = 0b00111000 ; 8-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-8f (asm)",0,0
program_date:
.db "Sep 9, 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 ; 8 data lines - output
sbi lcd_D6_ddr, lcd_D6_bit
sbi lcd_D5_ddr, lcd_D5_bit
sbi lcd_D4_ddr, lcd_D4_bit
sbi lcd_D3_ddr, lcd_D3_bit
sbi lcd_D2_ddr, lcd_D2_bit
sbi lcd_D1_ddr, lcd_D1_bit
sbi lcd_D0_ddr, lcd_D0_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
sbi lcd_RW_ddr, lcd_RW_bit ; RW line - output
; initialize the LCD controller as determined by the equates (LCD instructions)
call lcd_init_8f ; initialize the LCD display for an 8-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_8f
; 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_8f
; endless loop
here:
rjmp here
; ****************************** End of Main Program Code *******************
; ============================== 8-bit LCD Subroutines ======================
; Name: lcd_init_8f
; Purpose: initialize the LCD module for an 8-bit data interface
; Entry: equates (LCD instructions) set up for the desired operation
; Exit: no parameters
; Notes: uses the busy flag instead of time delays when possible
lcd_init_8f:
; Power-up delay
ldi temp, 100 ; initial 40 mSec delay
call delayTx1mS
; Reset the LCD controller.
ldi temp, lcd_FunctionReset ; first part of reset sequence
call lcd_write_instruction_8f
ldi temp, 10 ; 4.1 mS delay (min)
call delayTx1mS
ldi temp, lcd_FunctionReset ; second part of reset sequence
call lcd_write_instruction_8f
ldi temp, 200 ; 100 uS delay (min)
call delayTx1uS
ldi temp, lcd_FunctionReset ; third part of reset sequence
call lcd_write_instruction_8f
ldi temp, 200 ; this delay is omitted in the data sheet
call delayTx1uS
; Function Set instruction
ldi temp, lcd_FunctionSet8bit ; set mode, lines, and font
call lcd_write_instruction_8f
; --> from this point on the busy flag is available <--
; 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_check_BF_8 ; make sure LCD controller is ready
call lcd_write_instruction_8f
; Clear Display instruction
ldi temp, lcd_Clear ; clear display RAM
call lcd_check_BF_8 ; make sure LCD controller is ready
call lcd_write_instruction_8f
; Entry Mode Set instruction
ldi temp, lcd_EntryMode ; set desired shift characteristics
call lcd_check_BF_8 ; make sure LCD controller is ready
call lcd_write_instruction_8f
; 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_check_BF_8 ; make sure LCD controller is ready
call lcd_write_instruction_8f
ret
; ---------------------------------------------------------------------------
; Name: lcd_write_string_8f
; 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 the busy flag instead of time delays when possible
lcd_write_string_8f:
; 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 an address instruction
call lcd_check_BF_8 ; make sure LCD controller is ready
call lcd_write_instruction_8f ; set up the first DDRAM address
; write the string of characters
lcd_write_string_8f_01:
lpm temp, Z+ ; get a character
cpi temp, 0 ; check for end of string
breq lcd_write_string_8f_02 ; done
; arrive here if this is a valid character
call lcd_check_BF_8 ; make sure LCD controller is ready
call lcd_write_character_8f ; display the character
rjmp lcd_write_string_8f_01 ; not done, send another character
; arrive here when all characters in the message have been sent to the LCD module
lcd_write_string_8f_02:
pop ZL ; restore pointer registers
pop ZH
ret
; ---------------------------------------------------------------------------
; Name: lcd_write_character_8f
; Purpose: send a byte of information to the LCD data register
; Entry: (temp) contains the data byte
; Exit: no parameters
; Notes: configures RW (busy flag is implemented)
lcd_write_character_8f:
cbi lcd_RW_port, lcd_RW_bit ; write to LCD module (RW low)
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_8 ; write the data
ret
; ---------------------------------------------------------------------------
; Name: lcd_write_instruction_8f
; Purpose: send a byte of information to the LCD instruction register
; Entry: (temp) contains the data byte
; Exit: no parameters
; Notes: configures RW (busy flag is implemented)
lcd_write_instruction_8f:
cbi lcd_RW_port, lcd_RW_bit ; write to LCD module (RW low)
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_8 ; write the instruction
ret
; ---------------------------------------------------------------------------
; Name: lcd_write_8
; Purpose: send a byte of information to the LCD module
; Entry: (temp) contains the data byte
; 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_8:
; set up the data bits
sbi lcd_D7_port, lcd_D7_bit ; assume that the 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'
sbi lcd_D6_port, lcd_D6_bit ; repeat for each data bit
sbrs temp, 6
cbi lcd_D6_port, lcd_D6_bit
sbi lcd_D5_port, lcd_D5_bit
sbrs temp, 5
cbi lcd_D5_port, lcd_D5_bit
sbi lcd_D4_port, lcd_D4_bit
sbrs temp, 4
cbi lcd_D4_port, lcd_D4_bit
sbi lcd_D3_port, lcd_D3_bit
sbrs temp, 3
cbi lcd_D3_port, lcd_D3_bit
sbi lcd_D2_port, lcd_D2_bit
sbrs temp, 2
cbi lcd_D2_port, lcd_D2_bit
sbi lcd_D1_port, lcd_D1_bit
sbrs temp, 1
cbi lcd_D1_port, lcd_D1_bit
sbi lcd_D0_port, lcd_D0_bit
sbrs temp, 0
cbi lcd_D0_port, lcd_D0_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
; ---------------------------------------------------------------------------
; Name: lcd_check_BF_8
; Purpose: check busy flag, wait until LCD is ready
; Entry: no parameters
; Exit: no parameters
; Notes: main program will hang if LCD module is defective or missing
; data is read while 'E' is high
lcd_check_BF_8:
push temp ; preserve register
cbi lcd_D7_ddr, lcd_D7_bit ; set D7 data direction to input
cbi lcd_RS_port, lcd_RS_bit ; select the Instruction Register (RS low)
sbi lcd_RW_port, lcd_RW_bit ; read from LCD module (RW high)
in temp, SREG ; save the status register
push temp
sec ; set up the carry flag to 'mirror' the busy flag status
; get data
lcd_check_BF_8bit_01:
sbi lcd_E_port, lcd_E_bit ; Enable pin high
call delay1uS ; implement 'Delay data time' (160 nS) and 'Enable pulse width' (230 nS)
sbis lcd_D7_pin, lcd_D7_bit ; check busy flag
; arrive here if busy flag is clear (LCD is ready for next instruction)
clc ; clear our busy flag 'mirror' to indicate that we are done
cbi lcd_E_port, lcd_E_bit ; Enable pin low
call delay1uS ; implement 'Address hold time (10 nS), 'Data hold time' (10 nS), and 'Enable cycle time' (500 nS )
; check our busy flag 'mirror'
brcs lcd_check_BF_8bit_01 ; check again if busy flag was high
; arrive here if busy flag is clear - clean up and return
pop temp ; restore status register
out SREG, temp
cbi lcd_RW_port, lcd_RW_bit ; write to LCD module (RW low)
sbi lcd_D7_ddr, lcd_D7_bit ; reset D7 data direction to output
pop temp ; restore register
ret
; ============================== End of 8-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 ==============