Рис. 13.10. Сигнал во временной и частотной областях
К счастью для нас, умные люди часто готовы поделиться своим кодом. Вы можете загрузить из Интернета функцию, выполняющую быстрое преобразование Фурье. Пример кода, который использовал я, не организован в библиотеку — он распространяется в виде двух файлов: заголовочного файла и файла реализации (с расширением .h и .cpp соответственно). Чтобы воспользоваться им, просто сохраните оба файла в папку со скетчем. Они находятся в пакете примеров, сопровождающем книгу, поэтому их не придется загружать из Интернета. Впервые код появился в статье на форуме Arduino (http://forum.arduino.cc/index.php/topic,38153.0.html). Эти же два файла можно найти в составе других примеров на следующих веб-сайтах: https://code.google.com/p/arduino-integer-fft/ и https://github.com/slytown/arduino-spectrum-analyzer/.
Следующие два примера иллюстрируют код, действующий в плате Arduino Uno и выполняющий замеры аудиосигнала.
Пример анализатора спектра
Этот пример плата Arduino Uno использует для получения текстового отображения частотного спектра. Исходный код можно найти в скетче sketch_13_06_FFT_Spectrum. Скетч слишком длинный, чтобы воспроизводить его здесь целиком, поэтому я буду демонстрировать лишь некоторые его фрагменты. Откройте скетч в Arduino IDE, чтобы заглядывать в него в ходе обсуждения.
Алгоритм БПФ использует два массива типа char. Этот тип выбран вместо типа byte по той простой причине, что в Arduino C тип byte представляет однобайтовые целые числа без знака, тогда как сигнал, подлежащий преобразованию, как предполагается, будет колебаться относительно значения 0. После применения алгоритма БПФ массив data будет содержать мощность каждой частотной составляющей в заданном диапазоне. Диапазон частот зависит от частоты выполнения замеров. Данный скетч выполняется с максимальной скоростью, на которую только способна плата Uno, и обеспечивает анализ полосы частот с верхней границей около 15 кГц, что для каждого из 63 равномерно распределенных частотных интервалов дает ширину 240 Гц.
Чтобы максимально быстро выполнить аналоговое преобразование и получить приличную частоту замеров, был использован трюк увеличения аналогово-цифрового преобразования, обсуждавшийся в главе 4. Он заключен в следующих двух строках в функции setup:
ADCSRA &= ~PS_128; // сбросить масштаб 128
ADCSRA |= PS_16; // добавить масштаб 16 (1 МГц)
Функция loop содержит совсем немного кода:
void loop()
{
sampleWindowFull();
fix_fft(data, im, 7, 0);
updateData();
showSpectrum();
}
Функция sampleWindowFull заполняет временное окно 128 замерами данных. Я расскажу о ней чуть позже. Затем к данным применяется алгоритм БПФ. Параметр 7 — это логарифм по основанию 2 от числа замеров. Это значение всегда будет равно 7. Параметр 0 — это признак инверсии, который также всегда будет равен 0, что означает false. После применения алгоритма БПФ производится обновление значений в массивах. В заключение вызывается функция showSpectrum, отображающая частотную информацию.
Функция sampleWindowFull читает значение аналогового входа 128 раз и предполагает, что сигнал колеблется относительно средней точки 2,5 В, поэтому она вычитает 512 из прочитанного значения, в результате чего может получиться положительное или отрицательное значение. Затем оно масштабируется константой GAIN, чтобы немного усилить слабые сигналы. Далее 10-битный замер делением на 4 преобразуется в 8-битное значение, чтобы можно было уместить его в массив типа char. Массив im хранит мнимую часть сигнала, установленную в 0. Это внутренняя особенность алгоритма; желающие больше узнать об этом могут обратиться к статье https://ru.wikipedia.org/wiki/Быстрое_преобразование_Фурье.
void sampleWindowFull()
{
for (int i = 0; i < 128; i++)
{
int val = (analogRead(analogPin) — 512) * GAIN;
data[i] = val / 4;
im[i] = 0;
}
}
Функция updateData вычисляет амплитуду в каждом частотном интервале. Сила сигнала вычисляется как длина гипотенузы прямоугольного треугольника, двумя другими сторонами которого являются действительная и мнимая части сигнала (практическое применение теоремы Пифагора!):
void updateData()
{
for (int i = 0; i < 64; i++)
{
data[i] = sqrt(data[i] * data[i] + im[i] * im[i]);
}
}
Результаты выводятся в монитор последовательного порта в одну строку через запятую. Первое значение игнорируется, потому что содержит постоянную составляющую сигнала и обычно не представляет интереса.
Массив data можно было бы использовать, например, для управления высотой столбиков диаграммы на жидкокристаллическом дисплее. Подключить источник сигнала (например, аудиовыход MP3-плеера) можно с помощью той же схемы, обеспечивающей колебание сигнала относительно средней точки 2,5 В, что была показана ранее, на рис. 13.4.