;
; $Header: C:/PLAY/HAM/ANTCIV/RCS/antciv.asm 2.19 2001/08/09 17:21:20 doug Exp $
; $Log: antciv.asm $
; Revision 2.19  2001/08/09 17:21:20  doug
; Added ADC hard limit checking.  Ready for reelase.
;
; Revision 2.18  2001/08/08 23:46:19  doug
; WDT timeout detection and motor overrun detection added.
;
; Revision 2.17  2001/08/06 23:12:04  doug
; Fixed subtle problem with interrupt service routine context saving.
; Caused crash whenever we got an interrupt while not in bank 0,
; such as during read_ee.
;
; Revision 2.16  2001/08/04 01:04:36  doug
; Some changes to serial I/O to try to improve reliability.
; Still locks up after a while.
;
; Revision 2.15  2001/08/03 22:26:21  doug
; Seems to work OK with interrupt-driven UART and receive buffer.
;
; Revision 2.14  2001/08/03 18:54:36  doug
; Working version with UART and one-char ex buffer.
;
; Revision 2.13  2001/08/03 16:35:58  doug
; Corrected park position.  Last version without UART for CIV interface.
;
; Revision 2.12  2001/08/01 19:03:01  doug
; Az and El work OK with ADC control.  First useable version.
;
; Revision 2.11  2001/07/30 10:22:21  doug
; ADC reading and control logic basically works for AZ.
; AX scale/offset more or less accurate.
;
; Revision 2.10  2001/07/29 03:00:42  doug
; Broken test version
;
; Revision 2.9  2001/07/27 05:28:35  doug
; Start of coding for ADC-based positioning.
;
; Revision 2.8  2001/07/26 18:40:50  doug
; Fixed ADC test code.
; Last timer-based version.
;
; Revision 2.7  2001/07/24 17:44:01  doug
; Properly working 16-bit version with rounding.
; Also converted to old DOS version of RCS.
;
; Revision 2.6  2001/07/19 20:43:21  Doug
; First pass at making position and target data 16-bits
;
; Revision 2.5  2001/07/11 23:06:28  Doug
; Fixed typo
;
; Revision 2.4  2001/07/11 23:01:42  Doug
; Fixed problem with EEIF:  It was on a different register than
; it was on the 16F84.  Program works 100% now on 16F876.
;
; Revision 2.3  2001/07/11 21:14:37  Doug
; Basically works.  Still some problem with EEIF bit.
; Commented out for now in read_ee.
;
; Revision 2.2  2001/07/10 03:04:33  Doug
; Did obvious changes for 16F876: config bits, register banks.
;
; Revision 2.1  2001/07/10 02:43:24  Doug
; First 16F876 version
;
; Revision 1.5  2001/07/10 02:34:43  Doug
; last 16F84 version
;
; Revision 1.4  2001/06/13 16:34:39  Doug
; Reduced az and el slop.
; Tweaked LCD serial output timing.
; Added meaningful park az/el values.
;
; Revision 1.3  2001/02/26 01:34:59  Doug
; Fixed a couple of LCD bugs and shortened Rotor_check code a bit.
;
; Revision 1.2  2001/02/22 06:26:35  Doug
; Reduced code size and stack usage.
; Added basic LCD output routines.
; Added flip and park commands.
;
; Revision 1.1  2001/02/07 16:29:29  Doug
; Initial revision
;
; Revision 1.2  2001/01/30 15:40:24  dbraun
; Basically works
;
; Revision 1.1  2001/01/24 03:08:50  Doug
; Initial revision
;
;

; This program reads CI-V format commands from a serial input
; and controls a set of antenna rotators.

        PROCESSOR	16F876
        include	"P16F876.inc"
        
        __config	_CP_OFF & _PWRTE_ON & _DEBUG_OFF & _WDT_ON & _XT_OSC & _BODEN_ON & _WRT_ENABLE_OFF & _LVP_OFF

	__idlocs	0xa1db
        
        radix	dec    ; decimal numbers by default
	errorlevel	-305     ; Turn off "default destination" msg.
        
;*****************************************************************************

cur_reg	set	0x20   ; first general-purpose register in 16F876 bank 0

; Macro to allocate registers
register	macro	name
name	equ	cur_reg
cur_reg	set	cur_reg + 1
	endm
	
; Macro to allocate a block of registers
reg_block	macro	name, len
name	equ	cur_reg
cur_reg	set	cur_reg + len
	endm
	

cur_ee_reg	set	0x00   ; 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


; This macro defines a character string in data EEPROM.
; "name" will be set to the starting data address.
; "val" is a character string
; A zero byte is added at the end.

ee_string	macro	name, val
	local	cur_org
cur_org set	$
name	equ	cur_ee_reg
	org	0x2100+name
	de	val
	de	0
cur_ee_reg	set	$-0x2100
	org	cur_org
	endm



; This macro defines a special character string in data EEPROM.
; "name" will be set to the starting data address.
; "pos" is a single byte the represents a LCD character address
; "val" is a character string
; A zero byte is added at the end.

ee_pos_string	macro	name, pos, val
	local	cur_org
cur_org set	$
name	equ	cur_ee_reg
	org	0x2100+name
	de	pos
	de	val
	de	0
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


; This macro adds register pair A_h/A_l to B_h/B_l,
; leaving the result in B, and correctly leaving the carry bit set.
; Unfortunately the Z bit is not properly set afterwards...

add16	macro	AH,AL,BH,BL
	; Do 100% kosher 16-bit add with carry-out, as per AN617
	movf	AL,W
	addwf	BL
	movf	AH,W
	btfsc	STATUS,C
	incfsz	AH,W
	addwf	BH
	endm


; This macro adds the immediate 16-bit value A to B_h/B_l,
; leaving the result in B, and correctly leaving the carry bit set.
; Unfortunately the Z bit is not properly set afterwards...

addi16	macro	A,BH,BL
	movlw	LOW(A)
	addwf	BL
	movlw	HIGH(A)
	if (HIGH(A) == 0xff)
	  btfss	STATUS,C
	  addwf	BH
	else
	  btfsc	STATUS,C
	  movlw	HIGH(A)+1
	  addwf	BH
	endif

	endm


; Add an immediate 8-bit value to BH/BL.
; A bit faster than using addi16.

addi0816	macro	A,BH,BL
	movlw	A
	addwf	BL
	movlw	1
	btfsc	STATUS,C
	addwf	BH

	endm


; This macro subtracts register pair A_h/A_l from B_h/B_l,
; leaving the result in B, and correctly leaving the carry bit set.
; Remember that the Carry bit is negated for subtracted.  I.e. C=0
; means that a borrow DID occur.
; Unfortunately the Z bit is not properly set afterwards...

sub16	macro	AH,AL,BH,BL
	; Do 100% kosher 16-bit subtract with carry-out, as per AN617
	movf	AL,W
	subwf	BL
	movf	AH,W
	btfss	STATUS,C
	incfsz	AH,W
	subwf	BH
	endm


; This is similar to the sub16 macro, but A and B are left unchanged.
; The carry bit will be set if A <= B, and cleared if A > B.
; Unfortunately the Z bit is not properly set afterwards...

cmple16	macro	AH,AL,BH,BL
	; Do 100% kosher 16-bit subtract with carry-out, as per AN617
	movf	AL,W
	subwf	BL,W
	movf	AH,W
	btfss	STATUS,C
	incfsz	AH,W
	subwf	BH,W
	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

MAX_ARGS	equ	5   ; Max legal number of args, and size of arg array



; The allowable error in position vs. target, in tenths of degrees
; High byte is assumed to be zero, so max value is 25.5 degrees
AZ_SLOP_L = 30
EL_SLOP_L = 20

; Port A bit assignments
;LCD_BIT	equ	0	; Serial output to LCD

; Port C bit assignments
;
; Simmstik edge connector pin 10 is connected to C3 on the
; DT106 simmstik used for 16F87X processors, and to A0 on the
; old PIC001 simmstik that was used for 16F84s.
;
; Bit C7 is connected to simmstik edge connector pin 2, which is A2 on the
; old PIC001 simmstik that was used for 16F84s.

LCD_BIT	equ	3	; Serial output to LCD:  C3
CIV_OUT_BIT equ	7	; Bit 7 of port C is the CIV output bit,
			; shared with the UART input.

CIV_IN_BIT equ	7	; Bit 7 of port C is the UART input bit.
			; This must be tested, even when the UART is used.

; Port B bit assignments
AZ_DOWN_BIT equ	0
AZ_UP_BIT equ	1
EL_DOWN_BIT equ	2
EL_UP_BIT equ	3
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

AZ_MASK equ	(1<<AZ_UP_BIT)|(1<<AZ_DOWN_BIT)
EL_MASK equ	(1<<EL_UP_BIT)|(1<<EL_DOWN_BIT)

;*******************************************************************************

 
	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
	
	; These are incremented during each timer interrupt, such
	; that they will overflow after about 70 seconds.
	; For a 15.258 Timer0 rate, they should overflow after
	; 1068 incrementations, so the increment should be 61.

TIMER_INCREMENT	=	61

	register az_timer_h
	register az_timer_l
	register el_timer_h
	register el_timer_l

; Various status flags: bit numbers in the flags register.
LOCKED_FLAG	equ	0 ; Set under user control to prevent all movement while still updating LCD
FLIPPED_FLAG	equ	1 ; set under user control to indicate flipped mode
HW_FAIL_FLAG	equ	2 ; set if bad things happen
TIMER_FLAG	equ	3 ; set by timer0 interrupt every 65mS.
NOT_TO_FLAG	equ	4 ; copy of STATUS NOT_TO flag at powerup.

	register flags

	register az_pos_l
	register az_pos_h	; Most significant byte

	register az_target_h	; Target value of position (tenths of degrees).
	register az_target_l	; LSB 

	register el_pos_l
	register el_pos_h

	register el_target_h
	register el_target_l	; LSB 

	; EEPROM registers

	ee_register ee_park_az_h, HIGH(2050)     ; 205 degrees
	ee_register ee_park_az_l, LOW(2050)   ; 205 degrees
	ee_register ee_park_el_h, 0
	ee_register ee_park_el_l, 0

	; Conversion factors for mapping the ADC output (16-bit 0000 to ffc0)
	; to tenths of degrees (0-3600 or 0-1800).
	; Must be tweaked to match hardware.

	; The formula is:  position = (ADC - offset) * scale

	; The offset factor is roughly the lowest
	; ADC value (0 to 65535).  For the NA1DB az rotor this might
	; be about 3000.  For the Yaesu elevation rotor, it will
	; be much smaller.

AZ_OFFSET = 2432
EL_OFFSET = 5470

	ee_register ee_offset_az_h, HIGH(AZ_OFFSET)
	ee_register ee_offset_az_l, LOW(AZ_OFFSET)
	ee_register ee_offset_el_h, HIGH(EL_OFFSET)
	ee_register ee_offset_el_l, LOW(EL_OFFSET)

	; A value of 3600 would scale the max ADC output ffc0 to 3600.
	; In reality the scale factor will be larger, since we do not
	; use the full range of the ADC.

AZ_SCALE = 3944
EL_SCALE = 1962

	ee_register ee_scale_az_h, HIGH(AZ_SCALE)
	ee_register ee_scale_az_l, LOW(AZ_SCALE)
	ee_register ee_scale_el_h, HIGH(EL_SCALE)
	ee_register ee_scale_el_l, LOW(EL_SCALE)

; The LOWER_LIMIT and UPPER_LIMIT values give the absolute limits
; of legal ADC values.  If the ADC output goes beyond them,
; a fatal error happens.  The cause is assumed to be hardware
; failure in the position sensors or cabling.
;
; The lower limit should be about halfway between the ADC's 0-degree
; value (same as OFFSET) and 0x0000.
; The upper limit should be about halfway between the ADC's
; 360 degree value and 0xffc0 (the highest ADC value).

AZ_LOWER_LIMIT = 1200
	ee_register ee_lower_limit_az_h, HIGH(AZ_LOWER_LIMIT)
	ee_register ee_lower_limit_az_l, LOW(AZ_LOWER_LIMIT)
AZ_UPPER_LIMIT = 64000
	ee_register ee_upper_limit_az_h, HIGH(AZ_UPPER_LIMIT)
	ee_register ee_upper_limit_az_l, LOW(AZ_UPPER_LIMIT)

EL_LOWER_LIMIT = 2700
	ee_register ee_lower_limit_el_h, HIGH(EL_LOWER_LIMIT)
	ee_register ee_lower_limit_el_l, LOW(EL_LOWER_LIMIT)
EL_UPPER_LIMIT = 65400
	ee_register ee_upper_limit_el_h, HIGH(EL_UPPER_LIMIT)
	ee_register ee_upper_limit_el_l, LOW(EL_UPPER_LIMIT)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; reset vector
	org	0
 	goto	start2		; hop over interrupt vector

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; interrupt vector
	org	4
	goto	interrupt

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	org	5
start2:
        ; turn off all interrupts.  
        clrf	INTCON    

	bsf	STATUS,RP0  ;switch to bank 1

        ; Options: 1:256 prescaler on Timer0, driven by internal clock.
	; Timer0 is driven from the CPU clock with the divide-by-256
	; prescaler.  For a 4 MHz crystal, this gives us a 15.258 Hz
	; interrupt rate, or ~65.5 mS between interrupts.

        movlw	1<<PS0 | 1<<PS1 | 1<<PS2
        movwf	OPTION_REG

        movlw	B'11111111'
        movwf	TRISA	; Pins A0 and A1 will be analog inputs to ADC.

        movlw	B'11010000'
        movwf	TRISB	; Set pins B0..B3 and B5 as output

        movlw	B'11110111'
        movwf	TRISC	; Set pin C3 as output to LCD.  C7 is input to UART.

	; Set up ADC.
	movlw	B'0000100'	; Specify A0/1/3 as ADC analog inputs,
				; and result left-justified.
	movwf	ADCON1 		

	; Set up the UART baud rate generator
	; (More UART setup below.)
	movlw	25		; For 4 MHz Xtal, BRGH=1, and 9600 baud
	movwf	SPBRG
	bsf	TXSTA,BRGH	; Set High Baud Rate bit

	; Set up the interrupt overflow enable for the UART receiver.
	; This is also controlled by the PEIE and GIE bits in the
	; Bank 0 INTCON register.
	bsf	PIE1,RCIE

	; Set up the interrupt overflow enable for Timer1
	; This is also controlled by the PEIE and GIE bits in the
	; Bank 0 INTCON register.
	; bsf	PIE1,TMR1IE

        bsf	STATUS,RP1  ; go to bank 3 from bank 1

	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
        bcf	STATUS,RP1  ; go back to bank 0
        

	clrf	PORTB	; clear all relays and the CIV I/O bit, and turn on the LED
	clrf	PORTC	; set LCD serial output to idle state.

	clrf	flags

	; Save a copy of the status NOT_TO bit before a clrdwt instruction
	; clears it.
	btfsc	STATUS,NOT_TO
	bsf	flags,NOT_TO_FLAG

	clrf	az_timer_h
	clrf	az_timer_l
	clrf	el_timer_h
	clrf	el_timer_l

	; Initialize the LCD positions with an illegal value,
	movlw	0xff
	movwf	lcd_az_h	
	movwf	lcd_az_l	
	movwf	lcd_el_h
	movwf	lcd_el_l
	
	; Set up Timer 1.  It will wrap around every 512k instructions,
	; or 1.907 Hz.
	; Timer mode, internal clock,  8:1 prescaling
	; movlw	(1<<TMR1ON)|(1<<T1CKPS1)|(1<<T1CKPS0)
	; movwf	T1CON
	

	; Blink the LED a few times.  Allow 750 mS for the LCD to get ready.
	register blink_cnt
	
	movlw	4
	movwf	blink_cnt  ; This is unused by Rotor_check, etc.
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  blink_cnt
	goto    blink_loop

	; Erase the LCD
	movlw	0x01		; "Clear screen" command.
	call	LCD_cmd

	movlw	2
	call	Delaynms  ; Required delay after "clear screen"

	; See if we were reset by a watchdog timeout.
	; If so, display a message and force the user to cycle the power.
	; We saved a copy of the NOT_TO flag right after powerup.
	; Delaynms did a clrwdt, which reset the original flag.
	btfsc	flags,NOT_TO_FLAG
	goto	no_wdt_timeout

	; Display a message on the LCD.
	ee_pos_string	watchdog_msg, 192, "Watchdog!!!"
	movlw	watchdog_msg
	call	LCD_put_ee_pos_str
	goto	fatal_error_loop
no_wdt_timeout:

	
	call	read_az		; Get the very latest positions from the ADC
	call	read_el

	movf	az_pos_h,W
	movwf	az_target_h	; Make target = current pos
	movf	az_pos_l,W
	movwf	az_target_l	; Make target = current pos
	
	movf	el_pos_h,W
	movwf	el_target_h	; Make target = current pos
	movf	el_pos_l,W
	movwf	el_target_l	; Make target = current pos

; LCD screen addresses for printing AZ and EL values.
LCD_AZ_POS = 131
LCD_EL_POS = 139

	ee_pos_string  background1, 128, "AZ=???  EL=???"
	movlw	background1
	call	LCD_put_ee_pos_str

	; Enable the Timer1 Overflow and/or UART interrupts.
	bsf	INTCON,PEIE	
	
	; Enable the Timer0 interrupt.
	bsf	INTCON,T0IE
	bsf	INTCON,GIE

	call	Flush		; Flush the receive buffer.


        ; Start the main loop
done_with_cmd:			; commands goto here when finished OK

	; Activate the UART receiver (it must be turned off when
	; we are not receiving, so Putchar can control pin C7).
	bsf	RCSTA,SPEN
	bsf	RCSTA,CREN	; Allow UART to receive characters

	; Turn on the LED. It blinks off to indicate a command being processed.
	bcf	PORTB,LED_BIT

look_for_preamble:

	call	Getchar
	sublw	CIV_PREAMBLE
	btfss	STATUS,Z	; see if W == CIV_PREAMBLE
	goto	look_for_preamble	; keep looking if not
	
got_first_preamble:
	call	Getchar		; look for second CIV_PREAMBLE
	sublw	CIV_PREAMBLE
	btfss	STATUS,Z
	goto	look_for_preamble  ; 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

	movf	cur_dest_addr,W
	sublw	MY_CIV_ADDR
	btfss	STATUS,Z
	goto	look_for_preamble ; 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

	; Turn off UART receiver (so we don't receive our own transmissions).
	bcf	RCSTA,CREN
	; Completely turn off the UART so Putchar can control pin C7.
	bcf	RCSTA,SPEN
		
	; 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_position_cmd
	goto	read_status_cmd
	goto	goto_cmd		; Cmd number 05
	goto	read_target_cmd
	goto	stop_cmd
	goto	lock_cmd		; old calibrate command
	goto	unlock_cmd		; old uncalibrate command
	goto	flip_cmd
	goto	park_cmd
	goto	bad_cmd
	goto	bad_cmd
	goto	bad_cmd
	goto	bad_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	read_adc_cmd		; Cmd 0x15
	goto	test_watchdog_cmd
	goto	test_timer_cmd
cmd_table_end:

MAX_CMD_NUM	equ	cmd_table_end - cmd_jump_table - 1

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	
goto_cmd:
	movf	cur_arg_cnt,W
	sublw	5
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly five args
	
	btfsc	flags,HW_FAIL_FLAG	; If we are timed-out, give an error return
	goto	bad_cmd

	call	args_to_targets  ;Convert the arg format, and put in the targets

	; TODO: error checking of parameter values...

	; The rotors will start (if necessary) when Rotor_check is called,
	; by Putchar before it sends the first reply byte.

	; Acknowledge the command
	goto	send_ok_done
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	
read_status_cmd:
	movf	cur_arg_cnt,F	; test the arg cnt
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly zero args

	; Send the reply: two bytes with the relay bits and flags.
	call	send_data_repl_preamble
	
	movf	flags,W
	andlw	0x07		; mask off unused flag bits
	call	Putchar		; send the flags
	
	movf	PORTB,W		; get current relay values
	andlw	0x0f		; clear the irrelevant bits
	call	Putchar		; send the realy values
	
	goto	send_eom_done	; send EOM and we are finished
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


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
	call	set_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
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	
read_position_cmd:
	movf	cur_arg_cnt,F	; test the arg cnt
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly zero args

	; Prepare and send the reply: five bytes with the positions.
	call	send_data_repl_preamble
	call	read_az		; Get the very latest positions from the ADC
	call	read_el
	call	pos_to_args
	call	send_5_args	; send the data
	goto	send_eom_done	; send EOM and we are finished
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	
read_target_cmd:
	movf	cur_arg_cnt,F	; test the arg cnt
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly zero args

	; Prepare and send the reply: five bytes with the targets
	call	send_data_repl_preamble
	call	targets_to_args
	call	send_5_args	; send the data
	goto	send_eom_done	; send EOM and we are finished
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Note: if you activate flip mode, the rotors will not move.
; The current position will be reported in a mapped form.
; So, if the position was az=123, el=45 and you turned on
; mapping, the position would then be reported as az=303, el=135.

flip_cmd:
	movf	cur_arg_cnt,W
	sublw	1
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly one arg

	movf	cur_arg0,W	; get the flip on/off argument
	andlw	0xfe		; make sure parameter is 0 or 1
	btfss	STATUS,Z
	goto	bad_cmd

	bcf	flags,FLIPPED_FLAG
	btfsc	cur_arg0,0		; Test the argument
	bsf	flags,FLIPPED_FLAG	; If arg==1, set the flipped bit

	goto	send_ok_done
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Move to the predefined park location (in EEPROM)

park_cmd:
	movf	cur_arg_cnt,F	; test the arg cnt
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly zero args
	
	btfsc	flags,HW_FAIL_FLAG	; If we are timed-out, give an error return
	goto	bad_cmd

	movlw	ee_park_az_h
	call	read_ee
	movwf	az_target_h
	movlw	ee_park_az_l
	call	read_ee
	movwf	az_target_l

	movlw	ee_park_el_h
	call	read_ee
	movwf	el_target_h
	movlw	ee_park_el_l
	call	read_ee
	movwf	el_target_l

	; The rotors will start (if necessary) when Rotor_check is called,
	; by Putchar before it sends the first reply byte.

	; Acknowledge the command
	goto	send_ok_done
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

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

	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	
stop_cmd:
	;Don't worry about now many args there were.

	; stop!!!
	call	stop_az_el

	; Make the current position the target so no further
	; movement occurs.
	call	pos_to_target

	; The rotors will stop when Rotor_check is called,
	; by Putchar before it sends the first reply byte.

	; Send the reply: Just an OK.
	goto	send_ok_done
	

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

lock_cmd:
	movf	cur_arg_cnt,W
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly zero args

	; stop!!!
	call	stop_az_el

	; Make the current position the target so no further
	; movement occurs if we later unlock
	call	pos_to_target

	bsf	flags,LOCKED_FLAG

	ee_pos_string	locked_msg, 192, "Locked"
	movlw	locked_msg
	call	LCD_put_ee_pos_str

	; Send the reply: Just an OK.
	goto	send_ok_done
	

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

unlock_cmd:
	movf	cur_arg_cnt,W
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly zero args

	; If we are not actually locked, simply return.
	btfss	flags,LOCKED_FLAG
	goto	send_ok_done	

	; Make the current position the target so no sudden
	; movement occurs.
	call	pos_to_target

	bcf	flags,LOCKED_FLAG

	ee_pos_string	unlocked_msg, 192, "      "
	movlw	unlocked_msg
	call	LCD_put_ee_pos_str

	; Send the reply: Just an OK.
	goto	send_ok_done
	

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


; A direct low-level read of the ADC.

read_adc_cmd:
	movf	cur_arg_cnt,W
	btfss	STATUS,Z
	goto	bad_cmd		; make sure we have exactly zero args

	; Send the reply: two bytes with the adc result
	call	send_data_repl_preamble
	
	MOVLW	B'01000001'	; /8 Clock, A/D is on, Channel 0 is selected
	call	read_adc
	movf	data_h,W
	call	Putchar		; send the MSB of the ADC data

	movf	data_l,W
	call	Putchar		; send the LSB of the ADC data
	
	goto	send_eom_done	; send EOM and we are finished
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

test_watchdog_cmd:
	; Simply loop forever with no clrwdt
	goto	test_watchdog_cmd

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

test_timer_cmd:
	; Simply loop forever with a clrwdt
	; If a rotor is running, we should get a motor overrun timeout
	clrwdt
	goto	test_timer_cmd

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Safely copy the current position to the target, effectively
; stopping rotation.

pos_to_target:
	movf	az_pos_h,W	; Copy 16 bits of data
	movwf	az_target_h
	movf	az_pos_l,W
	movwf	az_target_l

	movf	el_pos_h,W
	movwf	el_target_h
	movf	el_pos_l,W
	movwf	el_target_l

	return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; 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.
	call	Rotor_check	; deal with rotation while waiting
	btfss	PORTC,CIV_IN_BIT
	goto	send_basic_preamble

	movlw	10
	call	Delaynms	; Check it again after a short delay

	btfss	PORTC,CIV_IN_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
	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,1
	goto	dlynloop
	return
	
	
	register	nms_count


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; This translates the args from BCD degrees to 0..255 values
; and puts them in the az and el target registers.

; CX6DD's WispDDE Format:
; AZ/EL data format is packed BCD, LS digits first.
; EL*1000 + AZ.  For example, el=45, az=123, becomes 0000045123,
; encoded as:  23 51 04 00 00.  
; The last 2 arg bytes should always be 00.
; Also note that the MS nibble of the AZ and the LS nibble of
; the EL are in the same byte...

args_to_targets:
	call	clear_data	; Build three digits worth of BCD in data_h/l
	movlw	cur_arg0	; Use temporary format: 01 23 00 45 00
	movwf	FSR
	call	accum_bcd_l
	incf	FSR
	call	accum_bcd_h
	call	accum_bcd_l

	call	multx10		; convert from 0-360 to 0-3600

	movf	data_h,W
	movwf	az_target_h	; store as azimuth target
	movf	data_l,W
	movwf	az_target_l

	call	clear_data	; Build three digits worth of BCD in data_h/l
	movlw	cur_arg2	; In the above example, we process 0, 4, then 5
	movwf	FSR
	call	accum_bcd_l
	incf	FSR
	call	accum_bcd_h
	call	accum_bcd_l

	call	multx10		; convert from 0-180 to 0-1800

	movf	data_h,W
	movwf	el_target_h	; store as elevation target
	movf	data_l,W
	movwf	el_target_l

	; If we are operating in flipped mode, map the targets
	call	flip_targets

	return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

targets_to_args:
	; If we are operating in flipped mode, map the targets
	call	flip_targets

	movf	az_target_h,W
	movwf	data_h
	movf	az_target_l,W
	movwf	data_l
	call	divx10		; Convert from tenths of degrees to degrees

	movlw	cur_arg1	; Use temporary format: 01 23 00 45 00
	movwf	FSR
	call	divide_bcd_l
	call	divide_bcd_h
	decf	FSR
	call	divide_bcd_l
	call	divide_bcd_h	; Should in theory be zero...


	movf	el_target_h,W
	movwf	data_h
	movf	el_target_l,W
	movwf	data_l
	call	divx10		; Convert from tenths of degrees to degrees

	movlw	cur_arg3
	movwf	FSR
	call	divide_bcd_l
	call	divide_bcd_h
	decf	FSR
	clrf	cur_arg2
	call	divide_bcd_l
	call	divide_bcd_h	; Should in theory be zero...

	clrf	cur_arg4

	; Map the targets again to get them the way they originally were
	call	flip_targets

	return


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Same idea as targets_to_args, but current positions instead of targets.

pos_to_args:
	; If we are operating in flipped mode, map the positions
	call	flip_pos

	movf	az_pos_h,W
	movwf	data_h
	movf	az_pos_l,W
	movwf	data_l
	call	divx10		; Convert from tenths of degrees to degrees

	movlw	cur_arg1	; Use temporary format: 01 23 00 45 00
	movwf	FSR
	call	divide_bcd_l
	call	divide_bcd_h
	decf	FSR
	call	divide_bcd_l
	call	divide_bcd_h	; Should in theory be zero...


	movf	el_pos_h,W
	movwf	data_h
	movf	el_pos_l,W
	movwf	data_l
	call	divx10		; Convert from tenths of degrees to degrees

	movlw	cur_arg3
	movwf	FSR
	call	divide_bcd_l
	call	divide_bcd_h
	decf	FSR
	call	divide_bcd_l
	call	divide_bcd_h	; Should in theory be zero...

	clrf	cur_arg4

	; Map the positions again to get them the way they originally were
	call	flip_pos

	return


; This maps the targets to/from flipped mode.  The operation is
; symmetric, so it will work both ways.  Also, doing it twice in a
; row will leave the values unchanged.

;TODO: fix for tenths of degrees
flip_targets:
	; If we are operating in flipped mode, map the targets
	btfss	flags,FLIPPED_FLAG
	return

	movlw	128		; az_target_h += 128   (wraps)
	addwf	az_target_h	; lower byte stays unchanged

	; TODO: Fix to use 16 bits of precision!!!
	comf	el_target_h	; el_target_h = 255-el_target_h
	return


; Same as above, but for positions.
; WARNING: This should only be called inside a critical section,
; since if you leave the positions flipped during an interrupt,
; they will be improperly updated!

;TODO: fix for tenths of degrees
flip_pos:
	; If we are operating in flipped mode, map the positions
	btfss	flags,FLIPPED_FLAG
	return

	movlw	128		; az_pos += 128   (wraps)
	addwf	az_pos_h

	; TODO: Fix to use 16 bits of precision!!!
	comf	el_pos_h	; el_pos = 255-el_pos
	return


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	; These are general data storage for 16-bit values.
	register	data_l
	register	data_h

	; Intra-function temps
	register	tmp_l
	register	tmp_h

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;Move W into data_h/l, zero-extending it.
w_to_data:	
	movwf	data_l
	goto	clear_data_h	; return from there

; Clear data_h/l
clear_data:
	clrf	data_l
clear_data_h:
	clrf	data_h
	return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


; Multiply data_h/l by 10, and add the lower nibble
; of [FSR].  FSR and [FSR] are not changed.
accum_bcd_l:
	call	multx10
	movf	INDF,W
	andlw	0x0f
	addwf	data_l
	btfsc	STATUS,C
	incf	data_h	; Do 16-bit add 
	return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


; Multiply data_h/l by 10, and add the upper nibble
; of [FSR].  FSR and [FSR] are not changed.
accum_bcd_h:
	call	multx10
	swapf	INDF,W
	andlw	0x0f
	addwf	data_l
	btfsc	STATUS,C
	incf	data_h	; Do 16-bit add 
	return


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Divide data_h/l by 10, and put the remainder into the lower nibble
; of [FSR].  The other half of [FSR] is not changed.
divide_bcd_l:
	movlw	0xf0
	andwf	INDF		; Clear lower nibble of INDF
	call	divx10
	movf	REMB0,W		; Remainder will be in range of 0..9
	iorwf	INDF
	return


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Divide data_h/l by 10, and put the remainder into the upper nibble
; of [FSR].  The other half of [FSR] is not changed.
divide_bcd_h:
	movlw	0x0f
	andwf	INDF		; Clear upper nibble of INDF
	call	divx10		; Remainder will be in range of 0..9
	swapf	REMB0,W		; Get remainder into upper nibble of W.
				; (Lower nibble will be 0.)
	iorwf	INDF		; Combine with lower nibble of INDF
	return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Multiply data_l/data_h by 2
multx2:
	bcf	STATUS,C
	rlf	data_l
	rlf	data_h
	return


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Multiply data_h/l by 10.  Will overflow if data_h/l is greater then 6553 or so...
multx10:
	movlw	10
	goto	byte_mult	;return from there

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; This multiplies data_h/l by the byte in W, treated as
; an integer.  If overflow occurs, tough luck.
byte_mult:
	movwf	BARGB0
	movf	data_h,W  ; Transfer data and constant to regs used by UMULT1608
	movwf	AARGB0
	movf	data_l,W
	movwf	AARGB1
	call	UMULT1608
	movf	AARGB1,W  ; Get 2 LS bytes of result
	movwf	data_h
	movf	AARGB2,W 
	movwf	data_l
	return


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Divide data_l/data_h by 2, losing remainder bit
divx2:
	bcf	STATUS,C
	rrf	data_h
	rrf	data_l
	return


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Divide data_h/l by 10.  The remainder is left in register REMB0
divx10:
	movlw	10
	goto	byte_div	;return from there



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; This divides data_h/l by the byte in W, treated as
; an integer.  The remainder is left in register REMB0
byte_div:
	movwf	BARGB0
	movf	data_h,W  ; Transfer data and constant to regs used by UDIV1608
	movwf	AARGB0
	movf	data_l,W
	movwf	AARGB1
	call	UDIV1608
	movf	AARGB0,W  ; Get both bytes of result
	movwf	data_h
	movf	AARGB1,W 
	movwf	data_l
	return


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; This multiplies data_h/l by an 16-bit value in BARGB0/1, that is treated
; as a fraction (0..65535/65536).  Thus, the result will always be less
; than the original value, and overflow is not an issue.
fract_mult:
	movf	data_h,W
	movwf	AARGB0
	movf	data_l,W
	movwf	AARGB1
	goto	UMULT1616  ; return from there

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


; This is called whenever there is nothing better to do.
; It checks to see if the rotors have on or near their target
; positions and starts or stops them if necessary.


Rotor_check:
	; Simply return if insufficient time has passed.
	btfss	flags,TIMER_FLAG	
	return

	bcf	flags,TIMER_FLAG  ;Clear flag that is set by timer
	call	az_check
	call	el_check
	call	LCD_check
	return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


az_check:
	; See if either rotor position has reached or passed its target
	; Compare target and current positions.  Decide if
	; rotors need to be started up, started down, or stopped.
	;
	; Three situations: the rotor is either moving up, down, or not at all
	; If moving up, stop it if its position is >= target.
	; If moving down, stop it if its position is <= target.
	; If stopped, start it (in the correct direction) if
	; abs(position - target) is greater than some slop factor.

	call	read_az		; Get AZ value from ADC

	; In locked mode, simply make sure that the rotors are stopped.
	btfsc	flags,LOCKED_FLAG
	goto	check_az_stop

	btfsc	PORTB,AZ_UP_BIT
	goto	check_az_up
	btfsc	PORTB,AZ_DOWN_BIT
	goto	check_az_down
	goto    check_az_stopped

check_az_up:
	; Calculate (position-target)
	movf	az_pos_l,W 	; Move position to tmp_h/l
	movwf	tmp_l
	movf	az_pos_h,W
	movwf	tmp_h

	; Subtract the target from tmp_h/l
	sub16	az_target_h,az_target_l,tmp_h,tmp_l

	; if (pos<target), then C will be cleared
	btfss	STATUS,C
	goto	done_check_az   ; Position < target 

	; Position >= target, so stop motor.
check_az_stop:
	call	stop_az		; Stop motion if target MSB/LSB is greater
	goto	done_check_az

	
check_az_down:
	; Calculate (target-position)
	movf	az_target_l,W 	; Move target to tmp_h/l
	movwf	tmp_l
	movf	az_target_h,W
	movwf	tmp_h

	; Subtract the position from tmp_h/l
	sub16	az_pos_h,az_pos_l,tmp_h,tmp_l

	; if (target<pos), then C will be cleared
	btfss	STATUS,C
	goto	done_check_az   ; Position <= target 

	; Target >= position, so stop motor.
	call	stop_az	
	goto	done_check_az

	
check_az_stopped:
	; See if abs(current_position - target) > slop

	; Calculate (position-target)
	movf	az_pos_l,W 	; Move position to tmp_h/l
	movwf	tmp_l
	movf	az_pos_h,W
	movwf	tmp_h

	; Subtract the target from tmp_h/l
	sub16	az_target_h,az_target_l,tmp_h,tmp_l

	movf	tmp_h,W
	movwf	data_h		 ; Stash tmp_h for later


	; Calculate absolute value
	btfsc	STATUS,C	; Carry is set if result is positive
	goto	az_stopped_positive  ; Skip negation if positive

	comf	tmp_l		; negate and increment tmp_h/l
	comf	tmp_h
	incf	tmp_l
	btfsc	STATUS,Z
	incf	tmp_h
az_stopped_positive:

	; Subtract 16-bit constant (with MSB == 0) from tmp_h/l.
	; TODO: use cmple16 macro and avoid stashing trick.
	; TODO: Store slop values in eeprom to allow user modification.
	movlw	AZ_SLOP_L
	subwf	tmp_l
	movlw	0
	btfss	STATUS,C
	movlw	1
	subwf	tmp_h

	btfss	STATUS,C	; Carry is clear if result is negative
	return			; tmp_h/l is less than slop value

	; Test highest (sign) bit of tmp_h, which was stashed in data_h
	btfss	data_h,7   ; If the bit is set, then target > position
	goto	check_az_2

	call	start_az_up
	goto	done_check_az

check_az_2:
	call	start_az_down

done_check_az:
	return


el_check:
	; Now do elevation
	call	read_el		; Get EL value from ADC

	; In locked mode, simply make sure that the rotors are stopped.
	btfsc	flags,LOCKED_FLAG
	goto	check_el_stop

	btfsc	PORTB,EL_UP_BIT
	goto	check_el_up
	btfsc	PORTB,EL_DOWN_BIT
	goto	check_el_down
	goto	check_el_stopped

check_el_up:
	; Calculate (position-target)
	movf	el_pos_l,W 	; Move position to tmp_h/l
	movwf	tmp_l
	movf	el_pos_h,W
	movwf	tmp_h

	; Subtract the target from tmp_h/l
	sub16	el_target_h,el_target_l,tmp_h,tmp_l

	; if (pos<target), then C will be cleared
	btfss	STATUS,C
	goto	done_check_el   ; Position < target 

	; Position >= target, so stop motor.
check_el_stop:
	call	stop_el		; Stop motion if target MSB/LSB is greater
	goto	done_check_el

	
check_el_down:
	; Calculate (target-position)
	movf	el_target_l,W 	; Move target to tmp_h/l
	movwf	tmp_l
	movf	el_target_h,W
	movwf	tmp_h

	; Subtract the position from tmp_h/l
	sub16	el_pos_h,el_pos_l,tmp_h,tmp_l

	; if (target<pos), then C will be cleared
	btfss	STATUS,C
	goto	done_check_el   ; Position <= target 

	; Target >= position, so stop motor.
	call	stop_el	
	goto	done_check_el

	
check_el_stopped:
	; See if abs(current_position - target) > slop

	; Calculate (position-target)
	movf	el_pos_l,W 	; Move position to tmp_h/l
	movwf	tmp_l
	movf	el_pos_h,W
	movwf	tmp_h

	; Subtract the target from tmp_h/l
	sub16	el_target_h,el_target_l,tmp_h,tmp_l

	movf	tmp_h,W
	movwf	data_h		 ; Stash tmp_h for later


	; Calculate absolute value
	btfsc	STATUS,C	; Carry is set if result is positive
	goto	el_stopped_positive  ; Skip negation if positive

	comf	tmp_l		; negate and increment tmp_h/l
	comf	tmp_h
	incf	tmp_l
	btfsc	STATUS,Z
	incf	tmp_h
el_stopped_positive:

	; Subtract 16-bit constant (with MSB == 0) from tmp_h/l
	movlw	EL_SLOP_L
	subwf	tmp_l
	movlw	0
	btfss	STATUS,C
	movlw	1
	subwf	tmp_h

	btfss	STATUS,C	; Carry is clear if result is negative
	return			; tmp_h/l is less than slop value

	; Test highest (sign) bit of tmp_h, which was stashed in data_h
	btfss	data_h,7   ; If the bit is set, then target > position
	goto	check_el_2

	call	start_el_up
	goto	done_check_el

check_el_2:
	call	start_el_down

done_check_el:
	return


;**********************************************************************

start_az_up:
	bsf	PORTB,AZ_UP_BIT		; Start the az rotor CW
	nop				; Time delay for port B to settle
	bcf	PORTB,AZ_DOWN_BIT
	goto	done_start_az

start_az_down:
	bsf	PORTB,AZ_DOWN_BIT	; Start the az rotor CCW
	nop				; Time delay for port B to settle
	bcf	PORTB,AZ_UP_BIT		; Fall into done_start_az
					
done_start_az:
	di
	clrf	az_timer_h		; restart its timer
	clrf	az_timer_l
	ei
	return
	
start_el_up:
	bsf	PORTB,EL_UP_BIT
	nop				; Time delay for port B to settle
	bcf	PORTB,EL_DOWN_BIT
	goto	done_start_el

start_el_down:
	bsf	PORTB,EL_DOWN_BIT	; Start the el rotor down
	nop				; Time delay for port B to settle
	bcf	PORTB,EL_UP_BIT		; fall into done_start_el

done_start_el:
	di
	clrf	el_timer_h		; restart its timer
	clrf	el_timer_l
	ei
	return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

stop_az:
	movf	PORTB,W
	andlw	~AZ_MASK
	movwf	PORTB		; Turn off AZ relay bits in one write to PORTB

	return

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

stop_az_el:
	call	stop_az		; then fall into stop_el
stop_el:
	movf	PORTB,W
	andlw	~EL_MASK
	movwf	PORTB		; Turn off EL relay bits in one write to PORTB

	return

;**********************************************************************

; This is called from the interrupt service routine, but it never returns.
overrun_error:
	; Oops, stop everything!
	call	stop_az_el

	bsf	flags,HW_FAIL_FLAG	; set the "timed out" flag

	ee_pos_string	overrun_msg, 192, "Motor overrun!!!"
	movlw	overrun_msg
	call	LCD_put_ee_pos_str
	goto	fatal_error_loop

;**********************************************************************

; Called when the ADC result indicates a hardware failure.
adc_error:
	; Oops, stop everything!
	call	stop_az_el

	bsf	flags,HW_FAIL_FLAG	; set the "timed out" flag

	ee_pos_string	adc_err_msg, 192, "ADC Error!!!"
	movlw	adc_err_msg
	call	LCD_put_ee_pos_str
	goto	fatal_error_loop

;**********************************************************************

fatal_error_loop:
	clrwdt
	call	stop_az_el
	bcf	PORTB,LED_BIT
	movlw	100
	call	Delaynms  ; let the LED blink on for a bit as an alarm.
	bsf	PORTB,LED_BIT
	movlw	100
	call	Delaynms  ; let the LED blink off for a bit.

	goto	fatal_error_loop		; Loop forever

	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

	; Positions most recently sent to LCD.  In tenths-of-degrees
	; format.  E.g. az runs from 0 to 3600.
	register	lcd_az_h	
	register	lcd_az_l	
	register	lcd_el_h
	register	lcd_el_l

LCD_check:
LCD_check_az:
	; See if the az position has changed since we last updated the LCD
	; Do 16-bit compare
	;TODO: use cmple16 macro
	movf	lcd_az_h,W
	subwf	az_pos_h,W
	btfss	STATUS,Z
	goto	lcd_check_az_2

	movf	lcd_az_l,W
	subwf	az_pos_l,W
	btfsc	STATUS,Z
	goto	LCD_check_el

lcd_check_az_2:	
	movlw	LCD_AZ_POS
	call	LCD_cmd		; move cursor to correct position
	movf	az_pos_h,W
	movwf	lcd_az_h	; remember what the LCD is displaying.
	movwf	data_h
	movf	az_pos_l,W
	movwf	lcd_az_l
	movwf	data_l
	call	divx10		; Convert from tenths of degrees to degrees
	call	LCD_put_data
	
LCD_check_el:
	; See if the el position has changed since we last updated the LCD
	; Do 16-bit compare
	;TODO: use cmple16 macro
	movf	lcd_el_h,W
	subwf	el_pos_h,W
	btfss	STATUS,Z
	goto	lcd_check_el_2

	movf	lcd_el_l,W
	subwf	el_pos_l,W
	btfsc	STATUS,Z
	goto	LCD_check_done

lcd_check_el_2:
	movlw	LCD_EL_POS
	call	LCD_cmd		; move cursor to correct position
	movf	el_pos_h,W
	movwf	lcd_el_h	; remember what the LCD is displaying.
	movwf	data_h
	movf	el_pos_l,W
	movwf	lcd_el_l
	movwf	data_l
	call	divx10		; Convert from tenths of degrees to degrees
	call	LCD_put_data

LCD_check_done:
	call	flip_pos  ; finish dealing with flipping
	return


; This prints data_h/l on the LCD as a three digit number (0-360).
; If data_h/l is greater than 999, this will mess up.
LCD_put_data:
	movlw	100
	call	byte_div  ; now data_l has hundreds digit, and REMB0 has 0-99.
	movf	data_l,W
	call	LCD_putdigit	; print hundreds
	movf	REMB0,W
	movwf	data_l
	movlw	10
	call	byte_div  ; now data_l has tens digit, and REMB0 has 0-9.
	movf	data_l,W
	call	LCD_putdigit	; print tens
	movf	REMB0,W
	goto	LCD_putdigit	; print ones, and return from there


;**************  Data RAM Assignments for CIV serial I/O  ******************
;

BUFFER_LEN =	16
	reg_block	_rcv_buffer,BUFFER_LEN      ; RX Circular Buffer
LAST_BUFFER_POS = _rcv_buffer + BUFFER_LEN - 1

	register	_rcv_head	; Head pointer: prev avail char
	register	_rcv_tail	; Tail pointer: last rcvd char
	; When head == tail, buffer is empty
	; Both pointers should be pre-incremented (with wraparound).

	register	_gc_fsr		; For saving FSR
	
	register	_xmt_reg    ; Data to be transmitted
	register	_ser_count   ; Counter for #of Bits Transmitted
	register	_ser_dly   ; counter for various delays
	
;***********************************************************************


Getchar:
_gc_wait_idle:
	di			; Critical section
	movf	_rcv_head,W	; See if any chars in buffer
	subwf	_rcv_tail,W	; If tail == head, the buffer is empty.
	btfsc	STATUS,Z
	goto	_gc_no_char

	incf	_rcv_head	; Advance the head pointer to the next char.

	movf	_rcv_head,W
	sublw	LAST_BUFFER_POS ; See if _rcv_head is past end of buffer.
	movlw	_rcv_buffer
	btfss	STATUS,C
	movwf	_rcv_head 	; Wrap the head pointer back to the beginning

	movf	FSR,W 		; Save FSR 
	movwf	_gc_fsr

	movf	_rcv_head,W
	movwf	FSR		; Get next avail char
	movf	INDF,W
	movwf	tmp_l		; Stash in tmp_l

	movf	_gc_fsr,W	; Restore FSR
	movwf	FSR

	movf	tmp_l,W		; return with character in W

	ei
	return
	
_gc_no_char:
	ei			; no longer in critical section
	clrwdt			; We could be in this loop forever...
	call	Rotor_check	; deal with rotation while waiting.
	goto	_gc_wait_idle
	
;*************************************************************************

; This flushes the RX buffer.
Flush:
	di
	movlw	_rcv_buffer
	movwf	_rcv_head
	movwf	_rcv_tail
	ei
	return


;*************************************************************************
;       Transmitter: Done with timing loops.


;*************************************************************************
; Bit cycle counts (BCCs) for 4MHz clock:
; 19200		52
; 9600		104
; 4800		208
; 1200		833	;bigger than 256...


TX_DLY_CONST1  =     31	; for 4MHz and 9600 baud
TX_DLY_CONST2  =     29	; for 4MHz and 9600 baud
TX_DLY_CONST3  =     35	; for 4MHz and 9600 baud


Putchar:
	clrwdt
	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

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Send 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	TRISC,CIV_OUT_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	PORTC,CIV_OUT_BIT
	bsf	STATUS,RP0  ;switch to bank 1
	bcf	TRISC,CIV_OUT_BIT
	bcf	STATUS,RP0  ;switch to bank 0
	return
	
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


; These numbers are different than the ones for the CIV serial output
; because the actual outputting of the data bit takes less time.
LCD_DLY_CONST1  =     33	; for 4MHz and 9600 baud
LCD_DLY_CONST2  =     31	; for 4MHz and 9600 baud
LCD_DLY_CONST3  =     70	; for 4MHz and 9600 baud  (two stop bits)

; Send a pos_string, consisting of a character position code followed
; by a zero-terminated string, to the LCD.  The pos_string's EEPROM address
; is passed in W.

LCD_put_ee_pos_str:
	movwf	tmp_h		; save the data
	call	LCD_escape	; send the escape code
	movf	tmp_h,W 	; get the data, and fall into LCD_put_ee_str.

; A puts() for the LCD...
LCD_put_ee_str:
	movwf	tmp_h		; save the address parameter
	call	read_ee		; get actual char; and set Z flag based on it
	btfsc	STATUS,Z	; see if W is zero
	return			; return if so
	call	LCD_putchar	; if nonzero, send the string char
	incf	tmp_h,W		; Increment pointer into W
	goto	LCD_put_ee_str	; Keep sending chars

	
	
; Send a command byte to the LCD.  It can be a "real" command or
; a character address (range: 128-143 and 192-207).
; Input data in W. (This routine is barely worth the space overhead...)
LCD_cmd:
	movwf	tmp_h
	call	LCD_escape
	movf	tmp_h,W
	goto	LCD_putchar	; return from there

; Send LCD controller escape code.
LCD_escape:
	movlw	254		; Escape code for Seetron BPI-216 LCD controller
	goto	LCD_putchar	; return from there

; Send 0-9 value in W to LCD as an ASCII digit
LCD_putdigit:
	addlw	'0'		; Convert to ASCII
				; Fall thru to LCD_putchar

; Send char in W to the LCD.  (Don't call Rotor_check from within this:
; Rotor_check might be calling it!)

LCD_putchar:
	; The 16F84 apparently had a longer WDT timeout, but the
	; 16F876's WDT will time out during a long data transmission
	; to the LCD...
	clrwdt

	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.

	bsf	PORTC,LCD_BIT       ; Send (inverted) Start Bit

	movlw   LCD_DLY_CONST1  ; Wait exactly one bit time
	movwf   _ser_dly
lcd_loop_1:
	decfsz  _ser_dly
	goto    lcd_loop_1
	
_lcd_next:
	rrf	_xmt_reg	; shift next bit into carry
	btfsc	STATUS,C
	bcf	PORTC,LCD_BIT       ; Send (inverted) one bit
	btfss	STATUS,C
	bsf	PORTC,LCD_BIT       ; Send (inverted) zero bit

	movlw   LCD_DLY_CONST2	; Wait exactly one bit time
	movwf   _ser_dly
	nop			; timing tweak
lcd_loop_2:
	decfsz  _ser_dly
	goto    lcd_loop_2

	decfsz  _ser_count
	goto    _lcd_next
	
	bcf	PORTC,LCD_BIT       ; Send (inverted) Stop Bit

	ei			; (No harm if interrupt causes stop bit to
				; be extended.)

	movlw   LCD_DLY_CONST3  ; Wait exactly one bit time
	movwf   _ser_dly
lcd_loop_3:
	decfsz  _ser_dly
	goto    lcd_loop_3

	return

;   End of Transmission to LCD
;


; Interrupt service routine
interrupt:
	movwf	isr_w		; save W (isr_w is mirrored in all banks).
	swapf	STATUS,W	; get status into W
	clrf	STATUS		; go to bank 0
	movwf	isr_status	; save status

	btfss	INTCON,T0IF	; See if interrupt was caused by Timer0.
	goto	end_tmr_intr	; Skip processing if not.

	bcf	INTCON,T0IF	; reset timer overflow interrupt bit

	bsf	flags,TIMER_FLAG  ; set flag used for Rotor_check timing

	; If necessary, increment the 16-bit counters used for overrun detection
	movlw	AZ_MASK
	andwf	PORTB,W
	btfsc	STATUS,Z  ; Test the up and down az bits
	goto	tmr_intr_4

	; If the motor is running, increment the overrun counter
	addi16	TIMER_INCREMENT, az_timer_h, az_timer_l
	; If it has overflowed, it is a fatal error.
	btfsc	STATUS,C
	goto	overrun_error

tmr_intr_4:

	movlw	EL_MASK
	andwf	PORTB,W
	btfsc	STATUS,Z  ; Test the up and down el bits
	goto	tmr_intr_5

	; If the motor is running, increment the overrun counter
	addi16	TIMER_INCREMENT, el_timer_h, el_timer_l
	; If it has overflowed, it is a fatal error.
	btfsc	STATUS,C
	goto	overrun_error

tmr_intr_5:
end_tmr_intr:

	; Now see if there was an interrupt for the UART receiver
	; There might be a couple of bytes in the FIFO, so we will
	; loop here if necessary.
check_uart_rx:
	btfss	PIR1,RCIF
	goto	end_uart_rx_intr ; Skip if no data available.

	; Note:  The only way to clear RCIF is to read the data from RCREG

	btfsc	RCSTA,OERR	; Check for overrun.
	goto	uart_error
	btfsc	RCSTA,FERR	; Check for framing error.
	goto	uart_error

	; Advance the tail pointer to the next empty slot in the
	; circular buffer.  If the buffer is already full (i.e. if
	; head == tail after advancing tail), everything gets scrambled.
	; (Buffer will appear to be empty.)
	; But in any case, data will be lost.

	incf	_rcv_tail

	movf	_rcv_tail,W
	sublw	LAST_BUFFER_POS ; See if _rcv_tail is past end of buffer.
	movlw	_rcv_buffer
	btfss	STATUS,C	; if (reg > literal), C=0
	movwf	_rcv_tail 	; Wrap the tail pointer back to the beginning


	movf	FSR,W 		; Save FSR 
	movwf	isr_fsr

	movf	_rcv_tail,W
	movwf	FSR
	movf	RCREG,W		; Get data byte (clears RCIF).
	movwf	INDF		; Put in buffer.

	movf	isr_fsr,W	; Restore FSR
	movwf	FSR

 	goto	check_uart_rx	; Go check for more.

uart_error:
	movf	RCREG,W		; Clear out data byte (clears RCIF).
	bcf	RCSTA,CREN	; Reset the receiver to clear the error
	nop
	bsf	RCSTA,CREN
				; Fall into end_uart_rx_intr

end_uart_rx_intr:
	; Restore W and status with official voodoo code
	swapf	isr_status,W
	movwf	STATUS		; Back to original bank
	swapf	isr_w,F
	swapf	isr_w,W

	retfie

	; Temporary storage for interrupt service routine
	register isr_status
	register isr_fsr

; isr_w must be at special address that is mirrored in all banks!!!
isr_w	equ	0x7f


;******************************************************************************

; 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:
	;For some bizarre reason, the program will hang after a while
	;if interrupts are enabled in this routine.
	di
	bsf	STATUS,RP1  ;switch to bank 2
	bcf	STATUS,RP0  ;switch to bank 2
	movwf	EEADR
	bsf	STATUS,RP0  ;switch to bank 3 from bank 2

read_ee_wait:
	clrwdt
	btfsc	EECON1,WR	; make sure no write is in progress
	goto	read_ee_wait

	bcf	EECON1,EEPGD	; select data (vs program) EEPROM
	bsf	EECON1,RD
	bcf	STATUS,RP0  ;switch to bank 2 from bank 3
	movf	EEDATA,W    ;get data
	bcf	STATUS,RP1  ;switch back to bank 0 from bank 2
	ei
	return

; This copies W to the EEDATA register, doing the right thing
; with the bank select bits.  Unlike the 16F84, the 16F876
; does not have EEDATA in bank 0.  Call this before calling
; write_ee.
set_eedata:
	bsf	STATUS,RP1  ;switch to bank 2
	movwf	EEDATA
	bcf	STATUS,RP1  ;switch back to bank 0 from bank 2
	return

; This writes the data that has been previously stored in the EEDATA
; register (e.g. by set_eedata) 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:
	bsf	STATUS,RP1  ;switch to bank 3
	bsf	STATUS,RP0  ;switch to bank 3

write_ee_wait:
	clrwdt
	btfsc	EECON1,WR	; make sure no write is in progress
	goto	write_ee_wait

	bcf	STATUS,RP0  ;switch to bank 2 from bank 3
	movwf	EEADR

	bsf	STATUS,RP0  ;switch to bank 3 from bank 2
	di			; critical section
	bcf	EECON1,EEIF
	bcf	EECON1,EEPGD	; select data (vs program) EEPROM
	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 from bank 3
	bcf	STATUS,RP1  ;switch back to bank 0 from bank 3
	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 3
	bsf	STATUS,RP1  ;switch to bank 3
wait_ee_loop:
	clrwdt
	btfsc	EECON1,WR	; test the "write finished" bit
	goto	wait_ee_loop
	bcf	STATUS,RP0  ;switch back to bank 0
	bcf	STATUS,RP1  ;switch back to bank 0
	return

;******************************************************************************


read_az:
	MOVLW	B'01000001'	; /8 Clock, A/D is on, Channel 0 is selected
	call	read_adc

	; Get the lower hard limit into tmp_h/l
	movlw	ee_lower_limit_az_h
	call	read_ee
	movwf	tmp_h
	movlw	ee_lower_limit_az_l
	call	read_ee
	movwf	tmp_l
	
	; Compare ADC to the lower hard limit
	cmple16	tmp_h,tmp_l,data_h,data_l  ; C=0 if limit>ADC
	btfss	STATUS,C
	goto	adc_error

	; Get the upper hard limit into tmp_h/l
	movlw	ee_upper_limit_az_h
	call	read_ee
	movwf	tmp_h
	movlw	ee_upper_limit_az_l
	call	read_ee
	movwf	tmp_l
	
	; Compare ADC to the upper hard limit
	cmple16	tmp_h,tmp_l,data_h,data_l  ; C=0 if limit>ADC
	btfsc	STATUS,C
	goto	adc_error

	; Get the offset factor into tmp_h/l
	movlw	ee_offset_az_h
	call	read_ee
	movwf	tmp_h
	movlw	ee_offset_az_l
	call	read_ee
	movwf	tmp_l
	
	; Calculate (ADC-offset)
	sub16	tmp_h,tmp_l,data_h,data_l

	; Check for unsigned integer underflow (fatal error)
	; Cause: position sensor hardware failure.

	; If Carry == 0, the subtraction underflowed.
	; (Remember: we cannot treat the ADC output as a signed number.)
	; If so, set it to zero.  (The rotor's motor can
	; momentarily force the position less than zero degrees)
	btfsc	STATUS,C
	goto	read_az_scale
	clrf	data_h
	clrf	data_l

read_az_scale:
	; Scale by the correct scale factor.

	; Put the data in AARGB0/1
	movf	data_h,W
	movwf	AARGB0
	movf	data_l,W
	movwf	AARGB1

	; Put the scale factor in BARGB
	movlw	ee_scale_az_h
	call	read_ee
	movwf	BARGB0
	movlw	ee_scale_az_l
	call	read_ee
	movwf	BARGB1

	; Multiply
	; If the ADC result is left-justified, the 6 LS bits of its
	; result will be zeroes, so there should not be a rounding problem.
	call	UMULT1616
	
	; Get 2 MS bytes of result and leave in data_h/l
	movf	AARGB0,W
	movwf	az_pos_h
	movf	AARGB1,W 
	movwf	az_pos_l

	; TODO:  check for > 3600 (fatal error)
	; Cause: position sensor hardware failure.

	return


read_el:
	MOVLW	B'01001001'	; /8 Clock, A/D is on, Channel 1 is selected
	call	read_adc

	; Invert the data value, since the hardware gives
	; max voltage at 0 degrees, and min voltage at 180.
	comf	data_h
	comf	data_l

	; Get the lower hard limit into tmp_h/l
	movlw	ee_lower_limit_el_h
	call	read_ee
	movwf	tmp_h
	movlw	ee_lower_limit_el_l
	call	read_ee
	movwf	tmp_l
	
	; Compare ADC to the lower hard limit
	cmple16	tmp_h,tmp_l,data_h,data_l  ; C=0 if limit>ADC
	btfss	STATUS,C
	goto	adc_error

	; Get the upper hard limit into tmp_h/l
	movlw	ee_upper_limit_el_h
	call	read_ee
	movwf	tmp_h
	movlw	ee_upper_limit_el_l
	call	read_ee
	movwf	tmp_l
	
	; Compare ADC to the upper hard limit
	cmple16	tmp_h,tmp_l,data_h,data_l  ; C=0 if limit>ADC
	btfsc	STATUS,C
	goto	adc_error

	; Get the offset factor into tmp_h/l
	movlw	ee_offset_el_h
	call	read_ee
	movwf	tmp_h
	movlw	ee_offset_el_l
	call	read_ee
	movwf	tmp_l
	
	; Calculate (ADC-offset)
	sub16	tmp_h,tmp_l,data_h,data_l

	; Check for unsigned integer underflow (fatal error)
	; Cause: position sensor hardware failure.

	; If Carry == 0, the subtraction underflowed.
	; (Remember: we cannot treat the ADC output as a signed number.)
	; If so, set it to zero.  (The rotor's motor can
	; momentarily force the position less than zero degrees)
	btfsc	STATUS,C
	goto	read_el_scale
	clrf	data_h
	clrf	data_l

read_el_scale:
	; Scale by the correct scale factor.

	; Put the data in AARGB0/1
	movf	data_h,W
	movwf	AARGB0
	movf	data_l,W
	movwf	AARGB1

	; Put the scale factor in BARGB
	movlw	ee_scale_el_h
	call	read_ee
	movwf	BARGB0
	movlw	ee_scale_el_l
	call	read_ee
	movwf	BARGB1

	; Multiply
	; If the ADC result is left-justified, the 6 LS bits of its
	; result will be zeroes, so there should not be a rounding problem.
	call	UMULT1616
	
	; Get 2 MS bytes of result and leave in data_h/l
	movf	AARGB0,W
	movwf	el_pos_h
	movf	AARGB1,W 
	movwf	el_pos_l

	; TODO:  check for > 1800 (fatal error)
	; Cause: position sensor hardware failure.

	return



; When calling this, W must be set to the correct byte for ADCON0.
; The result is in data_h/l.
read_adc:
	MOVWF	ADCON0		; Configure convertor
	
	; Ensure that the required sampling time for the selected input
	; channel has elapsed. Then the conversion may be started.

	movlw	6	; Wait 30 uS for signal to settle.
adc1loop:
	clrwdt		; We want 5 cycles in the loop (for 4 MHz clock)
	addlw	-1	; Decrement w
	btfss	STATUS,Z
	goto	adc1loop ; Loop if w not zero yet
	
	BSF	ADCON0,GO	; Start A/D Conversion

adc2loop:
	clrwdt
	BTFSC	ADCON0,GO	; Test the GO/DONE bit
	goto	adc2loop	; Keep checking until it clears

	; Get the data into data_h/l
	movf	ADRESH,W
	movwf	data_h
        bsf	STATUS,RP0  	; go to bank 1
	movf	ADRESL,W
        bcf	STATUS,RP0  	; go to bank 0
	movwf	data_l

	return


;******************************************************************************
; Variables needed for math routines below

	register	AARGB0	; MSB of 16-bit input and 24-bit result
	                        ; (16-bit result for division)
	register	AARGB1  ; LSB of input
	register	AARGB2  ; LSB of result
	register	AARGB3  ; LSB of 32-bit result

	register	BARGB0  ; 8-bit input  (divisor for division)
	register	BARGB1  ; LSb of 16-bit input

	register	REMB0	; 8-bit remainer for division

	register	TEMPB0
	register	TEMPB1

	register	LOOPCOUNT

;******************************************************************************
;       16x8 PIC16 FIXED POINT MULTIPLY ROUTINES

UMUL1608L        macro

;       Max Timing:     2+13+6*15+14 = 119 clks
;       Min Timing:     2+7*6+5+4 = 54 clks
;       PM: 26            DM: 7

                MOVLW   0x08
                MOVWF   LOOPCOUNT

LOOPUM1608A
                RRF     BARGB0,F
                BTFSC   STATUS,C
                GOTO    LUM1608NAP
                DECFSZ  LOOPCOUNT,F
                GOTO    LOOPUM1608A

                CLRF    AARGB0
                CLRF    AARGB1
                RETLW   0x00

LUM1608NAP
                BCF     STATUS,C
                GOTO    LUM1608NA

LOOPUM1608
                RRF     BARGB0,F
                BTFSS   STATUS,C
                GOTO    LUM1608NA
                MOVF    TEMPB1,W
                ADDWF   AARGB1,F
                MOVF    TEMPB0,W
                BTFSC   STATUS,C
                INCFSZ  TEMPB0,W
                ADDWF   AARGB0,F
LUM1608NA       RRF     AARGB0,F
                RRF     AARGB1,F
                RRF     AARGB2,F
                DECFSZ  LOOPCOUNT,F
                GOTO    LOOPUM1608

                endm



UMUL1608        macro

;       Max Timing:     1+6+7*11 = 84 clks
;       Min Timing:     1+2*8+4 = 21 clks
;       PM: 1+2*8+4+6*7 = 63            DM: 4

                variable i =0

                BCF     STATUS,C              ; clear carry for first right shift
                
                while i < 8
                
                BTFSC   BARGB0,i
                GOTO    UM1608NA#v(i)

                variable i =i + 1

                endw

                CLRF    AARGB0         ; if we get here, BARG = 0
                CLRF    AARGB1
                RETURN

UM1608NA0       RRF    AARGB0,F
                RRF    AARGB1,F
                RRF    AARGB2,F

                variable i =1

                while   i < 8

                BTFSS   BARGB0,i
                GOTO    UM1608NA#v(i)
UM1608A#v(i)    MOVF   TEMPB1,W
                ADDWF   AARGB1,F
                MOVF            TEMPB0,W
                BTFSC           STATUS,C
                INCFSZ          TEMPB0,W
                ADDWF           AARGB0,F
UM1608NA#v(i)   RRF    AARGB0,F
                RRF    AARGB1,F
                RRF             AARGB2,F

                variable i =i + 1

                endw

                endm


;******************************************************************************
;       16x8 Bit Unsigned Fixed Point Multiply 16x8 -> 24

;       Input:  16 bit unsigned fixed point multiplicand in AARGB0
;                       8 bit unsigned fixed point multiplier in BARGB0

;       Use:    CALL    FXM1608U

;       Output: 24 bit unsigned fixed point product in AARGB0

;       Result: AARG  <--  AARG x BARG

;       Max Timing:     5+119+2 = 126 clks

;       Min Timing:     5+54 = 59 clks

;       PM: 5+26+1 = 31              DM: 7

UMULT1608:      CLRF    AARGB2          ; clear partial product
                MOVF   AARGB0,W
                MOVWF   TEMPB0
                MOVF   AARGB1,W
                MOVWF   TEMPB1

                UMUL1608L

                RETLW           0x00



;******************************************************************************
;	RCS Header $Id: antciv.asm 2.19 2001/08/09 17:21:20 doug Exp $

;	$Revision: 2.19 $

;       16x16 PIC16 FIXED POINT MULTIPLY ROUTINES
;
;       Input:  fixed point arguments in AARG and BARG
;
;       Output: product AARGxBARG in AARG
;
;       All timings are worst case cycle counts
;
;       It is useful to note that the additional unsigned routines requiring a non-power of two
;       argument can be called in a signed multiply application where it is known that the
;       respective argument is nonnegative, thereby offering some improvement in
;       performance.
;
;         Routine            Clocks     Function
;
;       FXM1616U     256        16x16 -> 32 bit unsigned fixed point multiply
;
;       The above timings are based on the looped macros. If space permits,
;       approximately 64-73 clocks can be saved by using the unrolled macros.
;

;*****************************************************************************


UMUL1616L        macro

;       Max Timing:     2+13+6*15+14+2+7*16+15 = 248 clks

;       Min Timing:     2+7*6+5+1+7*6+5+4 = 101 clks

;       PM: 51            DM: 9

                MOVLW   0x08
                MOVWF   LOOPCOUNT

LOOPUM1616A
                RRF     BARGB1, F
                BTFSC   STATUS,C
                GOTO    ALUM1616NAP
                DECFSZ  LOOPCOUNT, F
                GOTO    LOOPUM1616A

                MOVWF   LOOPCOUNT

LOOPUM1616B
                RRF     BARGB0, F
                BTFSC   STATUS,C
                GOTO    BLUM1616NAP
                DECFSZ  LOOPCOUNT, F
                GOTO    LOOPUM1616B

                CLRF    AARGB0
                CLRF    AARGB1
                RETLW   0x00

BLUM1616NAP
                BCF     STATUS,C
                GOTO    BLUM1616NA

ALUM1616NAP
                BCF     STATUS,C
                GOTO    ALUM1616NA

ALOOPUM1616
                RRF     BARGB1, F
                BTFSS   STATUS,C
                GOTO    ALUM1616NA
                MOVF   TEMPB1,W
                ADDWF   AARGB1, F
                MOVF            TEMPB0,W
                BTFSC           STATUS,C
                INCFSZ          TEMPB0,W
                ADDWF           AARGB0, F

ALUM1616NA
                RRF    AARGB0, F
                RRF    AARGB1, F
                RRF    AARGB2, F
                DECFSZ  LOOPCOUNT, F
                GOTO    ALOOPUM1616

                MOVLW   0x08
                MOVWF   LOOPCOUNT

BLOOPUM1616
                RRF             BARGB0, F
                BTFSS   STATUS,C
                GOTO    BLUM1616NA
                MOVF   TEMPB1,W
                ADDWF   AARGB1, F
                MOVF            TEMPB0,W
                BTFSC           STATUS,C
                INCFSZ          TEMPB0,W
                ADDWF           AARGB0, F

BLUM1616NA
                RRF    AARGB0, F
                RRF    AARGB1, F
                RRF    AARGB2, F
                RRF             AARGB3, F
                DECFSZ  LOOPCOUNT, F
                GOTO    BLOOPUM1616

                endm


UMUL1616        macro

;       Max Timing:     1+6+7*11+8*12 = 180 clks

;       Min Timing:     1+2*8+2*8+4 = 37 clks

;       PM: 1+2*8+2*8+4+7*11+8*12 = 210            DM: 8


                variable i =0

                BCF     STATUS,C              ; clear carry for first right shift
                
                while i < 8
                
                BTFSC   BARGB1,i
                GOTO    UM1616NA#v(i)

                variable i =i + 1

                endw

                variable i =8
                
                while i < 16
                
                BTFSC   BARGB0,i-8
                GOTO    UM1616NA#v(i)

                variable i =i + 1

                endw

                CLRF    AARGB0          ; if we get here, BARG = 0
                CLRF    AARGB1
                RETURN

UM1616NA0       RRF    AARGB0, F
                RRF    AARGB1, F
                RRF             AARGB2, F

                variable i =1

                while   i < 8

                BTFSS   BARGB1,i
                GOTO    UM1616NA#v(i)
UM1616A#v(i)    MOVF   TEMPB1,W
                ADDWF   AARGB1, F
                MOVF            TEMPB0,W
                BTFSC           STATUS,C
                INCFSZ          TEMPB0,W
                ADDWF           AARGB0, F
UM1616NA#v(i)   RRF    AARGB0, F
                RRF    AARGB1, F
                RRF             AARGB2, F

                variable i =i + 1

                endw

                variable i =8

                while   i < 16

                BTFSS   BARGB0,i-8
                GOTO    UM1616NA#v(i)
UM1616A#v(i)    MOVF   TEMPB1,W
                ADDWF   AARGB1, F
                MOVF            TEMPB0,W
                BTFSC           STATUS,C
                INCFSZ          TEMPB0,W
                ADDWF           AARGB0, F
UM1616NA#v(i)   RRF    AARGB0, F
                RRF    AARGB1, F
                RRF             AARGB2, F
                RRF             AARGB3, F

                variable i =i + 1

                endw

                endm


;*****************************************************************************
        
;       16x16 Bit Unsigned Fixed Point Multiply 16x16 -> 32

;       Input:  16 bit unsigned fixed point multiplicand in AARGB0
;                       16 bit unsigned fixed point multiplier in BARGB0

;       Use:    CALL    FXM1616U

;       Output: 32 bit unsigned fixed point product in AARGB0

;       Result: AARG  <--  AARG x BARG

;       Max Timing:     6+248+2 = 256 clks

;       Min Timing:     6+101 = 107 clks

;       PM: 6+51+1 = 58              DM: 9

UMULT1616:
                CLRF    AARGB2          ; clear partial product
                CLRF    AARGB3
                MOVF   AARGB0,W
                MOVWF   TEMPB0
                MOVF   AARGB1,W
                MOVWF   TEMPB1

                UMUL1616L

                RETLW           0x00

;*****************************************************************************
        


;******************************************************************************



;	RCS Header Id: fxd68.a16 2.3 1996/10/16 14:23:57 F.J.Testa Exp 

;	$Revision: 2.19 $

;*****************************************************************************

;       16/08 BIT Division Macros


UDIV1608L  macro

;       Max Timing: 2+7*12+11+3+7*24+23 = 291 clks

;       Min Timing: 2+7*11+10+3+7*17+16 = 227 clks

;       PM: 39                                  DM: 7

                MOVLW           8
                MOVWF           LOOPCOUNT

LOOPU1608A      RLF             AARGB0,W
                RLF             REMB0,F
                MOVF            BARGB0,W
                SUBWF           REMB0,F

                BTFSC           STATUS,C
                GOTO            UOK68A          
                ADDWF           REMB0,F
                BCF             STATUS,C
UOK68A          RLF             AARGB0,F

                DECFSZ          LOOPCOUNT,F
                GOTO            LOOPU1608A

                CLRF            TEMPB0

                MOVLW           8
                MOVWF           LOOPCOUNT

LOOPU1608B      RLF             AARGB1,W
                RLF             REMB0,F
                RLF             TEMPB0,F
                MOVF            BARGB0,W
                SUBWF           REMB0,F
                CLRF            TEMPB1
                CLRW
                BTFSS           STATUS,C
                INCFSZ          TEMPB1,W
                SUBWF           TEMPB0,F

                BTFSC           STATUS,C
                GOTO            UOK68B          
                MOVF            BARGB0,W
                ADDWF           REMB0,F
                CLRF            TEMPB1
                CLRW
                BTFSC           STATUS,C
                INCFSZ          TEMPB1,W
                ADDWF           TEMPB0,F

                BCF             STATUS,C
UOK68B          RLF             AARGB1,F

                DECFSZ          LOOPCOUNT,F
                GOTO            LOOPU1608B

                endm


;****************************************************************************
        
;       16/8 Bit Unsigned Fixed Point Divide 16/8 -> 16.08

;       Input:  16 bit unsigned fixed point dividend in AARGB0, AARGB1
;               8 bit unsigned fixed point divisor in BARGB0

;       Use:    CALL    FXD1608U

;       Output: 16 bit unsigned fixed point quotient in AARGB0, AARGB1
;               8 bit unsigned fixed point remainder in REMB0

;       Result: AARG, REM  <--  AARG / BARG

;       Max Timing:     1+291+2 = 294 clks

;       Min Timing:     1+227+2 = 230 clks

;       PM: 1+39+1 = 41         DM: 7

UDIV1608        CLRF            REMB0

                UDIV1608L

                RETLW           0x00

;*****************************************************************************


     END
