;
; CI-V DDS Controller
;
; Version 1.0 - March 2007
;
; Alex Krist - KR1ST
; 
; This software acts as a Icom CI-V driver between a serial port and the DDS-60
; using the Serial DDS board. It will accept CI-V commands from the serial port 
; and converts them into control words to change the frequency of the DDS. The
; commands supported are "00", "05" and "03". "00" and "05" set the frequency
; of the DDS, and "00" reads the frequency of the DDS. The difference between 
; commands "00" and "05" is that the former does not get achknowleged by the
; controller, while the latter will send an "OK" response to the computer.
; The address of the controller is "5C" by default, which is the address of
; a IC-756Pro. This means that the PC software communicating with the controller
; should be set to work with a Icom IC-756Pro.

	include "P16f628.inc" 
	processor       PIC16F628
        __config 	_XT_OSC & _WDT_OFF & _PWRTE_OFF & _BODEN_OFF & _MCLRE_OFF & _LVP_OFF     
	radix           dec
	errorlevel	-302	; suppress message 302 from list file

; Oscillator frequency currently 180 MHz (6 x 30 MHz).
ref_osc_3	equ		0x17	; MSB
ref_osc_2	equ		0xDC
ref_osc_1	equ		0x65
ref_osc_0	equ		0xDE    ; LSB

; Default contains the default startup frequency as a 32 bit integer.
default_3	equ		0x00	; MSB for 14.025 MHz
default_2	equ		0xD6
default_1	equ		0x01
default_0	equ		0x28    ; LSB

;	I/O Pins, B register bits
DDS_clk		equ		0x02	; AD9851 write clock
DDS_dat		equ		0x03	; AD9851 serial data input
DDS_load	equ		0x07	; Update pin on AD9851

; Serial Port constants
;
; These are used by the serial comm routines for timing. Note that
; slower speeds may not work well because the delays might be larger
; than 255, requiring the use of the prescalar.

_Clkspd			equ		1000000	;external clock / 4
_BaudRate		equ		9600	;desired baud rate
_Period			equ	(_Clkspd/_BaudRate)	;clock cycles / bit

_StartRxDelay	equ	(_Period/2 - 15)/3	;this is how long to
							;wait to get to the
							;middle of the start
							;bit. This is loops,
							;not clock cycles.

_BitRxDelay		equ	277 - _Period	;this is what to load
							;into TMR0 for correct
							;interval between bits
							;on RX

_BitTxDelay		equ	285 - _Period	;this is what to load
							;into TMR0 for correct
							;interval between bits
							;on TX

_StopTxDelay	equ	272 - _Period	;this is what to load
							;into TMR0 for correct
							;interval betweeen last
							;bit and stop bit on TX
; CI-V Constants

OurID 			equ 0x5c	;identifier code of this controller (IC-756Pro radio)

CPUID			equ	0xE0	;identifier of caller (CPU)

SOF				equ	0xFE	;Start Of Frame character

EOF				equ	0xFD	;End Of Frame character

OKResponse		equ	0xFB	;OK CI-V response code

NGResponse		equ	0xFA	;No Good CI-V response code

ReadCommand		equ	0x03	;CI-V Read Command character

ASCII_offset	equ	0x30	; ASCII offset


; constants for PORTA:

_TX		equ	1			;serial output is PORTA,1

; constants for PORTB:

_RX		equ	0			;PORTB,0 receives the serial input

; constants for ascii to binary conversion of input frequency:
; defines for hex equivalent of each of the eight ascii digits to be received:

; billions
_Digit9_byte3	equ 0x3B
_Digit9_byte2	equ 0x9A
_Digit9_byte1	equ	0xCA
_Digit9_byte0	equ	0x00

; 100 millions
_Digit8_byte3	equ 0x05
_Digit8_byte2	equ 0xF5
_Digit8_byte1	equ	0xE1
_Digit8_byte0	equ	0x00

; 10 millions
_Digit7_byte2	equ 0x98
_Digit7_byte1	equ	0x96
_Digit7_byte0	equ	0x80

; millions
_Digit6_byte2	equ	0x0F
_Digit6_byte1	equ	0x42
_Digit6_byte0	equ	0x40

; 100 thousands
_Digit5_byte2	equ	0x01
_Digit5_byte1	equ	0x86
_Digit5_byte0	equ	0xA0

; 10 thousands
_Digit4_byte1	equ	0x27
_Digit4_byte0	equ	0x10

; thousands
_Digit3_byte1	equ	0x03
_Digit3_byte0	equ	0xE8

; hundreds
_Digit2_byte0	equ	0x64

; tens
_Digit1_byte0	equ	0x0A

; ones
_Digit0_byte0	equ	0x01

; Defines for the EEPROM memory locations:

_EEPROM_Freq	equ	0x00
_EEPROM_Start	equ	0x04
_EEPROM_End		equ	0x08
_EEPROM_Step	equ	0x0C

; Variables in general purpose register space
	CBLOCK  0x20	; Start Data Block
	freq_0			; Display frequency (hex) 
		freq_1		;  (4 bytes) 
		freq_2
		freq_3         
	AD9851_0		; AD9851 control word 
		AD9851_1	;  (5 bytes)
		AD9851_2
		AD9851_3
		AD9851_4
	mult_count		; Used in calc_dds_word 
	bit_count		;   "
	byte2send
	osc_0			; Current oscillator 
		osc_1		;  (4 bytes)
		osc_2
		osc_3
	osc_temp_0		; Oscillator frequency 
		osc_temp_1	;  (4 bytes)
		osc_temp_2        
		osc_temp_3
	timer1			; Used in delay routines
	timer2			;   "
	count			; loop counter  (gets reused)

	BitCount		;number of bits left to send or
					;receive, not including start & stop
					;bits

	RXChar			;received character while being received
	RXBuff			;most recently received character

	TXChar			;character to transmit

	SerialReg		;status register:
					;bit 0: on if character has been
					;       received
					;bit 1: on if busy with RX/TX
					;bit 2: on if sending, off if receiving
					;bit 3: on if next bit to send is stop bit

	WSave			;copy of W register

	SSave			;copy of the Status register

	Digit_val		; the value of the current frequency digit being processed
					; (not the ASCII value)

	Add_0			; a four-byte number to add using the Add_DWord subroutine
	Add_1			; (Add_3 is high byte)
	Add_2
	Add_3

	ASCII_Buf:10	; ten-character ASCII buffer

	BCD_value		; to store a BCD value

        ENDC                      ; End of Data Block


; The 16F628 resets to 0x00.                                      * 
        ORG     0x0000                
reset_entry
	movlw	h'07'
	movwf	CMCON	; Turn off Comparator

	goto	start	; Jump around the band table to main program

; The Interrupt vector is at 0x04.                                         *
	org	0x04

; Serial Communication Routines
;
; The serial comm routines generate 1 start bit, 8 data bits, 1 stop bit, 
; no parity. The baud rate is determined by the delay programmed
; into the onboard timer. The sending and receiving is interrupt driven,
; meaning other tasks can be carried on while the characters are being
; sent and received.

; Main Interrupt Routine

; Save the W and STATUS registers:

Int
	movwf	WSave
	swapf	STATUS,W	;use swapf to prevent any
	movwf	SSave		;status flags from being changed

; Check first for a timer overflow interrupt.

	btfsc	INTCON,T0IE
	goto	DoBit		;we're in the middle of sending or
						;receiving

; If not a timer overflow interrupt, check for external interrupt:

	btfsc	INTCON,INTE	;RB0 is our receive line and it
	goto	StartRX		;generates an interrupt on a high-
						;to-low transition

; Restore the W and STATUS registers:

Restore	
	swapf	SSave,W
	movwf	STATUS
	swapf	WSave,F
	swapf	WSave,W

	retfie

;	Subroutine SerSetup

SerSetup

; set up the option register for internal counting, WDT disabled,
; no prescaler.

	clrf	TMR0
	bsf		STATUS,RP0
	clrwdt				;set bits in OPTION_REG to
	movlw	b'10001000'	;enable internal clock counting,
	movwf	OPTION_REG	;disable watchdog timer.
	bcf		STATUS,RP0	;switch to bank 0

; set the output line to idle (high)

	bsf	PORTA,_TX	;set the output line to idle(high) state

; enable the external interrupt via RB0

	movlw	b'10010000'	;set bits in INTCON to enable
	movwf	INTCON		;external interrupt

; initialize the SerialReg:

	clrf	SerialReg

	return

; Subroutine StartRX
;
; This subroutine is called by the main interrupt routine when an
; external interrupt on RB0 occurs. This means we're receiving the
; start bit for a character. We want to enable the external TMR0
; interrupt and prepare to receive the character.

StartRX

; wait halfway through the bit to see if it's real:

	bcf		INTCON,INTF	;clear the interrupt
	movlw	_StartRxDelay
	movwf	BitCount	;this is the 15th instruction since
						;the interrupt. Note--we're using
						;BitCount for this loop purely for
						;convenience. Usually it's used to
						;actually count the bits we TX/RX.

RXWait
	decfsz	BitCount,F	;this loop takes 3 times the initial
	goto	RXWait		;value of BitCount clock cycles
	
; now we should be at the middle of the start bit. Is the input still
; low? If not, goto Restore and ignore this interrupt.

	btfsc	PORTB,_RX
	goto	Restore

; if we get to here it must really be the start bit. Load TMR0,
; disable the external interrupt, and enable the TMR0 interrupt. 

; load up the appropriate delay to get us to the middle of the
; first bit:

	movlw	_BitRxDelay
	movwf	TMR0		;4 cycles from read of PORTB
	movlw	b'00100000'
	movwf	INTCON

; set the SerialReg to indicate that the routines are busy getting
; a character:

	movlw	b'00000010'
	movwf	SerialReg

; initialize BitCount:

	movlw	8
	movwf	BitCount

; okay, now we return.

	goto	Restore

; DoBit
;
; sends or receives the next bit. Bits are sent/received from least
; to most significant bit.

DoBit

; clear the TMR0 overflow interrupt flag:

	bcf	INTCON,T0IF

; Are we receiving?

	btfsc	SerialReg,2
	goto	Sending

; check to see if we're receiving the stop bit:

	movf	BitCount,F
	btfsc	STATUS,Z
	goto	GetStopBit

; if we get to here, we're in the middle of receiving. Get the next
; bit: (16 cycles to get to the next instruction from the start of
; the interrupt).

	rrf	PORTB,W		;rrf PORTB into W. This sets
				;the carry bit if RB0 was high.
	rrf	RXChar,F	;doing a rrf on RXChar brings
				;in the carry bit to the MSB.

; Decrement the bit counter.

	decf	BitCount,F

; reload TMR0 for the next interrupt, and
; go to the end of the interrupt routine.

	movlw	_BitRxDelay	
	movwf	TMR0		;21 cycles from start of interrupt
	goto	Restore

; if we get to here it's because we need to check for the stop bit.

GetStopBit
	btfss	PORTB,_RX	;is the RX line low? If so, it's not
	goto	Done		;the stop bit. Otherwise, set the
	movlw	b'00000001'	;SerialReg to show a character has
	movwf	SerialReg	;been received
	movf	RXChar,W	;copy the received character to RXBuff
	movwf	RXBuff
	goto	Done
		
; We got here because we're sending.
; check to see if we're finished sending the stop bit:

Sending
	btfsc	SerialReg,3
	goto	Done

; check to see if we need to send the stop bit:

	movf	BitCount,F
	btfsc	STATUS,Z	;18th cycle
	goto	SendStopBit

; if we get to here, we're in the middle of sending. Send the next
; bit: (16 cycles to get to the next instruction from the start of
; the interrupt).

	rrf		TXChar,F	;doing rrf on TXChar puts the
	btfss	STATUS,C	;least significant bit in the
	goto	SendZero	;carry flag.
	nop
	bsf		PORTA,_TX	;if carry is set, send a one.
	goto	EndDoBit	;PORTA,_TX is set on the 24th cycle

SendZero
	bcf	PORTA,_TX	;otherwise, send a zero. (24th cycle)
	nop				;nop's are for taking the same time
	nop				;to get to reloading TMR0 as for when
					;a one is sent.

; Decrement the bit counter.

EndDoBit
	decf	BitCount,F

; reload TMR0 for the next interrupt, and
; go to the end of the interrupt routine.

	movlw	_BitTxDelay	
	movwf	TMR0		;29th cycle
	goto	Restore


; Here we need to send the stop bit, turn off the TMR0 interrupt,
; turn on the external interrupt, and set the SerStatus register
; flags appropriately.

SendStopBit
	nop
	nop
	nop	
	bsf		PORTA,_TX		;no. Send the stop bit. (24th cycle)
	bsf		SerialReg,3		;set the "sending stop bit" flag

; reload TMR0 for the next interrupt, and
; go to the end of the interrupt routine.

	movlw	_StopTxDelay
	movwf	TMR0		;27th cycle
	goto	Restore

; we're completely done sending or receiving. Clean up.

Done	movlw	b'00010000'	;set bits in INTCON to enable
	movwf	INTCON		;external interrupt
	movlw	b'00000001'
	andwf	SerialReg,F	;clear the busy bits in SerialReg
	goto	Restore

; Subroutine SendChar
;
; This is not called by the interrupt handler. Rather, it activates 
; the interrupts needed to send it. Put the character to be sent in
; the TXChar file register before calling this subroutine.
;

SendChar
	
; send the start bit:

	bcf	PORTA,_TX

; set the SerStatus to indicate that the routines are busy sending
; a character:

	movlw	b'00000110'
	movwf	SerialReg

; load up TMR0 so it overflows at the right time.

	nop			;for timing
	movlw	_BitTxDelay
	movwf	TMR0		;5th cycle after write to PORTA

; clear the external interrupt flag, disable the external interrupt,
; and enable the TMR0 interrupt.

	movlw	b'10100000'
	movwf	INTCON

; set the BitCount for the eight bits to send:

	movlw	8
	movwf	BitCount

	return

; GetAChar

GetAChar
	call	Idle
	btfss	SerialReg,0	;wait for a character to be received
	goto	GetAChar
	bcf		SerialReg,0
	return

; SendAChar

SendAChar
	call	SendChar

WaitToFinish
	call	Idle
	btfsc	SerialReg,1	;wait for the character to be sent
	goto	WaitToFinish
	return

; ParseCIVCommand
;
; This is a table jump to CIV commands identified by the received
; frame.

ParseCIVCommand
	addwf	PCL,F
	goto	SetFrequencyNoResponse
	goto	NoWhere
	goto	NoWhere
	goto	ReadOperatingFrequency
	goto	NoWhere
	goto	SetFrequency


; Sub_DWord
; subtracts Add_* from freq_*. Stores the result in freq_*. Does
; subtraction using two's complement addition.

Sub_DWord

	comf	Add_0,F		;complement the four bytes
	comf	Add_1,F
	comf	Add_2,F
	comf	Add_3,F
	incf	Add_0,F		;add one
	btfss	STATUS,Z	;handle carry in byte 0
	goto	Add_DWord
	incf	Add_1,F		;handle carry in byte 1
	btfss	STATUS,Z
	goto	Add_DWord	;handle carry in byte 2
	incf	Add_2,F
	btfss	STATUS,Z	;handle carry in byte 3
	goto	Add_DWord
	incf	Add_3,F

	; falls through to Add_DWord now to finish the subtraction


; Add_DWord
; adds freq_* to Add_*. Stores the result in freq_*.

Add_DWord

	movf	freq_3,W
	addwf	Add_3,W
	movwf	freq_3

	movf	freq_2,W
	addwf	Add_2,W
	btfsc	STATUS,C
	call	inc_3
	movwf	freq_2

	movf	freq_1,W
	addwf	Add_1,W
	btfsc	STATUS,C
	call	inc_2
	movwf	freq_1

	movf	freq_0,W
	addwf	Add_0,W
	btfsc	STATUS,C
	call	inc_1
	movwf	freq_0

	return

; inc_1, inc_2, and inc_3 provide a convenient way to increment on carries
; during the add above, in case multiple carries occur on an add

inc_1
	incf	freq_1,F
	btfss	STATUS,Z
	return
inc_2
	incf	freq_2,F
	btfss	STATUS,Z
	return
inc_3
	incf	freq_3,F
	return

; Loop_Add
;
; Loop_Add is called by ASCII_to_Bin to repeatedly add one to the current
; digit. It assumes that Digit_val has been loaded with the ASCII character
; for the digit and Add_3, Add_2, Add_1, Add_0 are the value to add to the
; buffer repeatedly. For example, if Digit_val is '5' and we're working on the
; tens digit, ten is added to the buffer five times. Loop_Add skips the add
; process if Digit_val is '0'.

Loop_Add
	movlw	'0'		;skip if the digit is zero
	subwf	Digit_val,F
	btfsc	STATUS,Z
	return

Loop_Add_Start
	call	Add_DWord
	decfsz	Digit_val,F
	goto	Loop_Add_Start
	return

; ASCII_to_Bin
;
; ASCII_to_Bin converts an 8-character ASCII numeric string to a binary value
; and stores it in buf_3, buf_2, buf_1, buf_0 (buf_3 is high byte).

ASCII_to_Bin
; Billions
	movf	ASCII_Buf,W
	movwf	Digit_val

	movlw	_Digit9_byte3
	movwf	Add_3
	movlw	_Digit9_byte2
	movwf	Add_2
	movlw	_Digit9_byte1
	movwf	Add_1
	movlw	_Digit9_byte0
	movwf	Add_0

	call	Loop_Add

; Hundred Millions
	movf	ASCII_Buf+1,W
	movwf	Digit_val

	movlw	_Digit8_byte3
	movwf	Add_3
	movlw	_Digit8_byte2
	movwf	Add_2
	movlw	_Digit8_byte1
	movwf	Add_1
	movlw	_Digit8_byte0
	movwf	Add_0

	call	Loop_Add

; Ten Millions
	movf	ASCII_Buf+2,W
	movwf	Digit_val

	clrf	Add_3
	movlw	_Digit7_byte2
	movwf	Add_2
	movlw	_Digit7_byte1
	movwf	Add_1
	movlw	_Digit7_byte0
	movwf	Add_0

	call	Loop_Add

; Millions
	movf	ASCII_Buf+3,W
	movwf	Digit_val

	movlw	_Digit6_byte2
	movwf	Add_2
	movlw	_Digit6_byte1
	movwf	Add_1
	movlw	_Digit6_byte0
	movwf	Add_0

	call	Loop_Add

; Hundred Thousands
	movf	ASCII_Buf+4,W
	movwf	Digit_val

	movlw	_Digit5_byte2
	movwf	Add_2
	movlw	_Digit5_byte1
	movwf	Add_1
	movlw	_Digit5_byte0
	movwf	Add_0

	call	Loop_Add

; Ten Thousands
	movf	ASCII_Buf+5,W
	movwf	Digit_val

	clrf	Add_2
	movlw	_Digit4_byte1
	movwf	Add_1
	movlw	_Digit4_byte0
	movwf	Add_0

	call	Loop_Add

; Thousands
	movf	ASCII_Buf+6,W
	movwf	Digit_val

	movlw	_Digit3_byte1
	movwf	Add_1
	movlw	_Digit3_byte0
	movwf	Add_0

	call	Loop_Add

; Hundreds
	movf	ASCII_Buf+7,W
	movwf	Digit_val

	clrf	Add_1
	movlw	_Digit2_byte0
	movwf	Add_0

	call	Loop_Add

; Tens
	movf	ASCII_Buf+8,W
	movwf	Digit_val

	movlw	_Digit1_byte0
	movwf	Add_0

	call	Loop_Add

; Ones
	movf	ASCII_Buf+9,W
	movwf	Digit_val

	movlw	_Digit0_byte0
	movwf	Add_0

	call	Loop_Add
	
	return


; Loop_Subtract
;
; used by Bin_To_ASCII
; continues to subtract the contents of Add_* from freq_* until
; an underflow is detected, then adds Add_* back once to reverse
; the underflow. For each successful subtraction, increments
; INDF (which should point to a digit in ASCII_Buf).

Loop_Subtract
	movlw	'0'
	movwf	INDF

	; make the first subtraction. After this call, Add_*
	; contains the two's complement of the original entry.
	; Thus, subsequent subtractions (in the loop below)
	; should be performed using Add_DWord, since adding the
	; two's complement is the same as subtracting the original
	; value and this saves us the trouble of reloading the
	; original value.

	call	Sub_DWord

Loop_Subtract_Loop
	btfsc	freq_3,7			; test for negative result
	goto	Loop_Subtract_End	; jump out of the loop if negative
	incf	INDF,F
	call	Add_DWord			;see explanation above for why
								;we call Add_DWord and not Sub_DWord
	goto	Loop_Subtract_Loop

Loop_Subtract_End

	; add Add_* back to freq_* to make it positive again. Use Sub_DWord
	; since Add_* contains the two's complement of the value to add back.

	call	Sub_DWord
	return

; Bin_To_ASCII
;
; takes the value in freq_*, converts it to ASCII, and stores it
; in ASCII_Buf. NOTE: freq_* contents get blown away in the process.

Bin_To_ASCII
; Billions
	movlw	ASCII_Buf
	movwf	FSR

	movlw	_Digit9_byte3
	movwf	Add_3
	movlw	_Digit9_byte2
	movwf	Add_2
	movlw	_Digit9_byte1
	movwf	Add_1
	movlw	_Digit9_byte0
	movwf	Add_0

	call	Loop_Subtract

; Hundred Millions
	;movlw	ASCII_Buf
	;movwf	FSR
	incf	FSR,F
	movlw	_Digit8_byte3
	movwf	Add_3
	movlw	_Digit8_byte2
	movwf	Add_2
	movlw	_Digit8_byte1
	movwf	Add_1
	movlw	_Digit8_byte0
	movwf	Add_0

	call	Loop_Subtract

; Ten Millions
	;movlw	ASCII_Buf + 2
	;movwf	FSR
	incf	FSR,F
	clrf	Add_3
	movlw	_Digit7_byte2
	movwf	Add_2
	movlw	_Digit7_byte1
	movwf	Add_1
	movlw	_Digit7_byte0
	movwf	Add_0

	call	Loop_Subtract

; Millions
	incf	FSR,F
	clrf	Add_3
	movlw	_Digit6_byte2
	movwf	Add_2
	movlw	_Digit6_byte1
	movwf	Add_1
	movlw	_Digit6_byte0
	movwf	Add_0

	call	Loop_Subtract

; Hundred Thousands
	incf	FSR,F
	clrf	Add_3
	movlw	_Digit5_byte2
	movwf	Add_2
	movlw	_Digit5_byte1
	movwf	Add_1
	movlw	_Digit5_byte0
	movwf	Add_0

	call	Loop_Subtract

; Ten Thousands
	incf	FSR,F
	clrf	Add_3
	clrf	Add_2
	movlw	_Digit4_byte1
	movwf	Add_1
	movlw	_Digit4_byte0
	movwf	Add_0

	call	Loop_Subtract

; Thousands
	incf	FSR,F
	clrf	Add_3
	clrf	Add_2
	movlw	_Digit3_byte1
	movwf	Add_1
	movlw	_Digit3_byte0
	movwf	Add_0

	call	Loop_Subtract

; Hundreds
	incf	FSR,F
	clrf	Add_3
	clrf	Add_2
	clrf	Add_1
	movlw	_Digit2_byte0
	movwf	Add_0

	call	Loop_Subtract

; Tens
	incf	FSR,F
	clrf	Add_3
	clrf	Add_2
	clrf	Add_1
	movlw	_Digit1_byte0
	movwf	Add_0

	call	Loop_Subtract

; Ones
	incf	FSR,F
	clrf	Add_3
	clrf	Add_2
	clrf	Add_1
	movlw	_Digit0_byte0
	movwf	Add_0

	call	Loop_Subtract
	
	return


; GetFrequencyFromASCII
;
; GetFrequencyFromASCII converts the character string to binary and stores it in
; freq_*

GetFrequencyFromASCII

	clrf	freq_0
	clrf	freq_1
	clrf	freq_2
	clrf	freq_3

	call	ASCII_to_Bin

	return


; Idle
;
; Idle should be called whenever the chip is waiting for something
; to happen (waiting for a character to be sent or received, for
; example). Currently, Idle doesn't do anything.

Idle
	return

; get_char waits for a serial character to arrive from the controlling PC
; and then echos it.

get_char
	btfss	SerialReg,0		;has character been received?
	goto	get_char		;no, loop again
	bcf		SerialReg,0		;got a char, so clear the flag
	movf	RXBuff,W		;move the rx char into W
	movwf	TXChar		
	call	SendAChar		;echo the char
	movf	RXBuff,w
	return

; calc_dds_word
;
; Purpose:  Multiply the 32 bit number for oscillator frequency times the 
;           32 bit number for the displayed frequency.
;
;   Input:  The reference oscillator value in osc_3 ... osc_0 and the
;           current frequency stored in freq_3 ... freq_0.  The reference
;           oscillator value is treated as a fixed point real, with a 24
;           bit mantissa.
;
;  Output:  The result is stored in AD9851_3 ... AD9851_0.

calc_dds_word
        clrf    AD9851_0          ; Clear the AD9851 control word bytes
        clrf    AD9851_1          ; 
        clrf    AD9851_2          ; 
        clrf    AD9851_3          ; 
        clrf    AD9851_4          ; 

		movlw   0x20              ; Set count  to 32   (4 osc bytes of 8 bits)
        movwf   mult_count        ; Keep running count
        movf    osc_0,w           ; Move the four osc bytes
        movwf   osc_temp_0        ;   to temporary storage for this multiply
        movf    osc_1,w           ; (Don't disturb original osc bytes)
        movwf   osc_temp_1        ; 
        movf    osc_2,w           ; 
        movwf   osc_temp_2        ; 
        movf    osc_3,w           ; 
        movwf   osc_temp_3        ; 
mult_loop
        bcf     STATUS,C          ; Start with Carry clear
        btfss   osc_temp_0,0      ; Is bit 0 (Least Significant bit) set?
        goto    noAdd             ; No, don't need to add freq term to total
        movf    freq_0,w          ; Yes, get the freq_0 term
        addwf   AD9851_1,f        ;   and add it in to total
        btfss   STATUS,C          ; Does this addition result in a carry?
        goto    add7              ; No, continue with next freq term
        incfsz  AD9851_2,f        ; Yes, add one and check for another carry
        goto    add7              ; No, continue with next freq term
        incfsz  AD9851_3,f        ; Yes, add one and check for another carry
        goto    add7              ; No, continue with next freq term
        incf    AD9851_4,f        ; Yes, add one and continue
add7
        movf    freq_1,w          ; Use the freq_1 term
        addwf   AD9851_2,f        ; Add freq term to total in correct position
        btfss   STATUS,C          ; Does this addition result in a carry?
        goto    add8              ; No, continue with next freq term
        incfsz  AD9851_3,f        ; Yes, add one and check for another carry
        goto    add8              ; No, continue with next freq term
        incf    AD9851_4,f        ; Yes, add one and continue
add8
        movf    freq_2,w          ; Use the freq_2 term
        addwf   AD9851_3,f        ; Add freq term to total in correct position
        btfss   STATUS,C          ; Does this addition result in a carry?
        goto    add9              ; No, continue with next freq term
        incf    AD9851_4,f        ; Yes, add one and continue
add9
        movf    freq_3,w          ; Use the freq_3 term
        addwf   AD9851_4,f        ; Add freq term to total in correct position
noAdd
        rrf     AD9851_4,f        ; Shift next multiplier bit into position
        rrf     AD9851_3,f        ; Rotate bits to right from byte to byte
        rrf     AD9851_2,f        ; 
        rrf     AD9851_1,f        ; 
        rrf     AD9851_0,f        ; 
        rrf     osc_temp_3,f      ; Shift next multiplicand bit into position
        rrf     osc_temp_2,f      ; Rotate bits to right from byte to byte
        rrf     osc_temp_1,f      ; 
        rrf     osc_temp_0,f      ; 
        decfsz  mult_count,f      ; One more bit has been done.  Are we done?
        goto    mult_loop         ; No, go back to use this bit
        movlw   0x01              ; Set the 6x reference oscilator bit
        movwf   AD9851_4	      ; 
        return                    ; Done.

; send_dds_word
;
; Purpose:  This routine sends the AD9851 control word to the DDS chip
;           using a serial data transfer.
;
;   Input:  AD9851_4 ... AD9851_0
;
;  Output:  The DDS chip register is updated.
;
send_dds_word
        movlw   AD9851_0          ; Point FSR at AD9851
        movwf   FSR               ; 
next_byte
        movf    INDF,w            ; 
        movwf   byte2send         ; 
        movlw   0x08              ; Set counter to 8
        movwf   bit_count         ; 
next_bit
        rrf     byte2send,f       ; Test if next bit is 1 or 0
        btfss   STATUS,C          ; Was it zero?
        goto    send0             ; Yes, send zero
        bsf     PORTB,DDS_dat     ; No, send one
        bsf     PORTB,DDS_clk     ; Toggle write clock
        bcf     PORTB,DDS_clk     ;
        goto    break             ; 
send0
        bcf     PORTB,DDS_dat     ; Send zero
        bsf     PORTB,DDS_clk     ; Toggle write clock
        bcf     PORTB,DDS_clk     ;
break
        decfsz  bit_count,f       ; Has the whole byte been sent?
        goto    next_bit          ; No, keep going.
        incf    FSR,f             ; Start the next byte unless finished
        movlw   AD9851_4+1        ; Next byte (past the end)
        subwf   FSR,w             ; 
        btfss   STATUS,C          ;
        goto    next_byte         ;
        bsf     PORTB,DDS_load    ; Send load signal to the AD9851
        bcf     PORTB,DDS_load    ;
        return                    ;

; wait
;
; Purpose:  Wait for a specified number of milliseconds.
;
;           Entry point wait_a_sec:  Wait for 1 second
;           Entry point wait_256ms:  Wait for 256 msec
;           Entry point wait_128ms:  Wait for 128 msec
;           Entry point wait_64ms :  Wait for 64 msec
;           Entry point wait_32ms :  Wait for 32 msec
;           Entry point wait_16ms :  Wait for 16 msec
;           Entry point wait_8ms  :  Wait for 8 msec
;
;   Input:  None
;
;  Output:  None
;
wait_a_sec_and_a_half  ; ****** Entry point ******    
        call    wait_256ms        ;       
        call    wait_256ms        ;       
        call    wait_256ms        ;       
        call    wait_256ms        ;       
        call    wait_256ms        ;       
        call    wait_256ms        ;       
		return
wait_a_sec  ; ****** Entry point ******    
        call    wait_256ms        ;       
        call    wait_256ms        ;       
        call    wait_256ms        ;       
        call    wait_256ms        ;       
        return
wait_256ms  ; ****** Entry point ******    
        call    wait_128ms        ;
        call    wait_128ms        ;
        return
wait_128ms  ; ****** Entry point ******    
        movlw   0xFF              ; Set up outer loop 
        movwf   timer1            ;   counter to 255
        goto    outer_loop        ; Go to wait loops
wait_64ms  ; ****** Entry point ******     
        movlw   0x80              ; Set up outer loop
        movwf   timer1            ;   counter to 128
        goto    outer_loop        ; Go to wait loops
wait_32ms   ; ****** Entry point ******    
        movlw   0x40              ; Set up outer loop
        movwf   timer1            ;   counter to 64
        goto    outer_loop        ; Go to wait loops
wait_16ms   ; ****** Entry point ******    
        movlw   0x20              ; Set up outer loop
        movwf   timer1            ;   counter to 32  
        goto    outer_loop        ; Go to wait loops
wait_8ms   ; ****** Entry point ******     
        movlw   0x10              ; Set up outer loop
        movwf   timer1            ;   counter to 16
                                  ; Fall through into wait loops
;
; Wait loops used by other wait routines
;  - 1 microsecond per instruction (with a 4 MHz microprocessor crystal)
;  - 510 instructions per inner loop
;  - (Timer1 * 514) instructions (.514 msec) per outer loop
;  - Round off to .5 ms per outer loop
;
outer_loop                        
        movlw   0xFF              ; Set up inner loop counter
        movwf   timer2            ;   to 255
inner_loop
        decfsz  timer2,f          ; Decrement inner loop counter
        goto    inner_loop        ; If inner loop counter not down to zero, 
                                  ;   then go back to inner loop again
        decfsz  timer1,f          ; Yes, Decrement outer loop counter
        goto    outer_loop        ; If outer loop counter not down to zero,
                                  ;   then go back to outer loop again
        return                    ; Yes, return to caller



; Start of the program
;

start
	clrf	INTCON          ; No interrupts ... for now
	bsf		STATUS,RP0      ; Switch to bank 1
	bcf		0x01,7          ; Enable weak pullups
	movlw	0xF5            ; Setup PortA 
	movwf	TRISA           ; Set port A to all Inputs except RA1 and RA3
	movlw	0x01            ; Setup PORTB
	movwf	TRISB           ; Set port B to all outputs except RB0
	bcf		STATUS,RP0      ; Switch back to bank 0

    movlw   0x00			; clear out the buffer in the 9851
    movwf   AD9851_0		; to make sure it is a defined state
    movlw   0x00    
    movwf   AD9851_1
    movlw   0x00
    movwf   AD9851_2
    movlw   0x00
    movwf   AD9851_3
    movlw   0x00
    movwf   AD9851_4
 	call	send_dds_word 
	
;
; Set the power on frequency to the defined value (14.025 MHz).
;
	movlw	ref_osc_3
	movwf	osc_3
	movlw	ref_osc_2
	movwf	osc_2
	movlw	ref_osc_1
	movwf	osc_1
	movlw	ref_osc_0
	movwf	osc_0

	movlw	freq_0
	call	Get_Frequency	  ; get frequency from EEPROM and into freq_*

	call	calc_dds_word     ; Convert to delta value
	call	send_dds_word     ; Send the power-on frequency to the DDS
	call	send_dds_word     ; Send the power-on frequency to the DDS (to be sure)
	call	wait_128ms		  ; a short delay in case the oscillator is slow
				              ; getting up to speed
	call	SerSetup	  	  ; set up serial comm routines & int.

MainLoop

	call	GetAChar	; get char from user
	
	; is it a Start Of Frame charachter?
	movlw	SOF
	subwf	RXBuff,W
	btfss	STATUS,Z
	goto	MainLoop 	; received characer was not SOF

	call	GetAChar	; see if we find the second SOF character
	movlw	SOF
	subwf	RXBuff,W
	btfss	STATUS,Z
	goto	MainLoop 	; received characer was not second SOF character

	; at this point we are receiving what appears to be a valid frame.
	; now let's look and see if the frame is meant for us and if it came
    ; from a CPU.
	call	GetAChar	; get char from CPU
	
	; is it for us?
	movlw	OurID
	subwf	RXBuff,W
	btfss	STATUS,Z
	goto	MainLoop 	; this frame is not for us

	call	GetAChar	; get char from CPU

	; is it sent by the CPU?
	movlw	CPUID
	subwf	RXBuff,W
	btfss	STATUS,Z
	goto	MainLoop 	; not sent by CPU, and we only listen to that

	; so far we have started to receive a frame that is indeed meant from us and it
    ; came from the CPU (PC). now let's see what the the CPU wants from us.

	call	GetAChar	; get the command character from CPU
	
	;movlw	6			; make sure the command code is < 6
	;subwf	RXBuff,W
	;btfsc	STATUS,C	;Carry bit will be clear if result is negative
	;goto	MainLoop 	;not negative -- out of range; go wait for new frame.

	movf	RXBuff,W
	call	ParseCIVCommand

	goto	MainLoop	; we're done, so let's wait for the next command


; The following subs handle the various CIV commands:

; SetFrequencyNoResponse
; sets the DDS to the frequency in the CIV frame, but does
; not send a response back
	
SetFrequencyNoResponse
	;get the BCD frequency from the CI-V frame
	call	Get_Digits

	; convert it to binary and store it in freq_*
	call	GetFrequencyFromASCII

	; command the DDS to change frequency:
	call    calc_dds_word
    call    send_dds_word

	; save the frequency in EEPROM:
	call	Save_Frequency

	return


; ReadOperatingFrequency
;
; reads the frequency the DDS is set to and sends it back
; to the CPU in a CIV frame.

ReadOperatingFrequency
	call	Send_Output_Frequency
	return


; SetFrequency
;
; sets the DDS to the frequency in the CIV frame, but does
; send a response back to the CPU
	
SetFrequency
	; get the BCD frequency from the CI-V frame
	call 	Get_Digits

	; convert it to binary and store it in freq_*
	call	GetFrequencyFromASCII

	; command the DDS to change frequency:
    call    calc_dds_word
    call    send_dds_word

	; save the frequency in EEPROM:
	call	Save_Frequency

	; send an OK response back to the CPU
	call	Send_OK_Response

	return


; NoWhere
;
; the command is not supported, so we're going nowhere

NoWhere
	return


; Send_CRLF
;
; Send_CRLF sends a carriage return followed by a line feed.

Send_CRLF
	movlw	0x0D
	movwf	TXChar
	call	SendAChar
	movlw	0x0A
	movwf	TXChar
	call	SendAChar
	return


; Save_Frequency
;
; gets the current frequency from freq_* and stores it in EEPROM

Save_Frequency
	movlw	freq_0
	movwf	FSR
	movlw	_EEPROM_Freq
	call	Write_EEPROM
	
	return

; Get_Frequency
;
; Gets the current frequency from EEPROM and stores it in
; address given in W.

Get_Frequency
	movwf	FSR
	movlw	_EEPROM_Freq
	call	Read_EEPROM

	return

; Get_Digits
;
; Get_Digits gets up to eight digits from the serial port.

Get_Digits
	; prefill ASCII_Buf with 0s
	movlw	10
	movwf	count
	movlw	ASCII_Buf
	movwf	FSR

Prefill_Buf_Loop
	clrf	INDF
	incf	FSR,F	
	decfsz	count,F
	goto	Prefill_Buf_Loop
	
	; count will keep track of space remaining in buffer
	movlw	10
	movwf	count
	movlw	ASCII_Buf + 9
	movwf	FSR

Get_Digits_Loop
	call	GetAChar

	; is it an End Of Frame charachter?
	movlw	EOF
	subwf	RXBuff,W
	btfsc	STATUS,Z
	goto	EndOfFrame
	
	;safe BCD value
	movfw	RXBuff
	movwf	BCD_value

    ;convert lower nibble to ascii
	andlw	0x0f		; clear bits 4-7
	addlw	ASCII_offset	; add 30h to make ascii
	;movwf	RXBuff
	
	;check to see if there's room in the buffer:

	movf	count,F		; will set the Z flag if zero
	btfsc	STATUS,Z
	goto	Get_Digits_Loop

	; there's room in the buffer. Store it.
	;movf	RXBuff,W
	movwf	INDF
	decf	FSR,F
	decf	count,F

	;swap the nibbles of the BCD value and store in W
	swapf	BCD_value, W

    ;convert higher nibble to ascii
	andlw	0x0f		; clear bits 4-7
	addlw	ASCII_offset		; add 30h to make ascii
	;movwf	RXBuff

	;check to see if there's room in the buffer:

	movf	count,F		; will set the Z flag if zero
	btfsc	STATUS,Z
	goto	Get_Digits_Loop

	; there's room in the buffer. Store it.
	;movf	RXBuff,W
	movwf	INDF
	decf	FSR,F
	decf	count,F

	goto	Get_Digits_Loop

EndOfFrame
	;got an EOF character. Stop getting digits.
	return

	;not full. Loop to move the characters to the end of the buffer


; Send_Output_Frequency
;
; sends the contents of ASCII_Buf over the serial port in a CI-V frame
; This happens as a result of an inquiring CI-V command frame.

Send_Output_Frequency
    ; get the frequency from the EEPROM
	movlw	freq_0
	call	Get_Frequency

	; convert to ASCII
	call	Bin_To_ASCII

	; send two SOF (Start Of Frame) characters
	movlw	SOF
	movwf	TXChar
	call	SendAChar
	movlw	SOF
	movwf	TXChar
	call	SendAChar

	; send CPUID as to-address
	movlw	CPUID
	movwf	TXChar
	call	SendAChar

	; send Our ID as from-address
	movlw	OurID
	movwf	TXChar
	call	SendAChar

	; send Read_Command character
	movlw	ReadCommand
	movwf	TXChar
	call	SendAChar

	; now send the ascii buffer in BCD format
	movlw	5
	movwf	count
	movlw	ASCII_Buf + 9
	movwf	FSR

	; now send the frequency in BCD format

Loop_Output_ASCII_Buffer
	movf	INDF,W		; load first char of frequency in W
	movwf	TXChar	
	movlw	ASCII_offset
	subwf	TXChar,F	; subtract 30h to find BCD value
	swapf	TXChar,F	; swap the nibbles of the BCD value

	decf	FSR,F
	movf	INDF,W		; get next char of frequency
	addwf	TXChar,F	; add to previous BCD value and store in W
	movlw	ASCII_offset
	subwf	TXChar,F	; subtract 30h to find BCD value
	swapf	TXChar,F	; swap the nibbles of the BCD value

	; TXChar now contains two digits of the frequency in BCD format

	;movwf	TXChar
	call	SendAChar
	decf	FSR,F
	decfsz	count,F
	goto	Loop_Output_ASCII_Buffer

	; send EOF (End Of Frame) character
	movlw	EOF
	movwf	TXChar
	call	SendAChar

	return


; Send_OK_Response

Send_OK_Response

	; send two SOF (Start Of Frame) characters
	movlw	SOF
	movwf	TXChar
	call	SendAChar
	movlw	SOF
	movwf	TXChar
	call	SendAChar

	; send CPUID as to address
	movlw	CPUID
	movwf	TXChar
	call	SendAChar

	; send Our ID as from address
	movlw	OurID
	movwf	TXChar
	call	SendAChar

	; send Read_Command character
	movlw	OKResponse
	movwf	TXChar
	call	SendAChar

	; send EOF (End Of Frame) character
	movlw	EOF
	movwf	TXChar
	call	SendAChar

	return


; Send_NoGood_Response

Send_NoGood_Response

	; send two SOF (Start Of Frame) characters
	movlw	SOF
	movwf	TXChar
	call	SendAChar
	movlw	SOF
	movwf	TXChar
	call	SendAChar

	; send CPUID as to address
	movlw	CPUID
	movwf	TXChar
	call	SendAChar

	; send Our ID as from address
	movlw	OurID
	movwf	TXChar
	call	SendAChar

	; send Read_Command character
	movlw	NGResponse
	movwf	TXChar
	call	SendAChar

	; send EOF (End Of Frame) character
	movlw	EOF
	movwf	TXChar
	call	SendAChar

	return

; Read_EEPROM
;
; gets a four byte word starting at the index given in W, and stores
; it starting at INDF.

Read_EEPROM
	bsf	STATUS,RP0
	movwf	EEADR
	bcf	STATUS,RP0
	movlw	4
	movwf	count

Read_EEPROM_Loop
	bsf	STATUS,RP0
	bsf	EECON1,RD
	movf	EEDATA,W
	incf	EEADR,F
	movwf	INDF
	incf	FSR,F
	bcf	STATUS,RP0
	decfsz	count,F
	goto	Read_EEPROM_Loop
	return

; Write_EEPROM
;
; writes a four byte word starting at the index given in W, reading it
; from INDF.

Write_EEPROM
	bsf	STATUS,RP0
	movwf	EEADR
	bcf	STATUS,RP0
	movlw	4
	movwf	count
	bcf	INTCON,GIE

Write_EEPROM_Loop
	bsf	STATUS,RP0
	movf	INDF,W
	movwf	EEDATA
	clrf	EECON1
	bsf	EECON1,WREN
	movlw	0x55
	movwf	EECON2
	movlw	0xAA
	movwf	EECON2
	bsf	EECON1,WR
	btfsc	EECON1,WR	; End of writing test
	goto	$-1		; Loop until end
	bcf	EECON1,EEIF	; Clear EEIF bit telling end of writing sequence
	incf	EEADR,F
	bcf	STATUS,RP0
	bcf	PIR1,EEIF	; ditto in PIR1 register
	incf	FSR,F
	decfsz	count,F
	goto	Write_EEPROM_Loop
	bsf	INTCON,GIE
	return


; initialize EEPROM to startup values:

	org	0x2100

	; currently-set values for frequency and scanning
	de	0x28, 0x01, 0xD6, 0x00	; current frequency (lo to hi byte)
	de	0x80, 0x9F, 0xD5, 0x00	; lower band edge for scan
	de	0xB0, 0xF6, 0xDA, 0x00	; upper band edge for scan
	de	0x64, 0x00, 0x00, 0x00	; step size for scan & step

	END
