Introduction | Moving Average Filters | FIR Filters | Code and data | IIR Filters | Transfer Functions |
If we go to the coefficients view, we can see the coefficients used to descibe the filter in numerical form which we can copy and paste into other applications. If you're using a third party DSP library, a third party product or writing your own code then you can select, copy and paste these directly.
The first set of coefficients is in floating point format and defines the unscaled coefficients for the whole filter.
If you are developing for an embedded system, then your device might not have a floating point unit or you may choose integer computation because it is faster. To select integer computation, use the "Arithmetic" field to select the word-lengths for memory, coefficients and accumulator. A good rule of thumb is to match these to the hardware multiplier on your device. If your embedded device has a 16x16 multiplier then one of the 16x16 options is usually a good choice. (A 16x16 multiplier multiples 2 16 bit numbers together to produce a 32 bit number). When using integer computation, it is a good idea to also check the output of the filter simulator by clicking on the right hand graph buttons. The simulator will imitate the effects of integer round-off and is a good way to spot if the real filter will work in the same way as the theoretical one.
If you are using integer filters then the remaining coefficients will be optimally scaled and listed in integer format.
The remaining coefficients are listed for each filter section. IIR filters are typically split into sub-filters (such as biquads) for stability reasons. Since FIR filters are always stable, the entire filter is usually in one section. If you have selected integer arithmetic then the remaining coefficients will also have been optimally scaled and converted to your selected integer format. The following diagram shows the scaled integer coefficients for an IIR filter.
If we go to the code view, we can see that it has automatically generated C code for the filter that we can copy and paste into our project. The header is listed first, followed by the C file and some test cases. Note that the test cases can increate the code size.
Looking at the header, we can see the following functions:
filter1Type * filter1_create( void ); // Create a new instance of the filter void filter1_destroy( filter1Type *pFilter ); // Destroy an instance of the filter void filter1_init( filter1Type *pFilter ); // Initialize a statically allocated filter void filter1_reset( filter1Type *pFilter ); // Reset the filter to its initial state void filter1_writeInput( filter1Type *pFilter, float input ); // Write to the filters input #define filter1_readOutput(THIS) ((THIS)->output) // Read from the filters output int filter1_filterBlock( filter1Type * pThis, float * pInput, float * pOutput, unsigned int count ); // Efficiently filter a batch of 'count' samples
We call create() to create a new filter and destroy() to destroy it once we are finished using it. We use writeInput() to feed data into the filter and readOutput() to read data from it.
Large projects can have many symbols and variables and we want to ensure that our filter does not collide with the names of other functions or libraries. We also want to give our filter a meaningful name so that we can identify it. You can use the "Class" field to change the name of the filter. All of the functions and structure definitions are prefixed by this name, esuring their uniqueness.
The frequency response of a digital filter is always measured relative to the sample rate, which is the speed (in samples per second) that numbers will be fed into the filter. The default is 1 Hz or 1 sample per second but a typical audio application might use, say 48000 samples per second. We can enter this value in the "Sample Rate" box and our displays will be updated to reflect the sample rate of that signal. Filters are always scaled relative to the sample rate, so changing the sample rate does not alter the filter coefficients or code. However, it does alter the scaling of the frequency displays on the graph and it's useful to see the actual frequencies and times of the signals rather than the normalized ones.
The following simple example shows how we might use the filter to process data coming from an analog to digital converter (ADC) then write it to a digital to analog converter (DAC). An ADC converts an analog voltage into a digital number. The DAC converts the digital number back into an analog voltage:
#include "fir1.h" short ADCRead( void ); // User supplied function to read an analog voltage void DACWrite( short value ); // User supplied function to write an analog voltage int main( int argc, char **argv ) // Typical C style main() { fir1Type *fir1 = fir1_create(); // Create the filter while( 1 ) // Infinite loop { fir1_writeInput( fir1, ADCRead() ); // Read a sample from the ADC and write it into the filter DACWrite( fir1_readOutput( fir1 ) ); // Read the output from the filter and write it to the DAC } fir1_destroy( fir1 ); // Done. If we every reach here, destroy the filter }
In practice, ADCRead() and DACWrite() would be implemented via an interrupt service routine so that the program does not have to stop while the peripheral is working. However, this simple example serves to illustrate the basic use of the filter.
On tiny embedded systems with tightly constrained memory, allocating memory can be hazardous. You may wish to pre-declare the filter then use the init() function to initialize it, rather than create() and destroy().
#include "fir1.h" short ADCRead( void ); // User supplied function to read an analog voltage void DACWrite( short value ); // User supplied function to write an analog voltage extern fir1Type fir1; fir1Type fir1; // Statically declare the filter int main( int argc, char **argv ) // Typical C style main() { fir1_init( &fir1 ); // Initialize the filter while( 1 ) // Infinite loop { fir1_writeInput( &fir1, ADCRead() ); // Read a sample from the ADC and write it into the filter DACWrite( fir1_readOutput( &fir1 ) ); // Read the output from the filter and write it to the DAC } }
The following example shows how we might initially process a 1D array of samples:
#include "fir1.h" fir1Type *fir1; // Declare pointer to the filter void filterSignal( short *inputArray, short *outputArray, int length ) { for( int i = 0; i < length; ++ i ) // Loop for the length of the array { fir1_writeInput( fir1, inputArray[i] ); // Write one sample into the filter outputArray[ i ] = fir1_readOutput( fir1 ); // Read one sample from the filter and store it in the array. } } int main(int argc, char **argv) { fir1 = fir1_create(); // Create the filter once at startup (or see "Statically Allocating the Filter", above) ... // Do something / main event loop fir1_destroy(fir1); // Done. Destroy the filter }
The above example introduces the concept. However, multiple samples can be more efficiently processed in batches. e.g. fir1_filterBlock() allows you to more efficiently process multiple samples at one time. The filter code is optimized to handle this internally and it is much faster than processing samples one at a time, as in filterSignal(), above. The above filterSignal() may be more efficiently replaced with the following:
void filterSignal( short *inputArray, short *outputArray, int length ) { fir1_filterBlock( fir1, inputArray, outputArray, length); // Filter 'length' samples more efficiently in a single batch }
You should be aware that filters delay the signal they are processing. You may notice that the output has been offset from the input because of this delay. Higher order filters have longer delays.
For FIR filters, the group delay (in samples) is half the filter length - 1. So, for a 31 tap FIR filter, the delay is (31 - 1)/2 = 15 samples.
FIR filters with an odd numbers of taps have delays that are an integer number of samples.
IIR filters usually have asymmetric impulse responses. Their delay is frequency dependent and can be seen by clicking the "Show the filter's group delay" button to the left of the graphs.
FIR filters and other filters with symmetric impulse responses have a delay that is constant across all frequencies. These are called linear phase filters, because phase shift varies linearly with frequency.
Introduction | Moving Average Filters | FIR Filters | Code and data | IIR Filters | Transfer Functions |