; SDI-12 transmitter - accepts commands from the PC, buffers them, then sends them out via SDI-12
; Received data is passed back directly to PC, bypassing this code/hardware entirely.
; Version 1, sjm, 31 May 2010

	list		p=16f688
	#include	<P16F688.inc>
	errorlevel	-302
	radix		decimal	

	__CONFIG	_CP_OFF & _CPD_OFF & _BOD_OFF & _PWRTE_ON & _WDT_OFF & _INTRC_OSC_NOCLKOUT & _FCMEN_OFF & _IESO_OFF & _MCLRE_OFF


	cblock 0x20 		; (Maxmium of 5Fh or 95d bytes available)
		temp1: 1
		temp2: 1
		data_buff: 16		; data buffer
	endc


; IO pin definitions
#define	DIR_SW		PORTC,3		; Direction switch
#define	SPARE1_LED	PORTC,2		; 
#define WAIT_LED	PORTC,1		; 
#define BREAK_LED	PORTC,0
#define SPARE2_LED	PORTA,2

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	ORG	0x0

; setup IO pins
	bsf		STATUS,RP0		; bank 1
	clrf	ANSEL			; digital IO
	movlw	b'00111011'		; port A pins = inputs, except 2  = output
	movwf	TRISA
	movlw	b'00010000'		; all port C pins = outputs except 5, = uart rx
	movwf	TRISC
	bcf		STATUS,RP0		; bank 0
	movlw	0x07
	movwf	CMCON0

; setup serial port
	bsf		BAUDCTL,BRG16	; use 16 bit baud rate generator
	bcf		TXSTA,BRGH		; use low speed baud rate
	movlw	d'207'			; 207=1200 baud
	movwf	SPBRG
	; transmitter
	bcf		TXSTA,SYNC		; setup async operation
	bsf		TXSTA,SPEN		; configure pin as uart tx
	bsf		TXSTA,TXEN		; enable transmitter
	; receiver
	bsf		RCSTA,SPEN		; configure pins as uart rx/tx
	bcf		RCSTA,SYNC		; setup async operation
	bsf		RCSTA,CREN		; enable receiver

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
start:
	bsf		SPARE1_LED			; Turn off ? LED
	bsf		WAIT_LED			; Turn off wait_time LED
	bsf		BREAK_LED			; Turn off BREAK LED
	bsf		SPARE2_LED			; Turn off ? LED

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

read_cmd_from_logger:
	bsf		DIR_SW			; change switch direction to receive (HI-Z SDI-12 bus)
	movlw	data_buff	
	movwf	FSR
get_cmd_byte:
	call 	receive_byte
	movwf	INDF			; save into data buffer
	incf	FSR,f
	sublw 	'!'				; is it end of command?
	btfss 	STATUS,Z		; Yes, skip
	goto 	get_cmd_byte	; No
send_cmd_to_sensor:
	bcf		DIR_SW			; change switch dir to transmit (micro drives SDI-12 bus)
SC_top:
	call	send_break		; This will take 12 ms 
	call	wait_10ms		; Mark after sending break
	movlw	data_buff		; load pointer
	movwf	FSR
SC1:
	movf	INDF,w
	call	send_byte
	movf	INDF,w			; get same char again
	incf	FSR,f
	sublw	'!'				; End of command?
	btfss	STATUS,Z		; Yes, skip
	goto	SC1				; No, get next char
	goto 	read_cmd_from_logger	; back to top of main loop





;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Subroutines follow - functions that are called, and return, 
; as opposed to code that jumps or branches. These functions all
; alter the stack.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Send a byte via the UART
; Data to be sent must be in W as an 8 bit character
; Parity is calculated manually, resulting in 7E1
send_byte:
	btfss	TXSTA,TRMT		; PIR1,TXIF
	goto 	send_byte
	call	calc_parity		; since PIC doesn't support 7E1
	movwf	TXREG
wait_for_sent:
	btfss	TXSTA,TRMT
	goto	wait_for_sent
	return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Receive an 8 bit character from the UART, encoded as 7E1
; Partity bit is ignored, leaving a 7 bit character
; Data received is returned in W
receive_byte:
	btfss	PIR1,RCIF		; received a char yet?
	goto	receive_byte	; No, keep looking
	btfss	RCSTA,FERR		; Was there a framing error? (ie, Break)
	goto	bypass_break	; No, treat it as a normal char
	bcf		BREAK_LED		; Turn on LED to indicate Break
	movf	RCREG,W			; flush RX FIFO, and ...
	goto	receive_byte	; ... look for next char
bypass_break:
	bsf		BREAK_LED		; Turn off LED
	movf 	RCREG,W
	andlw	0x7F		; mask out parity bit
	return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Calculates parity of a 7 bit value and puts parity bit in 8th position
; Byte to process should be passed in W, result is returned in W
; Must clear carry bit before rotating
calc_parity:
	movwf 	temp1
	bcf		temp1,7		; start with even parity, set this bit for odd
	swapf	temp1,w
	xorwf	temp1,w
	movwf	temp2
	bcf		STATUS,C	
	rlf		temp2,f
	bcf		STATUS,C	
	rlf		temp2,f
	xorwf	temp2,f
	bcf		STATUS,C	
	rlf		temp2,w
	xorwf	temp2,w
	andlw	0x80
	iorwf	temp1,w
	return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Send a break sequence on the serial line. 
; This is required by the SDI-12 standard to wake up the sensor.
; Assuming TXEN is already enabled - saves an instruction
; BREAK confuses the PC UART, don't send when testing with PC
send_break:
;	return	; TEMPORARY - only for PC UART testing - REMOVE for production!

	; change the baud rate to a non-standard value, in order to get
	; a 12ms break period
	movlw	d'230'		; 230 gives exactly 12.0ms break 
	movwf	SPBRG

	bsf		TXSTA,SENDB	; enable break mode
	movwf	TXREG		; write anything to start transmission

wait_break:				; wait for break to finish
	btfss	TXSTA,TRMT
	goto	wait_break

	movlw	d'207'		; reset baud rate 207=1200 baud
	movwf	SPBRG
	return


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SDI-12 standard requires marking periods of at least 8.33ms after 
; sending a break, and before issuing a response. This loop takes Xms.
wait_10ms:
	movlw	11
	movwf	temp2
w1:
	movlw	250
	movwf	temp1
w2:	
	decfsz	temp1,f
	goto w2
	decfsz	temp2,f
	goto w1
	return


	end
