; *************************************************************************** ; 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 . ; ; *************************************************************************** ; 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 ==============