;********************************************************************
;
; Hitachi LCD Display routines
; Karl Grabe, Cork, Ireland
; 09 May 95	Initial
; 10 May 95	8 Bit data port to LCD
; 13 Jun 95	4 bit data path on hi port nybble 		
; 17 Jun 95	4 bit data path on low port nybble
;		Preserve other nybble's Tris and I/O state
; 22 Jun 95	Binary to BCD routines, PrintBCDw, PrintBCDlo
; 19 Jul 95	Remove LatchData to save a subroutine call
;
; Declare the following in main program along with ram:
;		   
;	LCD_DATA    :	Port connected to LCD for 4/8 bit data xfer
;	LCD_TRIS    :	TRIS port associated with LCD_DATA port
;	E, RW, RS   :	Enable, read/write and instruction/data for LCD
;	FourBit	    :	True if using 4 bit data path to lcd (else 8)
;	DataHi	    :	True if using hi nybble of port, else lo nybble
;	LineEnd	    : 	Number of characters in line 1 LCD ram
;			(jump to line two when cursor passes LineEnd)
;	LineLenght  :	Number of physical characters on a display line
;
; 	RAM:
;	Cursor		; LCD Cursor Posn
;	TablePtr	; Table message pointer
;	CHAR		; Save char/inst to print	
;	CHAR2		; Save char to print
;	MsgLen		; Lenght of a table message
;	WhichMsg	; Save message pointer (CenMsg)
;	LCD_T2		; Temp 4 bit xfer to port: Wh2LCDl
;	LCD_T		; Reading busy flag: Busy_Q					

;********************************************************************

INCLUDE LCD.inc				; Hitachi LCD command codes
	page
;********************************************************************
;
; 'High' level LCD routines
;
;********************************************************************

; Clear LCD and return cursor to position 0 on line 1
ClearHome
	CallW	LCD_CMD, CLR_HOME	; Clear and home
	clrf	Cursor			; Set our cursor to 0
	return


; Print a message in Table pointed to by w 
DispMsg 
	movwf	TablePtr		; TablePtr holds start of message address
	call    Table
	andlw   h'FF'			; Check if at end of message (zero
	btfsc   STATUS,Z		; returned at end)
	goto    out_msg             
	call    PrintW			; Display character
	movf    TablePtr,w		; Point to next character
	addlw   1
	goto    DispMsg
out_msg
	return

; Get message length of message in Table to by w 
; Lenght returned in w
MesLen	clrf	MsgLen
NextChar	
	movwf	TablePtr		; TablePtr holds start of message address
	call    Table
	andlw   h'FF'			; Check if at end of message (zero
	btfsc   STATUS,Z		; returned at end)
	goto    out_len             
	incf	MsgLen, f
	movf    TablePtr,w		; Point to next character
	addlw   1
	goto    NextChar
out_len
	movfw	MsgLen
	RETURN

; Display a message centered on the lcd
; W is a pointer to the message in table
CenMsg
	movwf	WhichMsg		; Save the message pointer
	call	ClearHome
	movfw	WhichMsg		; Get message pointer
	Call 	MesLen			; Get the message lenght
	sublw	LineLenght		; Find display blank spaces
	movwf	MsgLen
	clrc				; (prevent problems with rrf)
	rrf	MsgLen, w		; Devide by 2
	Call	PosCurs			; Position cursor
	movfw	WhichMsg		; Get message pointer
	call	DispMsg			; Print message in mid screen
	return

; Position cursor at position W (0...)
; If cursor > LineEnd then move to correct position on line 2
PosCurs
	movwf	CHAR			; Save new cursor position
	movwf	Cursor			; Used by Print to switch lines
	sublw	LineEnd-1		; Carry clear for w > LineEnd
	skpnc
	goto	NoMov			; On line 1 
	movlw	LineEnd			; Move to start of 2nd line...
	subwf	CHAR, w			; .. by subtracting LineEnd
	addlw	LINE_2			; Add line 2 offset for command
	movwf	CHAR
NoMov	movfw	CHAR
	addlw	DD_POS_MASK		; Mask with DD address command
	call	LCD_CMD			; Postion cursor
	return

; Shift display one character to the right
; Note: Shifts both lines in a two line display...
; Some one line dislays are logically 2 lines so the shift
; won't be as expected.
ShiftRight
	CallW	LCD_CMD, SHIFT_MASK | SHIFT_DISP | SHIFT_RIGHT
	return
	
; Shif display one character to the left
ShiftLeft
	CallW	LCD_CMD, SHIFT_MASK | SHIFT_DISP | SHIFT_LEFT
	return
	PAGE
;********************************************************************
;
; Integer Printing routines for LCD display
;
;********************************************************************


;********************************************************************
; PrintIntW
; Print Integer pointed to by W on LCD as 5 digits.
;
; Input:
;	W: Pointer to lower (least significant) byte of integer
;
; Uses:
;	FSR, L_byte, H_byte
; Calls:
; 	B2_BCD, PrintBCDw, PrintBCDlo
;********************************************************************
; 
PrintIntW
	movwf	FSR
	movfw	INDF
        movwf   L_byte			; Least sig byte of integer
        incf	FSR, f
	movfw	INDF
	movwf	H_byte			; Most sig byte of integer
PrintInt				; Call here with H, L_byte loaded
        call    B2_BCD
        movlw	R0			; Most significant Digits
	movwf	FSR
	call	PrintBCDlo		; No need to print leading digit..
	movlw	R1			; ..it's always zero
	call	PrintBCDw
	movlw	R2			; LSD
	call	PrintBCDw
	return

; Print w as an short integer using 3 lcd digits
PrintWint
	movwf	L_byte			; copy to print register
	clrf	H_byte
        call    B2_BCD
        movlw	R1			; Most significant Digits
	movwf	FSR
	call	PrintBCDlo		; No need to print leading digit..
	movlw	R2			; ..it's always zero
	call	PrintBCDw
	return
	
; Print a 2 byte ingeger in EE ram pointed to by W
PrintEEintW
	movwf	EEADR			; EE address of low byte
	Call	ReadEEw			; Read EE data
	movwf	L_byte			; Save for printing
	incf	EEADR, f		; EE address of hi byte
	Call	ReadEEw			; Read EE data
	movwf	H_byte			; Save for printing
	goto	PrintInt

;********************************************************************
; PrintBCDw
; 	Print BCD byte (2 digits) pointed to by W
; PrintBCDlo
;	Print least significant BCD byte (1 digit)
;
; Input:
;	W: Pointer to byte
; Uses:
;	 FSR
; Calls:
;	PrintW
;********************************************************************
PrintBCDw
	movwf	FSR
	swapf	INDF, w			; Get hi nybble
	andlw	h'0F'
	addlw	'0'			; Number to ACSII char
	call	PrintW
PrintBCDlo				; Just print the lower BCD nybble
        movfw	INDF
	andlw	h'0F'
	addlw	'0'
	call	PrintW
	return
	page
;********************************************************************
; 
; LCD low level communication routines
; Set FourBit for 4 bit data xfer to LCD, else 8 bit.
; Set DataHi for 4 bit xfer on Port hi nybble, else low nybble
;
;********************************************************************

;********************************************************************
; Initialise the LCD Display
;********************************************************************

	if (FourBit)
; Data path 4 bit		
InitLCD
	clrf	Cursor			; Set to start of display
	BCF	LCD_RW			; Write to display
	BCF	LCD_RS			; Display in instrucion mode
	
	CallW	Wait10ms, d'1'		; Wait for LCD to power up
theCmd	set	FUNC_MASK | DL4 | L2 | F5x7 
	movlw	theCmd
	if (DataHi)
	 call	Wh2LCDh			; mov W hi to Port hi nybble
	else
	 call	Wh2LCDl			; mov W hi to Port low nybble
	endif
; Latch data out to display
	bsf	LCD_E			; Validate data on LCD bus
	IF	F_OSC > 5000000 	; 10 Mhz clock = 400ns instruction cycle
	NOP
	ENDIF
	BCF	LCD_E			; LCD has data, E off

	CallW	Wait10ms, d'1'		; Can't use Busy_Q yet

; Repeat data with LCD_CMD, this time use 2 data xfers and Busy_Q works now
	CallW	LCD_CMD, theCmd

; Display on, cursor	
theCmd	set	DISP_MASK | DISP_ON | CURS_OFF | BLINK_OFF
	CallW	LCD_CMD, theCmd

; Cursor direction, shift on or off
theCmd	set	ENTRY_MASK | INC_CURS | SHIFT_OFF	; Entry mode
	CallW	LCD_CMD, theCmd	

	Return

	
	else
	
; Data Path is 8 bit
InitLCD
	clrf	Cursor			; Set to start of display
	BCF	LCD_RW			; Write to display
	BCF	LCD_RS			; Display in instrucion mode
	
	CallW	Wait10ms, d'1'		; Wait for LCD to power up
; Data width
;	CallW	LCD_CMD, FUNC_MASK | DL8 | L2 | F5x7 ; 4/8 bit, lines, font
	movlw	FUNC_MASK | DL8 | L2 | F5x7
	movwf	LCD_DATA
; Latch data out to display
	bsf	LCD_E			; Validate data on LCD bus
	IF	F_OSC > 5000000 	; 10 Mhz clock = 400ns instruction cycle
	NOP
	ENDIF
	BCF	LCD_E			; LCD has data, E off

	CallW	Wait10ms, d'1'		; Can't use Busy_Q yet

; Display on, cursor	
	CallW	LCD_CMD, DISP_MASK | DISP_ON | CURS_OFF | BLINK_OFF

; Cursor direction, shift on or off
	CallW	LCD_CMD, ENTRY_MASK | INC_CURS | SHIFT_OFF	; Entry mode
	clrf	Cursor			; Cursor at start of display

	Return
	endif
	page
;********************************************************************
;
; Print a char in W to the LCD display
; Move to line 2 if necessary
;
;********************************************************************

	if (FourBit)
; Use 4 bit High nybble data xfer to LCD
PrintW	
	movwf	CHAR2			; Save the char to print
	MOVFW	Cursor			; Get the cursor
	XORLW	LineEnd			; Check if its at the end of the line
	SKPZ
	goto	Line1		
	CallW	LCD_CMD, DD_POS_MASK | LINE_2 ; Yes, move to line 2
Line1		
	movfw	CHAR2			; Get back char to print
	call	Busy_Q
	BSF	LCD_RS			; Data mode

	if 	(DataHi)
	 call 	Wh2LCDh			; W hi to LCD port Hi
	else
	 call	Wh2LCDl			; W hi to LCD port lo
	endif

; Latch data out to display
	bsf	LCD_E			; Validate data on LCD bus
	IF	F_OSC > 5000000 	; 10 Mhz clock = 400ns instruction cycle
	NOP
	ENDIF
	BCF	LCD_E			; LCD has data, E off

	swapf	CHAR2, W		; Get low nybble

	if 	(DataHi)
	 call 	Wh2LCDh			; W hi to LCD port Hi
	else
	 call	Wh2LCDl			; W hi to LCD port lo
	endif
	
	movwf	LCD_DATA		; Send low nybble
; Latch data out to display
	bsf	LCD_E			; Validate data on LCD bus
	IF	F_OSC > 5000000 	; 10 Mhz clock = 400ns instruction cycle
	NOP
	ENDIF
	BCF	LCD_E			; LCD has data, E off

	incf	Cursor,f		; Move to next LCD posn
	RETURN

	else

; 8 Bit data xfer
PrintW	
	movwf	CHAR2			; Save the char to print
	MOVFW	Cursor			; Get the cursor
	XORLW	LineEnd			; Check if its at the end of the line
	SKPZ
	goto	Line1		
	CallW	LCD_CMD, DD_POS_MASK | LINE_2 ; Yes, move to line 2
Line1		
	movfw	CHAR2			; Get back char to print
	call	Busy_Q
	BSF	LCD_RS			; Data mode
	MOVWF	LCD_DATA
; Latch data out to display
	bsf	LCD_E			; Validate data on LCD bus
	IF	F_OSC > 5000000 	; 10 Mhz clock = 400ns instruction cycle
	NOP
	ENDIF
	BCF	LCD_E			; LCD has data, E off

	incf	Cursor,f		; Move to next LCD posn
	RETURN
	endif
	page
;********************************************************************
;
; Do an LCD command in w
; LCD assumed to be in write mode after Busy_Q
; Leave LCD in Instrucion mode
;
;********************************************************************
	if (FourBit)

; 4 Bit data path, high nybble used
LCD_CMD
	call	Busy_Q			; Saves command in W and CHAR
	BCF	LCD_RS			; Instruction mode

	if 	(DataHi)
	 call 	Wh2LCDh			; W hi to LCD port hi
	else
	 call	Wh2LCDl			; W hi to LCD port lo
	endif
	
; Latch data out to display
	bsf	LCD_E			; Validate data on LCD bus
	IF	F_OSC > 5000000 	; 10 Mhz clock = 400ns instruction cycle
	NOP
	ENDIF
	BCF	LCD_E			; LCD has data, E off

	swapf	CHAR, W			; Get lower nybble

	if 	(DataHi)
	 call 	Wh2LCDh			; W hi to LCD port hi
	else
	 call	Wh2LCDl			; W hi to LCD port lo
	endif 

; Latch data out to display
	bsf	LCD_E			; Validate data on LCD bus
	IF	F_OSC > 5000000 	; 10 Mhz clock = 400ns instruction cycle
	NOP
	ENDIF
	BCF	LCD_E			; LCD has data, E off

	return
	else

; 8 Bit data path
LCD_CMD
	call	Busy_Q
	BCF	LCD_RS			; Instruction mode
	MOVWF	LCD_DATA
; Latch data out to display
	bsf	LCD_E			; Validate data on LCD bus
	IF	F_OSC > 5000000 	; 10 Mhz clock = 400ns instruction cycle
	NOP
	ENDIF
	BCF	LCD_E			; LCD has data, E off

	return
	endif

	if (FourBit)
;********************************************************************
; Move the high nybble of W to the Hi byte of LCD_DATA
; Only Required in 4 bit modes
;********************************************************************
	if (DataHi)
Wh2LCDh
	MovWhFh	LCD_DATA		; Mov W(hi) to LCD_DATA(hi)
	return
	else
	
Wh2LCDl MovpWhFl LCD_DATA, LCD_T2	; Mov W(hi) to LCD_DATA(low)
	return		
	endif
	endif
	page
;**************************************************************
; Checks the busy flag, returns when not busy   
; Uses: W, CHAR, LCD_T
;                                                 
; Outputs:	
; LCD_T - Returned with busy/address
; W is preserved
; W also saved in CHAR                     
;
; Leave LCD in Write mode, and command mode
;
;**************************************************************

	if (FourBit)			; otherwise 8 bit port xfer

	if (DataHi)
; 4 Bit Data path, Xfer on HIGH port nybble
Busy_Q
	movwf	CHAR			; Save the char first

	BSF	STATUS,RP0		; Select Register page 1

; Port tri-State: 0=Output
	movlw	h'f0'
	iorwf	LCD_TRIS, f		; Hi port nybble to inputs
					; ..leave low nybble alone			

	BCF	STATUS, RP0     	; Select Register page 0
	BCF	LCD_RS    		; Set LCD for command mode
	BSF	LCD_RW   		; Read LCD 
	
StillBusy	
	bsf	LCD_E			; LCD puts valid data on bus
	NOP				; E must stay hi for >450ns
	IF	F_OSC > 5000000 	; 10 Mhz clock = 400ns instruction cycle
	NOP
	ENDIF

; First Transfer: LCD hi data into the Hi nybble of W
	MOVFW	LCD_DATA
	BCF	LCD_E			; Tristate LCD again
	andlw	h'F0'			; mask out lower nybble
	MOVWF	LCD_T			; Save LCD data
	
	bsf	LCD_E			; LCD puts valid data on bus
	NOP				; E must stay hi for >450ns
	IF	F_OSC > 5000000 	; 10 Mhz clock = 400ns instruction cycle
	NOP
	ENDIF
	
; Second Transfer: LCD low nybble into low nybble of W
	swapf	LCD_DATA, w		; Put lower nybble in hi w
	BCF	LCD_E			; Tristate LCD again
	andlw	h'0F'
	iorwf	LCD_T, f		; Combine nybbles
		
	BTFSC	LCD_T, 7		; Check busy flag, high=busy
	GOTO	StillBusy		; Wait 'till bit 7 is clear
	BCF	LCD_RW			; Default: write to lcd... 
					; ..Before switching port to o/p 
	BSF	STATUS, RP0     	; Select Register page 1

; Port tri-State: 0=Output
	movlw	h'0F'
	andwf	LCD_TRIS, f		; Hi nubble tris to outpurs

	BCF	STATUS, RP0     	; Select Register page 0
	MOVFW	CHAR			; Restor char to print
	RETURN

	else				; .. if (DataHi)
	
; 4 Bit Data path, Xfer on LOW port nybble
Busy_Q
	movwf	CHAR			; Save the char first

	BSF	STATUS,RP0		; Select Register page 1

; Port tri-State: 0=Output
	movlw	h'0F'			; Low nybble
	iorwf	LCD_TRIS, f		; Lo port nybble to inputs
					; ..leave hi nybble alone			
	BCF	STATUS, RP0     	; Select Register page 0
	BCF	LCD_RS    		; Set LCD for command mode
	BSF	LCD_RW   		; Read LCD 
	
StillBusy	
	bsf	LCD_E			; LCD puts valid data on bus
	NOP				; E must stay hi for >450ns
	IF	F_OSC > 5000000 	; 10 Mhz clock = 400ns instruction cycle
	NOP
	ENDIF

; First Transfer: LCD hi data into the lo nybble of W
	MOVFW	LCD_DATA		; Get hi half of data First
	BCF	LCD_E			; Tristate LCD again
	andlw	h'0F'			; mask out higher nybble
	MOVWF	LCD_T			; Save LCD data
	swapf	LCD_T, F		; Put hi LCD data in hi LCD_T
	
; Second Transfer: LCD lo data into the lo nybble of W
	bsf	LCD_E			; LCD puts valid data on bus
	NOP				; E must stay hi for >450ns
	IF	F_OSC > 5000000 	; 10 Mhz clock = 400ns instruction cycle
	NOP
	ENDIF
	movfw	LCD_DATA		; Put lower nybble in low w
	BCF	LCD_E			; Tristate LCD again
	andlw	h'0F'			; Mask out hi nybble
	iorwf	LCD_T, f		; Combine nybbles
		
	BTFSC	LCD_T, 7		; Check busy flag, high=busy
	GOTO	StillBusy		; Wait 'till bit 7 is clear
	BCF	LCD_RW			; Default: write to lcd... 
					; ..Before switching port to o/p 
	BSF	STATUS, RP0     	; Select Register page 1

; Port tri-State: 0=Output
	movlw	h'F0'
	andwf	LCD_TRIS, f		; Hi nubble tris to outpurs

	BCF	STATUS, RP0     	; Select Register page 0
	MOVFW	CHAR			; Restor char to print
	RETURN

	endif				; .. if (DataHi)


	else				; ... if (FourBit)
; 8 Bit Data path
Busy_Q
	movwf	CHAR			; Save the char first
	BSF	STATUS,RP0		; Select Register page 1

; Port tri-State: 0=Output
        MOVLW   B'11111111'     	; Port to outputs 
        MOVWF   LCD_TRIS		; I/O
	BCF	STATUS, RP0     	; Select Register page 0
	BCF	LCD_RS    		; Set LCD for command mode
	BSF	LCD_RW   		; Read LCD
StillBusy	
	bsf	LCD_E			; LCD puts valid data on bus
	NOP				; E must stay hi for >450ns
	IF	F_OSC > 5000000 	; 10 Mhz clock = 400ns instruction cycle
	NOP
	endif
	MOVFW	LCD_DATA
	BCF	LCD_E			; Tristate LCD again
	MOVWF	LCD_T			; Save LCD data
	BTFSC	LCD_T, 7		; Check busy flag, high=busy
	GOTO	StillBusy		; Wait 'till bit 7 is clear

	BCF	LCD_RW			; Default to write to lcd         
	BSF	STATUS, RP0     	; Select Register page 1

; Port tri-State: 0=Output
        MOVLW   B'00000000'     	; PortB 
        MOVWF   LCD_TRIS		; I/O
	BCF	STATUS, RP0     	; Select Register page 0

	MOVFW	CHAR			; Restor char to print
	RETURN
	endif				;  .. if (FourBit)
	
	page
; - Mchip library routines:
;
;********************************************************************
;                  Binary To BCD Conversion Routine
;      This routine converts a 16 Bit binary Number to a 5 Digit
; BCD Number. This routine is useful since PIC16C55 & PIC16C57
; have  two 8 bit ports and one 4 bit port ( total of 5 BCD digits)
;
;       The 16 bit binary number is input in locations H_byte and
; L_byte with the high byte in H_byte.
;       The 5 digit BCD number is returned in R0, R1 and R2 with R0
; containing the MSD in its right most nibble.
;
;   Performance :
;               Program Memory  :       35
;               Clock Cycles    :       885
; NB uses FSR 
; 22 Jun-95: mod kg, use cblock in main for ram registers instead of EQU 
;*******************************************************************;
;
;count  equ      16
;temp   equ      17
;
;H_byte  equ     10
;L_byte  equ     11
;R0      equ     12              ; RAM Assignments
;R1      equ     13
;R2      equ     14
;
;	include         "mpreg.h"
;
B2_BCD  bcf     STATUS,0                ; clear the carry bit
	movlw   .16
	movwf   count
	clrf    R0
	clrf    R1
	clrf    R2
loop16  rlf     L_byte, f
	rlf     H_byte, f
	rlf     R2, f
	rlf     R1, f
	rlf     R0, f
;
	decfsz  count, f
	goto    adjDEC
	RETLW   0
;
adjDEC  movlw   R2
	movwf   FSR
	call    adjBCD
;
	movlw   R1
	movwf   FSR
	call    adjBCD
;
	movlw   R0
	movwf   FSR
	call    adjBCD
;
	goto    loop16
;
adjBCD  movlw   3
	addwf   0,W
	movwf   temp
	btfsc   temp,3          ; test if result > 7
	movwf   0
	movlw   30
	addwf   0,W
	movwf   temp
	btfsc   temp,7          ; test if result > 7
	movwf   0               ; save as MSD
	RETLW   0
