Summary: This module describes a processor exercise in which students implement a spectrum analyzer using mixed C and assembly code. Students are to acquire a block of 1024 samples, apply a Hamming window, compute a length-1024 Discrete Fourier Transform using provided Fast Fourier Transform code, and display the magnitude-squared spectrum on an oscilloscope.
Note: You are viewing an old version of this document. The latest version is available here.
As your spectrum analyzer works on a block of samples at a
time, you will need to use interrupts to pause your processing
while samples are transferred from/to the CODEC (A/D and D/A)
buffer. Fortunately, the interrupt handling routines have
been written for you in a C shell program available at
v:\ece320\54x\dspclib\lab4main.c and the core
code.
Interrupts are an essential part of the operation of any microprocessor. They are particularly important in embedded applications where DSPs are often used. Hardware interrupts provide a way for interacting with external devices while the processor executes code. For example, in a key entry system, a key press would generate a hardware interrupt. The system code would then jump to a specified location in program memory where a routine could process the key input. Interrupts provide an alternative to polling. Instead of checking for key presses at a predetermined rate (requires a clock), the system could be busy executing other code. On the TI-C54x DSP, interrupts provide a convenient way to transfer blocks of data to/from the CODEC in a timely fashion.
The lab4main.c and the core code are intended
to make your interaction with the hardware much simpler. At
the heart of this interaction is the auto-buffering serial
port. In the auto-buffering serial mode, the TI-C54x
processor is able to do processing
uninterrupted while samples are
transferred to/from a buffer of length
BlockLen, then no additional interrupt handling
routines would be necessary. Samples could be collected in
a 1024-length buffer and a 1024-point FFT could be computed
uninterrupted while the auto-buffering buffer fills.
Unfortunately, the DSP is not fast enough to accomplish this
task.
We now provide an explanation of the shell C program
lab4main.c listed in Appendix A. The
lab4main.c file contains the function
interrupt void irq and a main program. The
main program is an infinite loop over blocks of
BlockLen samples. Inside the infinite loop,
you will insert code to do the operations which
follow. Although each of these operations may be performed
in C or assembly, we suggest you follow the guidelines
suggested.
An interrupt from the CODEC occurs every
BlockLen samples. The
SetAudioInterrupt(irq) call in the main program
tells the core code to jump to the irq function
when an interrupt occurs. In the irq function,
BlockLen samples of the A/D input in
Rcvptr (channel 1) are written to a length
inputs buffer, and
BlockLen of the output samples in the
outputs buffer are written to the D/A output
via Xmitptr on channel 2. On channel 1 of the
output, the input is echoed out. You are to fill
the buffer outputs with the windowed
magnitude-squared FFT values by performing the operations
listed above.
In the main code, the while(!input_full);
loop waits for
inputs
buffer. Next, the
BlockLen sample times;
otherwise the first BlockLen of samples of
output would not be available on time. Once this loop is
finished, the lengthy processing of the FFT can continue.
During this processing, the DSP is interrupted every
BlockLen samples to transfer samples. Once
this processing is over, the infinite loop returns to
while(!input_full); to wait for
The flow diagram in Figure 1 summarizes the operation of the interrupt handling routine
|
As the list of operations indicates, bit-reversal and FFT
computation are to be done in assembly. We are providing
you with a shell assembly file, available at
v:\ece320\54x\dspclib\c_fft_given.asm and shown
in Appendix B, containing many
useful declarations and some code. The code for performing
bit-reversal and other declarations needed for the FFT
routine are also provided in this section.
However, we would like you to enter this code
manually, as you will be expected to understand its
operation.
Now, we explain how to use the FFT routine provided by TI
for the C54x. The FFT routine fft.asm located
in v:\ece320\54x\dsplib\ computes an in-place,
complex FFT. The length of the FFT is defined as a label
K_FFT_SIZE and the algorithm assumes that the
input starts at data memory location _fft_data.
To have your code assemble for an
N .set 1024
K_FFT_SIZE .set N ; size of FFT
K_LOGN .set 10 ; number of stages (log_2(N))
In addition to defining these constants, you will have to
include twiddle-factor tables for the FFT. These tables
twiddle1 and twiddle2) are available in the shared
directory v:\ece320\54x\dsplib\. Note that the
tables are each
.sect ".data"
.align 1024
sine .copy "v:\ece320\54x\dsplib\twiddle1"
.align 1024
cosine .copy "v:\ece320\54x\dsplib\twiddle2"
The FFT provided requires that the input be in bit-reversed
order, with alternating real and imaginary
components. Bit-reversed addressing is a convenient way to
order input
| Input Order | Binary Representation | Bit-Reversed Representation | Output Order |
|---|---|---|---|
| 0 | 000 | 000 | 0 |
| 1 | 001 | 100 | 4 |
| 2 | 010 | 010 | 2 |
| 3 | 011 | 110 | 6 |
| 4 | 100 | 001 | 1 |
| 5 | 101 | 101 | 5 |
| 6 | 110 | 011 | 3 |
| 7 | 111 | 111 | 7 |
The following routine performs the bit-reversed reordering
of the input data. The routine assumes that the input is
stored in data memory starting at the location labeled
_bit_rev_data, which must be aligned to the
least power of two greater than the input buffer length, and
consists of alternating real and imaginary parts. Because
our input data is going to be purely real in this lab, you
will have to make sure that you set the imaginary parts to
zero by zeroing out every other memory location.
1 bit_rev:
2 STM #_bit_rev_data,AR3 ; AR3 -> original input
3 STM #_fft_data,AR7 ; AR7 -> data processing buffer
4 MVMM AR7,AR2 ; AR2 -> bit-reversed data
5 STM #K_FFT_SIZE-1,BRC
6 RPTBD bit_rev_end-1
7 STM #K_FFT_SIZE,AR0 ; AR0 = 1/2 size of circ buffer
8 MVDD *AR3+,*AR2+
9 MVDD *AR3-,*AR2+
10 MAR *AR3+0B
11 bit_rev_end:
12 NOP
As mentioned, in the above code _bit_rev_data
is a label indicating the start of the input data and
_fft_data is a label indicating the start of a
circular buffer where the bit-reversed data will be
written. Note that although AR7 is not used by
the bit-reversed routine directly, it is used extensively in
the FFT routine to keep track of the start of the FFT data
space.
In general, to have a pointer index memory in bit-reversed
order, the AR0 register needs to be set to
one-half the length of the circular buffer; a statement such
as ARx+0B is used to move the ARx
pointer to the next location. For more information regarding
the bit-reversed addressing mode, refer to page
5-18 in the TI-54x
CPU and Peripherals manual. Is it possible to
bit-reverse a buffer in place? For a diagram of the
ordering of the data expected by the FFT routine, see
Figure 4-10 in the TI-54x
Applications Guide. Note that the FFT code uses all
the pointers available and does not restore the pointers to
their original values.
As mentioned, you will be using the FFT to compute the
spectrum of a windowed input. For your implementation you
will need to create a 1024-point Hamming window. Create a
Hamming window in matlab using the function
hamming, then use save_coef to
save the window to a file that can then be included in your
code with the .copy directive.
Once the DFT has been computed, you must calculate the squared magnitude of the spectrum for display.
squr and squra useful in
implementing Equation 1.
Because the squared magnitude is always nonnegative, you can
replace one of the magnitude values with a -1.0 as a trigger
pulse for display on the oscilloscope. This is easily
performed by replacing the DC term (
If you are planning on writing some of the code in C, then
you may be forced to use intrinsics. Intrinsic instructions
provide a way to use assembly instructions directly in C.
An example of an intrinsic instruction is
bit_rev_data[0]=_smpyr(bit_rev_data[0],window[0])
which performs the assembly signed multiply round
instruction. You may also find the _lsmpy
instruction useful. For more information on intrinsics, see
page 6-22 of the TI-C54x
Optimizing C/C++ Compiler User's Guide.
From your prelab experiments, you should be able to describe
the effect of windowing and zero-padding on FFT spectral
analysis. In your DSP system, experiment with different
inputs, changing
1 /* v:/ece320/54x/dspclib/lab4main.c */
2 /* dgs - 9/14/2001 */
3
4 #include "v:/ece320/54x/dspclib/core.h"
5
6 #define N 1024 /* Number of FFT points */
7
8 /* Function defined by c_fft_given.asm */
9 void bit_rev_fft(void);
10
11 /* FFT data buffers (declared in c_fft_given.asm) */
12 extern int bit_rev_data[N*2]; /* Data input for bit-reverse function */
13 extern int fft_data[N*2]; /* In-place FFT & Output array */
14 extern int window[N]; /* The Hamming window */
15
16 /* Our input/output buffers */
17 int inputs[N];
18 int outputs[N];
19
20 volatile int input_full = 0; /* volatile means interrupt changes it */
21 int count = 0;
22
23 interrupt void irq(void)
24 {
25 int *Xmitptr,*Rcvptr; /* pointers to Xmit & Rcv Bufs */
26 int i;
27
28 static int in_irq = 0; /* Flag to prevent reentrance */
29
30 /* Make sure we're not in the interrupt (should never happen) */
31 if( in_irq )
32 return;
33
34 /* Mark we're processing, and enable interrupts */
35 in_irq = 1;
36 enable_irq();
37
38 /* The following waitaudio call is guaranteed not to
39 actually wait; it will simply return the pointers. */
40 WaitAudio(&Rcvptr,&Xmitptr);
41
42 /* input_full should never be true... */
43 if( !input_full )
44 {
45 for (i=0; i<BlockLen; i++)
46 {
47 /* Save input, and echo to channel 1 */
48 inputs[count] = Xmitptr[6*i] = Rcvptr[4*i];
49
50 /* Send FFT output to channel 2 */
51 Xmitptr[6*i+1] = outputs[count];
52
53 count++;
54 }
55 }
56
57 /* Have we collected enough data yet? */
58 if( count >= N )
59 input_full = 1;
60
61 /* We're not in the interrupt anymore... */
62 disable_irq();
63 in_irq = 0;
64 }
65
66 main()
67 {
68 /* Initialize IRQ stuff */
69 count = 0;
70 input_full = 0;
71 SetAudioInterrupt(irq); /* Set up interrupts */
72
73 while (1)
74 {
75 while( !input_full ); /* Wait for a data buffer to collect */
76
77 /* From here until we clear input_full can only take *
78 * BlockLen sample times, so don't do too much here. */
79
80 /* First, transfer inputs and outputs */
81
82 /* . . . i n s e r t y o u r c o d e h e r e . . . */
83
85 /* Done with that... ready for new data collection */
86 count = 0; /* Need to reset the count */
87 input_full = 0; /* Mark we're ready to collect more data */
88
89 /************************************************************/
90 /* Now that we've gotten the data moved, we can do the *
91 * more lengthy processing. */
92
93 /* Multiply the input signal by the Hamming window. */
94
95 /* . . . i n s e r t y o u r c o d e h e r e . . . */
96 /* o r i n a s s e m b l y */
97
98 /* Bit-reverse and compute FFT*/
99 bit_rev_fft();
100
101 /* Now, take absolute value squared of FFT */
102 /* . . . i n s e r t y o u r c o d e h e r e . . . */
103 /* o r i n a s s e m b l y */
104
105 /* Last, set the DC coefficient to -1 for a trigger pulse */
106 /* . . . i n s e r t y o u r c o d e h e r e . . . */
107 /* o r i n a s s e m b l y */
108
109 /* done, wait for next time around! */
110 }
111 }
1 ; v:/ece320/54x/dspclib/c_fft_given.asm
2 ; dgs - 9/14/2001
3 .copy "v:\ece320\54x\dspclib\core.inc"
4
5 .global _bit_rev_data
6 .global _fft_data
7 .global _window
8
9 .global _bit_rev_fft
10
11 .sect ".data"
12
13 .align 4*N
14 _bit_rev_data .space 16*2*N ; Input to _bit_rev_fft
15
16 .align 4*N
17 _fft_data .space 16*2*N ; FFT output buffer
18
19
20 ; Copy in the Hamming window
21 _window ; The Hamming window
22 .copy "window.asm"
23
24 .sect ".text"
25
26 _bit_rev_fft
27 ENTER_ASM
28
29 call bit_rev ; Do the bit-reversal.
30
31 call fft ; Do the FFT
32
33 LEAVE_ASM
34 RET
35
36 ; Copy the actual FFT subroutine.
37 fft_data .set _fft_data ; FFT code needs this.
38 .copy "v:/ece320/54x/dsplib/fft.asm"
39
40
41 ; If you need any more assembly subroutines, make sure you name them
42 ; _name, and include a ".global _name" directive at the top. Also,
43 ; don't forget to use ENTER_ASM at the beginning, and LEAVE_ASM
44 ; and RET at the end!