From 0a6fd7040bdc3811de4a827d2c193debd0dc08c3 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 25 May 2021 14:02:22 +0300 Subject: [PATCH] add --- README.md | 139 ++++++++++++++++++ examples/GFilterRA/GFilterRA.ino | 16 ++ examples/GLinear_arrays/GLinear_arrays.ino | 32 ++++ examples/GLinear_arrays/excel.jpg | Bin 0 -> 48954 bytes examples/GLinear_running/GLinear_running.ino | 34 +++++ examples/RingAverage/RingAverage.ino | 11 ++ .../alphabeta_example/alphabeta_example.ino | 24 +++ examples/fastFilter/fastFilter.ino | 16 ++ .../filters_comparsion/filters_comparsion.ino | 30 ++++ examples/kalman_example/kalman_example.ino | 31 ++++ examples/median3_example/median3_example.ino | 21 +++ examples/median_example/median_example.ino | 23 +++ keywords.txt | 34 +++++ library.properties | 9 ++ src/GyverFilters.h | 43 ++++++ src/filters/FastFilter.h | 86 +++++++++++ src/filters/RingAverage.h | 33 +++++ src/filters/alfaBeta.h | 40 +++++ src/filters/kalman.h | 41 ++++++ src/filters/linear.h | 50 +++++++ src/filters/median.h | 39 +++++ src/filters/median3.h | 34 +++++ src/filters/runningAverage.cpp | 45 ++++++ src/filters/runningAverage.h | 26 ++++ 24 files changed, 857 insertions(+) create mode 100644 README.md create mode 100644 examples/GFilterRA/GFilterRA.ino create mode 100644 examples/GLinear_arrays/GLinear_arrays.ino create mode 100644 examples/GLinear_arrays/excel.jpg create mode 100644 examples/GLinear_running/GLinear_running.ino create mode 100644 examples/RingAverage/RingAverage.ino create mode 100644 examples/alphabeta_example/alphabeta_example.ino create mode 100644 examples/fastFilter/fastFilter.ino create mode 100644 examples/filters_comparsion/filters_comparsion.ino create mode 100644 examples/kalman_example/kalman_example.ino create mode 100644 examples/median3_example/median3_example.ino create mode 100644 examples/median_example/median_example.ino create mode 100644 keywords.txt create mode 100644 library.properties create mode 100644 src/GyverFilters.h create mode 100644 src/filters/FastFilter.h create mode 100644 src/filters/RingAverage.h create mode 100644 src/filters/alfaBeta.h create mode 100644 src/filters/kalman.h create mode 100644 src/filters/linear.h create mode 100644 src/filters/median.h create mode 100644 src/filters/median3.h create mode 100644 src/filters/runningAverage.cpp create mode 100644 src/filters/runningAverage.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..bbebc7e --- /dev/null +++ b/README.md @@ -0,0 +1,139 @@ +![License: MIT](https://img.shields.io/badge/License-MIT-green.svg) +![author](https://img.shields.io/badge/author-AlexGyver-informational.svg) +# GyverFilters +GyverFilters - библиотека с некоторыми удобными фильтрами для Arduino +- GFilterRA - компактная альтернатива фильтра экспоненциальное бегущее среднее (Running Average) +- GMedian3 - быстрый медианный фильтр 3-го порядка (отсекает выбросы) +- GMedian - медианный фильтр N-го порядка. Порядок настраивается в GyverFilters.h - MEDIAN_FILTER_SIZE +- GABfilter - альфа-бета фильтр (разновидность Калмана для одномерного случая) +- GKalman - упрощённый Калман для одномерного случая (на мой взгляд лучший из фильтров) +- GLinear - линейная аппроксимация методом наименьших квадратов для двух массивов +- FastFilter - быстрый целочисленный экспоненциальный фильтр +- RingAverage - бегущее среднее с кольцевым буфером + +### Совместимость +Совместима со всеми Arduino платформами (используются Arduino-функции) + +### Документация +К библиотеке есть [расширенная документация](https://alexgyver.ru/GyverFilters/) + +## Содержание +- [Установка](#install) +- [Инициализация](#init) +- [Использование](#usage) +- [Пример](#example) +- [Версии](#versions) +- [Баги и обратная связь](#feedback) + + +## Установка +- Библиотеку можно найти по названию **GyverFilters** и установить через менеджер библиотек в: + - Arduino IDE + - Arduino IDE v2 + - PlatformIO +- [Скачать библиотеку](https://github.com/GyverLibs/GyverFilters/archive/refs/heads/main.zip) .zip архивом для ручной установки: + - Распаковать и положить в *C:\Program Files (x86)\Arduino\libraries* (Windows x64) + - Распаковать и положить в *C:\Program Files\Arduino\libraries* (Windows x32) + - Распаковать и положить в *Документы/Arduino/libraries/* + - (Arduino IDE) автоматическая установка из .zip: *Скетч/Подключить библиотеку/Добавить .ZIP библиотеку…* и указать скачанный архив +- Читай более подробную инструкцию по установке библиотек [здесь](https://alexgyver.ru/arduino-first/#%D0%A3%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0_%D0%B1%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA) + + +## Инициализация +См. примеры + + +## Использование +```cpp +// ============== Бегущее среднее ============== +GFilterRA(); // инициализация фильтра +GFilterRA(float coef, uint16_t interval); // расширенная инициализация фильтра (коэффициент, шаг фильтрации) +void setCoef(float coef); // настройка коэффициента фильтрации (0.00 - 1.00). Чем меньше, тем плавнее +void setStep(uint16_t interval); // установка шага фильтрации (мс). Чем меньше, тем резче фильтр + +float filteredTime(int16_t value); // возвращает фильтрованное значение с опорой на встроенный таймер +float filtered(int16_t value); // возвращает фильтрованное значение + +float filteredTime(float value); // возвращает фильтрованное значение с опорой на встроенный таймер +float filtered(float value); // возвращает фильтрованное значение +// ============== Медиана из трёх ============== +GMedian3(); // инициализация фильтра +uint16_t filtered(uint16_t); // возвращает фильтрованное значение +// ============== Медиана из MEDIAN_FILTER_SIZE (настраивается в GyverFilters.h) ============== +GMedian(); // инициализация фильтра +uint16_t filtered(uint16_t); // возвращает фильтрованное значение +// ============== Альфа-Бета фильтр ============== +GABfilter(float delta, float sigma_process, float sigma_noise); +// период дискретизации (измерений), process variation, noise variation + +void setParameters(float delta, float sigma_process, float sigma_noise); +// период дискретизации (измерений), process variation, noise variation + +float filtered(float value); // возвращает фильтрованное значение +// ============== Упрощённый Калман ============== +GKalman(float mea_e, float est_e, float q); +// разброс измерения, разброс оценки, скорость изменения значений + +GKalman(float mea_e, float q); +// разброс измерения, скорость изменения значений (разброс измерения принимается равным разбросу оценки) + +void setParameters(float mea_e, float est_e, float q); +// разброс измерения, разброс оценки, скорость изменения значений + +void setParameters(float mea_e, float q); +// разброс измерения, скорость изменения значений (разброс измерения принимается равным разбросу оценки) + +float filtered(float value); // возвращает фильтрованное значение +// ============== Линейная аппроксимация ============== +void compute(int *x_array, int *y_array, int arrSize); // аппроксимировать +float getA(); // получить коэффициент А +float getB(); // получить коэффициент В +float getDelta(); // получить аппроксимированное изменение +``` + + +## Пример +Остальные примеры смотри в **examples**! +```cpp +/* + Пример использования медианного фильтра. +*/ + +#include "GyverFilters.h" + +// указываем размер окна и тип данных в <> +GMedian<10, int> testFilter; + +void setup() { + Serial.begin(9600); +} + +void loop() { + delay(80); + int value = analogRead(0); + // добавляем шум "выбросы" + value += random(2) * random(2) * random(-1, 2) * random(50, 250); + Serial.print(value); + Serial.print(','); + value = testFilter.filtered(value); + Serial.println(value); +} +``` + + +## Версии +- v1.6 от 12.11.2019 +- v1.7: исправлен GLinear +- v1.8: небольшие улучшения +- v2.0: + - Улучшен и исправлен median и median3 + - Улучшен linear + - Смотрите примеры! Использование этих фильтров чуть изменилось +- v2.1: Исправлен расчёт дельты в линейном фильтре +- v2.2: Исправлена ошибка компиляции +- v3.0: Добавлен FastFilter и RingAverage + + +## Баги и обратная связь +При нахождении багов создавайте **Issue**, а лучше сразу пишите на почту [alex@alexgyver.ru](mailto:alex@alexgyver.ru) +Библиотека открыта для доработки и ваших **Pull Request**'ов! \ No newline at end of file diff --git a/examples/GFilterRA/GFilterRA.ino b/examples/GFilterRA/GFilterRA.ino new file mode 100644 index 0000000..c6db151 --- /dev/null +++ b/examples/GFilterRA/GFilterRA.ino @@ -0,0 +1,16 @@ +#include "GyverFilters.h" +GFilterRA analog0; // фильтр назовём analog0 + +void setup() { + Serial.begin(9600); + + // установка коэффициента фильтрации (0.0... 1.0). Чем меньше, тем плавнее фильтр + analog0.setCoef(0.01); + + // установка шага фильтрации (мс). Чем меньше, тем резче фильтр + analog0.setStep(10); +} + +void loop() { + Serial.println(analog0.filteredTime(analogRead(0))); +} diff --git a/examples/GLinear_arrays/GLinear_arrays.ino b/examples/GLinear_arrays/GLinear_arrays.ino new file mode 100644 index 0000000..2b50dc9 --- /dev/null +++ b/examples/GLinear_arrays/GLinear_arrays.ino @@ -0,0 +1,32 @@ +/* + Пример линейной аппроксимации методом наименьших квадратов + Два массива: по оси Х и по оси У + Линейная аппроксимация повозоляет получить уравнение прямой, + равноудалённой от точек на плоскости ХУ. Удобно для расчёта + роста изменяющейся шумящей величины. Уравнение вида у = A*x + B + В папке с данным примером есть скриншот из excel, + иллюстрирующий работу аппроксимации с такими же исходными +*/ + +// два массива с данными (одинаковой размероности и размера) +int x_array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; +int y_array[] = {1, 5, 2, 8, 3, 9, 10, 5, 15, 12}; + +#include +GLinear test; // указываем тип данных в <> + +void setup() { + Serial.begin(9600); + + // передаём массивы и размер одного из них + test.compute((int*)x_array, (int*)y_array, 10); + + // Уравнение вида у = A*x + B + Serial.println(test.getA()); // получить коэффициент А + Serial.println(test.getB()); // получить коэффициент В + Serial.println(test.getDelta()); // получить изменение (аппроксимированное) +} + +void loop() { + +} diff --git a/examples/GLinear_arrays/excel.jpg b/examples/GLinear_arrays/excel.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9cd711176987f941c3ad8a3de488a7f519868db9 GIT binary patch literal 48954 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 literal 0 HcmV?d00001 diff --git a/examples/GLinear_running/GLinear_running.ino b/examples/GLinear_running/GLinear_running.ino new file mode 100644 index 0000000..575c50d --- /dev/null +++ b/examples/GLinear_running/GLinear_running.ino @@ -0,0 +1,34 @@ +/* + Пример линейной аппроксимации методом наименьших квадратов + Два массива: по оси Х и по оси У + Наполнение массивов осуществляется динамически: сдвигом и записью в крайнюю ячейку, + то есть аппроксимация по последним ARRAY_SIZE изменениям!! +*/ +#define ARRAY_SIZE 10 // размер пространства для аппроксимации + +// два массива с данными (одинаковой размероности и размера) +int x_array[ARRAY_SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // ось x от 1 до 10, допустим СЕКУНД +int y_array[ARRAY_SIZE]; // значения по оси У будем брать с датчика + +#include +GLinear test; // указываем тип данных в <> + +void setup() { + Serial.begin(9600); +} + +void loop() { + for (byte i = 0; i < ARRAY_SIZE - 1; i++) { // счётчик от 0 до ARRAY_SIZE + y_array[i] = y_array[i + 1]; // сдвинуть массив давлений КРОМЕ ПОСЛЕДНЕЙ ЯЧЕЙКИ на шаг назад + } + // последний элемент массива теперь - новое значение (просто с аналог. датчика) + y_array[ARRAY_SIZE - 1] = analogRead(0); + + // передаём массивы и размер одного из них + test.compute((int*)x_array, (int*)y_array, sizeof(x_array)); + + // по нашим исходным данным это будет производная, т.е. "изменение единиц в секунду" + Serial.println(test.getDelta()); // получить изменение (аппроксимированное) + + delay(1000); // секундная задержка +} diff --git a/examples/RingAverage/RingAverage.ino b/examples/RingAverage/RingAverage.ino new file mode 100644 index 0000000..8636b9d --- /dev/null +++ b/examples/RingAverage/RingAverage.ino @@ -0,0 +1,11 @@ +#include "GyverFilters.h" +RingAverage fil; + +void setup() { + Serial.begin(9600); +} + +void loop() { + Serial.println(fil.filtered(random(50))); + delay(10); +} diff --git a/examples/alphabeta_example/alphabeta_example.ino b/examples/alphabeta_example/alphabeta_example.ino new file mode 100644 index 0000000..8f43b77 --- /dev/null +++ b/examples/alphabeta_example/alphabeta_example.ino @@ -0,0 +1,24 @@ +/* + Пример альфа-бета фильтра +*/ + +#include "GyverFilters.h" + +// параметры: период дискретизации (измерений), process variation, noise variation +GABfilter testFilter(0.08, 40, 1); + +void setup() { + Serial.begin(9600); +} + +void loop() { + delay(80); + int value = analogRead(0); + value += random(2) * random(-1, 2) * random(10, 70); + Serial.print("$"); + Serial.print(value); + Serial.print(" "); + value = testFilter.filtered((int)value); + Serial.print(value); + Serial.println(";"); +} diff --git a/examples/fastFilter/fastFilter.ino b/examples/fastFilter/fastFilter.ino new file mode 100644 index 0000000..b79ac6c --- /dev/null +++ b/examples/fastFilter/fastFilter.ino @@ -0,0 +1,16 @@ +// быстрый запаздывающйи фильтр + +#include +FastFilter fil(29); // 0-32 +void setup() { + Serial.begin(9600); + fil.setK(30); + fil.setRaw(1000); + fil.setFil(0); +} + +void loop() { + fil.computeNow(); + Serial.println(fil.getFil()); + delay(100); +} diff --git a/examples/filters_comparsion/filters_comparsion.ino b/examples/filters_comparsion/filters_comparsion.ino new file mode 100644 index 0000000..b981993 --- /dev/null +++ b/examples/filters_comparsion/filters_comparsion.ino @@ -0,0 +1,30 @@ +/* + Сравнение калмана и бегущего среднего +*/ +#include "GyverFilters.h" + +// параметры: разброс измерения, разброс оценки, скорость изменения значений +// разброс измерения: шум измерений +// разброс оценки: подстраивается сам, можно поставить таким же как разброс измерения +// скорость изменения значений: 0.001-1, варьировать самому + +GKalman kalman(90, 90, 0.5); +GFilterRA average(0.5, 80); + +void setup() { + Serial.begin(9600); +} + +void loop() { + int value = analogRead(0); + value += random(2) * random(-1, 2) * random(50, 100); + Serial.print("$"); + Serial.print(value); + Serial.print(" "); + + Serial.print((int)kalman.filtered(value)); + Serial.print(" "); + Serial.print((int)average.filtered(value)); + Serial.println(";"); + delay(80); +} diff --git a/examples/kalman_example/kalman_example.ino b/examples/kalman_example/kalman_example.ino new file mode 100644 index 0000000..802307a --- /dev/null +++ b/examples/kalman_example/kalman_example.ino @@ -0,0 +1,31 @@ +/* + Пример простого одномерного фильтра +*/ + +#include "GyverFilters.h" + +// параметры: разброс измерения, разброс оценки, скорость изменения значений +// разброс измерения: шум измерений +// разброс оценки: подстраивается сам, можно поставить таким же как разброс измерения +// скорость изменения значений: 0.001-1, варьировать самому + +GKalman testFilter(40, 40, 0.5); + +// также может быть объявлен как (разброс измерения, скорость изменения значений) +// GKalman testFilter(40, 0.5); + +void setup() { + Serial.begin(9600); +} + +void loop() { + delay(80); + int value = analogRead(0); + value += random(2) * random(-1, 2) * random(10, 70); + Serial.print("$"); + Serial.print(value); + Serial.print(" "); + value = testFilter.filtered((int)value); + Serial.print(value); + Serial.println(";"); +} diff --git a/examples/median3_example/median3_example.ino b/examples/median3_example/median3_example.ino new file mode 100644 index 0000000..c99176b --- /dev/null +++ b/examples/median3_example/median3_example.ino @@ -0,0 +1,21 @@ +/* + Пример использования быстрого медианного фильтра 3 порядка +*/ + +#include "GyverFilters.h" +GMedian3 testFilter; // указываем тип данных в <> + +void setup() { + Serial.begin(9600); +} + +void loop() { + int value = analogRead(0); + // добавляем шум "выбросы" + value += random(2) * random(2) * random(-1, 2) * random(50, 250); + Serial.print(value); + Serial.print(','); + value = testFilter.filtered(value); + Serial.println(value); + delay(80); +} diff --git a/examples/median_example/median_example.ino b/examples/median_example/median_example.ino new file mode 100644 index 0000000..c3fb006 --- /dev/null +++ b/examples/median_example/median_example.ino @@ -0,0 +1,23 @@ +/* + Пример использования медианного фильтра. +*/ + +#include "GyverFilters.h" + +// указываем размер окна и тип данных в <> +GMedian<10, int> testFilter; + +void setup() { + Serial.begin(9600); +} + +void loop() { + delay(80); + int value = analogRead(0); + // добавляем шум "выбросы" + value += random(2) * random(2) * random(-1, 2) * random(50, 250); + Serial.print(value); + Serial.print(','); + value = testFilter.filtered(value); + Serial.println(value); +} \ No newline at end of file diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..56abb30 --- /dev/null +++ b/keywords.txt @@ -0,0 +1,34 @@ +####################################### +# Syntax Coloring Map For GyverFilters +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +GyverFilters KEYWORD1 +GFilterRA KEYWORD1 +GMedian3 KEYWORD1 +GMedian KEYWORD1 +GABfilter KEYWORD1 +GKalman KEYWORD1 +GLinear KEYWORD1 +RingAverage KEYWORD1 +FastFilter KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +setK KEYWORD2 +setCoef KEYWORD2 +setStep KEYWORD2 +filteredTime KEYWORD2 +filtered KEYWORD2 +setParameters KEYWORD2 +getA KEYWORD2 +getB KEYWORD2 +getDelta KEYWORD2 +setRaw KEYWORD2 +setFil KEYWORD2 +computeNow KEYWORD2 \ No newline at end of file diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..4576cd0 --- /dev/null +++ b/library.properties @@ -0,0 +1,9 @@ +name=GyverFilters +version=3.0 +author=AlexGyver +maintainer=AlexGyver +sentence=Library with few filters for data processing +paragraph=Library with few filters for data processing +category=Data Processing +url=https://github.com/GyverLibs/GyverFilters +architectures=* \ No newline at end of file diff --git a/src/GyverFilters.h b/src/GyverFilters.h new file mode 100644 index 0000000..04d3bf7 --- /dev/null +++ b/src/GyverFilters.h @@ -0,0 +1,43 @@ +/* + GyverFilters - библиотека с некоторыми удобными фильтрами для Arduino + Документация: https://alexgyver.ru/gyverfilters/ + GitHub: https://github.com/GyverLibs/GyverFilters + - GFilterRA - компактная альтернатива фильтра экспоненциальное бегущее среднее (Running Average) + - GMedian3 - быстрый медианный фильтр 3-го порядка (отсекает выбросы) + - GMedian - медианный фильтр N-го порядка. Порядок настраивается в GyverFilters.h - MEDIAN_FILTER_SIZE + - GABfilter - альфа-бета фильтр (разновидность Калмана для одномерного случая) + - GKalman - упрощённый Калман для одномерного случая (на мой взгляд лучший из фильтров) + - GLinear - линейная аппроксимация методом наименьших квадратов для двух массивов + - FastFilter - быстрый целочисленный экспоненциальный фильтр + - RingAverage - бегущее среднее с кольцевым буфером + + AlexGyver, alex@alexgyver.ru + https://alexgyver.ru/ + MIT License + + Версии: + v1.6 от 12.11.2019 + v1.7: исправлен GLinear + v1.8: небольшие улучшения + v2.0: + - Улучшен и исправлен median и median3 + - Улучшен linear + - Смотрите примеры! Использование этих фильтров чуть изменилось + v2.1: Исправлен расчёт дельты в линейном фильтре + v2.2: Исправлена ошибка компиляции + v3.0: Добавлен FastFilter и RingAverage +*/ + +#ifndef GyverFilters_h +#define GyverFilters_h +#include +#include +#include +#include +#include +#include +#include +#include + + +#endif \ No newline at end of file diff --git a/src/filters/FastFilter.h b/src/filters/FastFilter.h new file mode 100644 index 0000000..5166963 --- /dev/null +++ b/src/filters/FastFilter.h @@ -0,0 +1,86 @@ +// быстрый целочисленный экспоненциальный фильтр + +#ifndef FastFilter_h +#define FastFilter_h +#include + +#define FF_PASS_MAX 1 +#define FF_PASS_MIN 2 + +class FastFilter { +public: + // коэффициент 0-31 + FastFilter(byte k = 20, int dt = 0) { + setK(k); + setDt(dt); + } + + // коэффициент 0-31 + void setK(byte k) { + _k1 = k; + _k2 = 32 - k; + } + + // установить период фильтрации + void setDt(int dt) { + _dt = dt; + } + + // установить режим пропуска (FF_PASS_MAX / FF_PASS_MIN) + void setPass(byte pass) { + _pass = pass; + } + + // установить исходное значение для фильтрации + void setRaw(int raw) { + _raw = raw; + } + + // установить фильтрованное значение + void setFil(int fil) { + _raw_f = fil; + } + + // проверка на переполнение + bool checkPass(int val) { + if (_pass == FF_PASS_MAX && val > _raw_f) { + _raw_f = val; + return 1; + } else if (_pass == FF_PASS_MIN && val < _raw_f) { + _raw_f = val; + return 1; + } + return 0; + } + + // расчёт по таймеру + void compute() { + if (_dt == 0 || millis() - _tmr >= _dt) { + _tmr = millis(); + computeNow(); + } + } + + // произвести расчёт сейчас + void computeNow() { + _raw_f = (_k1 * _raw_f + _k2 * _raw) >> 5; + } + + // получить фильтрованное значение + long getFil() { + return _raw_f; + } + + // получить последнее сырое значение + long getRaw() { + return _raw; + } + +private: + uint32_t _tmr = 0; + int _dt = 0; + byte _k1 = 20, _k2 = 12; + byte _pass = 0; + int _raw_f = 0, _raw = 0; +}; +#endif \ No newline at end of file diff --git a/src/filters/RingAverage.h b/src/filters/RingAverage.h new file mode 100644 index 0000000..e085cb2 --- /dev/null +++ b/src/filters/RingAverage.h @@ -0,0 +1,33 @@ +// бегущее среднее с кольцевым буфером + +#ifndef RingAverage_h +#define RingAverage_h +#include + +template < typename TYPE, int SIZE > +class RingAverage { +public: + RingAverage() { + for (int i = 0; i < SIZE; i++) buf[i] = 0; + } + TYPE filtered(TYPE val) { + if (++t >= SIZE) t = 0; // перемотка t + sum -= buf[t]; // вычитаем старое + sum += val; // прибавляем новое + buf[t] = val; // запоминаем в массив + return (sum / SIZE); + } + float filteredFloat(TYPE val) { + if (++t >= SIZE) t = 0; // перемотка t + sum -= buf[t]; // вычитаем старое + sum += val; // прибавляем новое + buf[t] = val; // запоминаем в массив + return ((float)sum / SIZE); + } + +private: + TYPE buf[SIZE]; + int32_t sum = 0; + int t = 0; +}; +#endif \ No newline at end of file diff --git a/src/filters/alfaBeta.h b/src/filters/alfaBeta.h new file mode 100644 index 0000000..12a4cc4 --- /dev/null +++ b/src/filters/alfaBeta.h @@ -0,0 +1,40 @@ +// альфа-бета фильтр + +#ifndef GABfilter_h +#define GABfilter_h +#include + +class GABfilter { +public: + // период дискретизации (измерений), process variation, noise variation + GABfilter(float delta, float sigma_process, float sigma_noise) {setParameters(delta, sigma_process, sigma_noise);} + + // период дискретизации (измерений), process variation, noise variation + void setParameters(float delta, float sigma_process, float sigma_noise) { + dt = delta; + float lambda = (float)sigma_process * dt * dt / sigma_noise; + float r = (4 + lambda - (float)sqrt(8 * lambda + lambda * lambda)) / 4; + a = (float)1 - r * r; + b = (float)2 * (2 - a) - 4 * (float)sqrt(1 - a); + } + + // возвращает фильтрованное значение + float filtered(float value) { + xm = value; + xk = xk_1 + ((float) vk_1 * dt ); + vk = vk_1; + rk = xm - xk; + xk += (float)a * rk; + vk += (float)( b * rk ) / dt; + xk_1 = xk; + vk_1 = vk; + return xk_1; + } + +private: + float dt; + float xk_1, vk_1, a, b; + float xk, vk, rk; + float xm; +}; +#endif \ No newline at end of file diff --git a/src/filters/kalman.h b/src/filters/kalman.h new file mode 100644 index 0000000..26766ac --- /dev/null +++ b/src/filters/kalman.h @@ -0,0 +1,41 @@ +// упрощённый Калман для одномерного случая + +#ifndef GKalman_h +#define GKalman_h +#include + +class GKalman { +public: + // разброс измерения, разброс оценки, скорость изменения значений + GKalman(float mea_e, float est_e, float q) { setParameters(mea_e, est_e, q); } + + // разброс измерения, скорость изменения значений (разброс измерения принимается равным разбросу оценки) + GKalman(float mea_e, float q) {GKalman::setParameters(mea_e, mea_e, q);} + + // разброс измерения, разброс оценки, скорость изменения значений + void setParameters(float mea_e, float est_e, float q) { + _err_measure = mea_e; + _err_estimate = est_e; + _q = q; + } + + // разброс измерения, скорость изменения значений (разброс измерения принимается равным разбросу оценки) + void setParameters(float mea_e, float q) {setParameters(mea_e, mea_e, q);} + + // возвращает фильтрованное значение + float filtered(float value) { + float _kalman_gain, _current_estimate; + _kalman_gain = _err_estimate / (_err_estimate + _err_measure); + _current_estimate = _last_estimate + _kalman_gain * (value - _last_estimate); + _err_estimate = (1.0 - _kalman_gain)*_err_estimate + fabs(_last_estimate-_current_estimate)*_q; + _last_estimate=_current_estimate; + return _current_estimate; + } + +private: + float _err_measure = 0.0; + float _err_estimate = 0.0; + float _q = 0.0; + float _last_estimate = 0.0; +}; +#endif \ No newline at end of file diff --git a/src/filters/linear.h b/src/filters/linear.h new file mode 100644 index 0000000..814dd1b --- /dev/null +++ b/src/filters/linear.h @@ -0,0 +1,50 @@ +// линейная аппроксимация методом наименьших квадратов + +#ifndef GLinear_h +#define GLinear_h +#include + +template < typename TYPE > +class GLinear { +public: + GLinear(){}; + void compute(TYPE *x_array, TYPE *y_array, int arrSize) { // аппроксимировать + int32_t sumX = 0, sumY = 0, sumX2 = 0, sumXY = 0; + for (int i = 0; i < arrSize; i++) { // для всех элементов массива + sumX += x_array[i]; + sumY += (long)y_array[i]; + sumX2 += x_array[i] * x_array[i]; + sumXY += (long)y_array[i] * x_array[i]; + } + a = (long)arrSize * sumXY - (long)sumX * sumY; // расчёт коэффициента наклона приямой + a = (float)a / (arrSize * sumX2 - sumX * sumX); + b = (float)(sumY - (float)a * sumX) / arrSize; + delta = a * (x_array[arrSize-1] - x_array[0]); // расчёт изменения + } + float getA() {return a;} // получить коэффициент А + float getB() {return b;} // получить коэффициент В + float getDelta() {return delta;} // получить аппроксимированное изменение + +private: + float a, b, delta; +}; + +/* + Сам алгоритм выглядит так: + void loop() { + sumX = 0; + sumY = 0; + sumX2 = 0; + sumXY = 0; + for (int i = 0; i < steps; i++) { + sumX += X[i]; + sumY += Y[i]; + sumX2 += X[i] * X[i]; + sumXY += X[i] * Y[i]; + } + a = (steps * sumXY - sumX * sumY) / (steps * sumX2 - sumX * sumX); + b = (sumY - a * sumX) / steps; + int delta = steps * a; + } +*/ +#endif \ No newline at end of file diff --git a/src/filters/median.h b/src/filters/median.h new file mode 100644 index 0000000..17a297f --- /dev/null +++ b/src/filters/median.h @@ -0,0 +1,39 @@ +// медианный фильтр N-го порядка + +#ifndef GMedian_h +#define GMedian_h +#include + +template < int SIZE, typename TYPE > +class GMedian { +public: + TYPE filtered(TYPE newVal) { + buffer[_count] = newVal; + if ((_count < _numRead - 1) && (buffer[_count] > buffer[_count + 1])) { + for (int i = _count; i < _numRead - 1; i++) { + if (buffer[i] > buffer[i + 1]) { + TYPE buff = buffer[i]; + buffer[i] = buffer[i + 1]; + buffer[i + 1] = buff; + } + } + } else { + if ((_count > 0) and (buffer[_count - 1] > buffer[_count])) { + for (int i = _count; i > 0; i--) { + if (buffer[i] < buffer[i - 1]) { + TYPE buff = buffer[i]; + buffer[i] = buffer[i - 1]; + buffer[i - 1] = buff; + } + } + } + } + if (++_count >= _numRead) _count = 0; + return buffer[(int)_numRead / 2]; + } +private: + TYPE buffer[SIZE]; + byte _count = 0; + byte _numRead = SIZE; +}; +#endif \ No newline at end of file diff --git a/src/filters/median3.h b/src/filters/median3.h new file mode 100644 index 0000000..f7a5d6f --- /dev/null +++ b/src/filters/median3.h @@ -0,0 +1,34 @@ +// быстрый медианный фильтр 3-го порядка + +#ifndef GMedian3_h +#define GMedian3_h +#include + +template < typename TYPE > +class GMedian3 { +public: + TYPE filtered(TYPE value) { // возвращает фильтрованное значение + buffer[_counter] = value; + if (++_counter > 2) _counter = 0; + + TYPE middle; + + if ((buffer[0] <= buffer[1]) && (buffer[0] <= buffer[2])) { + middle = (buffer[1] <= buffer[2]) ? buffer[1] : buffer[2]; + } + else { + if ((buffer[1] <= buffer[0]) && (buffer[1] <= buffer[2])) { + middle = (buffer[0] <= buffer[2]) ? buffer[0] : buffer[2]; + } + else { + middle = (buffer[0] <= buffer[1]) ? buffer[0] : buffer[1]; + } + } + return middle; + } + +private: + TYPE buffer[3]; + uint8_t _counter = 0; +}; +#endif \ No newline at end of file diff --git a/src/filters/runningAverage.cpp b/src/filters/runningAverage.cpp new file mode 100644 index 0000000..c6134bc --- /dev/null +++ b/src/filters/runningAverage.cpp @@ -0,0 +1,45 @@ +#include + +GFilterRA::GFilterRA() {} + +GFilterRA::GFilterRA(float coef, uint16_t interval) { + _coef = coef; + _filterInterval = interval; +} + +GFilterRA::GFilterRA(float coef) { + _coef = coef; +} + +void GFilterRA::setCoef(float coef) { + _coef = coef; +} +void GFilterRA::setStep(uint16_t interval) { + _filterInterval = interval; +} + +float GFilterRA::filteredTime(int16_t value) { + if (millis() - _filterTimer >= _filterInterval) { + _filterTimer = millis(); + filtered(value); + } + return _lastValue; +} + +float GFilterRA::filteredTime(float value) { + if (millis() - _filterTimer >= _filterInterval) { + _filterTimer = millis(); + filtered(value); + } + return _lastValue; +} + +float GFilterRA::filtered(int16_t value) { + _lastValue += (float)(value - _lastValue) * _coef; + return _lastValue; +} + +float GFilterRA::filtered(float value) { + _lastValue += (float)(value - _lastValue) * _coef; + return _lastValue; +} \ No newline at end of file diff --git a/src/filters/runningAverage.h b/src/filters/runningAverage.h new file mode 100644 index 0000000..d5177ef --- /dev/null +++ b/src/filters/runningAverage.h @@ -0,0 +1,26 @@ +// экспоненциальное бегущее среднее + +#ifndef GFilterRA_h +#define GFilterRA_h +#include + +class GFilterRA { +public: + GFilterRA(); // инициализация фильтра + GFilterRA(float coef); // расширенная инициализация фильтра (коэффициент) + GFilterRA(float coef, uint16_t interval); // расширенная инициализация фильтра (коэффициент, шаг фильтрации) + void setCoef(float coef); // настройка коэффициента фильтрации (0.00 - 1.00). Чем меньше, тем плавнее + void setStep(uint16_t interval); // установка шага фильтрации (мс). Чем меньше, тем резче фильтр + + float filteredTime(int16_t value); // возвращает фильтрованное значение с опорой на встроенный таймер + float filtered(int16_t value); // возвращает фильтрованное значение + + float filteredTime(float value); // возвращает фильтрованное значение с опорой на встроенный таймер + float filtered(float value); // возвращает фильтрованное значение + +private: + float _coef = 0.0, _lastValue = 0.0; + uint32_t _filterTimer = 0; + uint16_t _filterInterval = 0; +}; +#endif \ No newline at end of file