;****************************************************************
;**           :
;** Project   : Personal Logic Debugger (PLD)
;** Filename  : LCD.ASM
;** Author    : Don Lekei
;** Status    : Experimental
;** Date      : 08/27/93
;** Purpose   : LCD driver code
;**           :
;****************************************************************
;
;           LCD DRIVER WAVEFORMS
;           --------------------
;
;      |<->| could be 2-8ms depending on application
;
;      .---.                                .---. 
;BP0  -'   `-------------.   .--------------'   `
;                        `---'                   
;            .---.                               
;BP1  -------'   `-------------.   .-------------
;                              `---'             
;                  .---.                         
;BP2  -------------'   `-------------.   .-------
;                                    `---'                   
;     #     #     #     #-----#-----#-----.#     
;RB0  #     #     #     #     #     #     |#      
;     #-----#-----#-----#     #     #     `#----- 
;                                                 
;SEG  #-----#-----#-----#-----#-----#------#----- 
;1-18 #-BP0 #-BP1 #-BP2 #+BP0 #+BP1 #+BP2  #-BP0  
;     #-----#-----#-----#-----#-----#------#----- 
;                                                 
;SEG  #-----#-----#-----#-----#-----#-----K#----- 
;19-24#-BP0 #-BP1 #-BP2 #+BP0 #+BP1 #+BP2 K#-BP0  
;     #-----#-----#-----#-----#-----#-----K#----- 
;     ^     ^     ^     ^     ^     ^     ^^
;     `-----`-----|-----'-----'-----'     ||    
;                 |      Keyboard Scan----'|
;       Changing data during shift out-----'
;
;
;         SEGMENT DRIVER SHIFT REGISTER
;         -----------------------------
;      _   _   _   _   _   _   _   _   _    _   _   _   _   _   _   _   _____  
;CLOCK  | | | | | | | | | | | | | | | | |  | | | | | | | | | | | | | | | | 
;(RB0) _|_| |_| |_| |_| |_| |_| |_| |_| |__| |_| |_| |_| |_| |_| |_| |_| |___
;      __ ___ ___ ___ ___ ___ ___ ___ ____ _____________________________ ____
;DATA1   |S2 |S4 |S6 |S8 |S10|S12|S14|S16 |S18                          |S24
;(RB7) __|___|___|___|___|___|___|___|____|_____________________________|____
;      __ ___ ___ ___ ___ ___ ___ ___ ____ _____________________________ ____
;DATA0   |S1 |S3 |S5 |S7 |S9 |S11|S13|S15 |S17                          |S23
;(RB6) __|___|___|___|___|___|___|___|____|_____________________________|____
;      _                                  ______________________________ ____
;LATCH  |                                |                              |S22
;(RB5) _|________________________________|                              |____
;
;
;The backplane configuration determines the bit order. Bits are sent to
;the LCD (display dependant) as:
;	-BP1---- -BP2---- -BP3----
;	S1,S2,S3,S1,S2,S3,S1,S2,S3
;       h, c, d, b, g, e, *, a, f
;
; * = legend: Digit 1 - [E], Digit 2 - [M], Digit 3 - [-]   
;There are 3 segment lines per digit

	.seg	REGS
LCD_STATE:	.ds 1	;lcd state
LCD_TEMP:	.ds 1	;temp location for use by LCD/KBD/LOGIC routines
LCD_TEMP2:	.ds 1	;temp location for use by LCD/KBD/LOGIC routines
	.seg	ROMDATA	;pch points to this bank
;**********************************************************************
;* 
;* Goto LCD STATE in W. Executed by interrupt server. Saves new LCD_STATE.
;* 
;**********************************************************************

I_LCD_GET:
	movwf	LCD_TEMP	;save byte
	movfw	LCD_STATE	;get current state 
	andlw	7		;for safety	
	addwf	PCL		;goto state (6...1)
oops1:	nop			;0 - invalid
..tab:
	goto	_LCDG_BP3	;backplane 3
	goto	_LCDG_BP2	;backplane 2
	goto	_LCDG_BP1	;backplane 1
	goto	_LCDG_BP3N	;backplane 3 (inverted)
	goto	_LCDG_BP2N	;backplane 2 (inverted)
	goto	_LCDG_BP1N	;backplane 1 (inverted)
oops2:	goto	_LCD_RES	;7 - invalid! fix lcd state machine!

MAX_LCD_STATE = * - ..tab - 1


;**********************************************************************
;* Digit get functions.
;* 
;* These returns the next 3 segment's data in bits 5,6 & 7 of LCD_TMP,
;* from a byte pointed in W.
;* 
;* The bits are: 
;* 	Data Bit - 76543210
;* 	Plane    - 11122233
;* 	Segment# - 12312323
;* 
;* Segment 3,1 bits come from LCD_TEMP2 which contains a copy of the 
;* legend segment bits (eg. "E", "M", "-").
;*
;* These functions all return a bit pattern which contains the data to
;* complete the state: The value for trisa (LCD planes) with the MSB set to 
;* the new state of the planes.
;* 
;**********************************************************************

	.seg	CODE
_LCDG_BP3:	;backplane 3 - bits 1,0
	rrf	LCD_TEMP        ;start shifts  F=x7654321 C=0
	rrf	LCD_TEMP	;move bottom two bits to the top! F=0x654321 c=1
	rrf	LCD_TEMP	;move bottom two bits to the top! F=10x65432 c=2
	rlf	LCD_TEMP2	;get legend bit into CY
	rrf	LCD_TEMP	;F=L10x65432
	retlw  	TAINIT & ~^B.BP3

_LCDG_BP2:	;backplane 2 - bits 4,3,2
	rrf	LCD_TEMP        ;shift into position  F=x7654321
	swapf	LCD_TEMP	;move bottom 4 bits to the top! F=4321x765
	retlw  	TAINIT & ~^B.BP2

_LCDG_BP1:	;backplane 1 - BITS 7,6,5 (in place)
	retlw  	TAINIT & ~^B.BP1

_LCDG_BP3N:	;backplane 3 - bits 1,0 (inverted)
	rrf	LCD_TEMP        ;start shifts  F=x7654321 C=0
	rrf	LCD_TEMP	;move bottom two bits to the top! F=0x654321 c=1
	rrf	LCD_TEMP	;move bottom two bits to the top! F=10x65432 c=2
	rlf	LCD_TEMP2	;get legend bit into CY
	rrf	LCD_TEMP	;F=L10x65432
	comf	LCD_TEMP	
	retlw  	TAINIT | ^7 & ~^B.BP3

_LCDG_BP2N:	;backplane 2 - bits 4,3,2 (inverted)
	rrf	LCD_TEMP        ;shift into position  F=x7654321
	swapf	LCD_TEMP	;move bottom 4 bits to the top! F=4321x765
	comf	LCD_TEMP        ;this is inverted plane
	retlw  	TAINIT | ^7 & ~^B.BP2

_LCDG_BP1N:	;backplane 1 - BITS 7,6,5 (inverted)
	comf	LCD_TEMP         ;this is inverted plane
	retlw  	TAINIT | ^7 & ~^B.BP1


;**********************************************************************
;* 
;* Pseudo-interrupt called from the TICKER interrupt process
;* Backplane states driven by LCD_STATE from main interrupt driver.
;*      NOTE: This code is called in an interrupt. Appropriate precautions
;*            must be taken.
;* 
;* FSR is not used for speed and so we don't have to save and restore it!
;* The main transmission of LCD bits is done inline for speed and registers.
;* 
;**********************************************************************

I_LCD:
	MOVLW	TAINIT		;tristate all segment lines
	TRIS	PORTA		;tristate all segment lines

	movfw	DISPLAYF	;get display flags
	movwf	LCD_TEMP2	;hold display flags

;2 digits
	movlw	~(^B.CLOCK|^B.DATA0|^B.DATA1|^B.STROBE)  ;init s/r data and clock = 0
	movwf	PORTB		;init port, clock low
	movfw	DISPLAY+7	;get byte from display
	call	I_LCD_GET	;get the next 3 segment bits in LCD_TEMP 
	btfsc	LCD_TEMP,7	;skip if not set
	seb	B.DATA1		;set data bit
	btfsc	LCD_TEMP,6	;skip if not set
	seb	B.DATA0		;set data bit
	seb	B.CLOCK		;set clock bit - strobe 2 bits

	movlw	~(^B.CLOCK|^B.DATA0|^B.DATA1|^B.STROBE)  ;init s/r data and clock = 0
	movwf	PORTB		;init port, clock low
	btfsc	LCD_TEMP,5	;skip if not set
	seb	B.DATA1		;set data bit
	movfw	DISPLAY+6	;get next byte from display
	call	I_LCD_GET	;get the next 3 segment bits in LCD_TEMP 
	btfsc	LCD_TEMP,7	;skip if not set
	seb	B.DATA0		;set data bit
	seb	B.CLOCK		;set clock bit - strobe 2 bits

	movlw	~(^B.CLOCK|^B.DATA0|^B.DATA1|^B.STROBE)  ;init s/r data and clock = 0
	movwf	PORTB		;init port, clock low
	btfsc	LCD_TEMP,6	;skip if not set
	seb	B.DATA1		;set data bit
	btfsc	LCD_TEMP,5	;skip if not set
	seb	B.DATA0		;set data bit
	seb	B.CLOCK		;set clock bit - strobe 2 bits

;2 digits
	movlw	~(^B.CLOCK|^B.DATA0|^B.DATA1|^B.STROBE)  ;init s/r data and clock = 0
	movwf	PORTB		;init port, clock low
	movfw	DISPLAY+5	;get byte from display
	call	I_LCD_GET	;get the next 3 segment bits in LCD_TEMP 
	btfsc	LCD_TEMP,7	;skip if not set
	seb	B.DATA1		;set data bit
	btfsc	LCD_TEMP,6	;skip if not set
	seb	B.DATA0		;set data bit
	seb	B.CLOCK		;set clock bit - strobe 2 bits

	movlw	~(^B.CLOCK|^B.DATA0|^B.DATA1|^B.STROBE)  ;init s/r data and clock = 0
	movwf	PORTB		;init port, clock low
	btfsc	LCD_TEMP,5	;skip if not set
	seb	B.DATA1		;set data bit
	movfw	DISPLAY+4	;get next byte from display
	call	I_LCD_GET	;get the next 3 segment bits in LCD_TEMP 
	btfsc	LCD_TEMP,7	;skip if not set
	seb	B.DATA0		;set data bit
	seb	B.CLOCK		;set clock bit - strobe 2 bits

	movlw	~(^B.CLOCK|^B.DATA0|^B.DATA1|^B.STROBE)  ;init s/r data and clock = 0
	movwf	PORTB		;init port, clock low
	btfsc	LCD_TEMP,6	;skip if not set
	seb	B.DATA1		;set data bit
	btfsc	LCD_TEMP,5	;skip if not set
	seb	B.DATA0		;set data bit
	seb	B.CLOCK		;set clock bit - strobe 2 bits

;2 digits
	movlw	~(^B.CLOCK|^B.DATA0|^B.DATA1|^B.STROBE)  ;init s/r data and clock = 0
	movwf	PORTB		;init port, clock low
	movfw	DISPLAY+3	;get byte from display
	call	I_LCD_GET	;get the next 3 segment bits in LCD_TEMP 
	btfsc	LCD_TEMP,7	;skip if not set
	seb	B.DATA1		;set data bit
	btfsc	LCD_TEMP,6	;skip if not set
	seb	B.DATA0		;set data bit
	seb	B.CLOCK		;set clock bit - strobe 2 bits

	movlw	~(^B.CLOCK|^B.DATA0|^B.DATA1|^B.STROBE)  ;init s/r data and clock = 0
	movwf	PORTB		;init port, clock low
	btfsc	LCD_TEMP,5	;skip if not set
	seb	B.DATA1		;set data bit
	movfw	DISPLAY+2	;get next byte from display
	call	I_LCD_GET	;get the next 3 segment bits in LCD_TEMP 
	btfsc	LCD_TEMP,7	;skip if not set
	seb	B.DATA0		;set data bit
	seb	B.CLOCK		;set clock bit - strobe 2 bits


;done 16 bits, strobe to latches
	seb	B.STROBE	;STROBE 8 bits into each latch


	movlw	~(^B.DATA0|^B.DATA1|^B.CLOCK) ;init port data and clock = 0
	andwf	PORTB		;init port, clock low
	btfsc	LCD_TEMP,6	;skip if not set
	seb	B.DATA1		;set data bit
	btfsc	LCD_TEMP,5	;skip if not set
	seb	B.DATA0		;set data bit
;push out last 2 bits to the end of the shift registers (8 clocks)
	seb	B.CLOCK		;-
	clb	B.CLOCK		;
	seb	B.CLOCK		;-
	clb	B.CLOCK		;
	seb	B.CLOCK		;-
	clb	B.CLOCK		;
	seb	B.CLOCK		;-
	clb	B.CLOCK		;
	seb	B.CLOCK		;-
	clb	B.CLOCK		;
	seb	B.CLOCK		;-
	clb	B.CLOCK		;
	seb	B.CLOCK		;-
	clb	B.CLOCK		;
	seb	B.CLOCK		;-

	seb	B.DATA0		;set all data bits.
	seb	B.DATA1		;

;now all bits on the port are set, so to prevent rising edges here,
;we just clear the necessary bits from the last 2 digits.

	movfw	DISPLAY+1	;get next byte from display
	call	I_LCD_GET	;get the next 3 segment bits in LCD_TEMP
	btfss	LCD_TEMP,7	;skip if not set
	clb	B.S19		;set data bit (segment)
	btfss	LCD_TEMP,6	;skip if not set
	clb	B.S20		;set data bit
	btfss	LCD_TEMP,5	;skip if not set
	clb	B.S21		;set data bit

	movfw	DISPLAY+0	;get next byte from display
	call	I_LCD_GET	;get the next 3 segment bits in LCD_TEMP
	btfss	LCD_TEMP,7	;skip if not set
	clb	B.S22		;set data bit
	btfss	LCD_TEMP,6	;skip if not set
	clb	B.S23		;set data bit
	btfss	LCD_TEMP,5	;skip if not set
	clb	B.S24		;set data bit

;W is set-up to initialize port after last call to I_LCD_GET
;W = The value for trisa (LCD planes) with the MSB set to the new state of
;    the planes.
;
; This also puts a very short pulse on the input clamp to ensure bias on input
; this may need to be changed if RTCC input is used
;
	addlw	$80		;copy bit 7 to carry
	TRIS	PORTA		;we won't blow 2 cycles using the "official" way
	movlw	^B.BP1|^B.BP2|^B.BP3
	btfss	CY 		;CS if backplanes are high
	xorlw	^B.BP1|^B.BP2|^B.BP3
	movwf	PORTA		;set backplane drivers - pulses clamp on input
	btfsc	CY 		;CC if backplanes are LOW (B.CLOCK is HIGH)
	clb	B.CLOCK		;High backplane, set low
	btfss	B.RA5ON		;Adjust RA5
	seb	B.SCALE		;Turn on clamp to ground

	TICK_EXIT		;tick interrupt exit code

;**********************************************************************
;* RESET the LCD display 
;**********************************************************************

LCD_RESET:
	call	CLS
	DISPLAY	"pld r092"    ;hello message "P.L.D. rx.xx"
	bsf	DISPLAY+5,dp	;set decimal point for rev.

_LCD_RES:
	movlw	MAX_LCD_STATE		;initial LCD state
	movwf	LCD_STATE		;
	incf	TICKER			;make ticker non zero
	return 

;**********************************************************************
;* 
;* CLEAR DISPLAY - preserves all regs except Z
;* 
;**********************************************************************
CLS:
	CLRF	DISPLAYF		;clear flags
	CLRF	DISPLAY+0		;clear display
	CLRF	DISPLAY+1		;clear display
	CLRF	DISPLAY+2		;clear display
	CLRF	DISPLAY+3		;clear display
	CLRF	DISPLAY+4		;clear display
	CLRF	DISPLAY+5		;clear display
	CLRF	DISPLAY+6		;clear display
	CLRF	DISPLAY+7		;clear display
	CLB	B.NUMPROC		;no number on display
	return


;**********************************************************************
;* 
;* Dispaly a string on LCD 
;* TEMP is already setup with the length of the string, W=address in ROMDATA
;* returns last character of string in W
;* 
;**********************************************************************

DISPLAY_STR:
	movwf	DSPTEMP2	;save address of string
	CALLW			;get data (FIRST BYTE IS LENGTH)
	movwf	DSPTEMP1	;save length
	movlw	DISPLAY		;point FSR to display MSD
	movwf	FSR		;

..loop:
	incf	DSPTEMP2        ;next char
	movfw	DSPTEMP2	;address of next char (RETLW)
	CALLW			;get data
	movwf	(ind)		;write data
	incf	FSR		;next LCD address
	decfsz	DSPTEMP1        ;count length
	goto	..loop          ;

	CLB	B.NUMPROC	;no number on display
	return                  ;

;**********************************************************************
;* 
;* Convert W or DSPTEMP2 to an LCD digit in W.
;* W_TO_XDIGIT: converts W to LCD 0-F. (sets B.ZEROENAB)
;* T2_TO_DIGIT: converts DSPTEMP2 to LCD 0-F. ZERO=SPACE if B.ZEROENAB=0
;* W_TO_DIGIT: converts W to LCD 0-F. ZERO=SPACE if B.ZEROENAB=0
;* 
;**********************************************************************

	.seg	ROMDATA
T2_TO_DIGIT:
        movfw	DSPTEMP2	;get number to xlate
W_TO_DIGIT:
	iorlw	0		;test W	
	btfss	z
	goto	W_TO_XDIGIT	;non zero
	btfss	B.ZEROENAB      ;skip if not leading spaces
	retlw	chr(" ")	;return LCD space
W_TO_XDIGIT:
	seb	B.ZEROENAB	;enable zeros after digit
	andlw	$0F
	addwf	PCL
	.text	"0123456789ABCDEF"

	.oldseg

;**********************************************************************
;* 
;* Display number on LCD
;* SHOW_W_3DEC: show W as 3 decimal digits with leading ZEROS
;* SHOW_W_DEC: show W as 3 decimal digits with leading SPACES
;* SHOW_3DEC_FSR : show Digits with FSR already set B.ZEROENAB sets 0/space
;* 
;**********************************************************************
;
; Itteritave subtraction is actually the most efficient due to the nature of the
; instruction set. 
;
SHOW_W_3DEC:
	SEB	B.ZEROENAB	;leading 0's
	goto _showd_2
SHOW_W_DEC:
	CLB	B.ZEROENAB	;trim leading zeros
_showd_2:
	movwf	DSPTEMP1	;save w
	movlw	DISPLAY+5	;point to display
	movwf	FSR		;
	movfw	DSPTEMP1	;restore w
SHOW_3DEC_FSR:
	clrf	DSPTEMP2
	decf	DSPTEMP2	;-1
..loop:
	incf	DSPTEMP2	;digit under construction
	movwf	DSPTEMP1	;number to convert
	addlw	-100		;cs if success
	btfsc	CY		;skip if cy clear (borrow)
	goto	..loop
	CALL	T2_TO_DIGIT	;convert to DSPTEMP2 to LCD code
	movwf	(ind)		;display digit
	incf	FSR		;next display loc.
	
	movfw	DSPTEMP1	;restore w
	clrf	DSPTEMP2
	decf	DSPTEMP2	;-1
..loop:
	incf	DSPTEMP2	;digit under construction
	movwf	DSPTEMP1	;number to convert
	addlw	-10		;cs if success
	btfsc	CY		;skip if cy clear (borrow)
	goto	..loop

	CALL	T2_TO_DIGIT	;convert to DSPTEMP2 to LCD code
	movwf	(ind)		;display digit
	incf	FSR		;next display loc.

	movfw	DSPTEMP1	;remainder
	CALL	W_TO_XDIGIT	;convert to DSPTEMP2 to LCD code
	movwf	(ind)		;display digit
	return




;**********************************************************************
;* ENTER A DIGIT ON LCD DISPLAY and into VALUE (4 bytes l...h)
;* 
;* INPUT DIGIT IS IN TEMP3
;* 
;* ACCEPTS INDIVIDUAL DIGITS.  B.NUMPROC indicates if number processing
;* has already been started.
;* RETURNS CY CLR and value-so-far in VALUE (always valid)
;* USES TEMP,TEMP2,DSPTEMP1,DSPTEMP2
;**********************************************************************
;digit carry calls

_d1_cy:
	incfsz	VALUE+1
	return
_d2_cy:
	incfsz	VALUE+2
	return
_d3_cy:
	incfsz	VALUE+3
	return
	seb	B.SEG_E		;overflow!
	return
;**********************************************************************
;* 
;* Function when number key is pressed - add another digit
;* 
;**********************************************************************

F_NUMBER:
	bbs	B.NUMPROC,PROC_NEXT_NUM
	CLRF	VALUE+0
	CLRF	VALUE+1
	CLRF	VALUE+2
	CLRF	VALUE+3
	CALL    CLS
	SEB     B.NUMPROC
	goto	_nproc1

PROC_NEXT_NUM:
;shift display
	movfw	DISPLAY+1
	movwf	DISPLAY+0
	movfw	DISPLAY+2
	movwf	DISPLAY+1
	movfw	DISPLAY+3
	movwf	DISPLAY+2
	movfw	DISPLAY+4
	movwf	DISPLAY+3
	movfw	DISPLAY+5
	movwf	DISPLAY+4
	movfw	DISPLAY+6
	movwf	DISPLAY+5
	movfw	DISPLAY+7
	movwf	DISPLAY+6

;VALUE = 10 * VALUE
;(10 = 8+2)
;multiply in place
	clrc
	rlf	VALUE+0		;*2
	rlf	VALUE+1		;
	rlf	VALUE+2		;
	rlf	VALUE+3		;
	skpnc                    ;overflow if cs
	seb	B.SEG_E		;overflow!
	rlf	VALUE+0,w	;*4
	movwf	DSPTEMP1	;hold intermediate value
	rlf	VALUE+1,w	;*4
	movwf	DSPTEMP2	;hold intermediate value
	rlf	VALUE+2,w	;*4
	movwf	TEMP		;hold intermediate value in TEMP
	rlf	VALUE+3,w	;*4
	movwf	TEMP2		;hold intermediate value in TEMP2
	skpnc                    ;overflow if cs
	seb	B.SEG_E		;overflow!
	rlf	DSPTEMP1	;intermediate value VALUE*8
	rlf	DSPTEMP2	;
	rlf	TEMP		;
	rlf	TEMP2		;
	skpnc                   ;overflow if cs
	seb	B.SEG_E		;overflow!
	movfw	DSPTEMP1
	addwf	VALUE+0		;*2+*8=*10
	skpnc
	call	_d1_cy		;carry into first digit
	movfw	DSPTEMP2
	addwf	VALUE+1		;*2+*8=*10
	skpnc
	call	_d2_cy		;carry into second digit
	movfw	TEMP
	addwf	VALUE+2		;*2+*8=*10
	skpnc
	call	_d3_cy		;carry into second digit
	movfw	TEMP2
	addwf	VALUE+3		;*2+*8=*10
	skpnc
	seb	B.SEG_E		;overflow!

_nproc1:
	decf	TEMP3,w		;get key as value
	addwf	VALUE+0		;add new digit
	skpnc
	call	_d1_cy		;carry into first digit
	call	W_TO_XDIGIT	;
	movwf	DISPLAY+7	;put digit into number
	clrc
	retlw 0			;done!


