Frequency Detection on an Ubicom SX Microcontroller

Chris Fogelklou

Introduction:

This is a demonstration of a Scenix SX microcontroller performing frequency detection on an analog signal. The user selects a frequency, and the SX determines the amount of the selected frequency present in the input signal. The magnitude of the signal is then output to the terminal in a "level-meter" display. Several Virtual Peripherals are utilized for this application:

• Sigma-Delta Analog to Digital converter
• Modified First-Order Goertzel Algorithm
• 115.2k UART

The Detection Algorithm

In this interpretation of the Goertzel Algorithm, we utilize a 1-bit Sigma-Delta Analog to Digital converter. Most DSP algorithms require the processor to store hundreds of values and post-process them. Since this Scenix algorithm processes the incoming signal as it is sampled, RAM usage is tiny and the processing required after a sampling period is minimized. Also, because the analog to digital conversion is only a 1-bit conversion, only an add- or subtract-function is executed on each sample. Over a period of time, the result is the same as multiplying a multi-bit sample by a coefficient and accumulating the results, but the multiplication process is eliminated. Though each sample is of 1-bit resolution, the overall resolution is good because the input signal is oversampled. (400x oversampling on a 1kHz input signal.)

Vin (2.5VDC +/- Signal)

The hardware required to perform frequency detection on an SX consists of only two I/O pins of the SX, 2 resistors, and a capacitor.

C1

A sine and a cosine reference wave are generated at the frequency to be detected. Each 1-bit A/D sample determines whether or not to add or subtract the value of each reference wave from its respective sine accumulator or cosine accumulator,

After a period of time has passed (20ms), the value stored in these accumulators indicates the amount of the detected frequency present in the signal. To determine the amount of the selected frequency detected in the signal, this calculation is used:

Frequency Magnitude = (accumulated sine result2 + accumulated cosine result2) -2

The result of this calculation is normalized and output to the terminal screen in a "level-meter" format.

Some possible applications of this algorithm are:

• Spectrum Analysis
• DTMF (Dual-Tone Multiple Frequency) detection
· Call-Progress detection

1 of 1

```;******************************************************************************
;
; 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: 	Freq_Det_1_03.src
;
; Authors:	Chris Fogelklou
;		Applications Engineer
;		Scenix Semiconductor,Inc
;
; Revision:	1.03
;
; Part:		SX28AC datecode 9929AA
; Freq:		50Mhz
; Compiled using Parallax SX-Key software v1.07 and SASM 1.40
;
; Date Written: 04/22/1999
;
; Last Revised: 10/20/1999
;
; Program Description:
;	This program demonstrates the use of a correlation algorithm
;	to calculate how similar an incoming frequency is to a frequency
;	input by the user.  It uses a sigma-delta A/D in conjunction with
;	simple arithmetic to accumulate the filter output.
;
;	This program is compatible with the SX-Demo board, but some values need to
;	be modified to run the sigma-delta A/D converter.  R12 should be 10k,
;	R11 should be 22k, and C7 should be 1000pF.  These values gave excellent
;	results when used with a function generator.  For best results, set the output
;	of the function generator to 2.5VDC with an output swing of about 0.8V.
;	For a more sensitive A/D, Decrease the value of R11, while increasing the value
;	of R12.
;
;
;		R12		R11
;		10k		22k
;	RC0-----\/\/----x-------\/\/------>Vin (2.5V +/- 0.8V)
;			|
;	RC1-------------x
;			|
;		      ----- C7
;		      ----- 1000pF
;			|
;			O
;		       VSS
;
;
;	SCREENSHOT:
;
;	SX Frequency Detector
;	Enter a frequency in Hz: 1000
;	Press any key to escape
;	LEVEL:*****************************************************           >
;
;	This program runs on the Parallax SX-Demo board.
;	It uses a baud rate which is chosen by uncommenting the appropriate baud rate defines below:
;
;	19200 baud (sample rate /16)
;		baud_bit	=       4                       ;for 19200 baud
;		start_delay	=       16+8+1                  ; "    "     "
;		int_period	=       163                     ; "    "     "
;	38400 baud (sample rate /8)
;		baud_bit	=       3                       ;for 38400 baud
;		start_delay	=       8+4+1                  ; "    "     "
;		int_period	=       163                     ; "    "     "
;	57600 baud (sample rate /8)
;		baud_bit	=       3                       ;for 57600 baud
;		start_delay	=       8+4+1                  ; "    "     "
;		int_period	=       109                     ; "    "     "
;	115.2kbaud (sample rate /4)
baud_bit	=       2                       ;for 115200 baud
start_delay	=       4+2+1                  ; "    "     "
int_period	=       109                     ; "    "     "
;
;       Length:  731 words
;	Version: 1.02
;	Revision History
;		1.0	Initial program written.
;		1.01 	Some formatting and documentation changed, as well as some subroutines
;			such as delay_n_ms.
;		1.02	Re-documented for use with SASM and SX-52
;			Re-wrote some of the routines for compatibility with
;			SX-52.
;		1.03	Removed CARRYX directive and modified code accordingly
;******************************************************************************
;*****************************************************************************************
; Target SX
; Uncomment one of the following lines to choose the SX18AC, SX20AC, SX28AC, SX48BD/ES,
; SX48BD, SX52BD/ES or SX52BD. For SX48BD/ES and SX52BD/ES, uncomment both defines,
; SX48_52 and SX48_52_ES.
;*****************************************************************************************
;SX18_20
SX28
;SX48_52
;SX48_52_ES
;*****************************************************************************************
; Assembler Used
; Uncomment the following line if using the Parallax SX-Key assembler. SASM assembler
; enabled by default.
;*****************************************************************************************
SX_Key

;*********************************************************************************
; Assembler directives:
;	high speed external osc, turbo mode, 8-level stack, and extended option reg.
;
;	SX18/20/28 - 4 pages of program memory and 8 banks of RAM enabled by default.
;	SX48/52 - 8 pages of program memory and 16 banks of RAM enabled by default.
;
;*********************************************************************************

IFDEF SX_Key 				;SX-Key Directives
IFDEF SX18_20				;SX18AC or SX20AC device directives for SX-Key
device	SX18L,oscxt4,turbo,stackx_optionx,carryx
ENDIF
IFDEF SX28				;SX28AC device directives for SX-Key
device	SX28L,oscxt4,turbo,stackx_optionx,carryx
ENDIF
IFDEF SX48_52_ES			;SX48BD/ES or SX52BD/ES device directives for SX-Key
device	oschs,turbo,stackx,optionx,carryx
ELSE
IFDEF SX48_52				;SX48/52/BD device directives for SX-Key
device	oschs2,stackx_optionx,carryx
ENDIF
ENDIF
freq	50_000_000
ELSE  					;SASM Directives
IFDEF SX18_20				;SX18AC or SX20AC device directives for SASM
device	SX18,oschs2,turbo,stackx,optionx,carryx
ENDIF
IFDEF SX28				;SX28AC device directives for SASM
device	SX28,oschs2,turbo,stackx,optionx,carryx
ENDIF
IFDEF SX48_52_ES			;SX48BD/ES or SX52BD/ES device directives for SASM
device	SX52,oschs,turbo,stackx,optionx,carryx
ELSE
IFDEF SX48_52			;SX48BD or SX52BD device directives for SASM
device	SX52,oschs2,stackx,optionx,carryx
ENDIF
ENDIF
ENDIF
id	'DFT1_02 '			;
;*****************************************************************************************
; Macros
;*****************************************************************************************

;*********************************************************************************
; Macro: _bank
; Sets the bank appropriately for all revisions of SX.
;
; This is required since the bank instruction has only a 3-bit operand, it cannot
; be used to access all 16 banks of the SX48/52. For this reason FSR.4 (for SX48/52BD/ES)
; or FSR.7 (SX48/52bd production release) needs to be set appropriately, depending
; on the bank address being accessed. This macro fixes this.
;
; So, instead of using the bank instruction to switch between banks, use _bank instead.
;
;*********************************************************************************
_bank	macro	1
bank	\1

IFDEF SX48_52
IFDEF SX48_52_ES
IF \1 & %00010000		;SX48BD/ES and SX52BD/ES (engineering sample) bank instruction
setb	fsr.4		;modifies FSR bits 5,6 and 7. FSR.4 needs to be set by software.
ENDIF
ELSE
IF \1 & %10000000		;SX48BD and SX52BD (production release) bank instruction
setb	fsr.7		;modifies FSR bits 4,5 and 6. FSR.7 needs to be set by software.
ELSE
clrb	fsr.7
ENDIF
ENDIF
ENDIF
endm

;*********************************************************************************
; Macro: _mode
; Sets the MODE register appropriately for all revisions of SX.
;
; This is required since the MODE (or MOV M,#) instruction has only a 4-bit operand.
; The SX18/20/28AC use only 4 bits of the MODE register, however the SX48/52BD have
; the added ability of reading or writing some of the MODE registers, and therefore use
; 5-bits of the MODE register. The  MOV M,W instruction modifies all 8-bits of the
; MODE register, so this instruction must be used on the SX48/52BD to make sure the MODE
; register is written with the correct value. This macro fixes this.
;
; So, instead of using the MODE or MOV M,# instructions to load the M register, use
;
;*********************************************************************************
_mode	macro	1
IFDEF SX48_52
mov	w,#\1		;loads the M register correctly for the SX48BD and SX52BD
mov	m,w
ELSE
mov	m,#\1		;loads the M register correctly for the SX18AC, SX20AC
;and SX28AC
ENDIF
endm

;*****************************************************************************************
; These definitions ensure the proper address is used for banks 0 - 7 for 2K SX devices
; (SX18/20/28) and 4K SX devices (SX48/52).
;*****************************************************************************************
IFDEF SX48_52

global_org	=	\$0A
bank0_org	=	\$00
bank1_org	=	\$10
bank2_org	=	\$20
bank3_org	=	\$30
bank4_org	=	\$40
bank5_org	=	\$50
bank6_org	=	\$60
bank7_org	=	\$70

ELSE

global_org	=	\$08
bank0_org	=	\$10
bank1_org	=	\$30
bank2_org	=	\$50
bank3_org	=	\$70
bank4_org	=	\$90
bank5_org	=	\$B0
bank6_org	=	\$D0
bank7_org	=	\$F0

ENDIF
;*****************************************************************************************
; Global Register definitions
; NOTE: Global data memory starts at \$0A on SX48/52 and \$08 on SX18/20/28.
;*****************************************************************************************
org     global_org

temp		equ	global_org	; Temporary storage register
temp2		equ	global_org+1
flags		equ	global_org+2
freq_det_en	equ	flags.0
timer_flag	equ	flags.1
rx_flag		equ	flags.2
loopcount	equ	global_org+3
isr_temp	equ	global_org+4

;*****************************************************************************************
; RAM Bank Register definitions
;*****************************************************************************************

;*********************************************************************************
; Bank 0
;*********************************************************************************
org     bank0_org

bank0		=	\$
timers		=	\$
timer_l		ds	1
timer_m		ds	1
timer_h		ds	1
sine_gen_bank	=	\$
freq_acc_l	ds	1
freq_acc_h	ds	1
freq_count_l	ds	1
freq_count_h	ds	1
;*********************************************************************************
; Bank 1
;*********************************************************************************
org     bank1_org

bank1		=	\$

freq_det_bank	=	\$

sine_index	ds	1
sine_value	ds	1
cose_value	ds	1
sine_acc_l	ds	1
sine_acc_h	ds	1
cose_acc_l	ds	1
cose_acc_h	ds	1

math_bank	=	\$

input		ds	1
input2		ds	1
loop_count	ds	1
math_flags	ds	1
neg		equ	math_flags.0
;*********************************************************************************
; Bank 2
;*********************************************************************************
org     bank2_org

bank2		=	\$

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
string		ds	1
byte		ds	1
dec_l		ds	1			;Holds the 16-bit value returned
dec_h		ds	1			;from get_dec subroutine.
;*********************************************************************************
; Bank 3
;*********************************************************************************
org     bank3_org

bank3		=	\$

;*********************************************************************************
; Bank 4
;*********************************************************************************
org     bank4_org
bank4		=	\$
;*********************************************************************************
; Bank 5
;*********************************************************************************
org     bank5_org
bank5		=	\$
;*********************************************************************************
; Bank 6
;*********************************************************************************
org     bank6_org
bank6		=	\$
;*********************************************************************************
; Bank 7
;*********************************************************************************
org     bank7_org
bank7		=	\$
IFDEF SX48_52
;*********************************************************************************
; Bank 8
;*********************************************************************************
org	\$80	;bank 8 address on SX52
bank8		=	\$
;*********************************************************************************
; Bank 9
;*********************************************************************************
org	\$90	;bank 9 address on SX52
bank9		=	\$
;*********************************************************************************
; Bank A
;*********************************************************************************
org	\$A0	;bank A address on SX52
bankA		=	\$
;*********************************************************************************
; Bank B
;*********************************************************************************
org	\$B0	;bank B address on SX52
bankB		=	\$
;*********************************************************************************
; Bank C
;*********************************************************************************
org	\$C0	;bank C address on SX52
bankC		=	\$
;*********************************************************************************
; Bank D
;*********************************************************************************
org	\$D0	;bank D address on SX52
bankD		=	\$
;*********************************************************************************
; Bank E
;*********************************************************************************
org	\$E0	;bank E address on SX52
bankE		=	\$
;*********************************************************************************
; Bank F
;*********************************************************************************
org	\$F0	;bank F address on SX52
bankF		=	\$
ENDIF
;*****************************************************************************************
; Port Assignment
;*****************************************************************************************

RA_init		equ	%11111111		;SX18/20/28/48/52 port A latch init
RA_io		equ	%11110111		;SX18/20/28/48/52 port A DDIR value
RA_plp		equ	%11111100		;SX18/20/28/48/52 port A PLP value (pullups)
RA_cmos		equ	%00000000		;SX18/20/28/48/52 port A LVL value
RB_init		equ	%11111111		;SX18/20/28/48/52 port B latch init
RB_io		equ	%11111111		;SX18/20/28/48/52 port B DDIR value
RB_cmos		equ	%00000000		;SX18/20/28/48/52 port B LVL value
RC_init		equ	%00000000		;SX18/20/28/48/52 port C latch init
RC_io		equ	%11111110		;SX18/20/28/48/52 port C DDIR value
RC_cmos		equ	%00000000		;SX18/20/28/48/52 port C LVL value

IFDEF SX48_52
RD_init		equ	%11111111		;SX48/52 port D latch init
RD_io		equ	%11111111		;SX48/52 port D DDIR value
RD_cmos		equ	%00000000		;SX18/20/28/48/52 port D LVL value
RE_init		equ	%11111111		;SX48/52 port E latch init
RE_io		equ	%11111111		;SX48/52 port E DDIR value
RE_cmos		equ	%00000000		;SX18/20/28/48/52 port E LVL value
ENDIF

;**************************************************************************
; Pin Definitions
;**************************************************************************
rx_pin          EQU     ra.2                    ;UART receive input
tx_pin          EQU     ra.3                    ;UART transmit output

;**************************************************************************
;**************************************************************************
;**************************************************************************
; START OF PROGRAM MEMORY HERE!!!
org	0
;**************************************************************************
; Interrupt...  This interrupt routine has two different vectors with two seperate
;		interrupt rates.  As a result, the UART MUST be finished sending
;		before the rates are switched.  This is why the send_byte routine
;		was changed to not exit until the entire byte has been sent.
;**************************************************************************
mov	w,>>rc			;1	; Perform negative feedback on input
not	w			;1	; signal.  Continue even when DFT is not
mov	rc,w			;1	; running, to keep input at 2.5V DC
snb	freq_det_en		;1	; IF frequency detection is enabled, go
jmp	@SINE_DETECTION		;3	; to the frequency detection ISR instead.
;--------------------------------------------------------------------------
:transmit
; This is an asynchronous transmitter for RS-232 transmission
; INPUTS:
;	divider.divider_bit -	Transmitter/receiver only executes when this bit is = 1
;	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?
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

;--------------------------------------------------------------------------
; 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.
;--------------------------------------------------------------------------
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
;******************************************************************************
:ISR_DONE
;******************************************************************************
mov	w,#-int_period
;1	; interrupt (int_period) cycles from the start of this interrupt.
retiw			;3	; return from the interrupt
;**************************************************************************
; This is the end of the interrupt service routine for the UARTs
;**************************************************************************
; This is the beginning of the interrupt service routine for the Frequency detection
;**************************************************************************
;--------------------------------------------------------------------------
sine_table	;7 cycles		; Returns with the value at (w) in (w)
;	This entire table MUST be located in the first half of the page
;	from which it is called.
;--------------------------------------------------------------------------
;		Returns the sine value at index.
clc			;1
jmp	pc+w		;3,3
retw	0
retw	3
retw	6
retw	8
retw	11
retw	12
retw	14
retw	15
retw	15
retw	15
retw	14
retw	12
retw	11
retw	8
retw	6
retw	3
retw	0
retw	-3
retw	-6
retw	-8
retw	-11
retw	-12
retw	-14
retw	-15
retw	-15
retw	-15
retw	-14
retw	-12
retw	-11
retw	-8
retw	-6
retw	-3
jmp	\$
;--------------------------------------------------------------------------
SINE_DETECTION					;4
; 	Part of the interrupt service routine!!!  This routine adds or
;	subtracts the values of the sine and cose references from their
;	corresponding accumulators, depending on whether or not the input
;	matches or does not match the reference.
;--------------------------------------------------------------------------
bank	sine_gen_bank		;1
clr	isr_temp
snc
setb	isr_temp.0
add	freq_acc_l,freq_count_l	;2	; If it is time to update the sine
snc
inc	freq_acc_h
test	freq_acc_h
jnz	:no_update		;1 15
jnb	isr_temp.0,:no_update

:update		bank	freq_det_bank		;1	; OK, Carry produced.  Now, update the
inc	sine_index		;1	; sine and cose references.  Do this
mov	w,sine_index		;1	; by incrementing the index into the table.
and	w,#\$1f			;1
call	sine_table		;7 26	; and get the value at (index)

mov	sine_value,w		;1	; store the new sine reference value

mov	w,#8			;1	; Now get the cose reference value.  Add 8
add	w,sine_index		;1	; to the sine index and get the value at
and	w,#\$1f			;1	; this location.
call	sine_table		;7

mov	cose_value,w		;1 38	; store the new cose reference value.
:no_update
bank	freq_det_bank		;1	; Perform the DFT calculations.  If the
do_sine_ref						; negative feedback pin is positive, add
mov	w,sine_value		;1	; the reference, otherwise subtract it.
sb	rc.0			;1
:sub_reference
stc				;1 43	; Signed 16-bit subtraction of 8-bit reference
sub	sine_acc_l,w		;1
clr	w			;1
snb	sine_value.7		;1
not	w			;1
sub	sine_acc_h,w		;1
jmp	do_cose_ref		;3 49
clc				;1 44	; Signed 16-bit addition of 8-bit reference
clr	w			;1
snb	sine_value.7		;1
not	w			;1
do_cose_ref
mov	w,cose_value		;1 52	; Now do the same calculations for the cose
sb	rc.0			;1	; reference.
:sub_reference
stc				;1 55	; Signed 16-bit subtraction of 8-bit reference
sub	cose_acc_l,w		;1
clr	w			;1
snb	cose_value.7		;1
not	w			;1
sub	cose_acc_h,w		;1
jmp	DFT_DONE		;3 63
clc				;1 56	; Signed 16-bit addition of 8-bit reference
clr	w			;1
snb	cose_value.7		;1
not	w			;1

DFT_DONE					; 65
do_nothing
sb	rx_pin			; If the rx_pin goes low, set a flag to indicate
setb	rx_flag			; it is time to reset the program.  (User has pushed a key.)
;--------------------------------------------------------------------------
do_timers	bank	timers			; 16-bit timer for delay_n_ms routine.  These timers tick at
; a rate of (interrupt rate * 20ns) = 2.38us = 420.168 kHz
inc	timer_l
snz
inc	timer_h
snz
setb	timer_flag
;**************************************************************************
;	End of the ISR for the Frequency detection algorithm.
;**************************************************************************
mov	w,#-119			; Return value for easy DFT constants.  (frequency * 5)
retiw

;**************************************************************************
; END OF THE INTERRUPT SERVICE ROUTINES!!!
;**************************************************************************
;**************************************************************************

;**************************************************************************
org	\$1fe
;******************************************************************************************
reset_entry			; Program starts here on power-up and on reset.
;**************************************************************************
org	\$200
;******************************************************************************************
org	\$300
;**************************************************************************
; String data (for RS-232 output) and tables needs to be in page \$300 for send_string
; to work.
;**************************************************************************
_hello          dw      13,10,'SX Frequency Detector',0
_enter_freq	dw	13,10,'Enter a frequency in Hz: ',0
_press_key	dw	13,10,'Press any key to escape',13,10,0
_CR		dw	13,10,0
_error		dw	13,10,'ERROR: Value is too large.  Must be <= 13107Hz.  Press ENTER ',0
_dec_error	dw	13,10,'ERROR: Value is too large.  Must be <= 65535 :',0
_LEVEL		dw	13,'LEVEL:',0
;******************************************************************************************
org	\$400
;--------------------------------------------------------------------------
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
mov	timer_h,#\$0fe	; These constants are used for the 119 cycle interrupt rate.
mov	timer_l,#\$05c
jnb	timer_flag,\$
dec	temp		; do it w-1 times.
jnz	:loop
clrb	timer_flag
retp
;--------------------------------------------------------------------------
; Initialization Code...
;--------------------------------------------------------------------------
init
_mode	\$1d			; CMOS Init
mov	!ra,#RA_cmos
mov	!rb,#RB_cmos
mov	!rc,#RC_cmos
_mode	\$1f
mov     ra,#RA_init              	; Initialize data latches for ports
mov	rb,#RB_init
mov	rc,#RC_init
; Initialize direction registers for ports.
mov	!ra,#RA_io		; port A.
mov	!rb,#RB_io		; port B.
mov	!rc,#RC_io		; port C.

call	@zero_ram
mov     !option,#%00011111      ; enable rtcc interrupt and wreg, no prescaler on RTCC.

retp
;--------------------------------------------------------------------------
zero_ram
; Subroutine - Zero all ram.
; INPUTS:	None
; OUTPUTS:	All ram locations (except special function registers) are = 0
;--------------------------------------------------------------------------
IFDEF SX48_52   				;SX48/52 RAM clear routine
mov	w,#\$0a			;reset all ram starting at \$0A
mov	fsr,w
:zero_ram	clr	ind			;clear using indirect addressing
incsz	fsr			;repeat until done
jmp	:zero_ram

_bank	bank0			;clear bank 0 registers
clr	\$10
clr	\$11
clr	\$12
clr	\$13
clr	\$14
clr	\$15
clr	\$16
clr	\$17
clr	\$18
clr	\$19
clr	\$1a
clr	\$1b
clr	\$1c
clr	\$1d
clr	\$1e
clr	\$1f

ELSE     					;SX18/20/28 RAM clear routine
clr	fsr			;reset all ram banks
:zero_ram	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
incsz	fsr			;repeat until done
jmp	:zero_ram
ENDIF
retp
;--------------------------------------------------------------------------
SignedMultiply
;	8 * 8 Signed Multiply
;	INPUTS:		Multiplies input2 * input
;				(low byte and high byte, respectively)
;--------------------------------------------------------------------------
mov	input2,w	;1
mov	w,#\$ff		;1
sb	input.7		;1
jmp	:input2		;1,3
xor	input,w		;1
inc	input		;1
inc	loop_count	;1;7
:input2
sb	input2.7	;1
jmp	:done		;1
xor	input2,w	;1
inc	input2		;1
inc	loop_count	;1;12
:done
setb	neg		;1
sb	loop_count.0	;1
clrb	neg		;1
clr	loop_count	;1
mov	w,input2	;1;17
call	Multiply	;87;104

sb	neg		;1
retp			;3
mov	w,#\$ff		;1
snz			;1
retp			;3;113 cycles worst case

;--------------------------------------------------------------------------
Multiply ; Multiply W by input
;	INPUTS:		W * input
;--------------------------------------------------------------------------
setb	loop_count.3	;1
:loop
clc			;1
snb	input.0	;1
rr	input		;1
decsz	loop_count	;1
jmp	:loop		;3	10=looptime (78 on exit)
;    	78 + 3 = 81
retp			;3	82 + 3 = 84	ALWAYS!!!
;	for 16 bit result.
;--------------------------------------------------------------------------
clear_bank	; Clears an entire bank of RAM.
; of the bank to clear.
;--------------------------------------------------------------------------
:loop
clr	indf
inc	fsr
sb	fsr.4
retp
jmp	:loop
;--------------------------------------------------------------------------
;	SQ_ROOT 		; By John Keenan
;	Routine to take the square root of a 16 bit unsigned number
;	entry: 	input - low byte of number
;		input2 - high byte of number
;	exit:	W register returns 8 bit result
;--------------------------------------------------------------------------

:sq1		stc
sub	input2,w		; subtract the root develped so far
sc				; restore subtraction if carry cleared
jmp	:sq5

:sq2		or	w,root_mask		; set the current bit

:sq3		nop
rl	input			; shift number left one position
rl	input2
rr	root_mask		; picks up ms bit of input2
jmp	:sq6

xor	w,root_mask		; append 01 to the root developed so far
sc				; if the lsb of root_mask was shifted into carry,
jmp	:sq1			; then we're done. Otherwise loop again

stc
sub	input2,w		;
sc
retp

snz
snb	input.7
xor	w,#1
retp

:sq6		snc
retp

stc
sub	input2,w
jmp	:sq2

:sq5		add	input2,w		; carry=0 here
jmp	:sq3
;--------------------------------------------------------------------------
; Subroutine - Get byte via serial port and echo it back to the serial port
; INPUTS:
;	-NONE
; OUTPUTS:
;	-received byte in w, and 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
retp
;--------------------------------------------------------------------------
; Subroutine - Send byte via serial port
; INPUTS:
;	w 	-	The byte to be sent via RS-232
;--------------------------------------------------------------------------
send_byte    	bank    serial

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

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

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
: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?
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
;--------------------------------------------------------------------------
uppercase    	csae	  byte,#'a'            	;if byte is lowercase, then skip ahead
RETP

sub     byte,#'a'-'A'           ;change byte to uppercase
RETP                            ;leave and fix page bits
;--------------------------------------------------------------------------
; Subroutine - Output a hex number
; INPUTS:
;	w	-	The byte to be output
;--------------------------------------------------------------------------
send_hex
mov	temp,w
swap	wreg
and	w,#\$0f
call	hex_table
call	send_byte
mov	w,temp
and	w,#\$0f
call	hex_table
call	send_byte
retp

hex_table	; called by send_hex
;--------------------------------------------------------------------------
clc
retw	'0'
retw	'1'
retw	'2'
retw	'3'
retw	'4'
retw	'5'
retw	'6'
retw	'7'
retw	'8'
retw	'9'
retw	'A'
retw	'B'
retw	'C'
retw	'D'
retw	'E'
retw	'F'
;--------------------------------------------------------------------------
dec_table	; called by get_dec
;--------------------------------------------------------------------------
clc
retw	'0'
retw	'1'
retw	'2'
retw	'3'
retw	'4'
retw	'5'
retw	'6'
retw	'7'
retw	'8'
retw	'9'
retw	0
retw	0
retw	0
retw	13		; A carriage return will be valid.  It will return
retw	0		; with the value of a CR in it.
retw	0
;--------------------------------------------------------------------------
get_dec
;	This routine returns with an 16-bit value in dec_l and dec_h registers
;	register.  It accepts a decimal number from the terminal screen and
;	returns when the number is within range and the user presses <return>
;--------------------------------------------------------------------------
bank	serial
clr	dec_l
clr	dec_h
:get_next
call	:get_valid_dec	; get a valid decimal number.
mov	w,#13
xor	w,byte
jz	:done
clc			; multiply current dec input by 10.
rl	dec_l
rl	dec_h
jc	:error
mov	temp,dec_l	; save (last number * 2)
mov	temp2,dec_h
rl	dec_l
rl	dec_h
jc	:error
rl	dec_l
rl	dec_h
jc	:error
add	dec_h,temp2	; add ((last number * 2) + (last number * 10))
jc	:error
clr	w
jc	:error
jmp	:get_next	; Get the next valid decimal number

:done
retp
:error
mov	w,#_dec_error
call	@send_string	; Send the error message if the decimal number
; is too long.
jmp	get_dec		; and start over.
;--------------------------------------------------------------------------
:get_valid_dec	; Return with a character which is a valid decimal number,
; or with a '13' in 'byte' if the user presses return.
;--------------------------------------------------------------------------
bank	serial
call	@get_byte
clr	temp
:loop
mov	w,temp
call	dec_table
xor	w,byte
jz	:got_it
inc	temp
jb	temp.4,:get_valid_dec
jmp	:loop
:got_it
mov	w,byte
call	@send_byte
mov	byte,temp
ret
;******************************************************************************************
org	\$600
;******************************************************************************************
;		START OF MAIN PROGRAM
start		call	@init	; Initialize all registers and ports.
;******************************************************************************************
; Send Hello message
;******************************************************************************************

mov	w,#_hello		; Send hello string.
call	@send_string
;******************************************************************************************
; Get the desired 16-bit frequency value from the user.
;******************************************************************************************
mov	w,#_enter_freq		; Prompt for frequency value
call	@send_string
call	@get_dec		; Get Frequency value

mov	temp,dec_h		; Store the input 16-bit decimal value
mov	temp2,dec_l
;******************************************************************************************
; Now multiply the input frequency by 5 and load it into the freq_count registers.  This
; will produce a constant which will cause the sine and cose reference generators to
; construct a sine/cose wave of the desired frequency.  The value of 5 is determined by a
; number of factors, including the size of the sine lookup table, and the interrupt rate,
; which is 419.4 kHz in this application during the frequency detection.  If, while multiplying
; the input frequency by 5, the result exceeds 16-bits, output an error message.
;******************************************************************************************
bank	sine_gen_bank
mov	w,temp
mov	freq_count_h,w
mov	freq_acc_h,w		; save the upper byte in the input register
mov	w,temp2
mov	freq_count_l,w		; move it into freq_count_l
clc				; Multiply freq_count by 5 because with the known interrupt
; rate, this is the constant to use.
rl	freq_count_l		; First multiply freq_count by 4
rl	freq_count_h
jc	input_error		; If the input was too high, say "error"
rl	freq_count_l
rl	freq_count_h
jc	input_error		; If the input was too high, say "error"
add	freq_count_l,w		; freq_count = freq_count + 4(freq_count)
jc	input_error		; If the input was too high, say "error"
;******************************************************************************************
; There is a slight discrepancy between the actual interrupt rate and the ideal interrupt
; rate, but luckily this is easily remedied by performing this calculation:
; freq_count = freq_count - (freq_count/512)
;******************************************************************************************
mov	w,freq_count_h		; Compensate for the interrupt rate discrepancy
clc				; by performing this calculation: Frequency = Frequency - (Frequency/512)
rr	wreg			; Divide frequency by 512
stc
sub	freq_count_l,w		; subtract (frequency/512) from frequency
sc
dec	freq_count_h
mov	w,#_press_key		; Send out a carriage return.
call	@send_string
clrb	rx_flag

;******************************************************************************************
; Now get into the loop which detects a frequency for 40ms, and then outputs the results
; of the DFT in a level meter on the screen.
;******************************************************************************************
:loop		jb	rx_flag,start
mov	w,#_LEVEL		; Send "LEVEL :" message.
call	@send_string
mov	fsr,#freq_det_bank	; clear the bank of sin/cos accumulators
call	@clear_bank
setb	freq_det_en		; Enable the frequency detectors
mov	w,#40
call	@delay_n_ms		; Detect for 40ms
clrb	freq_det_en		; Disable the frequency detectors (Enabling the UARTs)
;**********************************************************************
; Now the sine and cose accumulators have been loaded with values which correspond
; with the magnitude of the signal at the chosen frequency.  To get the actual
; magnitude, we need to perform this calculation:
; MAG = (SIN_acc^2 * COS_acc ^2)^(-2)
; Just like (a^2 + b^2 = c^2), where c is the magnitude
;**********************************************************************
bank	math_bank		; Calculation 1
mov	input,sine_acc_h
mov	input2,sine_acc_h
call	@SignedMultiply		; SIN_acc^2

mov	input,cose_acc_h	; Calculation 2
mov	input2,cose_acc_h
call	@SignedMultiply		; COS_acc^2
clc

mov	input,w
add	w,sine_acc_h		; (SIN_acc^2 + COS_acc^2)

mov	input2,w		; Calculation 4
call	@sq_root		; (SIN_acc^2 + COS_acc^2)^(-2)
;**********************************************************************
; Now display the detected magnitude on the screen, as a level meter
; composed of stars.
;**********************************************************************
break
rr	answer_l	; Maximum value is 63
:max_enter	and	w,#\$3f		; Ensure that the maximum value is 63.
mov	temp,w
mov	loopcount,#64	; Output 1 space the number of stars.
test	temp
:star_loop
jz	:space_loop	; Output [temp] stars (maximum of 63)
mov	w,#'*'
call	@send_byte
dec	loopcount
dec	temp
jmp	:star_loop
:space_loop
mov	w,#' '		; Output [loopcount - temp] spaces
call	@send_byte
decsz	loopcount
jmp	:space_loop

:done_stars	mov	w,#'>'		; Output a '>' to show the maximum level.
call	@send_byte
jmp	:loop		; Do ALL over again.

:max
mov	w,#\$ff		; If the input value was maxed out,
jmp	:max_enter	; show the absolute maximum on the screen.

input_error
mov	w,#_error	; We get here if the user input a value > 13107Hz
call	@send_string	; 13107 is 65535/5.
:CR_loop	call	@get_byte	; Wait until user presses CR
mov	w,byte
xor	w,#13
jnz	:CR_loop
jmp	start	; and restart the process

```

 file: /Techref/scenix/lib/math/dsp/freqdet/index.htm, 44KB, , updated: 2007/3/8 21:13, local time: 2023/3/23 10:16, TOP NEW HELP FIND:  44.200.171.156:LOG IN

 ©2023 These pages are served without commercial sponsorship. (No popup ads, etc...).Bandwidth abuse increases hosting cost forcing sponsorship or shutdown. This server aggressively defends against automated copying for any reason including offline viewing, duplication, etc... Please respect this requirement and DO NOT RIP THIS SITE. Questions?Please DO link to this page! Digg it! / MAKE! index

After you find an appropriate page, you are invited to your to this massmind site! (posts will be visible only to you before review) Just type a nice message (short messages are blocked as spam) in the box and press the Post button. (HTML welcomed, but not the <A tag: Instead, use the link box to link to another page. A tutorial is available Members can login to post directly, become page editors, and be credited for their posts.

Attn spammers: All posts are reviewed before being made visible to anyone other than the poster.
 Did you find what you needed? "No. I'm looking for: " "No. Take me to the search page." "No. Take me to the top so I can drill down by catagory" "No. I'm willing to pay for help, please refer me to a qualified consultant"

Welcome to sxlist.com!

Site supported by
& kind contributors
just like you!

(here's why

Copies of the site on CD
are available at minimal cost.

.