Data Smoothies & Interpolation

Data points change over time. However, they might be changing across large or potentially inconsistent intervals, depending on the type of data you are retrieving.

New values coming in will end up ‘snapping’ from one value to another, which is especially noticeable over a longer interval. For example:

0
50    (new data point, 1s later)
100   (new data point, 3s later)
44    (new data point, 5s later)

This might result in ‘jerky’ or ‘laggy’ behaviour, which may be undesirable. We can write a very basic algorithm that allows a far smoother ‘transition’ by slowly ‘spreading’ this change in value over time. This is also known as interpolation:

0
1     (new data point set: 50)
2     (interpolation begins... notice how it doesn't 'snap' to 50 right away)
3
...
48
49
50    (data point reached: 50)
50
50
51    (new data point set: 100)
52    (interpolation transitions to new point...)
53
...
98
99
100   (data point reached: 100)
100

This above illustration can also be understood as a line graph, traversing from one known point to another:

Serial output of a data plot traversing from one value to another as a linear transition.

Serial output of a data plot traversing from one value to another as a linear transition.

Customisations made to this algorithm allow you to set how quickly you want the data to smooth over, making this a very handy tool for very quick, linear translations between data points.

The examples discussed here are of the LINEAR nature, i.e. the data points traverse in a straight path towards the next. This linear interpolation (also known as ‘lerping’ in some software packages) provides a surprisingly-sufficient dynamism to your physical actuations. Other more advanced modes of interpolation can also be explored, using easing functions.

The below example uses pure integers for simplicity and legibility. See if you can convert this example code to use doubles instead (doubles are double-precision floating point numbers, i.e. fractions with higher resolution than regular floating point numbers).

To see the results, open a Serial monitor to your microcontroller.

Note

This recipe does not include any other bits of working with actuators/sensors – it is meant for you to integrate into your existing code.

Code

Two code variants are provided as starting examples. The Arduino Uno requires you to install the Ticker code library, while the Particle Photon comes with the built-in Timer library and demonstrates how a server-triggered Particle function can be used to drive the change in value of myVar.


  • Libraries Used

    Code

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    
    /*
        Data Smoothies – an example on how to take incoming data values for a variable but smoothly transition to the new value
        (instead of having the old value 'snap' to the new one)
    */
    /*
        Please note that the code provided here is licensed under the MIT license.
    
        The MIT License (MIT)
        Copyright © 2018 Chuan Khoo
    
        Permission is hereby granted, free of charge, to any person obtaining
        a copy of this software and associated documentation files (the
        "Software"), to deal in the Software without restriction, including
        without limitation the rights to use, copy, modify, merge, publish,
        distribute, sublicense, and/or sell copies of the Software, and to
        permit persons to whom the Software is furnished to do so, subject to
        the following conditions:
    
        The above copyright notice and this permission notice shall be included
        in all copies or substantial portions of the Software.
    
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
        OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
        THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
        OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
        ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
        OTHER DEALINGS IN THE SOFTWARE.
    */
    
    #include "Ticker.h"
    
    // The Arduino UNO needs the Ticker code library (or any other similar) to work. Set up a timer that can 'manage' the updating of our variable that we wish to 'smooth'
    // Check the updateDataVar function - because this is where all of the data smoothing action happens
    // The smaller this interval is (50), the smoother + faster the transition will be
    
    void updateDataVar();
    Ticker updateDataTimer(updateDataVar, 50);
    
    // setting up some variables to manage incoming test Serial data:
    String inputString = "";      // a String to hold incoming data
    bool stringComplete = false;  // whether the string is complete
    
    // we declare newVar as 'volatile' because it is a variable that is actively being changed from the Particle cloud (via our ParticleFunc node)
    volatile int newVar = 0;
    int myVar = 0;
    
    // set a value to increment the variable by.
    // in this case, we are using integers (whole numbers),
    // so the minimum increment must be 1.
    int incre = 1;  // the larger this is, the faster the transition too
    
    void setup() {
      Serial.begin(115200);
      inputString.reserve(200);
      updateDataTimer.start();  // start the timer to keep updating the data smoothing!
    }
    
    void loop() {
      // add your other code here to read sensor readings and set it to newVar, for example...
      checkSerial();             // keep checking the Serial port for available data
      updateDataTimer.update();  // keep updating the Ticker
    }
    
    //////////// CUSTOM FUNCTIONS BELOW
    
    // checkSerial runs each time loop() runs, to check for incoming data from our Serial Monitor
    void checkSerial() {
      if (Serial.available() > 0) {
        // get the new byte:
        char inChar = (char)Serial.read();
        // add it to the inputString:
        inputString += inChar;
        // if the incoming character is a newline, set a flag so the main loop can
        // do something about it:
        if (inChar == '\n') {
          stringComplete = true;
        }
      }
    
      if (stringComplete) {
        // let's convert the incoming string into a number
        newVar = inputString.toInt();
        Serial.print(">>> newVar is now: ");
        Serial.println(newVar);
        inputString = "";  // clear the string
        stringComplete = false;
      }
    }
    
    void updateDataVar() {
      // here is the 'smoothing' algorithm used for linear traversal
      // it runs all the time and automatically slews myVar to newVar
    
      // find how how far we are away form target value
      int delta = myVar - newVar;
    
      // check if absolute (positive) value of delta is larger than incre
      if (abs(delta) >= incre) {
        if (newVar < myVar) {
          myVar -= incre;
        } else if (newVar > myVar) {
          myVar += incre;
        }
      } else {
        // we are too 'near' the target value but the size of incre means we will oscillate just above/under the target value!
        // we don't want that, so this else condition catches that (delta < incre) and sets the final value immediately
        // this is a lot more noticeable if you use a very large value for incre
        myVar = newVar;
      }
    
      Serial.println(myVar);
    }
    
  • Libraries Used

    • None

    Code

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    
    /*
        Data Smoothies – an example on how to take incoming data values for a variable but smoothly transition to the new value
        (instead of having the old value 'snap' to the new one)
    */
    /*
        Please note that the code provided here is licensed under the MIT license.
    
        The MIT License (MIT)
        Copyright © 2018 Chuan Khoo
    
        Permission is hereby granted, free of charge, to any person obtaining
        a copy of this software and associated documentation files (the
        "Software"), to deal in the Software without restriction, including
        without limitation the rights to use, copy, modify, merge, publish,
        distribute, sublicense, and/or sell copies of the Software, and to
        permit persons to whom the Software is furnished to do so, subject to
        the following conditions:
    
        The above copyright notice and this permission notice shall be included
        in all copies or substantial portions of the Software.
    
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
        OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
        THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
        OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
        ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
        OTHER DEALINGS IN THE SOFTWARE.
    */
    
    // Set up a timer that can 'manage' the updating of our variable that we wish to 'smooth'
    // Check the updateDataVar function - because this is where all of the data smoothing action happens
    // The smaller this interval is (50), the smoother + faster the transition will be
    Timer updateDataTimer(50, updateDataVar);
    
    // we declare newVar as 'volatile' because it is a variable that is actively being changed from the Particle cloud (via our ParticleFunc node)
    volatile int newVar = 0;
    int myVar = 0;
    
    // set a value to increment the variable by.
    // in this case, we are using integers (whole numbers),
    // so the minimum increment must be 1.
    int incre = 1;      // the larger this is, the faster the transition too
    
    void setup() {
        Serial.begin(115200);
    
        // register a function with the Particle cloud, so you can call this function using the ParticleFunc node in your flows
        Particle.function("newData", newData);
        delay(2000);
    
        // start the timer to keep updating the data smoothing!
        updateDataTimer.start();
    }
    
    void loop() {
        // nothing to do here (except add your other code to read sensor readings, for example)
    }
    
    //////////// CUSTOM FUNCTIONS BELOW
    int newData(String command) {
        // assume for this example that we have a Particle flow setup to send a string containing just a single value ("91")
        // ready to transition to this new value
        newVar = atoi(command);
        // return a 1 back to ParticleFunc, to say we are 'good'
        return 1;
    }
    
    void updateDataVar() {
        // here is the 'smoothing' algorithm used for linear traversal
        // it runs all the time and automatically slews myVar to newVar
    
        // find how how far we are away form target value
        int delta = myVar - newVar;
    
        // check if absolute (positive) value of delta is larger than incre
        if(abs(delta) >= incre) {
            if(newVar < myVar) {
                myVar -= incre;
            } else if(newVar > myVar) {
                myVar += incre;
            }
        } else {
            // we are too 'near' the target value but the size of incre means we will oscillate just above/under the target value!
            // we don't want that, so this else condition catches that (delta < incre) and sets the final value immediately
            // this is a lot more noticeable if you use a very large value for incre
            myVar = newVar;
        }
    
        Serial.println(myVar);
    }
    

This page was last updated: 23 Sep 2024