If you are a current student, please Log In for full access to the web site.
Note that this link will take you to an external site (https://shimmer.csail.mit.edu) to authenticate, and then you will be redirected back to this page.
1) Writing an Averaging Filter
In Week 1 we talked about difference equations. In Lab 02A we then implemented a 3-way running average filter to smooth out our accelerometer readings a little bit. In difference equation form this took on the form:
We could also say this is a "second-order" moving average filter.
What we'd now like to do is write some code that will allow us to write an arbitrarily large moving average filter of mth order (which would correspond to a m+1-way moving average) such that:
which for values of m\geq2 could be expanded out to look like this generally:
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. This function should be more flexible/versatile, however since we can specify how many time steps back we use!
Our first attempt at a generic averaging filter will be used like this, for example:
uint32_t time_counter;
const uint32_t DT = 50; //sample period
const uint32_t FILTER_ORDER = 13; //size of filter
float values[50]; //used for remembering an arbitrary number of old previous values
void setup(){
Serial.begin(115200);
// Initialize vs to all zeros (to be safe)
// Could also do to non-zero values if desired!
for (int i=0; i<50; i++){
values[i] = 0.0;
}
time_counter = millis();
}
void loop(){
float input = some_function(); //get some input value from a function that returns measurements (maybe IMU 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);
Serial.println(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 afloat
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. 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 toaveraging_filter
.int order
: an integer indicating the "order" of the filter. For example a 0 should result in simply y[n] = x[n], a1
should result in y[n] = 0.5x[n] + 0.5x[n-1], and so on.order
should not be expected to go above49
in value. The value oforder
will be the same in each function call toaveraging_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:
float output1 = averaging_filter(analogRead(A3), values1, 3); //step/update filter 1 with analog measurement
float output2 = averaging_filter(analogRead(A3), values2, 9); //step/update filter 2 with analog measurement
...code reusability is a virtue!
Your function should return the current output (y which is y[n]). The details of exactly how to use stored_values
and are up to you!
2) A Different Way...
Chances are your previous solution 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:
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.
uint32_t time_counter;
const uint32_t DT = 50; //sample period
const uint32_t FILTER_ORDER = 13; //size of filter
float values[50]; //used for remembering an arbitrary number of old previous values
int indx = 0;
void setup(){
Serial.begin(115200);
// Initialize vs to all zeros (to be safe)
// Could also do to non-zero values if desired!
for (int i=0; i<50; i++){
values[i] = 0.0;
}
time_counter = millis();
}
void loop(){
int input = some_function(); //get some input value from a function that returns measurements (maybe IMU 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, &indx);
Serial.println(average);
while(millis()-time_counter < DT);
time_counter = millis();
}
Concretely, 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 afloat
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. We make no modifications to this array, and your modifications will "live on" between calls toaveraging_filter
.int order
: an integer indicating the "order" of the filter. For example a 0 should result in simply y[n] = x[n], a1
should result in y[n] = 0.5x[n] + 0.5x[n-1], and so on.order
should not be expected to go above49
in value. The value oforder
will be the same in each function call toaveraging_filter
, within a given test case.int* 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 toaveraging_filter
.
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];
int index1 = 0;
float values2[50];
int index2 = 0;
we could proceed to do the following:
float output1 = averaging_filter(analogRead(A3), values1, 3, &index1);
float output2 = averaging_filter(analogRead(A3), values2, 9, &index2);
...code reusability is a virtue!
Your function should return the current output (y which is y[n]).