Saturday, 17 January 2015

How To Connect Digital Signal Processing Functions To An ADC And A DAC

In my earlier post I showed how to implement an Infinite Impulse Response (IIR) filter :
Let's Do Some More Digital Signal Processing - Infinite Impulse Response (IIR) Filters.

In this blog I will look at how to interface DSP functions (e.g. the SigLib DSP library functions) to an Analog to Digital Converter (ADC) or Digital to Analog Converter (DAC). For this blog I will use the IIR filter functions mentioned in the above blog entry.

The system configuration is shown in this diagram :



In order to handle the ADC/DAC data input and output (I/O) it is necessary to write an Interrupt Service Routine (ISR) that responds to the interrupt from the ADC/DAC.

Any computer with a soundcard can be used to demonstrate this however modern Operating Systems trap interrupts and use drivers to interface to the hardware. In order to demonstrate hardware interrupts it is necessary to write a virtual software interrupt generator using a call back function.
I am going to use the free, cross-platform open-source audio I/O library PortAudio to implement the I/O then I will call the callback function that we will use as our ISR.

On a typical embedded system it will not be necessary to use PortAudio, just hook your ISR function to the interrupt from your ADC/DAC and it should work in exactly the same way.

Here is the header file (analog_io.h.) Note that I have declared the input and output registers as global variables. I know this doesn't conform to any standards for structured programming however these are designed to represent the input and output registers of the ADC and DAC devices.

int analog_open (int SampleRate, void (*analog_isr) (void));
int analog_close (void);

volatile short adc_in0, adc_in1, dac_out0, dac_out1;

Here is the wrapper code (analog_io.c.) for simulating our ISR using PortAudio. Note the pointer to the ISR is passed to the function analog_open () along with the desired sample rate.


#include <stdio.h>
#include "portaudio.h"
#include "analog_io.h"

#define PA_SAMPLE_TYPE      paInt32
#define FRAMES_PER_BUFFER   (64)

void (*p_analog_isr) (void);

typedef int SAMPLE;
PaStream *stream;

static int paCallback( const void *inputBuffer, void *outputBuffer,
                       unsigned long framesPerBuffer,
                       const PaStreamCallbackTimeInfo* timeInfo,
                       PaStreamCallbackFlags statusFlags,
                       void *userData );

/* PortAudio callback */
static int paCallback( const void *inputBuffer, void *outputBuffer,
                       unsigned long framesPerBuffer,
                       const PaStreamCallbackTimeInfo* timeInfo,
                       PaStreamCallbackFlags statusFlags,
                       void *userData )
{
    SAMPLE *out = (SAMPLE*)outputBuffer;
    const SAMPLE *in = (const SAMPLE*)inputBuffer;
    unsigned int i;
    (void) timeInfo; /* Prevent unused variable warnings. */
    (void) statusFlags;
    (void) userData;

    if( inputBuffer == NULL )         // If input buffer empty then output silence
    {
        for( i=0; i<framesPerBuffer; i++ )
        {
            *out++ = 0;               /* Left */
            *out++ = 0;               /* Right */
        }
    }
    else                              // If input buffer NOT empty then process data
    {
        for( i=0; i<framesPerBuffer; i++ )
        {
          adc_in0 = (short)(*in++ >> 16);            /* Left */
          adc_in1 = (short)(*in++ >> 16);            /* Right */
          p_analog_isr ();
          *out++ = (int)(dac_out0 << 16);
          *out++ = (int)(dac_out1 << 16);
        }
    }
    return paContinue;
}


int analog_open (int SampleRate, void (*analog_isr) (void))
{
  p_analog_isr = analog_isr;

  PaStreamParameters inputParameters, outputParameters;
  PaError err;

  err = Pa_Initialize();
  if( err != paNoError ) goto error;

  inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */
  if (inputParameters.device == paNoDevice) {
    fprintf(stderr,"Error: No default input device.\n");
    goto error;
  }
  inputParameters.channelCount = 2;       /* stereo input */
  inputParameters.sampleFormat = PA_SAMPLE_TYPE;
  inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency;
  inputParameters.hostApiSpecificStreamInfo = NULL;

  outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
  if (outputParameters.device == paNoDevice) {
    fprintf(stderr,"Error: No default output device.\n");
    goto error;
  }
  outputParameters.channelCount = 2;       /* stereo output */
  outputParameters.sampleFormat = PA_SAMPLE_TYPE;
  outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;
  outputParameters.hostApiSpecificStreamInfo = NULL;

  err = Pa_OpenStream(&stream,
                      &inputParameters,
                      &outputParameters,
                      SampleRate,
                      FRAMES_PER_BUFFER,
                      0, /* paClipOff, */  /* we won't output out of range samples so don't bother clipping them */
                      paCallback,
                      NULL );
  if( err != paNoError ) goto error;

  err = Pa_StartStream( stream );
  if( err != paNoError ) goto error;

  return 0;

error:
  Pa_Terminate();
  fprintf( stderr, "An error occured while using the portaudio stream\n" );
  fprintf( stderr, "Error number: %d\n", err );
  fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
  return -1;
}

int analog_close (void)

{
  int err;

  err = Pa_CloseStream( stream );
  if( err != paNoError ) goto error;

  Pa_Terminate();
  return 0;

error:
    Pa_Terminate();
    fprintf( stderr, "An error occured while using the portaudio stream\n" );
    fprintf( stderr, "Error number: %d\n", err );
    fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
  return -1;
}

Here is the header file (iir.h.) for the IIR filtering functions :

void iir_init (double *pState,
  const int NumberOfStages);

double iir_filter (const double Source,
  double *pState,
  const double *pCoeffs,
  const int NumberOfStages);

And here are the IIR filtering functions (taken from the blog mentioned earlier)  (iir.c.) :

#include "iir.h"

void iir_init (double *pState,
  const int NumberOfStages)

{
  int  i;

      /* Initialise the filter state array to 0 */
  for (i = 0; i < (NumberOfStages * 2); i++)
  {
    *pState++ = 0.0;
  }

}   /* End of iir_init () */


double iir_filter (const double Source,
  double *pState,
  const double *pCoeffs,
  const int NumberOfStages)

{
  register double       FeedbackSumOfProducts;
  register int i;
  register double       TempInputData;

  TempInputData = Source;

  for (i = 0; i < NumberOfStages; i++)
  {
    FeedbackSumOfProducts = TempInputData - (*(pCoeffs+3) * *pState) -
        (*(pCoeffs+4) * *(pState+1));       /* Feedback */
    TempInputData = (*pCoeffs * FeedbackSumOfProducts) +
        (*(pCoeffs + 1) * *pState) +
        (*(pCoeffs + 2) * *(pState+1));     /* Feedforward and save result for next time round */
    
    *(pState+1) = *pState;                  /* Move delayed samples */
    *pState = FeedbackSumOfProducts;

    pState += 2;        /* Increment array pointers */
    pCoeffs += 5;
  }

  return (TempInputData);                     /* Save output */

}   /* End of iir_filter () */

Now all we need is the top level program (pa_filter.c) which includes the filter coefficients (generated using Digital Filter Plus http://www.numerix-dsp.com/dfplus/), the main () function to initialize the filters and the analog interface and we need the ISR to call the DSP functions when the interrupt occurs.

#include <stdio.h>
#include <math.h>
#include "analog_io.h"
#include "iir.h"

#define SAMPLE_RATE     44100
#define FILTER_STAGES   1           /* Single 2nd ordrer biquad */

// Fc = 1 kHz LPF
double LPFCoefficientArray [] =
{
3.43448050764769180000e-003, 6.86896101529538370000e-003, 3.43448050764769180000e-003,
-1.89857508921973460000e+000, 9.12313011250325380000e-001
};

// Fc = 1 kHz HPF
double HPFCoefficientArray [] =
{
9.32663817644617140000e-001, -1.86532763528923430000e+000, 9.32663817644617140000e-001,
-1.85191320077338670000e+000, 8.78746746701696060000e-001
};

double Chan0FilterState [2 * FILTER_STAGES], Chan1FilterState [2 * FILTER_STAGES];


void analog_isr (void)
{
  double analog_sample;

                            // Process channel 0
  analog_sample = (double)adc_in0;
  analog_sample = iir_filter (analog_sample, Chan0FilterState, LPFCoefficientArray, FILTER_STAGES);
  dac_out0 = (short)(analog_sample);

                            // Process channel 1
  analog_sample = (double)adc_in1;
  analog_sample = iir_filter (analog_sample, Chan1FilterState, HPFCoefficientArray, FILTER_STAGES);
  dac_out1 = (short)(analog_sample);

}

int main(void)
{
  int Error;

  iir_init (Chan0FilterState, FILTER_STAGES);
  iir_init (Chan1FilterState, FILTER_STAGES);

  Error = analog_open (SAMPLE_RATE, analog_isr);  // Open the analog interface
  if (Error == -1)
    return 1;

  printf("Hit ENTER to stop program.\n");
  getchar();

  Error = analog_close ();                        // Close the analog interface
  if (Error == -1)
    return 1;

  return 0;
}

Testing

Under Microsoft Visual Studio the code can be built using the following command (assuming you have already got a working PortAudio installation) :
cl pa_filter.c analog_io.c iir.c -DPA_USE_ASIO=1 portaudio_x86.lib

My laptop only supports a single microphone input to the ADC so to test the code I used an XMOS Multi-Function Audio (MFA) platform, shown in this image :



The XMOS xCORE processor on this board is more than capable of handling the signal processing code shown in this blog but for the purposes of the demonstration I am running the DSP code on the host.

If you run the code yourself you will hear the difference between the two channels after applying the Low-pass and High-pass filters.

2 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Thanks for the article. It was quite useful for me. You've mentioned virtual software, thus I would like to ask if you ever made any virtual data rooms reviews ? If yes, is it ok to ask you to provide me with a link to this article?
    Regards,
    Rick

    ReplyDelete