This shows you the differences between two versions of the page.
milestone_3_task_1 [2023/01/02 23:17] scott [Detailed Requirements] |
milestone_3_task_1 [2023/02/17 16:34] (current) scott [Pass Off and Code Submission] |
||
---|---|---|---|
Line 7: | Line 7: | ||
==== Summary: What You Are Building ==== | ==== Summary: What You Are Building ==== | ||
- | Ultimately, for all of Milestone 3, you are writing the software - the **detector** - the thing that will detect when and what player frequency "hits" you when playing the game. For Task 1, you are implementing just the filtering and power-computation parts. The diagram below shows you the entire structure for detector. You are implementing the part contained in the gray box. This consists of the following parts: | + | Ultimately, for all of Milestone 3, you are writing the software - the **detector** - the thing that will detect when and what player frequency "hits" you when playing the game. For Task 1, you are implementing just the filtering and power-computation parts. The diagram below shows you the entire structure for the detector. You are implementing the part contained in the gray box. This consists of the following parts: |
* xQueue: this contains the input data for the filters. Ultimately, inputs to the xQueue will be based upon digitized voltages obtained from the photo-diode circuitry that you constructed in ECEn 340. We will be using test data (provided) for this task. | * xQueue: this contains the input data for the filters. Ultimately, inputs to the xQueue will be based upon digitized voltages obtained from the photo-diode circuitry that you constructed in ECEn 340. We will be using test data (provided) for this task. | ||
* FIR Filter: the decimating anti-aliasing FIR filter. | * FIR Filter: the decimating anti-aliasing FIR filter. | ||
Line 15: | Line 15: | ||
* outputQueue 0 - 9: these are the outputs of the 10 IIR filters. | * outputQueue 0 - 9: these are the outputs of the 10 IIR filters. | ||
* Compute Power 0-9: compute the amount of power in the signal output by the corresponding IIR filter. | * Compute Power 0-9: compute the amount of power in the signal output by the corresponding IIR filter. | ||
+ | |||
+ | Note: The IIR filters and associated queues are now numbered 0 - 9 in this task. This is because 'C' arrays are zero indexed and starting at 0 makes coding easier. In MATLAB for Milestone 2, the frequencies and IIR filters were numbered 1 - 10. | ||
{{::systemdiagramtask1.jpg?800|}} | {{::systemdiagramtask1.jpg?800|}} | ||
Line 26: | Line 28: | ||
* Your IIR-filters must have a very narrow frequency response. For example, for frequency 4, your power output should be high for IIR-filter 4, but should be very low for all of the other IIR-filters. A difference of 10X is preferred for good performance in the laser-tag game. | * Your IIR-filters must have a very narrow frequency response. For example, for frequency 4, your power output should be high for IIR-filter 4, but should be very low for all of the other IIR-filters. A difference of 10X is preferred for good performance in the laser-tag game. | ||
* You will test your filters and functions that compute power using the provided ''filter_runTest()'' function. | * You will test your filters and functions that compute power using the provided ''filter_runTest()'' function. | ||
- | |||
- | ==== Pass Off and Code Submission ==== | ||
- | |||
- | * You will show the TAs how your code behaves when running the provided test code. You will mostly show the TAs the information displayed on the LCD on the board. | ||
- | * You will submit your source code by doing the following: | ||
- | - from the top-level directory (e.g. ecen390), run the check_and_zip program: "./check_and_zip.py 390m3-1". | ||
- | - The resulting .zip file will be in the top-level directory. Submit that to Learning Suite. | ||
- | - Submit only one .zip file per team. The TAs will make sure that both team members receive credit. | ||
==== General Notes ==== | ==== General Notes ==== | ||
Line 56: | Line 50: | ||
- Implement all of the power-computation functions using the comments to guide you. | - Implement all of the power-computation functions using the comments to guide you. | ||
- You must test all of your filter.c code using the filter test code provided below. More information is provided below so you know what to expect when running the tests. | - You must test all of your filter.c code using the filter test code provided below. More information is provided below so you know what to expect when running the tests. | ||
- | - You must follow the [[https://byu-cpe.github.io/ecen330/other/coding-standard/|software coding standard]]. | + | - You must follow the [[https://byu-cpe.github.io/ecen330/other/coding-standard/|software coding standard]]. **Exception: clang-format is not required. Ignore any clang-format rules.** |
---- | ---- | ||
- | ===== Detailed Requirements ===== | + | ===== Resources ===== |
- | 1. Declare and initialize 22 queues: an ''xQueue'' to store incoming inputs from the ADC, a ''yQueue'' that holds the history of outputs from the FIR filter, an array of 10 zQueues that hold the history of outputs from the corresponding IIR filters, and an array of 10 outputQueues that accumulate output values from each of the IIR filters for the purpose of power computations. Declare all of these queues as static ''queue_t'' variables in ''filter.c''. Give each of these queues a meaningful name using the name argument in ''queue_init()'' function. | + | ==== Support and Examples ==== |
+ | |||
+ | * [[Convert MATLAB Filter Coefficients to C Code]] | ||
+ | * [[Frequency Response Plots]] | ||
+ | * [[https://youtu.be/TdCgfiP3JCk|Video Demonstration of FilterTest code]] | ||
+ | |||
+ | ---- | ||
+ | |||
+ | ==== Source Code ==== | ||
+ | |||
+ | Note that the following files are provided in your ecen390 project directory. The test code is used to check the correctness of your code. | ||
+ | * lasertag/main.c | ||
+ | * lasertag/filter.h | ||
+ | * lasertag/support/filterTest.h | ||
+ | * lasertag/support/filterTest.c | ||
+ | * lasertag/support/histogram.h | ||
+ | * lasertag/support/histogram.c | ||
+ | |||
+ | You are expected to create and implement the following file. See the provided header file (.h) for a description of each function. | ||
+ | * lasertag/filter.c | ||
+ | |||
+ | ---- | ||
+ | |||
+ | ===== Implementation Details ===== | ||
+ | |||
+ | ==== Queue Initialization ==== | ||
+ | Declare and initialize 22 queues: an ''xQueue'' to store incoming inputs from the ADC, a ''yQueue'' that holds the history of outputs from the FIR filter, an array of 10 zQueues that hold the history of outputs from the corresponding IIR filters, and an array of 10 outputQueues that accumulate output values from each of the IIR filters for the purpose of power computations. Declare all of these queues as static ''queue_t'' variables in ''filter.c''. Give each of these queues a meaningful name using the name argument in ''queue_init()'' function. | ||
Here's an example of how to declare and initialize an array of queues. Note this code will not compile "as is." | Here's an example of how to declare and initialize an array of queues. Note this code will not compile "as is." | ||
Line 72: | Line 92: | ||
void initZQueues() { | void initZQueues() { | ||
- | for (uint32_t i=0; i<FILTER_IIR_FILTER_COUNT; i++) { | + | for (uint32_t i = 0; i < FILTER_IIR_FILTER_COUNT; i++) { |
- | queue_init(&(zQueue[i]), Z_QUEUE_SIZE); | + | queue_init(&(zQueue[i]), Z_QUEUE_SIZE, "zQueue"); |
- | for (uint32_t j=0; j<Z_QUEUE_SIZE; j++) | + | for (uint32_t j = 0; j < Z_QUEUE_SIZE; j++) |
queue_overwritePush(&(zQueue[i]), QUEUE_INIT_VALUE); | queue_overwritePush(&(zQueue[i]), QUEUE_INIT_VALUE); | ||
} | } | ||
Line 81: | Line 101: | ||
</code> | </code> | ||
- | 2. In ''filter_init()'', initialize all of the queue's and fill them with zeros. Below is one example, that uses small helper functions to initialize each queue and fill it with zeros. These helper functions are then called from within ''filter_init()''. | + | ==== Filter Initialization ==== |
+ | In ''filter_init()'', initialize all of the queue's and fill them with zeros. Below is one example, that uses small helper functions to initialize each queue and fill it with zeros. These helper functions are then called from within ''filter_init()''. | ||
<code C> | <code C> | ||
// This must be called before invoking any filter functions. | // This must be called before invoking any filter functions. | ||
Line 97: | Line 118: | ||
**Note: an empty queue is not the same as a queue that is filled with 0s.** | **Note: an empty queue is not the same as a queue that is filled with 0s.** | ||
+ | ==== Filter Code ==== | ||
+ | |||
+ | === Overview === | ||
At run-time, you "run" the filters by doing the following: | At run-time, you "run" the filters by doing the following: | ||
* As they become available, add new inputs to the xQueue. This is what the function ''filter_addNewInput()'' will do (once you implement it). When you finish the completion of your laser-tag system, you will use ''filter_addNewInput()'' to take the output from the digitized voltage from your photo-diode-based detector and provide it to the FIR filter. | * As they become available, add new inputs to the xQueue. This is what the function ''filter_addNewInput()'' will do (once you implement it). When you finish the completion of your laser-tag system, you will use ''filter_addNewInput()'' to take the output from the digitized voltage from your photo-diode-based detector and provide it to the FIR filter. | ||
Line 103: | Line 127: | ||
* invoke the filter_iirFilter() function 10 times, once for each frequency. Return the computed z-value for each frequency. Also add the output from the IIR-filter to the corresponding outputQueue. You will use the values contained in the outputQueues to compute the total power of the signal that passes through the IIR band-pass filter. | * invoke the filter_iirFilter() function 10 times, once for each frequency. Return the computed z-value for each frequency. Also add the output from the IIR-filter to the corresponding outputQueue. You will use the values contained in the outputQueues to compute the total power of the signal that passes through the IIR band-pass filter. | ||
- | The code below provides a little more context. Note that this code will **not** compile. It is provided 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 do decimation in this task. If you want to use this code for verification, simply set the decimation factor to 1. | + | === Caution === |
+ | For the IIR filters, note that the first a-coefficient (a0) is always 1.0 and is not used in the computation. As such, if your a-coefficient array is of size 11, your z-queue must be size 10 (1 less than the size of the array) because a0 is always ignored. Watch out for this! | ||
+ | |||
+ | === Background === | ||
+ | In this task you will implement the FIR and IIR filters that you designed using MATLAB 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: | ||
+ | |||
+ | {{ ::firexamplefromwikipedia.jpg?600 |}} | ||
+ | |||
+ | 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. 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]''. For example, at time=0, you start out computing ''y[0]''. After playing the game for several minutes, ''n'' would be in the billions. And, it only goes up from there. Simply put, you want to eliminate the ''[n]'' part so that the output is simply ''y''. | ||
+ | |||
+ | Note that when we read the English-based description from Wikipedia (see above), indexes were not discussed. 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 properly 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: | ||
+ | - Create a data structure that will keep an ordered history of past values. The size of the data structure must match the order of the filter, e.g., a 50-tap FIR filter needs a history of 50 values. | ||
+ | - At start-up time, fill the data structure with zeros. | ||
+ | - As each new value arrives, throw away the oldest value. | ||
+ | - Read the stored past values from the data structure and multiply them with the correct coefficients. | ||
+ | |||
+ | 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. | ||
+ | |||
+ | === Code Example === | ||
+ | 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. Note that for pedagogical purposes, the code below does not necessarily adhere to the coding standard. | ||
<code C> | <code C> | ||
- | // THIS IS PROVIDED FOR CONTEXT ONLY. | + | #include "queue.h" |
- | // IF YOU USE THIS CODE DIRECTLY IN THIS TASK YOU ARE DOING THINGS WRONG. | + | #include <stdio.h> |
+ | #define FIR_COEF_COUNT 4 | ||
- | #define FILTER_IIR_FILTER_COUNT 10 | + | int main() { |
- | #define FILTER_FIR_DECIMATION_FACTOR 10 | + | // 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 (uint32_t i=0; i<FIR_COEF_COUNT; i++) // Start out with a queue full of zeros. | ||
+ | queue_overwritePush(&xQ, 0.0); | ||
+ | |||
+ | const 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); | ||
- | uint32_t sampleCount = 0; | + | // Compute the next y using a for loop (a better way). |
- | filter_addNewInput(adcValue); // add a new input to the FIR history queue. | + | // += accumulates the result during the for-loop. Must start out with y = 0. |
- | sampleCount++; // Keep track of how many samples you have acquired. | + | y = 0.0; |
- | if (sampleCount == FILTER_FIR_DECIMATION_FACTOR) { // Only invoke the filters after every DECIMATION_FACTOR times. | + | // This for-loop performs the identical computation to that shown above. |
- | sampleCount = 0; // Reset the sample count when you run the filters. | + | for (uint32_t i=0; i<FIR_COEF_COUNT; i++) { // iteratively adds the (b * input) products. |
- | filter_firFilter() // Runs the FIR filter on the accumulated input and places the output in the y-queue. | + | y += queue_readElementAt(&xQ, FIR_COEF_COUNT-1-i) * b[i]; |
- | for (uint32_t filterNumber=0; filterNumber<FILTER_IIR_FILTER_COUNT; filterNumber++) { | + | |
- | filter_iirFilter(filterNumber); // Run each of the IIR filters. | + | |
- | filter_computePower(filterNumber, false, false);// Compute the power for each of the IIR filters. | + | |
} | } | ||
- | } | + | printf("%lf\n", y); |
+ | } | ||
</code> | </code> | ||
- | 3. Implement all of the power-related functions (they all have the word "power" in their names). You will need to make sure to write ''filter_computePower()'' so that it does not take too much execution time. Carefully think about how you might be able to reuse computations performed in a previous invocation of ''filter_computePower()'' to reduce overall computation time. To initially debug your power code, you can fill the power queues with constant values, say 2.0 for example, and then compare the output from the function with your own calculation. The provided test code contained in the file filterTest.c (see below) provides a comprehensive test of the power functions. | + | 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. |
- | Here's a picture of your overall filter structure to help you understand the purpose of the queues used to compute power. The "Detector" consists of all your filtering code, the power-computing code, and the "Compare and Threshold" code. Note that the code that performs "Compare and Threshold" will be implemented in a later milestone. | + | {{ ::firqueueexample1.jpg?700 |}} |
- | {{ :filterstructure.jpg?700 |}} | + | 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. |
- | ---- | + | {{ ::firqueueexample2.jpg?700 |}} |
- | ==== Caution ==== | + | You can see that the 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. |
- | For the IIR filters, note that the first a-coefficient (a0) is always 1.0 and is not used in the computation. As such, if your a-coefficient array is of size 11, your z-queue must be size 10 (1 less than the size of the array) because a0 is always ignored. Watch out for this! | + | === What About Decimation? === |
+ | Decimation is really easy. In our laser-tag system we will be decimating by 10. All we do is invoke our FIR-filter each time we receive 10 new 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 then invoke the IIR filters right after you invoke the FIR-filter. Decimation-wise, there is nothing required for this task. You will implement this later. | ||
+ | |||
+ | === IIR Filters === | ||
+ | The equation for an IIR filter is shown below. | ||
+ | |||
+ | {{::iirfilterequation1.jpg?400| }} | ||
+ | |||
+ | However, we can simplify this a bit because the first **a** coefficient is 1.0 and can be ignored. | ||
+ | |||
+ | {{::iirfilterequation2.jpg?400| }} | ||
+ | |||
+ | We finally end up with: | ||
+ | |||
+ | {{::iirfilterequation3.jpg?400| }} | ||
+ | |||
+ | The implementation of the IIR filter is similar to the FIR filter. However, the IIR filter relies on two signal histories: y and z, as shown in the equation above. As you can see from the equation, you would need two queues of different sizes (11 and 10) 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. | ||
---- | ---- | ||
==== Computing Power ==== | ==== Computing Power ==== | ||
+ | |||
+ | Implement all of the power-related functions (they all have the word "power" in their names). You will need to make sure to write ''filter_computePower()'' so that it does not take too much execution time. Carefully think about how you might be able to reuse computations performed in a previous invocation of ''filter_computePower()'' to reduce overall computation time. To initially debug your power code, you can fill the output queues with constant values, say 2.0 for example, and then compare the output from the function with your own calculation. The provided test code contained in the file filterTest.c provides a comprehensive test of the power functions. | ||
To compute power, you must keep a running history of 200 ms of output data from each of the 10 IIR-based band-pass filters. To achieve this, do the following: | To compute power, you must keep a running history of 200 ms of output data from each of the 10 IIR-based band-pass filters. To achieve this, do the following: | ||
Line 160: | Line 247: | ||
Verify the correct operation of filter_computePower(), filter_getCurrentPowerValue(), and filter_getNormalizedPowerValues() using your own test code. An easy way to do this is to fill the power queues with constant values, say 2.0 for example, and then compare the output from the function with your own calculation. | Verify the correct operation of filter_computePower(), filter_getCurrentPowerValue(), and filter_getNormalizedPowerValues() using your own test code. An easy way to do this is to fill the power queues with constant values, say 2.0 for example, and then compare the output from the function with your own calculation. | ||
- | === How the Power Code is Invoked === | + | ---- |
- | The power functions are invoked along with the filtering functions. To provide you with some context, here is a code snippet that demonstrates how the power functions will be used once you finish coding the detector in the next task. Assume that the code below is called continuously and forever. | + | ==== Filter Function Usage ==== |
+ | |||
+ | The code below provides context on how the filter functions will be used in a future task. Assume that this code is called whenever there is a new scaled ADC value available. | ||
+ | |||
+ | **You don't need to write this code yet.** For now, just enable the provided filter test code to verify that your filter functions work. The test code will call your filter functions with meaningful arguments. | ||
<code C> | <code C> | ||
- | // THIS IS PROVIDED FOR CONTEXT ONLY. | + | // Constants and filter functions are declared in filter.h |
- | // IF YOU USE THIS CODE DIRECTLY IN THIS TASK YOU ARE DOING THINGS WRONG. | + | |
- | filter_addNewInput(scaledAdcValue); // Copy the the scaled ADC value into the main filter input queue. | + | ... |
- | sampleCount++; // Keep track of how many samples you have acquired. | + | filter_addNewInput(scaledAdcValue); // Add scaled ADC value to x-queue |
- | if (sampleCount == FILTER_FIR_DECIMATION_FACTOR) { // Only invoke the filters after every DECIMATION_FACTOR times. | + | sample_cnt++; // Count samples since last filter run |
- | 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. | + | // Run filters and hit detection if decimation factor reached |
- | // Run all 10 of the IIR filters and compute power in each of the output queues. | + | if (sample_cnt == FILTER_FIR_DECIMATION_FACTOR) { |
- | for (uint32_t filterNumber=0; filterNumber<FILTER_FREQUENCY_COUNT; filterNumber++) { | + | uint16_t filterNumber; |
- | filter_iirFilter(filterNumber); // Run each of the IIR filters. | + | sample_cnt = 0; // Reset the sample count. |
+ | filter_firFilter(); // Runs the FIR filter, output goes in the y-queue. | ||
+ | // Run all the IIR filters and compute power in each of the output queues. | ||
+ | for (filterNumber = 0; filterNumber < FILTER_FREQUENCY_COUNT; filterNumber++) { | ||
+ | filter_iirFilter(filterNumber); // Run each of the IIR filters. | ||
// Compute the power for each of the filters, at lowest computational cost. | // Compute the power for each of the filters, at lowest computational cost. | ||
- | // false means do not compute from scratch. | + | // 1st false means do not compute from scratch. |
- | filter_computePower(filterNumber, false); | + | // 2nd false means no debug prints. |
- | ... | + | filter_computePower(filterNumber, false, false); |
} | } | ||
+ | ... | ||
+ | } | ||
... | ... | ||
</code> | </code> | ||
- | |||
- | |||
- | **Note**: Remember that these functions are declared in filter.h | ||
---- | ---- | ||
- | ==== Test Code ==== | + | ===== Test Code ===== |
To pass off this task, you must run your filter and power code with the provided filterTest.c source code. The test code tests the arithmetic for all of your filters. It also plots the frequency response for the FIR and IIR filters on the TFT display. The provided test code comes in four pieces: histogram.h, histogram.c and filterTest.h and filterTest.c. In filter.h, you will see a section of code labeled "Verification-Assisting Functions". These accessor functions provide a way for the test-code to access the various named variables in your filter.c code. These accessors are necessary because your naming schemes will likely differ from my test-code. | To pass off this task, you must run your filter and power code with the provided filterTest.c source code. The test code tests the arithmetic for all of your filters. It also plots the frequency response for the FIR and IIR filters on the TFT display. The provided test code comes in four pieces: histogram.h, histogram.c and filterTest.h and filterTest.c. In filter.h, you will see a section of code labeled "Verification-Assisting Functions". These accessor functions provide a way for the test-code to access the various named variables in your filter.c code. These accessors are necessary because your naming schemes will likely differ from my test-code. | ||
Line 203: | Line 297: | ||
**Note that "aligned" means that coefficient values are multiplied with corresponding queue values using correct indices.** | **Note that "aligned" means that coefficient values are multiplied with corresponding queue values using correct indices.** | ||
- | To run this test code, simply add the line: | + | To run this test code, uncomment ''filter_runTest()'' in the body of your ''main()'' code and uncomment ''#define RUNNING_MODE_TESTS''. Along with various informational and perhaps error messages, the frequency response will be plotted out on the TFT display on the ZYBO carrier board. You can compare your results to those on this [[http://ece390web.groups.et.byu.net/dokuwiki/doku.php?id=frequency_response_plots|page]]. Your results should look similar. |
- | ''filterTest_runTest();'' | + | The informational messages that should appear in your console will look like this if everything passes. Numerical values won't be exact because your FIR filter will be different, but values should be roughly similar: |
- | to the body of your ''main()'' code. You will also need to include ''filter.h'' and ''filterTest.h''. Along with various informational and perhaps error messages, the frequency response will be plotted out on the TFT display on the BYU ECEn Development board. You can compare your results to those on this [[http://ece390web.groups.et.byu.net/dokuwiki/doku.php?id=frequency_response_plots|page]]. Your results should look similar. | + | |
- | The informational messages that should appear in your console will look like this if everything passes. Numerical values won't be exact because your FIR filter will be different than mine but values should be roughly similar: | + | |
<code> | <code> | ||
filter_runFirAlignmentTest passed. | filter_runFirAlignmentTest passed. | ||
Line 253: | Line 345: | ||
running filter_runFirPowerTest(9) - plotting power for all player frequencies for IIR filter(9) to TFT display. | running filter_runFirPowerTest(9) - plotting power for all player frequencies for IIR filter(9) to TFT display. | ||
</code> | </code> | ||
+ | |||
+ | ---- | ||
+ | |||
+ | ===== Pass Off and Code Submission ===== | ||
+ | |||
+ | * You will show the TAs how your code behaves when running the provided test code. You will mostly show the TAs the information displayed on the LCD on the board. | ||
+ | * You will submit your source code by doing the following: | ||
+ | - From the top-level directory (e.g. ecen390), run the check_and_zip program: "./check_and_zip.py 390m3-1". | ||
+ | - The resulting .zip file will be in the top-level directory. Submit that to Learning Suite. | ||
+ | - Submit only one .zip file per team. Both team members will receive credit. | ||
---- | ---- | ||
Line 262: | Line 364: | ||
- Check to make sure that the plots on the TFT display look correct, e.g., the FIR-filter is flat across the frequency range and that the bandpass filters have a narrow response. | - Check to make sure that the plots on the TFT display look correct, e.g., the FIR-filter is flat across the frequency range and that the bandpass filters have a narrow response. | ||
- | ---- | ||
- | |||
- | ===== Resources ===== | ||
- | |||
- | ==== Frequency Response Plots ==== | ||
- | You can compare your filter response against those shown on this page. | ||
- | |||
- | * [[Frequency Response Plots]] | ||
- | * [[https://youtu.be/TdCgfiP3JCk|Video Demonstration of FilterTest code]] | ||
- | |||
- | ===== Background ===== | ||
- | |||
- | In this task you will implement the FIR and IIR filters that you designed using MATLAB 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: | ||
- | |||
- | {{ ::firexamplefromwikipedia.jpg?600 |}} | ||
- | |||
- | 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. 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]''. For example, at time=0, you start out computing ''y[0]''. After playing the game for several minutes, ''n'' would be in the billions. And, it only goes up from there. Simply put, you want to eliminate the ''[n]'' part so that the output is simply ''y''. | ||
- | |||
- | Note that when we read the English-based description from Wikipedia (see above), indexes were not discussed. 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 properly 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: | ||
- | - Create a data structure that will keep an ordered history of past values. The size of the data structure must match the order of the filter, e.g., a 50-tap FIR filter needs a history of 50 values. | ||
- | - At start-up time, fill the data structure with zeros. | ||
- | - As each new value arrives, throw away the oldest value. | ||
- | - Read the stored past values from the data structure and multiply them with the correct coefficients. | ||
- | |||
- | 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. | ||
- | |||
- | ==== Filters and Queues ==== | ||
- | |||
- | 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. Note that for pedagogical purposes, the code below does not necessarily adhere to the coding standard. | ||
- | |||
- | <code C> | ||
- | #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 (uint32_t i=0; i<FIR_COEF_COUNT; i++) // Start out with a queue full of zeros. | ||
- | queue_overwritePush(&xQ, 0.0); | ||
- | |||
- | const 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 (uint32_t i=0; i<FIR_COEF_COUNT; i++) { | ||
- | y += queue_readElementAt(&xQ, FIR_COEF_COUNT-1-i) * b[i]; // iteratively adds the (b * input) products. | ||
- | } | ||
- | printf("%lf\n\r", y); | ||
- | |||
- | } | ||
- | </code> | ||
- | |||
- | 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. | ||
- | |||
- | {{ ::firqueueexample1.jpg?700 |}} | ||
- | |||
- | 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. | ||
- | |||
- | {{ ::firqueueexample2.jpg?700 |}} | ||
- | |||
- | You can see that the 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. | ||
- | |||
- | ==== What About Decimation? ==== | ||
- | |||
- | Decimation is really easy. In our laser-tag system we will be decimating by 10. All we do is invoke our FIR-filter each time we receive 10 new 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 then invoke the IIR filters right after you invoke the FIR-filter. Decimation-wise, there is nothing required for this task. You will implement this later. | ||
- | |||
- | |||
- | ==== IIR Filters and Queues ==== | ||
- | |||
- | The equation for an IIR filter is shown below. | ||
- | |||
- | {{::iirfilterequation1.jpg?400| }} | ||
- | |||
- | However, we can simplify this a bit because the first **a** coefficient is 1.0 and can be ignored. | ||
- | |||
- | {{::iirfilterequation2.jpg?400| }} | ||
- | |||
- | We finally end up with: | ||
- | |||
- | {{::iirfilterequation3.jpg?400| }} | ||
- | |||
- | The implementation of the IIR filter is similar to the FIR filter. However, the IIR filter relies on two signal histories: y and z, as shown in the equation above. As you can see from the equation, you would need two queues of different sizes (11 and 10) 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. | ||
- | |||
- | ---- | ||
- | |||
- | ===== Source Code ===== | ||
- | |||
- | Note that the following files are provided in your ecen390 project directory. The test code is used to check the correctness of your code. | ||
- | * lasertag/main.c | ||
- | * lasertag/filter.h | ||
- | * lasertag/support/filterTest.h | ||
- | * lasertag/support/filterTest.c | ||
- | * lasertag/support/histogram.h | ||
- | * lasertag/support/histogram.c | ||
- | |||
- | You are expected to create and implement the following file. See the provided header file (.h) for a description of each function. | ||
- | |||
- | * lasertag/filter.c | ||
- | |||
- | ---- | ||
- | |||
- | ===== Formating Code ===== | ||
- | Use this code to format your coefficients into arrays that follow correct 'C' syntax. **This code is not part of the cloned student repo so you will need to paste it into a file in order to use it with MATLAB.** | ||
- | |||
- | Instructions: | ||
- | - The fir coefficients must be stored in a 81-element vector named 'b'. | ||
- | - The iir a-coefficients must be stored in a 10 X 11 array named a_iir. | ||
- | - The iir b-coefficients must be stored in a 10 X 11 array named b_iir. | ||
- | - Run this script in MATLAB and it will produce a file named coef.txt that will contain C-formatted text. | ||
- | |||
- | Note: this code does not include the leading 1.0 coefficient for the a coefficients for the IIR filter because it is not used. | ||
- | |||
- | <file matlab format.m> | ||
- | %format of filter coefficients | ||
- | % | ||
- | % FIR vector name: b | ||
- | % | ||
- | % IIR a coefficients | ||
- | % name: a_iir | ||
- | % size: 10, 11 matrix | ||
- | % IIR b coefficients | ||
- | % name: b_iir | ||
- | % size: 10, 11 matrix | ||
- | |||
- | fileID = fopen('coef.txt', 'wt'); %open file | ||
- | %write FIR filter coefficients | ||
- | fprintf(fileID,'const static double firCoefficients[FIR_FILTER_TAP_COUNT] = {\n') | ||
- | formatSpec = '%1.16e, \n'; | ||
- | fprintf(fileID,formatSpec,b(1:end-1)) | ||
- | formatSpec = '%1.16e};\n\n'; | ||
- | fprintf(fileID,formatSpec,b(end)) | ||
- | %write IIR 'a' filter coefficients | ||
- | %This file does not write the first 'a' coefficient | ||
- | fprintf(fileID,'const static double iirACoefficientConstants[FILTER_FREQUENCY_COUNT][IIR_A_COEFFICIENT_COUNT] = {\n') | ||
- | |||
- | for lp=1:9 | ||
- | fprintf(fileID,'{') | ||
- | formatSpec = '%1.16e, '; | ||
- | fprintf(fileID,formatSpec,a_iir(lp, 2:end-1)) | ||
- | formatSpec = '%1.16e},\n'; | ||
- | fprintf(fileID,formatSpec,a_iir(lp, end)) | ||
- | end | ||
- | fprintf(fileID,'{') | ||
- | formatSpec = '%1.16e, '; | ||
- | fprintf(fileID,formatSpec,a_iir(10, 2:end-1)) | ||
- | formatSpec = '%1.16e}\n'; | ||
- | fprintf(fileID,formatSpec,a_iir(10, end)) | ||
- | fprintf(fileID,'};\n') | ||
- | fprintf(fileID,'\n') | ||
- | |||
- | %write IIR b filter coefficients | ||
- | fprintf(fileID,'const static double iirBCoefficientConstants[FILTER_FREQUENCY_COUNT][IIR_B_COEFFICIENT_COUNT] = {\n') | ||
- | |||
- | for lp=1:9 | ||
- | fprintf(fileID,'{') | ||
- | formatSpec = '%1.16e, '; | ||
- | fprintf(fileID,formatSpec,b_iir(lp, 1:end-1)) | ||
- | formatSpec = '%1.16e},\n'; | ||
- | fprintf(fileID,formatSpec,b_iir(lp, end)) | ||
- | end | ||
- | fprintf(fileID,'{') | ||
- | formatSpec = '%1.16e, '; | ||
- | fprintf(fileID,formatSpec,b_iir(10, 1:end-1)) | ||
- | formatSpec = '%1.16e}\n'; | ||
- | fprintf(fileID,formatSpec,b_iir(10, end)) | ||
- | fprintf(fileID,'};') | ||
- | |||
- | fclose(fileID); | ||
- | </file> |