Simple real-time audio DSP on the dsPIC

This is the real-time audio DSP example we worked through in today’s Robotics class as an example of interrupt-driven signal sampling. The signal source was a moving coil loudspeaker (used here as a microphone).

The program repeats the following steps indefinitely:

  1. Read 200 samples from AN0 at a sampling frequency of 10kHz (a 20ms window length). Store the samples in an int array (the audio data buffer).
  2. Calculate the average sample value in the 200 sample window.
  3. Estimate the short-term signal amplitude over the 200 sample window by calculating the distance between each sample and the average value.
  4. Print the average and amplitude to the PC screen via UART.

Circuit

This is the circuit used.

This is a photo of the working system. Unfortunately, the camera on my phone really isn’t great for this kind of thing, so it’s difficult to make out much of the detail.

Program Code

This is the complete C code.

//
// dsPIC30F4011 audio DSP example
// Written by Ted Burke
// Last updated 8-11-2012
//
// This program uses the Timer 1 interrupt
// to drive sampling of an audio signal on
// AN0 at a sampling frequency of 10kHz.
// Whenever the sample counter is reset to
// zero, the ISR begins adding new samples
// to the audio buffer until it is full
// (the buffer length is 200), after which
// the ISR continues to be called every
// 100us, but samples are not added to the
// buffer.
//
// The while loop in the main function
// resets the sample counter, then waits
// for it to fill up, at which point it
// begins analysing the audio data. It
// calculates the average sample value
// and estimates the short term signal
// amplitude. Both values are printed
// to the PC via the UART.
//

#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 prototype for analog read
unsigned int read_analog_channel(int n);

// Function prototype for Timer 1 interrupt service routine
void __attribute__((__interrupt__, __auto_psv__)) _T1Interrupt(void);

// audio data buffer and counter
int v[200], n=0;	

int main()
{
	// Declare variables
	int i;
	double average, amplitude;
	
	// Make all port D pins outputs
	TRISD = 0;

	// Configure AN0-AN8 as analog inputs
	ADCON3bits.ADCS = 15;  // Tad=266ns, conversion time=12*Tad
	ADCON1bits.ADON = 1;   // Turn ADC ON
	
	// Setup UART
	U1BRG = 48;            // 38400 baud @ 30 MIPS
	U1MODEbits.UARTEN = 1; // Enable UART
	
	// Configure Timer 1
	PR1 = 3000;           // 100us sampling period
	TMR1 = 0;             // Reset Timer 1 counter
	IEC0bits.T1IE = 1;    // Enable Timer 1 interrupt
	T1CONbits.TCKPS = 0;  // Prescaler set to 1:1
	T1CONbits.TON = 1;    // Turn on Timer 1
	
	while(1)
	{
		// Reset sample counter and wait for
		// audio buffer to be filled with 200
		// new samples by the Timer 1 ISR
		n = 0;
		while(n<200);
		
		// Find average (dc level of signal)
		average = 0;
		for(i=0 ; i<200 ; ++i)
		{
			average = average + v[i];
		}
		average = average / 200.0;
		
		// Estimate signal amplitude
		amplitude = 0;
		for (i=0 ; i<200 ; ++i)
		{
			// Calculate each sample's distance from
			// the average level and add it to the
			// total
			if (v[i] < average)
				amplitude = amplitude + (average - v[i]);
			else
				amplitude = amplitude + (v[i] - average);
		}
		amplitude = amplitude / 200.0;
		
		printf("%f %f\n", average, amplitude);
	}
	
	return 0;
}

// Timer 1 interrupt service routine
void __attribute__((__interrupt__, __auto_psv__)) _T1Interrupt(void)
{
	// Clear Timer 1 interrupt flag
	IFS0bits.T1IF = 0;

	// Toggle LED on RD1
	if (n<200)
	{
		v[n] = read_analog_channel(0);
		n = n + 1;
	}
}

// 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 built the program using Microchip’s XC16 compiler. I don’t use MPLAB, so I used the following build script (saved as the batch file “build.bat” in the same directory as main.c).

xc16-gcc main.c -mcpu=30F4011 -Wl,--script=p30F4011.gld
if errorlevel 0 xc16-bin2hex a.out

Assuming the program compiles without errors, the script “build.bat” produces an output file called “a.hex” which I downloaded to the dsPIC30F4011 using the PICkit2 software application.

Program Output

The following are screenshots of the PICkit2 application’s UART tool displaying the output of the program during different sounds.

During silence (very low amplitude):

During “sss” speech sound (quite low amplitude):

During an “aaah” speech sound (relatively high amplitude):

Advertisements
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s