;**********************************************************************
;                                                                     *
;    Filename:	    LINUART688i.asm					*
;    Date:          05.11.18                                          *
;    File Version:  2.0                                               *
;                                                                     *
;    Author:        Chuck Simmers                                     *
;    Company:       Microchip Technology, Inc.                        *
;
;	LIN 2.0 compatible slave driver for PIC16F688-type EAUSART	*
;                                                                     * 
;**********************************************************************
;                                                                     *
;    Files required: LINUART688i.inc					*
;
;	The following code needs to be put into the interrupt routine	*
;	and RCIE ansd TXIE enabled.

;Check if LIN comm channel is active
;	bsf	STATUS,RP0
;	btfss	PIE1,RCIE		; is AUSART receive interrupt enabled?
;	goto	CheckXmitFlag		; no, go check transmit
;	bcf	STATUS,RP0
;	btfsc	PIR1,RCIF		; did a receive inerrupt happen?
;	goto	LINHandler		; yes, handle it
;	bsf	STATUS,RP0
;CheckXmitFlag
;	btfss	PIE1,TXIE		; is transmit interrupt enabled?
;	goto	$+4			; no, return from interrupt
;	bcf	STATUS,RP0
;	btfsc	PIR1,TXIF		; yes,
;	goto	PutDATAbyte		;  send a byte of data

;                                                                     *
;**********************************************************************
;                                                                     *
;    Notes:                                                           *
;    1.0 first revision                                               *
;	2.0 Revised to support LIN 2.0 addressing			*
;                                                                     *
;                                                                     *
;**********************************************************************

; ***********************************************************************
; * Initialization							*
; ***********************************************************************

; Setup LIN USART
SetupLINUSART
	movlw	B'10010000'	; UART enabled,8-bit,continuous receive
	movwf	RCSTA
	movlw	B'00000100'	; 8-bit, asynchronous, high-baudrate
	movwf	TXSTA
	movlw	B'00001000'	; 16-bit BRG
	movwf	BAUDCTL
	clrf	SPBRGH
	movlw	0x31		; setup initialy for 20KBaud @ 4.0MHz, BRGH=1, BRG16=1
	movwf	SPBRG
	movlw	.12		; message preamble is three bytes long (BREAK,SYNC,ID)
	movwf	MESSAGE_COUNTER
	movfw	RCREG		; dump any pending character, reset RCIF and FERR
	bsf	INTCON,PEIE	; enable peripheral interrupts
	bsf	STATUS,RP0
	bsf	PIE1,RCIE	; enable receive interrupt
	bcf	STATUS,RP0
	bsf	INTCON,GIE	; global enable interrupts
	clrf	ID_TEMP

	call	WAIT001		; wait 1mS
	bcf	LINCS		; toggle LINCS
	bsf	LINCS		; to enable transceiver
	return
		
		; ***********************************************************************
		; * Autobaud routine							*
		; ***********************************************************************
LINHandler
	;ProcessPacket
	movlw	HIGH GetBREAK
	movwf	PCLATH
		movfw	MESSAGE_COUNTER		; MESSAGE_COUNTER is also used as state machine pointer
	 	addwf	PCL, f		; add to PC
		goto	GetCHECKSUM	;0; CHECKSUM byte
		goto	GetDATAbyte	;1; Data byte
		goto	GetDATAbyte	;2; Data byte
		goto	GetDATAbyte	;3; Data byte
		goto	GetDATAbyte	;4; Data byte
		goto	GetDATAbyte	;5; Data byte
		goto	GetDATAbyte	;6; Data byte
		goto	GetDATAbyte	;7; Data byte
		goto	GetDATAbyte	;8; Data byte
		goto	GetDATAbyte	;9; Data byte
		goto	GetIDENTIFIER	;10; ID byte
		goto	GetSYNC		;11; SYNC character
		goto	GetBREAK	;12; BREAK character


GetBREAK
;	btfss	RCSTA,FERR	; was BREAK character longer than 8 bits?
;	goto	BadBREAKchar	; no, not a valid BREAK, too short
	movfw	RCREG		; dump break character, reset RCIF and FERR
	btfss	STATUS,Z
	goto	BadBREAKchar	; no, not a valid BREAK, not zero
	decf	MESSAGE_COUNTER
	btfss	LINRX
	goto	$-1
	bsf	BAUDCTL,ABDEN	;enable AutoBaud
	goto	RestoreStatus
BadBREAKchar
	movfw	RCREG		; dump break character, reset RCIF and FERR
	goto	RestoreStatus

GetSYNC	
	btfsc	BAUDCTL,ABDOVF	; did baud rate generator overflow?
	goto	BadSYNCchar	; yes, bad sync character
	btfsc	RCSTA,FERR	; was there a Framing Error?
	goto	BadSYNCchar	; yes, bad sync character
	decf	SPBRG
	movfw	RCREG		; dump sync character, reset RCIF
	decf	MESSAGE_COUNTER
	goto	RestoreStatus
BadSYNCchar
	bcf	BAUDCTL,ABDOVF	; clear the overflow condition
	movlw	.12		; reset the state machine
	movwf	MESSAGE_COUNTER
	goto	RestoreStatus

GetIDENTIFIER
	movfw	RCREG		; get character, reset RCIF and FERR
	movwf	RXTX_REG
	; The following is LIN 2.0 compliant code
	movwf	ID_TEMP		; copy Identifier into ID_TEMP register
		; Decode ID4 and ID5. These two bits indicate how many bytes of
		; data have to be transmitted or received.
	call	IDDecodeTable
	andlw	0x0F		; mask out length data
	btfsc	STATUS,Z	; if length = 0
	goto	UnrecognisedID		; then, quit, else,
	movwf	MESSAGE_COUNTER	; store length of message into MESSAGE_COUNTER register
	incf    MESSAGE_COUNTER,f ; increment message counter by one for receiving or
                                ; transmitting CRC byte
	movfw	ID_TEMP		; get identifier again
	call	IDDecodeTable	; lookup type and length
	andlw	0xC0		; mask out length, leave type
	xorlw	ReceiveData	; if this is Receive Mode
	btfsc	STATUS,Z
	goto	ReceiveMode	; go to Receive Mode, else
		
TransmitMode
;		incf    MESSAGE_COUNTER,f ; increment message counter by one for transmitting CRC byte
		call	CheckParityBits		; check if parity bits are correct
		movlw	0x01			; Tell CRC routine that checksum has to
		movwf	TEMP_TRANSFER		; be transfered
		call	CheckCRC		; generated CRC
		movlw	DATAPOINTER		; point to first data byte
		movwf	FSR			; initialize FSR register
		bsf	STATUS,RP0
		bcf	PIE1,RCIE		; disable receive interrupt
		bsf	PIE1,TXIE		;enable transmitter interrupt
		bcf	STATUS,RP0
		bsf	TXSTA,TXEN
		; fall through to transmit the first byte
		movfw	INDF			; copy data byte into w-register
		movwf	TXREG
		incf	FSR, f			; point to next location
		decf	MESSAGE_COUNTER, f	; decrement Message Counter by one
		goto	RestoreStatus
				
PutDATAbyte
	movfw	INDF			; copy data byte into w-register
	movwf	TXREG
	incf	FSR, f			; point to next location
	decfsz	MESSAGE_COUNTER, f	; decrement Message Counter by one		
	goto	RestoreStatus
	btfss	TXSTA,TRMT		; wait for final transmission
	goto	$-1
	bsf	STATUS,RP0
	bcf	PIE1,TXIE		; no more to transmit
	bsf	PIE1,RCIE		; get ready to receive another frame
	bcf	STATUS,RP0
	movfw	RCREG			; dump any pending character, reset RCIF
	bcf	RCSTA,SPEN
	bsf	RCSTA,SPEN
	bcf	RCSTA,CREN
	bsf	RCSTA,CREN
	movlw	.12
	movwf	MESSAGE_COUNTER
	goto	RestoreStatus

		; Receive Mode in this sequence data is received
ReceiveMode
	clrf	Error_Byte	; clean slate
	movlw	DATAPOINTER		; point to data location
	movwf	FSR			; where data should be stored
	decf	MESSAGE_COUNTER, f 	; decrement number of bytes to receive by one
	goto	RestoreStatus

GetDATAbyte
	movfw	RCREG		; get character, reset RCIF and FERR
	movwf	RXTX_REG		; copy data into w-register
	movwf	INDF			; copy data into data area
	incf	FSR, f			; point to next location
	decf	MESSAGE_COUNTER, f 	; decrement number of bytes to receive by one
	goto	RestoreStatus

GetCHECKSUM
	movfw	RCREG		; get character, reset RCIF and FERR
	movwf	RXTX_REG		; copy data into w-register
	movwf	INDF			; copy data into data area

	call	CheckParityBits		; check if parity bits are correct
	iorwf	Error_Byte,f		; put any error bits into Error
	clrf	TEMP_TRANSFER		; Tell CRC routine to check CRC
	call	CheckCRC		; check if checksum is correct
	iorwf	Error_Byte,f		; put any error bits into Error

UnrecognisedID
	movlw	.12
	movwf	MESSAGE_COUNTER
	goto	RestoreStatus

ListenMode
	goto	RestoreStatus		; return to main

		; ***********************************************************************
		; * LIN checksum calculation (assumes FSR points to the first DB)	*
		; ***********************************************************************
CheckCRC	movlw	DATAPOINTER		; point to first data byte
		movwf	FSR			; 
	movfw	ID_TEMP		; get Identifier
		; Decode ID4 and ID5. These two bits indicate how many bytes of
		; data have to be transmitted or received.
	call	IDDecodeTable
	andlw	0x0F		; mask out length data	
		movwf	TEMP1			; copy number of data bytes into Temp-Counter
		decf    TEMP1, f                ; decrement number of data bytes by one, because 
                                                ; TEMP2 register is preloaded
		
		movf	INDF, w			; copy first data byte into w-register
		movwf	TEMP2			; copy first data byte into temp register
NextCalc	incf	FSR, f			; point to next data memory location
		movf	INDF, w			; move data into w-register
		addwf	TEMP2, f		; add data byte to temp and store in temp
		btfsc	STATUS, C		; add with carry?
		incf	TEMP2, f		; yes, increment TEMP
		decfsz	TEMP1, f		; decrement bit counter
		goto	NextCalc		; calculate next
		
		btfss	TEMP_TRANSFER, 0	; check if checksum has to be generated or checked
		goto	CRCCheck
		
		comf	TEMP2, f		; complement CRC value
		movf	TEMP2, w		; copy checksum into w-register
		incf	FSR, f			; point to location for checksum
		movwf	INDF			; copy checksum behind
                return                          ; return from subroutine		

CRCCheck	incf	FSR, f			; point to CRC byte
                movf	INDF, w			; copy received CRC value into w-register
		addwf	TEMP2, w		; add received CRC to calculated CRC
		xorlw	0xff			; Result should be 0xFF after XOR result is zero
		btfss	STATUS, Z		; is result zero?
		retlw	CRC_ERROR		; return with CRC_ERROR
		retlw	CRC_OK
		
		; ***********************************************************************
		; * LIN ID parity generation 						*
		; ***********************************************************************
CheckParityBits	movf 	ID_TEMP, w		; copy ID value into w-register
		; calculate P0
		
		movwf	TEMP1			; move ID value into TEMP1
		movwf	TEMP2			; move ID into TEMP2
		rrf	TEMP1, f		; rotate ID_TEMP one to the right (get ID1)
		movf	TEMP1, w		; copy TEMP1 to w
		xorwf	TEMP2, f		; TEMP2=ID0 XOR ID1
		rrf	TEMP1, f		; get ID2
		movf	TEMP1, w		; copy ID2 into w-register
		xorwf	TEMP2, f		; TEMP2= TEMP2 XOR ID2
		bcf	STATUS, C		; clear carry flag
		rrf	TEMP1, f		; get ID3 into bit 0
		rrf	TEMP1, w		; ID4 into bit 0 and store result in w-register
		xorwf	TEMP2, f		; TEMP2 = TEMP2 XOR ID4

		btfsc	TEMP2, 0		; test if bit is zero or one
		goto	CheckP0			; check if received P0=1
		btfss	ID_TEMP, 6		; Check if received P0=0
		goto	CalcP1			;
		retlw	PARITY_ERROR_P0		; parity error occured

CheckP0		btfss	ID_TEMP, 6		; check if P0 to 1
		retlw	PARITY_ERROR_P0		; P1=0 therefore parity error occrured
		
		; calculate P1
CalcP1		movf	ID_TEMP, w		; copy ID_TEMP into w-register
		movwf	TEMP1			; copy ID_TEMP into TEMP1
		movwf	TEMP2			; and TEMP2
		rrf	TEMP2, f		; ID1 into bit0
		rrf	TEMP1, f		; ID1 into bit0
		rrf	TEMP1, f		; ID2 into bit0
		rrf	TEMP1, f		; ID3 into bit0
		movf	TEMP1, w		; copy TEMP1 into w-register
		xorwf	TEMP2, f		; TEMP2 = ID1 XOR ID3
		rrf	TEMP1, f		; ID4 into bit0
		movf	TEMP1, w		; TEMP1 into w-register
		xorwf	TEMP2, f		; TEMP2 = TEMP2 XOR ID4
		rrf	TEMP1, w		; ID5 into bit0
		xorwf	TEMP2, f		; TEMP2 = TEMP2 XOR ID5
		comf	TEMP2, f		; negate TEMP2
		
		btfsc	TEMP2, 0		; check if P1=1
		goto	CheckP1			; check if received P1=1 
		btfsc	ID_TEMP, 7		; check if P1=0
		retlw	PARITY_ERROR_P1		; received P1 is not 0 therefore parity error
		retlw	PARITY_OK		; received P1 is 0 therefore no parity error

CheckP1		btfss	ID_TEMP, 7		; set P1		
		retlw	PARITY_ERROR_P1		; parity error occured
testb		retlw	PARITY_OK		; no parity error occured


