Как настроить рафт-ранговый FIR-фильтр в STM32: от теории до рабочего кода

Как настроить рафт-ранговый FIR-фильтр в STM32: от теории до рабочего кода

Ты хочешь убрать шум с датчика температуры, но простой скользящее среднее уже не справляется. Или тебе нужно фильтровать сигнал с акселерометра в реальном времени — без задержек, без артефактов. Ты пробовал IIR, но он нестабилен при изменении температуры микроконтроллера. И тут ты наткнулся на FIR-фильтр с рафт-ранговой обработкой. Звучит сложно? На деле — просто. Я покажу, как это работает на STM32, без лишней теории, только то, что нужно, чтобы заработать.

Почему именно рафт-ранговый FIR?

FIR (Finite Impulse Response) — это фильтр, который не имеет обратной связи. Всё просто: берёшь несколько последних отсчётов, умножаешь на коэффициенты, складываешь — и получил новый выход. Никаких колебаний, никакой нестабильности. Но есть проблема: если в данных есть выброс (например, импульсный шум от реле или электромагнитный скачок), FIR его просто «сглаживает» — и ты теряешь полезный сигнал, или, наоборот, оставляешь артефакт.

Рафт-ранговый (rank-order) FIR — это модификация, где вместо простого усреднения мы сортируем отсчёты и выбираем медиану. Это как если бы ты взял 7 измерений температуры, выстроил их по возрастанию, и взял четвёртое — среднее по порядку. Выбросы? Они оказываются на краях — и не влияют на результат. Это работает даже если 2 из 7 значений — полный мусор.

На STM32 это особенно ценно: ты работаешь в реальном времени, без ОС, с жёсткими ограничениями по памяти и тактовой частоте. IIR может «сойти с ума» при переполнении, а рафт-ранговый FIR — просто работает. И не требует плавающей точки.

Как это работает: шаг за шагом

Представь, что у тебя есть датчик, который даёт 100 отсчётов в секунду. Ты хочешь фильтровать их с окном из 9 значений. Вот что происходит на каждом такте:

  1. Записываешь новое значение в буфер из 9 ячеек (циклический буфер).
  2. Сортируешь все 9 значений по возрастанию.
  3. Берёшь значение с индексом 4 (медиана — центральное в нечётном окне).
  4. Это и есть твой выход.

Нет умножений. Нет делений. Только сравнения и перестановки. Это идеально для Cortex-M0/M3 — они медленные, но сортировка 9 чисел — это 36 сравнений максимум. На 72 МГц STM32F1 это занимает 15–20 мкс. Ты спокойно можешь фильтровать 1 кГц сигнал.

Вот как выглядит код на C (всё на целых числах, без float):

// Буфер из 9 отсчётов
int16_t buffer[9];
uint8_t buffer_index = 0;

// Функция фильтрации
int16_t raft_rank_filter(int16_t new_sample) {
    // Добавляем новый отсчёт в буфер
    buffer[buffer_index] = new_sample;
    buffer_index = (buffer_index + 1) % 9;

    // Сортируем буфер (пузырьком — просто и надёжно)
    for (int i = 0; i < 8; i++) {
        for (int j = 0; j < 8 - i; j++) {
            if (buffer[j] > buffer[j + 1]) {
                int16_t temp = buffer[j];
                buffer[j] = buffer[j + 1];
                buffer[j + 1] = temp;
            }
        }
    }

    // Возвращаем медиану (4-й элемент, индекс 4)
    return buffer[4];
}

Этот код работает на любом STM32 — даже на STM32F030 с 48 МГц. Никаких библиотек, никаких FFT, никаких floating point.

Как выбрать размер окна

Размер окна — это компромисс между задержкой и фильтрацией. Чем больше окно — тем лучше убираются выбросы, но тем больше задержка. Вот что реально работает на практике:

Размер окна Задержка (при 1 кГц) Подходит для Риск
5 5 мс Быстрые сигналы: кнопки, энкодеры, импульсные датчики Мало убирает шум, если выбросы частые
7 7 мс Температура, давление, датчики с низкой частотой изменения Оптимальный баланс для большинства задач
9 9 мс Акселерометры, шумные аналоговые сигналы, датчики с резкими скачками Задержка может мешать в системах управления
11+ 11+ мс Очень шумные среды: промышленные датчики, моторные драйверы Слишком медленный отклик — может вызвать нестабильность в ПИД-контроллере

Большинство моих проектов — 7. Почему? Потому что 7 — это минимальное число, при котором медиана начинает надёжно подавлять одиночные выбросы. Если у тебя два выброса подряд — они всё ещё могут повлиять. Но если один — он уходит. Это работает как шумоподавление в аудио, но без артефактов.

Частые ошибки — и как их избежать

  • Используешь float. Зачем? Ты сортируешь целые числа — датчик даёт 12-битный ADC. Переводи в int16_t и работай с ними. Float на Cortex-M0 тормозит в 10 раз.
  • Забываешь про инициализацию буфера. Если буфер не заполнен при старте — первые 8 отсчётов будут мусором. Инициализируй его первым значением: for (int i=0; i<9; i++) buffer[i] = new_sample;
  • Используешь нечётное окно, но берёшь среднее двух центральных. Нет. Рафт-ранг — это медиана, а не усреднение. Бери одно значение — центральное. Иначе теряешь преимущества.
  • Сортируешь весь буфер на каждом шаге. Да, это нормально для 9 элементов. Но если ты хочешь окно 15 — перейди на быструю сортировку или используй алгоритм с вставкой. Пузырёк для 15 — это уже 105 сравнений. На 168 МГц — ок, на 8 МГц — уже заметно.
  • Пытаешься фильтровать сигнал с частотой выше 100 Гц окном 9. Ты получаешь задержку 90 мс. Это как управлять роботом с задержкой в полсекунды — не сработает. Уменьшай окно или уменьшай частоту сэмплинга.

Когда использовать, а когда — нет

Не думай, что это «волшебная таблетка». Вот когда он работает идеально:

  • Ты фильтруешь сигнал с периодическими выбросами (например, от реле, тиристоров, моторов).
  • Шум не гауссовский — а импульсный, «пиковый».
  • Тебе не критична задержка в 5–10 мс.
  • Ты не можешь позволить себе IIR из-за нестабильности при температурных колебаниях.

А вот когда он — плохой выбор:

  • Ты фильтруешь синусоидальный сигнал с высокой частотой (например, 50 Гц). Тут лучше FIR с окном 128 и коэффициентами Чебышёва.
  • Тебе нужна фазовая линейность с минимальной задержкой — тогда используй FIR с линейной фазой, но без ранговой обработки.
  • Ты работаешь с очень медленными сигналами (например, 1 отсчёт в секунду). Тогда просто сглаживай с экспоненциальным фильтром — он быстрее и проще.

Как лучше сделать: практические рекомендации

  1. Начни с окна 7. Это золотая середина. Протестируй на реальном датчике — включи/выключи реле рядом с платой и посмотри, исчезают ли пики.
  2. Используй int16_t для буфера. Даже если ADC 12-битный — он даёт значения от 0 до 4095. В int16_t это влезает с запасом.
  3. Не используй рекурсию, динамическую память, malloc. Всё — статически.
  4. Если у тебя есть DMA — сбрасывай его в буфер, а фильтр запускай в прерывании по окончанию буфера. Так ты не тормозишь основной цикл.
  5. Тестируй с «плохими» данными. Сгенерируй сигнал с 10% выбросов — и посмотри, как фильтр себя ведёт. Если медиана всё ещё «внутри» сигнала — ты всё сделал правильно.
  6. Если нужно уменьшить задержку — не уменьшай окно. Уменьшай частоту сэмплинга. Например, бери отсчёт каждые 2 мс (500 Гц), а фильтруй окном 7 — получишь задержку 14 мс, но снизишь нагрузку на процессор.

Сценарии выбора: что делать в твоей ситуации

Если твоя задача выглядит так — вот что делать:

  • «У меня датчик температуры в шкафу с реле. Шум — скачки на 20°C» → Окно 7. Сэмплинг 100 Гц. Фильтр в прерывании таймера. Результат — плавная кривая без ложных срабатываний термостата.
  • «Мне нужно фильтровать сигнал с акселерометра в роботе-пылесосе. Он дергается при столкновении» → Окно 9. Сэмплинг 500 Гц. Тестируй на ударе — если пики уходят, а основной сигнал сохраняется — отлично.
  • «Я измеряю ток через датчик с шумом от PWM-драйвера» → Окно 5. Сэмплинг 10 кГц. Быстрый отклик важен. Медиана 5 — убирает одиночные импульсы, не добавляя задержки.
  • «Я хочу фильтровать сигнал с микрофона» → Не используй рафт-ранг. Это аудио — нужна частотная фильтрация. Используй FIR с окном 64 и окном Хэмминга.

Итог: что делать прямо сейчас

Вот твой план на сегодня:

  1. Возьми свой STM32 и подключи датчик, который шумит.
  2. Скопируй код фильтра из примера выше — с окном 7.
  3. Инициализируй буфер первым значением.
  4. Запусти сэмплинг через таймер (например, 100 Гц).
  5. Выведи до и после на UART или осциллограф.
  6. Проверь, исчезли ли выбросы — и сохранился ли полезный сигнал.

Если всё работает — ты только что внедрил промышленный метод фильтрации, который используют в системах управления двигателями, медицинских приборах и космической аппаратуре. Без лицензий. Без дорогих чипов. Без сложных библиотек.

Это не «надо попробовать». Это — рабочее решение, которое ты можешь запустить за час. И оно будет работать через 5 лет, когда другие фильтры сойдут с ума от температуры.

Информация в этой статье носит ознакомительный характер. При проектировании систем управления, безопасности или медицинских устройств всегда консультируйся с инженером, специализирующимся на обработке сигналов и надёжности микроконтроллерных систем.

Radio-Blog.ru