СуперПупер ЦОС на пальцах.
http://cxem.net/mc/mc137.php ДПФ пример.
В очередной раз решил найти как делать дискретное преобразование Фурье. И опять огромное количество ссылок на наукообразные объяснения. Как дети малые, переписывают друг у друга с различной степенью понимания. Хоть бы где формулу Эйлера написали, что бы не вводить в ступор мнимыми отрицательными степенями экспоненты программеров не отягощенных комплексной алгеброй и прочим. Вот решил себя избавить от этого и добавить понятную инфу о том как численно реализовать ДПФ на микроконтроллере. Тут разные ссылки для любопытных с инфой разной, не принципиальной, степени точности :
http://shackmaster.narod.ru/fourier.htm , хотя есть вопросы к коэф. в обратном преобразовании, но зато в прямом минус правильный.
http://speech-text.narod.ru/chap2_3.html
http://www.super-profi.com/index.php?option=com_content&view=article&id=3:2011-07-28-11-17-12&catid=2:2011-07-14-13-50-23
Итак, начнем с постановки задачи. Самая первая и очевидная задача это анализировать спектр некоего сигнала. Пусть у нас есть источник звукового сигнала и мы хотим на дисплее видеть его спектр в реальном времени. Рассмотрим необходимые этапы. Первое это оцифровка входного сигнала, второе это анализ его спектра и третье это вывод результатов на дисплей.
Для ясности и во избежание ошибочного толкования рассмотрим ситуацию, когда у нас есть звуковой сигнал в диапазоне частот к примеру от 30Герц до 20000Герц и максимальной амплитудой 1 вольт. Амплитуду нам надо знать, что бы правильно выбрать коэф. усиления предусилителя для АЦП так, что-бы максимально использовать его разрядность. Например у нас 16 битное АЦП настроенное так , что максимальное число на выходе АЦП (2 в 16 степени) соответствует входному напряжению в 5 вольт. Тогда нам надо усилить наш входной сигнал на 5 , чтобы его максимальная амплитуда увеличилась с 1 го до 5 ти вольт. Хочется еще коснуться вопроса о частотном спектре сигнала. ДПФ накладывает ограничения на максимальную частоту в спектре, которую он может выделить в зависимости от того с какой частотой мы оцифровываем сигнал. Например, если мы преобразовываем текущее напряжение на входе АЦП 1000 раз в секунду, т.е. с частотой 1000 герц , то максимальную частоту, которую ДПФ может распознать в таком массиве данных это 1000/2 =500 Гц.(или если частота оцифровки это F , то максимальная частота во входном сигнале не должна быть выше чем F деленное на два) И если во входном сигнале будут спектральные составляющие с частотой выше чем половина частоты оцифровки, то ДПФ будет работоть с ошибкой. Из этого следует, что перед тем как строить систему анализа спектра, мы как минимум должны знать максимальную частоту с которой наше АЦП может преобразовывать входной сигнал. Тогда, чтобы избежать ошибок работы ДПФ на вход АЦП мы поставим фильтр , который будет ограничивать частоту входного сигнала частотой оцифровки деленной пополам. Применительно к нашему примеру мы предположили, что у нас максимальная частота в спектре будет 20000Герц, соответственно минимальная частота преобразования АЦП должна быть 40000Герц.(т.е. время преобразования 1/40000 = 25 микросекунд или меньше) и входной фильтр с частотой пропускания от 0 до 20000Герц. Теперь, после того как мы отфильтровали, оцифровали и записали наш сигнал в массив данных мы можем начать его анализировать.
После анализа мы хотим получить набор частот из которого состоит наш сигнал, их амплитуды и фазы. Пусть мы записывали наш сигнал "x" в течении 1 секунды и соответственно записали 40000 значений "x"(выборок) . т.е. х = х[0]...x[39999]. Хочется отметить, что существует связь между количеством выборок "х" и спектральным разрешением. Например если у нас частота оцифровки равна 40000Гц и количество выборок равно 40000, то мы получим спектральное разрешение равное (F/2)/(N/2) где F это наша частота оцифровки, а N это количество выборок, т.е. (40000/2)/(40000/2)=1Герц.,(или не так тривиально, пусть мы записывали сигнал 0.01секунды, тогда у нас набралось 400 выборок х и соответственно после анализа мы получим значения 400/2=200 спектральных составляющих в диапазоне от 0 до 20000 Герц, т.е.спектральное разрешение будет 20000/200 = 100 герц ) .
Собственно формула:
N-1
ReX[k]=(1/(N/2))*∑ x[i]*cos(2*PI*k*i/N) ;
i=0
N-1
ImX[k]=(1/(N/2))*∑ x[i]*sin(2*PI*k*i/N) ;
i=0
где :
PI - число ПИ , 3.14....
ReX - это так называемая "вещественная" составляющая сигнала. Она имеет некое отношение к амплитуде спектральной составляющей.
ImX - это так называемая "мнимая" составляющая сигнала. Оно имеет некое отношение к фазе спектральной составляющей.
N - полное число выборок( в нашем примере 40000).
k - текущий номер спектральной составляющей. Меняется от 0 до (N/2 - 1).
i - текущий номер выборки, соответственно от 0 до (N-1).
Реальная амплитуда спектральной составляющей с номером "к" из ReX и ImX получается как квадратный корень из суммы их квадратов : A[k] = sqrt( ReX[k]^2 + ImX[k]^2)/(N/2). А фаза, если вам интересно, как арктангенс отношения мнимой части к реальной. Fi[k] = arctg( ImX[k]/ReX[k]) .
Но фаза мне не интересна. Да и вычислять ее утомительно. Мы в последствии пойдем другим путем.
Для ясности амплитуда и фаза это координаты точки в полярных координатах, а ReX и ImX это координаты точки в декартовой системе и строго говоря нам для вывода информации на дисплей удобней использовать декартову систему, а расчитывать непосредственно амплитуду и фазу лишь по необходимости, если она возникнет.
Собственно все что нам надо, это в цикле все аккуратно посчитать и правильно интерпретировать.
пример на Си :
main()
{
int k=0; //текущий номер спектральной составляющей.
int i=0; //текущий номер выборки.
int N=40000;//общее количество выборок.
float ReX[N/2]; // массив вещественных частей спектральных составляющих.
float ImX[N/2]; // массив мнимых частей спектральных составляющих.
float x[N]; //массив содержащий наши оцифрованные данные.
/* функция которая оцифровывает наш сигнал и записывает его в массив x .*/
SetADCData(x,N);
k=0;
while(k < N/2 ) // главный цикл для вычисления всех спектральный компонент.
{
i=0;
ReX(k) = 0;// в ней мы будем накапливать вещественную часть спектральной компоненты.
ImX(k) = 0;// в ней мы будем накапливать мнимую часть спектральной компоненты.
while( i < N)// тут мы вычисляем одну спектральную компоненту.
{
ReX(k)=ReX(k)+x(i)*cos(2*PI*k*i/N);
ImX(k)=ImX(k)+x(i)*sin(2*PI*k*i/N);
i++;
}
ReX(k) = ReX(k)/(N/2);
ImX(k) = ImX(k)/(N/2);
k++;
}
}
И ПОМНИТЕ О ПЕРЕПОЛНЕНИИ РАЗРЯДНОСТИ.
Вот собственно мы и вычислили наши спектральные компоненты ReX и ImX . Что теперь с ними делать? Сначала надо разобраться в соответствии частоты и номера спектральной компоненты. Для 40000 выборок в нашем примере шаг частоты будет равен 1 Герц. Соответственно значения, например ReX(3) и ImX(3) будут для спектральной компоненты с частотой 3 Герца, а ReX(3500) и ImX(3500) с частотой 3500 Герц. Для случая, если у нас только N= 400 выборок, как было рассмотрено выше спектральное разрешение будет 100 герц , соответственно ReX(0) и ImX(0) будут для спектральной компоненты с частотой 0 Герца, ReX(1) и ImX(1) с частотой 100 Герц, ReX(2) и ImX(2) с частотой 200 Герц, ReX(10) и ImX(10) с частотой 1000 Герц, ReX(k) и ImX(k) с частотой k*100 Герц, где максимальный k=N/2 -1, т.е. в этом случае k = N/2 -1 = 400/2 -1 = 199, что соответствует ReX(199) и ImX(199) для спектральной компоненты с частотой 19900 Герца.
Теперь , что бы визуализировать спектр нам надо получить амплитуду его спектральных компонент:
A[k] = sqrt( ReX[k]^2 + ImX[k]^2)/(N/2) ,
хотя , если вывести ReX[k] и ImX[k] как две гистограммы на дисплей, будет уже очень информативно. Все зависит от того, что именно вы в спектре ищете.
A[k] для ясности это расстояние от центра полярной системы координат до точки , которая соответствует текущей амплитуде. ( длинна вектора, а фаза это соответственно угол наклона этого вектора. )
Возможно последует продолжение о том как это все использовать в металлоискателе...
Мультичастотный металлоискатель.
Обычно металлоискатели работающие на принципе индукционного баланса (ИБМ) используют только одну частоту и соответственно настроенную в резонанс катушку. Также известно, что существует зависимость между минимальным размером цели которую может увидеть детектор и частотой на которой он работает(чем выше частота тем геометрически меньшие объекты можно увидеть, но тем выше влияние земли и меньше дальность обнаружения при тех же размерах катушки), также известно, что при всех прочих равных, отклик от цели зависит от частоты , т.е. одна и таже цель дает разный по амплитуде и фазе отклик при разных частотах. И существуют частота на которой отклик от цели максимален. И эта частота зависит также от материала и состояния цели. Таким образом выбор частоты для детектора это всегда компромис, как минимум, между дальность обнаружения и минимальным размером обнаруживаемого объекта. Собственно очевидно, что если у нас детектор одновременно излучает некоторый, разумно выбранный спектр частот, то как результат измерения мы будем иметь целый набор спектральных амплитуд и фаз зависящих от разнообразных параметров нашей цели и окружающего ее грунта. Единственный недостаток, это то , что невозможно в таком случае использавать резонансные свойства катушки, но, как показывает практика , нерезонансные детекторы благополучно работают и даже ходят в числе супер профи.
Конечно, для реализации этой плодотворной идеи нам надо нехилый контроллер, который сможет шустро все считать, выводить на экран, гудеть динамиком в зависимости от того, что он там насчитал... Но в наше время использавать ARM для такой задачи самое оно.
Вот собственно и весь детектор. С одной ножки арма подаем сигнал с заранее известным спектром на передающую катушку детектора, потом с приемной катушки снимаем сигнал от цели, усиливаем, фильтруем его, подаем на вход АЦП арма и дальше обрабатываем его ДПФ, причем не по всему спектру, а только по тем частотам, которые входят в нами сформированный сигнал. ( Собственно синхронный детектор это можно сказать схемотехнический аналог ДПФ, но он детектирует только одну спектральную составляющую) В результате получаем набор амплитуд и фаз спектральных составляющих нашего входного сигнала измененных взаимодействием с целью. Дальше, набирая статистику и используя регрессирнные алгоритмы можно построить очень даже хорошие дискриминаторы по типу цели и наконец то научиться отличать монету от пивного язычка или пивной пробки. Плюс ко всему расширить диапазон чувствитетьности к мелким объектам и уменьшить влияние грунта. Как результат - увеличить дальность обнаружения или , вернее сказать стабильность, независимость от грунта и материала цели.
Со временем, в светлом будущем, я продолжу этот проект.
Вот и пришло светлое будущее. 30.07.2013.
Реализацию будем делать на STM32F103VCT6 на борде HY-Smart( У меня их три, так что это достаточный аргумент при выборе платформы). У этой борды на борту есть цветной LCD не котором можно рисовать результаты наших вычислений и замечательный аудио кодек WM8731. Его теоретически можно использовать как ЦАП для генерации сигналов в катушку детектора и как качественный АЦП для оцифровки отклика от цели. Единственно что надо сделать, это подключить I2S интерфейс АЦП к процессору, т.к. в оригинале подключен только интерфейс ЦАП.(пути китайских разработчиков неисповедимы).
Разберемся, что куда подключать.
Наши задачи первого этапа:
1. Научиться оцифровывать произвольный сигнал на входе аудио кодека.
1.1 Подключить интерфейс I2S к АЦП аудио кодека.
1.1.1 Подключить ADCDAT к I2S3_SD, а BL_CNT подключить к свободному GPIO.
1.1.2 Внести изменения в драйвера LCD,что бы обеспечить поддержку BL_CNT на новом GPIO.
1.2 Подключить аудио вход кодека к аудио разъему.
1.3 Решить проблемы с конфликтами при подключении.
1.4 Найти в исходниках или создать новый проект с примером как читать
данные из АЦП аудио кодека по I2S.
2. Научиться генерировать произвольный сигнал на выходе аудио кодека.
2.1 Используя 1.4 добавить возможность генерации сигнала
произвольной формы на выходе ЦАП аудио кодека.
1.Учимся оцифровывать произвольный сигнал на входе аудио кодека.
1.1 Подключаем интерфейс I2S к АЦП аудио кодека.
Тут описание кодека на русском: http://radiotech.kz/blogs/alexander/dsp-na-fpga-chast-1-audiokodek-wm8731.html .
Вот часть схемы с аудио кодеком. Рис. 1 :
Нам надо подключить ADCDAT и ADCLRC к соответствующим контактам I2S. Учитывая мультифункциональность GPIO STM32 трудно надеяться, что ножки I2S3 процессора свободны. Смотри часть схемы процессора. Рис. 2:
На схеме нас интересует куда подключены сигналы I2S3.
I2S3_WS подключен к JTDI, это сигнал JTAG, просто создаст сложности с отладкой. Ничего менять не надо.
I2S3_CK подключен к JTDO, это тоже сигнал JTAG, тоже ничего не меняем.
I2S3_SD подключен к BL_CNT. А вот тут уже проблема. Этот сигнал, в соответствии со схемой используется где-то в управлении LCD. И что бы его безболезненно отключить надо найти свободный GPIO и присоединить BL_CNT туда, а так же внести изменения в драйвера управления LCD, что бы драйвер знал на какой GPIO теперь присоединен BL_CNT. То есть добавились еще задачи к нашему проекту.
Теперь бы еще надо понимать, как работает I2S и что куда правильно подключать.Собственно говоря вопрос в подключении I2Sx_CK - ножка синхроимпульсов. Этот сигнал может передаваться в аудио кодек, когда он ведомый и тогда мы имеем проблему как подключить I2S2_CK и I2S3_CK на один вход. Или аудио кодек может быть ведущим и посылать синхроимпульсы I2S к процессору, что облегчает ситуацию, но надо прояснить, как писать и читать данные используя прерывания и DMA(очень хочется DMA).
Что бы разобраться что и как инициализировать, смотрим примеры.(Например тут http://work.ripz.info/STM32/Examples/ или на диске который пришел с бордой. Тут еще хотелось бы заметить, что примеры в основном на Keil, а я люблю IAR, поэтому еще надо все под него переделать. И это хорошо, добавляет опыта.)
Самое время создать место для работы и хранения проекта.
Cсылки по инициализации DMA:
http://www.st.com/st-web-ui/static/active/en/resource/technical/document/application_note/DM00040808.pdf
http://habrahabr.ru/post/146501/
http://chipspace.ru/stm32-spi-stdperiph_lib/
http://kazus.ru/forums/showthread.php?t=95313
http://microtechnics.ru/stm32-uchebnyj-kurs-dma/
http://we.easyelectronics.ru/STM32/gui-dlya-vstraivaemyh-sistem.html
I2S2 Tx обслуживается модулем DMA 1 каналом 5. Идеологически инициализация выглядит так:
0. Генерируем форму сигнала и складываем ее в буфер.
1. Инициализируем I2S.
2. Инициализируем DMA.
3. Включаем I2S.
4. Подключаем I2S_Tx к каналу DMA.
5. Включаем DMA.
После включения DMA начинается передача данных в I2S Tx регистр.
В проекте DMA инициализирован в циклическом режиме, соответственно он циклически пишет данные из буфера в кодек. На текущий момент (ревизия 11 проекта) добавлена функция инициализации I2S + DMA. Проект умеет при помощи DMA через I2S(I2S на борде в мастер моде ) с частотой выборок 96000Гц генерировать трех частотный сигнал 8000Гц + 5400Гц+2700Гц. Спектр сигнала можно посмотреть на осциллографе в режиме FFT.
Следующим шагом надо решить, как сделать I2S на чипе кодека ведущим, а I2S процессора ведомым с сохранением всех преимуществ DMA.
http://www.multixmedia.org/test/harakiri-doxigen/group___s_p_i___exported___constants.html
Далее, в этой ревизии (13) коммуникация работает как и хотелось. I2S борде установлена как ведомая, а на кодеке как ведущая. DMA включено и работает. Проблема была в том, что не совсем понятно в каком режиме работает I2S на борде. Выглядит это так, что он все время передает два 16 битных слова в 32 битном фрейме. Хотя есть у него режим, когда он должен передавать одно 16 битное слово в 32 битном фрейме. Но что то не пошло.
Проект генерирует спектр частот 3 кГц + 4 кГц + 6 кГц + 8 кГц + 12 кГц + 18 кГц . Сигнал гораздо стабильнее чем был, потому что используется кварцевый генератор кодека.
Теперь можно пробовать запустить микрофонный вход или линейный вход. Пока не совсем ясно , что будет лучше. Можно подавать сигнал прямо на микрофонный вход, а можно использовать некий пред усилитель и использовать линейный вход. Пока что буду ориентироваться на микрофонный вход.
А вот, отвлекаясь от темы нашел картинку замечательную http://www.md4u.ru/viewtopic.php?f=77&t=7544 :
http://www.reviewdetector.ru/index.php?showtopic=19278&st=4700
Примерная архитектура системы:
Хочется отвлечься и взглянуть на архитектуру системы в целом. У нас будет графический дисплей для вывода информации, настройки и прочего. Т.е. надо сделать графический интерфейс. Плюс будет расчетная часть которая будет вычислять амплитуды гармоник и сдвиги фаз в сигнале. К этому надо добавить чисто системные задачи, такие как вычисление времени, контроль батарей и прочее. Все это можно сделать в супер цикле, но мне видится более интересным и правильным реализовать все при помощи RTOS. Можно будет разбить все на задачи и запустить из в операционной системе реального времени.
FreeRTOS:
http://www.freertos.org/portstm32iar.html
http://we.easyelectronics.ru/STM32/stm32-freertos.html
http://microtechnics.ru/stm32-uchebnyj-kurs-freertos-chast-1/
http://www.be-em.ru/%D1%83%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0-freertos-%D0%BD%D0%B0-stm32f100rbt6b/
http://akb77.com/g/stm32/maple-stm32-library-for-freertos/
http://habrahabr.ru/post/129105/
http://wiki.fh-up.ru/index.php?title=FreeRTOS
http://we.easyelectronics.ru/STM32/primery-inicializacii-periferii-stm32f103-bez-ispolzovaniya-bibliotek.html
http://we.easyelectronics.ru/STM32/stm32-organizaciya-virtualnogo-com-porta.html
http://microsin.net/programming/ARM/freertos-part1.html
http://www.embed.com.ua/komplektuyushhie/parallelnyiy-interfeys-8080-dlya-tft-3-2-inch-320x240/
http://we.easyelectronics.ru/STM32/atomarnye-operacii-v-cortex-m3.html#cut
Как то все застопорилось.
Перепишем наш план ближе к реальности:
1. Научиться генерировать произвольный сигнал на выходе аудио кодека.
1.1 Кодек должен быть в режиме "Master". Так гораздо меньше девиация генерируемого сигнала.
1.2 Добавить возможность генерации периодического сигнала произвольной формы на выходе ЦАП аудио кодека, используя DMA.
По результатам тестов на реальной катушке, выходной сигнал из кодека слабоват( на Rx катушке при сильном рассогласовании( большой кусок железа на катушке) осцилограф ничего не видит.) Следовательно надо добавить усилитель. Хорошо бы мост на MOSFET, но пока буду использовать обычный модуль звукового усилителя на TDA.
Следующий пункт претерпел изменения.
Для работы в входными сигналами необходимо их как то визуализировать. Для этого надо написать подсистему графического дисплея. Попутно надо решить задачу кнопочек на тачскрине. Уже напрашивается RTOS.
Таким образом дальнейшая работа пойдет так:
2. Запустить FreeRTOS на борде.
2.1 Разработать и добавить структуру тасков для обслуживания GUI.
2.2 Добавить GUI.
2.3 Запустить тачскрин.
2.4 Разработать интерфейс( положение и функционал кнопочек и окошек).
2.5 Добавить возможность просматривать входной сигнал в графическим виде.
3. Научиться оцифровывать произвольный сигнал на входе аудио кодека.
3.1 Подключить интерфейс I2S к АЦП аудио кодека.
3.1.1 Подключить ADCDAT к I2S3_SD, а BL_CNT подключить к свободному GPIO.
3.1.2 Внести изменения в драйвера LCD,что бы обеспечить поддержку BL_CNT на свободном GPIO.
3.2 Подключить аудио вход кодека к аудио разъему.
3.3 Решить проблемы с конфликтами при подключении.
3.4 Найти в исходниках или создать новый проект с примером как читать
данные из АЦП аудио кодека по I2S.
WiKi для FreeRTOS
http://wiki.fh-up.ru/index.php?title=FreeRTOS
Продвинулся в разработке GUI. Добавил FreeRTOS, два таска. Один таск печатает на экране координаты нажатия, второй таск обслуживает очередь графических компонент. Компоненту сделал пока только одну TextButton с кучей параметров, на которые еще надо бы написать обработчики, если они вообще понадобятся в рамках проекта.
Линк на проект(revision 28): http://digital-metal-detector.googlecode.com/svn/trunk/%20digital-metal-detector%20--username%20seagsm_trash@mail.ru/touch_screen/
А пока это выглядит вот так :
Возможно, для ускорения реакции, будет необходимо переключить тачскрин на прерывание. Пока что опрос нажатия происходит в таске в вечном цикле.
Соответственно, промежуточный отчет выглядит так :
2.1 Разработать и добавить структуру тасков для обслуживания GUI. - Практически выполнено. Сомнения больше философского характера.
2.3 Запустить тачскрин. Выполнено.
2.2 Добавить GUI. В процессе.
Есть кнопка, теперь хочется нормальный элемент для отображения графиков. Еще, вероятно, понадобится CheckBox, может ползунок какой. В процессе разберемся. Лишнего делать не будем.
Да и время начинать думать над "2.4 Разработать интерфейс( положение и функционал кнопочек и окошек)." Заранее, до того, как окажется , что все надо переделывать.
Для начала разработки интерфейса очень полезно написать требования, которым он должен удовлетворять.
Описание как работают фильтры:
https://embedderslife.wordpress.com/2014/06/11/fir_filters_2_ru/
В очередной раз решил найти как делать дискретное преобразование Фурье. И опять огромное количество ссылок на наукообразные объяснения. Как дети малые, переписывают друг у друга с различной степенью понимания. Хоть бы где формулу Эйлера написали, что бы не вводить в ступор мнимыми отрицательными степенями экспоненты программеров не отягощенных комплексной алгеброй и прочим. Вот решил себя избавить от этого и добавить понятную инфу о том как численно реализовать ДПФ на микроконтроллере. Тут разные ссылки для любопытных с инфой разной, не принципиальной, степени точности :
http://shackmaster.narod.ru/fourier.htm , хотя есть вопросы к коэф. в обратном преобразовании, но зато в прямом минус правильный.
http://speech-text.narod.ru/chap2_3.html
http://www.super-profi.com/index.php?option=com_content&view=article&id=3:2011-07-28-11-17-12&catid=2:2011-07-14-13-50-23
Итак, начнем с постановки задачи. Самая первая и очевидная задача это анализировать спектр некоего сигнала. Пусть у нас есть источник звукового сигнала и мы хотим на дисплее видеть его спектр в реальном времени. Рассмотрим необходимые этапы. Первое это оцифровка входного сигнала, второе это анализ его спектра и третье это вывод результатов на дисплей.
Для ясности и во избежание ошибочного толкования рассмотрим ситуацию, когда у нас есть звуковой сигнал в диапазоне частот к примеру от 30Герц до 20000Герц и максимальной амплитудой 1 вольт. Амплитуду нам надо знать, что бы правильно выбрать коэф. усиления предусилителя для АЦП так, что-бы максимально использовать его разрядность. Например у нас 16 битное АЦП настроенное так , что максимальное число на выходе АЦП (2 в 16 степени) соответствует входному напряжению в 5 вольт. Тогда нам надо усилить наш входной сигнал на 5 , чтобы его максимальная амплитуда увеличилась с 1 го до 5 ти вольт. Хочется еще коснуться вопроса о частотном спектре сигнала. ДПФ накладывает ограничения на максимальную частоту в спектре, которую он может выделить в зависимости от того с какой частотой мы оцифровываем сигнал. Например, если мы преобразовываем текущее напряжение на входе АЦП 1000 раз в секунду, т.е. с частотой 1000 герц , то максимальную частоту, которую ДПФ может распознать в таком массиве данных это 1000/2 =500 Гц.(или если частота оцифровки это F , то максимальная частота во входном сигнале не должна быть выше чем F деленное на два) И если во входном сигнале будут спектральные составляющие с частотой выше чем половина частоты оцифровки, то ДПФ будет работоть с ошибкой. Из этого следует, что перед тем как строить систему анализа спектра, мы как минимум должны знать максимальную частоту с которой наше АЦП может преобразовывать входной сигнал. Тогда, чтобы избежать ошибок работы ДПФ на вход АЦП мы поставим фильтр , который будет ограничивать частоту входного сигнала частотой оцифровки деленной пополам. Применительно к нашему примеру мы предположили, что у нас максимальная частота в спектре будет 20000Герц, соответственно минимальная частота преобразования АЦП должна быть 40000Герц.(т.е. время преобразования 1/40000 = 25 микросекунд или меньше) и входной фильтр с частотой пропускания от 0 до 20000Герц. Теперь, после того как мы отфильтровали, оцифровали и записали наш сигнал в массив данных мы можем начать его анализировать.
После анализа мы хотим получить набор частот из которого состоит наш сигнал, их амплитуды и фазы. Пусть мы записывали наш сигнал "x" в течении 1 секунды и соответственно записали 40000 значений "x"(выборок) . т.е. х = х[0]...x[39999]. Хочется отметить, что существует связь между количеством выборок "х" и спектральным разрешением. Например если у нас частота оцифровки равна 40000Гц и количество выборок равно 40000, то мы получим спектральное разрешение равное (F/2)/(N/2) где F это наша частота оцифровки, а N это количество выборок, т.е. (40000/2)/(40000/2)=1Герц.,(или не так тривиально, пусть мы записывали сигнал 0.01секунды, тогда у нас набралось 400 выборок х и соответственно после анализа мы получим значения 400/2=200 спектральных составляющих в диапазоне от 0 до 20000 Герц, т.е.спектральное разрешение будет 20000/200 = 100 герц ) .
Собственно формула:
N-1
ReX[k]=(1/(N/2))*∑ x[i]*cos(2*PI*k*i/N) ;
i=0
N-1
ImX[k]=(1/(N/2))*∑ x[i]*sin(2*PI*k*i/N) ;
i=0
где :
PI - число ПИ , 3.14....
ReX - это так называемая "вещественная" составляющая сигнала. Она имеет некое отношение к амплитуде спектральной составляющей.
ImX - это так называемая "мнимая" составляющая сигнала. Оно имеет некое отношение к фазе спектральной составляющей.
N - полное число выборок( в нашем примере 40000).
k - текущий номер спектральной составляющей. Меняется от 0 до (N/2 - 1).
i - текущий номер выборки, соответственно от 0 до (N-1).
Реальная амплитуда спектральной составляющей с номером "к" из ReX и ImX получается как квадратный корень из суммы их квадратов : A[k] = sqrt( ReX[k]^2 + ImX[k]^2)/(N/2). А фаза, если вам интересно, как арктангенс отношения мнимой части к реальной. Fi[k] = arctg( ImX[k]/ReX[k]) .
Но фаза мне не интересна. Да и вычислять ее утомительно. Мы в последствии пойдем другим путем.
Для ясности амплитуда и фаза это координаты точки в полярных координатах, а ReX и ImX это координаты точки в декартовой системе и строго говоря нам для вывода информации на дисплей удобней использовать декартову систему, а расчитывать непосредственно амплитуду и фазу лишь по необходимости, если она возникнет.
Собственно все что нам надо, это в цикле все аккуратно посчитать и правильно интерпретировать.
пример на Си :
main()
{
int k=0; //текущий номер спектральной составляющей.
int i=0; //текущий номер выборки.
int N=40000;//общее количество выборок.
float ReX[N/2]; // массив вещественных частей спектральных составляющих.
float ImX[N/2]; // массив мнимых частей спектральных составляющих.
float x[N]; //массив содержащий наши оцифрованные данные.
/* функция которая оцифровывает наш сигнал и записывает его в массив x .*/
SetADCData(x,N);
k=0;
while(k < N/2 ) // главный цикл для вычисления всех спектральный компонент.
{
i=0;
ReX(k) = 0;// в ней мы будем накапливать вещественную часть спектральной компоненты.
ImX(k) = 0;// в ней мы будем накапливать мнимую часть спектральной компоненты.
while( i < N)// тут мы вычисляем одну спектральную компоненту.
{
ReX(k)=ReX(k)+x(i)*cos(2*PI*k*i/N);
ImX(k)=ImX(k)+x(i)*sin(2*PI*k*i/N);
i++;
}
ReX(k) = ReX(k)/(N/2);
ImX(k) = ImX(k)/(N/2);
k++;
}
}
И ПОМНИТЕ О ПЕРЕПОЛНЕНИИ РАЗРЯДНОСТИ.
Вот собственно мы и вычислили наши спектральные компоненты ReX и ImX . Что теперь с ними делать? Сначала надо разобраться в соответствии частоты и номера спектральной компоненты. Для 40000 выборок в нашем примере шаг частоты будет равен 1 Герц. Соответственно значения, например ReX(3) и ImX(3) будут для спектральной компоненты с частотой 3 Герца, а ReX(3500) и ImX(3500) с частотой 3500 Герц. Для случая, если у нас только N= 400 выборок, как было рассмотрено выше спектральное разрешение будет 100 герц , соответственно ReX(0) и ImX(0) будут для спектральной компоненты с частотой 0 Герца, ReX(1) и ImX(1) с частотой 100 Герц, ReX(2) и ImX(2) с частотой 200 Герц, ReX(10) и ImX(10) с частотой 1000 Герц, ReX(k) и ImX(k) с частотой k*100 Герц, где максимальный k=N/2 -1, т.е. в этом случае k = N/2 -1 = 400/2 -1 = 199, что соответствует ReX(199) и ImX(199) для спектральной компоненты с частотой 19900 Герца.
Теперь , что бы визуализировать спектр нам надо получить амплитуду его спектральных компонент:
A[k] = sqrt( ReX[k]^2 + ImX[k]^2)/(N/2) ,
хотя , если вывести ReX[k] и ImX[k] как две гистограммы на дисплей, будет уже очень информативно. Все зависит от того, что именно вы в спектре ищете.
A[k] для ясности это расстояние от центра полярной системы координат до точки , которая соответствует текущей амплитуде. ( длинна вектора, а фаза это соответственно угол наклона этого вектора. )
Возможно последует продолжение о том как это все использовать в металлоискателе...
Мультичастотный металлоискатель.
Обычно металлоискатели работающие на принципе индукционного баланса (ИБМ) используют только одну частоту и соответственно настроенную в резонанс катушку. Также известно, что существует зависимость между минимальным размером цели которую может увидеть детектор и частотой на которой он работает(чем выше частота тем геометрически меньшие объекты можно увидеть, но тем выше влияние земли и меньше дальность обнаружения при тех же размерах катушки), также известно, что при всех прочих равных, отклик от цели зависит от частоты , т.е. одна и таже цель дает разный по амплитуде и фазе отклик при разных частотах. И существуют частота на которой отклик от цели максимален. И эта частота зависит также от материала и состояния цели. Таким образом выбор частоты для детектора это всегда компромис, как минимум, между дальность обнаружения и минимальным размером обнаруживаемого объекта. Собственно очевидно, что если у нас детектор одновременно излучает некоторый, разумно выбранный спектр частот, то как результат измерения мы будем иметь целый набор спектральных амплитуд и фаз зависящих от разнообразных параметров нашей цели и окружающего ее грунта. Единственный недостаток, это то , что невозможно в таком случае использавать резонансные свойства катушки, но, как показывает практика , нерезонансные детекторы благополучно работают и даже ходят в числе супер профи.
Конечно, для реализации этой плодотворной идеи нам надо нехилый контроллер, который сможет шустро все считать, выводить на экран, гудеть динамиком в зависимости от того, что он там насчитал... Но в наше время использавать ARM для такой задачи самое оно.
Вот собственно и весь детектор. С одной ножки арма подаем сигнал с заранее известным спектром на передающую катушку детектора, потом с приемной катушки снимаем сигнал от цели, усиливаем, фильтруем его, подаем на вход АЦП арма и дальше обрабатываем его ДПФ, причем не по всему спектру, а только по тем частотам, которые входят в нами сформированный сигнал. ( Собственно синхронный детектор это можно сказать схемотехнический аналог ДПФ, но он детектирует только одну спектральную составляющую) В результате получаем набор амплитуд и фаз спектральных составляющих нашего входного сигнала измененных взаимодействием с целью. Дальше, набирая статистику и используя регрессирнные алгоритмы можно построить очень даже хорошие дискриминаторы по типу цели и наконец то научиться отличать монету от пивного язычка или пивной пробки. Плюс ко всему расширить диапазон чувствитетьности к мелким объектам и уменьшить влияние грунта. Как результат - увеличить дальность обнаружения или , вернее сказать стабильность, независимость от грунта и материала цели.
Со временем, в светлом будущем, я продолжу этот проект.
Вот и пришло светлое будущее. 30.07.2013.
Реализацию будем делать на STM32F103VCT6 на борде HY-Smart( У меня их три, так что это достаточный аргумент при выборе платформы). У этой борды на борту есть цветной LCD не котором можно рисовать результаты наших вычислений и замечательный аудио кодек WM8731. Его теоретически можно использовать как ЦАП для генерации сигналов в катушку детектора и как качественный АЦП для оцифровки отклика от цели. Единственно что надо сделать, это подключить I2S интерфейс АЦП к процессору, т.к. в оригинале подключен только интерфейс ЦАП.(пути китайских разработчиков неисповедимы).
Разберемся, что куда подключать.
Наши задачи первого этапа:
1. Научиться оцифровывать произвольный сигнал на входе аудио кодека.
1.1 Подключить интерфейс I2S к АЦП аудио кодека.
1.1.1 Подключить ADCDAT к I2S3_SD, а BL_CNT подключить к свободному GPIO.
1.1.2 Внести изменения в драйвера LCD,что бы обеспечить поддержку BL_CNT на новом GPIO.
1.2 Подключить аудио вход кодека к аудио разъему.
1.3 Решить проблемы с конфликтами при подключении.
1.4 Найти в исходниках или создать новый проект с примером как читать
данные из АЦП аудио кодека по I2S.
2. Научиться генерировать произвольный сигнал на выходе аудио кодека.
2.1 Используя 1.4 добавить возможность генерации сигнала
произвольной формы на выходе ЦАП аудио кодека.
1.Учимся оцифровывать произвольный сигнал на входе аудио кодека.
1.1 Подключаем интерфейс I2S к АЦП аудио кодека.
Тут описание кодека на русском: http://radiotech.kz/blogs/alexander/dsp-na-fpga-chast-1-audiokodek-wm8731.html .
Вот часть схемы с аудио кодеком. Рис. 1 :
Нам надо подключить ADCDAT и ADCLRC к соответствующим контактам I2S. Учитывая мультифункциональность GPIO STM32 трудно надеяться, что ножки I2S3 процессора свободны. Смотри часть схемы процессора. Рис. 2:
На схеме нас интересует куда подключены сигналы I2S3.
I2S3_WS подключен к JTDI, это сигнал JTAG, просто создаст сложности с отладкой. Ничего менять не надо.
I2S3_CK подключен к JTDO, это тоже сигнал JTAG, тоже ничего не меняем.
I2S3_SD подключен к BL_CNT. А вот тут уже проблема. Этот сигнал, в соответствии со схемой используется где-то в управлении LCD. И что бы его безболезненно отключить надо найти свободный GPIO и присоединить BL_CNT туда, а так же внести изменения в драйвера управления LCD, что бы драйвер знал на какой GPIO теперь присоединен BL_CNT. То есть добавились еще задачи к нашему проекту.
Теперь бы еще надо понимать, как работает I2S и что куда правильно подключать.Собственно говоря вопрос в подключении I2Sx_CK - ножка синхроимпульсов. Этот сигнал может передаваться в аудио кодек, когда он ведомый и тогда мы имеем проблему как подключить I2S2_CK и I2S3_CK на один вход. Или аудио кодек может быть ведущим и посылать синхроимпульсы I2S к процессору, что облегчает ситуацию, но надо прояснить, как писать и читать данные используя прерывания и DMA(очень хочется DMA).
Что бы разобраться что и как инициализировать, смотрим примеры.(Например тут http://work.ripz.info/STM32/Examples/ или на диске который пришел с бордой. Тут еще хотелось бы заметить, что примеры в основном на Keil, а я люблю IAR, поэтому еще надо все под него переделать. И это хорошо, добавляет опыта.)
Самое время создать место для работы и хранения проекта.
********************************
Вот, теперь там лежит проект, который в текущей ревизии умеет генерировать пилу. (Несомненно это великое достижение.) Причем это еще весьма далеко от того, что хочется. CPU I2S_TX в мастер моде, а хочется что бы был слейв, да еще и DMA его обслуживал и в ЦАП данные из буфера складывал. А к этому надо еще добавить второй I2S_RX для АЦП который будет входной сигнал оцифровывать и в буфер при помощи DMA складывать.Cсылки по инициализации DMA:
http://www.st.com/st-web-ui/static/active/en/resource/technical/document/application_note/DM00040808.pdf
http://habrahabr.ru/post/146501/
http://kazus.ru/forums/showthread.php?t=95313
http://microtechnics.ru/stm32-uchebnyj-kurs-dma/
http://we.easyelectronics.ru/STM32/gui-dlya-vstraivaemyh-sistem.html
I2S2 Tx обслуживается модулем DMA 1 каналом 5. Идеологически инициализация выглядит так:
0. Генерируем форму сигнала и складываем ее в буфер.
1. Инициализируем I2S.
2. Инициализируем DMA.
3. Включаем I2S.
4. Подключаем I2S_Tx к каналу DMA.
5. Включаем DMA.
После включения DMA начинается передача данных в I2S Tx регистр.
В проекте DMA инициализирован в циклическом режиме, соответственно он циклически пишет данные из буфера в кодек. На текущий момент (ревизия 11 проекта) добавлена функция инициализации I2S + DMA. Проект умеет при помощи DMA через I2S(I2S на борде в мастер моде ) с частотой выборок 96000Гц генерировать трех частотный сигнал 8000Гц + 5400Гц+2700Гц. Спектр сигнала можно посмотреть на осциллографе в режиме FFT.
Следующим шагом надо решить, как сделать I2S на чипе кодека ведущим, а I2S процессора ведомым с сохранением всех преимуществ DMA.
http://www.multixmedia.org/test/harakiri-doxigen/group___s_p_i___exported___constants.html
Далее, в этой ревизии (13) коммуникация работает как и хотелось. I2S борде установлена как ведомая, а на кодеке как ведущая. DMA включено и работает. Проблема была в том, что не совсем понятно в каком режиме работает I2S на борде. Выглядит это так, что он все время передает два 16 битных слова в 32 битном фрейме. Хотя есть у него режим, когда он должен передавать одно 16 битное слово в 32 битном фрейме. Но что то не пошло.
Проект генерирует спектр частот 3 кГц + 4 кГц + 6 кГц + 8 кГц + 12 кГц + 18 кГц . Сигнал гораздо стабильнее чем был, потому что используется кварцевый генератор кодека.
Теперь можно пробовать запустить микрофонный вход или линейный вход. Пока не совсем ясно , что будет лучше. Можно подавать сигнал прямо на микрофонный вход, а можно использовать некий пред усилитель и использовать линейный вход. Пока что буду ориентироваться на микрофонный вход.
А вот, отвлекаясь от темы нашел картинку замечательную http://www.md4u.ru/viewtopic.php?f=77&t=7544 :
http://www.reviewdetector.ru/index.php?showtopic=19278&st=4700
Примерная архитектура системы:
Хочется отвлечься и взглянуть на архитектуру системы в целом. У нас будет графический дисплей для вывода информации, настройки и прочего. Т.е. надо сделать графический интерфейс. Плюс будет расчетная часть которая будет вычислять амплитуды гармоник и сдвиги фаз в сигнале. К этому надо добавить чисто системные задачи, такие как вычисление времени, контроль батарей и прочее. Все это можно сделать в супер цикле, но мне видится более интересным и правильным реализовать все при помощи RTOS. Можно будет разбить все на задачи и запустить из в операционной системе реального времени.
FreeRTOS:
http://www.freertos.org/portstm32iar.html
http://we.easyelectronics.ru/STM32/stm32-freertos.html
http://microtechnics.ru/stm32-uchebnyj-kurs-freertos-chast-1/
http://www.be-em.ru/%D1%83%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0-freertos-%D0%BD%D0%B0-stm32f100rbt6b/
http://akb77.com/g/stm32/maple-stm32-library-for-freertos/
http://habrahabr.ru/post/129105/
http://wiki.fh-up.ru/index.php?title=FreeRTOS
http://we.easyelectronics.ru/STM32/primery-inicializacii-periferii-stm32f103-bez-ispolzovaniya-bibliotek.html
http://we.easyelectronics.ru/STM32/stm32-organizaciya-virtualnogo-com-porta.html
http://microsin.net/programming/ARM/freertos-part1.html
http://www.embed.com.ua/komplektuyushhie/parallelnyiy-interfeys-8080-dlya-tft-3-2-inch-320x240/
http://we.easyelectronics.ru/STM32/atomarnye-operacii-v-cortex-m3.html#cut
Как то все застопорилось.
Перепишем наш план ближе к реальности:
1. Научиться генерировать произвольный сигнал на выходе аудио кодека.
1.1 Кодек должен быть в режиме "Master". Так гораздо меньше девиация генерируемого сигнала.
1.2 Добавить возможность генерации периодического сигнала произвольной формы на выходе ЦАП аудио кодека, используя DMA.
По результатам тестов на реальной катушке, выходной сигнал из кодека слабоват( на Rx катушке при сильном рассогласовании( большой кусок железа на катушке) осцилограф ничего не видит.) Следовательно надо добавить усилитель. Хорошо бы мост на MOSFET, но пока буду использовать обычный модуль звукового усилителя на TDA.
Следующий пункт претерпел изменения.
Для работы в входными сигналами необходимо их как то визуализировать. Для этого надо написать подсистему графического дисплея. Попутно надо решить задачу кнопочек на тачскрине. Уже напрашивается RTOS.
Таким образом дальнейшая работа пойдет так:
2. Запустить FreeRTOS на борде.
2.1 Разработать и добавить структуру тасков для обслуживания GUI.
2.2 Добавить GUI.
2.3 Запустить тачскрин.
2.4 Разработать интерфейс( положение и функционал кнопочек и окошек).
2.5 Добавить возможность просматривать входной сигнал в графическим виде.
3. Научиться оцифровывать произвольный сигнал на входе аудио кодека.
3.1 Подключить интерфейс I2S к АЦП аудио кодека.
3.1.1 Подключить ADCDAT к I2S3_SD, а BL_CNT подключить к свободному GPIO.
3.1.2 Внести изменения в драйвера LCD,что бы обеспечить поддержку BL_CNT на свободном GPIO.
3.2 Подключить аудио вход кодека к аудио разъему.
3.3 Решить проблемы с конфликтами при подключении.
3.4 Найти в исходниках или создать новый проект с примером как читать
данные из АЦП аудио кодека по I2S.
WiKi для FreeRTOS
http://wiki.fh-up.ru/index.php?title=FreeRTOS
Продвинулся в разработке GUI. Добавил FreeRTOS, два таска. Один таск печатает на экране координаты нажатия, второй таск обслуживает очередь графических компонент. Компоненту сделал пока только одну TextButton с кучей параметров, на которые еще надо бы написать обработчики, если они вообще понадобятся в рамках проекта.
Линк на проект(revision 28): http://digital-metal-detector.googlecode.com/svn/trunk/%20digital-metal-detector%20--username%20seagsm_trash@mail.ru/touch_screen/
А пока это выглядит вот так :
Возможно, для ускорения реакции, будет необходимо переключить тачскрин на прерывание. Пока что опрос нажатия происходит в таске в вечном цикле.
Соответственно, промежуточный отчет выглядит так :
2.1 Разработать и добавить структуру тасков для обслуживания GUI. - Практически выполнено. Сомнения больше философского характера.
2.3 Запустить тачскрин. Выполнено.
2.2 Добавить GUI. В процессе.
Есть кнопка, теперь хочется нормальный элемент для отображения графиков. Еще, вероятно, понадобится CheckBox, может ползунок какой. В процессе разберемся. Лишнего делать не будем.
Да и время начинать думать над "2.4 Разработать интерфейс( положение и функционал кнопочек и окошек)." Заранее, до того, как окажется , что все надо переделывать.
Для начала разработки интерфейса очень полезно написать требования, которым он должен удовлетворять.
Описание как работают фильтры:
https://embedderslife.wordpress.com/2014/06/11/fir_filters_2_ru/
Комментарии
Отправить комментарий