;////////////////////////////////////////////////////////////////////////
;/
;/ LCD controlling code
;/
;/ lcd_control		- write to both display control chips
;/ lcd_set_column	- set the start address for the display
;/ lcd_set_cursor	- position the text cursor
;/ lcd_write 		- write to the display based on 'column'
;/ lcd_init			- reset the display and clear it
;/ lcd_cls			- clear the display
;/ lcd_scroll		- move the cursor to the next line
;/ lcd_char			- write a character to the display
;/ lcd_string		- write a zero-terminated string to the display
;/
;///////////////////////////////////////////////////////////////////////


;this version is for the built-in elcd backlight driver
;pinout for the display:
;
;1		Vss (ground)
;2		Vdd (+5v logic)
;3-10	D0-7
;11		R/~W
;12		E1 (enable for ic1)
;13		A0
;14		E2 (enable for ic2)
;15		ELCNT (high to enable backlight)
;16		EL+ (backlight power 5v)

	.def    arg0 =		r16     ;general registers when not used by mult/div
	.def	arg1 = 		r17		;
	.def    arg2 =		r18     ;
	.def    arg3 =		r19     ;

;***************************************************************************
;* Port Pin Assignments
;***************************************************************************

; pc0-7		d0-7
; pa7		elcnt	(backlight control)	
; pa6		e2
; pa5		a0
; pa4		e1
; pa3		r/~w

	.equ		rw = pa3
	.equ		e1 = pa4
	.equ		a0 = pa5
	.equ		e2 = pa6
	.equ		elcnt = pa7


;/ lcd_control		- write commands to both LCD controllers
;/					- generally, we want both controllers in the same mode
;/					- arg0 destroyed

lcd_control:

	push 	arg1
	
	ldi		arg1,0xff
	out		ddrc,arg1		;port c all op

	ldi		arg1,1<<elcnt
	out		porta,arg1		;set e1, e2, rw, a0 low

	sbi		porta,e1
	sbi		porta,e2		;raise e1, e2
	
	out		portc,arg0		;write the data
	nop
	nop						;hang around for 250ns or so
	out		porta,arg1		;and let the stuff get latched

	pop		arg1
	ret	

;////////////////////////////////////////////////////////////////////////
; some ram definitions
;////////////////////////////////////////////////////////////////////////
	.dseg

column:		.byte	1		;which column are we writing?
row:		.byte	1		;and the row
cursorx:	.byte	1		;text cursor position			
cursory:	.byte	1

	.cseg

; lcd_set_column	- set the address counter in both controller chips
;					- arg0 = column address (0-119)
;					- arg1 = page address (0-3)
;					- also sets 'column' to match
;					- does not set cursorx or cursory; see lcd_set_cursor

lcd_set_column:

	sts		column,arg0		;set the column count
	cpi		arg0,60			;is it over 59
	brlt	lcd_sc01		;nope, skip
	subi	arg0,60			;else subtract sixty
lcd_sc01:
	rcall	lcd_control		;send the set column instruction
	mov		arg0,arg1		;get the page address back
	andi	arg0,3
	ori		arg0,0xb8		;include the control code
	rcall	lcd_control		;and select it
	ret


;/ lcd_set_cursor	- position the text cursor
;					- x pos in arg0, y pos in arg1
;					- the screen displays 20*4 characters
;					- also sets column and presets the controllers
;					- since 2313 doesn't have 'mul' we do a double
;					- shift and add to do it
lcd_set_cursor:

	sts		cursorx,arg0
	sts		cursory,arg1
	mov		arg1,arg0
	lsl		arg0
	add		arg0,arg1		
	lsl		arg0			;multiply by six the quick way
	lds		arg1,cursory
	rcall	lcd_set_column	;which puts the pointers in the right place	
	ret						;short but sweet :)



;/ lcd_write		- writes arg0 to the currently selected column of the display
;					- decides which display to write to by looking at 'column' but
;					- does not preset the chips' internal column counters - these
;					- are set by the routine lcd_set_column before writing starts.
;					- the chips update their internal column counter after each
;					- write so sequential writes will be to sequential columns but
;					- this routine does not adjust 'column'

lcd_write:

	push 	arg0
	ldi		arg0,0xff
	out		ddrc,arg0		;set the pins for output

	lds		arg0,column
	cpi		arg0,60			;column sixty?
	brne	lcd_w01

	clr		arg0
	rcall	lcd_control		;if it is, select column 0

lcd_w01:

	clr		arg0
	sbr		arg0,1<<elcnt
	sbr		arg0,1<<a0
	out		porta,arg0		;set e1, e2, rw low, a0 high

	lds		arg0,column
	cpi		arg0,60			
	brge	lcd_w02
	
	;if it's less that sixty we're in controller 1
	sbi		porta,e1		;raise e1
	rjmp	lcd_w03

lcd_w02:
	sbi		porta,e2		;else raise e2

lcd_w03:
	pop		arg0
	out		portc,arg0		;set the data
	nop
	nop						;hang around for 250ns or so
	cbi		porta,e1		;and let the stuff get latched
	cbi		porta,e2
	ret	




;/ lcd_init			- reset the display and clear it
;					- we have to do this for both controllers
;					- destroys arg0 and arg1

lcd_init:

	clr		arg0
	clr		arg1
	rcall	lcd_set_cursor	;set cursor to top left of window

	ldi		arg0,0xae		;display off
	rcall	lcd_control

	ldi		arg0,100
	rcall	waitms			;pause a tenth of a second

	ldi		arg0,0xc0		;start line = com 0
	rcall	lcd_control

	ldi		arg0,0xa4		;static driving off
	rcall	lcd_control

	ldi		arg0,0x00		;column = 0
	rcall	lcd_control

	ldi		arg0,0xb8		;page = 0
	rcall	lcd_control

	ldi		arg0,0xa9		;1/32 duty
	rcall	lcd_control

	ldi		arg0,0xa0		;output bits forwards
	rcall	lcd_control

	ldi		arg0,0xee		;make sure we're not in read-modify-write
	rcall	lcd_control

	ldi		arg0,0xf8		;we use the top five bits of port a
	out		ddra,arg0
	sbi		porta,elcnt		;turn the light on

	; and all that's left is to clear the screen
	rcall	lcd_cls

	ldi		arg0,0xaf
	rcall	lcd_control

	ret


;/ lcd_cls			- clear the display
;					- zero all the bytes in each half of the display
;					- destroys arg0, arg1

lcd_cls:

	clr		arg1

lcd_c01:
	push	arg1
	clr		arg0
	rcall	lcd_set_column		;start at the start of each line
	clr		arg0

lcd_c02:
	sts		column,arg0
	clr		arg0
	rcall	lcd_write			;write zeros

	lds		arg0,column
	inc		arg0
	cpi		arg0,120
	brne	lcd_c02				;for 120 positions

	pop		arg1
	inc		arg1
	cpi		arg1,4
	brne	lcd_c01

	clr		arg0
	clr		arg1
	rcall	lcd_set_column
	clr		arg0
	clr		arg1
	rcall	lcd_set_cursor

	ret						;and home

	

;/ lcd_scroll		- move the cursor to the start of the next line
;					- if the cursor is already on the bottom line,
;					- move the text up on the whole screen

lcd_scroll:
	
	clr		arg0
	sts		cursorx,arg0		;zero the x cursor

	lds		arg1,cursory
	cpi		arg1,3				;are we on the bottom line yet?
	brne	lcd_s01				;no...
	ldi		arg1,-1				;else preset row to -1

lcd_s01:
	inc		arg1
	sts		cursory,arg1
	rcall	lcd_set_cursor
	ret							;otherwise we return



	
;/ lcd_char			- write a character to the display
;					- the character in arg0 is written at the current cursor position
;					- the cursor position is updated
;					- if the cursorx becomes greater than 20 then the display is scrolled
;					- if the character cr is received the display is scrolled to the
;					- start of the next line
;					- if the character lf is received the cursor is set to the start
;					- of the current line

lcd_char:

	cpi		arg0,cr				;is it cr?
	brne	lcd_ch01			;nope, skip
	rcall	lcd_scroll			;else scroll
	ret							;and return

lcd_ch01:
	cpi		arg0,lf				;line feed perhaps?
	brne	lcd_ch02			;nah...
	clr		arg0
	lds		arg1,cursory
	rcall	lcd_set_cursor		;go back to start of line
	ret							;and return

lcd_ch02:
	;now the serious stuff
	;we write a character by selecting a row and column location based on 
	;cursorx and cursory, and then copying the five bytes that define it
	;to the screen
	
	push	zh
	push	zl					;we don't want to trash z
	
	push	arg0				;save the character 
	
	lds		arg0,cursorx
	lds		arg1,cursory
	rcall	lcd_set_cursor		;we set the cursor to where we are
								;which sets the screen addresses up
	;now we need a pointer into our font image

	pop		arg0				;get the character back
	subi	arg0,0x20			;our code starts with the space char
	clr		arg1
	mov		arg2,arg0
	lsl		arg0
	rol		arg1
	lsl		arg0
	rol		arg1
	add		arg0,arg2			;multiply arg0 by five
	clr		arg2
	adc		arg1,arg2

	ldi		zh,high(characters<<1)
	ldi		zl,low(characters<<1)
	add		zl,arg0
	adc		zh,arg1				;this should be pointing at the first byte of char image
								;so we grab the bytes and shove'em at the display
	lds		arg3,column
	ldi		arg2,0

lcd_ch03:
	lpm							;grab the byte
	mov		arg0,r0
	rcall	lcd_write			;send to the display
	inc		arg3
	sts		column,arg3			;point to next column
	inc		zl					;and next byte
	brne	lcd_ch04
	inc		zh					;(if we've crossed a boundary)

lcd_ch04:
	inc		arg2
	cpi		arg2,5				;sent five chars yet?
	brne	lcd_ch03			;nope, loop

	clr		arg0
	rcall	lcd_write			;and the final blank space

	;now we just have to update the cursor pointers
	lds		arg0,cursorx
	inc		arg0
	sts		cursorx,arg0
	cpi		arg0,20				;got to the end of the line?
	brlt	lcd_ch05
	rcall	lcd_scroll			;yup, scroll

lcd_ch05:
	pop		zl
	pop		zh					;restore the z pointer
	ret							;home james!

;/ lcd_string		- write a zero-terminated string to the display
;					- pointer to string is in Z
;					- bytes written until the terminating zero

lcd_string:

	lpm							;grab the byte
	tst		r0					;zero?
	breq	lcd_st01			;yup, all done
	mov		arg0,r0
	rcall	lcd_char			;else print it
	inc		zl
	brne	lcd_string
	inc		zh
	rjmp	lcd_string			;and back for more
lcd_st01:
	ret