Browse Source

Собрал в кучку полезную инфу по АЦП

Vladimir N. Shilov 9 năm trước cách đây
mục cha
commit
8f179826ff
3 tập tin đã thay đổi với 196 bổ sung0 xóa
  1. 0 0
      adc/adc_calibrate.txt
  2. 170 0
      adc/adc_filter.txt
  3. 26 0
      adc/adc_vref.txt

+ 0 - 0
adc.txt → adc/adc_calibrate.txt


+ 170 - 0
adc/adc_filter.txt

@@ -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
+

+ 26 - 0
adc/adc_vref.txt

@@ -0,0 +1,26 @@
+http://we.easyelectronics.ru/GYUR22/stm32-adcdmaitcusrednenie.html#comment94893
+
+ ADCout (uint) — уже откалиброваные данные (которые надо получить)
+ ADCin (uint)- сырые данные
+ ADCref (uint)- измеренный внутренний референс. Он на 17м канале сидит вроде бы.
+
+ А дальше следим за руками:
+ 1. ADCin = (Vin/Vdd)*4095 (измерение относительно питания, 12 бит)
+ 2. ADCref = (Vref/Vdd)*4095 (Vref смотрим в даташите, не помню сколько) (1489)
+ 3. Выведем Vin:
+ Vin = Vref*ADCin/ADCref
+ 4. Но это бесполезные в целочисленной арифметике вольты. А нам нужны попугаи:
+ ADCout = (Vin/Vdd) * 4095 (эмулируем АЦП )))) )
+ ADCout = (ADCin * (Vref*4095/Vdd) )/ ADCref
+ 5. Обозначим K = Vref*4095/Vdd (коэффициент типа uint)
+ 6. Итоговый вариант:
+ ADCout = ((u32)ADCin * K)/ADCref;
+ K — какой коэффициентугодно можно. Но для красоты и предсказуемости данных я его высчитываю относительно номинального напряжения питания.
+
+ Как уже было подмечено, калибровку делаем после каждого измерения. А потом хоть заусредняйтесь.
+ Еще момент (было в сообществе кстати), на точность измерения очень сильно влияет сэмплинг тайм. На ваш вкус. Но для рефа мы делаем не менее 28.5 циклов тактирования АЦП.
+ Как-то так
+---
+http://www.robobuild.ru/index.php?itid=4
+---
+http://microtechnics.ru/stm32-uchebnyj-kurs-adc-acp/