;=============== LCD HD44780 driver ===========================
;By Simon van Veen
;Last update: 05 jul 2007
;I advise you to read the readme file for instructions

.include "m32def.inc"

;--------------- Define LCD parameters ------------------------
.equ LCDPORT  	= PORTD			; Change to port you're using
.equ LCDDDR   	= DDRD			; Use PIN[4-7] as data pins
.equ LCDPIN   	= PIND			; Connect these to pins DB[4-7] of LCD
.equ RSPIN    	= 1				; RS pin on LCDPORT
.equ RWPIN    	= 2				; RW pin on LCDPORT
.equ EPIN     	= 3				; E pin on LCDPORT
.set lcdn       = 1				; Number of lines;  n=1: 2 lines,  n=0: 1 line
.set lcdch	    = 16			; Number of characters per row
.set lcdavrspd  = 3			; AVR speed; mess around with this value
								;  untill the LCD starts nicely
								; The higher your AVR speed, the higher this value
.set lcdc		= 0				; Cursor on/off at startup;  c=1: on,  c=0: off
.set lcdb		= 0             ; Cursor blink on/off at startup;  b=1: on,  b=0: off

;--- LCD controll, Nothing needs changing beyond this line ----
LCDLOAD:	; - Do not execute file when it is only included
	rjmp   LCDEND

LCDINIT:	; - Initialize the LCD to use 4 bit mode
	ser    r16
	out    LCDDDR, r16			; AVRPort as output
	cbi    LCDPORT, RWPIN		; RW set to write
	sbi    LCDPORT, RSPIN       ; RS to RAM mode
	rcall  LCDDELAY				; Wait >15ms untill LCD is turned on
	ldi    r17, 0b00110000		; Function set: 4 bit mode
	rcall  LCDCMD8				; Command LCD in 8 bit mode
	rcall  LCDDELAY				; Wait >4.1ms 
	ldi    r17, 0b00110000		; Function set: 4 bit mode
	rcall  LCDCMD8				; Command LCD in 8 bit mode
	rcall  LCDDELAY				; Wait > 100us
	ldi    r17, 0b00110000		; Function set: 4 bit mode
	rcall  LCDCMD8				; Command LCD in 8 bit mode
	rcall  LCDDELAY				; Wait > 100us
	ldi    r17, 0b00100000		; Function set: 4 bit mode
	rcall  LCDCMD8				; Command LCD in 8 bit mode
	rcall  LCDDELAY				; Wait > 100us

	ldi    r17, (1<<5|lcdn<<3)	; Set 4 bit, lines, chartype
	rcall  LCDCMD
	ldi    r17, (1<<5|lcdn<<3)	; Send again, LCD's really in 4bit mode
	rcall  LCDCMD
	ldi    r17, (1<<3|1<<2|lcdc<<1|lcdb<<0)
	rcall  LCDCMD				; Display on, set cursor / blink
	ldi    r17, 0b0000110		; Entry mode set, cursor direction
	rcall  LCDCMD
	rcall  LCDCLEAR
	rcall  LCDDELAY
	ret

LCDCLEAR:	; - Clears display and sets DDRAM adress to 0
	ldi    r17, 0b00000001
	rcall  LCDCMD				; Clear display command
	ldi    r17, ' '
	rcall  LCDPRTCHAR			; Write adress 0 to get to adress 1
	ret

LCDBACKSPACE: ; - Clears previous cursor position
	rcall  LCDGOL1				; Go to previous position
	ldi    r17, 0b00100000	
	rcall  LCDPRTCHAR			; Write a space there
	rcall  LCDGOL1				; Go to previous position
	ret

LCDCLEARROW: ; - Clear next lcdch chatacters
	ldi    r16, lcdch
	LCDCLEARROWLOOP:
	ldi    r17, 0b00100000
	call   LCDPRTCHAR
	dec    r16
	tst    r16
	breq   LCDCLEARROWEND
	rjmp   LCDCLEARROWLOOP
	LCDCLEARROWEND:
	ret

LCDCMD:		; - 4 bit mode command 
	in     r18, LCDPORT
	cbr    r18, 0b11111110		; Get backlight status
	push   r17					; Backup command data to stack
	cbr    r17, 0b00001111		; Only high nibble is left
	or	   r17, r18				; Set backlight
	out    LCDPORT, r17			; Set data on LCD
	cbi    LCDPORT, RSPIN		; RS to command mode
	sbi    LCDPORT, EPIN		; Rising flank on Enable pin
	nop							; Give E some time to get high
	nop
	nop
	cbi    LCDPORT, EPIN		; Falling flank on Enable pin   	
	pop    r17					; Get command data from stack
	swap   r17					; Swap nibbles
	cbr    r17, 0b00001111		; Only low nibble is left
	or	   r17, r18				; Set backlight
	out    LCDPORT, r17			; Set data on LCD
	cbi    LCDPORT, RSPIN		; RS to command mode
	sbi    LCDPORT, EPIN		; Rising flank on Enable pin
	nop							; Give E some time to get high
	nop
	nop
	cbi    LCDPORT, EPIN		; Falling flank on Enable pin   

	rcall  LCDWAIT
	ret

LCDPRTCHAR:	; - Print a character at current position ;TEMP r16 = r18
	in     r16, LCDPORT
	cbr    r16, 0b11111110		; Get backlight status
	push   r17					; Backup command data to stack
	cbr    r17, 0b00001111		; Only high nibble is left
	or	   r17, r16				; Set backlight
	out    LCDPORT, r17			; Set data on LCD
	sbi    LCDPORT, RSPIN		; RS to RAM mode
	sbi    LCDPORT, EPIN		; Rising flank on Enable pin
	nop							; Give E some time to get high
	nop
	nop
	cbi    LCDPORT, EPIN		; Falling flank on Enable pin   	
	pop    r17					; Get command data from stack
	swap   r17					; Swap nibbles
	cbr    r17, 0b00001111		; Only low nibble is left
	or	   r17, r16				; Set backlight
	out    LCDPORT, r17			; Set data on LCD
	sbi    LCDPORT, RSPIN		; RS to RAM mode
	sbi    LCDPORT, EPIN		; Rising flank on Enable pin
	nop							; Give E some time to get high
	nop
	nop
	cbi    LCDPORT, EPIN		; Falling flank on Enable pin   
	rcall  LCDWAIT
	rcall  LCDDELAY	;TEMP NOTE: must be lcddelay2??
	ret

LCDPRTBIN:  ; - Print r17 in binary, uses r3,r18!
	mov    r3, r17				; Backup r17
	ldi    r18, 0b10000000		; Counter, First bit to test is 7
 LCDPRTBINLOOP:
	cp     r3, r18				; Compare same or higher?
	brsh   LCDPRTBIN1			; Bit is 1
	rjmp   LCDPRTBIN0			; Bit is 0
 LCDPRTBIN1:
	ldi    r17, '1'				; Load acsii 1
	rcall  LCDPRTCHAR			; Print char
	sub    r3, r18				; Substract from r18 for next compare
	lsr    r18					; Next compare is next bit
	cpi    r18, 0				; Done?
	brne   LCDPRTBINLOOP		; Not done
	ret							; Done
 LCDPRTBIN0:
	ldi    r17, '0'				; Load acsii 0
	rcall  LCDPRTCHAR			; Print char
	lsr    r18					; Next compare is next bit
	cpi    r18, 0				; Done?
	brne   LCDPRTBINLOOP		; Not done
	ret							; Done


LCDPRTDEC:  ; - Print r17 in decimal form at current position
	mov    r3,  r17				; Backup r17
 LCDPRTDEC100:
 	ldi    r16, 2				; Start with looking for 200
  LCDPRTDEC100L:
  	ldi    r18, 100	
	mul    r18, r16
	mov    r18, r0				; r18 is 100xr16
 	cp     r3,  r18				; Compare
	brsh   LCDPRTDECFOUND100	; Found number
 	dec    r16					; To next number
	cpi    r16, 0				; Out of tries?
	breq   LCDPRTDEC10S			; Yes, Print space
	rjmp   LCDPRTDEC100L		; No, Try again
  LCDPRTDECFOUND100:
 	ldi    r17, '0'				; Load ACSII 0
	add    r17, r16				; Add found number
	rcall  LCDPRTCHAR			; Display
	rjmp   LCDPRTDEC10			; Find next char
 LCDPRTDEC10S:
 	ldi    r17, ' '
	rcall  LCDPRTCHAR			; Print space
 LCDPRTDEC10:
 	mov    r17, r3
  	ldi    r18, 100
	mul    r18, r16
	mov    r18, r0
	sub    r17, r18
	mov    r3,  r17
	ldi    r16, 9
  LCDPRTDEC10L:
	ldi    r18, 10
	mul    r18, r16
	mov    r18, r0
 	cp     r3,  r18
	brsh   LCDPRTDECFOUND10
	dec    r16
	cpi    r16, 0
	breq   LCDPRTDEC1S
	rjmp   LCDPRTDEC10L
  LCDPRTDECFOUND10:
 	ldi    r17, '0'
	add    r17, r16
	rcall  LCDPRTCHAR
	rjmp   LCDPRTDEC1
 LCDPRTDEC1S:
 	ldi    r17, ' '
	rcall  LCDPRTCHAR			; Print space
 LCDPRTDEC1:
 	mov    r17, r3
  	ldi    r18, 10
	mul    r18, r16
	mov    r18, r0
	sub    r17, r18
	mov    r3,  r17
 	ldi    r16, 9
  LCDPRTDEC1L:
	ldi    r18, 1
	mul    r18, r16
	mov    r18, r0
 	cp     r3,  r18
	brsh   LCDPRTDECFOUND1
	dec    r16
	cpi    r16, 0
	breq   LCDPRTDECFOUND1
	rjmp   LCDPRTDEC1L
  LCDPRTDECFOUND1:
 	ldi    r17, '0'
	add    r17, r16
	rcall  LCDPRTCHAR
  	clr    r3
  	ret

LCDPRTDECNOSPACE:
	mov   r3,  r17			; Backup :)
	ldi   r18, 5 			; Highest number to find is 59
	ldi   r16, 10

	LCDPRTDECNOSPACELOOP10:
		mul   r16, r18
		cp    r17, r0
		brsh  LCDPRTDECNOSPACEFOUND10
		dec   r18
		rjmp  LCDPRTDECNOSPACELOOP10

	LCDPRTDECNOSPACEFOUND10:
		ldi   r17, '0'
		add   r17, r18
		cpi   r17, '0'		
		breq  LCDPRTDECNOSPACESKIP10S
		call  LCDPRTCHAR

	LCDPRTDECNOSPACESKIP10S:	
		mov   r16, r3		; Retrieve backup
		sub   r16, r0
		ldi   r17, '0'
		add   r17, r16
		call  LCDPRTCHAR
	ret

LCDTOROW1:	; - Move cursor to first row
	ldi    r17, 0b10000000		; Adress to go to
	call   LCDCMD				; Cursor to adress 
	ret
LCDTOROW2:	; - Move cursor to second row
	ldi    r17, 0b11000000		; Adress to go to
	call   LCDCMD				; Cursor to adress 
	ret
LCDTOROW3:	; - Move cursor to third row
	ldi    r17, 0b10000000		; Adress to go to
	ori    r17, lcdch			; Add n.o. chars to addres
								; Because row3 is part of line1
	call   LCDCMD				; Cursor to adress 
	ret
LCDTOROW4:	; - Move cursor to fourth row
	ldi    r17, 0b11000000		; Adress to go to
	ori    r17, lcdch			; Add n.o. chars to addres
								; Because row4 is part of line2
	call   LCDCMD				; Cursor to adress 
	ret

; Shift cursor right
LCDGOR20: rcall LCDGOR
LCDGOR19: rcall LCDGOR
LCDGOR18: rcall LCDGOR
LCDGOR17: rcall LCDGOR
LCDGOR16: rcall LCDGOR
LCDGOR15: rcall LCDGOR
LCDGOR14: rcall LCDGOR
LCDGOR13: rcall LCDGOR
LCDGOR12: rcall LCDGOR
LCDGOR11: rcall LCDGOR
LCDGOR10: rcall LCDGOR
LCDGOR9:  rcall LCDGOR
LCDGOR8:  rcall LCDGOR
LCDGOR7:  rcall LCDGOR
LCDGOR6:  rcall LCDGOR
LCDGOR5:  rcall LCDGOR
LCDGOR4:  rcall LCDGOR
LCDGOR3:  rcall LCDGOR
LCDGOR2:  rcall LCDGOR
LCDGOR1:
LCDGOR:		; - Shift cursor right X times
	ldi    r17,0b00010100		; Shift cursor right
	rcall  LCDCMD				; Send command
	ret

; Shift cursor left
LCDGOL20: rcall LCDGOL
LCDGOL19: rcall LCDGOL
LCDGOL18: rcall LCDGOL
LCDGOL17: rcall LCDGOL
LCDGOL16: rcall LCDGOL
LCDGOL15: rcall LCDGOL
LCDGOL14: rcall LCDGOL
LCDGOL13: rcall LCDGOL
LCDGOL12: rcall LCDGOL
LCDGOL11: rcall LCDGOL
LCDGOL10: rcall LCDGOL
LCDGOL9:  rcall LCDGOL
LCDGOL8:  rcall LCDGOL
LCDGOL7:  rcall LCDGOL
LCDGOL6:  rcall LCDGOL
LCDGOL5:  rcall LCDGOL
LCDGOL4:  rcall LCDGOL
LCDGOL3:  rcall LCDGOL
LCDGOL2:  rcall LCDGOL
LCDGOL1:
LCDGOL:		; - Move cursor to left X times
	ldi    r17,0b00010000		; Shift cursor left
	rcall  LCDCMD				; Send command
	ret

; ------ UNDER CONSTRUCTION ---
LCDTOCOL1:	; - Move cursor to column
	ldi    r17,1				; Column to move to
	rjmp   LCDTOCOL				; Go move
LCDTOCOL2:	; - Move cursor to column
	ldi    r17,2				; Column to move to
	rjmp   LCDTOCOL				; Go move
LCDTOCOL3:	; - Move cursor to column
	ldi    r17,3				; Column to move to
	rjmp   LCDTOCOL				; Go move
LCDTOCOL4:	; - Move cursor to column
	ldi    r17,4				; Column to move to
	rjmp   LCDTOCOL				; Go move
LCDTOCOL12:	; - Move cursor to column
	ldi    r17,12				; Column to move to
	rjmp   LCDTOCOL				; Go move
LCDTOCOL:	; - Move cursor to column
	rcall  LCDDDRAMADD
	ret

LCDDDRAMADD: ; - Get current DDRAM address | UNDER CONSTRUCTION
	;First we need to know the current column position
	cbi    LCDPORT, RSPIN		; RS to command mode
	sbi    LCDPORT, RWPIN		; RW set to read
	sbi    LCDPORT, EPIN		; Rising flank on Enable pin
	ldi    r16, 0b00001111		; r16 holds no data anymore, so can be used
	out    LCDDDR, r16			; AVRPort 4-7 as input
	nop							; Wait for DDR change
	in     r16, LCDPIN			; Read data from LCD
	cbi    LCDPORT, EPIN		; Falling flank on Enable pin
	nop
	nop
	nop
	sbrc   r16, 7				; Skip if busyflag cleared
	rjmp   LCDWAIT				; LCD still busy, wait again

	cbr    r16, 0b10000000		; Don't need busy flag anymore
	cp     r16, r17
	brne   LCDDDRAMADD

	ser    r16
	out    LCDDDR, r16			; AVRPort as output
	cbi    LCDPORT, RWPIN		; RW set to write
	sbi    LCDPORT, RSPIN		; RS to RAM mode


	ret

LCDCURSORON: ; - Display cursor without blink
	ldi    r17, (1<<3|1<<2|1<<1|0<<0)
	rcall  LCDCMD				; Display on, set cursor / blink
	ret

LCDBLINKON:  ; - Display cursor with blink
	ldi    r17, (1<<3|1<<2|1<<1|1<<0)
	rcall  LCDCMD				; Display on, set cursor / blink
	ret

LCDBLINKOFF:
LCDCURSOROFF: ; Hide cursor
	ldi    r17, (1<<3|1<<2|0<<1|0<<0)
	rcall  LCDCMD				; Display on, set cursor / blink

LCDBACKLIGHTOFF:
	cbi    LCDPORT, 0
	ret

LCDBACKLIGHTON:
	sbi    LCDPORT, 0
	ret

LCDCMD8:	; - 8 bit mode command, used to initialize 4 bit mode
	out    LCDPORT, r17			; Put data to LCD
	cbi    LCDPORT, RSPIN		; RS to command mode
	sbi    LCDPORT, EPIN		; Rising flank on Enable pin
	nop							; Give E some time to get high
	nop
	nop
	cbi    LCDPORT, EPIN		; Falling flank on Enable pin
	ret	

LCDWAIT:	; - Wait for the LCD busyflag to be cleared
	cbi    LCDPORT, RSPIN		; RS to command mode
	sbi    LCDPORT, RWPIN		; RW set to read
	sbi    LCDPORT, EPIN		; Rising flank on Enable pin
	ldi    r17, 0b00001111		; r17 holds no data anymore, so can be used
	out    LCDDDR, r17			; AVRPort 4-7 as input
	nop							; Wait for DDR change
	in     r17, LCDPIN			; Read data from LCD
	cbi    LCDPORT, EPIN		; Falling flank on Enable pin
	nop
	nop
	nop
	sbrc   r17, 7				; Skip if busyflag cleared
	rjmp   LCDWAIT				; LCD still busy, wait again
	ser    r17
	out    LCDDDR, r17			; AVRPort as output
	cbi    LCDPORT, RWPIN		; RW set to write
	sbi    LCDPORT, RSPIN		; RS to RAM mode
	rcall  LCDDELAY
	ret


LCDDELAY:	; - Wait when we can't read busyflag yet
	clr    r16					; r16 is 0
	LCDDELAYLOOP:
	ldi    r17, lcdavrspd		; r17 is lcdwait
	LCDDELAYLOOP2:		
	dec    r17					; r17 = r17 -1
	tst    r17
	brne   LCDDELAYLOOP2		; r17 is 0?
	dec    r16 					; r16 = r16 -1 
	tst    r16
	brne   LCDDELAYLOOP			; r16 is 0 again?
	ret

LCDDELAY2:	; - Temp wait debut loop
	clr    r19					; r16 is 0
	LCDDELAYLOOPb:
	ser    r20				; r17 is lcdwait
	LCDDELAYLOOP2b:		
	dec    r20					; r17 = r17 -1
	tst    r20
	brne   LCDDELAYLOOP2b		; r17 is 0?
	dec    r19 					; r16 = r16 -1 
	tst    r19
	brne   LCDDELAYLOOPb			; r16 is 0 again?
	ret

;----- Code involving CGRAM -----------------
LCDTOCUSTOMCHAR: ; - Selects CGRAM addres
	ldi    r16, 0b01000000 		; Lowest CGRAM addres
	subi   r17, 1				; Char 1 is at addres 0
	ldi    r18, 8
	mul    r17, r18
	mov    r17, r0
	add    r17, r16
	rcall  LCDCMD	
	ret
LCDEND:
