FilterDemos Arduino Sketch

N.B. this is new and still being tested.

This sketch demonstrates several examples of single-channel filters for processing sensor data.

The filter functions are purely numeric operations and not dependent on any Arduino-specific features. This supports offline testing, as they can be compiled for debugging and evaluation on a normal desktop computer. The code is designed to be included directly in an Arduino sketch, either as additional files or directly pasted into a sketch.

Please note this code follows Arduino conventions and uses global variables for filter state. For this reason, these functions will need to be customized for each application. This sample code is intended to be integrated into a sketch, not serve as a standalone library.

For more efficient implementations and a much broader variety of signal processing algorithms, I recommend exploring the Arduino library collections.

The sketch files may be downloaded in a single archive file as FilterDemos.zip, or browsed in raw form in the source folder. The individual files are documented below.

Main Sketch

The main sketch file is FilterDemos.ino. It includes an event loop to sample a sonar range finder sensor and apply several signal processing filters. The output is printed in a form suitable for real-time plotting using the IDE Serial Plotter.

  1// FilterDemos.ino : Arduino program to demonstrate a variety of single-channel signal filters.
  2// No copyright, 2020, Garth Zeglin.  This file is explicitly placed in the public domain.
  3
  4// The actual filter functions are kept in a separate .ino files which will
  5// automatically be compiled with this one by the Arduino IDE.  The filters are
  6// purely numerical and can compiled for testing on a normal desktop computer.
  7#include <stdlib.h>
  8
  9// The baud rate is the number of bits per second transmitted over the serial port.
 10const long BAUD_RATE = 115200;
 11
 12//================================================================
 13// Hardware definitions. You will need to customize this for your specific hardware.
 14const int sonarTriggerPin = 7;    // Specify a pin for a sonar trigger output.
 15const int sonarEchoPin    = 8;    // Specify a pin for a sonar echo input.
 16
 17//================================================================
 18// Global variables.
 19#include "statistics.h"
 20CentralMeasures stats; // class defined in statistics.h
 21
 22
 23//================================================================
 24// Standard Arduino initialization function to configure the system.
 25void setup()
 26{
 27  // initialize the Serial port
 28  Serial.begin( BAUD_RATE );
 29
 30  // Initialize the digital input/output pins.
 31  pinMode(sonarTriggerPin, OUTPUT);
 32  pinMode(sonarEchoPin, INPUT);
 33}
 34
 35//================================================================
 36// Standard Arduino polling function. This function is called repeatedly to
 37// handle all I/O and periodic processing.  This loop should never be allowed to
 38// stall or block so that all tasks can be constantly serviced.
 39
 40void loop()
 41{
 42  // Calculate the interval in microseconds since the last polling cycle.
 43  static unsigned long last_time = 0;
 44  unsigned long now = micros();
 45  unsigned long interval = now - last_time;
 46  last_time = now;
 47
 48  // Poll the sonar at regular intervals.
 49  static long sonar_timer = 0;
 50  sonar_timer -= interval;
 51  if (sonar_timer < 0) {
 52    sonar_timer += 100000; // 10 Hz sampling rate
 53
 54    // read the sonar; zeros represent a no-ping condition
 55    int raw_ping = ping_sonar();
 56
 57    // suppress zeros in the input, just repeating the last input
 58    int nz_ping = suppress_value(raw_ping, 0);
 59
 60    // apply a median filter to suppress individual outliers
 61    int median = median_3_filter(nz_ping);
 62    
 63    // convert the value from microseconds to centimeters
 64    float cm = fmap(median, 0.0, 5900.0, 0.0, 100.0);
 65
 66    // track central measures such as average and variance
 67    stats.add(cm);
 68    stats.compute_stats();
 69
 70    // apply the low-pass, high-pass, band-pass, and band-stop filters
 71    float lowpass_cm = lowpass(cm);
 72    float highpass_cm = highpass(cm);
 73    float bandpass_cm = bandpass(cm);
 74    float bandstop_cm = bandstop(cm);
 75    
 76    // apply the range data to the ring buffer and filter
 77    ring_buffer_put(cm);
 78
 79    // calculate the finite differencing derivative
 80    float velocity = ring_buffer_deriv(0.1);
 81
 82    // calculate the median filter over the ring buffer
 83    float ring_median = ring_median_filter();
 84    
 85    // fit a trajectory curve to recent sample history
 86    float traj[3];
 87    trajfit(cm, traj);
 88
 89    // emit some data to plot
 90    // Serial.print(raw_ping); Serial.print(" ");         // ping time in microseconds
 91    // Serial.print(median); Serial.print(" ");           // median-filtered ping time
 92    // Serial.print(cm); Serial.print("  ");              // centimeter-scaled of median-filtered
 93    // Serial.print(velocity); Serial.print("  ");        // velocity using finite differencing
 94    Serial.print(ring_median); Serial.print(" ");         // median-filtered distance using ring buffer
 95    Serial.print(traj[0]); Serial.print(" ");             // quadratic position
 96    Serial.print(traj[1]); Serial.print(" ");             // quadratic velocity
 97    // Serial.print(stats.average); Serial.print("  ");   // position average over all data
 98    Serial.println();
 99  }
100}
101
102//================================================================
103// Run a measurement cycle on the sonar range sensor. Returns the round-trip
104// time in microseconds.
105
106int ping_sonar(void)
107{
108  // Generate a short trigger pulse.
109  digitalWrite(sonarTriggerPin, HIGH);
110  delayMicroseconds(10);
111  digitalWrite(sonarTriggerPin, LOW);
112
113  // Measure the echo pulse length.  The ~6 ms timeout is chosen for a maximum
114  // range of 100 cm assuming sound travels at 340 meters/sec.  With a round
115  // trip of 2 meters distance, the maximum ping time is 2/340 = 0.0059
116  // seconds.  You may wish to customize this for your particular hardware.
117  const unsigned long TIMEOUT = 5900;
118  unsigned long ping_time = pulseIn(sonarEchoPin, HIGH, TIMEOUT);
119	
120  return ping_time;
121}
122//================================================================

statistics.h

class CentralMeasures

Object encapsulating a set of accumulators for calculating the mean, variance, min, and max of a stream of values. All instance variables are public to simplify reading out current properties. This class is suitable for static initialization as per Arduino programming conventions.

 1// statistics.ino : compute some basic central measures in an accumulator
 2// No copyright, 2009-2020, Garth Zeglin.  This file is explicitly placed in the public domain.
 3#include <math.h>
 4#include <float.h>
 5
 6#ifndef MAXFLOAT
 7#define MAXFLOAT FLT_MAX
 8#endif
 9
10class CentralMeasures {
11
12public:
13  long samples;        // running sum of value^0, i.e., the number of samples
14  float total;         // running sum of value^1, i.e., the accumulated total
15  float squared;       // running sum of value^2, i.e., the accumulated sum of squares
16  float min;           // smallest input seen
17  float max;           // largest input seen
18  float last;          // most recent input
19
20  // computed statistics
21  float average;       // mean value
22  float variance;      // square of the standard deviation
23
24  // Constructor to initialize an instance of the class.
25  CentralMeasures(void) {
26    samples = 0;
27    total = squared = 0.0;
28    min = MAXFLOAT;
29    max = -MAXFLOAT;
30    last = 0.0;
31    average = variance = 0.0;
32  }
33
34  // add a new sample to the accumulators; does not update the computed statistics
35  void add(float value) {
36    total += value;
37    squared += value*value;
38    if (value < min) min = value;
39    if (value > max) max = value;
40    samples += 1;
41    last = value;
42  }
43
44  void compute_stats(void) {
45    if ( samples > 0 ) {
46      average = total / samples;
47      if (samples > 1) {
48	// The "standard deviation of the sample", which is only correct
49	// if the population is normally distributed and a large sample
50	// is available, otherwise tends to be too low:
51	// sigma = sqrtf( samples * squared - total*total ) / ((float)samples);
52      
53	// Instead compute the "sample standard deviation", an unbiased
54	// estimator for the variance.  The standard deviation is the
55	// square root of the variance.
56	variance = ( samples * squared - total*total) / ((float) samples * (float)(samples - 1)) ;
57      }
58    }
59  }
60};

linear.ino

float fmap(float x, float in_min, float in_max, float out_min, float out_max)

Floating-point version of map(). The standard Arduino map() function only operates using integers; this extends the idea to floating point. The Arduino function can be found in the WMath.cpp file within the Arduino IDE distribution. Note that constrain() is defined as a preprocessor macro and so doesn’t have data type limitations.

 1// linear.ino : platform-independent linear transforms
 2// No copyright, 2020, Garth Zeglin.  This file is explicitly placed in the public domain.
 3
 4//================================================================
 5// Floating-point version of map().  The standard Arduino map() function only
 6// operates using integers; this extends the idea to floating point.  The
 7// Arduino function can be found in the WMath.cpp file within the Arduino IDE
 8// distribution.  Note that constrain() is defined as a preprocessor macro and
 9// so doesn't have data type limitations.
10
11float fmap(float x, float in_min, float in_max, float out_min, float out_max) {
12  float divisor = in_max - in_min;
13  if (divisor == 0.0) {
14    return out_min;
15  } else {
16    return (x - in_min) * (out_max - out_min) / divisor + out_min;
17  }
18}
19
20//================================================================

hysteresis.ino

int suppress_value(int input, int value)

Suppress a specific value in an input stream. One integer of state is required.

 1// hysteresis.ino : platform-independent non-linear filters
 2// No copyright, 2020, Garth Zeglin.  This file is explicitly placed in the public domain.
 3
 4//================================================================
 5// Quantize an input stream into a binary state.  Dual thresholds are needed to
 6// implement hysteresis: the input needs to rise above the upper threshold to
 7// trigger a high output, then drop below the input threshold to return to the
 8// low output.  One bit of state is required.
 9
10bool hysteresis(int input, int lower=300, int upper=700)
11{
12  // The previous state is kept in a static variable; this means this function
13  // can only be applied to a single input stream.
14  static bool output = false;  // previous binary output
15
16  if (output) {
17    if (input < lower) output = false;
18  } else {
19    if (input > upper) output = true;
20  }
21  return output;
22}
23
24//================================================================
25// Suppress a specific value in an input stream.  One integer of state is required.
26int suppress_value(int input, int value)
27{
28  static int previous = 0;
29  if (input != value) previous = input;
30  return previous;
31}
32
33//================================================================
34// Debounce an integer stream by suppressing changes from the previous value
35// until a specific new value has been observed a minimum number of times. Three
36// integers of state are required.
37
38int debounce(int input, int samples)
39{
40  static int current_value = 0;
41  static int new_value = 0;
42  static int count = 0;
43
44  if (input == current_value) {
45    count = 0;
46  } else {
47    if (count == 0) {
48      new_value = input;
49      count = 1;
50    } else {
51      if (input == new_value) {
52	count += 1;
53	if (count >= samples) {
54	  current_value = new_value;
55	  count = 0;
56	}
57      } else {
58	new_value = input;
59	count = 1;
60      }
61    }
62  }
63  return current_value;
64}
65//================================================================

smoothing.ino

 1// smoothing.ino : platform-independent first-order smoothing filter
 2// No copyright, 2020, Garth Zeglin.  This file is explicitly placed in the public domain.
 3
 4// Smooth an input signal using a first-order filter.  One floating point state
 5// value is required.  The smaller the coefficient, the smoother the output.
 6
 7float smoothing(float input, float coeff=0.1)
 8{
 9  // The previous state is kept in a static variable; this means this function
10  // can only be applied to a single input stream.
11  static float value = 0.0;          // filtered value of the input
12
13  float difference = input - value;  // compute the error
14  value += coeff * difference;       // apply a constant coefficient to move the smoothed value toward the input
15
16  return value;
17}

moving_average.ino

 1// moving_average.ino : windowed moving average filter with integer data (platform-independent)
 2// No copyright, 2020, Garth Zeglin.  This file is explicitly placed in the public domain.
 3
 4// Smooth a signal by averaging over multiple samples.  The recent time history
 5// (the 'moving window') is kept in an array along with a running total.  The
 6// state memory includes one long, an integer, and an array of integers the
 7// length of the window.
 8
 9int moving_average(int input)
10{
11  // The window size determines how many samples are held in memory and averaged together.
12  const int WINDOW_SIZE = 5;
13  
14  static int ring[WINDOW_SIZE];   // ring buffer for recent time history
15  static int oldest = 0;          // index of oldest sample
16  static long total = 0;          // sum of all values in the buffer
17
18  // subtract the oldest sample from the running total before overwriting
19  total = total - ring[oldest];
20  
21  // save the new sample by overwriting the oldest sample
22  ring[oldest] = input;
23
24  // advance to the next position, wrapping around as needed
25  oldest = oldest + 1;
26  if (oldest >= WINDOW_SIZE) oldest = 0;
27
28  // add the new input value to the running total
29  total = total + input;
30
31  // calculate the average; this is integer arithmetic so fractional parts will be truncated
32  return total / WINDOW_SIZE;
33}

median.ino

int median_3_filter(int c)

Reduce signal outliers using a non-linear median filter with a fixed width of three samples. Two integer values of state are required. The input signal is typically delayed by one sample period.

 1// median.ino : platform-independent three-sample median filter
 2// No copyright, 2020, Garth Zeglin.  This file is explicitly placed in the public domain.
 3
 4// Return the median of three integers, i.e. the middle value of the three.
 5// There are six possible sorted orderings from which to choose: ABC, ACB, BAC,
 6// BCA, CAB, CBA.  Note that equality can make some of these cases equivalent.
 7int median_of_three(int a, int b, int c)
 8{
 9  if (a < b) {
10    if (b < c)      return b; // ABC
11    else if (a < c) return c; // ACB
12    else            return a; // CAB
13  } else {
14    if (a < c)      return a; // BAC
15    else if (b < c) return c; // BCA
16    else            return b; // CBA
17  }    
18}  
19
20//================================================================
21// Reduce signal outliers using a non-linear median filter with a fixed width of
22// three samples.  Two integer values of state are required.  The input signal
23// is typically delayed by one sample period.
24
25int median_3_filter(int c)
26{
27  // The previous state is kept in a static variable; this means this function
28  // can only be applied to a single input stream.
29  static int a = 0, b = 0;  // previous two inputs in sample order
30  int median = median_of_three(a, b, c);
31  a = b;
32  b = c;
33  return median;
34}
35//================================================================

lowpass.ino

../_images/lowpass.png
float lowpass(float input)

Reduce high-frequency components to smooth a signal. Four float values of state are required.

This file was automatically generated using filter_gen.py and the SciPy toolkit. In general you will need to regenerate the filter for your particular application needs.

The plot shows the low-pass signal transfer ratio as a function of frequency. Please note this is plotted on a linear scale for clarity; on a logarithmic scale (dB) the rolloff slope becomes straight.

 1// Low-Pass Butterworth IIR digital filter, generated using filter_gen.py.
 2// Sampling rate: 10 Hz, frequency: 1.0 Hz.
 3// Filter is order 4, implemented as second-order sections (biquads).
 4// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
 5float lowpass(float input)
 6{
 7  float output = input;
 8  {
 9    static float z1, z2; // filter section state
10    float x = output - -1.04859958*z1 - 0.29614036*z2;
11    output = 0.00482434*x + 0.00964869*z1 + 0.00482434*z2;
12    z2 = z1;
13    z1 = x;
14  }
15  {
16    static float z1, z2; // filter section state
17    float x = output - -1.32091343*z1 - 0.63273879*z2;
18    output = 1.00000000*x + 2.00000000*z1 + 1.00000000*z2;
19    z2 = z1;
20    z1 = x;
21  }
22  return output;
23}

highpass.ino

../_images/highpass.png
float highpass(float input)

Reduce low-frequency components to remove constant and slowly-changing components of a signal. Four float values of state are required.

This file was automatically generated using filter_gen.py and the SciPy toolkit. In general you will need to regenerate the filter for your particular application needs.

The plot shows the high-pass signal transfer ratio as a function of frequency. Please note this is plotted on a linear scale for clarity; on a logarithmic scale (dB) the rolloff slope becomes straight.

 1// High-Pass Butterworth IIR digital filter, generated using filter_gen.py.
 2// Sampling rate: 10 Hz, frequency: 1.0 Hz.
 3// Filter is order 4, implemented as second-order sections (biquads).
 4// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
 5float highpass(float input)
 6{
 7  float output = input;
 8  {
 9    static float z1, z2; // filter section state
10    float x = output - -1.04859958*z1 - 0.29614036*z2;
11    output = 0.43284664*x + -0.86569329*z1 + 0.43284664*z2;
12    z2 = z1;
13    z1 = x;
14  }
15  {
16    static float z1, z2; // filter section state
17    float x = output - -1.32091343*z1 - 0.63273879*z2;
18    output = 1.00000000*x + -2.00000000*z1 + 1.00000000*z2;
19    z2 = z1;
20    z1 = x;
21  }
22  return output;
23}

bandpass.ino

../_images/bandpass.png
float bandpass(float input)

Remove the components of a signal which fall outside a specific frequency band. Eight float values of state are required.

This file was automatically generated using filter_gen.py and the SciPy toolkit. In general you will need to regenerate the filter for your particular application needs.

The plot shows the band-pass signal transfer ratio as a function of frequency. Please note this is plotted on a linear scale for clarity; on a logarithmic scale (dB) the rolloff slopes become straight.

 1// Band-Pass Butterworth IIR digital filter, generated using filter_gen.py.
 2// Sampling rate: 10 Hz, frequency: [0.5, 1.5] Hz.
 3// Filter is order 4, implemented as second-order sections (biquads).
 4// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
 5float bandpass(float input)
 6{
 7  float output = input;
 8  {
 9    static float z1, z2; // filter section state
10    float x = output - -1.10547167*z1 - 0.46872661*z2;
11    output = 0.00482434*x + 0.00964869*z1 + 0.00482434*z2;
12    z2 = z1;
13    z1 = x;
14  }
15  {
16    static float z1, z2; // filter section state
17    float x = output - -1.48782202*z1 - 0.63179763*z2;
18    output = 1.00000000*x + 2.00000000*z1 + 1.00000000*z2;
19    z2 = z1;
20    z1 = x;
21  }
22  {
23    static float z1, z2; // filter section state
24    float x = output - -1.04431445*z1 - 0.72062964*z2;
25    output = 1.00000000*x + -2.00000000*z1 + 1.00000000*z2;
26    z2 = z1;
27    z1 = x;
28  }
29  {
30    static float z1, z2; // filter section state
31    float x = output - -1.78062325*z1 - 0.87803603*z2;
32    output = 1.00000000*x + -2.00000000*z1 + 1.00000000*z2;
33    z2 = z1;
34    z1 = x;
35  }
36  return output;
37}

bandstop.ino

../_images/bandstop.png
float bandstop(float input)

Remove the components of a signal which fall inside a specific frequency band. Eight float values of state are required.

This file was automatically generated using filter_gen.py and the SciPy toolkit. In general you will need to regenerate the filter for your particular application needs.

The plot shows the band-stop signal transfer ratio as a function of frequency. Please note this is plotted on a linear scale for clarity; on a logarithmic scale (dB) the rolloff slopes become straight.

 1// Band-Stop Butterworth IIR digital filter, generated using filter_gen.py.
 2// Sampling rate: 10 Hz, frequency: [0.5, 1.5] Hz.
 3// Filter is order 4, implemented as second-order sections (biquads).
 4// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html
 5float bandstop(float input)
 6{
 7  float output = input;
 8  {
 9    static float z1, z2; // filter section state
10    float x = output - -1.10547167*z1 - 0.46872661*z2;
11    output = 0.43284664*x + -0.73640270*z1 + 0.43284664*z2;
12    z2 = z1;
13    z1 = x;
14  }
15  {
16    static float z1, z2; // filter section state
17    float x = output - -1.48782202*z1 - 0.63179763*z2;
18    output = 1.00000000*x + -1.70130162*z1 + 1.00000000*z2;
19    z2 = z1;
20    z1 = x;
21  }
22  {
23    static float z1, z2; // filter section state
24    float x = output - -1.04431445*z1 - 0.72062964*z2;
25    output = 1.00000000*x + -1.70130162*z1 + 1.00000000*z2;
26    z2 = z1;
27    z1 = x;
28  }
29  {
30    static float z1, z2; // filter section state
31    float x = output - -1.78062325*z1 - 0.87803603*z2;
32    output = 1.00000000*x + -1.70130162*z1 + 1.00000000*z2;
33    z2 = z1;
34    z1 = x;
35  }
36  return output;
37}

ring_buffer.ino

 1// ring_buffer.ino : fixed-length sample history buffer useful for finite filters
 2
 3const unsigned int RING_LENGTH = 10;
 4float ring_buffer[RING_LENGTH];       // circular buffer of samples
 5unsigned int ring_position = 0;       // index of the oldest sample
 6
 7// Put a new value into a circular sample buffer, overwriting the oldest sample.
 8void ring_buffer_put(float value)
 9{
10  if (ring_position < RING_LENGTH) ring_buffer[ring_position] = value;
11  if (++ring_position >= RING_LENGTH) ring_position = 0;
12}
13
14// Calculate the first derivative as the finite difference between the newest
15// and oldest values.  The delta_t parameter is the sampling interval in
16// seconds.
17float ring_buffer_deriv(float delta_t)
18{
19  float oldest = ring_buffer[ring_position];
20  float newest = (ring_position < RING_LENGTH-1) ? ring_buffer[ring_position+1] : ring_buffer[0];
21  return (newest - oldest) / ((RING_LENGTH-1) * delta_t);
22}

ring_median.ino

 1// ring_median.ino : platform-independent median filter on ring buffer
 2// No copyright, 2020, Garth Zeglin.  This file is explicitly placed in the public domain.
 3
 4// Note: this assumes the data is held in a global ring buffer, see ring_buffer.ino.
 5
 6float median_buffer[RING_LENGTH];    // working buffer of samples copied from ring_buffer
 7
 8//================================================================
 9// Utility function for the sorting function.
10int float_compare(const void *e1, const void *e2)
11{
12  float f1 = *((const float *) e1);
13  float f2 = *((const float *) e2);  
14  if (f1 < f2) return -1;
15  else if (f1 == f2) return 0;
16  else return 1;
17}
18
19//================================================================
20// Reduce signal outliers using a median filter applied over a ring buffer.
21// No additional state is required, but uses the global ring_buffer array.
22
23float ring_median_filter(void)
24{
25  // copy and sort the ring buffer samples
26  memcpy(median_buffer, ring_buffer, sizeof(median_buffer));
27  qsort(median_buffer, RING_LENGTH, sizeof(float), float_compare);
28
29  // return the median element
30  return median_buffer[RING_LENGTH/2];
31}
32//================================================================

trajfit.ino

This file contains a causal finite filter to estimate trajectory parameters by fitting a quadratic curve to the samples in a ring buffer. The result is an estimate of position, velocity, and acceleration for the most recent sample. The filter coefficients were generated using trajfit_gen.py and the SciPy toolkit.

In general you will need to regenerate the filter for your particular application needs; the coefficients encode the sampling rate for correct scaling of the output, and the best window size depends upon your application.

 1// Trajectory estimation filter generated using trajfit_gen.py.
 2// Based on Savitzky-Golay polynomial fitting filters.
 3// Sampling rate: 10 Hz.
 4// The output array will contain the trajectory parameters representing the signal
 5// at the current time: [position, velocity, acceleration], with units of [1, 1/sec, 1/sec/sec].
 6// Reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.savgol_coeffs.html
 7void trajfit(float input, float output[3])
 8{
 9  const float coeff[3][5] = 
10    {{  0.085714,  -0.142857,  -0.085714,   0.257143,
11        0.885714},
12     {  3.714286,  -3.857143,  -5.714286,  -1.857143,
13        7.714286},
14     { 28.571429, -14.285714, -28.571429, -14.285714,
15       28.571429}};
16  static float ring[5]; // buffer for recent time history
17  static int oldest = 0;      // index of oldest sample
18
19  // save the new sample by overwriting the oldest sample
20  ring[oldest] = input;
21  if (++oldest >= 5) oldest = 0;
22
23  // iterate over the coefficient rows
24  int index = oldest;
25  for (int i = 0; i < 3; i++) {
26    output[i] = 0.0; // clear accumulator
27
28    // Iterate over the samples and the coefficient rows.  The index cycles
29    // around the circular buffer once per row.
30    for (int j = 0; j < 5; j++) {
31      output[i] += coeff[i][j] * ring[index];
32      if (++index >= 5) index = 0;
33    }
34  }
35}

Development Tools

The development of this sketch involves several other tools which are not documented:

  1. A Python script for generating digital filters using SciPy: filter_gen.py

  2. A Python script for generating the trajfit filters using SciPy: trajfit_gen.py

  3. An Arduino sketch to capture testing data: RecordSonar.ino

  4. A customizable Python script for capturing a serial data stream: record_Arduino_data.py

  5. A C++ program for offline testing of the filter code using the test data: test_filters.cpp

  6. A Python matplotlib script for generating figures from the test data: generate_plots.py

  7. A set of recorded and computed test data files: data/

  8. A set of plotted figures of the test data files: plots/

The Python scripts use several third-party libraries:

  1. pySerial: portable support for the serial port used for Arduino communication

  2. SciPy: comprehensive numerical analysis; linear algebra algorithms used during filter generation

  3. Matplotlib: plotting library for visualizing data