{----------------------------------------------------------------------------- interpol.asm: Real time Interpolator ------------------------------------------------------------------------------ Uses an N-tap Direct Form Finite Impulse Filter in an efficient algorithm to interpolate by L for an increase of L times the input sample rate. A restriction on the number of taps is that N divided by L be an integer. The internal timer is used to generate interrupts at L times the sample rate. The input signal to the interpolator is a signal whose sampling frequency the user would like to scale up by an integer factor L. This is usually done to allow the use of a lower-order output filter than would be necessary if the signal were output at the input frequency. The interpolator adds L-1 zero-valued samples to each sample of the input signal, and uses an FIR filter to low-pass filter this signal. The filter interpolates the zero values to values between that of the original sample and the sample immediately following it. The filter must bandlimit the output signal to one-half the output frequency. An undesirable byproduct of the FIR filter is the attenuation of the input signal by a factor of L. To correct this, the input data is upscaled by L prior to being filtered. In this example, the timer is first programmed to the desired output frequency rate. The timer service routine (tmzh_svc) calls the interpolator (interpol), which gets another sample from the A/D converter (adc) every Lth pass. This is equivalent to sampling at the input rate. The interpolator runs N/L taps of the FIR filter at the output frequency rate. This is equivalent to running an N-tap FIR filter on data generated by adding L-1 zero samples to each data input. The filter output is written to a D/A converter (dac) after each interpolation. ------------------------------------------------------------------------------ Program Characteristics: Input: adc Output: dac Labels: init_int reset-time initialization interpol called by Timer Zero High Priority interrupt Registers Used: r0 FIR data in / ADC data / temp register r1 scale factor L r2 counter r4 FIR coefficients r8 FIR accumulate result reg r12 FIR multiply result reg / temporary reg r14 = 16 = exponent scale factor Computation Time: tmzh_svc = 5 + interpol = N/L + 9 (no sample) = N/L + 17 (with sample) interpol = N/L + 4 (no sample) = N/L + 12 + [5 misses] (with sample) Minimum Timer Period: N/L+20 Maximum Output Frequency: FP/(N/L+20) Maximum Input Frequency: FP/(L*(N/L+20)) ------------------------------------------------------------------------------ Creating & Using the Test Case: Running the test case requires two data files: coef.dat 32-tap FIR filter coefficients (floating point) wave.1 waveform data file coef.dat contains a 32-tap FIR filter which bandlimits the output signal to 1/8 of the output frequency. Since the interpolator in the test case interpolates by a factor M=4, this bandlimitation is equivalent to the required limit of 1/2 the input frequency. The filter is a Parks-McClellan filter with a passband ripple of 1.5dB out to about 1/10 its input frequency, and a stopband with greater than 70dB of attenuation above 1/8 the input frequency. As an example, if the interpolated output frequency is 64kHz (==input frequency of 16kHz), the passband extends out to about 6.4kHz. The stopband starts at 8kHz, and the output frequency is 16kHz. The example data file is wave.1. It contains 512 signed fixed-point data which emulate a 16-bit A/D converter whose bits are read from Data Memory bits 39:24. wave.1 contains a composite waveform generated from 3 sine waves of differing frequency and magnitude. The cb7_svc routine arithmetically shifts this data to the right by 16 bits, effectively zero-ing out the garbage data which would be found in bits 23:0 if a real 16-bit ADC port were read. The test case writes the interpolated output to a dac port. Since there are 512 samples in wave.1, and the test case performs interpolation by 4, there will be 128 data values written to the dac port if all data samples are read in and processed. The values written out are left-shifted by 16 bits in parallel with the float-->fixed conversion, based again on the assumption that the D/A converter port is located in the upper 16 bits of data memory. Armed with this information, you are ready to run: 1. Assemble & Start Simulator: asm21k -DTEST interpol ld21k -a generic interpol sim21k -a generic -e interpol -k port1 Note: the keystroke file port1.ksf opens two ports: 1st port - DM, F0000000, Fixed-Pt., auto-wrap, wave.1 2nd port - DM, F0000001, Fixed-Pt., no wrap, out.1 2. In the simulator, a. Go to the program memory (disassembled) window, d. Go to symbol sample, set to break on 513th occurence, and c. Run 3. Compare wave.1 to out.1 on the graphing program of your choice. ------------------------------------------------------------------------------ Author: Jim Donahue, Analog Devices DSP Division Created: 3/6/91 -----------------------------------------------------------------------------} #include "def21020.h" #define N 32 /* number of taps */ #define L 4 /* interpolate by factor of L */ #define FP 20.0e6 /* Processor Frequency = 20MHz */ #define FI 64.0e3 /* Interrupt Frequency = 64KHz */ #ifndef TEST /*----------------------------------------------------------*/ #define TPER_VAL 312 /* TPER_VAL = FP/FI - 1 */ #else /*-----------------------------------------------------------------*/ #define TPER_VAL 27 /* interrupt every 28 cycles */ #endif /*----------------------------------------------------------------*/ .SEGMENT /dm dm_data; .VAR data[N/L]; { ADC fixed-point data buffer } .ENDSEG; .SEGMENT /pm pm_data; .VAR coef[N]="coef.dat"; { fltg-pt. FIR coefficients } .ENDSEG; .SEGMENT /dm ports; .PORT adc; /* Analog to Digital (input) converter */ .PORT dac; /* Digital to Analog (output) converter */ .ENDSEG; {------------------------------------------------------------------------------- RESET Service Routine -------------------------------------------------------------------------------} .SEGMENT /pm rst_svc; rst_svc: PMWAIT=0x0021; { no wait states,internal ack only } DMWAIT=0x8421; { no wait states,internal ack only } jump init_int; { initialize the test case } .ENDSEG; {------------------------------------------------------------------------------- TMZH Service Routine -------------------------------------------------------------------------------} .SEGMENT /pm tmzh_svc; { sample input: this code occurs at the L*input rate } tmzh_svc: r2=r2-1,modify(i8,m9); { decrement counter, and } { shift coef pointer back } if eq call sample; { test and input if L times } jump interpol (db); { perform the interpolation } { filter pass, occurs at L times the input input rate } f0=dm(i0,m0), f4=pm(i8,m8); f8=f0*f4, f0=dm(i0,m0), f4=pm(i8,m8); .ENDSEG; {------------------------------------------------------------------------------- Initialize Interpolation Routine -------------------------------------------------------------------------------} .SEGMENT /pm pm_code; init_int: { initialize buffer index registers } b0=data; l0=@data; m0=1; b8=coef; l8=@coef; m8=L; { modifier for coef is L } m9=-1; { Register Initialization } r1=L; { value for upscaling sample } f1=float r1; { fix --> float conversion } r2=1; { set counter to 1 for 1st sample } r14=16; { exponent scale factor } tperiod=TPER_VAL; { program timer } tcount=TPER_VAL; bit set imask TMZHI; { enable TMZH interrupt } bit clr irptl TMZHI; { clear any pending interupts } bit set mode1 IRPTEN|ALUSAT; { enable interrupts, ALU sat } bit set mode2 TIMEN; { turn timer on } r0=0; { clear data buffer } lcntr=N, do clrbuf until lce; clrbuf: dm(i0,m0)=r0; wait: idle; jump wait; { infinite wait loop } .ENDSEG; {------------------------------------------------------------------------------- sample & interpolate code executes at the (L*input_rate) -------------------------------------------------------------------------------} .SEGMENT /pm pm_code; interpol: { FIR Filter, occurs at L times the input input rate. } { First two instructions of this filter have been run } { in the delay field of the jump to this routine } f12=f0*f4, f0=dm(i0,m0), f4=pm(i8,m8); lcntr=N/L-3, do taps until lce; taps: f12=f0*f4, f8=f8+f12, f0=dm(i0,m0), f4=pm(i8,m8); f12=f0*f4, f8=f8+f12; f0=f8+f12; rti (db); r0 = fix f0 by r14; { float -> fixed } dm(dac)=r0; { output data sample } .ENDSEG; {------------------------------------------------------------------------------- get sample code executes at the input_rate -------------------------------------------------------------------------------} .SEGMENT /pm pm_code; { input data sample once every L times (i.e., at the input rate) } sample: r0=dm(adc); { input data sample } { input sample occuppies bits 39:24; shift to 15:0 } r0 = ashift r0 by -16; { shift w/ sign extend } { do fix->float, and shift coef pointer up by L } f0=float r0, modify(i8,m8); rts(db), f0=f0*f1; { upscale sample by L } dm(i0,m0)=f0; { update delay line } r2=m8; { reset counter to L } .ENDSEG;