Skip to content Skip to navigation

Connexions

You are here: Home » Content » Spectrum Analyzer: Processor Exercise Using C Language

Navigation

Content Actions

  • Download module PDF
  • Add to ...
    Add the module to:
    • My Favorites
    • A lens
    • An external social bookmarking service
    • My Favorites (What is 'My Favorites'?)
      'My Favorites' is a special kind of lens which you can use to bookmark modules and collections directly in Connexions. 'My Favorites' can only be seen by you, and collections saved in 'My Favorites' can remember the last module you were on. You need a Connexions account to use 'My Favorites'.
    • A lens (What is a lens?)

      Definition of a lens

      Lenses

      A lens is a custom view of Connexions content. You can think of it as a fancy kind of list that will let you see Connexions through the eyes of organizations and people you trust.

      What is in a lens?

      Lens makers point to Connexions materials (modules and collections), creating a guide that includes their own comments and descriptive tags about the content.

      Who can create a lens?

      Any individual Connexions member, a community, or a respected organization.

      What are tags? tag icon

      Tags are descriptors added by lens makers to help label content, attaching a vocabulary that is meaningful in the context of the lens.

    • External bookmarks
  • E-mail the author
  • Rate this module (How does the rating system work?)

    Rating system

    Ratings

    Ratings allow you to judge the quality of modules. If other users have ranked the module then its average rating is displayed below. Ratings are calculated on a scale from one star (Poor) to five stars (Excellent).

    How to rate a module

    Hover over the star that corresponds to the rating you wish to assign. Click on the star to add your rating. Your rating should be based on the quality of the content. You must have an account and be logged in to rate content.

    (0 ratings)

Recently Viewed

This feature requires Javascript to be enabled.

Spectrum Analyzer: Processor Exercise Using C Language

Module by: Matthew Berry

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: Your browser may not currently support MathML. See our browser support page for additional details. You can always view the correct math in the PDF version.

Implementation

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.

Interrupt Basics

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.

Interrupt Handline

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=64 BlockLen 64 samples. However, the spectrum analyzer to be implemented in this lab works over a block of N=1024 N 1024 samples. If it were possible to compute a 1024-point FFT in the sample time of one 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 N=1024 N 1024 samples. Note that while the DSP is executing instructions in this loop, interrupts occur every 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.

  1. Transfer inputs and outputs (C)
  2. Apply a Hamming Window (C/assembly)
  3. Bit-reverse the input (assembly)
  4. Apply an N N-point FFT (assembly)
  5. Compute the magnitude-squared spectrum (C/assembly)
  6. Include a trigger pulse (C/assembly)

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 N N 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 N N samples to collect in the inputs buffer. Next, the N N inputs and outputs must be transferred. You are to write this portion of code. This portion of code is to be done first, within 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 N N samples to finish collecting.

The flow diagram in Figure 1 summarizes the operation of the interrupt handling routine

Figure 1: Overall program flow of the main function and the interrupt handling function.
(a) main(b) interrupt handler
Figure 1(a) (main_flow.png)Figure 1(b) (interrupt_flow.png)

FFT 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 N-point FFT, you will have to include the following label definitions in your assembly code.


	  
	  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 N N points long representing values from 0 to just shy of 180 degrees and must be accessed using a circular pointer. To include these tables at the proper location in memory with the appropriate labels referenced by the FFT, use the following


	  
	  .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 xn x n into a FFT so that the output Xk X k is in sequential order (i.e. X0 X 0 , X1 X 1 , …, XN1 X N 1 for an N N-point FFT). The following table illustrates the bit-reversed order for an eight-point sequence.

Table 1
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 [link]. 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 [link]. Note that the FFT code uses all the pointers available and does not restore the pointers to their original values.

Creating the Window

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.

Displaying the Spectrum

Once the DFT has been computed, you must calculate the squared magnitude of the spectrum for display.

|Xk|2=Xk2+Xk2 X k 2 X k 2 X k 2 (1)
You may find the assembly instructions 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 ( k=0 k 0 ) with a -1.0 when copying the magnitude values to the output buffer. The trigger pulse is necessary for the oscilloscope to lock to a specific point in the spectrum and keep the spectrum fixed on the scope.

Intrinsics

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 [link].

Quiz Information

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 N N and the type of window. Does the |Xk|2 X k 2 coincide with what you expect from Matlab? What is the relationship between the observed spectrum and the DTFT?

Appendix A:

lab4main.c


        
	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    }
        
      

Appendix B:

c_fft_given.asm


        
	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!
        
      

Comments, questions, feedback, criticisms?

Send feedback