include "lcd.inc"

;LcdInicializa
;  Right now, the display could be either in 8-bit mode if we 
; just powered up, or it could be in 4-bit mode if we just 
; experienced a reset. So to begin, we have to initialize the 
; display to a known state: the 8-bit mode. This is done by 
; sending the LCD command "Function Set" with the 8-bit mode 
; bit set. We need to do this 3 times!
;
LcdInicializa
	;Initialize the LCD_PORT control and data lines to outputs
	bsf		STATUS, RP0			; Banco 0
	bcf		LCD_E_PORT, LCD_E
	bcf		LCD_RS_PORT, LCD_RS
	bcf		LCD_RW_PORT, LCD_RW
	bcf		STATUS, RP0			; Banco 1
	
	movlw	~LCD_DATA_MASK
	andwf	LCD_DATA_PORT,F		;Clear the control lines
	bsf		STATUS, RP0			;Select Register page 1
	andwf	LCD_DATA_TRIS,F		;Make LCD_PORT IO lines outputs
	bcf		STATUS, RP0			;Select Register page 0

	movlw	3
	movwf	TMP1				;Use TMP1 as a loop counter

	bcf		LCD_E_PORT, LCD_E
	bcf		LCD_RS_PORT, LCD_RS
	bcf		LCD_RW_PORT, LCD_RW

init_8bit:
	call	LcdDelay

	movf	LCD_DATA_PORT,W
	andlw	~LCD_DATA_MASK
	iorlw	(LCD_FUNCTION_SET | LCD_8BIT_INTERFACE) >> LCD_DATA_SHIFT
	movwf	LCD_DATA_PORT			;Put command on the bus
	
	;goto	$+1
	bsf		LCD_E_PORT, LCD_E	;Enable the LCD, i.e. write the command.
	;goto	$+1						;NOP's are only needed for 20Mhz crystal
	;goto	$+1
	bcf		LCD_E_PORT, LCD_E	;Disable the LCD

	decfsz	TMP1, F
	goto	init_8bit

	;We should now have the LCD module in 8-bit mode. Now let's put it in 4-bit mode
	call	LcdDelay

	movf	LCD_DATA_PORT,W
	andlw	~LCD_DATA_MASK
	iorlw	(LCD_FUNCTION_SET | LCD_4BIT_INTERFACE) >> LCD_DATA_SHIFT
	movwf	LCD_DATA_PORT			;Put command on the bus

	;goto	$+1
	bsf		LCD_E_PORT, LCD_E	;Enable the LCD, i.e. write the command.
	;goto	$+1						;NOP's are only needed for 20Mhz crystal
	;goto	$+1
	bcf		LCD_E_PORT, LCD_E	;Disable the LCD
	call	LcdDelay

	;Now we are in 4-bit mode. This means that all reads and writes of bytes have to be done
	;a nibble at a time. But that's all taken care of by the read/write functions.
	;Set up the display to have 2 lines and the small (5x7 dot) font.
	movlw	LCD_FUNCTION_SET | LCD_4BIT_INTERFACE | LCD_2_LINES | LCD_SMALL_FONT
	call	LcdWriteCommand

	;Turn on the display and turn off the cursor. Set the cursor to the non-blink mode
	movlw	LCD_DISPLAY_CONTROL | LCD_DISPLAY_ON | LCD_CURSOR_OFF | LCD_BLINKING_OFF
	call	LcdWriteCommand

	;Clear the display memory. This command also moves the cursor to the home position.
	movlw	LCD_CLEAR_DISPLAY
	call	LcdWriteCommand

	;Set up the cursor mode.
	movlw	LCD_ENTRY_MODE_SET | LCD_INC_CURSOR_POS | LCD_NO_SCROLL
	call	LcdWriteCommand

	;Set the Display Data RAM address to 0
	movlw	LCD_SET_DDRAM
	call	LcdWriteCommand

	call	LcdClearScreen

	return

;*******************************************************************
;LcdDelay
; This routine takes the calculated times that the delay loop needs to
;be executed, based on the LCD_INIT_DELAY EQUate that includes the
;frequency of operation.
;
LcdDelay	movlw	LCD_INIT_DELAY	;
A_DELAY		movwf	TMP1			; Use TMP1 and TMP2
			clrf	TMP2			;
LOOP2		decfsz	TMP2, F			; Delay time = TMP1 * ((3 * 256) + 3) * Tcy
			goto	LOOP2			;            = TMP1 * 154.2 (20Mhz clock)
			decfsz	TMP1, F			;
			goto	LOOP2			;
			return

;*******************************************************************
;LcdToggleE
;  This routine toggles the "E" bit (enable) on the LCD module. The contents
;of W contain the state of the R/W and RS bits along with the data that's
;to be written (that is if data is to be written). The contents of the LCD port
;while E is active are returned in W.
;
LcdToggleE
		bcf		LCD_E_PORT,LCD_E	;Make sure E is low
		movlw	0
LTE1:	;goto	$+1						;Delays needed primarily for 10Mhz and faster clocks
		;goto	$+1
		;goto	$+1
		btfsc	LCD_E_PORT, LCD_E	;E is low the first time through the loop
		goto	LTE2

		bsf		LCD_E_PORT, LCD_E	;Make E high and go through the loop again
		goto	LTE1

LTE2:
		addlw	1
		skpz 
		goto	LTE2

		movf	LCD_DATA_PORT, W		;Read the LCD Data bus
		andlw	LCD_DATA_MASK			;We're only interested in the data lines
		bcf		LCD_E_PORT, LCD_E	;Turn off E
		return


;*******************************************************************
;LcdWriteData - Sends a character to LCD
;  This routine splits the character into the upper and lower
;nibbles and sends them to the LCD, upper nibble first.
;
; Memory used:
;    LCD_CHAR,
; Calls
;    LcdToggleE
;
LcdWriteData
	movwf	LCD_CHAR		;Character to be sent is in W
	call	LcdBusyCheck	;Wait for LCD to be ready

	bsf		LCD_RS_PORT,LCD_RS
	bcf		LCD_RW_PORT,LCD_RW

LCD_WRITE
	if LCD_DATA_MASK == 0x0f
	swapf	LCD_CHAR,F
	endif

	;First, write the upper nibble
	movf	LCD_CHAR, w
	andlw	LCD_DATA_MASK
	movwf	TMP1
	movf	LCD_DATA_PORT,W
	andlw	~LCD_DATA_MASK
	iorwf	TMP1,W
	movwf	LCD_DATA_PORT
	call	LcdToggleE

	;Next, write the lower nibble
	swapf	LCD_CHAR, w
	andlw	LCD_DATA_MASK
	movwf	TMP1
	movf	LCD_DATA_PORT,W
	andlw	~LCD_DATA_MASK
	iorwf	TMP1,W
	movwf	LCD_DATA_PORT
	goto	LcdToggleE

;*******************************************************************
;LcdWriteCommand
;
;  This routine splits the command into the upper and lower
;nibbles and sends them to the LCD, upper nibble first.
;
LcdWriteCommand
	movwf   LCD_CHAR		;Character to be sent is in W
	call    LcdBusyCheck	;Wait for LCD to be ready

	;Both R_W and RS should be low
	bcf     LCD_RS_PORT,LCD_RS
	bcf     LCD_RW_PORT,LCD_RW
	goto    LCD_WRITE

;*******************************************************************
;LcdReadData
;This routine will read 8 bits of data from the LCD. Since we're using
;4-bit mode, two passes have to be made. On the first pass we read the
;upper nibble, and on the second the lower.
;
LcdReadData
	call	LcdBusyCheck

	;For a data read, RS and R/W should be high
	bsf		LCD_RS_PORT,LCD_RS
	bsf		LCD_RW_PORT,LCD_RW
LcdRead
	bsf		STATUS, RP0		;Select Register page 1
	movf	LCD_DATA_TRIS,W	;Get the current setting for the whole register
	iorlw	LCD_DATA_MASK	;Set the TRIS bits- make all of the data
	movwf	LCD_DATA_TRIS	;   lines inputs.
	bcf		STATUS, RP0		;Select Register page 0

	call	LcdToggleE		;Toggle E and read upper nibble
	movwf	TMP2			;Save the upper nibble
	call	LcdToggleE		;Toggle E and read lower nibble
	movwf	TMP1			;Save the lower nibble
	swapf	TMP1, W			;Put the lower nibble of data in lower half of W
	iorwf	TMP2, F			;Combine nibbles

	bsf		STATUS, RP0		;Select Register page 1

	movlw	~LCD_DATA_MASK	;Clear the TRIS bits- make all of the data
	andwf	LCD_DATA_TRIS,F	;   lines outputs.

	bcf		STATUS, RP0		;Select Register page 0
	movf	TMP2,W
	if LCD_DATA_MASK == 0x0f
	swapf	TMP2,W
	endif

	return

;*******************************************************************
;LcdBusyCheck
;This routine checks the busy flag, returns when not busy
;
LcdBusyCheck
	;For a busy check, RS is low and R/W is high
	bcf		LCD_RS_PORT,LCD_RS
	bsf		LCD_RW_PORT,LCD_RW

	call	LcdRead
	andlw	0x80			;Check busy flag, high = busy
	skpnz
	return
	movlw	5
	call	A_DELAY
	goto	LcdBusyCheck	;If busy, check again

;*******************************************************************
;LcdClearScreen
;
LcdClearScreen
		movlw	20-1
		movwf	LCD_BUFFER0
		movlw	LCD_SET_DDRAM | LCD_ROW_0 | LCD_COL_0
lcs1:	call	LcdWriteCommand
lcs2:	movlw	0x20			;ASCII space
		call	LcdWriteData
		decf	LCD_BUFFER0,W
		movwf	LCD_BUFFER0
		btfss	LCD_BUFFER0,6	;See if we generated a borrow
		goto	lcs2

		btfss	LCD_BUFFER0,7	;First time through, borrow should be in 7 too
		return

		movlw	20-1 + 0x80		;Reload count and set bit 7 (borrow will clear it)
		movwf	LCD_BUFFER0
		movlw	LCD_SET_DDRAM | LCD_ROW_1 | LCD_COL_0
		goto	lcs1
