Использование модуля FFT совместно с NIOS

image
В этой статье я хочу рассказать про работу с аппаратным модулем FFT (БПФ). Используя этот модуль, можно получить спектр входного сигнала. Для обработки и отображения полученных от FFT данных используется SOPC с софтовым процессором NIOS II. Данный проект является продолжением предыдущего: Захват данных от АЦП с использованием NIOS II.



Читать дальше
  • +10
  • 21 августа 2014, 22:42
  • citizen

Быстрое Преобразование Фурье, java

Решил я вплотную подступиться к Цифровой Обработке Сигналов.
Достаточно долгое время не мог понять, как реализовать алгоритм БПФ. Математика была понятна, но просмотрев готовые решения, понять как они работают не удавалось. На какое-то время необходимость в этом отпала, а потом снова появилась.
Ко второму заходу опыта программирования стало уже немного большое, и, наверно, поэтому мне удалось самому написать программу, выполняющую БПФ на java (код без особого труда можно портировать на C++ или C).
Я не буду выводить основные формулы и доказывать, что они верны — с этим справляются на многих сайтах и во многих книжках (по крайней мере лично у меня это не вызвало трудностей).
Основные формулы1

Я просто расскажу о том, как пользуясь этими формулами я писал саму программу, с какими трудностями столкнулся и как их преодолел.
Статья, по сути предназначена для тех кто:
1. Прочитал и понял математику, стоящую за БПФ.
2. Понял алгоритм бит-реверса.
3. Понял почему граф преобразования выглядит именно так

Первое, что я сделал — поставил основную задачу «Разработать программу быстрого преобразования Фурье на java». Затем приступил ко второму этапу: сбору информации. Прочитав доказательства и поняв математику, стоявшую за алгоритмом, я понял, следующие вещи:
1. Алгоритм можно спроектировать пользуясь парадигмой Divide and Conquer.
2. В данной парадигме задачу можно решить рекурсивно или итеративно.
Рекурсивный подход, как мне кажется, в какой-то мере проще в написании и понимании, но приведет к некоторым потерям в скорости из-за вложенных вызовов методов (функций).

Напомню или расскажу тем, кто не в курсе, что парадигма Divide and Conquer (Разделяй и Властвуй) сводится к трем общим этапам.
1. Этап разделения задачи на более простые задачи меньшего размера.
2. Этап решения всех этих задач.
3. Этап объединения результатов всех меньших задач в общий результат.

БПФ же можно разбить на три основных этапа:

1. Перестановка входных отсчетов (значений в массиве). Почему это нужно много рассказывать не буду, но в общем, как я понял, это позволяет сэкономить память, т.к. в таком случае не требуется массив для хранения промежуточных результатов и в целом упрощает написание программы.
2. БПФ основано на том факте, что если N = 2^n (т.е. 1,2,4,...,1024...2^n), то N-точечное ДПФ (т.е. количество отсчетов исходных данных равно числу n) можно разбить на два ДПФ размером вдвое меньше (2^(n-1)). В этом и заключается второй этап парадигмы. Мы разбиваем ДПФ, пока не приходим к базовому случаю — ДПФ от двух отсчетов, которое сводится к сложению и вычитанию двух элементов массива.
3. Объединение результатов каждого из разбиений.

Дальше можно собственно приступать к разработке алгоритма предварительно разбив его на эти отдельные задачи.
1. Разработать метод перестановки входных отсчетов с помощью алгоритма бит-реверса.
Если непонятно почему бит-реверс, пишите в коммантариях.
Собственно алгоритм бит-реверса

i — целое число, в котором надо переставить t-битов.

private static int reverse(int i, int t) {
		// TODO Auto-generated method stub
	    int Shift = t - 1;
	    int LowMask = 1;
	    int HighMask = 1 << Shift;
	    int R;
	    for(R = 0; Shift >= 0; LowMask <<= 1, HighMask >>= 1, Shift -= 2)
	        R |= ((i & LowMask) << Shift) | ((i & HighMask) >> Shift);
	    return R;
	}


А это, собственно, перестановка значений.

for(int i = 1; i < Nmax-1; i++)
		{
		    int J = reverse(i,powerOfTwo);           // reverse ����������� ���� � I � �������� �������
		    if (i >= J)                     // ���������� ��� ��������������
		    	continue;
		    double S = array[i]; array[i] = array[J]; array[J] = S;  // ������������ ��������� xI � xJ
		}


Т.е. здесь мы берем число i, переставляя в нем биты, получаем число J. i и J — это индексы двух элементов массива, которые нужно поменять местами. Ну и меняем их местами. Повторяем эту процедуру пока не пройдемся по всем элеметам массива, т.е. пока i не станет больше или равно Nmax-1.

А дальше начинается самое интересное.

*************************************************************
Статья пока недописана, но публикую, т.к. интересуют комментарии. О чем написать подробнее, что может вызывать вопросы и пр. Любые мнения и помощь приветствуются!