Как уже упоминали другие, вы должны рассмотреть фильтр IIR (бесконечный импульсный отклик), а не фильтр FIR (конечный импульсный отклик), который вы используете сейчас. Это еще не все, но на первый взгляд КИХ-фильтры реализованы в виде явных сверток и БИХ-фильтров с уравнениями.
Конкретный БИХ-фильтр, который я часто использую в микроконтроллерах, - это однополюсный фильтр нижних частот. Это цифровой эквивалент простого аналогового фильтра RC. Для большинства приложений они будут иметь лучшие характеристики, чем фильтр, который вы используете. Большинство применений блочного фильтра, с которым я столкнулся, являются результатом того, что кто-то не обращает внимания на класс цифровой обработки сигналов, а не следствием необходимости их особых характеристик. Если вы просто хотите ослабить высокие частоты, которые, как вы знаете, являются шумом, лучше использовать однополюсный фильтр нижних частот. Лучший способ реализовать один из них в микроконтроллере обычно:
FILT <- FILT + FF (NEW - FILT)
ФИЛЬТ - это часть постоянного состояния. Это единственная постоянная переменная, необходимая для вычисления этого фильтра. NEW - это новое значение, которое фильтр обновляет в этой итерации. FF - фракция фильтра , которая регулирует «тяжесть» фильтра. Посмотрите на этот алгоритм и убедитесь, что при FF = 0 фильтр бесконечно тяжел, поскольку выходная мощность никогда не меняется. Для FF = 1 это действительно не фильтр вообще, поскольку выход просто следует за входом. Полезные значения находятся между ними. В небольших системах вы выбираете FF как 1/2 Nтак что умножение на FF может быть выполнено как сдвиг вправо на N битов. Например, FF может быть 1/16, а умножение на FF, следовательно, сдвиг вправо на 4 бита. В противном случае этот фильтр требует только одного вычитания и одного сложения, хотя числа обычно должны быть шире входного значения (подробнее о числовой точности в отдельном разделе ниже).
Я обычно снимаю показания АЦП значительно быстрее, чем они нужны, и применяю два из этих фильтров каскадно. Это цифровой эквивалент двух последовательных 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 Development Tools на моей странице загрузки ПО .
Добавлено о числовой точности
Из комментариев и теперь нового ответа я вижу, что есть интерес к обсуждению количества битов, необходимых для реализации этого фильтра. Обратите внимание, что умножение на FF создаст новые биты Log 2 (FF) ниже двоичной точки. В небольших системах FF обычно выбирается равной 1/2 N, так что это умножение фактически реализуется путем сдвига вправо N битов.
Поэтому FILT обычно является целым числом с фиксированной точкой. Обратите внимание, что это не меняет никакой математики с точки зрения процессора. Например, если вы фильтруете 10-битные аналого-цифровые показания и N = 4 (FF = 1/16), то вам нужно на 4 дробных бита ниже 10-битных целочисленных аналого-цифровых показаний. Один из большинства процессоров, вы будете делать 16-битные целочисленные операции из-за 10-битного A / D чтения. В этом случае вы все равно можете выполнять точно такие же операции с 16-битными целыми числами, но начать с показаний АЦП влево, смещенных на 4 бита. Процессор не знает разницы и не нуждается в этом. Выполнение математических вычислений для целых 16-битных целых чисел работает независимо от того, считаете ли вы их 12,4-целыми или истинными 16-битными целыми числами (16,0-фиксированной точкой).
В общем, вам нужно добавить N битов на каждом полюсе фильтра, если вы не хотите добавлять шум из-за числового представления. В приведенном выше примере второй фильтр из двух должен иметь 10 + 4 + 4 = 18 бит, чтобы не потерять информацию. На практике на 8-битной машине это означает, что вы будете использовать 24-битные значения. Технически только второй полюс из двух будет нуждаться в более широком значении, но для простоты прошивки я обычно использую одинаковое представление и, следовательно, один и тот же код для всех полюсов фильтра.
Обычно я пишу подпрограмму или макрос для выполнения одной операции полюса фильтра, а затем применяю это к каждому полюсу. Выбор подпрограммы или макроса зависит от того, являются ли циклы или память программы более важными в этом конкретном проекте. В любом случае, я использую некоторое состояние нуля, чтобы передать NEW в подпрограмму / макрос, который обновляет FILT, но также загружает его в то же состояние, в котором находился NEW. Это позволяет легко применять несколько полюсов, так как обновленный FILT одного полюса НОВОЕ из следующего. При выполнении подпрограммы полезно указывать указатель на FILT при входе, который обновляется сразу после FILT при выходе. Таким образом, подпрограмма автоматически работает с последовательными фильтрами в памяти, если она вызывается несколько раз. С макросом вам не нужен указатель, так как вы передаете адрес для работы на каждой итерации.
Примеры кода
Вот пример макроса, как описано выше для PIC 18:
////////////////////////////////////////////////// //////////////////////////////
//
// Макро ФИЛЬТР фильт
//
// Обновляем один полюс фильтра новым значением в 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, w
subwfb newval + 1
movf [arg 1] +2, ш
subwfb newval + 2
/записывать
/ loop n filterbits, один раз для каждого бита, чтобы сдвинуть NEWVAL вправо
rlcf newval + 2, w; сдвиг NEWVAL вправо на один бит
rrcf newval + 2
rrcf newval + 1
rrcf newval + 0
/ ENDLOOP
/записывать
movf newval + 0, w; добавить смещенное значение в фильтр и сохранить в NEWVAL
addwf [аргумент 1] +0, ш
movwf [arg 1] +0
movwf newval + 0
мовф ньювэл + 1, ш
addwfc [arg 1] +1, w
movwf [arg 1] +1
movwf newval + 1
мовф ньювэл + 2, ш
addwfc [аргумент 1] +2, ш
movwf [arg 1] +2
movwf newval + 2
/ endmac
А вот аналогичный макрос для PIC 24 или dsPIC 30 или 33:
////////////////////////////////////////////////// //////////////////////////////
//
// Макрофильтр ffbits
//
// Обновляем состояние одного фильтра нижних частот. Новое входное значение в W1: W0
// и состояние фильтра для обновления указывается W2.
//
// Обновленное значение фильтра также будет возвращено в W1: W0 и W2 будут указывать
// в первую память после состояния фильтра. Поэтому этот макрос может быть
// вызывается последовательно для обновления серии каскадных фильтров нижних частот.
//
// Формула фильтра:
//
// FILT <- FILT + FF (NEW - FILT)
//
// где умножение на FF выполняется арифметическим сдвигом вправо
// ФФБИТЫ.
//
// ВНИМАНИЕ: W3 уничтожен.
//
/ макро фильтр
/ var new ffbits integer = [arg 1]; получить количество бит для смещения
/записывать
/ write "; Выполнить однополюсную фильтрацию нижних частот, сдвиг битов =" ffbits
/записывать " ;"
sub w0, [w2 ++], w0; NEW - FILT -> 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 на ассемблере , который более эффективен, чем любое из встроенных средств макросов.