;
; $Header: C:\\Play\\ham\\antciv/RCS/kenciv.asm,v 1.1 2001/03/21 22:04:47 Doug Exp Doug $
;
; $Log: kenciv.asm,v $
; Revision 1.1  2001/03/21 22:04:47  Doug
; Initial revision
;
;

; Initialized from: C:\\Play\\ham\\antciv/RCS/antciv.asm,v 1.3 2001/02/26 01:34:59 Doug Exp Doug 
;
;

; This program reads CI-V format commands from a serial input
; and translates and relays them to a Kenwood radio through
; another serial port.

        PROCESSOR	16F84
        include	"P16F84.inc"
        
        __config	_CP_OFF & _PWRTE_ON & _WDT_ON & _XT_OSC

	__idlocs	0xa1db
        
        radix	dec    ; decimal numbers by default
	errorlevel	-305     ; Turn off "default destination" msg.
        
;*****************************************************************************

cur_reg	set	0x0c   ; first general-purpose register

; Macro to allocate registers
register	macro	name
name	equ	cur_reg
cur_reg	set	cur_reg + 1
	endm
	

cur_ee_reg	set	0x0   ; first EEPROM register

; Macro to allocate EEPROM registers and initial value
ee_register	macro	name, val
	local	cur_org
cur_org set	$
name	equ	cur_ee_reg
	org	0x2100+name
	de	val
cur_ee_reg	set	cur_ee_reg + 1
	org	cur_org
	endm


; This macro stuffs data byte(s) into the next available EE data register
; The arg can be anything acceptable to the "de" directive.
ee_data	macro	val
	local	cur_org
cur_org set	$
	org	0x2100+name
	de	val
cur_ee_reg	set	$-0x2100
	org	cur_org
	endm


; Macros to turn interrupts on and off
di	macro
	bcf	INTCON,GIE	
	endm
	
ei	macro
	bsf	INTCON,GIE	
	endm

;*******************************************************************************

CIV_PREAMBLE	equ	0xfe
CIV_EOM		equ	0xfd
CONTROLLER_CIV_ADDR  equ	0xe0
CIV_OK		equ	0xfb
CIV_NG		equ	0xfa

MAX_ARGS	equ	5   ; Max legal number of args, and size of arg array


; Port B bit assignments
			; The RX and Tx signals use negative logic.
			;
KEN_TXD_BIT equ 0	; Serial data in from radio (DIN pin 2).
KEN_RXD_BIT equ	1	; Serial data out to radio (DIN pin 3).
			;
			; The flow control signals use positive logic:
			; a one means it is OK to xmit.
			;
KEN_CTS_BIT equ 2	; Clear-to-send output to radio (DIN pin 4).
KEN_RTS_BIT equ	3	; Request-to-send input from radio (DIN pin 5).
			; Note: DIN pin 1 should be grounded.

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


;*******************************************************************************

	; Our CIV address stored in EEPROM for easy updating
	ee_register my_civ_addr, 0x23

	register cur_cmd
	register cur_src_addr
	register cur_dest_addr
	register cur_tmp
	register cur_arg_cnt

	; Make sure that MAX_ARGS matches the size of this array!
	register cur_arg0
	register cur_arg1
	register cur_arg2
	register cur_arg3
	register cur_arg4
	
	register flags

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; reset vector
	org	0
        
	bsf	STATUS,RP0  ;switch to bank 1

        ; Options: 1:256 prescaler on watchdog timer.
	; We want a WDT timeout of at least a second.
        movlw	1<<PS0 | 1<<PS1 | 1<<PS2 | 1<<PSA
        movwf	OPTION_REG

 	goto	start2		; hop over interrupt vector

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; interrupt vector
	org	4
	goto	interrupt

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	org	5
start2:
        movlw	B'11011001'
        movwf	TRISB	; set pins B1, B2, and B5 as output

	bcf	EECON1,WRERR
	bsf	EECON1,EEIF  ; This will be cleared at the beginning of every 
			     ; data EEPROM write, and it will go high when
			     ; the write has finished.
        
        bcf	STATUS,RP0  ; go back to bank 0
        
        ; turn off all interrupts.  
        clrf	INTCON    

	clrf	PORTB	; clear all relays and the CIV I/O bit, and turn on the LED

	; Blink the LED a few times.
	
	movlw	2
	movwf	tmp_1  
blink_loop:
	bcf	PORTB,LED_BIT
	movlw	100
	call	Delaynms  ; let the LED blink on for a bit as a power-up indication.
	bsf	PORTB,LED_BIT
	movlw	100
	call	Delaynms  ; let the LED blink off for a bit.

	decfsz  tmp_1
	goto    blink_loop
	
	; Turn on and leave on the CTS signal, telling the Kenwood radio
	; that it can send data.
	bsf	PORTB,KEN_CTS_BIT

	; Wait a bit for the CTS signal to stabilize
	movlw	1
	call	Delaynms 

        ; Start the main loop

look_for_cmd:
done_with_cmd:			; commands goto here when finished OK
	; Turn on the LED. It blinks off to indicate a command being processed.
	bcf	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

	movlw	my_civ_addr
	call	read_ee		; Get our CIV address from EEPROM
	subwf	cur_dest_addr,W	; Compare to what was received
	btfss	STATUS,Z
	goto	look_for_cmd	; Not addressed to us; start looking again
	
	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).
	
	; Args should follow now.
	clrf	cur_arg_cnt
	movlw	cur_arg0
	movwf	FSR		; Set indirect pointer to first arg location
	
grab_args:
	
	call	Getchar		; grab remaining args until EOM is found,
				; or we have too many (max 6).
	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 cmd.
	
	; Got an arg
	incf	cur_arg_cnt

	; See if we already have the max legal number of args
	movf	cur_arg_cnt,W
	sublw	MAX_ARGS      ; See if cur_arg_cnt > MAX_ARGS
	btfss	STATUS,C
	goto	grab_args     ; too many args; throw it away.


	; Put the arg in the the arg array pointed to by FSR
	movf	cur_tmp,W	; i.e.: *FSR++ = W
	movwf	INDF
	incf	FSR

	goto	grab_args	; keep looking and grabbing until there are no more args.

	
process_cmd:
	; Turn off the LED indicate we are processing a command
	; (a 0 value = LED on)
	bsf	PORTB,LED_BIT
		
	; Branch to the correct command handler
	movf	cur_cmd,W
	sublw	MAX_CMD_NUM
	btfss	STATUS,C
	goto	bad_cmd		; Command number out of range
	
	; Use jump table to branch to correct command
	; Be sure that the end of the jump table is in same 256-byte
	; memory block as this code!!!
	movf	cur_cmd,W
	addwf	PCL	; Command table must immediately follow this instr!!!
	
cmd_jump_table:
	goto	bad_cmd			; Cmd number 0
	goto	bad_cmd
	goto	bad_cmd
	goto	read_freq_cmd
	goto	read_mode_cmd
	goto	set_freq_cmd		; Cmd number 05
	goto	set_mode_cmd
	goto	set_vfo_cmd
	goto	bad_cmd
	goto	bad_cmd
	goto	bad_cmd
	goto	bad_cmd
	goto	bad_cmd
	goto	bad_cmd
	goto	set_scan_cmd
	goto	set_split_cmd			; Cmd number 0F

	goto	reset_cmd		; Cmd number 0x10
	goto	read_reg_cmd
	goto	set_reg_cmd
	goto	read_ee_reg_cmd
	goto	set_ee_reg_cmd
	goto	send_raw_cmd
cmd_table_end:

MAX_CMD_NUM	equ	cmd_table_end - cmd_jump_table - 1

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


	
read_freq_cmd:
	movf	cur_arg_cnt,F	; test the arg cnt
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly zero args

	; Query Kenwood for freq
	movlw	'F'
	call	Ken_putchar
	movlw	'A'		; Assume we are using VFO A
	call	Ken_putchar
	movlw	';'
	call	Ken_putchar

	; Read frequency report from Kenwood and convert to BCD
	; in args registers.

	; TODO: Test the received characters
	; Have a way to flush garbage received from the Kenwood.

	call	Ken_getchar	; get the 'F'
	call	Ken_getchar	; get the 'A'

	call	Ken_getfreq

	call	Ken_getchar	; get the ';'
	sublw	';'
	btfss	STATUS,Z
	goto	bad_cmd		; Something went wrong

	; Prepare and send the reply: five bytes with the positions.
	call	send_data_repl_preamble
	call	send_5_args	; send the data
	goto	send_eom_done	; send EOM and we are finished
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	
set_freq_cmd:
	movf	cur_arg_cnt,W
	sublw	5
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly five args
	
	movlw	'F'
	call	Ken_putchar
	movlw	'A'		; Assume we are using VFO A
	call	Ken_putchar

	; Copy the frequency in the CIV args to the Kenwood serial port
	call    Ken_putfreq

	movlw	';'
	call	Ken_putchar


	; Acknowledge the command
	goto	send_ok_done
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	
read_mode_cmd:
	movf	cur_arg_cnt,F	; test the arg cnt
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly zero args

	; Query the Kenwood for its current information
	movlw	'I'
	call	Ken_putchar
	movlw	'F'
	call	Ken_putchar
	movlw	';'
	call	Ken_putchar

	; Read all the reply data, and extract the relevant byte.

	; First skip over 29 bytes of input data
	movlw	29
	movwf	tmp_2
read_mode_loop1:
	call	Ken_getchar
	decfsz	tmp_2
	goto	read_mode_loop1

	call	Ken_getdigit
	; Map the CIV mode code to the Kenwood mode code.
	; Assume the Kenwood sent a legal code value.
	call	reverse_map_mode

	movwf	cur_arg0	; Stash it in the arg0 register.

	; Read 7 more bytes of data from the Kenwood
	call	Ken_getchar
	call	Ken_getchar
	call	Ken_getchar
	call	Ken_getchar
	call	Ken_getchar
	call	Ken_getchar
	call	Ken_getchar

	; Get the last char, which is supposed to be a ';'
	call	Ken_getchar
	sublw	';'
	btfss	STATUS,Z
	goto	bad_cmd		; Something went wrong

	; Send the reply
	call	send_data_repl_preamble
	
	; Send the mode data byte
	movf	cur_arg0,W	; We stashed it here earlier
	call	Putchar		; send the data

	goto	send_eom_done	; send EOM and we are finished
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

set_mode_cmd:

	movf	cur_arg_cnt,W
	sublw	1
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly one arg

	; See of the mode number (in arg0) is in the range of the mode table.
	movf	cur_arg0,W
	sublw	MAX_MODE_NUM
	btfss	STATUS,C
	goto	bad_cmd		; arg value out of range
	
	movlw	'M'
	call	Ken_putchar
	movlw	'D'		; Assume we are using VFO A
	call	Ken_putchar

	; Map the CIV mode code to the Kenwood mode code.
	movf	cur_arg0,W
	call	map_mode

	; Send the mode code to the Kenwood
	call	Ken_putdigit

	movlw	';'
	call	Ken_putchar

	goto	send_ok_done

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; The table for mapping from CIV to Kenwood mode codes

map_mode:
	addwf	PCL
mode_map_table_begin:
	retlw	1	; LSB 
	retlw	2	; USB
	retlw	5	; AM
	retlw	3	; CW
	retlw	6	; FSK
	retlw	4	; FM
	retlw	4	; FM (wide)
mode_map_table_end:

MAX_MODE_NUM	equ	mode_map_table_end - mode_map_table_begin - 1

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; The reverse table: for mapping from Kenwood to CIV mode codes
; Assume that the Kenwood send a legal value in range of 1-6.

reverse_map_mode:
	addwf	PCL
reverse_mode_map_table_begin:
	retlw	255	; Illegal 
	retlw	0	; LSB 
	retlw	1	; USB
	retlw	3	; CW
	retlw	5	; FM
	retlw	2	; AM
	retlw	4	; FSK
reverse_mode_map_table_end:


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

set_vfo_cmd:
	movf	cur_arg_cnt,F	; test the arg cnt
	btfsc	STATUS,Z
	goto	no_vfo_args    ; if 0 args, switch to VFO from memory

	movf	cur_arg_cnt,W
	sublw	1
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have no more than one arg

	; We only support parameter values 0 and 1.
	; There are more values defined for CIV, but there is 
	; no corresponding Kenwood function.

	movf	cur_arg0,W
	sublw	1      		; See if the arg is > 1
	btfss	STATUS,C
	goto	bad_cmd         ; Invalid arg value.

	movlw	'F'
	call	Ken_putchar
	movlw	'N'
	call	Ken_putchar

	; The parameter values of 0 and 1 mean the same for both CIV
	; and Kenwood:  0 = VFO A and 1 = VFO B.
	movf	cur_arg0,W
	call	Ken_putdigit

	movlw	';'
	call	Ken_putchar

	goto	send_ok_done

; Switch to "previously used" VFO.  Unfortunately, there is no
; Kenwood command for this, so we switch to VFO A.
no_vfo_args:
	movlw	'F'
	call	Ken_putchar
	movlw	'N'
	call	Ken_putchar
	movlw	'0'
	call	Ken_putchar
	movlw	';'
	call	Ken_putchar

	goto	send_ok_done

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

set_scan_cmd:
	movf	cur_arg_cnt,W
	sublw	1
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly one arg

	; We only support subcommand values 0 and 1.
	; There are more values defined for CIV, but there is 
	; no corresponding Kenwood function.

	movf	cur_arg0,W
	sublw	1      		; See if the arg is > 1
	btfss	STATUS,C
	goto	bad_cmd         ; Invalid arg value.

	movlw	'S'
	call	Ken_putchar
	movlw	'C'
	call	Ken_putchar

	; The parameter values of 0 and 1 mean the same for both CIV
	; and Kenwood:  0 = scan off and 1 = scan on.
	movf	cur_arg0,W
	call	Ken_putdigit

	movlw	';'
	call	Ken_putchar

	goto	send_ok_done

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

set_split_cmd:
	movf	cur_arg_cnt,W
	sublw	1
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly one arg

	; We only support subcommand values 0 and 1.
	; There are more values defined for CIV, but there is 
	; no corresponding Kenwood function.

	movf	cur_arg0,W
	sublw	1      		; See if the arg is > 1
	btfss	STATUS,C
	goto	bad_cmd         ; Invalid arg value.

	movlw	'S'
	call	Ken_putchar
	movlw	'P'
	call	Ken_putchar

	; The parameter values of 0 and 1 mean the same for both CIV
	; and Kenwood:  0 = split off and 1 = split on.
	movf	cur_arg0,W
	call	Ken_putdigit

	movlw	';'
	call	Ken_putchar

	goto	send_ok_done

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; This commands sends all args to the Kenwood, exactly as given.

send_raw_cmd:
	movlw	cur_arg0   	; Point to first arg
	movwf	FSR

	movf	cur_arg0,W	; initialize counter to number of args
	movwf	tmp_2

_raw_loop:
	movf	INDF,W		; grab the next arg
	call	Ken_putchar
	incf	FSR

	decfsz  tmp_2		; loop the right number of times
	goto	_raw_loop

	goto	send_ok_done

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


read_reg_cmd:
	movf	cur_arg_cnt,W
	sublw	1
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly one arg

	; Send the reply: one byte with the desired register
	call	send_data_repl_preamble
	
	movf	cur_arg0,W
	movwf	FSR
	movf	INDF,W		; grab the desired register
	call	Putchar		; send the data
	
	goto	send_eom_done	; send EOM and we are finished
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


set_reg_cmd:
	movf	cur_arg_cnt,W
	sublw	2
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly two args

	; Send the reply: two bytes: the old and new register values
	call	send_data_repl_preamble
	
	movf	cur_arg0,W
	movwf	FSR
	movf	INDF,W		; grab the desired register
	call	Putchar		; send the old data
	
	movf	cur_arg1,W
	movwf	INDF		; set the register with the new data

	movf	INDF,W		; grab the desired register
	call	Putchar		; send the new data

	goto	send_eom_done	; send EOM and we are finished
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


read_ee_reg_cmd:
	movf	cur_arg_cnt,W
	sublw	1
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly one arg

	; Send the reply: one byte with the desired register
	call	send_data_repl_preamble
	
	movf	cur_arg0,W
	call	read_ee		; get the data from the EEPROM
	call	Putchar		; send the data
	
	goto	send_eom_done	; send EOM and we are finished
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


set_ee_reg_cmd:
	movf	cur_arg_cnt,W
	sublw	2
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly two args

	; Send the reply: two bytes: the old and new register values
	call	send_data_repl_preamble
	
	movf	cur_arg0,W	; get the address parameter
	call	read_ee		; grab the desired register
	call	Putchar		; send the old data
	
	movf	cur_arg1,W	; get the new data value
	movwf	EEDATA

	movf	cur_arg0,W 	; Get the data address
	call	write_ee	; Write the new data to the EEPROM

	movf	cur_arg0,W	; get the address parameter
	call	read_ee		; grab the new value from the register
	call	Putchar		; send the new data

	goto	send_eom_done	; send EOM and we are finished
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	
reset_cmd:
	movf	cur_arg_cnt,F	; test the arg cnt
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly zero args

	call	send_basic_preamble
	movlw	CIV_OK
	call	Putchar
	movlw	CIV_EOM
	call	Putchar
	goto	0		; Effectively restart the processor

	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	
; Subroutine to send the first part of any response.
; Four bytes.

send_basic_preamble:
	; First, wait if necessary for the CIV bus to be idle.
	btfss	PORTB,CIV_BIT
	goto	send_basic_preamble

	movlw	10
	call	Delaynms	; Check it again after a short delay

	btfss	PORTB,CIV_BIT
	goto	send_basic_preamble

	; It seems to really be idle now...
	movlw	CIV_PREAMBLE
	call	Putchar
	movlw	CIV_PREAMBLE
	call	Putchar
	movf	cur_src_addr,W
	call	Putchar
	movlw	my_civ_addr	; get CIV address from EEPROM
	call	read_ee
	goto	Putchar		; return from there

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Subroutine to send the first part of a data response.
; Five bytes; everything up to the data itself

send_data_repl_preamble:
	call	send_basic_preamble
	movf	cur_cmd,W
	goto	Putchar		; return from there

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; This is jumped to when a command finishes and wants
; to send an "OK" message.
send_ok_done:
	call	send_basic_preamble
	movlw	CIV_OK
	call	Putchar
	; Fall into send_eom_done

; This is jumped to when a command finishes and wants
; to send a EOM byte after sending data.
send_eom_done:
	movlw	CIV_EOM
	call	Putchar
	goto	done_with_cmd


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; commands jump to here when they have a problem
bad_cmd:
send_ng_done:
	call	send_basic_preamble
	movlw	CIV_NG
	call	Putchar
	goto	send_eom_done
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; This subroutine sends the arg0 thru arg4 bytes as part of a reply.
send_5_args:
	movf	cur_arg0,W
	call	Putchar		; send the data
	movf	cur_arg1,W
	call	Putchar		; send the data
	movf	cur_arg2,W
	call	Putchar		; send the data
	movf	cur_arg3,W
	call	Putchar		; send the data
	movf	cur_arg4,W
	goto	Putchar		; return from there

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


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
	goto	dlynloop
	return
	
	
	register	nms_count


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Copy the frequency in the CIV args to the Kenwood serial port
; The Kenwood expects an 11-digit ascii number, most significant first.
Ken_putfreq:
	movlw	0		; Most significant digit of frequency
	call	Ken_putdigit

	movlw	cur_arg4	
	movwf	FSR
	call	get_bcd_h
	call	Ken_putdigit
	call	get_bcd_l
	call	Ken_putdigit

	decf	FSR
	call	get_bcd_h
	call	Ken_putdigit
	call	get_bcd_l
	call	Ken_putdigit

	decf	FSR
	call	get_bcd_h
	call	Ken_putdigit
	call	get_bcd_l
	call	Ken_putdigit

	decf	FSR
	call	get_bcd_h
	call	Ken_putdigit
	call	get_bcd_l
	call	Ken_putdigit

	decf	FSR
	call	get_bcd_h
	call	Ken_putdigit
	call	get_bcd_l
	call	Ken_putdigit

	return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; This reads 11 ASCII digits from the Kenwood and places
; them to the CIV args in the correct BCD format.
; Like Ken_getchar, this will simply timeout if not enough
; data is sent from the radio.

Ken_getfreq:
	call	Ken_getdigit	; Discard most significant digit.

	; Pack two ASCII digits of data into each BCD arg register.
	movlw	cur_arg4	
	movwf	FSR
	call	Ken_getdigit
	call	put_bcd_h
	call	Ken_getdigit
	call	put_bcd_l

	decf	FSR
	call	Ken_getdigit
	call	put_bcd_h
	call	Ken_getdigit
	call	put_bcd_l

	decf	FSR
	call	Ken_getdigit
	call	put_bcd_h
	call	Ken_getdigit
	call	put_bcd_l

	decf	FSR
	call	Ken_getdigit
	call	put_bcd_h
	call	Ken_getdigit
	call	put_bcd_l

	decf	FSR
	call	Ken_getdigit
	call	put_bcd_h
	call	Ken_getdigit
	call	put_bcd_l

	return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	; Intra-function temps
	register	tmp_1
	register	tmp_2

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


; Get the lower nibble of [FSR] into W.
; FSR and [FSR] are not changed.
get_bcd_l:
	movf	INDF,W
	andlw	0x0f
	return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Get the upper nibble of [FSR] into W.
; FSR and [FSR] are not changed.
get_bcd_h:
	swapf	INDF,W
	andlw	0x0f
	return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;



; Put W into the lower nibble of [FSR].  The other half of
; [FSR] is not changed.  W should be <= 9.
put_bcd_l:
	andlw	0x0f	; Clear non-significant bits of W
	bcf	INDF,0	; Clear destination bits of [FSR]
	bcf	INDF,1
	bcf	INDF,2
	bcf	INDF,3
	iorwf	INDF  	;  FSR |= W
	return


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Put W into the upper nibble of [FSR].  The other half of
; [FSR] is not changed.  W should be <= 9.
put_bcd_h:
	swapf	INDF		; Do clever swapping trick
	call	put_bcd_l
	swapf	INDF
	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:
_gc_wait_idle:
	clrwdt
	btfss	PORTB,CIV_BIT	; wait until line is idle (from previous char)
	goto	_gc_wait_idle
	
_gc_wait_start_bit:
; Note: When there is nothing to do, we sit in this loop possibly forever.
	clrwdt
	btfsc   PORTB,CIV_BIT       ; test for a (zero) Start Bit
	goto    _gc_wait_start_bit 

_gc_got_start_bit:
	di	; No interrupts while getting the character.
		; The timing will get all messed up.
	
	; delay 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
	
	ei

	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

	di	; No interrupts while sending the character.
		; The timing will get all messed up.

	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

	ei			; (No harm if interrupt causes stop bit to
				; be extended.)

	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


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; BCC = 208 for Kenwood data rate of 4800 baud

KGETC_DLY_CONST1  equ     32  ; for start bit:  3*KGETC_DLY_CONST1 + 5 = BCC/2
KGETC_DLY_CONST2  equ     67  ; for other bits: 3*KGETC_DLY_CONST2 + 8 = BCC

; Note:  The caller is responsible for doing the handshaking.
; Remember that the signal is inverted!
Ken_getchar:
_kgetc_wait_idle:
	btfsc	PORTB,KEN_TXD_BIT	; wait until line is idle (from previous char)
	goto	_kgetc_wait_idle
	
_kgetc_wait_start_bit:
; Note: If the Kenwood never sends anything, we sit in this loop
; possibly forever.  Actually, the WDT will reset us if necessary.
	btfss   PORTB,KEN_TXD_BIT       ; test for a (one) Start Bit
	goto    _kgetc_wait_start_bit 

_kgetc_got_start_bit:
	di	; No interrupts while getting the character.
		; The timing will get all messed up.
	
	; delay until we are at the middle of the start bit
	movlw   KGETC_DLY_CONST1
	movwf   _ser_dly
_kgetc_loop3:
	decfsz  _ser_dly
	goto    _kgetc_loop3
	
	movlw   8               ; 8 Data bits
	movwf   _ser_count
	
_kgetc_next:
	bcf     STATUS,C
	rrf     _rcv_reg   	; shift right, leaving MSB clear
	

	; wait until we are at the middle of the next bit
	movlw   KGETC_DLY_CONST2
	movwf   _ser_dly
_kgetc_loop4:
	decfsz  _ser_dly
	goto    _kgetc_loop4
	
	btfss   PORTB,KEN_TXD_BIT	; read the bit
	bsf     _rcv_reg,7	; set MSB if input bit is a zero (inverted)
	
	decfsz  _ser_count
	goto    _kgetc_next	; do this 8 times
	
	ei

	movf	_rcv_reg,W	;return with character in W
	return
	
	

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Read an ASCII 0-9 character from the Kenwood and convert it
; to a binary value.  If the Kenwood does not feel like sending
; anything, the WDT will eventually time out and reset the CPU.

Ken_getdigit:
	call	Ken_getchar
	addlw	0-'0'		; Subtract ASCII '0' to convert to binary value
	return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


; These numbers are not proportional to the ones for the CIV serial output
; because the actual outputting of the data bit takes less time.
; Also, we want to send two stop bits, so the last delay constant
; is twice as long.

KPUTC_DLY_CONST1  equ     64	; for 4MHz and 4800 baud
KPUTC_DLY_CONST2  equ     63	; for 4MHz and 4800 baud
KPUTC_DLY_CONST3  equ     140	; for 4MHz and 4800 baud


; Send 0-9 value in W to Kenwood as an ASCII digit
Ken_putdigit:
	addlw	'0'		; Convert to ASCII
				; Fall thru to Ken_putchar

; Send char in W to the Kenwood. 
Ken_putchar:
	movwf	_xmt_reg		; put parameter in _xmt_reg

	; Wait for the RTS signal to indicate that the radio is ready
	; If the radio is turned off, etc., the WDT will eventually
	; break us out of this loop.
_kputc_wait_rts:
	btfss	PORTB,KEN_RTS_BIT
	goto _kputc_wait_rts

	
	movlw   8
	movwf   _ser_count

	di	; No interrupts while sending the character.
		; The timing will get all messed up.

	bsf	PORTB,KEN_RXD_BIT       ; Send (inverted) Start Bit

	movlw   KPUTC_DLY_CONST1  ; Wait exactly one bit time
	movwf   _ser_dly
kputc_loop_1:
	decfsz  _ser_dly
	goto    kputc_loop_1
	
_kputc_next:
	rrf	_xmt_reg	; shift next bit into carry
	btfsc	STATUS,C
	bcf	PORTB,KEN_RXD_BIT       ; Send (inverted) one bit
	btfss	STATUS,C
	bsf	PORTB,KEN_RXD_BIT       ; Send (inverted) zero bit

	movlw   KPUTC_DLY_CONST2	; Wait exactly one bit time
	movwf   _ser_dly
kputc_loop_2:
	decfsz  _ser_dly
	goto    kputc_loop_2

	decfsz  _ser_count
	goto    _kputc_next
	
	bcf	PORTB,KEN_RXD_BIT       ; Send (inverted) Stop Bit

	ei			; (No harm if interrupt causes stop bit to
				; be extended.)

	movlw   KPUTC_DLY_CONST3  ; Wait exactly two bit times
	movwf   _ser_dly
kputc_loop_3:
	decfsz  _ser_dly
	goto    kputc_loop_3

	return

;   End of Transmission to Kenwood
;

;******************************************************************************

; Interrupt service routine
interrupt:
	retfie


;******************************************************************************

; Read the data EEPROM byte specified by W into W.
; Waits, if neccessary, if a previous write is still
; in progress.  Valuable side effect: the Z flag is set
; based on the return value.
read_ee:
	movwf	EEADR
	bsf	STATUS,RP0  ;switch to bank 1

read_ee_wait:
	clrwdt
	btfss	EECON1,EEIF	; make sure no write is in progress
	goto	read_ee_wait

	bsf	EECON1,RD
	bcf	STATUS,RP0  ;switch back to bank 0
	movf	EEDATA,W    ;get data
	return

; This writes the data that has been previously stored in the EEDATA
; register to the EEPROM byte specified by W.
; This does NOT wait for the write to finish!!! 
; Call wait_ee_write to do this.
write_ee:
	movwf	EEADR
	bsf	STATUS,RP0  ;switch to bank 1

write_ee_wait:
	clrwdt
	btfss	EECON1,EEIF	; make sure no write is in progress
	goto	write_ee_wait

	di			; critical section
	bcf	EECON1,EEIF
	bsf	EECON1,WREN
	movlw	0x55		; do official voodoo
	movwf	EECON2
	movlw	0xaa
	movwf	EECON2
	bsf	EECON1,WR
	bcf	EECON1,WREN
	bcf	STATUS,RP0  ;switch back to bank 0
	ei
	movlw	20
	; WHY is this necessary???
	goto	Delaynms 	; return from there


; Wait until the EEPROM write has finished.  Returns immediately
; if no write is in progress.
wait_ee_write:
	bsf	STATUS,RP0  ;switch to bank 1
wait_ee_loop:
	clrwdt
	btfss	EECON1,EEIF	; test the "write finished" bit
	goto	wait_ee_loop
	bcf	STATUS,RP0  ;switch back to bank 0
	return

;******************************************************************************

     END
