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 = $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 = $
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,#$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
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 $1fe
;******************************************************************************************
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,#$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
; 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,#$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
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,#$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
Comments:
| file: /Techref/scenix/lib/math/dsp/freqdet/index.htm, 44KB, , updated: 2007/3/8 21:13, local time: 2025/10/25 04:53,
216.73.216.22,10-3-83-201: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? <A HREF="http://www.piclist.com/Techref/scenix/lib/math/dsp/freqdet/index.htm"> index</A> |
| Did you find what you needed? |
|
o List host: MIT, Site host massmind.org, Top posters @none found - Page Editors: James Newton, David Cary, and YOU! * Roman Black of Black Robotics donates from sales of Linistep stepper controller kits. * Ashley Roll of Digital Nemesis donates from sales of RCL-1 RS232 to TTL converters. * Monthly Subscribers: Gregg Rew. on-going support is MOST appreciated! * Contributors: Richard Seriani, Sr. |
Welcome to www.piclist.com! |
.