- Что такое генератор функций DDS?
- Понимание работы ИС функционального генератора AD9833
- Компоненты, необходимые для создания генератора функций на базе AD9833
- Генератор функций на основе AD9833 - принципиальная схема
- Генератор функций на основе AD9833 - Код Arduino
- Тестирование генератора функций на базе AD9833
- Дальнейшие улучшения
Если вы такой же энтузиаст электроники, как я, который хочет поработать с различными электронными схемами, наличие приличного функционального генератора иногда становится обязательным. Но иметь такой - проблема, потому что такое базовое оборудование может стоить целое состояние. Изготовление собственного испытательного оборудования не только дешевле, но и отличный способ улучшить свои знания.
Итак, в этой статье мы собираемся создать простой генератор сигналов с Arduino и модулем функционального генератора AD9833 DDS, который может генерировать синусоидальные, квадратные и треугольные волны с максимальной частотой на выходе 12 МГц. И наконец, протестируем выходную частоту с помощью нашего осциллографа.
Ранее мы построили простой генератор синусоидальной волны, генератор прямоугольной волны и генератор треугольной волны с помощью базовых аналоговых схем. Вы можете проверить их, если ищете базовые схемы генератора сигналов. Кроме того, если вы хотите создать более дешевый генератор функций Arduino без использования модуля AD9833, вы можете проверить проект DIY Arduino Waveform Generator Project.
Что такое генератор функций DDS?
Как следует из названия, генератор функций - это устройство, которое может выводить сигнал определенной формы с определенной частотой при настройке. Например, представьте, что у вас есть LC-фильтр, для которого вы хотите проверить свою выходную частотную характеристику, вы можете легко сделать это с помощью генератора функций. Все, что вам нужно сделать, это установить желаемую выходную частоту и форму сигнала, затем вы можете повернуть его вниз или вверх, чтобы проверить реакцию. Это был всего лишь один пример, вы можете делать с ним больше вещей по мере того, как список продолжается.
DDS означает прямой цифровой синтез. Это тип генератора сигналов, в котором используются цифро-аналоговые преобразователи (ЦАП) для создания сигнала с нуля. Этот метод специально используется для генерации синусоидальной волны. Но микросхема, которую мы используем, может генерировать сигналы прямоугольной или треугольной формы. Операции, происходящие внутри микросхемы DDS, являются цифровыми, поэтому он может очень быстро переключать частоту или очень быстро переключаться с одного сигнала на другой. Это устройство имеет прекрасное частотное разрешение с широким частотным спектром.
Понимание работы ИС функционального генератора AD9833
В основе нашего проекта лежит интегральная схема программируемого генератора сигналов AD9833, разработанная аналоговыми устройствами. Это маломощный программируемый генератор сигналов, способный генерировать синусоидальные, треугольные и прямоугольные сигналы с максимальной частотой 12 МГц. Это уникальная ИС, которая может изменять выходную частоту и фазу с помощью всего лишь программного обеспечения. Он имеет 3-проводный интерфейс SPI, поэтому связь с этой ИС становится очень простой и легкой. Функциональная блок-схема этой ИС показана ниже.
Работа этой ИС очень проста. Если мы взглянем на приведенную выше функциональную блок-схему, мы увидим, что у нас есть фазовый аккумулятор, задача которого - хранить все возможные цифровые значения синусоидальной волны, начиная с 0 до 2π. Затем у нас есть SIN ROM, задача которого - преобразовать информацию о фазе, которая впоследствии может быть напрямую преобразована в амплитуду. SIN ROM использует цифровую информацию о фазе как адрес справочной таблицы и преобразует информацию о фазе в амплитуду. И, наконец, у нас есть 10-битный цифро-аналоговый преобразователь, работа которого заключается в получении цифровых данных из SIN ROM и преобразовании их в соответствующие аналоговые напряжения, которые мы получаем на выходе. На выходе у нас также есть переключатель, который мы можем включить или выключить с помощью небольшого программного кода. Об этом мы поговорим позже в статье.Детали, которые вы видите выше, являются очень урезанной версией того, что происходит внутри IC, и большинство деталей, которые вы видите выше, взяты из таблицы данных AD9833, вы также можете проверить ее для получения дополнительной информации.
Компоненты, необходимые для создания генератора функций на базе AD9833
Компоненты, необходимые для создания генератора функций на основе AD9833, перечислены ниже. Мы разработали эту схему с очень общими компонентами, что делает процесс репликации очень простым.
- Ардуино Нано - 1
- AD9833 Генератор функций DDS - 1
- 128 X 64 OLED-дисплей - 1
- Стандартный поворотный энкодер - 1
- Разъем DC Barrel Jack - 1
- Регулятор напряжения LM7809 - 1
- Конденсатор 470uF - 1
- Конденсатор 220 мкФ - 1 шт.
- Конденсатор 104пФ - 1
- Резистор 10 кОм - 6
- Тактильные переключатели - 4
- Винтовой зажим 5,04 мм - 1
- Женский заголовок - 1
- Источник питания 12 В - 1
Генератор функций на основе AD9833 - принципиальная схема
Полная принципиальная схема генератора функций на базе AD9833 и Arduino показана ниже.
Мы собираемся использовать AD9833 с Arduino для генерации желаемой частоты. И в этом разделе мы объясним все детали с помощью схемы; позвольте мне дать вам краткий обзор того, что происходит со схемой. Начнем с модуля AD9833. Модуль AD9833 является модулем генератора функций и подключен к Arduino согласно схеме. Для питания схемы мы используем микросхему стабилизатора напряжения LM7809 с приличным разделительным конденсатором, это необходимо, потому что шум питания может мешать выходному сигналу, что приводит к нежелательному выходу. Как всегда, мозгом этого проекта является Arduino. Для отображения заданной частоты и другой ценной информации мы подключили модуль дисплея OLED 128 X 64. Чтобы изменить частотный диапазон, мы используем три переключателя. Первый устанавливает частоту в Гц, второй устанавливает выходную частоту в КГц, а третий устанавливает частоту в МГц, у нас также есть еще одна кнопка, которую можно использовать для включения или отключения вывода. Наконец, у нас есть поворотный энкодер,и мы должны подключить к нему подтягивающий резистор, иначе эти переключатели не будут работать, потому что мы проверяем событие нажатия кнопки в методе объединения. Поворотный энкодер используется для изменения частоты, а тактильный переключатель внутри поворотного энкодера используется для выбора заданной формы сигнала.
Генератор функций на основе AD9833 - Код Arduino
Полный код, используемый в этом проекте, можно найти внизу этой страницы. После добавления необходимых файлов заголовков и исходных файлов вы сможете напрямую скомпилировать файл Arduino. Вы можете загрузить библиотеку ad9833 Arduino и другие библиотеки по приведенной ниже ссылке, либо вы можете использовать метод менеджера плат для установки библиотеки.
- Скачать AD9833 Библиотека Билла Вильямса
- Скачать библиотеку OLED SSD1306 от Adafruit
- Скачать библиотеку Adafruit GFX
Объяснение кода в ino. файл выглядит следующим образом. Во-первых, мы начнем с включения всех необходимых библиотек. За библиотекой для модуля AD9833 DDS следует библиотека для OLED, а математическая библиотека требуется для некоторых наших расчетов.
#include // Библиотека модуля AD9833 #include
Затем мы определяем все необходимые входные и выходные контакты для кнопок, переключателя, поворотного энкодера и OLED.
#define SCREEN_WIDATA_PINH 128 // Ширина OLED-дисплея в пикселях #define SCREEN_HEIGHT 64 // Высота OLED-дисплея в пикселях #define SET_FREQUENCY_HZ A2 // Кнопка для установки частоты в Гц #define SET_FREQUENCY_KHZ A3QUENCY_KHZ_FREQUENCY_KHZ_FREQUENCY_KHZ_FREQUENCY_KHZ_FREQUENCY_KHZ_FREQUENCY_KHZ_SET_HZ_M A6 // Кнопка для установки частоты в МГц #define ENABLE_DISABLE_OUTPUT_PIN A7 // Кнопка для включения / выключения выхода #define FNC_PIN 4 // Fsync, необходимого модулю AD9833 #define CLK_PIN 8 // Тактовый вывод кодировщика #define DATA_PIN 7 / / Контакт данных энкодера #define BTN_PIN 9 // Внутренняя кнопка энкодера
После этого мы определяем все необходимые переменные, которые требуются в этом коде. Сначала мы определяем счетчик целочисленной переменной, в котором будет храниться значение датчика угла поворота. Следующие две переменные clockPin и clockPinState хранят статус вывода, необходимый для понимания направления энкодера. У нас есть временная переменная, которая содержит текущие значения таймера-счетчика, эта переменная используется для отключения кнопок. Затем у нас есть беззнаковая длинная переменная moduleFrequency, которая содержит вычисленную частоту, которая будет применяться. Далее у нас есть задержка дребезга. Эту задержку можно отрегулировать по мере необходимости. Затем у нас есть три логические переменные set_frequency_hz,set_frequency_Khz и set_frequency_Mhz - эти три переменные используются для определения текущей настройки модуля. Подробнее об этом мы поговорим позже в статье. Затем у нас есть переменная, которая хранит состояние выходного сигнала, выходной сигнал по умолчанию является синусоидальным. И, наконец, у нас есть переменная encoder_btn_count, которая содержит количество кнопок кодировщика, которое используется для установки формы выходного сигнала.
int counter = 1; // Это значение счетчика будет увеличиваться или уменьшаться, если вращать энкодер int clockPin; // Заполнитель для статуса вывода, используемый поворотным энкодером int clockPinState; // Заполнитель для статуса вывода, используемого поворотным энкодером unsigned long time = 0; // Используется для устранения ошибок типа unsigned long moduleFrequency; // используется для установки выходной частоты long debounce = 220; // Задержка устранения дребезга bool btn_state; // используется для включения вывода отключения модуля AD98333 bool set_frequency_hz = 1; // Частота по умолчанию модуля AD9833 bool set_frequency_khz; bool set_frequency_mhz; Строка waveSelect = "SIN"; // Форма сигнала запуска модуля int encoder_btn_count = 0; // Используется для проверки кнопки кодировщика. Нажмите Далее, у нас есть два объекта: один для OLED-дисплея, а другой для модуля AD9833.Дисплей Adafruit_SSD1306 (SCREEN_WIDATA_PINH, SCREEN_HEIGHT, & Wire, -1); AD9833 поколения (FNC_PIN);
Затем у нас есть функция setup (), в этой функции настройки мы начинаем с включения последовательного порта для отладки. Инициализируем модуль AD9833 с помощью метода begin (). Затем мы устанавливаем все назначенные выводы поворотного энкодера как входные. И мы сохраняем значение тактового вывода в переменной clockPinState, это необходимый шаг для поворотного энкодера.
Затем мы устанавливаем все контакты кнопок в качестве входных и включаем отображение OLED с помощью метода display.begin () , а также проверяем наличие ошибок с помощью оператора if . Когда это будет сделано, мы очищаем дисплей и распечатываем заставку при запуске, мы добавляем задержку в 2 секунды, которая также является задержкой для заставки, и, наконец, мы вызываем функцию update_display (), которая очищает экран и обновляет отобразить еще раз. Подробности метода update_display () будут обсуждаться позже в статье.
void setup () {Serial.begin (9600); // Включить последовательный порт на скорости 9600 бод gen.Begin (); // Это ДОЛЖНА быть первой командой после объявления pinMode объекта AD9833 (CLK_PIN, INPUT); // Установка выводов как входных pinMode (DATA_PIN, INPUT); pinMode (BTN_PIN, INPUT_PULLUP); clockPinState = digitalRead (CLK_PIN); pinMode (SET_FREQUENCY_HZ, INPUT); // Установка выводов как входных pinMode (SET_FREQUENCY_KHZ, INPUT); pinMode (SET_FREQUENCY_MHZ, INPUT); pinMode (ENABLE_DISABLE_OUTPUT_PIN, INPUT); if (! display.begin (SSD1306_SWITCHCAPVCC, 0x3C)) {// Адрес 0x3D для 128x64 Serial.println (F («Ошибка выделения SSD1306»)); за (;;); } display.clearDisplay (); // Очистить экран display.setTextSize (2); // Устанавливаем размер текста display.setTextColor (WHITE); // установить цвет ЖК-дисплея display.setCursor (30, 0); // Устанавливаем положение курсора display.println ("AD9833"); // Распечатать этот текстовый дисплей.setCursor (17, 20); // Устанавливаем положение курсора display.println ("Function"); // Распечатать этот текст display.setCursor (13, 40); // Устанавливаем положение курсора display.println ("Generator"); // Распечатать этот текст display.display (); // Обновляем задержку отображения (2000); // Задержка в 2 сек update_display (); // Вызов функции update_display}
Затем у нас есть наша функция loop (), все основные функции написаны в разделе цикла.
Сначала мы считываем вывод Clock поворотного энкодера и сохраняем его в объявленной ранее переменной clockPin. Затем в операторе if мы проверяем, совпадают ли предыдущее значение вывода и текущее значение вывода, а также проверяем текущее значение вывода. Если все верно, мы проверяем вывод данных, если истина, это означает, что энкодер вращается против часовой стрелки, и мы уменьшаем значение счетчика с помощью команды counter--. В противном случае мы увеличиваем значение счетчика с помощью команды counter ++. Наконец, мы добавляем еще один оператор if, чтобы установить минимальное значение 1. Затем мы обновляем clockPinState текущим значением clockPin.ценность для будущего использования.
недействительный цикл () {clockPin = digitalRead (CLK_PIN); if (clockPin! = clockPinState && clockPin == 1) {if (digitalRead (DATA_PIN)! = clockPin) {счетчик -; } else {counter ++; // Энкодер вращается по часовой стрелке так, чтобы увеличить} if (counter <1) counter = 1; Serial.println (счетчик); update_display (); }
Далее у нас есть код для обнаружения нажатия кнопки. В этом разделе мы обнаружили кнопку внутри кодировщика с помощью некоторых вложенных операторов if, if (digitalRead (BTN_PIN) == LOW && millis () - time> denounce), в этом операторе мы сначала проверяем, если кнопка штифт низкий или нет, если низкий, то он нажат. Затем мы снова проверяем значение таймера с задержкой устранения дребезга, если оба утверждения верны, тогда мы объявляем это успешным действием нажатия кнопки, если так, мы увеличиваем значение encoder_btn_count. Затем мы объявляем другой оператор if, чтобы установить максимальное значение счетчика равным 2, он нам нужен, потому что мы используем его для установки формы выходного сигнала.Это делают три последовательных оператора if: если значение равно нулю, выбирается синусоидальная волна, если единица, то прямоугольная волна, а если значение 2, то треугольная волна. Во всех трех операторах if мы обновляем отображение с помощью функции update_display () . И, наконец, мы обновляем переменную времени текущим значением счетчика таймера.
// Если мы обнаруживаем НИЗКИЙ сигнал, кнопка нажимается if (digitalRead (BTN_PIN) == LOW && millis () - time> debounce) {encoder_btn_count ++; // Увеличиваем значения if (encoder_btn_count> 2) // если значение больше 2, сбрасываем его на 0 {encoder_btn_count = 0; } if (encoder_btn_count == 0) {// если значение равно 0 выбирается синусоида waveSelect = "SIN"; // обновляем строковую переменную значением sin update_display (); // обновляем отображение} if (encoder_btn_count == 1) {// если выбрано значение 1 прямоугольная волна waveSelect = "SQR"; // обновить строковую переменную значением SQR update_display (); // обновляем отображение} if (encoder_btn_count == 2) {// если значение равно 1 Выбрана треугольная волна waveSelect = "TRI"; // обновляем строковую переменную значением TRI update_display ();// обновляем дисплей} time = millis (); // обновляем переменную времени}
Затем мы определяем весь необходимый код, который требуется для настройки всех кнопок с задержкой противодействия. Поскольку кнопки подключены к аналоговым выводам Arduino, мы используем команду аналогового чтения для идентификации нажатия кнопки, если значение аналогового считывания достигает ниже 30, затем мы обнаруживаем успешное нажатие кнопки и ждем 200 мс, чтобы проверьте, действительно ли это нажатие кнопки или только шум. Если это утверждение верно, мы присваиваем булевым переменным значения, которые используются для установки значений Hz, Khz и Mhz генератора функций. Затем мы обновляем отображение и обновляем переменную времени. Мы делаем это для всех четырех кнопок, связанных с Arduino.
if (analogRead (SET_FREQUENCY_HZ) <30 && millis () - time> debounce) {set_frequency_hz = 1; // обновляем логические значения set_frequency_khz = 0; set_frequency_mhz = 0; update_display (); // обновляем отображение time = millis (); // обновляем переменную времени} if (analogRead (SET_FREQUENCY_KHZ) <30 && millis () - time> debounce) {set_frequency_hz = 0; // обновляем логические значения set_frequency_khz = 1; set_frequency_mhz = 0; moduleFrequency = counter * 1000; update_display (); // обновляем отображение time = millis (); // обновляем переменную времени} if (analogRead (SET_FREQUENCY_MHZ) <30 && millis () - time> debounce) {// проверяем аналоговый вывод с задержкой отбойного сигнала set_frequency_hz = 0; // обновляем логические значения set_frequency_khz = 0; set_frequency_mhz = 1; moduleFrequency = counter * 1000000; update_display ();// обновляем отображение time = millis (); // обновляем переменную времени} if (analogRead (ENABLE_DISABLE_OUTPUT_PIN) <30 && millis () - time> debounce) {// проверяем аналоговый вывод с задержкой отбойного сигнала btn_state =! btn_state; // Инвертируем состояние кнопки gen.EnableOutput (btn_state); // Включение / отключение вывода генератора функций в зависимости от состояния кнопки update_display (); // обновление отображения time = millis (); // обновление временной переменной}}// обновляем переменную времени}}// обновляем переменную времени}}
Наконец, у нас есть функция update_display (). В этой функции мы сделали гораздо больше, чем просто обновили этот дисплей, потому что определенная часть дисплея не может быть обновлена в OLED. Чтобы обновить его, вы должны перекрасить его с новыми значениями. Это значительно усложняет процесс кодирования.
Внутри этой функции мы начинаем с очистки дисплея. Далее мы устанавливаем требуемый размер текста. После этого мы устанавливаем курсор и распечатываем Генератор функций с помощью display.println («Функция Функция»); команда. Мы снова устанавливаем размер текста на 2, а курсор на (0,20) с помощью функции display.setCursor (0, 20).
Здесь мы печатаем информацию о том, какая это волна.
display.clearDisplay (); // Сначала очищаем дисплей display.setTextSize (1); // установить размер текста display.setCursor (10, 0); // Устанавливаем позицию курсора display.println ("Генератор функций"); // распечатать текст display.setTextSize (2); // установить размер текста display.setCursor (0, 20); // установить позицию курсора
Затем мы проверяем логические переменные на предмет сведений о частоте и обновляем значение в переменной moduleFrequency. Мы делаем это для значений Гц, кГц и МГц. Затем мы проверяем переменную waveSelect и определяем, какая волна выбрана. Теперь у нас есть значения для установки типа и частоты волны.
if (set_frequency_hz == 1 && set_frequency_khz == 0 && set_frequency_mhz == 0) {// проверяем, нажата ли кнопка установки частоты в Гц moduleFrequency = counter; // обновляем переменную moduleFrequency текущим значением счетчика} if (set_frequency_hz == 0 && set_frequency_khz == 1 && set_frequency_mhz == 0) {// проверяем, нажата ли кнопка для установки частоты в кГц moduleFrequency = counter * 1000; // обновить переменную moduleFrequency текущим значением счетчика, но мы умножаем 1000, чтобы установить ее на KHZ} if (set_frequency_hz == 0 && set_frequency_khz == 0 && set_frequency_mhz == 1) {// проверяем, нажата ли кнопка для установки частоты в МГц moduleFrequency = счетчик * 1000000; если (moduleFrequency> 12000000) {moduleFrequency = 12000000;// не позволяем частоте быть больше, чем 12Mhz counter = 12; }} if (waveSelect == "SIN") {// Выбрана синусоида display.println ("SIN"); gen.ApplySignal (SINE_WAVE, REG0, moduleFrequency); Serial.println (moduleFrequency); } if (waveSelect == "SQR") {// Выбрана волна Sqr display.println ("SQR"); gen.ApplySignal (SQUARE_WAVE, REG0, moduleFrequency); Serial.println (moduleFrequency); } if (waveSelect == "TRI") {// Выбрана трехволновая волна display.println ("TRI"); gen.ApplySignal (TRIANGLE_WAVE, REG0, moduleFrequency); // обновляем модуль AD9833. Serial.println (moduleFrequency); }} if (waveSelect == "SQR") {// Выбрана волна Sqr display.println ("SQR"); gen.ApplySignal (SQUARE_WAVE, REG0, moduleFrequency); Serial.println (moduleFrequency); } if (waveSelect == "TRI") {// Выбрана тройная волна display.println ("TRI"); gen.ApplySignal (TRIANGLE_WAVE, REG0, moduleFrequency); // обновляем модуль AD9833. Serial.println (moduleFrequency); }} if (waveSelect == "SQR") {// Выбрана волна Sqr display.println ("SQR"); gen.ApplySignal (SQUARE_WAVE, REG0, moduleFrequency); Serial.println (moduleFrequency); } if (waveSelect == "TRI") {// Выбрана трехволновая волна display.println ("TRI"); gen.ApplySignal (TRIANGLE_WAVE, REG0, moduleFrequency); // обновляем модуль AD9833. Serial.println (moduleFrequency); }
Снова устанавливаем курсор и обновляем значения счетчика. Мы снова проверяем логическое значение, чтобы обновить частотный диапазон на дисплее, мы должны это сделать, потому что принцип работы OLED очень странный.
display.setCursor (45, 20); display.println (счетчик); // выводим информацию счетчика на дисплей. если (set_frequency_hz == 1 && set_frequency_khz == 0 && set_frequency_mhz == 0) {display.setCursor (90, 20); display.println ("Гц"); // выводим Гц на дисплей display.display (); // когда весь набор обновляет отображение} if (set_frequency_hz == 0 && set_frequency_khz == 1 && set_frequency_mhz == 0) {display.setCursor (90, 20); display.println («КГц»); display.display (); // когда весь набор обновляет отображение} if (set_frequency_hz == 0 && set_frequency_khz == 0 && set_frequency_mhz == 1) {display.setCursor (90, 20); display.println («МГц»); display.display (); // когда все установлено, обновляем дисплей}
Затем мы проверяем переменную нажатия кнопки для включения / выключения вывода на OLED. Опять же, это нужно сделать из-за модуля OLED.
если (btn_state) {display.setTextSize (1); display.setCursor (65, 45); display.print («Выход включен»); // выводим вывод на дисплей display.display (); display.setTextSize (2); } еще {display.setTextSize (1); display.setCursor (65, 45); display.print («Выход выключен»); // выводить вывод на дисплей display.display (); display.setTextSize (2); }
Это знаменует конец нашего процесса кодирования. Если вы запутались на этом этапе, вы можете проверить комментарии в коде для дальнейшего понимания.
Тестирование генератора функций на базе AD9833
Для проверки схемы используется указанная выше установка. Как видите, мы подключили адаптер питания 12 В постоянного тока к цилиндрическому разъему постоянного тока, а осциллограф Hantek подключили к выходу схемы. Мы также подключили осциллограф к ноутбуку для визуализации и измерения выходной частоты.
Как только это было сделано, мы устанавливаем выходную частоту на 5 кГц с помощью поворотного энкодера, и мы тестируем выходную синусоидальную волну и, конечно же, это синусоидальная волна на выходе 5 кГц.
Затем мы изменили форму выходного сигнала на треугольную, но частота осталась прежней, форма выходного сигнала показана ниже.
Затем мы изменили выходной сигнал на прямоугольную волну и наблюдали на выходе, и это была идеальная прямоугольная волна.
Мы также изменили частотные диапазоны и протестировали выход, и он работал хорошо.
Дальнейшие улучшения
Эта схема является лишь проверкой концепции и требует дальнейших улучшений. Во-первых, нам нужна качественная печатная плата и качественный BNC-разъем для вывода, иначе мы не сможем получить более высокую частоту. Амплитуда модуля очень мала, поэтому для ее увеличения нам понадобятся схемы операционных усилителей для усиления выходного напряжения. Для изменения выходной амплитуды можно подключить потенциометр. Можно подключить переключатель для смещения сигнала; это тоже обязательная функция. И, кроме того, код нуждается в большом улучшении, поскольку он немного глючит. Наконец, необходимо изменить OLED-дисплеи, иначе невозможно написать понятный код.
Это знаменует конец этого урока. Надеюсь, вам понравилась статья и вы узнали что-то новое. Если у вас есть какие-либо вопросы по статье, вы можете оставить их в разделе комментариев ниже или воспользоваться нашим форумом по электронике.