Принципы работы и настройка рафт-ранговых FIR‑фильтров в STM32

Если вы работаете с цифровой обработкой сигналов на STM32 и столкнулись с задачей реализации фильтра с конечной импульсной характеристикой, скорее всего вы уже поняли: стандартные функции CMSIS DSP — это хорошо, но они не всегда дают нужную производительность или гибкость. Особенно когда речь идёт о фильтрах высокого порядка, работающих в реальном времени с несколькими каналами. В таких случаях на помощь приходит аппаратная поддержка — в частности, рафт-ранговые (ranked) FIR-фильтры, которые можно реализовать через DMA и таймеры, либо используя специализированные периферийные модули в новых сериях STM32.

В этой статье разберём, как устроены FIR-фильтры в контексте STM32, когда имеет смысл переходить от софтварной реализации к аппаратно-ориентированным схемам, и как правильно настроить систему так, чтобы фильтр работал стабильно без перегрузки процессора.

Что такое FIR-фильтр и почему он важен для STM32

FIR (Finite Impulse Response) — это фильтр с конечной импульсной характеристикой. Его главное свойство: выходной сигнал зависит только от конечного числа предыдущих входных отсчётов. Это делает его по определению устойчивым и позволяет получить строго линейную фазу — то есть все частоты сигнала задерживаются на одинаковое время. Для задач обработки аудио, биомедицинских сигналов (ЭКГ, ЭЭГ) и измерительных систем это критически важно.

Математически работа FIR-фильтра описывается формулой:

y[n] = Σ b[k] · x[n−k], где k = 0..N−1

Здесь b[k] — коэффициенты фильтра (taps), x[n−k] — предыдущие отсчёты входного сигнала, N — порядок фильтра (количество отводов). Чем больше отводов, тем круче частотная характеристика, но тем больше вычислений нужно выполнить для каждого выходного отсчёта.

Когда стандартная реализация перестаёт справляться

В библиотеке CMSIS DSP от ST есть функция arm_fir_f32, которая реализует FIR-фильтр на процессоре Cortex-M4/M7 с поддержкой FPU. Для многих задач этого достаточно. Но проблемы начинаются, когда:

  • порядок фильтра превышает 100–200 отводов;
  • нужно обрабатывать несколько каналов одновременно;
  • частота дискретизации высокая (например, 192 кГц и выше);
  • процесор загружен другими задачами реального времени.

В этих случаях каждый новый отсчёт требует N умножений и N−1 сложений. При N=256 и частоте дискретизации 96 кГц это 24,5 миллиона операций умножения-сложения в секунду только на один канал. Процессор на 168 МГц потратит на это заметную часть своего времени.

Рафт-ранговый подход: суть идеи

Термин «рафт-ранговый» в контексте FIR-фильтров на STM32 означает организацию вычислений, при которой обработка распределена между аппаратными модулями и процессором по принципу ранжирования — приоритетные операции выполняются аппаратно, а процессор вмешивается минимально.

Идея в следующем: вместо того чтобы процессор сам читал данные из ADC, умножал их на коэффициенты и складывал, мы настраиваем систему так, чтобы:

  1. ADC по таймеру автоматически запускал преобразование и писал результат в память через DMA.
  2. DMA формировал кольцевой буфер с отсчётами.
  3. Процессор получал прерывание только когда накоплено достаточно данных для обработки блока.
  4. Вычисления выполнялись пакетно, с использованием оптимизированных функций CMSIS DSP или даже через аппаратный сопроцессор (если доступен).

Такой подход позволяет снизить нагрузку на процессор в 3–5 раз по сравнению с поотсчётной обработкой.

Архитектура системы с рафт-ранговым FIR-фильтром

Рассмотрим типичную архитектуру для STM32F4 или STM32F7. Система состоит из нескольких уровней:

Уровень Компонент Функция
1 (высший приоритет) Таймер (TIM) Генерация тактовой частоты дискретизации
2 ADC Аналого-цифровое преобразование
3 DMA Перенос данных из ADC в буфер без участия CPU
4 Кольцевой буфер Хранение текущих N отсчётов
5 (низший приоритет) CPU Вычисление FIR-фильтра по блоку данных

Ключевой момент: уровни 1–3 работают полностью автономно. Процессор участвует только на уровне 5, когда нужно выполнить свёртку. Это и есть суть рафт-рангового подхода — ранжирование задач по приоритету и вынос рутины в аппаратуру.

Пошаговая настройка

Шаг 1. Настройка таймера для генерации частоты дискретизации

Таймер настраивается так, чтобы генерировать событие обновления (update event) с частотой, равной нужной частоте дискретизации. Например, для 48 кГц при тактовой частоте таймера 84 МГц:

Prescaler = 0, Period = 84000000 / 48000 − 1 = 1749

Триггерный выход таймера (TRGO) настраивается на событие обновления и подключается к входу запуска ADC.

Шаг 2. Настройка ADC в режиме непрерывного сканирования

ADC настраивается на запуск от внешнего триггера (TRGO таймера). Режим непрерывного преобразования отключён — каждый запуск строго по таймеру. Это гарантирует стабильную частоту дискретизации без джиттера.

Важно: время преобразования ADC должно быть меньше периода дискретизации. Для 48 кГц период составляет 20,8 мкс. При 12-битном разрешении и частоте ADC 30 МГц время преобразования — около 0,5 мкс, что с запасом укладывается.

Шаг 3. Настройка DMA для кольцевого буфера

DMA настраивается в режиме circular (кольцевой), данные передаются из регистра данных ADC в массив в SRAM. Ширина данных — полуслово (16 бит) для 12-битного ADC.

Размер буфера DMA выбирается кратным размеру блока обработки. Например, если обрабатываем блоками по 64 отсчёта, буфер DMA может быть 128 или 256 отсчётов — чтобы процессор мог работать с одной половиной буфера, пока DMA заполняет другую (double buffering).

Шаг 4. Организация кольцевого буфер для коэффициентов

Для FIR-фильтра нужно хранить не только входные отсчёты, но и коэффициенты. Коэффициенты заранее рассчитываются (например, в MATLAB или Python с помощью scipy.signal.firwin) и сохраняются в массиве с фикситочкой или в float32.

При использовании CMSIS DSP структура arm_fir_instance_f32 содержит указатель на коэффициенты и на состояние (state buffer). State buffer должен быть размером numTaps + blockSize − 1 — это обеспечивает непрерывность фильтрации между блоками.

Шаг 5. Обработка в прерывании DMA

Когда DMA заполняет половину буфера или весь буфер (в зависимости от схемы), генерируется прерывание. В обработчике:

  1. Определяем, какая половина буфера готова.
  2. Копируем данные в рабочий массив (или обрабатываем на месте).
  3. Вызываем arm_fir_f32 для блока.
  4. Результат записываем в выходной буфер (для DAC или дальнейшей обработки).

Сравнение подходов к реализации

Параметр Поотсчётная (ISR) Блочная (DMA + CMSIS) Рафт-ранговая (DMA + блочная + приоритеты)
Нагрузка на CPU Высокая Средняя Низкая
Задержка Минимальная Размер блока Размер блока
Сложность настройки Низкая Средняя Высокая
Устойчивость к пропуску данных Низкая Средняя Высокая
Поддержка многоканальности Сложно Средне Хорошо
Минимальный порядок фильтра Любой Любой Любой
Оптимальный порядок фильтра < 32 32–256 > 128

Что выбрать в зависимости от задачи

Если у вас простой фильтр (до 32 отводов) и один канал — используйте стандартную CMSIS DSP функцию с обработкой в прерывании ADC. Не усложняйте. Настройка DMA и таймеров оправдана только когда есть реальная проблема с производительностью.

Если фильтр среднего порядка (32–128 отводов) и частота дискретизации до 96 кГц — блочная обработка через DMA с double buffering даст хороший результат. Процессор будет загружен на 30–50%, остальное время можно потратить на другие задачи.

Если фильтр высокого порядка (более 128 отводов), несколько каналов или частота выше 96 кГц — рафт-ранговый подход с чётким разделением приоритетов между периферией и процессором становится необходимостью. Рассмотрите также возможность использования STM32H7 с его более мощным DMA и кэшированием.

Частые ошибки

Ошибка 1: Неправильный размер state buffer. Если state buffer меньше чем numTaps + blockSize − 1, фильтр будет работать некорректно — на границах блоков появятся щелчки и искажения. Это самая частая проблема при переходе от поотсчётной обработки к блочной.

Ошибка 2: Отсутствие критических секций при double buffering. Если DMA и процессор одновременно работают с одной и той же областью памяти, данные могут быть повреждены. Нужно либо использовать полноценный double buffering (две независимые половины), либо отключать прерывание DMA на время обработки.

Ошибка 3: Переполнение буфера из-за задержки обработки. Если процессор не успевает обработать блок до того, как DMA перезапишет данные, произойдёт потеря отсчётов. Решение: увеличить размер буфера, оптимизировать код обработки или снизить частоту дискретизации.

Ошибка 4: Игнорирование выравнивания данных для DMA. На Cortex-M7 с кэшем данные, записанные DMA, могут быть невидны процессору без инвалидации кэша. Убедитесь, что буферы DMA находятся в не кэшируемой области памяти (например, SRAM2 на STM32F7) или используйте SCB_CleanInvalidateDCache().

Ошибка 5: Неправильный расёт коэффициентов. Коэффициенты для фиксированной точки нужно масштабировать с учётом разрядности. Если просто взять float-коэффициенты и привести их к Q15 без нормализации, фильтр может работать с неправильным усилением или даже нестабильно (для IIR, но для FIR это проявляется в неверной амплитуде).

Практические рекомендации

  • Начинайте с float32. Даже если в финальной версии планируете фиксированную точку, сначала отладьте алгоритм на float32 через CMSIS DSP. Это избавит от множества проблем с масштабированием и переполнением.
  • Используйте инструмент Filter Designer. MATLAB, scipy.signal (Python) или бесплатный онлайн-калькулятор — сгенерируйте коэффициенты и проверьте частотную характеристику до того, как загружать их в микроконтроллер.
  • Профилируйте нагрузку. Замерьте, сколько тактов уходит на обработку одного блока. Если это больше чем время накопления блока (blockSize / sampleRate), система не будет работать в реальном времени.
  • Рассмотрите децимацию. Если полоса сигнала узкая относительно частоты дискретизации, можно снизить частоту после первого каскада фильтрации и уменьшить нагрузку на последующие каскады.
  • Тестируйте на реальных данных. Синусоидальный тест покажет частотную характеристику, но только реальные данные с шумом и артефактами выявят проблемы с переполнением или потерей отсчётов.

Итог

Рафт-ранговый подход к FIR-фильтрации на STM32 — это не конкретная функция или библиотека, а архитектурный принцип: вынести рутинные операции (запуск ADC, перенос данных, хранение отсчётов) в аппаратуру, а процессор задействовать только для собственно вычислений свёртки. Это даёт стабильную работу в реальном времени даже для фильтров высокого порядка и многоканальных систем.

Начните с простой блочной обработки через DMA и CMSIS DSP. Если производительности не хватает — переходите к полноценной рафт-ранговой архитектуре с чётким разделением приоритетов. И всегда проверяйте систему на реальных данных, а не только на синтетических тестах.

Radio-Blog.ru