├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples └── FilterExample │ └── FilterExample.ino ├── keywords.txt ├── library.properties └── src ├── FilterDerivative.cpp ├── FilterDerivative.h ├── FilterOnePole.cpp ├── FilterOnePole.h ├── FilterTwoPole.cpp ├── FilterTwoPole.h ├── Filters.h ├── FloatDefine.h ├── RunningStatistics.cpp └── RunningStatistics.h /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # Change Log 3 | 4 | All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/), and this project adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | ## [0.1.1] - 2021-05-05 7 | 8 | Updated to comply with the Arduio 1.5 standard directory structure. [Library Manager FAQ · arduino_Arduino Wiki](https://github.com/arduino/Arduino/wiki/Library-Manager-FAQ). 9 | 10 | Library can now be installed From the Arduino IDO, by going to `File > XX` 11 | 12 | ### Added 13 | - Initial `README.md` file, for GitHub and user documentation. 14 | - `library.projects` file now allows the project repo (on GitHub) to be scanned by Arduino, and automatically added to the Library Manager. 15 | - Added version number (in `library.projects` file), and the `CHANGELOG.md` (this file) 16 | 17 | ### Changed 18 | - Directory structure is now compatible with the latest [Library specification - Arduino CLI](https://arduino.github.io/arduino-cli/latest/library-specification/) (rev.2.2). 19 | - Source files (`.h` and `.cpp`) are now in the `/src` directory 20 | 21 | ## [0.1.0] - 2015-01-11 22 | 23 | Initial code release. 24 | 25 | ### Added 26 | - Initial commit of the code. 27 | - Added Apache 2.0 License -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [2014] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Filters 2 | 3 | **A Realtime Digital Signal Processing (DSP) Library for Arduino.** 4 | 5 | The Filters library implements several useful digital filters for real-time signal processing in microcontrollers. These filters are easy-to-use, programmable versions of common analog signal processing filters, such as single-pole (RC) lowpass and highpass filters. The library also includes two-pole lowpass filters (Bessel and Butterworth), as well other "filters" to calculate running statistics, or return the time derivative of a signal. 6 | 7 | A common usage of the Filters library is to use a lowpass filter (or cascaded set of lowpass filters) to smooth out values read continuously from an analog input pin. The filter output values can be read as needed, and are always valid. This is a simple way to implement the standard oversample, filter, downsample patten common with in analog-to-digital conversion (ADC). 8 | 9 | The filters use floating point precision, and should be updated with new input values frequently and continuously (in a loop) for accuracy., but do not need to be updated at fixed intervals (updates can be asynchronous). That is, Filters can be updated with new input values at essentially any rate, as long as they are the updated much more frequently the filter cut-off frequency. 10 | 11 | In addition to standard analog-like (recursive) filters, the library also includes additional filters useful for statistics and signal processing. The `Derivative` filter returns the current time derivative (`dx/dt`) of the signal. The `RunningStatistics` keeps track of the running `mean()` as well standard deviation `sigma()` of a signal, over some user-defined time window. 12 | 13 | Filters can also be cascaded, and the output of one filter can be used as the input of another filter. 14 | 15 | ## Installation (Ardunio, Energia, etc.) 16 | 17 | Make sure you have the [Arduino](https://www.arduino.cc/en/software) environment installed on your system. 18 | 19 | To install the Filters library, simply download (or clone) the `Filters` folder (this repository) into the Arduino libraries directory. The libraries directory resides in the default save location for Arduino sketches. On a Mac, this is `Documents > Arduino > libraries`. The `Filters` should be added there. 20 | 21 | You can then use the filters in your sketches by adding 22 | 23 | ```cpp 24 | #include 25 | ``` 26 | 27 | ## Using the Filters 28 | 29 | There are some examples in `examples` directory. 30 | 31 | The standard pattern for using a filter is: 32 | 33 | * Declare a filter, and set any properties. 34 | 35 | * Update the filter value continuously in a loop, by calling `.input( newValue )` on the filter. Updates should happen frequently, for numerical stability and accuracy, but do not have to occur at fixed time intervals. 36 | 37 | * To retrieve the value of the filter, call `.output()` on the filter. The output is valid at any time, and contains the most up-to-date filtered values. 38 | 39 | ## Filter Types 40 | 41 | The `Filters` library contains four different filter classes. Please see the `.h` files for the various filters, for a full list of functions. 42 | 43 | --- 44 | 45 | The **one pole filter** functions like an analog RC filter, with a corner specified by `fc`. The filter is initialized to zero, but the initial value can be specified (useful for avoid jumps at startup, if the signal contains an offset). 46 | 47 | ```cpp 48 | // constructor for one pole filter 49 | FilterOnePole( FILTER_TYPE ft=LOWPASS, float fc=1.0, float initialValue=0 ); 50 | ``` 51 | 52 | The `FILTER_TYPE` can be `LOWPASS` or `HIGHPASS`, which function like the traditional analog RC highpass and lowpass circuits. The `INTEGRATOR` and `DIFFERENTIATOR` filter types are experimental. 53 | 54 | --- 55 | 56 | The **two pole filter** functions like an analog RCL lowpass filter. The filter values are specified by the `frequency0`, the natural undampened frequency of the filter, and the `qualityFactor`. In practice, It is often more useful to create a two pole filter with the default values, and then set the filter values by calling `setAsFilter` on the newly created filter: 57 | 58 | ```cpp 59 | // constructor for two pole filter 60 | FilterTwoPole( float frequency0 = 1, float qualityFactor = 1, float xInit = 0); 61 | 62 | // member function for specifying filter type and value 63 | void setAsFilter( OSCILLATOR_TYPE ft, float frequency3db, float initialValue=0 ); 64 | ``` 65 | 66 | The `OSCILLATOR_TYPE` can be `LOWPASS_BESSEL` or `LOWPASS_BUTTERWORTH`, which have slightly different phase frequency responses. The corner frequency (cutoff frequency) is specified by the `frequency3db` parameter. 67 | 68 | --- 69 | 70 | The **derivative** "filter" has the standard filter interface, but returns the latest time derivative `dx/dt` of the signal. 71 | 72 | ```cpp 73 | // constructor for derivative filter 74 | FilterDerivative() 75 | ``` 76 | 77 | --- 78 | 79 | The **running statistics** "filter" keeps a running value calculated for the `mean()` and `sigma()` (standard deviation) of a signal, over some specified running window. This can be useful, for instance, too see if the value on a sensor is constant, or noisy/changing. 80 | 81 | ```cpp 82 | // constructor for running statistics filter 83 | RunningStatistics(); 84 | 85 | // member function to specify the window time 86 | void setWindowSecs( float windowSecs ); 87 | ``` 88 | 89 | 90 | 91 | ## Update (2021) 92 | 93 | This code was released as an opensource project in 2015, and I have not been attending to it. It appears that the code has become useful to the community, and gets a fair number of downloads / forks / etc. 94 | 95 | First, I wanted to express a sincere **Thank You** to GitHub, Arduino, and the opensource community, for keeping up interest in the project, and for the contributions. I'll be updating the project with additional documentation and examples in the future, to extend the usefulness of the project. 96 | 97 | The planned future work includes: 98 | 99 | * [ ] Adding the code to the Arduino Library, to make it more widely available, and easier to install 100 | * [x] Adding additional user information (specifics on the various filters, and how to use). 101 | * [ ] Adding additional examples 102 | * [ ] Addressing any pending Issues on GitHub 103 | 104 | Progress and updates will be posted to this site, and this is the main repository for the Filters code. 105 | 106 | -------------------------------------------------------------------------------- /examples/FilterExample/FilterExample.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | float testFrequency = 2; // test signal frequency (Hz) 4 | float testAmplitude = 100; // test signal amplitude 5 | float testOffset = 100; 6 | 7 | float windowLength = 20.0/testFrequency; // how long to average the signal, for statistist 8 | 9 | float testSignalSigma = testAmplitude / sqrt(2.0); // the RMS amplitude of the test signal 10 | float testSignal3dbSigma = testSignalSigma / sqrt(2.0); // the RMS amplitude of the test signal, down -3db 11 | 12 | float printPeriod = 5.0; 13 | 14 | // return the current time 15 | float time() { 16 | return float( micros() ) * 1e-6; 17 | } 18 | 19 | void setup() { 20 | Serial.begin( 57600 ); // start the serial port 21 | } 22 | 23 | void testOnePoleFilters() { 24 | // filters are test with a sine wave input, keep track of those values here for a sanity check 25 | RunningStatistics inputStats; // create statistics to look at the raw test signal 26 | inputStats.setWindowSecs( windowLength ); 27 | 28 | FilterOnePole filterOneLowpass( LOWPASS, testFrequency ); // create a one pole (RC) lowpass filter 29 | RunningStatistics filterOneLowpassStats; // create running statistics to smooth these values 30 | filterOneLowpassStats.setWindowSecs( windowLength ); 31 | 32 | FilterOnePole filterOneHighpass( HIGHPASS, testFrequency ); // create a one pole (RC) highpass filter 33 | RunningStatistics filterOneHighpassStats; // create running statistics to smooth these values 34 | filterOneHighpassStats.setWindowSecs( windowLength ); 35 | 36 | float startTime = time(); 37 | float nextPrintTime = time(); 38 | 39 | while( true ) { 40 | // update all real time classes 41 | float inputValue = testAmplitude + testAmplitude*sin( TWO_PI * testFrequency * time() ); 42 | 43 | // update the test value statistics 44 | inputStats.input( inputValue); 45 | 46 | // update the one pole lowpass filter, and statistics 47 | filterOneLowpass.input( inputValue ); 48 | filterOneLowpassStats.input( filterOneLowpass.output() ); 49 | 50 | // update the one pole highpass filter, and statistics 51 | filterOneHighpass.input( inputValue ); 52 | filterOneHighpassStats.input( filterOneHighpass.output() ); 53 | 54 | if( time() > nextPrintTime ) { 55 | // display current values to the screen 56 | nextPrintTime += printPeriod; // update the next print time 57 | 58 | Serial.print( "\n" ); 59 | Serial.print( "time: " ); Serial.print( time() ); 60 | 61 | // output values associated with the inputValue itsel 62 | Serial.print( "\tin: " ); Serial.print( inputStats.mean() ); Serial.print( " +/- " ); Serial.print( inputStats.sigma() ); 63 | Serial.print( " (" ); Serial.print( testOffset ); Serial.print( " +/- " ); Serial.print( testSignalSigma ); Serial.print( ")" ); 64 | 65 | // output values associated with the lowpassed value 66 | Serial.print( "\tLP1: " ); Serial.print( filterOneLowpassStats.mean() ); Serial.print( " +/- " ); Serial.print( filterOneLowpassStats.sigma() ); 67 | Serial.print( " (" ); Serial.print( testOffset ); Serial.print( " +/- " ); Serial.print( testSignal3dbSigma ); Serial.print( ")" ); 68 | 69 | // output values associated with the highpass value 70 | Serial.print( "\tHP1: " ); Serial.print( filterOneHighpassStats.mean() ); Serial.print( " +/- " ); Serial.print( filterOneHighpassStats.sigma() ); 71 | Serial.print( " (" ); Serial.print( "0.0" ); Serial.print( " +/- " ); Serial.print( testSignal3dbSigma ); Serial.print( ")" ); 72 | } 73 | } 74 | } 75 | 76 | void testTwoPoleFilters() { 77 | float factor = sqrt(10); // sqrt 10 will make the attenuation 10x 78 | 79 | // filters are test with a sine wave input, keep track of those values here for a sanity check 80 | RunningStatistics inputStats; // create statistics to look at the raw test signal 81 | inputStats.setWindowSecs( windowLength ); 82 | 83 | // standard Lowpass, set to the corner frequency 84 | FilterTwoPole filterTwoLowpass; // create a two pole Lowpass filter 85 | filterTwoLowpass.setAsFilter( LOWPASS_BUTTERWORTH, testFrequency ); 86 | 87 | RunningStatistics filterTwoLowpassStats; 88 | filterTwoLowpassStats.setWindowSecs( windowLength ); 89 | 90 | // Lowpass, set above corner frequency 91 | FilterTwoPole filterTwoLowpassAbove; // create a two pole Lowpass filter 92 | filterTwoLowpassAbove.setAsFilter( LOWPASS_BUTTERWORTH, testFrequency*factor ); 93 | 94 | RunningStatistics filterTwoLowpassAboveStats; 95 | filterTwoLowpassAboveStats.setWindowSecs( windowLength ); 96 | 97 | // Lowpass, set below corner frequency 98 | FilterTwoPole filterTwoLowpassBelow; // create a two pole Lowpass filter 99 | filterTwoLowpassBelow.setAsFilter( LOWPASS_BUTTERWORTH, testFrequency/factor ); 100 | 101 | RunningStatistics filterTwoLowpassBelowStats; 102 | filterTwoLowpassBelowStats.setWindowSecs( windowLength ); 103 | 104 | float startTime = time(); 105 | float nextPrintTime = time(); 106 | 107 | while( true ) { 108 | // update all real time classes 109 | float inputValue = testAmplitude + testAmplitude*sin( TWO_PI * testFrequency * time() ); 110 | 111 | // update the test value statistics 112 | inputStats.input( inputValue); 113 | 114 | // update the two pole Lowpass filter, and statistics 115 | filterTwoLowpass.input( inputValue ); 116 | filterTwoLowpassStats.input( filterTwoLowpass.output() ); 117 | 118 | // update the two pole Lowpass filter, and statistics 119 | filterTwoLowpassAbove.input( inputValue ); 120 | filterTwoLowpassAboveStats.input( filterTwoLowpassAbove.output() ); 121 | 122 | // update the two pole Lowpass filter, and statistics 123 | filterTwoLowpassBelow.input( inputValue ); 124 | filterTwoLowpassBelowStats.input( filterTwoLowpassBelow.output() ); 125 | 126 | if( time() > nextPrintTime ) { 127 | // display current values to the screen 128 | nextPrintTime += printPeriod; // update the next print time 129 | 130 | Serial.print( "\n" ); 131 | Serial.print( "time: " ); Serial.print( time() ); 132 | 133 | // output values associated with the inputValue itsel 134 | Serial.print( "\tin: " ); Serial.print( inputStats.mean() ); Serial.print( " +/- " ); Serial.print( inputStats.sigma() ); 135 | Serial.print( " (" ); Serial.print( testOffset ); Serial.print( " +/- " ); Serial.print( testSignalSigma ); Serial.print( ")" ); 136 | 137 | // output values associated with the Lowpass value 138 | Serial.print( "\tLP2: " ); Serial.print( filterTwoLowpassStats.mean() ); Serial.print( " +/- " ); Serial.print( filterTwoLowpassStats.sigma() ); 139 | Serial.print( " (" ); Serial.print( "0.0" ); Serial.print( " +/- " ); Serial.print( testSignal3dbSigma ); Serial.print( ")" ); 140 | 141 | // output values associated with the Lowpass value, set above corner frequency (should be almost full) 142 | Serial.print( "\tLP2A: " ); Serial.print( filterTwoLowpassAboveStats.mean() ); Serial.print( " +/- " ); Serial.print( filterTwoLowpassAboveStats.sigma() ); 143 | 144 | // output values associated with the Lowpass value, set above corner frequency (should be lower amplitude) 145 | Serial.print( "\tLP2B: " ); Serial.print( filterTwoLowpassBelowStats.mean() ); Serial.print( " +/- " ); Serial.print( filterTwoLowpassBelowStats.sigma() ); 146 | } 147 | } 148 | } 149 | 150 | void loop() { 151 | //testOnePoleFilters(); 152 | testTwoPoleFilters(); 153 | } 154 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | # For information on keywords, see 2 | # http://arduino.cc/en/Hacking/LibraryTutorial 3 | # 4 | # Each line has the name of the keyword, 5 | # followed by a tab (not spaces), 6 | # followed by the kind of keyword. 7 | # 8 | # classes should be KEYWORD1 and are colored orange 9 | # functions should be KEYWORD2 and will be brown 10 | 11 | ### 12 | Filters KEYWORD1 13 | 14 | 15 | ### 16 | FilterDerivative KEYWORD1 17 | input KEYWORD2 18 | output KEYWORD2 19 | 20 | ### 21 | FilterOnePole KEYWORD1 22 | setFilter KEYWORD2 23 | setFrequency KEYWORD2 24 | setTau KEYWORD2 25 | input KEYWORD2 26 | output KEYWORD2 27 | print KEYWORD2 28 | test KEYWORD2 29 | setToNewValue KEYWORD2 30 | 31 | ### 32 | FilterTwoPole KEYWORD1 33 | SetQ KEYWORD2 34 | SetFrequency0 KEYWORD2 35 | setAsFilter KEYWORD2 36 | input KEYWORD2 37 | output KEYWORD2 38 | getMaxAmp KEYWORD2 39 | print KEYWORD2 40 | test KEYWORD2 41 | setToNewValue KEYWORD2 42 | 43 | ### 44 | RunningStatistics KEYWORD1 45 | setWindowSecs KEYWORD2 46 | setInitialValue KEYWORD2 47 | mean KEYWORD2 48 | variance KEYWORD2 49 | sigma KEYWORD2 50 | CV KEYWORD2 51 | 52 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Filters 2 | version=0.1.1 3 | author=Jonathan Driscoll 4 | maintainer=Jonathan Driscoll 5 | sentence=A realtime digital signal processing (DSP) library for Arduino. 6 | paragraph=Filters are easy-to-use, programmable versions of common analog filters, such as single-pole (RC) lowpass and highpass filters. The library also includes two-pole filters (Bessel and Butterworth), as well other "filters" to calculate running stastics, or return the time derivative of a signal. 7 | category=Data Processing 8 | url=https://github.com/JonHub/Filters 9 | architectures=* 10 | includes=Filters.h 11 | -------------------------------------------------------------------------------- /src/FilterDerivative.cpp: -------------------------------------------------------------------------------- 1 | #include "FilterDerivative.h" 2 | #include "Arduino.h" 3 | 4 | float FilterDerivative::input( float inVal ) { 5 | long thisUS = micros(); 6 | float dt = 1e-6*float(thisUS - LastUS); // cast to float here, for math 7 | LastUS = thisUS; // update this now 8 | 9 | Derivative = (inVal-LastInput) / dt; 10 | 11 | LastInput = inVal; 12 | return output(); 13 | } 14 | 15 | float FilterDerivative::output() { return Derivative; } 16 | 17 | void testFilterDerivative() { 18 | FilterDerivative der; 19 | 20 | while(true) { 21 | float t = 1e-6 * float( micros() ); 22 | float value = 100*sin(TWO_PI*t); 23 | 24 | der.input(value); 25 | 26 | Serial.print( "\n" ); 27 | Serial.print( value ); Serial.print( "\t"); 28 | Serial.print( der.output() ); 29 | 30 | delay(10); 31 | } 32 | } -------------------------------------------------------------------------------- /src/FilterDerivative.h: -------------------------------------------------------------------------------- 1 | #ifndef FilterDerivative_h 2 | #define FilterDerivative_h 3 | 4 | // returns the derivative 5 | struct FilterDerivative { 6 | long LastUS; 7 | float LastInput; 8 | 9 | float Derivative; 10 | 11 | float input( float inVal ); 12 | 13 | float output(); 14 | }; 15 | 16 | void testFilterDerivative(); 17 | 18 | #endif -------------------------------------------------------------------------------- /src/FilterOnePole.cpp: -------------------------------------------------------------------------------- 1 | #include "FilterOnePole.h" 2 | #include "FloatDefine.h" 3 | 4 | FilterOnePole::FilterOnePole( FILTER_TYPE ft, float fc, float initialValue ) { 5 | setFilter( ft, fc, initialValue ); 6 | } 7 | 8 | void FilterOnePole::setFilter( FILTER_TYPE ft, float fc, float initialValue ) { 9 | FT = ft; 10 | setFrequency( fc ); 11 | 12 | Y = initialValue; 13 | Ylast = initialValue; 14 | X = initialValue; 15 | 16 | LastUS = micros(); 17 | } 18 | 19 | float FilterOnePole::input( float inVal ) { 20 | long time = micros(); 21 | ElapsedUS = float(time - LastUS); // cast to float here, for math 22 | LastUS = time; // update this now 23 | 24 | // shift the data values 25 | Ylast = Y; 26 | X = inVal; // this is now the most recent input value 27 | 28 | // filter value is controlled by a parameter called X 29 | // tau is set by the user in microseconds, but must be converted to samples here 30 | TauSamps = TauUS / ElapsedUS; 31 | 32 | float ampFactor; 33 | #ifdef ARM_FLOAT 34 | ampFactor = expf( -1.0 / TauSamps ); // this is 1 if called quickly 35 | #else 36 | ampFactor = exp( -1.0 / TauSamps ); // this is 1 if called quickly 37 | #endif 38 | 39 | Y = (1.0-ampFactor)*X + ampFactor*Ylast; // set the new value 40 | 41 | return output(); 42 | } 43 | 44 | void FilterOnePole::setFrequency( float newFrequency ) { 45 | setTau( 1.0/(TWO_PI*newFrequency ) ); // τ=1/ω 46 | } 47 | 48 | void FilterOnePole::setTau( float newTau ) { 49 | TauUS = newTau * 1e6; 50 | } 51 | 52 | float FilterOnePole::output() { 53 | // figure out which button to read 54 | switch (FT) { 55 | case LOWPASS: 56 | // return the last value 57 | return Y; 58 | break; 59 | case INTEGRATOR: 60 | // using a lowpass, but normaize 61 | return Y * (TauUS/1.0e6); 62 | break; 63 | case HIGHPASS: 64 | // highpass is the _difference_ 65 | return X-Y; 66 | break; 67 | case DIFFERENTIATOR: 68 | // like a highpass, but normalize 69 | return (X-Y)/(TauUS/1.0e6); 70 | break; 71 | default: 72 | // should never get to here, return 0 just in case 73 | return 0; 74 | } 75 | } 76 | 77 | void FilterOnePole::print() { 78 | Serial.println(""); 79 | Serial.print(" Y: "); Serial.print( Y ); 80 | Serial.print(" Ylast: "); Serial.print( Ylast ); 81 | Serial.print(" X "); Serial.print( X ); 82 | Serial.print(" ElapsedUS "); Serial.print( ElapsedUS ); 83 | Serial.print(" TauSamps: "); Serial.print( TauSamps ); 84 | //Serial.print(" ampFactor " ); Serial.print( ampFactor ); 85 | Serial.print(" TauUS: "); Serial.print( TauUS ); 86 | Serial.println(""); 87 | } 88 | 89 | void FilterOnePole::test() { 90 | float tau = 10; 91 | float updateInterval = 1; 92 | float nextupdateTime = millis()*1e-3; 93 | 94 | float inputValue = 0; 95 | FilterOnePole hp( HIGHPASS, tau, inputValue ); 96 | FilterOnePole lp( LOWPASS, tau, inputValue ); 97 | 98 | while( true ) { 99 | float now = millis()*1e-3; 100 | 101 | // switch input values on a 20 second cycle 102 | if( round(now/20.0)-(now/20.0) < 0 ) 103 | inputValue = 0; 104 | else 105 | inputValue = 100; 106 | 107 | hp.input(inputValue); 108 | lp.input(inputValue); 109 | 110 | if( now > nextupdateTime ) { 111 | nextupdateTime += updateInterval; 112 | 113 | Serial.print("inputValue: "); Serial.print( inputValue ); 114 | Serial.print("\t high-passed: "); Serial.print( hp.output() ); 115 | Serial.print("\t low-passed: "); Serial.print( lp.output() ); 116 | Serial.println(); 117 | } 118 | } 119 | } 120 | 121 | void FilterOnePole::setToNewValue( float newVal ) { 122 | Y = Ylast = X = newVal; 123 | } 124 | 125 | 126 | // stuff for filter2 (lowpass only) 127 | // should be able to set a separate fall time as well 128 | FilterOnePoleCascade::FilterOnePoleCascade( float riseTime, float initialValue ) { 129 | setRiseTime( riseTime ); 130 | setToNewValue( initialValue ); 131 | } 132 | 133 | void FilterOnePoleCascade::setRiseTime( float riseTime ) { 134 | float tauScale = 3.36; // found emperically, by running test(); 135 | 136 | Pole1.setTau( riseTime / tauScale ); 137 | Pole2.setTau( riseTime / tauScale ); 138 | } 139 | 140 | float FilterOnePoleCascade::input( float inVal ) { 141 | Pole2.input( Pole1.input( inVal )); 142 | return output(); 143 | } 144 | 145 | // clears out the values in the filter 146 | void FilterOnePoleCascade::setToNewValue( float newVal ) { 147 | Pole1.setToNewValue( newVal ); 148 | Pole2.setToNewValue( newVal ); 149 | } 150 | 151 | float FilterOnePoleCascade::output() { 152 | return Pole2.output(); 153 | } 154 | 155 | void FilterOnePoleCascade::test() { 156 | // make a filter, how fast does it run: 157 | 158 | float rise = 1.0; 159 | FilterOnePoleCascade myFilter( rise ); 160 | 161 | // first, test the filter speed ... 162 | long nLoops = 1000; 163 | 164 | Serial.print( "testing filter with a rise time of "); 165 | Serial.print( rise ); Serial.print( "s" ); 166 | 167 | Serial.print( "\n running filter speed loop ... "); 168 | 169 | float startTime, stopTime; 170 | 171 | startTime = millis()*1e-3; 172 | for( long i=0; i 0.1 && !crossedTenPercent ) { 191 | // filter first crossed the 10% point 192 | startTime = millis()*1e-3; 193 | crossedTenPercent = true; 194 | } 195 | } 196 | stopTime = millis()*1e-3; 197 | 198 | Serial.print( "done, rise time: " ); Serial.print( stopTime-startTime ); 199 | 200 | Serial.print( "testing attenuation at f = 1/risetime" ); 201 | 202 | myFilter.setToNewValue( 0.0 ); 203 | 204 | float maxVal = 0; 205 | float valWasOutputThisCycle = true; 206 | 207 | float lastFilterVal = 0; 208 | 209 | while( true ) { 210 | float now = 1e-3*millis(); 211 | 212 | float currentFilterVal = myFilter.input( sin( TWO_PI*now) ); 213 | 214 | if( currentFilterVal < 0.0 ) { 215 | if( !valWasOutputThisCycle ) { 216 | // just crossed below zero, output the max 217 | Serial.print( maxVal*100 ); Serial.print( " %\n" ); 218 | valWasOutputThisCycle = true; 219 | } 220 | 221 | } 222 | 223 | } 224 | 225 | 226 | 227 | } 228 | -------------------------------------------------------------------------------- /src/FilterOnePole.h: -------------------------------------------------------------------------------- 1 | #ifndef FilterOnePole_h 2 | #define FilterOnePole_h 3 | 4 | #include 5 | 6 | enum FILTER_TYPE { 7 | HIGHPASS, 8 | LOWPASS, 9 | INTEGRATOR, 10 | DIFFERENTIATOR 11 | }; 12 | 13 | // the recursive filter class implements a recursive filter (low / pass / highpass 14 | // note that this must be updated in a loop, using the most recent acquired values and the time acquired 15 | // Y = a0*X + a1*Xm1 16 | // + b1*Ylast 17 | struct FilterOnePole { 18 | FILTER_TYPE FT; 19 | float TauUS; // decay constant of the filter, in US 20 | float TauSamps; // tau, measued in samples (this changes, depending on how long between input()s 21 | 22 | // filter values - these are public, but should not be set externally 23 | float Y; // most recent output value (gets computed on update) 24 | float Ylast; // prevous output value 25 | 26 | float X; // most recent input value 27 | 28 | // elapsed times are kept in long, and will wrap every 29 | // 35 mins, 47 seconds ... however, the wrap does not matter, 30 | // because the delta will still be correct (always positive and small) 31 | float ElapsedUS; // time since last update 32 | long LastUS; // last time measured 33 | 34 | FilterOnePole( FILTER_TYPE ft=LOWPASS, float fc=1.0, float initialValue=0 ); 35 | 36 | // sets or resets the parameters and state of the filter 37 | void setFilter( FILTER_TYPE ft, float tauS, float initialValue ); 38 | 39 | void setFrequency( float newFrequency ); 40 | 41 | void setTau( float newTau ); 42 | 43 | float input( float inVal ); 44 | 45 | float output(); 46 | 47 | void print(); 48 | 49 | void test(); 50 | 51 | void setToNewValue( float newVal ); // resets the filter to a new value 52 | }; 53 | 54 | // two pole filter, these are very useful 55 | struct FilterOnePoleCascade { 56 | 57 | FilterOnePole Pole1; 58 | FilterOnePole Pole2; 59 | 60 | FilterOnePoleCascade( float riseTime=1.0, float initialValue=0 ); // rise time to step function, 10% to 90% 61 | 62 | // rise time is 10% to 90%, for a step input 63 | void setRiseTime( float riseTime ); 64 | 65 | void setToNewValue( float newVal ); 66 | 67 | float input( float inVal ); 68 | 69 | float output(); 70 | 71 | void test(); 72 | }; 73 | 74 | 75 | #endif 76 | 77 | -------------------------------------------------------------------------------- /src/FilterTwoPole.cpp: -------------------------------------------------------------------------------- 1 | #include "FilterTwoPole.h" 2 | 3 | #ifdef ARDUINO_SAM_DUE 4 | #define ARM_FLOAT 5 | #endif 6 | 7 | // The driven, damped harmonic oscillator equation is: 8 | // http://en.wikipedia.org/wiki/Harmonic_oscillator) 9 | // 10 | // a + 2*ζ*ω0*v + ω0²*x = F(t)/m 11 | // 12 | // where the quality factor is related to the damping factor by 13 | // 14 | // Q = ½ζ 15 | // 16 | // It is useful to normalize the force to the spring constant, 17 | // so a force of F will result in the oscillator resting at a position x=F 18 | // The allows the oscillator to be used as a lowpass filter, 19 | // where the position X functions as the output voltage V. 20 | // Setting a=v=0, and solving for m, gives 21 | // 22 | // m = 1/w0 23 | // 24 | // and the final equation is 25 | // 26 | // a + 2*ζ*ω0*v + ω0²*x = ω0²*F(t) 27 | // 28 | // for determining the energy, you must know the effective spring constant, which is 29 | // found from the equation (for undampened, where w0 = w) 30 | // 31 | // w0² = k/m 32 | // w0 = k 33 | // 34 | // this makes the energy 35 | // 36 | // E = 0.5*k*x² + 0.5*m*v² 37 | // = 0.5*w0*x² + 0.5*v²/w0 38 | // 39 | // 40 | // Filter types (such as Bessel or Butterworth) are defined by 41 | // specific quality factors ... the quality factor also defines a 42 | // relationship between f0 (the undamped resonance frequency) 43 | // and the -3 dB frequency of the filter 44 | // 45 | // For a Butterworth filter, these values are 46 | // (see figures 8.26 and 8.32 Bessel) 47 | // (See Analog Devices note AN-649) 48 | // http://www.analog.com/static/imported-files/application_notes/447952852AN-649_0.pdf 49 | // http://www.analog.com/library/analogDialogue/archives/43-09/EDCh%208%20filter.pdf) 50 | // 51 | // Butterworth 52 | // F_0=1 53 | // F_(-3dB)=1 54 | // Q=1/√2 55 | // 56 | // Bessel 57 | // F_0=1.2754 58 | // F_(-3dB)=1 59 | // Q=1/√3 60 | // 61 | // (note – ramp time for a Bessel filter is about 1/(2F_0 ) ) 62 | 63 | 64 | FilterTwoPole::FilterTwoPole( float frequency0, float qualityFactor, float xInit ) { 65 | X = xInit; // start it some arbitrary position 66 | Vprev = 0; // initially stopped 67 | IsHighpass = false; // by default, a normal oscillator 68 | 69 | setQ( qualityFactor ); 70 | setFrequency0( frequency0 ); 71 | 72 | LastTimeUS = micros(); 73 | } 74 | 75 | void FilterTwoPole::setQ( float qualityFactor ) { 76 | // zero will result in divide by zero, upper value keeps it stable 77 | qualityFactor = constrain( qualityFactor, 1e-3, 1e3 ); 78 | 79 | Q = qualityFactor; 80 | } 81 | 82 | void FilterTwoPole::setFrequency0( float f ) { 83 | W0 = TWO_PI*abs(f); 84 | } 85 | 86 | void FilterTwoPole::setAsFilter( OSCILLATOR_TYPE ft, float frequency3db, float initialValue ) { 87 | // if this is a highpass filter, set to invert the transfer function on the output 88 | //if( ft == HIGHPASS_BESSEL || ft == HIGHPASS_BUTTERWORTH ) { 89 | // IsHighpass = true; 90 | //} 91 | //else { 92 | IsHighpass = false; 93 | //} 94 | 95 | X = initialValue; 96 | 97 | if( ft == LOWPASS_BESSEL ) { 98 | setFrequency0( frequency3db * 1.28 ); 99 | setQ( 0.5774 ); 100 | } 101 | //else if( ft == HIGHPASS_BESSEL ) { 102 | // setFrequency0( frequency3db * 1.28 ); 103 | // setQ( 0.5774 ); 104 | //} 105 | 106 | else if( ft == LOWPASS_BUTTERWORTH ) { 107 | // set as butterworth 108 | setFrequency0( frequency3db ); 109 | setQ( 0.7071 ); 110 | } 111 | //else if( ft == HIGHPASS_BUTTERWORTH ) { 112 | // set as butterworth 113 | // setFrequency0( frequency3db ); 114 | // setQ( 0.7071 ); 115 | //} 116 | 117 | 118 | } 119 | 120 | float FilterTwoPole::input( float drive ) { 121 | Fprev = drive; // needed when using filter as a highpass 122 | 123 | long now = micros(); // get current time 124 | float dt = 1e-6*float(now - LastTimeUS); // find dt 125 | LastTimeUS = now; // save the last time 126 | 127 | // constrain the dt 128 | // if input has not been called frequently enough 129 | // the velocity and position can fly off to extremly high values 130 | // ... constraining the dt effectively "pauses" the motion during delays in updating 131 | // note this will result in an incorrect answer, but if dt is too large 132 | // the answer will be incorrect, regardless. 133 | dt = constrain( dt, 0, 1.0/W0 ); 134 | 135 | float A = sq(W0)*drive - W0/Q*Vprev - sq(W0)*X; // *** compute acceleration 136 | float V = Vprev + A * dt; // step velocity 137 | Vavg = .5*(V+Vprev); 138 | X += Vavg * dt; // step position, using average V to reduce error 139 | // (trapezoidal integration) 140 | 141 | Vprev = V; // save the last V 142 | 143 | // normally, this returns output 144 | // use it here to figure out how to return highpass 145 | 146 | //return Q/W0*Vavg; 147 | return output(); 148 | } 149 | 150 | float FilterTwoPole::output() { 151 | // if( IsHighpass ) 152 | // return Fprev-X-Q/W0*Vavg; // this is almost correct ... 153 | // else 154 | return X; // the filtered value (position of oscillator) 155 | } 156 | 157 | // as a measure for the energy of the oscillator, returns the maxium amplitude 158 | float FilterTwoPole::getMaxAmp() { 159 | // first, calculate the energy 160 | // E = 0.5*w0*x² + 0.5*v²/w0 161 | 162 | float E = 0.5 * W0 * sq(X) + 0.5 * sq(Vprev) / W0; 163 | 164 | // calculate use this to calculate max amplitude 165 | // E = 0.5*w0*x² 166 | // x = sqrt(2*E/w0) 167 | #ifdef ARM_FLOAT 168 | return sqrtf(2.0*E/W0); 169 | #else 170 | return sqrt(2.0*E/W0); 171 | #endif 172 | } 173 | 174 | void FilterTwoPole::print() { 175 | Serial.print(" X: "); Serial.print( X ); 176 | Serial.print(" Vprev: "); Serial.print( Vprev ); 177 | Serial.println(""); 178 | } 179 | 180 | void FilterTwoPole::test() { 181 | float updateInterval = .1; 182 | float nextupdateTime = 1e-6*float(micros()); 183 | 184 | float inputValue = 0; 185 | FilterTwoPole osc( 0.2, 4, 0); 186 | 187 | while( true ) { 188 | float now = 1e-6*float(micros()); 189 | 190 | // switch input values on a 20 second cycle 191 | if( round(now/50.0)-(now/50.0) < 0 ) 192 | inputValue = 100; 193 | else 194 | inputValue = 150; 195 | 196 | osc.input(inputValue); 197 | 198 | analogWrite(10,osc.output() ); // hardcoded the dial pin 199 | 200 | if( now > nextupdateTime ) { 201 | nextupdateTime += updateInterval; 202 | 203 | Serial.print("inputValue: "); Serial.print( inputValue ); 204 | Serial.print("\t output: "); Serial.print( osc.output() ); 205 | Serial.println(); 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/FilterTwoPole.h: -------------------------------------------------------------------------------- 1 | #ifndef FilterTwoPole_h 2 | #define FilterTwoPole_h 3 | #include 4 | #define ATTODUINO 5 | 6 | enum OSCILLATOR_TYPE { 7 | LOWPASS_BESSEL, 8 | //HIGHPASS_BESSEL, 9 | LOWPASS_BUTTERWORTH, 10 | //HIGHPASS_BUTTERWORTH, 11 | }; 12 | 13 | // implements a driven harmonic oscillator, which can be used as a filter 14 | // mass is normalized to unity (does not appear explicity in the equations), 15 | // and the driving force is writtin in units of spring constant, so a static force 16 | // of X will cause the oscillator come to rest at X 17 | // 18 | // The oscillator parameters are 19 | // W0 (undampened resonant frequency) ... the user sets this in Hz (as F0) 20 | // Q (quality factor) 21 | // 22 | // In addition, the ocillator can be configured as a (two-pole) lowpass or high filter, 23 | // since these circuits are directly analogous to harmonic oscillators. 24 | 25 | // note that the two-pole lowpass Bessel, the rise time (for a step) and 26 | // and impulse response width are approx tau/2.0, or 1/(2*w0): 27 | // a tau = 1 is w0 (and wc) of TWO_PI 28 | // this has a rise time / impulse response of about 0.4 29 | // (i.e., the intergration time is about 0.4 * tau) 30 | 31 | struct FilterTwoPole { 32 | 33 | //OSCILLATOR_TYPE FT; 34 | float X; // position 35 | float Vprev; // previously computed velocity 36 | float Vavg; // average of the last two calculated velocities 37 | float Fprev; // previous driving force (not frequency!) 38 | 39 | float Q; // quality factor, must be > 0 40 | float W0; // undamped resonance frequency 41 | 42 | bool IsHighpass; // false for normal output, true will make a lowpass into a highpass 43 | 44 | long LastTimeUS; // last time measured 45 | 46 | FilterTwoPole( float frequency0 = 1, float qualityFactor = 1, float xInit = 0); 47 | 48 | void setQ( float qualityFactor ); 49 | 50 | void setFrequency0( float f ); 51 | 52 | void setAsFilter( OSCILLATOR_TYPE ft, float frequency3db, float initialValue=0 ); 53 | 54 | float input( float drive = 0 ); 55 | 56 | float output(); 57 | 58 | // as a measure for the energy of the oscillator, returns the maxium amplitude 59 | float getMaxAmp(); 60 | 61 | void print(); 62 | 63 | void test(); 64 | 65 | }; 66 | 67 | 68 | #endif 69 | 70 | -------------------------------------------------------------------------------- /src/Filters.h: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Jonathan Driscoll 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef Filters_h 16 | #define Filters_h 17 | 18 | #include "FilterOnePole.h" 19 | #include "FilterTwoPole.h" 20 | #include "FilterDerivative.h" 21 | #include "RunningStatistics.h" 22 | 23 | #endif -------------------------------------------------------------------------------- /src/FloatDefine.h: -------------------------------------------------------------------------------- 1 | #ifndef FloatDefine_h 2 | #define FloatDefine_h 3 | 4 | 5 | 6 | #endif -------------------------------------------------------------------------------- /src/RunningStatistics.cpp: -------------------------------------------------------------------------------- 1 | #include "RunningStatistics.h" 2 | #include "FloatDefine.h" 3 | 4 | RunningStatistics::RunningStatistics() { 5 | setWindowSecs( 1 ); // setup with one second average 6 | setInitialValue( 0 ); // start with zero 7 | } 8 | 9 | void RunningStatistics::setWindowSecs( float windowSecs ) { 10 | AverageSecs = windowSecs; 11 | 12 | averageValue.setRiseTime( AverageSecs ); 13 | averageSquareValue.setRiseTime( AverageSecs ); 14 | } 15 | 16 | void RunningStatistics::setInitialValue( float initialMean, float initialSigma ) { 17 | averageValue.setToNewValue( initialMean ); 18 | averageSquareValue.setToNewValue( sq(initialMean) + sq(initialSigma ) ); 19 | } 20 | 21 | void RunningStatistics::input( float inVal ) { 22 | averageValue.input(inVal); // calculates running average 23 | averageSquareValue.input(inVal*inVal); // calculates running average of square 24 | } 25 | 26 | float RunningStatistics::mean() { 27 | return averageValue.output(); 28 | } 29 | 30 | float RunningStatistics::variance() { 31 | float var = averageSquareValue.output() - averageValue.output()*averageValue.output(); 32 | 33 | // because of the filtering, it's possible that this could be negative ... check! 34 | if( var < 0 ) var = 0; 35 | 36 | return var; 37 | } 38 | 39 | float RunningStatistics::sigma() { 40 | 41 | #ifdef ARM_FLOAT 42 | return sqrtf(variance()); 43 | #else 44 | return sqrt(variance()); 45 | #endif 46 | 47 | } 48 | 49 | float RunningStatistics::CV() { 50 | static const float maxCV = 1e3; 51 | float meanTmp = mean(); 52 | 53 | // prevent divide by zero 54 | if( meanTmp == 0 ) return maxCV; 55 | else return sigma() / meanTmp; 56 | } 57 | 58 | void testRunningStatistics() { 59 | // a speed test for running statistics 60 | 61 | RunningStatistics myStats; 62 | 63 | myStats.setInitialValue( (1.0/1024)*float(analogRead( A0 )) ); 64 | 65 | float updateInterval = 1.0; 66 | float nextUpdateTime = 1e-6*float(micros()) + updateInterval; 67 | 68 | long nLoops = 0; 69 | 70 | while( true ) { 71 | myStats.input( (1.0/1024)*float(analogRead( A0 )) ); 72 | nLoops++; 73 | float t = 1e-6*float(micros()); 74 | 75 | if( t > nextUpdateTime ) { 76 | nextUpdateTime += updateInterval; 77 | 78 | Serial.print( "mean: "); Serial.print( myStats.mean() ); 79 | Serial.print( "\tsigma: " ); Serial.print( myStats.sigma() ); 80 | Serial.print( "\tHz: "); Serial.print( nLoops ); 81 | 82 | nLoops = 0; 83 | Serial.print("\n"); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/RunningStatistics.h: -------------------------------------------------------------------------------- 1 | #ifndef RunningStatistics_h 2 | #define RunningStatistics_h 3 | 4 | #include "FilterOnePole.h" 5 | 6 | struct RunningStatistics { 7 | // in statistics, SigmaSqr is: 8 | // σ^2 = - ^2 9 | // averages can be taken by low-pass smoothing with a (two-pole) filter 10 | 11 | float AverageSecs; // seconds to average over 12 | 13 | FilterOnePoleCascade averageValue; 14 | FilterOnePoleCascade averageSquareValue; 15 | 16 | void input( float inVal ); 17 | 18 | // constructor 19 | RunningStatistics(); 20 | 21 | void setWindowSecs( float windowSecs ); 22 | 23 | void setInitialValue( float initialMean, float initialSigma = 0 ); 24 | 25 | float mean(); 26 | 27 | float variance(); 28 | 29 | float sigma(); 30 | 31 | float CV(); 32 | }; 33 | 34 | void testRunningStatistics(); 35 | 36 | #endif --------------------------------------------------------------------------------