Запись от AZM на субдомене electronics-and-mechanics |
Все записи на субдомене: Электроника и механика (записки от AZM) |
Для микроконтроллеров и не только: Алгоритм DDS - синтез точной произвольной частоты на микроконтроллере (генерация сигнала произвольной формы нужной частотой с точностью до долей герца) |
Бывает так, что нужно сгенерировать частоту 1750 Гц с большой точностью или 254.4 Гц, а тактовая частота микроконтроллера 8 МГц и как не дели 8000000 на 1750 или на 254.4, всё то целого не найти. Тем хуже, если нужно не просто генерировать частоту, а скажем синусоиду, выводя её на простейший ЦАП на 5 резисторах. На первый взгляд кажется, что можно получить из тактовой частоты F любую другую частоту только из ряда F/N, где N целое. Если же нужно генерировать, например, синусоидальный сигнал, скажем с 11 точками на колебание, то картина становиться ещё более печальной: F/11/N, где опять же N целое. Проще говоря, может показаться что из 8 МГц мы можем сгенерировать лишь прямоугольник с частотами: 4МГц; 2,666МГц; 2МГц; 1,6МГц; 1,333МГц; ... и нет никакой возможности сгенерировать например частоту 2212 Гц, ведь для этой частоты N = 3616.636528028933, а ближайшие частоты для целого N будут: 2212.39 и 2211.78. Для некоторых приложений такой точности вполне достаточно, но что делать если нам нужна точно частота 2212 Гц? Решение есть! Синтез точной частоты с использованием накапливающего сумматораЧто если мы будем не считать от 0 до N_делитель, а при достижении N менять состояние выхода порта для генерации частоты, а начнём прибавлять к некой переменной некое значением M и будем менять состояние порта каждый раз, как эта переменная будет достигать некой большой величины, скажем, 1875000000?Возьмём реальный случай: Таймер вызывает прерывание 31250 раз в секунду (8000000/256). Если мы будем использовать счёт от 0 до N_делитель, то в лучшем случае, у нас есть 31250 шаг. Если же мы начнём считать от 0 до 1875000000, каждый раз прибавляя к переменной некое значение, то у нас есть 1875000000 шагов или в 60000 раз больше! В качестве примера следующий код: // Пусть нам нужно вызвать некую процедуру точно 2212 раз в секунду, учитывая, что основную процедуру таймер у нас исполняет 31250 раз в секунду #define ADD_VAL_TO_CounterToGeneratorCall 132720000 // = 1875000000 / (31250 / 2212) unsigned long int CounterToGeneratorCall=0; // 0....1875000000 // Эту функцию надо вызвать точно 2212 раз в секунду void Func2212(){ } // Пусть эта функция вызывается из прерывания таймера 31250 раз в секунду void GenerateCall(){ CounterToGeneratorCall=CounterToGeneratorCall+ADD_VAL_TO_CounterToGeneratorCall; if (CounterToGeneratorCall >= 1875000000){ CounterToGeneratorCall=CounterToGeneratorCall-1875000000; Func2212(); } }Другими словами, прибавляя 132720000 к нашей переменной 31250 раз в секунду, мы будем достигать значения 1875000000 как раз 2212 раз в секунду. Естественно, мы можем выбрать меньшее максимальное значение, чем 1875000000 или большее. Чем это число больше, тем выше точность выходной частоты, чем меньше, тем ниже. Синтез точной частоты с произвольной формой выходного сигналаТеперь самое интересное - вывод сигнала не только точной частоты, но и произвольной формы (синус, треугольник, пила).Поясню на примере, в коде: // 32 значения амплитуды, которые и есть одно полное колебание будущего сигнала unsigned char MatrixToSignalOnePeriod[]={ // в данном случае это "пила" - плавное увеличение напряжения до максимума 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15 } // Пусть нам нужно генерировать частоту 233.6 Гц, учитывая, что основную процедуру таймер у нас исполняет 31250 раз в секунду #define ADD_VAL_TO_CounterToGeneratorSignal 1027383664 // = 4294967296 / (31250 / (233.6 * 32)) unsigned long int CounterToGeneratorSignal=0; void GenerateSignal(){ CounterToGeneratorSignal=CounterToGeneratorSignal+ADD_VAL_TO_CounterToGeneratorSignal; PORTC=MatrixToSignalOnePeriod[(CounterToGeneratorSignal>>10)]; }Вот так всё просто. Некоторые пояснения: "CounterToGeneratorSignal>>10" это то же самое что "CounterToGeneratorSignal / 134217728" и отбросить дробную часть, деление на сдвигах. Соответственно: "CounterToGeneratorSignal>>1" аналогично "CounterToGeneratorSignal / 2" "CounterToGeneratorSignal>>2" аналогично "CounterToGeneratorSignal / 4" "CounterToGeneratorSignal>>3" аналогично "CounterToGeneratorSignal / 8" "CounterToGeneratorSignal>>4" аналогично "CounterToGeneratorSignal / 16" дальше, думаю, поняли. То есть всё работает так: к переменной прибавляется ADD_VAL_TO до тех пор, пока не наступит переполнение, оно наступит на значении 4294967296, то есть 4294967295 и далее процесс повторяется. В общем, всё то же, что и в первом примере, только здесь оптимизировано. Если разобрались с первым примером, то с этим проблем быть не должно. Для генерации "синусоиды" массив содержал бы значения: unsigned char MatrixToSignalOnePeriod[]={ 7, 9, 10, 12, 13, 14, 14, 15, 15, 15, 14, 14, 13, 12, 10, 9, 8, 6, 5, 3, 2, 1, 1, 0, 0, 0, 1, 1, 2, 3, 5, 6 }Как сгенерировать массив синусов, я пишу в заметке: Для микроконтроллеров и не только: Алгоритм Герцеля, БПФ, и другие цифровые способы определить наличие искомой частоты в оцифрованном сигнале (цифровые фильтры) Что касается использования порта C, то что бы получить из 4 его младших ножек ЦАП (цифро-аналоговый преобразователь), нужно лишь собрать схему: Что ещё почитать по теме: Л.И. Ридико. DDS: Прямой цифровой синтез частоты Цифро-аналоговые преобразователи (ЦАП), теория и принципы работы на сайте Рынок микроэлектроники Задать вопрос или оставить свой комментарий можно на форуме: Форум на 27kb.ru - Гражданская-радиосвязь.РФ |
Добавлено: 4197 дн 17 час 35 мин 20 сек назад | Внесений правок: 5 | Последняя правка: 4040 дн 0 час 18 мин 7 сек назад |