;******************************************************************************
; Copyright © [01/25/1999] Scenix Semiconductor, Inc. All rights reserved.
;
 ;Scenix Semiconductor, Inc. assumes no responsibility or liability for
; the use of this [product, application, software, any of these products].
; Scenix Semiconductor conveys no license, implicitly or otherwise, under
; any intellectual property rights.
; Information contained in this publication regarding (e.g.: application,
; implementation) and the like is intended through suggestion only and may
; be superseded by updates. Scenix Semiconductor makes no representation
; or warranties with respect to the accuracy or use of these information,
; or infringement of patents arising from such use or otherwise.
;******************************************************************************
;
; Filename:	bell_103_full_dup_1_13.src
;
; Author:	Chris Fogelklou
;		Applications Engineer
;		Scenix Semiconductor Inc.
;
; Revision:	1.08
;
; Date:		April 16, 1999.
;
; Part:		SX28AC rev. 2.5
;
; Freq:		50Mhz
;
; Compiled using Parallax SX-Key software v1.0
;
; Version:	1.10
;
; Program Description:	This simple program encodes an outgoing bell103 signal.
;			This program only creates the answer frequencies, since it
;			cannot dial out to originate.
;
;			To use this program, connect the SX-DTMF-DEMO board to a phone
;			line and to a PC.  The communications settings are 300,N,8,1.
;			Now, from a remote BELL103 modem, simply dial the modem's number.
;			When you here the line ringing on the dialing modem, press a key
;			in the comm window of the SX-modem-board.  This should force the
;			modem board off-hook and cause it to send out an answer tone for
;			3 seconds.  Once the answer tone is sent, the modem board will
;			begin modulating the state of the RS-232 pin onto the carrier.
;			If the terminal program is set up correctly, you will be able to
;			receive the sent characters on the remote modem.
;
; Revision History:  	1.0  Tried for weeks to get FSK-receive to work.  Finally got it
;			working.
;			1.01 Tried to eliminate all unnecessary code...
;			1.04 Originate and answer modes both working.  AT command set added.
;			1.10 Changed the fsk receive code so it is simpler to understand and
;				uses less RAM.
;
;	INPUTS:
;	Received RS-232 characters on rs-232 rx_pin (ra.1)
;	OUTPUTS:
;	Received RS-232 characters on tx_pin (ra.2)
;	FSK output on PPM_pin
;	LED flashes (rb.0)
;
;	RESOURCES:
;       Program memory: TBD
;       Data memory:	TBD
;       I/O Count:	TBD
;
;******************************************************************************
; Device Directives
;******************************************************************************
SX28L_compiler
IFDEF SX28L_compiler
		device	SX28L,oscxt4,carryx		; 28-pin device, 4 pages, 8 banks of RAM
		device	turbo,stackx_optionx		; High speed oscillator, turbo mode,
							; option register extend, 8-level stack
ELSE
		device	pins28,pages4,banks8,carryx	; 28-pin device, 1 pages, 8 banks of RAM
		device	oschs,turbo,optionx,stackx	; High speed oscillator, turbo mode,
ENDIF							; option register extend, 8-level stack
		freq	50_000_000			; default run speed = 50MHz
		ID	'B103TX10'			; Version = 1.0

		reset	reset_entry			; JUMP to reset_entry label on reset


;******************************************************************************
; Watches (For Debug in SX_Key software V.1.0 +)
;******************************************************************************
watch	fsk_answering,1,ubin
watch	freq_acc_low,16,udec
watch	freq_count_low,16,udec
watch	sine_index,8,udec
watch	D_to_A_val,8,udec
watch	freq_acc_high,16,uhex
watch	freq_count_high,8,uhex
watch	freq_count_low,8,uhex
watch	freq_count_high2,8,uhex
watch	freq_count_low2,8,uhex
watch	byte,1,fstr
watch	byte,8,udec
watch	curr_sine,8,sdec
watch	sine_index,8,sdec
watch	D_to_A_val,8,udec
watch	PPM0_acc,8,udec
watch	timer_flag,1,ubin
watch	timer_l,16,uhex
watch	temp,8,uhex
watch	wreg,8,uhex
watch	fsk_last_trans,8,udec

watch	fsk_trans_avg_l,16,udec
watch	fsk_avg_count,8,udec
watch	fsk_trans_count,8,udec
watch	fsk_glitch_acc,8,udec
watch	fsk_high_byte,8,udec
watch	fsk_average_index,8,udec
watch	fsk_temp_trans,8,udec
watch	fsk_processing_required1,1,ubin
watch	fsk_processing_required2,1,ubin
watch	fsk_carrier_detected,1,ubin
watch	fsk_rb_past_state,8,ubin


;**************************************************************************
; Equates for common data comm frequencies
;**************************************************************************
f697_h		equ	$012	; DTMF Frequency
f697_l		equ	$09d

f770_h		equ	$014	; DTMF Frequency
f770_l		equ	$090

f852_h		equ	$016	; DTMF Frequency
f852_l		equ	$0c0

f941_h		equ	$019	; DTMF Frequency
f941_l		equ	$021

f1209_h		equ	$020	; DTMF Frequency
f1209_l		equ	$049

f1336_h		equ	$023	; DTMF Frequency
f1336_l		equ	$0ad

f1477_h		equ	$027	; DTMF Frequency
f1477_l		equ	$071

f1633_h		equ	$02b	; DTMF Frequency
f1633_l		equ	$09c
;******************************************************************************
; Equates for FSK generation
;******************************************************************************
f2225_h		equ	$03b
f2225_l		equ	$06b

f2025_h		equ	$036
f2025_l		equ	$014

f1070_h		equ	$01c
f1070_l		equ	$093

f1270_h		equ	$021
f1270_l		equ	$0ea

f2100_h		equ	$038	; 2100Hz Signifies LOW data in Bell202 Spec
f2100_l		equ	$015

;**************************************************************************
; Equates for certain baud rates: 
;**************************************************************************
;baud_bit	=       4                       ;for 19200 baud
;start_delay	=       16+8+1                  ; "    "     "
;int_period	=       163                     ; "    "     "
; *** 2400 baud (for slower baud rates, increase the RTCC prescaler)
baud_bit	=       7                       ;for 2400 baud
start_delay	=       128+64+1                ; "    "    "
fsk_start_delay	=	128+96+1
int_period	=       163                     ; "    "    "
;******************************************************************************
; Pin Definitions (These definitions are for SX DTMF DEMO boards)
;******************************************************************************
PPM_pin		equ	ra.0	; D/A output pin
rx_pin		equ	ra.1	; RS-232 reception pin
tx_pin		equ	ra.2	; RS-232 transmission pin
nothing		equ	ra.3	; N/C
ra_dir_mask	equ	%11111010	; sets up the I/O directions of port ra
ra_data_mask	equ	%11111111	; sets up the output levels of the output pins on ra

led_pin		equ	rb.0	; LED pin
rxa_pin		equ	rb.1	; FSK receive pin
cntrl_1		equ	rb.2	; drive cntrl_1 low to disable the output of the LPF
ring		equ	rb.3	; ring detection pin
hook		equ	rb.4	; drive hook low to go off-hook
cntrl_3		equ	rb.5	; drive cntrl_3 low to disable the output of the HPF
rts		equ	rb.6	; indicates to the SX that the PC wants to transmit data
cts		equ	rb.7	; indicates to the PC that the SX is ready to receive data
rb_dir_mask	equ	%01101110	; sets up the I/O directions of port rb
rb_data_mask	equ	%01011011	; sets up the output levels of the output pins on ra
LPF_mask	equ	%01001110	; when LPF is enabled, tristate cntrl 1 and put cntrl 3 low
HPF_mask	equ	%01101010	; when HPF is enabled, tristate cntrl 3 and put cntrl 1 low
LPF_HPF_mask	equ	%01101110	; when both filters are enabled, tristate cntrl 1 and cntrl 3


dtmf_in_pin	equ	rc.0
dtmf_fdbk_pin	equ	rc.1
AtoD_in_pin	equ	rc.2
AtoD_fdbk_pin	equ	rc.3
imp_450_pin	equ	rc.4
imp_600_pin	equ	rc.5
imp_750_pin	equ	rc.6                  
imp_900_pin	equ	rc.7
rc_dir_mask	equ	%11010101	; sets up the I/O directions of port rc
rc_data_mask	equ	%00001111	; sets up the output levels of the output pins on ra
;******************************************************************************
; Global Variables
;******************************************************************************
		org	$8		; Global registers

	flags	ds	1
		dtmf_gen_en	equ	flags.0	; Signifies whether or not DTMF output is enabled
		sine_gen_en	equ	flags.1
		timer_flag	equ	flags.2
		fsk_tx_en	equ	flags.3
		fsk_rx_en	equ	flags.4 ; Enables the FSK receiver.
		rx_flag		equ	flags.5
		fsk_rx_flag	equ	flags.6
	temp		ds	1
	task_switcher	ds	1
	ascii_index	ds	1
	command_index	ds	1
;******************************************************************************
; Bank 0 Variables
;******************************************************************************
		org	$10

sine_gen_bank		=	$
	freq_acc_low		ds	1	; 16-bit accumulator which decides when to increment the sine wave
	freq_acc_high		ds	1	; 
	freq_count_low		ds	1	; 16-bit counter which decides which frequency for the sine wave
	freq_count_high		ds	1	; freq_count = Frequency * 6.83671552
	sine_index		ds	1
	sine_index2		ds	1	; The velocity of the sin wave
	freq_count_low2		ds	1	; 16-bit counter which decides which frequency for the sine wave
	freq_count_high2	ds	1	; freq_count = Frequency * 6.83671552
	freq_acc_high2		ds	1	; 
	freq_acc_low2		ds	1	; 16-bit accumulator which decides when to increment the sine wave
	curr_sine		ds	1	; The current value of the imitation sin wave
	curr_sine2		ds	1	; The current value of the imitation sin wave
	sine2_temp		ds	1	; This register is used to do a temporary shift/add register

PPM_bank		=	$

PPM0_acc		ds	1		; PPM accumulator
D_to_A_val			ds	1		; current PPM output

;******************************************************************************
; Bank 1 Variables
;******************************************************************************
		org	$30
timers			=	$
	timer_l		ds	1
	timer_h		ds	1
	timer_hh	ds	1
serial		=       $                       ;UART bank

tx_high		ds      1                       ;hi byte to transmit
tx_low		ds      1                       ;low byte to transmit
tx_count	ds      1                       ;number of bits sent
tx_divide	ds      1                       ;xmit timing (/16) counter
rx_count	ds      1                       ;number of bits received
rx_divide	ds      1                       ;receive timing counter
rx_byte		ds      1                       ;buffer for incoming byte
rx_count2	ds      1                       ;number of bits received
rx_divide2	ds      1                       ;receive timing counter
rx_byte2	ds      1                       ;buffer for incoming byte
string		ds	1
byte		ds	1
plus_count	ds	1

;******************************************************************************
;	Bank 2 Variables
;******************************************************************************
		org     $50                     ;bank3 variables
fsk_receive_bank	=	$
fsk_transmit_bank	=	$

	fsk_last_trans		ds	1
	fsk_trans_avg_l		ds	1
	fsk_trans_avg_h		ds	1
	fsk_avg_count		ds	1
	fsk_trans_count		ds	1		; This register counts the number of counts 
							; between transitions at the pin
	fsk_rb_past_state 	ds	1		; This register keeps track of the previous 
							; state of port RB, to watch for transitions
	fsk_glitch_acc		ds	1
	fsk_average		ds	1
	fsk_high_byte		ds	1
	fsk_average_index	ds	1
	fsk_temp_trans		ds	1
	fsk_no_carrier_count	ds	1

	fsk_flags		ds	1
	fsk_answering		equ	fsk_flags.0
	fsk_tx_bit		equ	fsk_flags.1
	fsk_rx_bit		equ	fsk_flags.2
	fsk_processing_required1 equ	fsk_flags.3
	fsk_processing_required2 equ	fsk_flags.4
	fsk_rx_bit_last		equ	fsk_flags.5
	fsk_carrier_detected	equ	fsk_flags.6
	fsk_answer_tone		equ	fsk_flags.7
;*************************************************************
; Bank 4, 5, 6, 7 (for ascii buffer, but can be reused.)
;*************************************************************
		org	$90
	ascii_buffer		=	$
		org	$b0
	ascii_buffer2		=	$
		org	$d0
	ascii_buffer3		=	$
		org	$f0
	ascii_buffer4		=	$

;************************ Beginning of program space ***************************
;******************************************************************************
; Interrupt
		org	$0			; The interrupt Service routine starts at location zero.
; 
; With a retiw value of -163 and an oscillator frequency of 50MHz, this
; code runs every 3.26us.
;******************************************************************************
PPM_output
		bank	PPM_bank		; Update the PPM pin
		clc
		add	PPM0_acc,D_to_A_val
		snc
		setb	PPM_pin
		sc
		clrb	PPM_pin
;******************************************************************************
FSK_output
		jnb	dtmf_gen_en,:dtmf_disabled
		call	@sine_generator1
		jmp	:task_switcher
:dtmf_disabled
		snb	sine_gen_en		; Output the frequencies set by the freq_count registers
		call	@sine_generator2
		snb	fsk_rx_en
		call	@fsk_receive
:task_switcher
		inc	task_switcher
		mov	w,task_switcher
		and	w,#$07
		clc
		jmp	pc+w
		jmp	:fsk_process_1	;0
		jmp	:fsk_process_2 	;1
		jmp	:fsk_process_3	;2
		jmp	:fsk_transmit	;3

		jmp	:fsk_get_carrier;4
		jmp	do_timers	;5
		jmp	:transmit	;6
		jmp	:receive	;7


:fsk_process_1
		call	@FSK_RECEIVE_PROCESSING1
		jmp	do_timers
:fsk_process_2
		call	@FSK_RECEIVE_PROCESSING2
		jmp	do_timers
:fsk_process_3
		call	@FSK_RECEIVE_PROCESSING3
		jmp	do_timers
:fsk_transmit
		snb	fsk_tx_en
		call	@TRANSMIT_FSK		; into its corresponding frequencies
		jmp	do_timers
:fsk_get_carrier
		call	@FSK_GET_CARRIER
		jmp	do_timers
		
;**************************************************************************
:transmit
; This is an asynchronous RS-232 transmitter
; INPUTS:
;	tx_divide.baud_bit  -	Transmitter only executes when this bit is = 1
;	tx_high		    -	Part of the data to be transmitted
;	tx_low		    -	Some more of the data to be transmitted
;	tx_count	    -	Counter which counts the number of bits transmitted.
; OUTPUTS:
;	tx_pin		    -	Sets/Clears this pin to accomplish the transmission.
;**************************************************************************
		bank	serial
		clrb    tx_divide.baud_bit      ;clear xmit timing count flag
		inc     tx_divide               ;only execute the transmit routine
		STZ                             ;set zero flag for test
		SNB     tx_divide.baud_bit      ; every 2^baud_bit interrupt
		test    tx_count                ;are we sending?
		JZ      do_timers	        ;if not, go to :receive
		clc                             ;yes, ready stop bit
		rr      tx_high                 ; and shift to next bit
		rr      tx_low                  ;
		dec     tx_count                ;decrement bit counter
		movb    tx_pin,/tx_low.6        ;output next bit
		jmp	do_timers
;**************************************************************************
:receive
; This is an asynchronous receiver for RS-232 reception
; INPUTS:
;	rx_pin		   -	Pin which RS-232 is received on.
; OUTPUTS:
;	rx_byte		   -	The byte received
;	rx_flag		   -	Set when a byte is received.
;**************************************************************************
		bank	serial
		movb    c,rx_pin                ;get current rx bit
		test    rx_count                ;currently receiving byte?
		jnz     :rxbit                  ;if so, jump ahead
		mov     w,#9                    ;in case start, ready 9 bits
		sc                              ;skip ahead if not start bit
		mov     rx_count,w              ;it is, so renew bit count
		mov     rx_divide,#start_delay  ;ready 1.5 bit periods
:rxbit		djnz    rx_divide,:rxdone       ;middle of next bit?
		setb    rx_divide.baud_bit      ;yes, ready 1 bit period
		dec     rx_count                ;last bit?
		sz                              ;if not
		rr      rx_byte                 ;  then save bit
		snz                             ;if so
		setb    rx_flag                 ;  then set flag
:rxdone

;**************************************************************************
do_timers
		bank	timers			; Update the timers
		inc	timer_l
		snz
		inc	timer_h
		snz
		setb	timer_flag
		snz
		inc	timer_hh
		setb	led_pin
		sb	timer_h.6
		clrb	led_pin
;******************************************************************************
:ISR_DONE
; This is the end of the interrupt service routine.  Now load 163 into w and
; perform a retiw to interrupt 163 cycles from the start of this one.  
; (3.26us@50MHz)
;******************************************************************************
		mov	w,#-163		;1	; interrupt 163 cycles after this interrupt
		retiw			;3	; return from the interrupt
;******************************************************************************
; End of the Interrupt Service Routine
;******************************************************************************

;**************************************************************************
time_n_ticks
; This subroutine times 'w' ticks, and returns with a '1' in w when 
; the specified time has timed out.  Each tick is 213.647 ms.
; This subroutine uses the TEMP register
; INPUT		w	-	# of milliseconds to delay for.
; OUTPUT	Returns after n milliseconds.
;**************************************************************************
	bank	timers
	test	wreg
	jz	:check_time
	clc
	add	w,timer_hh
	mov	temp,w
	retw	0
:check_time
	mov	w,temp
	xor	w,timer_hh
	sz	
	retw	0
	retw	1
;******************************************************************************
reset_entry
; Program Starts Here on Power Up
;******************************************************************************

		call	@init

		mov	!option,#%00011111	; enable wreg and rtcc interrupt
		mov	w,#_hello
		call	@send_string

main_2
_send_prompt	mov	w,#_CR
		call	@send_string
		mov	w,#_prompt			; send prompt
		call	@send_string

_cmd_loop
		jnb	rx_flag,$
		clrb	rx_flag
		bank	serial
		mov	byte,rx_byte
		call	@uppercase			; convert it to uppercase
		stc
		cje	byte,#$20,_cmd_loop		; if it equals a space, ignore it.
		stc
		cje	byte,#$0d,:enter		; if it equals a carriage return, parse the string.
		mov	w,byte				; if it does not resemble the above characters, echo it.
		call	@send_byte
		stc
		cje	byte,#$08,:backspace		; if it equals a backspace, delete one character in the buffer.
		call	@buffer_push			; otherwise, store it
		jmp	_cmd_loop			; and come back for more.
:backspace
		call	@buffer_backspace
		jmp	_cmd_loop

:enter							; If the user presses enter, then parse the string.


;**************************************************************************
; String parser (Checks to see if buffer = any commands)
; -Checks contents of ascii buffer against any commands stored in ROM
; -If a command = the contents of the ascii buffer, a routine will be called
; -Each routine MUST perform a retw 0 on exit, or parse_string will not 
;  know that a routine has run and it should exit back to command mode.
; -Exits back to command mode when it detects a zero after the table look-up.
; -Outputs 'OK' if no commands are matched.
;**************************************************************************
parse_string
		clr	ascii_index			; Clear the index into the ascii buffer
		clr	command_index			; And the index into the commands
		
:loop		call	@buffer_get			; Get a vale from the buffer at ascii_index
		call	command_table			; Get a character from one of the commands
		test	wreg				; If the return value is 0, then this matched
		jz	:nothing			; the command and ran a routine.  Exit.
		bank	serial				
		xor	w,byte				; compare the command's character with the 
		jnz	:not_equal			; buffer's character.
		call	@inc_ascii_index		; Increment the index into the buffer.
		jmp	:loop
:not_equal
		inc	command_index			; If the buffer did not equal the command,
		clr	ascii_index			; start from the beginning of a new command 
		stc
		cjne	command_index,#6,:loop		; and the buffer.  (This number = # of commands)
:nothing	mov	w,#_CR
		call	@send_string
		mov	w,#_OK				; If we have checked all 4 commands, then this
		call	@send_string			; did not equal any so send an 'OK' message.
:done
		bank	ascii_buffer
		clr	ascii_index
		clr	ascii_buffer
		jmp	_send_prompt

;**************************************************************************
command_table
		mov	w,command_index
		clc
		add	pc,w
		jmp	command_1
		jmp	command_2
		jmp	command_3
		jmp	command_4
		jmp	command_5
		jmp	command_6
;**************************************************************************
command_1					; Dial command
		mov	w,ascii_index
		add	PC,w
		retw	'A'
		retw	'T'
		retw	'D'
		retw	'T'
		jmp	DIAL_MODE
;**************************************************************************
command_2					; Hang up command
		mov	w,ascii_index
		add	PC,w
		retw	'A'
		retw	'T'
		retw	'H'
		jmp	HANG_UP
;**************************************************************************
command_3					; Initialize
		mov	w,ascii_index
		add	PC,w
		retw	'A'
		retw	'T'
		retw	'Z'
		jmp	INITIALIZE
;**************************************************************************
command_4					; Answer/ Auto answer
		mov	w,ascii_index
		add	PC,w
		retw	'A'
		retw	'T'
		retw	'A'
		jmp	AUTO_ANSWER
;**************************************************************************
command_5					; Data mode
		mov	w,ascii_index
		add	PC,w
		retw	'A'
		retw	'T'
		retw	'O'
		jmp	FSK_IO
;**************************************************************************
command_6					; Help
		mov	w,ascii_index
		add	PC,w
		retw	'?'
		jmp	HELP
;**************************************************************************
; END of String parser (Checks to see if buffer = any commands)
;**************************************************************************
HANG_UP
		setb	hook
		retw	0
INITIALIZE
		call	@init
		clr	flags
		retw	0
AUTO_ANSWER
		jmp	Answer
		retw	0
HELP		retw	0
				
	;**************************************************************************
	; Dial Mode:
	; -Dials contents of ascii buffer, starting from location pointed
	;  to by ascii_index.
	; -Responds to these commands:
	; 	0-9, *, #	- Dials the specified number
	;	,		- Pause for 2 seconds
	; -Jumps to data mode after dialing.
	;**************************************************************************
DIAL_MODE
		mov	w,#_CR
		call	@send_string
		mov	w,#_DIALING			; send Dialing
		call	@send_string
		clrb	hook
		
		mov	w,#255
		call	@delay_10n_ms
		bank	serial

:dial_loop	call	@buffer_get			; wait for an input character
		call	@uppercase			; convert it to uppercase
		mov	w,byte
		snz
		jmp	:originate_mode
		call	@send_byte
		cje	byte,#',',:pause		; if the character = ',', pause for 2s
		call	@digit_2_index			; convert the ascii digit to an 
							; index value
		call	@load_frequencies		; load the frequency registers
		call	@dial_it			; dial the number for 60ms and return.
:inc		call	@inc_ascii_index		; increment the index into the table
		jmp	:dial_loop
:pause
		mov	w,#201				; delay 2s
		call	@delay_10n_ms
		jmp	:inc


:originate_mode
	;******************************************************************
	; Go off-hook
	;******************************************************************
		mov	m,#$0f
		mov	!rb,#HPF_mask	; Enable HPF and disable LPF
		clrb	cntrl_1
		clr	flags
		bank	fsk_transmit_bank
		clrb	fsk_answering
		setb	fsk_rx_en
		setb	fsk_tx_bit
		setb	sine_gen_en
		setb	fsk_tx_en
		mov	w,#140				; wait 30 seconds for answer_tone
		call	time_n_ticks
:loop
;		clr	w
;		call	time_n_ticks
;		test	wreg
;		jnz	no_carrier
;		bank	fsk_receive_bank
;		jnb	fsk_answer_tone,:loop
		jmp	FSK_IO

Answer
		clrb	hook				; Go off-hook
		mov	m,#$0f
		mov	!rb,#LPF_mask	; Enable LPF and disable HPF
		clrb	cntrl_3
		call	@answer_tone			; Send out the answer tone for 3 seconds
		bank	fsk_transmit_bank		; Clear all the flags
		clr	flags
		setb	fsk_answering			; enable answering frequencies
		setb	fsk_tx_bit			; Set the tx_bit to output a high frequency
		setb	fsk_tx_en			; enable FSK transmit
		setb	sine_gen_en			; enable sine generation
		mov	w,#255
		call	@delay_10n_ms
		mov	w,#255
		call	@delay_10n_ms
		bank	fsk_receive_bank		; enable FSK receive
		setb	fsk_rx_en

FSK_IO

		mov	w,#47
		call	time_n_ticks
:loop
		clr	w
		call	time_n_ticks
		test	wreg
;		jnz	no_carrier
		bank	fsk_receive_bank
;		jnb	fsk_carrier_detected,:loop

		mov	w,#_DATA_MODE
		call	@send_string
		mov	w,#_prompt
		call	@send_string
		clr	plus_count
:loop2
		bank	fsk_receive_bank
;		jnb	fsk_carrier_detected,no_carrier
		movb	fsk_tx_bit,rx_pin	; move the rs-232 pin to the fsk_tx_bit
		movb	tx_pin,fsk_rx_bit	; and move the fsk_rx_bit to the rs-232 pin
		jb	rx_flag,:check_for_plus
		jmp	:loop2			; jump here forever (ISR does all the work)
:check_for_plus
		bank	serial
		clrb	rx_flag
		inc	plus_count
		mov	w,#'+'
		xor	w,rx_byte
		sz
		clr	plus_count
		mov	w,#3
		xor	w,plus_count
		snz
		retw	0
		jmp	:loop2

no_carrier
		setb	tx_pin
		mov	w,#255
		call	@delay_10n_ms
		mov	w,#_no_carrier
		call	@send_string
		bank	serial
:loop		test	tx_count
		jz	INITIALIZE
		jmp	:loop
		


org	$200

;******************************************************************************
FSK_RECEIVE
;**************************************************************************
		bank	fsk_receive_bank	; switch to fsk_receive_bank of RAM

		inc	fsk_trans_count		; Increment the transition count
		snz
		dec	fsk_trans_count		; If the result is zero, keep fsk_trans_count in range
		clc
		mov	w,fsk_rb_past_state	; Check for a transition
		xor	w,rb
		and	w,#%00000010
		snz
		retp				; return if there has been no transition
		xor	fsk_rb_past_state,w	; save the new value of the FSK_pin
		jb	fsk_answering,:dont_double
		sb	fsk_rb_past_state.1
		retp
:dont_double
		clc
		mov	w,#-30
		add	w,fsk_trans_count
		jnc	:glitch
		mov	fsk_temp_trans,fsk_trans_count
		clc
		add	fsk_trans_avg_l,w
		snc
		inc	fsk_trans_avg_h
		inc	fsk_avg_count
		clr	fsk_trans_count
		setb	fsk_processing_required1
		retp

:glitch		; Save any glitches in the glitch accumulator, to be added to 
		; the transition count before processing
		clc
		add	fsk_glitch_acc,fsk_trans_count
		retp



;**************************************************************************
FSK_RECEIVE_PROCESSING1	; This code is not very speed critical and can
			; run every so often in the ISR.
;**************************************************************************

		bank	fsk_receive_bank
		sb	fsk_processing_required1
		retp
		clrb	fsk_processing_required1
		setb	fsk_processing_required2
		retp


;**************************************************************************
FSK_RECEIVE_PROCESSING2	; This code is not very speed critical and can
			; run every so often in the ISR.
;**************************************************************************
	;******************************************************************
	; If no processing is required, exit
	;******************************************************************
		bank	fsk_receive_bank		;1
		sb	fsk_processing_required2	;1
		retp					;1
		clrb	fsk_processing_required2	;1

	;******************************************************************
	; Add the last transition time to this one, and divide by two.
	; and also add the glitch accumulator.
	;******************************************************************

		clc
		rr	fsk_glitch_acc
		clc
		mov	w,fsk_last_trans
		add	w,fsk_temp_trans
		rr	wreg
		clc
		add	w,fsk_glitch_acc
		clr	fsk_glitch_acc
		
	;******************************************************************
	; Now compare the result to 130 for answer mode or to 145 for
	; originate mode.  Anything above this threshold is a "high" bit,
	; and anything below this threshold is a "low" bit.
	;******************************************************************
		mov	fsk_last_trans,w		;1
		mov	w,#-130				;1
		sb	fsk_answering			;1
		mov	w,#-145				;1
		clc					;1
		add	w,fsk_last_trans		;1
		setb	fsk_rx_bit			;1
		snc					;1
		clrb	fsk_rx_bit			;1;50
							;3;53
		mov	fsk_last_trans,fsk_temp_trans
		retp
;**************************************************************************
FSK_RECEIVE_PROCESSING3
;**************************************************************************

		retp
		bank	fsk_receive_bank
		sb	fsk_rx_bit
		jmp	:rx_bit_low
:rx_bit_high
		snb	fsk_rx_bit_last
		retp
		setb	fsk_rx_bit_last
		mov	w,#121
		sb	fsk_answering
		mov	w,#138
		jmp	:hysterises
:rx_bit_low
		sb	fsk_rx_bit_last
		retp
		clrb	fsk_rx_bit_last
		mov	w,#144
		sb	fsk_answering
		mov	w,#152
:hysterises
		mov	fsk_last_trans,w
		retp
;**************************************************************************
FSK_GET_CARRIER
;**************************************************************************
		bank	fsk_receive_bank
		mov	w,fsk_avg_count
		sz
		retp
		inc	fsk_avg_count
		clrb	fsk_answer_tone
		clrb	fsk_carrier_detected
		jb	fsk_answering,:low_freqs
		mov	w,#-135
		clc
		add	w,fsk_trans_avg_h
		jnc	:get_answer_tone; If the average transition
					; time is less than 138, error...
		mov	w,#-152
		clc
		add	w,fsk_trans_avg_h
		sc
		setb	fsk_carrier_detected
:get_answer_tone
		mov	w,#-142
		clc
		add	w,fsk_trans_avg_h
		jnc	:no_answer_tone
		mov	w,#-148
		clc
		add	w,fsk_trans_avg_h
		sc
		setb	fsk_answer_tone
:no_answer_tone
		clr	fsk_trans_avg_h
		clr	fsk_trans_avg_l
		retp
:low_freqs
		mov	w,#-118
		clc
		add	w,fsk_trans_avg_h
		jnc	:no_carrier	; If the average transition
					; time is less than 138, error...
		mov	w,#-145
		clc
		add	w,fsk_trans_avg_h
		sc	
		setb	fsk_carrier_detected
:no_carrier	clr	fsk_trans_avg_h
		clr	fsk_trans_avg_l
		retp
		

		
		
		
;**************************************************************************
TRANSMIT_FSK
;**************************************************************************
		bank	fsk_transmit_bank
		jb	fsk_answering,transmit_answer_tones
transmit_originate_tones
		jnb	fsk_tx_bit,:low_freq
:high_freq
		bank	sine_gen_bank
		mov	freq_count_high2,#f1270_h
		mov	freq_count_low2,#f1270_l
		retp
:low_freq	
		bank	sine_gen_bank
		mov	freq_count_high2,#f1070_h
		mov	freq_count_low2,#f1070_l
		retp
transmit_answer_tones
		jnb	fsk_tx_bit,:low_freq
:high_freq
		bank	sine_gen_bank
		mov	freq_count_high2,#f2225_h
		mov	freq_count_low2,#f2225_l
		retp
:low_freq	
		bank	sine_gen_bank
		mov	freq_count_high2,#f2025_h
		mov	freq_count_low2,#f2025_l
		retp
;**************************************************************************
answer_tone
;**************************************************************************
		bank	sine_gen_bank		; send out the answer tone for 3 seconds
		clr	curr_sine
		mov	freq_count_high2,#f2100_h 
		mov	freq_count_low2,#f2100_l
		setb	sine_gen_en		; enable the FSK transmitter
		mov	w,#255
		call	@delay_10n_ms
		mov	w,#45
		call	@delay_10n_ms
		retp
;**************************************************************************
org	$300
;**************************************************************************
; String data (for RS-232 output) and tables
;**************************************************************************
_hello          dw      13,10,'SX Modem V 4.0',13,10,0
_instructions	dw	'- ? For Help',0
_DIALING	dw	'DIAL ',0
_ANSWERING	dw	'ANSWERING ',0
_AUTO_ANSWER	dw	'AUTO ANSWER ',13,10,0
_RING		dw	'RING',13,10,0
_PROMPT		dw	13,10,'>',0
_HANGING_UP	dw	'HANG UP ',13,10,0
_ATDT		dw	13,10,'ATDT=',0
_ATA		dw	'ATA =',0
_ATH		dw	'ATH =',0
_ATZ		dw	'ATZ =',0
_ATO		dw	'ATO =',0
_plus		dw	13,10,'+++ =',0
_OK		dw	'OK',13,10,0
_CR		dw	13,10,0
_COMMAND_MODE	dw	'COMMAND MODE',13,10,0
_DATA_MODE	dw	13,10,'CONNECT 300',0
_no_carrier	dw	13,10,'NO CARRIER',0
_INIT		dw	'INIT',0

org	$400	; Miscellaneous subroutines
;**************************************************************************
buffer_push
; This subroutine pushes the contents of byte onto the 32-byte ascii buffer. 
;**************************************************************************
	bank	serial			; Move the byte into the buffer
	mov	temp,byte
	mov	fsr,#ascii_buffer
	clc
	add	fsr,ascii_index
	mov	indf,temp
					; Increment index and keep it in range
	call	@inc_ascii_index
	mov	fsr,#ascii_buffer	; Null terminate the buffer.
	clc
	add	fsr,ascii_index
	clr	indf
	bank	serial
	retp
;**************************************************************************
;**************************************************************************
buffer_backspace
; This subroutine deletes one value of the buffer and decrements the index 
;**************************************************************************
	dec	ascii_index
	and	ascii_index,#%01101111

	mov	fsr,#ascii_buffer
	clc
	add	fsr,ascii_index
	clr	indf
	bank	serial
	retp
;**************************************************************************
inc_ascii_index
; This subroutine increments the index into the buffer
;**************************************************************************
	mov	w,ascii_index
	and	w,#%00001111
	xor	w,#%00001111
	jnz	:not_on_verge
	inc	ascii_index
	mov	w,#16
	clc
	add	w,ascii_index
	and	w,#$7f
	mov	ascii_index,w
	retp
:not_on_verge
	inc	ascii_index
	retp
;**************************************************************************
buffer_get
; This subroutine retrieves the buffered value at index
;**************************************************************************
	mov	fsr,#ascii_buffer
	clc
	add	fsr,ascii_index
	mov	w,indf
	bank	serial
	mov	byte,w
	
	retp
;**************************************************************************
delay_10n_ms
; This subroutine delays 'w'*10 milliseconds. 
; This subroutine uses the TEMP register
; INPUT		w	-	# of milliseconds to delay for.
; OUTPUT	Returns after 10 * n milliseconds.
;**************************************************************************
	mov	temp,w
	bank	timers
:loop	clrb	timer_flag	; This loop delays for 10ms
	mov	timer_h,#$0f4
	mov	timer_l,#$004
	jnb	timer_flag,$
	dec	temp		; do it w-1 times.
	jnz	:loop
	clrb	timer_flag
	retp
;**************************************************************************
delay_n_ms
; This subroutine delays 'w' milliseconds. 
; This subroutine uses the TEMP register
; INPUT		w	-	# of milliseconds to delay for.
; OUTPUT	Returns after n milliseconds.
;**************************************************************************
	mov	temp,w
	bank	timers
:loop	clrb	timer_flag	; This loop delays for 1ms
	mov	timer_h,#$0fe
	mov	timer_l,#$0cd
	jnb	timer_flag,$
	dec	temp		; do it w-1 times.
	jnz	:loop
	clrb	timer_flag
	retp
;**************************************************************************
zero_ram
; Subroutine - Zero all ram.
; INPUTS:	None
; OUTPUTS:	All ram locations (except special function registers) are = 0
;**************************************************************************
		CLR	FSR
:loop	    	SB      FSR.4                   ;are we on low half of bank?
		SETB    FSR.3                   ;If so, don't touch regs 0-7
		CLR     IND                     ;clear using indirect addressing
		IJNZ    FSR,:loop	        ;repeat until done
		retp
;**************************************************************************
init
;**************************************************************************
		mov	m,#$0d
		mov	!ra,#$00		; initialize all ports to CMOS levels
		mov	!rb,#$00
		mov	!rc,#$00
		mov	m,#$0f			; initialize the ports
		mov	!ra,#ra_dir_mask
		mov	ra,#ra_data_mask
		mov	!rb,#rb_dir_mask
		mov	rb,#rb_data_mask
		mov	!rc,#rc_dir_mask
		mov	rc,#rc_data_mask
		setb	hook			; go on hook.
		setb	led_pin			; turn on LED
		clr	flags			; Clear all flags
		call	zero_ram
		retp
;**************************************************************************
; Subroutine - Get byte via serial port and echo it back to the serial port
; INPUTS:
;	-NONE
; OUTPUTS:
;	-received byte in rx_byte
;**************************************************************************
get_byte     	jnb     rx_flag,$		;wait till byte is received
		clrb    rx_flag		;reset the receive flag
		bank	serial
		mov     byte,rx_byte		;store byte (copy using W)
						; & fall through to echo char back
;**************************************************************************
; Subroutine - Send byte via serial port
; INPUTS:
;	w 	-	The byte to be sent via RS-232
;**************************************************************************
send_byte    	bank    serial

:wait        	test    tx_count                ;wait for not busy
		jnz     :wait                   ;

		not     w                       ;ready bits (inverse logic)
		mov     tx_high,w               ; store data byte
		setb    tx_low.7                ; set up start bit
		mov     tx_count,#10            ;1 start + 8 data + 1 stop bit
		RETP                            ;leave and fix page bits

;**************************************************************************
; Subroutine - Send string pointed to by address in W register
; INPUTS:
;	w	-	The address of a null-terminated string in program
;			memory
; OUTPUTS:
; 	outputs the string via. RS-232
;**************************************************************************
send_string	bank	serial
 		mov     string,w                ;store string address
:loop        	mov     w,string                ;read next string character
		mov     m,#3                    ; with indirect addressing
		iread                           ; using the mode register
		mov     m,#$F                   ;reset the mode register
		test    w                       ;are we at the last char?
		snz                             ;if not=0, skip ahead
		RETP                            ;yes, leave & fix page bits
		call    send_byte               ;not 0, so send character
		inc     string                  ;point to next character
		jmp     :loop                   ;loop until done

;**************************************************************************
; Subroutine - Make byte uppercase
; INPUTS:
;	byte	-	The byte to be converted
; OUTPUTS:
;	byte	-	The uppercase byte
;**************************************************************************
uppercase    	stc
		csae	  byte,#'a'            	;if byte is lowercase, then skip ahead
		RETP
		stc
		sub     byte,#'a'-'A'           ;change byte to uppercase
		RETP                            ;leave and fix page bits
;**************************************************************************
; Subroutine - Disable the outputs
; Load DC value into PPM and disable the output switch.
;**************************************************************************
disable_o
		bank	PPM_bank	; input mode.
		mov	D_to_A_val,#128	; put 2.5V DC on PPM output pin
		retp
;**************************************************************************
org	$600			; These subroutines are on page 3.
;**************************************************************************
; DTMF transmit functions/subroutines
;**************************************************************************
;**************************************************************************
DTMF_TABLE	; DTMF tone constants
; This routine returns with the constant used for each of the frequency
; detectors.
; INPUT:	w	-	Index into the table (0-15 value)
; OUTPUT:	w	-	Constant at that index
;**************************************************************************
		clc
		jmp	PC+w
		retw	f697_l
		retw	f697_h			
		retw	f770_l
		retw	f770_h
		retw	f852_l
		retw	f852_h	
		retw	f941_l
		retw	f941_h
		retw	f1209_l
		retw	f1209_h
		retw	f1336_l
		retw	f1336_h
		retw	f1477_l
		retw	f1477_h
		retw	f1633_l
		retw	f1633_h
;**************************************************************************
ASCII_TABLE	; Ascii value at index (0-15)
; INPUT:	w	-	Index into the table (0-15 value)
; OUTPUT:	w	-	Constant at that index
;**************************************************************************
		clc
		jmp	PC+w
		retw	'1'
		retw	'2'			
		retw	'3'
		retw	'A'
		retw	'4'
		retw	'5'	
		retw	'6'
		retw	'B'
		retw	'7'
		retw	'8'
		retw	'9'
		retw	'C'
		retw	'*'
		retw	'0'
		retw	'#'
		retw	'D'
;**************************************************************************
index_2_digit
; This subroutine converts a digit from 0-9 or a '*' or a '#' to a table 
; lookup index which can be used by the load_frequencies subroutine.  To use
; this routine, pass it a value in the 'byte' register.  No invalid digits
; are used. (A, B, C, or D)
;**************************************************************************
		call	ASCII_TABLE
		retp
;**************************************************************************
digit_2_index
; This subroutine converts a digit from 0-9 or a '*' or a '#' to a table 
; lookup index which can be used by the load_frequencies subroutine.  To use
; this routine, pass it a value in the 'byte' register.  No invalid digits
; are used. (A, B, C, or D)
;**************************************************************************
		bank	serial
		clr	temp
:loop
		mov	w,temp
		call	ASCII_TABLE
		xor	w,byte
		jz	:done
		inc	temp
		jb	temp.4,:done
		jmp	:loop

:done		mov	w,temp
		retp
		
;**************************************************************************
load_frequencies
; This subroutine loads the frequencies using a table lookup approach.
; The index into the table is passed in the byte register.  The DTMF table
; must be in the range of $400 to $500.
;**************************************************************************
		mov	temp,w
		bank	sine_gen_bank

		mov	w,>>temp
		and	w,#%00000110
		call	DTMF_TABLE
		mov	freq_count_low,w

		mov	w,>>temp
		and	w,#%00000110
		inc	wreg
		call	DTMF_TABLE
		mov	freq_count_high,w

		rl	temp
		setb	temp.3
		mov	w,temp
		and	w,#%00001110
		mov	temp,w
		call	DTMF_TABLE
		mov	freq_count_low2,w

		mov	w,temp
		inc	wreg
		call	DTMF_TABLE
		mov	freq_count_high2,w
		retp		
		
;**************************************************************************
dial_it		; This subroutine puts out whatever frequencies were loaded
		; for 1000ms, and then stops outputting the frequencies.
;**************************************************************************
		bank	sine_gen_bank
		clr	sine_index
		clr	sine_index2
		enable_o 			; enable the output
		mov	w,#10
		call	@delay_10n_ms		; delay 30ms
		setb	dtmf_gen_en
		mov	w,#12
		call	@delay_10n_ms		; delay 100ms
		clrb	dtmf_gen_en
		call	@disable_o		; now disable the outputs
:end_dial_it	retp
;**************************************************************************
sine_generator1				;(Part of interrupt service routine)
; This routine generates a synthetic sine wave with values ranging
; from -32 to 32.  Frequency is specified by the counter.  To set the
; frequency, put this value into the 16-bit freq_count register:
; freq_count = FREQUENCY * 6.83671552 (@50MHz)
;**************************************************************************
		bank	sine_gen_bank
		
		clc
		add	freq_acc_low,freq_count_low
		add	freq_acc_high,freq_count_high
		sc
		jmp	:no_change
		inc	sine_index
		mov	w,sine_index
		and	w,#$1f
		call	sine_table
	
		mov	curr_sine,w		;1		; add the velocity to sin


:no_change

;**************************************************************************
sine_generator2						;(Part of interrupt service routine)
; This routine generates a synthetic sine wave with values ranging
; from -32 to 32.  Frequency is specified by the counter.  To set the
; frequency, put this value into the 16-bit freq_count register:
; freq_count = FREQUENCY * 6.83671552 (@50MHz)
;**************************************************************************
		bank	sine_gen_bank
		clc
		add	freq_acc_low2,freq_count_low2
		add	freq_acc_high2,freq_count_high2
		sc
		jmp	:no_change
		inc	sine_index2
		mov	w,sine_index2
		and	w,#$1f
		call	sine_table
		mov	curr_sine2,w
:no_change
		mov	D_to_A_val,curr_sine2			; mov sin2 into PPM0
		mov	sine2_temp,w			; mov the high_frequency sin wave's current value
		clc					 ; into a temporary register
		snb	sine2_temp.7			; divide temporary register by four by shifting right
		stc					 ; (for result = (0.25)(sin2))
		rr	sine2_temp
		clc
		snb	sine2_temp.7
		stc
		mov	w,>>sine2_temp
		clc
		add	D_to_A_val,w				; (1.25)(sin2) = sin2 + (0.25)(sin2)
		add	D_to_A_val,curr_sine			; add the value of SIN into the PPM output
		add	D_to_A_val,#128			; for result = PPM0 = 1.25*sin2 + 1*sin
		retp					; return with page bits intact
;******************************************************************************
sine_table
; The values in this table can be changed to increase/decrease the amplitude of
; the output sine wave.
;******************************************************************************
	clc
	jmp	pc+w
	retw	0
	retw	4
	retw	8
	retw	11
	retw	14
	retw	16
	retw	18
	retw	19
	retw	20
	retw	19
	retw	18
	retw	16
	retw	14
	retw	11
	retw	8
	retw	4
	retw	0
	retw	-4
	retw	-8
	retw	-11
	retw	-14
	retw	-16
	retw	-18
	retw	-19
	retw	-20
	retw	-19
	retw	-18
	retw	-16
	retw	-14
	retw	-11
	retw	-8
	retw	-4
;**************************************************************************
;******************************************************************************
;        Copyright © 1998 Scenix Semiconductor, Inc. All rights
;        reserved.
;        
;        Scenix Semiconductor, Inc. assumes no responsibility or liability for
;        the use of this [product, application, software, any of these products].
;        
;        Scenix Semiconductor conveys no license, implicitly or otherwise, under
;        any intellectual property rights.
;        Information contained in this publication regarding (e.g.: application,
;        implementation) and the like is intended through suggestion only and may
;        be superseded by updates. Scenix Semiconductor makes no representation
;        or warranties with respect to the accuracy or use of these information,
;        or infringement of patents arising from such use or otherwise.
;        
;        Scenix Semiconductor products are not authorized for use in life support
;        systems or under conditions where failure of the product would endanger
;        the life or safety of the user, except when prior written approval is
;        obtained from Scenix Semiconductor.
;******************************************************************************
