Averaging

The questions below are due on Sunday September 27, 2026; 11:59:00 PM.
 
You are not logged in.

Please Log In for full access to the web site.
Note that this link will take you to an external site (https://shimmer.mit.edu) to authenticate, and then you will be redirected back to this page.
Back to Exercises

Writing a Flexible Running Averaging Filter

What we'd now like to do is write some code that will allow us to write an arbitrarily large (up to a point) moving average filter of mth order (which would correspond to a m+1-way moving average) such that:

y[n] = \sum_{p=0}^{m}\frac{1}{1+m}x[n-p]

which for values of m\geq2 could be expanded out to look like this generally:

y[n] = \frac{1}{1+m}x[n] + \frac{1}{1+m}x[n-1]...\frac{1}{1+m}x[n-m]

For practical purposes, we'll restrict the range of m to be 0 \leq m \lt 50.

Write a function averaging_filter that implements a variable-length moving average.

Our first attempt at a generic averaging filter will be used like this, for example:

  //....
  while(true){
    float input = some_function(); //get some input value from a function that returns measurements (maybe a button for example)
    // Process the new input, and get a new output
    // In this case, we use a FILTER_ORDER moving average, but it can be anything
    float average = averaging_filter(input, values, FILTER_ORDER);
    printf("%f",average);
    while(millis()-time_counter < DT);
    time_counter = millis();
  }
}

Concretely, we're going to call the function repeatedly, giving it a new input (x) each time.

The averaging_filter function should take in three inputs:

  • float input: the current input to the averaging filter, which has been x in our discussion so far.
  • float* stored_values: a pointer to the start of a float array that is global in scope. You may assume the array has a length of at least 50. You can assume that in normal use, no outside function or block of code will make changes to this array and that your modifications will "live on" between calls to averaging_filter.
  • int order: an integer indicating the "order" of the filter. For example a 0 should result in simply y[n] = x[n], while a 1 should result in y[n] = 0.5x[n] + 0.5x[n-1], and so on. order should not be expected to go above 49 in value. The value of order will be the same in each function call to averaging_filter, within a given test case.

Make sure you use the values pointed to as inputs and do not assume global variable names. Doing this will allow us to potentially reuse this function so if we set up two global variable pairs in the following way:

float values1[50];
float values2[50];

we could proceed to do the following, where we use the same function on two different arrays in order to keep track of two different buttons!

float output1 = averaging_filter(digitalRead(BUTTON_1), values1, 3); //step/update filter 3rd order filter with digital measurement
float output2 = averaging_filter(digitalRead(BUTTON_2), values2, 9); //step/update filter 9th order filter with digital measurement

...code reusability is a virtue!

Your function should return the current output (y which is y[n]) on every call. As for how to use stored_values, for our first attempt at a running average, we recommend using the array to store an ordered history of previous values like shown below:

Recommended usage of array for running average

In addition to performing the running average calculation, in order to maintain the operation of the system, on each call to the function, it will need to "perform maintenance" on the array being handed it to it so that the next time it is called, the values and their positions in the array are updated appropriately like shown below:

Updating the values in the array

The details of exactly how to use stored_values are up to you!

A Different Way...

Your previous solution to a running average required a lot of copying on each step. Most likely, each time through the loop you shifted values back (or forward) through the array, which can take extra time. It probably looked something like the following:

One way of how a moving average filter could live in memory

If we were to expand this system to work with something like you'd see in research industry, let's say a 4096-point running average or something, the number of copies you have to do on each call to the function will be enormous.

One way around this is to change how you use the array. Instead of shifting the contents of the array, instead shift your frame of reference.

A different way of doing a moving average using an additional variable to help give a frame of reference for how to interpret data.

  //....
  while(1){
    int input = some_function(); //get some input value from a function that returns measurements
    // Process the new input, and get a new output
    // In this case, we use a FILTER_ORDER moving average, but it can be anything
    float average = averaging_filter(input, values, FILTER_ORDER, &indx);
    printf("%f\n",average);
    while(millis()-time_counter < DT);
    time_counter = millis();
  }
}

Just like before, we're going to call the function repeatedly, giving you a new input (x) each time.

The averaging_filter function should take in four inputs:

  • float input: the current input to the averaging filter, which has been x in our discussion so far.
  • float* stored_values: a pointer to a float array that is global in scope and at the time of the first function call has been initialized to all zeros. You may assume the array has a length of at least 50. The testing code makes no modifications to this array, and your modifications will "live on" between calls to averaging_filter.
  • int order: an integer indicating the "order" of the filter. For example a 0 should result in simply y[n] = x[n], a 1 should result in y[n] = 0.5x[n] + 0.5x[n-1], and so on. order should not be expected to go above 49 in value. The value of order will be the same in each function call to averaging_filter, within a given test case.
  • int* ptr_to_index: (NEW VARIABLE FROM BEFORE) A pointer to another global variable to use as you see fit. It will be initialized to zero. We make no modifications to this variable, and your modifications will "live on" between calls to averaging_filter.

Also, just like before, make sure you use the values pointed to as inputs and do not assume global variable names. Additionally, the outputs of the checkers below will append small array checks to the end of the output stream to make sure you're using the arrays correctly.

Back to Exercises