|
@@ -0,0 +1,170 @@
|
|
|
+http://www.elcojacobs.com/eleminating-noise-from-sensor-readings-on-arduino-with-digital-filtering/
|
|
|
+
|
|
|
+выжимки из статьи
|
|
|
+
|
|
|
+Медианный фильтр:
|
|
|
+сортируем массив значений (например 100) от меньшего к большему (?) и берём среднее.
|
|
|
+
|
|
|
+автор предлагает скомбинировать медианный фильтр и фильтрацию по среднему:
|
|
|
+сортируем массив значений, берём 10 (из 100) средних и из них считаем среднее.
|
|
|
+
|
|
|
+The final code to read one sensor:
|
|
|
+
|
|
|
+#define NUM_READS 100
|
|
|
+float readTemperature(int sensorpin){
|
|
|
+ // read multiple values and sort them to take the mode
|
|
|
+ int sortedValues[NUM_READS];
|
|
|
+ for(int i=0;i<NUM_READS;i++){
|
|
|
+ int value = analogRead(sensorpin);
|
|
|
+ int j;
|
|
|
+ if(value<sortedValues[0] || i==0){
|
|
|
+ j=0; //insert at first position
|
|
|
+ }
|
|
|
+ else{
|
|
|
+ for(j=1;j<i;j++){
|
|
|
+ if(sortedValues[j-1]<=value && sortedValues[j]>=value){
|
|
|
+ // j is insert position
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ for(int k=i;k>j;k--){
|
|
|
+ // move all values higher than current reading up one position
|
|
|
+ sortedValues[k]=sortedValues[k-1];
|
|
|
+ }
|
|
|
+ sortedValues[j]=value; //insert current reading
|
|
|
+ }
|
|
|
+ //return scaled mode of 10 values
|
|
|
+ float returnval = 0;
|
|
|
+ for(int i=NUM_READS/2-5;i<(NUM_READS/2+5);i++){
|
|
|
+ returnval +=sortedValues[i];
|
|
|
+ }
|
|
|
+ returnval = returnval/10;
|
|
|
+ return returnval*1100/1023;
|
|
|
+}
|
|
|
+
|
|
|
+-=-=-=-=-=-=-=-=-=-
|
|
|
+По своей сути, это похоже на алгоритм, которым я когда-то фильтровал значения
|
|
|
+от счётчика гейгера:
|
|
|
+ - считаем среднее в массиве измерений,
|
|
|
+ - отбираем из массива только те значения, которые укладываются в +-5% от
|
|
|
+ полученного среднего,
|
|
|
+ - берём среднее от оставшихся значений.
|
|
|
+кажется я просто занулял неподходящие данные, а потом не учитывал их при
|
|
|
+следующем усреднении.
|
|
|
+
|
|
|
+какой алгоритм точнее, меньше, быстрее -- хз. можно попробовать на Unicharger-е.
|
|
|
+-=-=-=-=-=-=-=-=-=-
|
|
|
+
|
|
|
+Addional filtering: IIR Butterworth low pass filters
|
|
|
+(I have changed the filters from second order to third order...)
|
|
|
+
|
|
|
+для медленно меняющихся процессов он предлагает использовать фильтр нижних
|
|
|
+частот Баттервота 4-го порядка.
|
|
|
+Фильтр Баттервота -- это "infinite impulse response filter" (IIR)
|
|
|
+
|
|
|
+(а теперь гугльтранслейт)
|
|
|
+Это означает, что каждое выходное значение фильтра рассчитывается из истории
|
|
|
+входных значений и предыдущих выходных значений фильтра.
|
|
|
+Поскольку выходной фильтр используется для вычисления будущих значений,
|
|
|
+влияние любого входного значения на будущие выходные -- есть регрессия к
|
|
|
+бесконечности.
|
|
|
+(конец)
|
|
|
+
|
|
|
+The implementation of a software Butterworth filter is actually very easy, see
|
|
|
+the code snippet below.
|
|
|
+
|
|
|
+void updateTemperatures(void){ //called every 200 milliseconds
|
|
|
+ fridgeTempFast[0] = fridgeTempFast[1];
|
|
|
+ fridgeTempFast[1] = fridgeTempFast[2];
|
|
|
+ fridgeTempFast[2] = fridgeTempFast[3];
|
|
|
+ fridgeTempFast[3] = readTemperature(fridgePin);
|
|
|
+
|
|
|
+ // Butterworth filter with cutoff frequency 0.033*sample frequency (FS=5Hz)
|
|
|
+ fridgeTempFiltFast[0] = fridgeTempFiltFast[1];
|
|
|
+ fridgeTempFiltFast[1] = fridgeTempFiltFast[2];
|
|
|
+ fridgeTempFiltFast[2] = fridgeTempFiltFast[3];
|
|
|
+ fridgeTempFiltFast[3] = (fridgeTempFast[0] + fridgeTempFast[3]
|
|
|
+ + 3 * (fridgeTempFast[1] + fridgeTempFast[2]) )
|
|
|
+ / 1.092799972e+03 + (0.6600489526 * fridgeTempFiltFast[0])
|
|
|
+ + (-2.2533982563 * fridgeTempFiltFast[1])
|
|
|
+ + ( 2.5860286592 * fridgeTempFiltFast[2]);
|
|
|
+
|
|
|
+ fridgeTemperatureActual = fridgeTempFiltFast[3];
|
|
|
+
|
|
|
+ beerTempFast[0] = beerTempFast[1];
|
|
|
+ beerTempFast[1] = beerTempFast[2];
|
|
|
+ beerTempFast[2] = beerTempFast[3];
|
|
|
+ beerTempFast[3] = readTemperature(beerPin);
|
|
|
+
|
|
|
+ // Butterworth filter with cutoff frequency 0.01*sample frequency (FS=5Hz)
|
|
|
+ beerTempFiltFast[0] = beerTempFiltFast[1];
|
|
|
+ beerTempFiltFast[1] = beerTempFiltFast[2];
|
|
|
+ beerTempFiltFast[2] = beerTempFiltFast[3];
|
|
|
+ beerTempFiltFast[3] = (beerTempFast[0] + beerTempFast[3]
|
|
|
+ + 3 * (beerTempFast[1] + beerTempFast[2]) )
|
|
|
+ / 3.430944333e+04 + (0.8818931306 * beerTempFiltFast[0])
|
|
|
+ + (-2.7564831952 * beerTempFiltFast[1])
|
|
|
+ + ( 2.8743568927 * beerTempFiltFast[2]);
|
|
|
+
|
|
|
+ beerTemperatureActual = beerTempFiltFast[3];
|
|
|
+}
|
|
|
+
|
|
|
+void updateSlowFilteredTemperatures(void){ //called every 10 seconds
|
|
|
+ // Input for filter
|
|
|
+ fridgeTempSlow[0] = fridgeTempSlow[1];
|
|
|
+ fridgeTempSlow[1] = fridgeTempSlow[2];
|
|
|
+ fridgeTempSlow[2] = fridgeTempSlow[3];
|
|
|
+ fridgeTempSlow[3] = fridgeTempFiltFast[3];
|
|
|
+
|
|
|
+ // Butterworth filter with cutoff frequency 0.01*sample frequency (FS=0.1Hz)
|
|
|
+ fridgeTempFiltSlow[0] = fridgeTempFiltSlow[1];
|
|
|
+ fridgeTempFiltSlow[1] = fridgeTempFiltSlow[2];
|
|
|
+ fridgeTempFiltSlow[2] = fridgeTempFiltSlow[3];
|
|
|
+ fridgeTempFiltSlow[3] = (fridgeTempSlow[0] + fridgeTempSlow[3]
|
|
|
+ + 3 * (fridgeTempSlow[1] + fridgeTempSlow[2]) )
|
|
|
+ / 3.430944333e+04 + (0.8818931306 * fridgeTempFiltSlow[0])
|
|
|
+ + (-2.7564831952 * fridgeTempFiltSlow[1])
|
|
|
+ + ( 2.8743568927 * fridgeTempFiltSlow[2]);
|
|
|
+
|
|
|
+ beerTempSlow[0] = beerTempSlow[1];
|
|
|
+ beerTempSlow[1] = beerTempSlow[2];
|
|
|
+ beerTempSlow[2] = beerTempSlow[3];
|
|
|
+ beerTempSlow[3] = beerTempFiltFast[3];
|
|
|
+
|
|
|
+ // Butterworth filter with cutoff frequency 0.01*sample frequency (FS=0.1Hz)
|
|
|
+ beerTempFiltSlow[0] = beerTempFiltSlow[1];
|
|
|
+ beerTempFiltSlow[1] = beerTempFiltSlow[2];
|
|
|
+ beerTempFiltSlow[2] = beerTempFiltSlow[3];
|
|
|
+ beerTempFiltSlow[3] = (beerTempSlow[0] + beerTempSlow[3]
|
|
|
+ + 3 * (beerTempSlow[1] + beerTempSlow[2]) )
|
|
|
+ / 3.430944333e+04 + (0.8818931306 * beerTempFiltSlow[0])
|
|
|
+ + (-2.7564831952 * beerTempFiltSlow[1])
|
|
|
+ + ( 2.8743568927 * beerTempFiltSlow[2]);
|
|
|
+}
|
|
|
+
|
|
|
+(это пиздец, считать на 8-мибитках такую вещественную арифметику...)
|
|
|
+
|
|
|
+Как видите, здесь использовано 2 быстрых и 2 медленных фильтра. Быстрый фильтр
|
|
|
+обновляется 5 раз в секунду и имеет угловые частоты 0.165 Гц и 0.05 Гц.
|
|
|
+Медленный фильтр обновляется раз в 10 сек и имеет угловую частоту 0.001 Гц.
|
|
|
+Медленный фильтр получает данные от быстрого.
|
|
|
+
|
|
|
+Почему быстрый и медленный фильтр?
|
|
|
+Каждый причинный фильтр (фильтр который не может заглянуть в будущее) вызывает
|
|
|
+задержку между входом и выходом: если брать среднее от К значений,
|
|
|
+потребуется К обновлений выхода чтобы полностью увидеть именения на входе.
|
|
|
+
|
|
|
+Каждый IIR low pass filter получает среднее от бесконечности предыдущих
|
|
|
+значений, но с уменьшающим множителем для более старых входных значений.
|
|
|
+Фильтр с меньшей угловой частотой усредняет больше значений и поэтому вызывает
|
|
|
+большую задержку.
|
|
|
+
|
|
|
+Дальше переводить особого смысла нет.
|
|
|
+Вобщем автора нужда заставила использовать два фильтра с разной угловой
|
|
|
+частотой.
|
|
|
+-=-=-=-=-
|
|
|
+
|
|
|
+Коэффициенты фильтра можно посчитать здесь:
|
|
|
+http://www-users.cs.york.ac.uk/~fisher/mkfilter/trad.html
|
|
|
+
|