;*******************************************************************
;dbt_kypd_echo_slave			  for Derbot Hand Controller
;Reads keypad value when pressed and sends interrupt to main AGV.
;Transmits keypad character on I2C (as Slave) when asked (by Master).
;Displays any character sent on I2C from Master.
;TJW 13.7.05				   tested & working 21.7.05
;Initialisation revised	July 06
;*******************************************************************
;Clock is 1MHz approx
;								
;   	Port A      Port B      	Port C
;   	------      ------          	------
;0	lcd bus 2	not used	Display RS
;1	lcd bus 3	keypad col 3	Display E
;2	lcd bus 4	keypad col 2	Interrupt op
;3	lcd bus 5	keypad col 1	SCL
;4	lcd bus 6	keypad row 4	SDA
;5	lcd bus 7	keypad row 3	Display R/W
;6	    -		keypad row 2 	lcd bus 0
;7	    -		keypad row 1   	lcd bus 1

;Set Configuration Word: crystal oscillator RC, WDT off, 
;     power-up timer on, code protect off, LV Program off.
	__CONFIG _RC_OSC & _WDT_OFF & _PWRTE_ON & _LVP_OFF

	list 	p=16F873A
	#include p16f873A.inc

;Specify RAM
delcntr1 	equ	20	;used in delays
delcntr2 	equ	21
temp	 	equ	22  	;a temp location, to be used only in consecutive instructions
lcd_op	 	equ	23	;holds word to be sent to display
pointer  	equ    	24  	;points to Character string Tables
kpad_pat 	equ	25	;holds pattern of keypad
kpad_add 	equ	26  	;holds kpad address to lu table
kpad_char 	equ	27	;holds most recent character from keypad
I2C_RX_word 	equ	28	;holds most recent I2C word recd
I2C_TX_word 	equ	2a	;holds word to be transmitted on I2C
display_pntr 	equ 	2b 	;holds pointer to next display digit, as this prog controls digit position
				;bit 4 indicates line
flags		equ	2c	;bit 0 holds RS state during busy_check SR
			
;Specify some port bits
;Port C
lcd_RS		equ	0	;0 for instruction, 1 for character
lcd_E		equ 	1
lcd_RW		equ	5
;Port A
lcd_busy 	equ 	5
;
	org 	00
	goto 	main
	org 	04		;interrupt vector
	goto 	Interrupt_SR
;
;Initialise SFRs in Bank 1
main bcf	status,rp1
	bsf	status,rp0 		;select memory bank 1
	movlw	B'00000110'	;All Port A bits digital
	movwf	adcon1
	movlw	B'00000000'	;Port A initially all op
	movwf	trisa	
	movlw	B'00011000'	;I2C bits of Port C to ip	
	movwf	trisc		
	movlw	B'11110000'	;Port B initially Row bits ip, column op
	movwf	trisb
	movlw	B'10100100'
	movwf	sspadd		;our address to be 52H (it's shifted by one in sspadd)		
	bsf	pie1,sspie	;enable I2C interrupt

;Initialise SFRs in Bank 0
	bcf	status,rp0	;select bank 0
	movlw	B'00110110'	;SSPCON1:MSSP on, I2C Slave, 7 bit address, interrupts off,
	movwf	sspcon					;no clock stretch 
;
;initialise display 
	call delay200		;wait for power supply to settle
	bcf	portc,lcd_RS	;set control lines
	bcf	portc,lcd_RW
;Relies first on automatic initialisation, as described in data sheet
;now send four command words. Busy Flag not used. 
	movlw B'00001111'	;first command word, Function Set, rotated by 2 bits
	movwf porta
	bsf	portc,lcd_E
	bcf	portc,lcd_E
	call delay1			;wait at least 39us
;
	movlw B'00000011'	;next command, Display ON/OFF
	movwf porta
	bsf	portc,6
	bsf	portc,7
	bsf	portc,lcd_E		
	bcf	portc,lcd_E
	call 	delay1			;wait at least 39us
;
	movlw B'00000000'	;next command, Display Clear
	movwf porta
	bcf	portc,7
	bsf	portc,6
	bsf	portc,lcd_E		
	bcf	portc,lcd_E
	call 	delay1		
	call 	delay1			;wait at least 1.53ms
;
	movlw B'00000001'	;next command, Entry Mode Set
	movwf porta
	bsf	portc,7
	bcf	portc,6
	bsf	portc,lcd_E		
	bcf	portc,lcd_E
	call 	delay1	
;further initialisation, and interrupt enabling
	clrf	portb	;initialise keypad value
	movlw	0ff 	;preset display pointer, so it increments to 0
	movwf 	display_pntr
	bcf	intcon,rbif	;clear pending interrupts
	bcf	pir1,sspif
	bsf	intcon,rbie
	bsf	intcon,peie
	bsf	intcon,gie
loop	goto	loop		;await keypad and I2C interrupts

;***************************************************************
;This is ISR, caused by keypad or I2C address match.
;Does not context save, as all action is in ISRs.
;***************************************************************
Interrupt_SR  btfsc intcon, rbif	;is it keypad interrupt?
	goto	kpad_ISR
;Here if interrupt is I2C, either address match (Ack sent automatically)
;OR further received byte has been detected. 
;check whether this byte was address or data
	bsf	status,rp0
	btfsc	sspstat,d_a
	goto	ISR1		;go if word was data
	bcf	status,rp0
	movf	sspbuf,0	;dummy read of the address byte, to clear flag
;check if read, if so load and send byte
	bsf	status,rp0
	btfsc	sspstat,r_w
	goto	Send_I2C
	bcf	status,rp0	;otherwise exit ISR, to await incoming data byte
	bcf	pir1,sspif	;clear interrupt bit, and end ISR
	retfie	
;Here if data byte has been detected, word is already in buffer. 
ISR1	call 	dig_pntr_set ;sort display pointer
	bcf	status,rp0	;read word
	movf	sspbuf,0
	movwf	I2C_RX_word	;save word
	movwf	lcd_op		;prepare to send word to display
	bsf	portc,lcd_rs
	call	lcd_write
;transfer to lcd is done, end ISR.
	bcf	pir1,sspif	;clear interrupt bit
	retfie	
;here if sending I2C word. Send byte held in kpad_char
Send_I2C bcf	status,rp0
	bcf	pir1,sspif	
	movf	kpad_char,0 	;move character to sspbuf
	movwf	sspbuf
	bsf	sspcon,ckp	;release clock
	btfss	pir1,sspif	;wait for completion of transfer
	goto	$-1		 
;transfer is complete, end ISR.
	bcf	pir1,sspif	;clear interrupt bit
	retfie	
;
;Keypad press has been detected through Port B Interrupt on Change.
;Gets value, converts to character, stores in kpad_char, awaits key release, 
;and sends interrupt to AGV
kpad_ISR call 	kpad_rd
	call 	kp_code_conv
rel_test call 	kpad_rd		;test now for keypad release
	movf	kpad_pat,0
	andlw	0fe		;suppress lsb, which is not used
	sublw	0fe		;test if inactive
	btfss	status,z
	goto 	rel_test
	bsf 	portc,2		;send interrupt to AGV
	nop
	bcf 	portc,2	
	bcf	intcon,rbif 	;clear interrupt bit
	retfie
;**********************************************
;SUBROUTINES
;**********************************************
;Reads keypad, places raw row/column pattern into kpad_pat, and 
;resets keypad interface
kpad_rd	movf	portb,w	;read portb value, this will be row pattern
	andlw	B'11110000' 	;ensure unwanted bits are suppressed
	movwf	kpad_pat
	bsf	status,rp0 	;set row to op, column to ip
	movlw	B'00001110'
	movwf	trisb
	bcf	status,rp0
	movlw	00
	movwf	portb		;ensure output values still zero
	movf	portb,w		;read portb value, this will be column pattern
	andlw	B'00001110' 	;ensure unwanted bits are suppressed
	iorwf	kpad_pat,1	;OR those results into the pattern
;reset keypad interface
	bsf	status,rp0 	;set row to ip, column to op
	movlw	B'11110000'
	movwf	trisb
	bcf	status,rp0
	clrf	portb		;ensure output values still zero
	return
;
;Converts keypad pattern held in kpad_pat to ASCII character, first forming 
;address that is used in lu table. Returns with character held in kpad_char
kp_code_conv bcf status,c
	rrf		kpad_pat,1	;discard bit 0 which is not used
	clrf	kpad_add
;deduce row
	btfsc	kpad_pat,6
	goto	kp1
	goto	col_find	;here if row 1, kpad_add stays as is
kp1	btfsc	kpad_pat,5
	goto	kp2
	movlw	B'00000100'	;here if row 2
	iorwf	kpad_add,1	;form table address
	goto	col_find
kp2	btfsc	kpad_pat,4
	goto	kp3
	movlw	B'00001000'	;here if row 3
	iorwf	kpad_add,1	;form table address
	goto	col_find
kp3	btfsc	kpad_pat,3
	goto	kp4
	movlw	B'00001100'	;here if row 3
	iorwf	kpad_add,1	;form table address
	goto	col_find
kp4	movlw	D'16'		;no row detected, return "E" via Table
	goto keypad_op
;now deduce column
col_find btfsc	kpad_pat,2
	goto	cf1
	goto   keypad_op	;here if column 1, kpad_add stays as is
cf1	btfsc	kpad_pat,1
	goto	cf2
	movlw	B'00000001'	;here if column 2
	iorwf	kpad_add,1	;form table address	
	goto   keypad_op
;assume now column 3
cf2	movlw	B'00000010'			
	iorwf	kpad_add,1	;form table address	
keypad_op movf	kpad_add,0
	call	kp_table
	movwf	kpad_char	;save the character
	return
;Table called to convert pattern recd from keypad to actual character. Note that
;ASCII codes will be returned, as each digit is in format 'D'.
kp_table addwf pcl,1
	retlw	'1'
	retlw	'2'
	retlw	'3'
	retlw	'A'		;Error code
	retlw	'4'
	retlw	'5'
	retlw	'6'
	retlw	'B'		;Error code
	retlw	'7'
	retlw	'8'
	retlw	'9'
	retlw	'C'		;Error code
	retlw	'*'
	retlw	'0'
	retlw	'#'
	retlw	'D'		;Error code
	retlw	'E'		;Error code
;
;adjusts the digit pointer on the display, so that sent digits don't fall off!
dig_pntr_set bcf status,rp0
	incf	display_pntr,1	;increment and test pointer
	movf	display_pntr,0
	andlw	0f				;test lower 4 bits only
	sublw	08
	btfss	status,z
	goto	pntr_on			;no adjustment needed
	movf	display_pntr,0
	xorlw	10				;toggle line bit
	andlw	10				;return all other bits to zero
	movwf	display_pntr
	movlw	B'10000000'		;set up new value for digit address command
	btfsc	display_pntr,4
	movlw	B'10101000'		;second row address command
	movwf	lcd_op
	bcf	portc,lcd_rs
	call	lcd_write
pntr_on	bcf	status,rp0
 	return
;
;Waits until busy clear, and writes word held in lcd_op to display.
;RS must be preset to required value, this status is preserved. 
lcd_write call  busy_check
	bcf	portc,lcd_rw
	bcf	status,c
	rrf	lcd_op,1	;form output bits, op word sits across ports a & c
	bcf	portc,6		;set value of bit 0 of bus
	btfsc status,c
	bsf	portc,6
	bcf	status,c
	rrf	lcd_op,1
	bcf	portc,7		;set value of bit 1 of bus
	btfsc status,c
	bsf	portc,7
	movf lcd_op,0
	movwf porta
	bsf	portc,lcd_E
	bcf	portc,lcd_E
	return
;Test Busy Flag, and wait till cleared
busy_check bsf status,rp0 	;select memory bank 1
	movlw 	B'00111111'	;set port A all ip
	movwf 	trisa	
	bcf	status,rp0
 	bcf	flags,0			
	btfsc 	portc,lcd_RS	;save RS bit in flags, 0
	bsf	flags,0		
	bcf	portc,lcd_RS 	;access instruction register
	bsf	portc,lcd_RW	;set to read
busy_loop bcf 	portc,lcd_E	
	bsf	portc,lcd_E
	btfsc 	porta,lcd_busy ;test the busy flag, loop if still busy 
	goto 	busy_loop		
	bcf	portc,lcd_E	
	bsf	status,rp0 	;select memory bank 1
	movlw 	B'00000000'	;set port A all op, 
	movwf 	trisa	
	bcf	status,rp0
	bcf	portc,lcd_RS	
	btfsc 	flags,0		;reinstate RS bit
	bsf	portc,lcd_RS	
	return
;
;introduces delay of 100us approx
delay100u  movlw 	D'6'	;6 cycles called, 
;			each taking 16us
        movwf  	delcntr1
del1001    nop	;4 inst cycles in this loop, ie 16us
        decfsz 	delcntr1,1
        goto 	del1001
        return

delay1  movlw 	D'62'	;62 cycles called, 
;			each taking 16us
        movwf  	delcntr1
del1    nop	;4 inst cycles in this loop, ie 16us
        decfsz 	delcntr1,1
        goto 	del1
        return
;
;introduces delay of 5ms approx
delay5  movlw 	D'250'	;250 cycles called 
        movwf  	delcntr1
del51   nop				;5 inst cycles in this loop, ie 20us
 	nop
        decfsz 	delcntr1,1
        goto 	del51
        return
;
;introduces delay of 200ms approx, by 200 calls to delay1
delay200  movlw D'200'
        movwf  	delcntr2
del2    call 	delay1
        decfsz  delcntr2,1
        goto 	del2
        return  
;
	end
