
        PROCESSOR	16F84
        include	"P16F84.inc"
        
        __config	_CP_OFF & _PWRTE_ON & _WDT_ON & _XT_OSC
        
        radix	dec    ; decimal numbers by default
        
cur_reg	set	0x0c   ; first general-purpose register

register	macro	name
name	equ	cur_reg
cur_reg	set	cur_reg + 1
	endm
	
	
MY_CIV_ADDR	equ	0x22   ; should not conflict with the address of any ICOM radio
CIV_PREAMBLE	equ	0xfe
CIV_EOM		equ	0xfd
CONTROLLER_CIV_ADDR  equ	0xe0
CIV_OK		equ	0xfb
CIV_NG		equ	0xfa

WRITE_RELAY_CMD	equ	0x05
READ_RELAY_CMD	equ	0x03

; after 120 seconds without a relay-setting command,
; we time out and turn them all off.

TIMEOUT_CONST = (120 * 1000000 / 65536) - 1


CIV_BIT	equ	4	; Bit 4 (of port B) is the CIV interface line
LED_BIT equ	5	; Bit 5 (of port B) is connected to a LED
			; Bits 6 and 7 of port B are reserved for in-circuit programming

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	org	0
reset:
 	goto	start
 	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
	register cur_cmd
	register cur_src_addr
	register cur_dest_addr
	register cur_arg
	register cur_tmp
	register cur_arg_cnt
	
	register timeout_counter_msb
	register timeout_counter_lsb
	register timeout_flag

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


	org	5
start:
	bsf	STATUS,RP0  ;switch to bank 1
        
        ; Options: 1:256 prescaler on timer, driven by internal clock
        movlw	1<<PS0 | 1<<PS1 | 1<<PS2
        movwf	OPTION_REG
        
        movlw	B'11010000'
        movwf	TRISB	; set pins B0..B3 and B5 as output
        
        bcf	STATUS,RP0  ; go back to bank 0
        
        ; turn off all interrupts.  Note that the T0IF bit will
        ; get set if and when the timer overflows.
        movlw	0
        movwf	INTCON    

	clrf	PORTB	; clear all relays and the CIV I/O bit, and turn on the LED
	
	call	Reset_timeout	
	
	movlw	250
	call	Delaynms  ; let the LED blink for 250mS as a power-up indication.
	

         
look_for_cmd:
	; Turn off the LED that says we are processing a command (a 0 value = LED on)
	bsf	PORTB,LED_BIT

	call	Getchar
	sublw	CIV_PREAMBLE
	btfss	STATUS,Z	; see if W == CIV_PREAMBLE
	goto	look_for_cmd	; keep looking if not
	
got_first_preamble:
	call	Getchar		; look for second CIV_PREAMBLE
	sublw	CIV_PREAMBLE
	btfss	STATUS,Z
	goto	look_for_cmd	; start all over if we get something else
	
got_second_preamble:
	call	Getchar		; get what should be the destination address
	movwf	cur_dest_addr
	sublw	CIV_PREAMBLE
	btfsc	STATUS,Z
	goto	got_second_preamble ; got more than two preambles; treat as if we got two
	
	call	Getchar		; get what should be the source address
	movwf	cur_src_addr
	sublw	CIV_PREAMBLE
	btfsc	STATUS,Z
	goto	got_first_preamble  ; if we see a preamble, start over (truncated cmd).
	
	call	Getchar		; get what should be the command code.
	movwf	cur_cmd
	sublw	CIV_PREAMBLE
	btfsc	STATUS,Z
	goto	got_first_preamble  ; if we see a preamble, start over (truncated cmd).
	
	clrf	cur_arg_cnt
	
	call	Getchar		; look for either command arg or EOM flag.
	movwf	cur_arg
	sublw	CIV_PREAMBLE
	btfsc	STATUS,Z
	goto	got_first_preamble  ; if we see a preamble, start over (truncated cmd).
	
	movlw	CIV_EOM
	subwf	cur_arg,W
	btfsc	STATUS,Z
	goto	process_cmd	; we have found a syntactically correct zero-arg cmd.

eat_args:
	incf	cur_arg_cnt
	
	call	Getchar		; eat up remaining args until EOM is found.
	movwf	cur_tmp
	sublw	CIV_PREAMBLE
	btfsc	STATUS,Z
	goto	got_first_preamble  ; if we see a preamble, start over (truncated cmd).
	
	movlw	CIV_EOM
	subwf	cur_tmp,W
	btfsc	STATUS,Z
	goto	process_cmd	; we have found a syntactically correct one-or-more-arg cmd.
	
	goto	eat_args	; keep looking and eating until there are no more args.

process_cmd:
	; see if the command is addressed to us
	
	movf	cur_dest_addr,W
	sublw	MY_CIV_ADDR
	btfss	STATUS,Z
	goto	look_for_cmd	; not addressed to us: ignore the command.

	; Turn on the LED that says we are processing a command (a 0 value = LED on)
	bcf	PORTB,LED_BIT
		
	; Branch to the correct command handler
	movf	cur_cmd,W
	sublw	WRITE_RELAY_CMD
	btfsc	STATUS,Z
	goto	write_relay_cmd
	
	movf	cur_cmd,W
	sublw	READ_RELAY_CMD
	btfsc	STATUS,Z
	goto	read_relay_cmd

bad_cmd:			; commands goto here when they have a problem
	call	Send_ng		; unrecognized command

done_with_cmd:			; commands goto here when finished OK
	goto	look_for_cmd	; end of main processing loop
	
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	
	
write_relay_cmd:
	movf	cur_arg_cnt,W
	sublw	1
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly one arg
	
	movf	cur_arg,W
	andlw	0xf0
	btfss	STATUS,Z
	goto	bad_cmd		; make sure arg is of form B'0000xxxx'
	
	movf	cur_arg,W	; everything is OK, so process it
	movwf	PORTB		; send to relays. Note that we should leave
				; the LED (connected to bit 5) turned on (low).
	
	call	Reset_timeout	; reset the timeout system

	; Acknowledge the command
	call	Send_ok
	
	goto	done_with_cmd   ; all done
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	
	
read_relay_cmd:
	movf	cur_arg_cnt,F	; test the arg cnd
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly zero args

	; Send the reply
	
	call	Wait_civ_idle
	movlw	CIV_PREAMBLE
	call	Putchar
	movlw	CIV_PREAMBLE
	call	Putchar
	movf	cur_src_addr,W
	call	Putchar
	movlw	MY_CIV_ADDR
	call	Putchar
	movlw	READ_RELAY_CMD
	call	Putchar
	
	movf	PORTB,W		; get current relay values
	andlw	0x0f		; clear the irrelevant bits
	
	btfsc	timeout_flag,0
	iorlw	0x10		; Put the timeout status in bit 4
	
	call	Putchar		; send the data
	
	movlw	CIV_EOM
	call	Putchar
	
	goto	done_with_cmd   ; all done
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Wait_civ_idle:
	movlw	10
	call	Delaynms	; wait until CIV line is clear
	return

Send_ok:
	call	Wait_civ_idle
	movlw	CIV_PREAMBLE
	call	Putchar
	movlw	CIV_PREAMBLE
	call	Putchar
	movf	cur_src_addr,W
	call	Putchar
	movlw	MY_CIV_ADDR
	call	Putchar
	movlw	CIV_OK
	call	Putchar
	movlw	CIV_EOM
	call	Putchar
	return
	
Send_ng:
	call	Wait_civ_idle
	movlw	CIV_PREAMBLE
	call	Putchar
	movlw	CIV_PREAMBLE
	call	Putchar
	movf	cur_src_addr,W
	call	Putchar
	movlw	MY_CIV_ADDR
	call	Putchar
	movlw	CIV_NG
	call	Putchar
	movlw	CIV_EOM
	call	Putchar
	return
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


Delay1ms:
	movlw	200
dly1loop:
	clrwdt		;we want 5 cycles in the loop (for 4 MHz clock)
	addlw	-1	;decrement w
	btfss	STATUS,Z
	goto	dly1loop ;loop if w not zero yet
	return


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Delaynms:
	; Delay the number of mS specified in W
	movwf	nms_count
dlynloop:
	call	Delay1ms
	decfsz	nms_count,1
	goto	dlynloop
	return
	
	
	register	nms_count

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; This is called whenever there is nothing better to do.
; It does the timeout processing

Timer_check:
	clrwdt			; clear watchdog timer
	btfss	INTCON,T0IF	; see if the timer has overflowed
	return			; return if not
	
	bcf	INTCON,T0IF	; clear the overflow flag (counter itself wraps)
	movlw	1
	subwf	timeout_counter_lsb
	btfsc	STATUS,C
	return			; return if LSB of timeout counter has not wrapped past zero
	
	; If LSB has reached zero, decrement the MSB
	movlw	1
	subwf	timeout_counter_msb
	btfsc	STATUS,C
	return			; return if MSB has not reached zero
	
	; If any relays are on, and we have timed out, turn off all
	; the relays and note the fact.
	movlw	0x0f
	andwf	PORTB,W
	btfsc	STATUS,Z
	goto	relays_are_off
	
	bcf	PORTB,0		; Take care not to affect the CIV or LED bits
	bcf	PORTB,1
	bcf	PORTB,2
	bcf	PORTB,3
	
	bsf	timeout_flag,0	; set the "timed out" flag

relays_are_off:
	return  		;let the counter keep running- no harm

; This completely resets the timeout system
Reset_timeout:
	clrf	timeout_flag
	clrf	TMR0
	bcf	INTCON,T0IF	
	movlw	HIGH(TIMEOUT_CONST)
	movwf	timeout_counter_msb
	movlw	LOW(TIMEOUT_CONST)
	movwf	timeout_counter_lsb
	
	return
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Sent the bit in the Carry flag out to the CIV port.
; Timing: 9 cycles, including the return.

CIV_out
	btfss	STATUS,C   ; Test the carry flag
	goto    CIV_zero
	nop		   ; timing tweak
	

; This sends a "one" or high value to the CIV port.  
; Since the CIV bus is open-collector, this is really a tristate output.
; Timing: 6 cycles, including the return.

CIV_one
	nop		    ; make this routine have the same cycle count as CIV_zero
	bsf	STATUS,RP0  ;switch to bank 1
	bsf	TRISB,CIV_BIT
	bcf	STATUS,RP0  ;switch to bank 0
	return
	

; This sends a zero by turning off the tristate.
; Timing: 6 cycles, including the return.

CIV_zero
	bcf	PORTB,CIV_BIT
	bsf	STATUS,RP0  ;switch to bank 1
	bcf	TRISB,CIV_BIT
	bcf	STATUS,RP0  ;switch to bank 0
	return
	
;

;************************  Data RAM Assignments  **********************
;
	register	_rcv_reg      ; Data received
	register	_xmt_reg    ; Data to be transmitted
	
	register	_ser_count   ; Counter for #of Bits Transmitted
	register	_ser_dly   ; counter for various delays
	
;***********************************************************************
;
;

;*************************************************************************
; Bit cycle counts (BCCs) for 4MHz clock:
; 19200		52
; 9600		104
; 4800		208
; 1200		833	;bigger than 256...

RX_DLY_CONST1  equ     16  	; for start bit:  3*RX_DLY_CONST1 + 5 = BCC/2
RX_DLY_CONST2  equ     32	; for other bits: 3*RX_DLY_CONST2 + 8 = BCC

Getchar:
	clrf    _rcv_reg          ; Clear all bits of _rcv_reg
_gc_loop1:
	call	Timer_check	; deal with timer while waiting
	btfss	PORTB,CIV_BIT	; wait until line is idle (from previous char)
	goto	_gc_loop1
	
_gc_loop2:
	; Note: Then there is nothing to do, we sit in this loop possibly forever.
	call	Timer_check	; deal with timer while waiting
	btfsc   PORTB,CIV_BIT       ; wait for a (zero) Start Bit
	goto    _gc_loop2 
	
	; wait until we are at the middle of the start bit
	movlw   RX_DLY_CONST1
	movwf   _ser_dly
_gc_loop3:
	decfsz  _ser_dly
	goto    _gc_loop3
	
	movlw   8               ; 8 Data bits
	movwf   _ser_count
	
_gc_next:
	bcf     STATUS,C
	rrf     _rcv_reg   	; shift right, leaving MSB clear
	

	; wait until we are at the middle of the next bit
	movlw   RX_DLY_CONST2
	movwf   _ser_dly
_gc_loop4:
	decfsz  _ser_dly
	goto    _gc_loop4
	
	btfsc   PORTB,CIV_BIT	; read the bit
	bsf     _rcv_reg,7	; set MSB if CIV port is a one
	
	decfsz  _ser_count
	goto    _gc_next	; do this 8 times
	
	movf	_rcv_reg,W	;return with character in W
	
	return
	
	
;****************************************************
;       Transmitter


TX_DLY_CONST1  equ     31	; for 4MHz and 9600 baud
TX_DLY_CONST2  equ     29	; for 4MHz and 9600 baud
TX_DLY_CONST3  equ     35	; for 4MHz and 9600 baud


Putchar:
	movwf	_xmt_reg		; put parameter in _xmt_reg
	
	movlw   8
	movwf   _ser_count
;
	call	CIV_zero       ; Send Start Bit

	movlw   TX_DLY_CONST1  ; Wait exactly one bit time
	movwf   _ser_dly
pc_loop_1:
	decfsz  _ser_dly
	goto    pc_loop_1
	
_pc_next:
	rrf	_xmt_reg	; shift next bit into carry
	call	CIV_out
	
	movlw   TX_DLY_CONST2	; Wait exactly one bit time
	movwf   _ser_dly
pc_loop_2:
	decfsz  _ser_dly
	goto    pc_loop_2

	decfsz  _ser_count
	goto    _pc_next
	
	call	CIV_one       ; Send Stop Bit
	movlw   TX_DLY_CONST3  ; Wait exactly one bit time
	movwf   _ser_dly
pc_loop_3:
	decfsz  _ser_dly
	goto    pc_loop_3

	return

;
;   End of Transmission
;



     END


