Spring 2019

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 (

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:

y[n] = \frac{1}{3}x[n] + \frac{1}{3}x[n-1] + \frac{1}{3}x[n-2]

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:

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. 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 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. We make 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.

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!

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 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. We make 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* 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`

.

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]).

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 array's correctly.

This page was last updated on Sunday February 24, 2019 at 02:39:11 PM (revision

`0aa8daa`

).\ / /\__/\ \__=( o_O )= (__________) |_ |_ |_ |_Course Site powered by CAT-SOOP 14.0.4.dev5.

CAT-SOOP is free/libre software, available under the terms

of the GNU Affero General Public License, version 3.

(Download Source Code)

CSS/stryling from the Outboxcraft library Beauter, licensed under MIT

Copyright 2017