Швидкий обчислювач середньої швидкості та ефективної пам'яті


33

Я шукаю ефективне для часу та пам’яті рішення, щоб обчислити ковзну середню в C. Мені потрібно уникати поділу, оскільки я перебуваю на PIC 16, у якому немає виділеного підрозділу.

На даний момент я просто зберігаю всі значення в буфері кільця і просто зберігаю та оновлюю суму щоразу, коли надходить нове значення. Це дійсно ефективно, але, на жаль, використовує більшу частину моєї доступної пам'яті ...


3
Я не думаю, що існує більш просторовий спосіб зробити це.
Rocketmagnet

4
@JobyTaffey добре, це досить широко використовуваний алгоритм в системах управління, і він вимагає роботи з обмеженими апаратними ресурсами. Тож я думаю, що він тут знайде більше допомоги, ніж на SO.
clabacchio

3
@Joby: Є деякі зморшки щодо цього питання, які стосуються невеликих систем з обмеженими ресурсами. Дивіться мою відповідь. Ви б робили це зовсім по-різному у великій системі, як звичайно, люди з SO. Це багато втілилося в моєму досвіді проектування електроніки.
Олін Латроп

1
Я згоден. Це цілком доречно для цього форуму, оскільки стосується вбудованих систем.
Rocketmagnet

Я відкликаю своє заперечення
Тобі Джаффі

Відповіді:


55

Як уже згадували інші, вам слід розглянути фільтр IIR (нескінченний імпульсний відгук), а не фільтр FIR (скінченна імпульсна відповідь), який ви використовуєте зараз. Тут є більше, але на перший погляд фільтри FIR реалізуються як явні згортки та фільтри IIR з рівняннями.

Конкретний IIR фільтр, який я багато використовую в мікроконтролерах, - це однополюсний фільтр низьких частот. Це цифровий еквівалент простого RC-аналогового фільтра. Для більшості застосунків вони матимуть кращі характеристики, ніж коробчастий фільтр, який ви використовуєте. Більшість застосувань коробкового фільтра, які я стикався, є результатом того, що хтось не звертає уваги в класі цифрової обробки сигналів, а не внаслідок необхідності їх конкретних характеристик. Якщо ви просто хочете ослабити високі частоти, про які ви знаєте, шум, краще однополюсний низькочастотний фільтр. Найкращий спосіб впровадити один цифровий мікроконтролер:

FILT <- FILT + FF (НОВО - FILT)

FILT - шматок стійкого стану. Це єдина стійка змінна, яка вам потрібна для обчислення цього фільтра. NEW - нове значення, яке фільтр оновлюється за допомогою цієї ітерації. FF - фільтрова фракція , яка регулює «важкість» фільтра. Подивіться на цей алгоритм і побачите, що для FF = 0 фільтр нескінченно важкий, оскільки вихід ніколи не змінюється. Для FF = 1, це насправді зовсім не фільтр, оскільки вихід просто слідує за входом. Корисні значення знаходяться між ними. У невеликих системах ви вибираєте FF на рівні 1/2 Nтак що множення на FF може бути здійснено як правильний зсув на N біт. Наприклад, FF може бути 1/16, а множення на FF, отже, правильний зсув 4 біта. В іншому випадку для цього фільтра потрібно лише одне віднімання та одне додавання, хоча цифри зазвичай мають бути ширшими за вхідне значення (докладніше про числову точність в окремому розділі нижче).

Зазвичай я приймаю показання A / D значно швидше, ніж вони потрібні, і застосовую два з цих фільтрів каскадно. Це цифровий еквівалент двох RC-фільтрів послідовно і ослаблюється на 12 дБ / октаву вище частоти відкатки. Однак для читання A / D зазвичай більш релевантно дивитися на фільтр у часовій області, враховуючи його ступінчату відповідь. Це говорить про те, наскільки швидко ваша система побачить зміни, коли те, що ви вимірюєте, зміниться.

Для полегшення проектування цих фільтрів (що означає лише вибір FF та визначення кількості їх каскаду), я використовую свою програму FILTBITS. Ви визначаєте кількість бітів зсуву для кожного FF у каскадній серії фільтрів, і він обчислює ступінчасту відповідь та інші значення. Насправді я зазвичай запускаю це за допомогою мого сценарію обгортки PLOTFILT. Це запускає FILTBITS, який створює файл CSV, а потім малює файл CSV. Наприклад, ось результат "PLOTFILT 4 4":

Два параметри для PLOTFILT означають, що буде два фільтри, каскадні типу описаного вище. Значення 4 вказують на кількість бітів зсуву для реалізації множення на FF. Отже, два значення FF в цьому випадку становлять 1/16.

Червоний слід - це відповідь одиничного кроку, і це головне, на що слід звернути увагу. Наприклад, це говорить про те, що якщо вхід зміниться миттєво, вихід комбінованого фільтра осяде до 90% від нового значення за 60 ітерацій. Якщо вам важливо 95% часу відстоювання, тоді вам доведеться почекати приблизно 73 ітерації, а для 50% часу відстеження - лише 26 ітерацій.

Зелений слід показує вам вихід з одного шипа повного амплітуди. Це дає вам деяке уявлення про випадкове придушення шуму. Схоже, що жоден зразок не спричинить більше 2,5% зміни у виході.

Синій слід - це дати суб'єктивне відчуття того, що робить цей фільтр із білим шумом. Це не суворий тест, оскільки немає гарантії, який саме вміст був випадкових чисел, вибраних як вхід білого шуму для цього пробігу PLOTFILT. Це лише дати тобі грубе відчуття того, наскільки воно буде розчавлене і наскільки воно гладке.

PLOTFILT, можливо, FILTBITS та багато інших корисних речей, особливо для розробки програмного забезпечення PIC доступні у випуску програмного забезпечення PIC Tools на моїй сторінці завантаження програмного забезпечення .

Додано про числову точність

Я бачу з коментарів і тепер нову відповідь, що є інтерес до обговорення кількості бітів, необхідних для впровадження цього фільтра. Зауважте, що множення на FF створить Log 2 (FF) нові біти нижче бінарної точки. У малих системах FF зазвичай вибирається рівним 1/2 N, так що це множення реально реалізується правильним зсувом N біт.

Тому FILT зазвичай є цілим числом фіксованої точки. Зауважте, що це не змінює жодної математики з точки зору процесора. Наприклад, якщо ви фільтруєте 10-бітові читання A / D і N = 4 (FF = 1/16), тоді вам знадобляться 4 дроби фракції нижче 10-бітових цілих чисел A / D. Один з найбільш процесорів, ви б виконували 16-бітові цілочисельні операції через 10-бітових A / D показань. У цьому випадку ви все ще можете виконати абсолютно ті ж 16-бітові цілочисельні операції, але почніть з зчитування A / D зліва, зміщеного на 4 біти. Процесор не знає різниці і не потребує цього. Виконання математики на цілих 16 бітових цілих числах працює, чи вважаєте ви їх 12,4 фіксованою точкою або справжніми 16-бітовими цілими числами (16,0 фіксованою точкою).

Взагалі вам потрібно додати N біт на кожен полюс фільтра, якщо ви не хочете додавати шум через числове представлення. У наведеному вище прикладі, другий фільтр з двох повинен мати 10 + 4 + 4 = 18 біт, щоб не втратити інформацію. На практиці на 8-бітовій машині, що означає, що ви використовуєте 24-бітні значення. Технічно лише другий полюс з двох потребує ширшого значення, але для простоти вбудованого програмного забезпечення я зазвичай використовую те саме представлення, і тим самим той самий код, для всіх полюсів фільтра.

Зазвичай я пишу підпрограму або макрос для виконання однієї операції зі стовпом фільтра, а потім застосовую її до кожного полюса. Будь підпрограма чи макрос залежить від того, чи важливіші цикли чи пам'ять програми у цьому конкретному проекті. Так чи інакше, я використовую деякий стан подряпин, щоб передати NEW в підпрограму / макрос, який оновлює FILT, але також завантажує його в той самий стан подряпини NEW. Це полегшує застосування декількох полюсів, оскільки оновлений FILT одного полюса є НОВИЙ наступний. У підпрограмі корисно мати вказівник на FILT на шляху, який оновлюється відразу після FILT на виході. Таким чином підпрограма автоматично працює на послідовних фільтрах у пам'яті, якщо їх викликає кілька разів. З макросом вам не потрібен вказівник, оскільки ви передаєте адресу для роботи над кожною ітерацією.

Приклади коду

Ось приклад макросу, як описано вище для PIC 18:

////////////////////////////////////////////////// //////////////////////////////
//
// Фільтр макросів FILTER
//
// Оновіть один полюс фільтра новим значенням у NEWVAL. NEWVAL оновлено до
// містять нове відфільтроване значення.
//
// FILT - назва змінної стану фільтра. Передбачається, що це 24 біта
// широкий і в місцевому банку.
//
// Формула оновлення фільтра:
//
// FILT <- FILT + FF (NEWVAL - FILT)
//
// Множення на FF здійснюється правильним зсувом бітів FILTBITS.
//
/ макрофільтр
  / писати
         dbankif lbankadr
         movf [arg 1] +0, w; NEWVAL <- NEWVAL - FILT
         subwf newval + 0
         movf [arg 1] +1, ж
         subwfb newval + 1
         movf [arg 1] +2, ш
         subwfb newval + 2

  / писати
  / loop n filtbits; один раз за кожен біт зміщувати NEWVAL вправо
         rlcf newval + 2, w; зсунути NEWVAL вправо на один біт
         rrcf newval + 2
         rrcf newval + 1
         rrcf newval + 0
    / endloop

  / писати
         movf newval + 0, w; додайте зміщене значення у фільтр та збережіть у NEWVAL
         addwf [arg 1] +0, ш
         movwf [arg 1] +0
         movwf newval + 0

         movf newval + 1, w
         addwfc [arg 1] +1, ш
         movwf [arg 1] +1
         movwf newval + 1

         movf newval + 2, ш
         addwfc [arg 1] +2, ш
         movwf [arg 1] +2
         movwf newval + 2
  / endmac

І ось подібний макрос для PIC 24 або dsPIC 30 або 33:

////////////////////////////////////////////////// //////////////////////////////
//
// Макрос FILTER ffbits
//
// Оновлення стану одного фільтра низьких частот. Нове вхідне значення знаходиться у W1: W0
// і стан фільтра, який потрібно оновити, вказується на W2.
//
// Оновлене значення фільтра також буде повернуто у W1: W0 і W2 вкажуть
// до першої пам'яті минулого стану фільтра. Таким макросом може бути такий
// посилається послідовно оновлювати серію каскадних фільтрів низьких частот.
//
// Формула фільтра:
//
// FILT <- FILT + FF (NEW - FILT)
//
// де множення на FF виконується арифметичним зсувом правої частини
// FFBITS.
//
// ПОПЕРЕДЖЕННЯ: W3 скидається.
//
/ макрофільтр
  / var new ffbits integer = [arg 1]; отримайте кількість бітів для зсуву

  / писати
  / write "; Виконайте однополюсну фільтрацію низьких частот, біт shift =" ffbits
  / написати ";"

         sub w0, [w2 ++], w0; НОВО - ФІЛЬТ -> W1: W0
         subb w1, [w2--], w1

         lsr w0, # [v ffbits], w0; змістіть результат на W1: W0 вправо
         sl w1, # [- 16 ffbits], w3
         ior w0, w3, w0
         asr w1, # [v ffbits], w1

         додайте w0, [w2 ++], w0; додайте FILT, щоб отримати остаточний результат у W1: W0
         addc w1, [w2--], w1

         mov w0, [w2 ++]; результат запису до стану фільтра, вказівник заздалегідь
         mov w1, [w2 ++]

  / писати
  / endmac

Обидва ці приклади реалізовані у вигляді макросів за допомогою мого препроцесора асемблеру PIC , який є більш здатним, ніж будь-який із вбудованих макро-засобів.


1
+1 - прямо на гроші. Єдине, що я хотів би додати, - це те, що фільтри ковзних середніх мають своє місце при синхронному виконанні якоїсь задачі (наприклад, створення форми хвилі приводу для керування ультразвуковим генератором), щоб вони фільтрували гармоніки 1 / T, де T рухається середній час.
Джейсон S

2
Гарна відповідь, але лише дві речі. По-перше: це не обов'язково відсутність уваги, що призводить до вибору неправильного фільтра; у моєму випадку мене ніколи не вчили про різницю, і те саме стосується людей, які не закінчили навчання. Тому іноді це просто незнання. Але друге: чому ти каскадуєш два цифрові фільтри першого порядку замість одного вищого порядку? (просто для розуміння, я не критикую)
clabacchio

3
два каскадні однополюсні фільтри IIR є більш надійними для чисельних питань та простішими у проектуванні, ніж один фільтр IIR ІІ-го порядку; компроміс полягає в тому, що з 2-х каскадних ступенів ви отримуєте низький Q (= 1/2?) фільтр, але в більшості випадків це не велика справа.
Джейсон S

1
@clabacchio: Ще одне питання, яке я мав би згадати, - це впровадження вбудованого програмного забезпечення. Ви можете написати однополюсну підпрограму фільтра низьких частот один раз, а потім застосувати її кілька разів. Насправді я зазвичай пишу таку підпрограму, щоб перенести покажчик у пам'яті на стан фільтра, а потім попросити її вказувати, щоб її можна було викликати послідовно, щоб легко реалізувати багатополюсні фільтри.
Олін Латроп

1
1. дуже дякую за ваші відповіді - всі вони. Я вирішив використовувати цей фільтр IIR, але цей фільтр не використовується як стандартний фільтр низьких частот, оскільки мені потрібно середнє значення лічильника та порівняння їх для виявлення змін у певному діапазоні. Оскільки ці Ван вагомі мають дуже різні розміри залежно від Апаратного забезпечення, я хотів взяти середнє значення, щоб мати можливість автоматично реагувати на ці специфічні зміни.
sensslen

18

Якщо ви можете жити з обмеженням потужності двох предметів до середнього (тобто 2,4,8,16,32 і т.д.), то ділення можна легко та ефективно зробити на мікроефекти з низькою продуктивністю без спеціального поділу, оскільки це можна зробити як невеликий зсув. Кожен правий зсув - це одна сила двох, наприклад:

avg = sum >> 2; //divide by 2^2 (4)

або

avg = sum >> 3; //divide by 2^3 (8)

тощо.


як це допомагає? ОП заявляє, що головна проблема - це збереження в пам'яті минулих зразків.
Джейсон S

Це взагалі не стосується питання ОП.
Rocketmagnet

12
ОП вважав, що у нього є дві проблеми: розділення PIC16 і пам'ять для його буфера дзвінка. Ця відповідь показує, що поділ не є складним. Справді, вона не стосується проблеми пам'яті, але система SE дозволяє часткові відповіді, і користувачі можуть взяти щось із кожної відповіді для себе, або навіть редагувати та комбінувати відповіді інших. Оскільки деякі інші відповіді потребують операції розділення, вони аналогічно неповні, оскільки не показують, як ефективно досягти цього на PIC16.
Мартін

8

Там є відповідь на істинному фільтр змінного середнього ( так званий «вагон фільтра») з меншими вимогами до пам'яті, якщо ви не заперечуєте Субдіскретізація. Це називається каскадним інтегратор-гребінчастим фільтром (CIC). Ідея полягає в тому, що у вас є інтегратор, який ви відрізняєте за певний проміжок часу, а ключовим пристроєм збереження пам’яті є те, що при знижувальному розмірі вам не доведеться зберігати кожне значення інтегратора. Він може бути реалізований за допомогою наступного псевдокоду:

function out = filterInput(in)
{
   const int decimationFactor = /* 2 or 4 or 8 or whatever */;
   const int statesize = /* whatever */
   static int integrator = 0;
   static int downsample_count = 0;
   static int ringbuffer[statesize];
   // don't forget to initialize the ringbuffer somehow
   static int ringbuffer_ptr = 0;
   static int outstate = 0;

   integrator += in;
   if (++downsample_count >= decimationFactor)
   {
     int oldintegrator = ringbuffer[ringbuffer_ptr];
     ringbuffer[ringbuffer_ptr] = integrator;
     ringbuffer_ptr = (ringbuffer_ptr + 1) % statesize;
     outstate = (integrator - oldintegrator) / (statesize * decimationFactor);
   }
   return outstate;
}

Ваша ефективна ковзаюча середня довжина, decimationFactor*statesizeале вам потрібно тримати лише statesizeзразки. Очевидно, ви можете досягти кращої продуктивності, якщо ваші statesizeта decimationFactorє повноваженнями 2, так що оператори поділу та решти заміняться змінами та масками.


Постскрипт: Я згоден з Оліном, що ви завжди повинні враховувати прості фільтри IIR перед фільтром середньої середньої температури. Якщо вам не потрібні частотні нулі фільтра боксерського автомобіля, 1-полюсний або 2-полюсний низькочастотний фільтр, ймовірно, спрацює нормально.

З іншого боку, якщо ви фільтруєте з метою децимації (беручи вхід з високою швидкістю вибірки та усереднюючи його для використання з низькошвидкісним процесом), то фільтр CIC може бути саме тим, що ви шукаєте. (особливо якщо ви можете використовувати stateize = 1 і взагалі уникати рингбуфера лише з одним значенням попереднього інтегратора)


8

Існує деякий поглиблений аналіз математики з використанням фільтра IIR першого порядку, який Олін Летроп уже описав на обміні стека цифрової обробки сигналів (включає багато гарних зображень.) Рівняння цього фільтра IIR:

y [n] = αx [n] + (1 − α) y [n − 1]

Це може бути реалізовано за допомогою лише цілих чисел та без поділу за допомогою наступного коду (можливо, знадобиться деяка налагодження під час введення з пам'яті.

/**
*  @details    Implement a first order IIR filter to approximate a K sample 
*              moving average.  This function implements the equation:
*
*                  y[n] = alpha * x[n] + (1 - alpha) * y[n-1]
*
*  @param      *filter - a Signed 15.16 fixed-point value.
*  @param      sample - the 16-bit value of the current sample.
*/

#define BITS 2      ///< This is roughly = log2( 1 / alpha )

short IIR_Filter(long *filter, short sample)
{
    long local_sample = sample << 16;

    *filter += (local_sample - *filter) >> BITS;

    return (short)((*filter+0x8000) >> 16);     ///< Round by adding .5 and truncating.
}

Цей фільтр наближає ковзну середню останніх проб K, встановлюючи значення альфа-1 / K. Робіть це в попередньому коді, #defineING BITSдо LOG2 (К), тобто для K = 16 набору BITSдо 4, при К = 4 безлічі BITSдо 2, і т.д.

(Я перевірте наведений тут код, як тільки я отримаю зміни та відредагую цю відповідь, якщо потрібно.)


6

Ось однополюсний низькочастотний фільтр (середня ковзання, із частотою відсічення = CutoffFrequency). Дуже просто, дуже швидко, працює чудово, і майже немає пам'яті накладних витрат.

Примітка: Усі змінні мають сферу застосування поза функцією фільтра, за винятком переданих у newInput

// One-time calculations (can be pre-calculated at compile-time and loaded with constants)
DecayFactor = exp(-2.0 * PI * CutoffFrequency / SampleRate);
AmplitudeFactor = (1.0 - DecayFactor);

// Filter Loop Function ----- THIS IS IT -----
double Filter(double newInput)
{
   MovingAverage *= DecayFactor;
   MovingAverage += AmplitudeFactor * newInput;

   return (MovingAverage);
}

Примітка. Це одноступінчастий фільтр. Кілька ступенів можна каскадувати разом, щоб збільшити різкість фільтра. Якщо ви використовуєте більше одного етапу, вам доведеться відрегулювати DecayFactor (як це стосується частоти відключення) для компенсації.

І очевидно, що все, що вам потрібно, - це два рядки, розміщені в будь-якому місці, вони не потребують своєї функції. Цей фільтр має час нарощування до того, як середня середня представляє вхідний сигнал. Якщо вам потрібно обійти цей час нарощування, ви можете просто ініціалізувати MovingAverage до першого значення newInput замість 0, і сподіваємось, що перший newInput не є іншим.

(CutoffFrequency / SampleRate) має діапазон від 0 до 0,5. DecayFactor - це значення від 0 до 1, зазвичай близьке до 1.

Одноточні поплавці досить хороші для більшості речей, я просто віддаю перевагу парним. Якщо вам потрібно дотримуватися цілих чисел, ви можете перетворити коефіцієнт DecayFactor і Amplitude в дробові цілі числа, в яких чисельник зберігається як ціле число, а знаменник - ціла сила 2 (так що ви можете перемістити бік вправо як знаменник, а не ділитися під час циклу фільтра). Наприклад, якщо DecayFactor = 0,99, і ви хочете використовувати цілі числа, ви можете встановити DecayFactor = 0,99 * 65536 = 64881. І тоді, коли ви помножите на DecayFactor у своєму циклі фільтру, просто змістіть результат >> 16.

Для отримання додаткової інформації про це відмінна книга в Інтернеті, глава 19 про рекурсивні фільтри: http://www.dspguide.com/ch19.htm

PS Для парадигми Ковзаюче середнє, інший підхід до налаштування DecayFactor та AmplitudeFactor, який може бути більш відповідним вашим потребам, скажімо, ви хочете попередній, про 6 предметів, усереднених разом, роблячи це дискретно, ви б додали 6 елементів і розділили на 6, тож ви можете встановити AmplitudeFactor на 1/6, а DecayFactor на (1.0 - AmplitudeFactor).


4

Ви можете наблизити рухомий простір для деяких додатків за допомогою простого фільтра IIR.

вага - значення 0..255, високі значення = коротший часовий графік для авангарду

Значення = (нове значення * вага + значення * (256-вага)) / 256

Щоб уникнути помилок округлення, значення, як правило, буде довгим, з яких ви використовуєте лише байти вищого порядку як "фактичне" значення.


3

Всі інші детально прокоментували корисність IIR проти FIR та розподіл потужності двох. Я просто хотів би надати деякі деталі реалізації. Нижче добре працює на невеликих мікроконтролерах без FPU. Немає множення, і якщо тримати N потужністю два, весь поділ є одноциклічним бітовим зміщенням.

Основний кільцевий буфер FIR: зберігайте запущений буфер останніх N значень та запущений SUM всіх значень у буфері. Кожен раз, коли входить новий зразок, віднімайте найстарше значення в буфері від SUM, замінюйте його новим зразком, додайте новий зразок до SUM та виводите SUM / N.

unsigned int Filter(unsigned int sample){
    static unsigned int buffer[N];
    static unsigned char oldest = 0;
    static unsigned long sum;

    sum -= buffer[oldest];
    sum += sample;
    buffer[oldest] = sample;
    oldest += 1;
    if (oldest >= N) oldest = 0;

    return sum/N;
}

Модифікований кільцевий буфер IIR: зберігайте поточний SUM останніх N значень. Кожен раз, коли надходить новий зразок, SUM - = SUM / N, додайте новий зразок та виведіть SUM / N.

unsigned int Filter(unsigned int sample){
    static unsigned long sum;

    sum -= sum/N;
    sum += sample;

    return sum/N;
}

Якщо я вас правильно читаю, ви описуєте фільтр IIR першого порядку; значення, яке ви віднімаєте, не є найдавнішим значенням, яке випадає, а натомість є середнім значенням попередніх значень. IIR фільтри першого порядку, безумовно, можуть бути корисними, але я не впевнений, що ви маєте на увазі, коли ви припускаєте, що вихід є однаковим для всіх періодичних сигналів. При частоті вибірки 10 КГц подача квадратної хвилі 100 ГГц у 20-ступінчастий фільтр подає сигнал, який рівномірно піднімається на 20 зразків, сидить високий на 30, падає рівномірно на 20 зразків і низький - на 30. IIR фільтр ...
supercat

... вийде хвиля, яка різко починає підніматися і поступово знижується біля (але не на) вхідного максимуму, потім різко починає падати і поступово знижується біля (але не на) вхідного мінімуму. Дуже інша поведінка.
supercat

Ви маєте рацію, я плутав два види фільтра. Це справді IIR II-го порядку. Я змінюю відповідь на відповідність. Спасибі.
Стівен Коллінгс

Одне питання полягає в тому, що проста ковзаюча середня може бути або не корисна. За допомогою IIR-фільтра ви можете отримати хороший фільтр з відносно невеликим обчисленням. Опис, яку ви описуєте, може дати вам прямокутник прямо в часі - sinc in freq - і ви не можете керувати бічними часточками. Можливо, варто вкласти кілька цілих множин, щоб зробити це симпатичним симетричним регульованим FIR, якщо ви можете пощадити годинник-кліщі.
Скотт Сейдман

@ScottSeidman: Не потрібно множуватись, якщо на кожному етапі FIR просто виводиться середнє вхідне значення для цього етапу та його попереднє збережене значення, а потім зберігається вхідне значення (якщо один має числовий діапазон, можна використовувати суму а не середній). Чи буде це краще, ніж коробний фільтр, залежить від програми (крок відповіді коробкового фільтра із загальною затримкою в 1 мс, наприклад, матиме неприємний шип d2 / dt при зміні входу, і знову 1 мс пізніше, але матиме мінімально можливий d / dt для фільтра із загальною затримкою 1 мс).
supercat

2

Як сказав mikeselectricstuff , якщо вам дійсно потрібно зменшити потреби в пам’яті, і ви не заперечуєте, щоб ваша імпульсна реакція була експоненціальною (замість прямокутного імпульсу), я б пішов на експоненціальний фільтр, що рухається середньою . Я їх широко використовую. З таким типом фільтра вам не потрібен буфер. Вам не доведеться зберігати N попередніх зразків. Тільки один. Отже, ваші вимоги до пам'яті зменшуються на коефіцієнт N.

Крім того, для цього вам не потрібно жодного підрозділу. Тільки множення. Якщо у вас є доступ до арифметики з плаваючою комою, використовуйте множення з плаваючою комою. В іншому випадку зробіть цілі множення та зміщення вправо. Однак ми перебуваємо в 2012 році, і я б рекомендував вам використовувати компілятори (та MCU), які дозволяють працювати з номерами з плаваючою комою.

Окрім того, що вона є більш ефективною та швидкою пам'яттю (вам не потрібно оновлювати елементи в будь-якому круговому буфері), я б сказав, що це також більш природно , оскільки експоненційний імпульсний відгук краще відповідає поведінці природи в більшості випадків.


5
Я не згоден з вами рекомендацією щодо використання цифр з плаваючою комою. ОП, ймовірно, використовує 8-бітний мікроконтролер з причини. Пошук 8-бітового мікроконтролера з апаратною підтримкою з плаваючою комою може бути складним завданням (чи знаєте ви?). І використання чисел з плаваючою комою без апаратної підтримки буде дуже трудомістким завданням.
PetPaulsen

5
Казати, що ви завжди повинні використовувати процес з можливістю плаваючої крапки просто нерозумно. Крім того, будь-який процесор може робити плаваючу крапку, це лише питання швидкості. У вбудованому світі кілька центів вартості побудови можуть мати значення.
Олін Латроп

@Olin Lathrop та PetPaulsen: Я ніколи не говорив, що він повинен використовувати MCU з апаратним FPU. Перечитайте мою відповідь. Під "(і MCU)" я маю на увазі MCU досить потужні, щоб працювати з програмним забезпеченням з арифметикою з плаваючою комою текучим способом, що не стосується всіх MCU.
Телаклаво

4
Не потрібно використовувати плаваючу крапку (апаратне АБО програмне забезпечення) лише для 1-полюсного фільтра низьких частот.
Джейсон S

1
Якби у нього були операції з плаваючою комою, він би не заперечував проти поділу.
Федеріко Руссо

0

Одне питання щодо фільтра IIR, якого майже торкнулися @olin та @supercat, але, очевидно, ігнорували його інші, полягає в тому, що округлення вниз вносить деяку неточність (і потенційно зміщення / усічення): припускаючи, що N - сила двох, і лише ціла арифметика є Застосовується, зсув праворуч систематично усуває LSB нового зразка. Це означає, що як довго триватиме серіал, середній показник ніколи їх не враховує.

Наприклад, припустимо, що повільно зменшується ряд (8,8,8, ..., 8,7,7,7, ... 7,6,6,) і припустимо, що середнє дійсно на початку 8. Зразок кулака "7" доведе середнє значення до 7, незалежно від сили фільтра. Всього за один зразок. Одна і та ж історія для 6 і т. Д. Тепер подумайте про зворотне: serie піднімається. Середній показник залишиться на 7 назавжди, доки вибірка не буде достатньо великою, щоб змінити її.

Звичайно, ви можете виправити "упередження", додавши 1/2 ^ N / 2, але це не вирішить проблему точності: у такому випадку зменшується серія залишиться назавжди на 8, поки зразок не стане 8-1 / 2 ^ (N / 2). Наприклад, для N = 4 будь-який зразок вище нуля збереже середнє значення без змін.

Я вважаю, що рішення для цього має на увазі збереження накопиченого втраченого LSB. Але я не зробив це достатньо далеко, щоб мати готовий код, і я не впевнений, що це не завдасть шкоди потужності IIR в деяких інших випадках серії (наприклад, 7,9,7,9 буде в середньому до 8 тоді) .

@Olin, ваш двоступеневий каскад також потребує деяких пояснень. Ви маєте на увазі утримання двох середніх значень з результатом першого, що подається на другого в кожній ітерації? Яка користь від цього?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.