Цифровая обработка сигналов на ARM7-микроконтроллерах

Для цифровой обработки сигналов (DSP) существует множество специализированных процессоров, это DSP от Texas Instruments серии TMS320, которая включает в себя и простые целочисленные ядра, и такие монстры как подсемейство семейство C6000, обрабатывающие данные с плавающей точкой. Есть целая серия ADSP от Analog Devices (в которую входит более-менее универсальный BlackFin), есть и более простые решения от MicroChip - dsPIC.

Однако, специализированный DSP - это хорошо, но вот всегда ли он так необходим? Да, при огромном потоке информации он просто незаменим, однако есть и более простые задачи обработки. Конкретно меня интересовала задача двойного преобразования - делается свертка аудиосигнала, тем самым получается спектр, затем над спектром можно провести любые операции и выполнить обратное преобразование, получив тем самым обработанный сигнал. Все это нужно провести в реальном времени и получить качество не ниже телефонного.

Сейчас не 2000-ый год, существуют и существенно упали в цене однокристальные решения на производительных ядрах ARM7/Cortex-M3, они являются 32 битными, имеют аппаратную реализацию операции 32-битного умножения (мало того, почти DSP-операцию умножения с накоплением и 64 битным результатом), а Cortex-M3 включает еще и аппаратное деление.

Сразу хочу предупредить, обработка сигналов не является моим профилем, почти все знания (а точнее понимания принципов) сохранились еще с института, сейчас же захотелось просто проверить и реализовать их. Это я к тому, что могут встречаться неточности в описании, подмены понятий и прочее. Собственно, академическая точность меня не очень сильно волновала.

Почти для любого DSP, озвученная выше задача является простой и подъемной. Но как же поведет себя на ней RISC-ядро общего применения? Если рассматривать AVR или PIC, то их вряд ли хватит. Сказывается 8-битность и низкая тактовая частота. Хотя, у Elm-Chan-а есть конструкции, в которых он на AVR проводит БПФ и рисует спектр сигнала. Однако, в данном случае в реальном времени делается просто визуализация (с минимальной точностью обработки), а не полная обработка сигнала с приемлемым аудио-качеством.

В качестве подопытного чипа был выбран LPC2146, основанный на ядре ARM7TDMI-S и имеющий максимальную тактовую частоту 60МГц (на практике, на работает на 72 и даже на 84МГц). Почему? Во-первых, у меня есть отладочная плата для него, во-вторых, на борту имеется АЦП и ЦАП, т.е. требуется минимальная внешняя обвеска.

Немного теории

В первую очередь, интересно было оценить производительность БПФ (Быстрого Преобразования Фурье) на ARM-микроконтроллерах. По этой оценке можно сделать вывод: хватит ли у него скорости для обработки потока аудиоданных, и сигнал с какой частотой дискретизации и на сколько каналов можно будет обработать на таком микроконтроллере.

На основе преобразования Фурье можно сооружать хитрые фильтры (при этом с очень заманчивыми характеристиками). Меня же в первую очередь интересовали задачи изменения тональности сигнала (поднятие и опускание спектра) и "отражение" спектра. Последнее требуется в SDR-радиоприемниках для прослушивания радиопередачи в нижней боковой полосе LSB.

Не буду загружать теорией и объяснять что такое Преобразование Фурье, материалов на эту тему довольно много, из того, что использовал я: вики и глава из очень хорошей и информативной книги.

Программная реализация

Программных реализаций БПФ существует очень много, однако, я писал собственную. Основная цель, которую я преследовал: оптимизация кода на конкретную архитектуру. Во-первых, я сразу ориентировался на 32-битность, во-вторых требовались только целочисленные вычисления и желательно было избежать операции деления. Под эти требования подобрать нечто готовое уже проблемно.

Все константы, которые можно было рассчитать заранее, были рассчитаны и помещены в таблицы (в основном это значения тригонометрических функций). Это основная оптимизация алгоритма, в остальном он почти полностью повторяет описанный алгоритм.

Самым же важным является требование к целочисленным вычислениям. В процессе реализации, была даже допущена ошибка, из-за которой происходило переполнение в одной из 32 битных переменных цикла. Мало того, она возникала не на всех тестовых данных, поэтому доставляла приличную головную боль, пока не была найдена.

Все исходные тексты я собрал в одном архиве. Реализация не окончательная, есть дублирующие вычисления (не учитывается симметрия спектра и фаз), и требуется оптимизация использования буферов, так как в текущий момент для расчетов используется слишком много оперативной памяти (почти 12к для преобразования из 1024 точек).

Первые тесты

Барабанная дробь: тестирую скорость работы преобразования для выборки из 1024 точек, частота процессорного ядра 60МГц. Тестирование проводилось в эмуляторе, так что не претендует на 100% точность, однако этот результат можно использовать в качестве показателя (по моему предыдущему опыту эмулятор хоть и врал, но не сильно). Тест первой версии кода, компилятор RealView MDK, опция оптимизации O3, ARM-режим генерации кода.

Итак, что мы видим:

По 6мс на каждое преобразование, итого чуть более 12мс на преобразование "туда и обратно". Получается, что при частоте дискретизации 44100Гц (стандартная для аудио) и выборках разрешением до 16 бит, чистые вычисления будут занимать ~44*12мс = 528мс. И это на промежуточной версии микропрограммы, когда еще не закончены некоторые оптимизации кода (по прикидкам алгоритм может быть ускорен еще чуть ли не в 2 раза)! По-моему просто отличный показатель.

Итого, загрузка ядра предполагается в районе 50%, еще 50% остается на преобразования над спектром и накладные расходы при работе с АЦП, ЦАП и прочие передачи данных. Если же понизить частоту выборок до "телефонного" уровня (около 4800-9600Гц), то загрузка ядра будет еще ниже (порядка 15-30%).

Итак, с математической частью более-менее понятно. Можно приступить к конкретной реализации.

Железо

Для тестовой платформы взята отладочная плата Keil MCB2140 с динамиком. Напаян шнурок Mini-Jack для подключения к линейному выходу устройства и собрана простейшая входная цепочка. Как уже было сказано, на плате уже установлен динамик, подключенный к аналоговому выходу микроконтроллера и есть элементы управления (кнопка и потенциометр).

Вот набросок входной цепи:

Отладка софта происходила поэтапно:

  1. Отладка всей необходимой периферии: АЦП, ЦАП, таймеры, светодиодная индикация.
  2. Тест с оцифровкой сигнала: оцифровываю данные с требуемой скоростью и кладу в буфер, затем извлекаю данные из буфера и воспроизвожу сигнал. Т.е. простой сдвиг сигнала во времени, без каких-либо преобразований. На этом этапе тестируется механизм работы с 2 буферами, необходимый для дальнейшей работы.
  3. К предыдущей версии добавляется прямое и обратное преобразование Фурье. Этот тест окончательно проверяет корректность работы кода БПФ, а так же проверка, что код укладывается в доступную производительность.
  4. После этого основной костяк приложения сделан, можно перейти к практическим применениям.

Проблема возникла после добавления в код БПФ: в сигнале появились посторонние шумы и свисты. Вообще, данное поведение показалось мне довольно странным, т.к. без преобразования сигнал проходя цифровой тракт оставался достаточно чистым. Первая причина этого: после аналоговой цепи, амплитуда сигнала на АЦП была не полной (0-3.3В), а только в пределах 0.5-2В на максимальной громкости плеера, вторая: достаточно сильный шум из-за целочисленных вычислений (+-1 единица, что оказалось достаточным для появления слышимых помех).

Для борьбы с первой проблемой, было решено заняться подстройкой аналоговой части. А для решения вопроса с шумами - попробовать использовать lowpass-фильтр.

Прикладное применение 1: изменяем тональность сигнала

На плате предусмотрен потенциометр (переменные резистор), его можно использовать для управления. В данном случае он задает смещение спектра сигнала вверх-вниз, вполне достаточно чтобы "преобразить" любимые композиции.

Вот что происходит в частотной области:

При этом результат преобразования содержится в 2 буферах. Один буфер - действительная часть, а другой - мнимая. Физический смысл полученных в них чисел: в действительной части содержатся значения гармоник, в мнимой - сдвиг фаз для данных гармоник. Мало того, как видно, изначальный сигнал описывается N-значениями, а после преобразования получается 2N-значений. Количество информации не меняется, а увеличение в 2 раза количества информации происходит из-за того, что данные буфера имеют избыточность в виде дублирования значений.

Прикладное применение 2: отражаем спектр

В данном случае отразим спектр. Схематически это действие выглядит так:

Данная операция почти полностью разрушает речь (в том плане, что ухо ее перестает воспринимать как нечто связное), но вот с инструментами так происходит далеко не со всеми, на некоторых моментах даже не сразу можно понять, что сигнал так жестко обработан. При этом сам по себе сигнал продолжает нести точно тот же объем информации, что и оригинальный (ничто не мешает повторно подвергнуть сигнал такому преобразованию и тем самым восстановить).

Эксперимент с отражением спектра был проведен из-за того, что данная операция очень актуальна для SDR-радиоприемников для приема нижней боковой полосы радиосигнала (разновидность SSB-модуляции, активно применяемая в любительском радиовещании).

Итог

ARM7TDMI-S на 60MHz позволяет на приемлемом уровне проводить обработку аудиосигнала (с "телефонный" качеством, без особых ухищрений и ограничений).

Ссылки

  1. DSP на AVR, Elm-Chan: Audio Spectrum Monitor.
  2. Тест АЦП в контроллере LPC214x
  3. Wiki: Преобразование Фурье и Дискретное преобразование Фурье.
  4. Уолт Кестер - Цифрования обработка сигналов, Глава 5 - Быстрое Преобразование Фурье.
  5. Павел Филимонов, Реализация целочисленного БПФ на процессорах с архитектурой ARM.
  6. Тест аудиочасти (ADC и DAC).
  7. Проект с БПФ логикой и кодом для преобразования сигнала.
  8. Описание и схема отладочной платы Keil MCD2140.
Original: http://igorkov.org/armdsp,
Author: igorkov