the library source is available from my github repository.

simple algorithms used for low-pass filtering (SRSmooth) to calculate proportional integral derivative (SRPID) by assuming that the sample rate (time) is constant between samples/invokations, eg. differentiation here is a simple sample-to-sample differencing, which is equivalent to Newton's difference quotient when h is constant (eg. 1). this is intended to be used with with my timed-event system SRTimer.

copyright tom jennings 2016, 2017

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation.

SRSmooth calculates smoothed (low-pass filtered) data, usually a time series (eg. periodically sampled). it has many advantages over the customary Simple Moving Average (SMA) method. where SMA requires one data storage unit for each sample to be averaged (eg. an array), exponential smoothing calculates the smoothed function with only two (floating point) datums. the "smoothing factor" can be changed dynamically. exponential smoothing also weights newer samples more than older samples. see wikipedia on exponential smoothing for details.

methods provided:

void begin (float SF); void set (float SF); float smooth (float input);

begin(float SF) is mainly provided to be consistent with most library objects, and is otherwise equivelant to setfloat SF). both set the single smoothing value. the smoothing value is a number greater than 0 and less than 1 (but could be 1). SF values approaching 1 smooth less, and values approaching 0 smooth more. in other workds SF is the proportion of the new, incoming, value, argument s passed to method smooth(). for example, when SF=0.6, the returned value (and internally remembered history) is the sum of 60% of the new, incoming value, and 40% of the previous history of accumulated smoothing calculation.

the smoothing factor SF is calculated by:

sample rate (milliseconds) SF= -------------- TC (milliseconds)

where TC is the desired time constant (67#percent; as in the usual electronic RC rule of thumb).

#includeSRSmooth SM; #include SRTimer T; const int SAMPLETIMER = 0; const int NUMTIMERS = 1; const int SAMPLERATE = 10; // sample rate, milliseconds const float TC = 470; // 470 mS time constant low pass filter const float SF= ((float)SAMPLERATE * .001) / TC; // calc smoothing factor float A; // smoothed analog input void setup() { Serial.begin (9600); SM.begin (SF); // set initial smoothing factor SM.set (SF); // change smoothing factor at any time T.begin (NUMTIMERS); // set up a timer T.setTimer (SAMPLETIMER, SAMPLERATE; } void loop () { if (T.timer (SAMPLETIMER)) { int s= analogRead (A0); // some changing input A= SM.smooth (s); // A is input smoothed with 1000mS TC } }

PID calculates a proportional integral deriviative function with each invokation of the pid() method with some input value, which is assumed to be a time series, eg. when called periodically using SRTimer or equivalent.

PID is generally used for closed-loop motor or other actuator::feedback systems. for an overview see the excellent wikipedia PID controller page.

PID tracks changes in it's single input data value, and returns a value that represents *information* about *changes* to that data over time. the return value indicates in sign and magnitude how fast and how much the input data is changing. the emphasis here is on *change*, not it's numeric value. a little-exploited use of PID will hopefully both illustrate what PID does and how to think about it.

PID is incredibly useful for deriving change information from one of the simplest of all analog sensors: the CdS photoresistor. the default values for SRPID are in fact setup for this use. the resistance of an CdS cell is quite arbitrary, in that it depends on manufacturing details and the number of photons impinging on it at any given moment. we can attach CdS cells to microcontrollers not to measure photons but to detect motion -- change in light.

deriving change information is what PID *does*. the fact that a given CdS cell and resistor attached to an analog input on an Arduino is returning "543" tells us literally nothing. intuitively you know that change to that value may mean something -- if the CdS cell is occluded, less light, resistance increases, current decreases, and the number returned by analogRead increases (in the setup below); conversely more light means lower numbers. it is change that is interesting, not numbers.

the following runnable example program will print out *changes* to the light impinging on a CdS cell attached to the A0 input pin and ground.

the time constant for integration (which is handled by SRSmooth, above) is passed initially in begin(), but it can be changed at any time with the SF(float f) method. TC is the "low pass" filter that selects what change is information and what is noise. see the example under SRSmooth for information on how to select the time constant.

#include#include SRPID P; SRTimer T; const int SAMPLETIMER = 0; const int NUMTIMERS = 1; const int SAMPLERATE = 10; // sample rate, milliseconds const float TC = 1000; // one second low pass filter const float SF= ((float)SAMPLERATE * .001) / TC; // calc smoothing factor void setup() { Serial.begin (9600); P.begin (SF); // init PID, set smoothing factor T.begin (NUMTIMERS); // set up a timer T.setTimer (SAMPLETIMER, SAMPLERATE; digitalWrite (A0, 1); // turn on internal pullup resistor } void loop () { int s= analogRead (A0); // read the raw CdS sensor int m- P.pid (s); // extract change information if (m != 0) Serial.println (m); // print out changes }

when first run the program will print a bunch of numbers as it determines what is initially the steady-state value from the CdS cell. it will settle within a second or so. from then on it will print out only when light on the sensor changes. the sign indicates the direction (light vs. dark) and the magnitude (absolute value) represents *rate of change* -- small value for slow, large value for fast -- regardless of almost and ambient light level, try changing the threshhold and the delay number which is sample rate.

the PID calculation for the instantaneous input E is the sum S of the three terms:

S = ( E * propGain + // proportion integral (E) * integGain + // integral difference (E) * diffGain + // derivative )

in addition to the above, if S is less than the threshhold value, 0 is returned. the default value for threshhold is 0, meaning all change is reported,

a major PID term not reflected in this code is the time series sample rate -- eg. how often you pass input to PID. it is assumed that this is a constant periodic rate, and it is a major tuning parameter. here i call this sample rate time T, units are milliseconds. SRTimer was designed in parallel with PID. see the SRTimer page for more details.

a brief mention of how SRTimer is used to invoke PID or any other time-based function is:

void begin (float SF); // initialize object, set smoothing factor float pid (float f); // calculate PID given set parameters float proportion(); // returns most recent proportion * gain float integral(); // returns most recent integral * gain float difference(); // returns most recent difference * gain SF (float f); // change smoothing factor float SF (); // returns current smoothing factor void propGain (float f); // set proportional gain (default: -1) void integGain (float f); // set integral gain (default: 1) void diffGain (float f); // set difference gain (default: 1) float propGain(); // returns current proportional gain float integGain(); // returns current integral gain float diffGain(); // returns current difference gain

the intermediate terms of the calculation are broken out for access; it's occasionally useful to run inputs through PID, but extract the integral portion of that calculation.

float P.proportion(); float P.integral(); float P.difference();

note that these are the result of internal calculation on the most-recent input value, and have gain applied to them. eg. if the most recent input vale was 3.3, and propGain is 2.0, P.proportion() will equal 6.6.