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:

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:

1 of 1


;******************************************************************************
; Copyright © [04/22/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: 	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 '			;
		reset	reset_entry			; JUMP to start label on reset
;*****************************************************************************************
; 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 instead.
	; 
	;*********************************************************************************
_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
;*****************************************************************************************
; Data Memory address definitions
; 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	=	0ドルA
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 0ドルA 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	=	$
answer_l	ds	1
answer_h	ds	1
input		ds	1
input2		ds	1
loop_count	ds	1
math_flags	ds	1
neg		equ	math_flags.0
root_mask	ds	1
	;*********************************************************************************
	; 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?
		JZ :receive ;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
;--------------------------------------------------------------------------
: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.
;--------------------------------------------------------------------------
		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
		add	freq_acc_h,freq_count_h	;2	; and cose references, this addition
		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,#1ドルf			;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,#1ドルf			;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
		jmp	:add_reference		;3
: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
:add_reference
		clc				;1 44	; Signed 16-bit addition of 8-bit reference
		add	sine_acc_l,w		;1
		clr	w			;1
		snb	sine_value.7		;1
		not	w			;1
		add	sine_acc_h,w		;1 50
do_cose_ref
		mov	w,cose_value		;1 52	; Now do the same calculations for the cose
		sb	rc.0			;1	; reference.
		jmp	:add_reference		;3,1 
: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
:add_reference
		clc				;1 56	; Signed 16-bit addition of 8-bit reference
		add	cose_acc_l,w		;1
		clr	w			;1
		snb	cose_value.7		;1
		not	w			;1
		add	cose_acc_h,w		;1 64
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	1ドルfe
;******************************************************************************************
reset_entry			; Program starts here on power-up and on reset.
		jmp	@start	; jump to the main program.
;**************************************************************************
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,#0ドルfe
;	mov	timer_l,#0ドルcd
	mov	timer_h,#0ドルfe	; These constants are used for the 119 cycle interrupt rate.
	mov	timer_l,#05ドルc
	jnb	timer_flag,$
	dec	temp		; do it w-1 times.
	jnz	:loop
	clrb	timer_flag
	retp
;--------------------------------------------------------------------------
; Initialization Code...
;--------------------------------------------------------------------------
init
	_mode	1ドルd			; CMOS Init
	mov	!ra,#RA_cmos		
	mov	!rb,#RB_cmos
	mov	!rc,#RC_cmos
	_mode	1ドルf
	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,#0ドルa			;reset all ram starting at 0ドルA
		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	1ドルa
		clr	1ドルb
		clr	1ドルc
		clr	1ドルd
		clr	1ドルe
		clr	1ドルf
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
;	OUTPUTS:	16-bit Signed value in answer_l and answer_h
;				(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
	xor	answer_h,w	;1
	xor	answer_l,w	;1
	inc	answer_l	;1
	snz			;1
	inc	answer_h	;1
	retp			;3;113 cycles worst case
		
;--------------------------------------------------------------------------
Multiply ; Multiply W by input
;	INPUTS:		W * input
;	OUTPUTS: 	16-bit output value in answer_l and answer_h
;--------------------------------------------------------------------------
	setb	loop_count.3	;1
	clr	answer_l	;1
	clr	answer_h	;1 ;3
:loop
	clc			;1
	snb	input.0	;1
	add	answer_h,w	;1
	rr	answer_h	;1
	rr	answer_l	;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.
		; To use, first load the FSR with the starting address 
		; 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
;--------------------------------------------------------------------------
sq_root		mov	root_mask,#$c0		; initialise root_mask
		mov	w,#40ドル			; initialise answer
: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
		snb	root_mask.7
		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
		clrb	root_mask.7
		xor	w,root_mask
		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
 		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
;--------------------------------------------------------------------------
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,#0ドルf
	call	hex_table
	call	send_byte
	mov	w,temp
	and	w,#0ドルf
	call	hex_table
	call	send_byte
	retp
hex_table	; called by send_hex
;--------------------------------------------------------------------------
	clc
	add	pc,w
	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
	add	pc,w
	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_l,temp
	add	dec_h,temp2	; add ((last number * 2) + (last number * 10))
	jc	:error
	add	dec_l,byte
	clr	w
	add	dec_h,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)
		add	freq_count_h,freq_acc_h	; = 5(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	sine_acc_h,answer_h
		mov	sine_acc_l,answer_l
		mov	input,cose_acc_h	; Calculation 2
		mov	input2,cose_acc_h
		call	@SignedMultiply		; COS_acc^2
		clc
		mov	w,answer_l		; Calculation 3
		add	w,sine_acc_l
		mov	input,w
		mov	w,answer_h
		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. 
		;**********************************************************************
		mov	answer_l,w
		clr	answer_h
		break
		jb	answer_l.7,:max
		rr	answer_l	; Maximum value is 63
		mov	w,answer_l
:max_enter	and	w,#3ドルf		; 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

Comments:


file: /Techref/scenix/lib/math/dsp/freqdet/index.htm, 44KB, , updated: 2007年3月8日 21:13, local time: 2025年9月6日 00:06,
40.74.122.252:LOG IN

©2025 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!

<A HREF="http://techref.massmind.org/techref/scenix/lib/math/dsp/freqdet/index.htm"> index</A>

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.


Link? Put it here:
if you want a response, please enter your email address:
Attn spammers: All posts are reviewed before being made visible to anyone other than the poster.
Did you find what you needed?

Welcome to massmind.org!

Welcome to techref.massmind.org!

.

AltStyle によって変換されたページ (->オリジナル) /