User Tools

Site Tools


milestone_3_task_1

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

milestone_3_task_1 [2023/02/06 13:54]
scott [Help]
milestone_3_task_1 [2023/02/17 16:34] (current)
scott [Pass Off and Code Submission]
Line 56: Line 56:
 ===== Resources ===== ===== Resources =====
  
-==== Help and Examples ====+==== Support ​and Examples ====
  
   * [[Convert MATLAB Filter Coefficients to C Code]]   * [[Convert MATLAB Filter Coefficients to C Code]]
Line 75: Line 75:
  
 You are expected to create and implement the following file. See the provided header file (.h) for a description of each function. 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   * lasertag/​filter.c
  
 ---- ----
  
-===== Detailed Requirements ​=====+===== Implementation Details ​=====
  
-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.+==== 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 92: 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 101: 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 117: 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 123: 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.
  
-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. +=== Caution ===  
- +
----- +
- +
-==== 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! 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 ===
- +
-==== Computing Power ==== +
- +
-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:​ +
-  * Declare a ''​static''​ array of 10 power-output queues in ''​filter.c''​ (these were the output-queues that were discussed above). These will be indexed by ''​filterNumber''​ in the various functions that need to access them. +
-  * Determine the size of the output queue so that it will hold 200 ms of output data. +
-  * Initialize these queues and fill them with "​0"​s. Use a separate function for this initialization as was required in Task 1. +
-  * Each time you invoke an IIR filter, push its output onto its respective output queue. +
-  * Declare a static array of 10 doubles (in filter.c) that will contain the latest computed-power numbers for all 10 frequencies. Call this array ''​currentPowerValue[]''​. +
-  * When you invoke ''​filter_computePower(filterNumber)'',​ compute the power for a frequency by computing a sum of the squares of all values contained in the output queue for that frequency. Store this value in the ''​currentPowerValue[]''​ array at the index that corresponds to that frequency. +
-  * Write the function ''​filter_getCurrentPowerValue(uint16_t filterNumber)''​ that simply returns the value stored in the array ''​currentPowerValue[]''​ at ''​filterNumber''​. +
-  * Write the function ''​filter_getNormalizedPowerValues(double normalizedArray[],​ uint16_t* indexOfMaxValue)''​. This function does two things: +
-    - Normalizes the power values that are contained in ''​currentPowerValue[]''​ so that they are distributed between 0.0 and 1.0 and stores these normalized values in the array argument ''​normalizedArray[]''​. To compute normalized values, simply find the largest value in the ''​currentPowerValue[]''​ array and divide all values stored in the array by the largest value. +
-    - Stores the index of the ''​currentPowerValue[]''​ that contains the highest power value in the argument ''​indexOfMaxValue''​. +
- +
-=== Efficiency === +
- +
-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. +
- +
-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. +
- +
----- +
- +
-==== Filter Function Usage ==== +
- +
-The code below provides context on how the filter functions should be used. It is provided as an example of how you will use the ''​filter_addNewInput()'',​ ''​filter_fir()'',​ and ''​filter_iir()'',​ ''​filter_computePower()''​ functions in a future task. You would run the code below each time there is at least one new available value. Assume that the code is called continuously and forever. For this task, you are only verifying that your filters work correctly so you don't need to write this code yet. The filter test code will call your filter functions and test them. +
- +
-<code C> +
-// Constants and filter functions are declared in filter.h +
- +
-... +
-filter_addNewInput(scaledAdcValue);​ // Add scaled ADC value to x-queue +
-sample_cnt++;​ // Count samples since last filter run +
- +
-// Run filters and hit detection if decimation factor reached +
-if (sample_cnt == FILTER_FIR_DECIMATION_FACTOR) { +
-  uint16_t filterNumber;​ +
-  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. +
-    // 1st false means do not compute from scratch. +
-    // 2nd false means no debug prints. +
-    filter_computePower(filterNumber,​ false, false); +
-  } +
-  ... +
-+
-... +
-</​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: 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:
  
Line 204: Line 147:
 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. 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 ​==== +=== 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. 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.
  
Line 241: Line 183:
   queue_overwritePush(&​xQ,​ 0.33);   queue_overwritePush(&​xQ,​ 0.33);
  
-  // Compute the next y, exactly the same computation as above but with concise ​for-loop instead.+  // Compute the next y using a for loop (a better way).
   // += accumulates the result during the for-loop. Must start out with y = 0.   // += accumulates the result during the for-loop. Must start out with y = 0.
   y = 0.0;   y = 0.0;
-  // This for-loop performs the identical computation to that shown above. for-loop is correct way to do it+  // This for-loop performs the identical computation to that shown above. 
-  for (uint32_t i=0; i<​FIR_COEF_COUNT;​ i++) { +  for (uint32_t i=0; i<​FIR_COEF_COUNT;​ i++) { // iteratively adds the (b * input) products. 
-    y += queue_readElementAt(&​xQ,​ FIR_COEF_COUNT-1-i) * b[i];  // iteratively adds the (b * input) products.+    y += queue_readElementAt(&​xQ,​ FIR_COEF_COUNT-1-i) * b[i];
   }   }
-  printf("​%lf\n\r", y);+  printf("​%lf\n",​ y);
  
 } }
Line 263: Line 205:
 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. 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? ===+=== 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. 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 ===
-==== IIR Filters ​and Queues ==== +
 The equation for an IIR filter is shown below. ​ The equation for an IIR filter is shown below. ​
  
Line 283: Line 222:
  
 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. ​ 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 ====
 +
 +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:
 +  * Declare a ''​static''​ array of 10 power-output queues in ''​filter.c''​ (these were the output-queues that were discussed above). These will be indexed by ''​filterNumber''​ in the various functions that need to access them.
 +  * Determine the size of the output queue so that it will hold 200 ms of output data.
 +  * Initialize these queues and fill them with "​0"​s. Use a separate function for this initialization as was required in Task 1.
 +  * Each time you invoke an IIR filter, push its output onto its respective output queue.
 +  * Declare a static array of 10 doubles (in filter.c) that will contain the latest computed-power numbers for all 10 frequencies. Call this array ''​currentPowerValue[]''​.
 +  * When you invoke ''​filter_computePower(filterNumber)'',​ compute the power for a frequency by computing a sum of the squares of all values contained in the output queue for that frequency. Store this value in the ''​currentPowerValue[]''​ array at the index that corresponds to that frequency.
 +  * Write the function ''​filter_getCurrentPowerValue(uint16_t filterNumber)''​ that simply returns the value stored in the array ''​currentPowerValue[]''​ at ''​filterNumber''​.
 +  * Write the function ''​filter_getNormalizedPowerValues(double normalizedArray[],​ uint16_t* indexOfMaxValue)''​. This function does two things:
 +    - Normalizes the power values that are contained in ''​currentPowerValue[]''​ so that they are distributed between 0.0 and 1.0 and stores these normalized values in the array argument ''​normalizedArray[]''​. To compute normalized values, simply find the largest value in the ''​currentPowerValue[]''​ array and divide all values stored in the array by the largest value.
 +    - Stores the index of the ''​currentPowerValue[]''​ that contains the highest power value in the argument ''​indexOfMaxValue''​.
 +
 +=== Efficiency ===
 +
 +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.
 +
 +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.
 +
 +----
 +
 +==== 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>
 +// Constants and filter functions are declared in filter.h
 +
 +...
 +filter_addNewInput(scaledAdcValue);​ // Add scaled ADC value to x-queue
 +sample_cnt++;​ // Count samples since last filter run
 +
 +// Run filters and hit detection if decimation factor reached
 +if (sample_cnt == FILTER_FIR_DECIMATION_FACTOR) {
 +  uint16_t filterNumber;​
 +  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.
 +    // 1st false means do not compute from scratch.
 +    // 2nd false means no debug prints.
 +    filter_computePower(filterNumber,​ false, false);
 +  }
 +  ...
 +}
 +...
 +</​code>​
  
 ---- ----
Line 356: Line 352:
   * 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 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: ​   * 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"​.+    - 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.     - 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.+    - Submit only one .zip file per team. Both team members ​will receive credit.
  
 ---- ----
Line 368: 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.
  
----- 
- 
-===== 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_iir 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>​ 
milestone_3_task_1.1675716861.txt.gz · Last modified: 2023/02/06 13:54 by scott