This is an old revision of the document!
In this milestone you will implement most of the 'C' code that implements the laser-tag system.
Demonstrate the system working with a feedback cable (provided) connecting the output of the transmitter to the input of the ADC. You will demonstrate the ability of your system to:
This milestone will be divided into three tasks that will be due and passed off weekly.
Note: you don't need the feedback cable until the final task.
In this task you will implement the FIR and IIR filters using 'C' code. Let's start with the FIR filter. Remember that the FIR filter is implemented as a weighted sum of some past number of inputs. Here's an example from Wikipedia:
It can be confusing to transition from the finite array-based approach used in MatLab to the “infinite” approach that is required in the implementation of a signal-processing system. To wit, the inputs and outputs of a real-time signal-processing system are essentially infinite. As such, the array-based notation in the equation above fails us because the output is an indexed array y[n]
. You want to eliminate the [n]
part so that the output is simply y
.
When we saw the English-based description from Wikipedia (see above), there were no indexes. Remember that the FIR-filter is implemented as a weighted-sum of some past number of inputs. All those indexes, the i
, the k
, etc., are just a way to keep the coefficients aligned with the data. As long as we can keep the incoming inputs properly aligned with the coefficients, we are good to go.
The idea is pretty simple and is based upon these ideas:
As you have probably guessed at this point, the queues that you implemented as a part of Milestone 1 are the perfect data structure for this purpose.
You can implement a FIR filter using the queues that you have already coded. Consider an example where the FIR filter uses 4 past values to compute its output. In the example code below, I have “pushed” four values onto the queue. Assume that these are 4 values that are based on values from the ZYBO's ADC.
#include "queue.h" #include <stdio.h> #define FIR_COEF_COUNT 4 int main() { // Initialization stuff. queue_t xQ; // x is the queue with the input history for the queue. queue_init(&xQ, FIR_COEF_COUNT); // Size of history queue must equal coefficient count. for (int i=0; i<FIR_COEF_COUNT; i++) // Start out with an empty queue (all zeroes). queue_overwritePush(&xQ, 0.0); double b[FIR_COEF_COUNT] = {0.25, 0.5, 0.75, 1.0}; // These coefficients are used for this example. // Add some example inputs to the queue. queue_overwritePush(&xQ, -0.1); // Add a new input to the queue (oldest in input history). queue_overwritePush(&xQ, -0.4); // Add a new input to the queue. queue_overwritePush(&xQ, 0.24); // Add a new input to the queue. queue_overwritePush(&xQ, 0.54); // Add a new input to the queue (newest in input history). // Compute output of FIR-filter (y) // using a single lone statement (broken into 4 lines to keep it readable). // This is just for example. You will use a for-loop as shown below. double y; y = queue_readElementAt(&xQ, 0) * b[3] + queue_readElementAt(&xQ, 1) * b[2] + queue_readElementAt(&xQ, 2) * b[1] + queue_readElementAt(&xQ, 3) * b[0]; printf("%lf\n\r", y); // Add new input. queue_overwritePush(&xQ, 0.33); // Compute the next y, exactly the same computation as above but with a concise for-loop instead. // += accumulates the result during the for-loop. Must start out with y = 0. y = 0.0; // This for-loop performs the identical computation to that shown above. for-loop is correct way to do it. for (int i=0; i<FIR_COEF_COUNT; i++) { y += queue_readElementAt(&xQ, i) * b[FIR_COEF_COUNT-1-i]; // iteratively adds the (b * input) products. } printf("%lf\n\r", y); }
Pictorially, implementing the FIR filter would appear as shown below. As shown, you can see the 4 values that were pushed onto the queue as well as the total computations.
The next computation of y is shown below. You can see that by adding a new value to the queue, all of the other values shifted over, relative to the b
coefficients. Thus you can use the same code to compute y
over and over again.
You can see that purpose of the queue is to store past values in the order that they were received and make all of the queue-contained values accessible during the computation.
Decimation is really easy. In our laser-tag system we will be decimating by 10. All we do is invoke our FIR-filter each 10 samples. As you add incoming samples to the FIR-filter input-queue, only invoke the FIR-filter each time you have received 10 new inputs. You will invoke the IIR filters right after you invoke the FIR-filter.
The IIR filter equations can be implemented similarly. However, the IIR filter relies on two signal histories: y and z, as shown in the equation below.
As you can see from the equation, you would need two queues of different sizes (5 and 4) to keep the necessary signal histories. The only other difference is that the computed value (z
) is also pushed onto the queue that keeps a history of z values. This is essentially what puts the “IIR” in the filter, e.g, feedback. The size of the queues for your IIR filter will depend upon your filter design. My IIR filter uses 8 “a” coefficients and 9 “b” coefficients, for example.
Viewed as a data-flow image, the filter structure would look as depicted below. In 'C' code, the data-flow is implemented sequentially, as in, 1) get a new sample from the ADC, 2) process the new sample with the FIR-filter to produce a single output (y), and 3) process the single output of the FIR-filter with the 10 IIR filters to produce 10 outputs (z[0] - z[9]). You do this process forever.
queue_t
variables in filter.c
.At run-time, you “run” the filters by doing the following:
filter_addNewInput()
is supposed to do.filter_firFilter()
. This will use the appropriate FIR coefficients and the x_queue that contains the necessary signal history to compute the output of the FIR filter. Add this output to the y_queue. Also return this computed value.
The code below provides a little more detail. Note that this code will not compile. I provide it as an example of how you will use the filter_addNewInput()
, filter_fir()
and filter_iir()
functions. You would run the code below each time there is at least one new available value. For this task, you are only verifying that your filters work correctly so you don't need to the decimation in this task. If you want to use this code for verification, simply set the decimation factor to 1.
#define FILTER_IIR_FILTER_COUNT 10 #define FILTER_FIR_DECIMATION_FACTOR 10 int sampleCount = 0; filter_addNewInput(adcValue); // add a new input to the FIR history queue. sampleCount++; // Keep track of how many samples you have acquired. if (sampleCount == FILTER_FIR_DECIMATION_FACTOR) { // Only invoke the filters after every DECIMATION_FACTOR times. sampleCount = 0; // Reset the sample count when you run the filters. filter_firFilter() // Runs the FIR filter on the accumulated input and places the output in the y-queue. for (int filterNumber=0; filterNumber<FILTER_IIR_FILTER_COUNT; filterNumber++) { filter_iirFilter(filterNumber); // Run each of the IIR filters. } }
It is important to carefully verify your filters at this stage. Here is the process that you will be required to follow.
filter()
command in MatLab (it is allowed for this purpose).Here's an simple example of what a portion of your filter_runTest() function could look like:
#include "filter.h" #include <stdio.h> #include <stdbool.h> #define TEST_DATA_COUNT 5 const double firInputTestData[TEST_DATA_COUNT] = {0.3, 0.25, 0.41, -0.11, -0.5 }; const double firOutputTestData[TEST_DATA_COUNT] = {0.6, 0.51, 0.25, -0.78, -0.12 }; int main() { filter_init(); // Standard init function. bool success = true; // Be optimistic uint16_t failedIndex = 0; // Keep track of the index where things failed. for (uint16_t i=0; i<TEST_DATA_COUNT; i++) { // No decimation for this test - just invoke the FIR filter each time you add new data. filter_addNewInput(firInputTestData[i]); if (filter_firFilter() != firOutputTestData[i]) { // Is the output what you expected? success = false; // Nope, note failure and break out of the loop. failedIndex = i; // Note the failed index. break; } } if (success) { printf("Success!\n\r"); } else { printf("Failure!\n\r"); printf("First failure detected at index: %d\n\r", failedIndex); } }
I show a screen-capture of my spreadsheet below.
Here's the basic IIR equation for reference (it's the same one as I pasted above). Note that the order is incorrect as the one in the spreadsheet is higher order. The computational structure is the same.
static
in filter.c
.filter_addNewInput(value)
must push value onto xQueue. filter_firFilter()
reads values from xQueue using filter_readElementAt()
and pushes its output onto yQueue. filter_iirFilter()
reads values from yQueue using filter_readElementAt()
and pushes it output onto zQueue[filterNumber].filter_runTest()
function as described.Here's an example of how to declare and initialize an array of queues. Note this code will not compile “as is.”
#define Z_QUEUE_SIZE IIR_A_COEFFICIENT_COUNT static queue_t zQueue[FILTER_IIR_FILTER_COUNT]; void initZQueues() { for (int i=0; i<FILTER_IIR_FILTER_COUNT; i++) { queue_init(&(zQueue[i]), Z_QUEUE_SIZE); for (int j=0; j<Z_QUEUE_SIZE; j++) queue_overwritePush(&(zQueue[i]), 0.0); } }
To pass off this task, you must:
Here is the filter.h file. You do not need to implement any of the functions that relate to power for this milestone.
#ifndef FILTER_H_ #define FILTER_H_ #include <stdint.h> #include "queue.h" #define FILTER_IIR_FILTER_COUNT 10 // You need this many IIR filters. #define FILTER_FIR_DECIMATION_FACTOR 10 // Filter needs this many new inputs to compute a new output. #define FILTER_INPUT_PULSE_WIDTH 2000 // This is the width of the pulse you are looking for, in terms of decimated sample count. // Filtering routines for the laser-tag project. // Filtering is performed by a two-stage filter, as described below. // 1. First filter is a decimating FIR filter with a configurable number of taps and decimation factor. // 2. The output from the decimating FIR filter is passed through a bank of 10 IIR filters. The // characteristics of the IIR filter are fixed. // The decimation factor determines how many new samples must be read for each new filter output. //uint16_t filter_getFirDecimationFactor(); // Must call this prior to using any filter functions. // Will initialize queues and any other stuff you need to init before using your filters. // Make sure to fill your queues with zeros after you initialize them. void filter_init(); // Print out the contents of the xQueue for debugging purposes. void filter_printXQueue(); // Print out the contents of yQueue for debugging purposes. void filter_printYQueue(); // Print out the contents of the the specified zQueue for debugging purposes. void filter_printZQueue(uint16_t filterNumber); // Use this to copy an input into the input queue (x_queue). void filter_addNewInput(double x); // Invokes the FIR filter. Returns the output from the FIR filter. Also adds the output to the y_queue for use by the IIR filter. double filter_firFilter(); // Use this to invoke a single iir filter. Uses the y_queue and z_queues as input. Returns the IIR-filter output. double filter_iirFilter(uint16_t filterNumber); // Use this to compute the power for values contained in a queue. // If force == true, then recompute everything from scratch. double filter_computePower(uint16_t filterNumber, bool forceComputeFromScratch, bool debugPrint); double filter_getCurrentPowerValue(uint16_t filterNumber); // Uses the last computed-power values, scales them to the provided lowerBound and upperBound args, returns the index of element containing the max value. // The caller provides the normalizedArray that will contain the normalized values. indexOfMaxValue indicates the channel with max. power. void filter_getNormalizedPowerValues(double normalizedArray[], uint16_t* indexOfMaxValue); void filter_runTest(); #endif /* FILTER_H_ */