I’ve been trying to create a simple DFT example for the dsPIC. I found it more difficult than I expected to come up with something which is both useful and readable. I’m intentionally not doing either of the following:
- Using the special DSP hardware features of the dsPIC.
- Using an FFT algorithm.
Obviously, this could run faster if I did either or both of the above items. However, I’m trying to create an example that’s as understandable as possible, rather than one which is as fast as possible.
This example records a 64-sample frame of data from AN0 (analog input channel 0, pin 2) and then calculates the DFT and power spectrum, up to and including the Nyquist frequency. (Since the input data is real, the frequency spectrum values above Nyquist are redundant.) The program then searches through the power spectrum to identify the frequency bin of maximum power. Bin 0 (DC) is exluded, since the input signal will presumably have a large DC component.
//
// dsPIC DFT Example
// Written by Ted Burke
// Last updated 10-12-2013
//
#include <xc.h>
#include <stdio.h>
#include <libpic30.h>
// Configuration settings
_FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, Fcy=30MHz
_FWDT(WDT_OFF); // Watchdog timer off
_FBORPOR(MCLR_DIS); // Disable reset pin
// Function prototypes
unsigned int read_analog_channel(int n);
// Window length, N (greater than 4 and a power of 2)
#define N 64
// Twiddle factors (64th roots of unity)
const float W[] = {
1.00000, 0.99518, 0.98079, 0.95694, 0.92388, 0.88192, 0.83147, 0.77301,
0.70711, 0.63439, 0.55557, 0.47139, 0.38268, 0.29028, 0.19509, 0.09801,
-0.00000,-0.09802,-0.19509,-0.29029,-0.38269,-0.47140,-0.55557,-0.63440,
-0.70711,-0.77301,-0.83147,-0.88192,-0.92388,-0.95694,-0.98079,-0.99519,
-1.00000,-0.99518,-0.98078,-0.95694,-0.92388,-0.88192,-0.83146,-0.77300,
-0.70710,-0.63439,-0.55556,-0.47139,-0.38267,-0.29027,-0.19508,-0.09801,
0.00001, 0.09803, 0.19510, 0.29030, 0.38269, 0.47141, 0.55558, 0.63440,
0.70712, 0.77302, 0.83148, 0.88193, 0.92388, 0.95694, 0.98079, 0.99519
};
int main()
{
// Configure AN0-AN8 as analog inputs
ADCON3bits.ADCS = 15; // Tad = 266ns, conversion time is 12*Tad
ADCON1bits.ADON = 1; // Turn ADC ON
// Setup UART
U1BRG = 48; // 38400 baud @ 30 MIPS
U1MODEbits.UARTEN = 1; // Enable UART
// time and frequency domain data arrays
int n, k; // time and frequency domain indices
float x[N]; // discrete-time signal, x
float Xre[N/2+1], Xim[N/2+1]; // DFT of x (real and imaginary parts)
float P[N/2+1]; // power spectrum of x
int a, b; // twiddle indices
int to_sin = 3*N/4; // index offset for sin
int k_max; // index of max frequency bin
float X_max; // power at max frequency bin
while(1)
{
// Record N samples @ 10kHz
for (n=0 ; n<N ; ++n)
{
x[n] = read_analog_channel(0);
__delay32(3000); // 100us delay
}
// Calculate DFT and power spectrum up to Nyquist frequency
for (k=0 ; k<=N/2 ; ++k)
{
Xre[k] = 0; Xim[k] = 0;
a = 0; b = to_sin;
for (n=0 ; n<N ; ++n)
{
Xre[k] += x[n] * W[a%N];
Xim[k] -= x[n] * W[b%N];
a += k; b += k;
}
P[k] = Xre[k]*Xre[k] + Xim[k]*Xim[k];
}
// Find and print max amplitude frequency bin
X_max = 0;
for (k=1 ; k<=N/2 ; ++k) if (P[k] > X_max)
{
X_max = P[k];
k_max = k;
}
printf("Max freq bin: %d\n", k_max);
}
return 0;
}
// This function reads a single sample from the specified
// analog input. It should take less than 5us when the
// microcontroller is running at 30 MIPS.
// The dsPIC30F4011 has a 10-bit ADC, so the value
// returned is between 0 and 1023 inclusive.
unsigned int read_analog_channel(int channel)
{
ADCHS = channel; // Select the requested channel
ADCON1bits.SAMP = 1; // Start sampling
__delay32(30); // 1us delay @ 30 MIPS
ADCON1bits.SAMP = 0; // Start Converting
while (!ADCON1bits.DONE); // Should take 12 * Tad = 3.2us
return ADCBUF0;
}
I saved the program above to a file called “main.c” and compiled it using Microchip’s XC16 C compiler. This is my build script:
xc16-gcc main.c -mcpu=30F4011 -Wl,--script=p30F4011.gld if errorlevel 0 xc16-bin2hex a.out
By the way, I generated the values for the global array of twiddle factors in the above example using the following short C program (which runs on the PC rather than the dsPIC):
//
// twiddle.c - Print array of twiddle factors
// Written by Ted Burke
// Last updated 10-12-2013
//
// To compile:
// gcc twiddle.c -o twiddle.exe
//
// To run:
// twiddle.exe
//
#include <stdio.h>
#include <math.h>
#define N 64
int main()
{
int n;
for (n=0 ; n<N ; ++n)
{
printf("%8.5lf", cos(n*6.2832/N));
if (n<N-1) printf(",");
if ((n+1)%8==0) printf("\n");
}
return 0;
}