Каталог товаров

Генератор сигналов на AD9850. Часть 2.

Генератор на AD9850 и Ардуино

В предыдущей публикации мы подробно рассмотрели возможности генератора AD9850, его подключение к Ардуино (как для последовательного, так и для параллельного режима обмена) и научились генерировать нужную частоту на выходе AD9850. Сегодня мы применим эти знания на практике: речь пойдёт о создании полноценного генератора на базе AD9850.

Компоненты

Кроме модуля AD9850 нам понадобятся:

  • Ардуино Нано
  • ЖК дисплей 1602 (я использую поддерживающий кириллицу, но подойдёт и с английским шрифтом)
  • Энкодер вращения с кнопкой EC11
  • BNC разъёмы
  • Корпус
  • Несколько винтов и гаек М3.

Я добавил в проект разъём для подключения питания через вход Vin Ардуино и 2-позиционный выключатель. Но можно обойтись и без них.

Схема генератора

Перечисленные компоненты соединяем в соответствии со следующей схемой:

Схема генератора на AD9850

Я выбрал параллельный способ управления AD9850. Думаю, это будет выгоднее, поскольку планирую добавить в устройство функцию sweep (ни к чему тратить время на последовательную передачу).

Выводами генератора служат ZOUT1 и QOUT1 – синусоида и прямоугольные импульсы. Для управления скважностью прямоугольных импульсов можно выпаять из модуля генератора потенциометр и вынести его на панель устройства. У меня такой необходимости нет, поэтому оставил всё как есть.

Корпус

Корпус устройства я распечатал на 3D принтере. За его основу я взял параметризованную модель https://www.thingiverse.com/thing:1264391. Она позволяет создать корпус нужных размеров. Остаётся лишь немного доработать его для своих целей: добавить необходимые отверстия и стойки для крепления компонентов. Кроме того я решил усилить боковые части корпуса, в которые вворачивается крепёж, т.к. это место показалось мне ненадёжным. Заодно предусмотрел в них пазы для гаек. Получилось довольно неплохо:

Скетч

Итак, соединяем все компоненты по приведённой ранее схеме и устанавливаем их в корпусе. Дело за скетчем. Я реализовал в нём следующие функции: изменение частоты энкодером, изменение шага, ввод произвольной частоты, режим sweep. В скетче использованы библиотеки LiquidCrystal_I2C_Menu для построения меню и TimerOne для работы с таймером 1 (в режиме sweep). Вы можете скачать их вместе со скетчем и моделями для печати на сайте thingiverse.com: https://www.thingiverse.com/thing:5335092

Обратите внимание: при скачивании библиотеки LiquidCrystal_I2C_Menu с сайта github, поддержка кириллицы в ней по умолчанию выключена. Если вы используете дисплей с кириллицей, то необходимо включить её поддержку. Для этого откройте в текстовом редакторе файл LiquidCrystal_I2C_Menu.h, раскомментируйте строку #define CYRILLIC_DISPLAY (удалите символы // в начале строки) и сохраните изменения. После этого вы сможете выводить на дисплей русский текст с использованием данной библиотеки.

#include <util/atomic.h>
#include "Wire.h"
#include "LiquidCrystal_I2C_Menu.h" // https://github.com/VladimirTsibrov/LiquidCrystal_I2C_Menu
#include  // https://github.com/PaulStoffregen/TimerOne
LiquidCrystal_I2C_Menu lcd(0x27, 16, 2);

// Выводы AD9850
const byte pinReset = 8;
const byte pinFQ_UD = 9;
const byte pinW_CLK = 10;

// Выводы энкодера
const byte pinCLK = A1; // Энкодер пин A
const byte pinDT  = A2; // Энкодер пин B
const byte pinBtn = A3; // Кнопка

// Прототипы функций
void menuHandler(); // Обработчик пунктов меню
void LCDRepaint();  // Функция перерисовки экрана
uint32_t inputFreq(uint32_t, String);

// Меню
enum eMenuKey {mkBack, mkRoot, mkSetFreq, mkSetFreqStep, mkSweepMode, mkSweepFrom, 
               mkSweepTo, mkSweepTime, mkSweepStart, mkAbout};
               
sMenuItem Menu[] = {
  {mkBack, mkRoot, "Menu", NULL},
    {mkRoot, mkSetFreq, "Установить частоту", menuHandler},
    {mkRoot, mkSetFreqStep, "Шаг изменения частоты", menuHandler},
    {mkRoot, mkSweepMode, "Режим Sweep", NULL},
      {mkSweepMode, mkSweepStart, "Запуск", menuHandler},
      {mkSweepMode, mkSweepFrom, "Начальная частота", menuHandler},
      {mkSweepMode, mkSweepTo, "Конечная частота", menuHandler},
      {mkSweepMode, mkSweepTime, "Время свипирования", menuHandler},
      {mkSweepMode, mkBack, "Назад", NULL},
    {mkRoot, mkAbout, "О программе", menuHandler},
    {mkRoot, mkBack, "Выход из меню", NULL}
};
const int MenuLength = sizeof(Menu) / sizeof(Menu[0]);

const uint8_t stepsCount = 7;
String listSteps[stepsCount] = {"1 Гц", "10 Гц", "100 Гц", "1 кГц", "10 кГц", "100 кГц", "1 МГц"}; // Массив для выбора шага изменения частоты
uint32_t freqSteps[stepsCount] = {1, 10, 100, 1000, 10000, 100000, 1000000}; // И соответствующий ему массив значений
uint32_t freq = 100;  // Частота
volatile double freqSweep;
double sweepStep;
uint8_t stepIndex = 0;
uint32_t sweepFrom = 1000; // Начальная частота качаня
uint32_t sweepTo = 100000; // Конечная часота качания
uint8_t sweepTime = 10;   // Время(с) качания
const uint8_t sweepDelay = 10; // Время(мс) промежуточного значения частоты в режиме sweep


//**************************************************************************

// Выдать импульс на указанном пине
void pulsePin(uint8_t pin) {
  digitalWrite(pin, HIGH);
  digitalWrite(pin, LOW);
}

// Установка частоты AD9850
void setFreq (uint32_t f) {
  uint32_t w = f * 4294967296.0 / 125000000;
  PORTD = 0;
  pulsePin(pinW_CLK);
  PORTD = w >> 24;
  pulsePin(pinW_CLK);
  PORTD = w >> 16;
  pulsePin(pinW_CLK);
  PORTD = w >> 8;
  pulsePin(pinW_CLK);
  PORTD = w;
  pulsePin(pinW_CLK);
  pulsePin(pinFQ_UD);
}

void timer1ISR(void) {
  // Увеличить частоту
  freqSweep += sweepStep;
  if (freqSweep > sweepTo) freqSweep = sweepFrom;
  setFreq(round(freqSweep));
}

//**************************************************************************

void setup() {
  // Инициализация дисплея
  lcd.begin();
  lcd.attachEncoder(pinDT, pinCLK, pinBtn);
  lcd.backlight();

  // Настраиваем используемые пины
  pinMode(pinReset, OUTPUT);
  pinMode(pinFQ_UD, OUTPUT);
  pinMode(pinW_CLK, OUTPUT);

  // Пины D0..D7 настроим на вывод, используя регистр DDRD
  DDRD = B11111111; // 1 - OUTPUT, 0 - INPUT

  digitalWrite(pinReset, LOW);
  digitalWrite(pinFQ_UD, LOW);
  digitalWrite(pinW_CLK, LOW);

  // Сброс AD9850
  pulsePin(pinReset);

  // Установка частоты
  setFreq(freq);
  LCDRepaint();
  Timer1.initialize(10000);
  Timer1.stop();
  Timer1.attachInterrupt(timer1ISR);
}

void loop() {
  // В цикле опрашиваем энкодер
  switch (lcd.getEncoderState()) {
    case eNone: return;
    case eLeft: { // Уменьшить частоту
          if (freq > freqSteps[stepIndex])
            freq = freq - freqSteps[stepIndex];
          else
            freq = 1;
          setFreq(freq);
          LCDRepaint();
        break;
      }
    case eRight: { // Увеличить частоту
          if (freq + freqSteps[stepIndex] <= 62500000)
            freq = freq + freqSteps[stepIndex];
          else
            freq = 62500000;
          setFreq(freq);
          LCDRepaint();
        break;
      }
    case eButton: { // При нажатии на кнопку показываем меню
        lcd.showMenu(Menu, MenuLength, false);
        // После выхода из меню перерисовываем главный экран
        LCDRepaint();
        return;
      }
  }
}

// Вывод информации на дисплей
void LCDRepaint() {
  lcd.clear();
  lcd.printf("F=%ld Гц", freq);
}

// Обработчик пунктов меню
void menuHandler() {
  uint8_t selectedMenuItem = lcd.getSelectedMenuItem();
  if (selectedMenuItem == mkSetFreq) {
    // ****************** Установить частоту ******************
    uint32_t f = inputFreq(freq, F("Частота:")); // Запрашиваем новое значение
    while ((f > 62500000) or (f < 1)) {
      // Частота должна быть в диапазоне 1Гц...62.5МГц
      lcd.printMultiline(F("Частота должна быть в диапазоне 1 Гц-62.5 МГц"));
      f = inputFreq(f, F("Частота:")); // Предлагаем ввести частоту повторно
    }
    if (f != freq) {
      freq = f;
      setFreq(freq); // Устанавливаем новое значение частоты
    }
  }
  else if (selectedMenuItem == mkSetFreqStep) {
    // ****************** Выбор шага изменения частоты энкодером ******************
    stepIndex = lcd.selectVal("", listSteps, stepsCount, true, stepIndex);
  }
  else if (selectedMenuItem == mkSweepFrom) {
    // ****************** Ввод начальной частоты качания ******************
    uint32_t f = inputFreq(sweepFrom, F("Начальная:"));
    while ((f > 62500000) or (f < 1)) {
      // Частота должна быть в диапазоне 1Гц...62.5МГц
      lcd.printMultiline(F("Частота должна быть в диапазоне 1 Гц-62.5 МГц"));
      f = inputFreq(f, F("Начальная:")); // Предлагаем ввести частоту повторно
    }
    if (f != sweepFrom) {
      sweepFrom = f;
    }
  }
  else if (selectedMenuItem == mkSweepTo) {
    // ****************** Ввод конечной частоты качания ******************
    uint32_t f = inputFreq(sweepTo, F("Конечная:"));
    while ((f > 62500000) or (f < 1)) {
      // Частота должна быть в диапазоне 1Гц...62.5МГц
      lcd.printMultiline(F("Частота должна быть в диапазоне 1 Гц-62.5 МГц"));
      f = inputFreq(f, F("Конечная:")); // Предлагаем ввести частоту повторно
    }
    if (f != sweepTo) {
      sweepTo = f;
    }
  }
  else if (selectedMenuItem == mkSweepTime) {
    // ****************** Ввод времени качания ******************
    sweepTime = lcd.inputVal(F("Время свип.: "), 1, 60, sweepTime);
  }
  else if (selectedMenuItem == mkSweepStart) {
    // ****************** Запуск sweep ******************
    unsigned long tm;
    double freqSweepCopy;
    lcd.clear();
    lcd.printAt(0, 0, "Режим sweep");
    sweepStep = (sweepTo - sweepFrom) * sweepDelay / (sweepTime * 1000);
    freqSweep = sweepTo;
    lcd.printfAt(0, 1, "%-8ld Гц", sweepFrom);
    Timer1.setPeriod(1000 * sweepDelay);
    Timer1.restart();
    tm = millis();
    while(lcd.getEncoderState() == eNone) {
      if(millis() - tm > 2000){
        ATOMIC_BLOCK(ATOMIC_RESTORESTATE){
          freqSweepCopy = freqSweep;
        }
        lcd.printfAt(0, 1, "%-8ld Гц", round(freqSweepCopy));
        tm = millis();
      }
    }
    Timer1.stop();
    setFreq(freq);
  }
  else if (selectedMenuItem == mkAbout) {
    lcd.clear();
    lcd.print("Генератор AD9850");
    lcd.printAt(0, 1, "CompactTool.ru");
    while (lcd.getEncoderState() == eNone);
  }
}

uint32_t inputFreq(uint32_t val, String title) {
  uint32_t f = val;
  char freqChars[] = "00 000 000Гц";
  uint8_t index = 9; // Позиция младшего разряда
  // Разбиваем частоту на разряды:
  for (uint8_t i = 0; i < 8; i++) {
    freqChars[index--] = (f % 10) + 48;
    f = f / 10;
    if (freqChars[index] < 48) index--; // Пропускаем позиции пробелов
  }

  if (lcd.inputStrVal(title, freqChars, 12, "0123456789")) {
    // Ввели новое значение частоты. Соберем его из строки в число:
    f = 0;
    for (int i = 0; i < 10; i++) {
      if (freqChars[i] < 48) continue;
      f = f * 10 + freqChars[i] - 48;
    }
    return f;
  }
  else return val; // Отказались от ввода
}

 

Получившийся в итоге генератор вы можете увидеть в следующем видео.

Теперь в планах дополнить генератор усилителем, а также сделать логарифмический режим sweep. Возможно, из этого получится продолжение данной публикации.

Контакты

г. Москва, Пятницкое ш. д. 18, пав. 566

zakaz@compacttool.ru

8-495-752-55-22

Информация представленная на данном информационном ресурсе преследует исключительно рекламные цели и не является договором-офертой !

© Все права защищены 2015 - 2022г https://compacttool.ru