вторник, 31 марта 2015 г.

Работа с DDS (синтезатором прямого синтеза частоты) AD9850 на базе модуля HC-SR08 из скрипта VBS

DDS AD9850 - является полнофункциональным КМОП синтезатором прямого цифрового синтеза с максимальной тактовой частотой 125 МГц, с интегрированным SFDR ЦАП и возможностью установки выходной частоты (от 1 Гц до ~60 МГц с точностью 0.0291 Гц) как параллельной шиной, так и в последовательном формате 32-х битным словом (на самом деле 40-битным младшими битами вперед, из них последние 8 бит являются режимом работы DDS на 32..34 биты и значениями фазы 35..39). DDS может использоваться как в своих конструкциях малогабаритных синтезаторов/генераторов, так и в серьезных системах навигации в качестве синхронизирующего генератора (при условии дополнительного термостатирования конечно). DDS формирует на выходе два квадратурных синусоидальных сигнала и меандр, и потребляет не более 380 мВт при тактовой 125 МГц при 5-ти вольтовом питании и до 155 мВт на 100 МГц  при 3.3-х вольтовом. На ебее или али готовый модуль EIM377 или HC-SR08 с этим синтезатором и всей обвязкой стоит даже дешевле самого чипа и такую "вкусность" нельзя было пропустить...


Забегая наперед, отметим, что контроль по USB используется как опция по удаленному контролю с ПК, само устройство полностью автономно и реализовано мезонином платы контроллера над платой синтезатора, достаточно лишь обеспечить питание. В то же время к буратине можно прикошачить модули прозрачного UART поверх блютуз - HC-05 или поверх Wi-Fi - ESP-03 на базе ESP8266 и поиметь беспроводной канал дополнительного контроля. Получилось компактно:


Набросал Gui-обертку с автоконтролем выдергивания USB шнурка (конвертора буратины) и переинициализацией:






Но не будем отвлекаться и рассмотрим по-порядку...

Взаимодействие с DDS будем осуществлять через модуль Arduino Nano по последовательному интерфейсу over USB посредством скрипта VBS, с голосовым сопровождением уставок. Благодаря самому модулю также появляется возможность автономной работы синтезатора, ведь сохранение и восстановление уставок будет осуществляться с помощью имеющейся EEPROM на базовом чипе ATMega328p буратины.

Структура и функционал DDS




Временные диаграммы установки частоты и режимов


Распределение битов управляющего 40-ка битного слова в параллельном режиме загрузки представлено в таблице:


Распределение битов управляющего 40-ка битного слова в последовательном режиме загрузки представлено в таблице:


Нижний уровень

Автономная работа с модулем HC-SR08 на нижнем уровне реализуется модулем Arduino Nano и следующим скетчем:
// Автономный DDS на базе Arduino Nano (ATMega328p) + HC-SR08 (DDS AD9850)
// Разработчик: Бадло Сергей Григорьевич
// H-page: http://raxp.radioliga.com


#include <EEPROM.h>

// соответствие пинов подключений
#define W_CLK 8   // 8  - W_CLK AD9850
#define FQ_UD 9   // 9  - F_EN AD9850
#define DATA 10   // 10 - DATA AD9850
#define RESET 11  // 11 - Reset AD9850

#define pulse(pin) {digitalWrite(pin, HIGH); digitalWrite(pin, LOW); }
int_fast32_t timepassed = millis(); // задержка

int_fast32_t rx=1000000; // начальное значение частоты DDS, Гц
int_fast32_t rx2=1;      // temp при новой частоте
int incbutton = 0;

byte ones, tens, gekto, kilo, tenkilo, gektokilo, mega, tenmega;
String freq;            // значение частоты
int flag_eeprom = 1;    // флаг разрешения записи в EEPROM по "0"
int ForceFreq = 0;      // установить в "0" для активации EEPROM



void setup() {
  Serial.begin(19200);
 
  pinMode(A0, INPUT);     // кнопку вешаем на землю,
  digitalWrite(A0, HIGH); // а вход подтягиваем на питание

  // настраиваем на выход
  pinMode(FQ_UD, OUTPUT);
  pinMode(W_CLK, OUTPUT);
  pinMode(DATA, OUTPUT);
  pinMode(RESET, OUTPUT);
 
  pulse(RESET); // Reset
  pulse(W_CLK); // ТИ
  pulse(FQ_UD); // флаг разрешения выдачи

   // грузим из EEPROM сохраненное значение частоты
  if (ForceFreq == 0) {
    freq = String(EEPROM.read(0))+String(EEPROM.read(1))+String(EEPROM.read(2))+String(EEPROM.read(3))+String(EEPROM.read(4))+String(EEPROM.read(5))+String(EEPROM.read(6))+String(EEPROM.read(7));
    rx = freq.toInt();
  }
}


// бесконечный цикл проверки новой частоты, установки
// запись в EEPROM по флагу + чтение кнопки + чтение Serial
void loop() {
 
  // чтение пакета с преобразованием массива принятых в единое число в INT
  if (Serial.available() > 0) {
      rx = Serial.parseInt();
      Serial.write(rx); // контроль эха в ASCII
  }
 
  // если предыдущее значение частоты <> новому
  if (rx!= rx2){        
        showFreq();        // пересчет частоты по мантиссе и поднятие флага разрешения записи в EEPROM = 0
        sendFrequency(rx); // установка частоты по SPI
        rx2 = rx;          // запоминаем новое значение частоты для последующего сравнения
  }
     
  // читаем замыкание на землю A0 (для будущих применений ручной установки, инкремента)
  incbutton = digitalRead(A0);
  if(incbutton == LOW) {
     //     
  };

  // если поднят флаг разрешения записи в EEPROM = 0
  if(flag_eeprom == 0){ 
   if(timepassed+2000 < millis()){ // ждем 2 сек
    saveEEPROM(); // запись в EEPROM
   }
  } 
}



// установка частоты page 8 = 125 000 000 * Fnew/2^32
void sendFrequency(double frequency) {
  int32_t freq = frequency * 4294967295/125000000;
  for (int b=0; b<4; b++, freq>>=8) {
    tx_byte(freq & 0xFF);
  }
  tx_byte(0x000); // control byte for 9850 chip
  pulse(FQ_UD);   // разрешаем выдачу
}


// передача младшими битами вперед 32-х битного слова частоты на DATA (оставшиеся из 40-32 бит = 8, 3 бита под режимы)
void tx_byte(byte data) {
  for (int i=0; i<8; i++, data>>=1) {
    digitalWrite(DATA, data & 0x01);
    pulse(W_CLK);
  }
}


// пересчет частоты
void showFreq() {
   tenmega = ((rx/10000000)%10);
   mega = ((rx/1000000)%10);
   gektokilo = ((rx/100000)%10);
   tenkilo = ((rx/10000)%10);
   kilo = ((rx/1000)%10);
   gekto = ((rx/100)%10);
   tens = ((rx/10)%10);
   ones = ((rx/1)%10);

   timepassed = millis();
   flag_eeprom = 0; // поднимаем флаг разрешения записи частоты в EEPROM
};


// запись в EEPROM
void saveEEPROM() {
   EEPROM.write(0, tenmega);   // 10 000 000
   EEPROM.write(1, mega);      // 1000 000
   EEPROM.write(2, gektokilo); // 100 000
   EEPROM.write(3, tenkilo);   // 10 000
   EEPROM.write(4, kilo);      // 1000
   EEPROM.write(5, gekto);     // 100   
   EEPROM.write(6, tens);      // 10
   EEPROM.write(7, ones);      // единицы
   flag_eeprom = 1;
};
Верхний уровень

Взаимодействие с верхним уровнем и передача команд установки частоты осуществляется посредством виртуального последовательного порта, эмулируемого драйвером FTDI over USB. Для взаимодействия с COM-портом я решил не использовать всяческие обертки, а работать непосредственно через WinAPI-функции (впрочем, как всегда), которые можно экспортировать в VBS благодаря ActiveX серверу DynWrapX (о нем мы уже вам рассказывали). Само приложение, по своей сути, формы не имеет и в бесконечном цикле перехватывает нажатия клавиш клавиатуры пользователем через функцию GetAsyncKeyState() и осуществляет передачу заданного массива байт (команды) в COM порт, в нашем случае это значение частоты. Для удобства установки пределов смены частоты использована распространенная и удобная всем "геймерам" :) комбинация клавиш 'W, A, S, D' и 'Space'. Клавиша 'W' отвечает за инкремент частоты на заданную дельту вверх, а клавиша 'S' за декремент этой дельты, клавиши 'A' и 'D' за [инкремент частоты * дельту] и [декремент частоты * дельту] соответственно. Клавиша пробела отвечает за дельту: первое нажатие - дельта равна единице, второе нажатие - дельта равна 10. Комбинируя эти клавиши можно установить практически любую частоту. Среди прочего, для упрощения введен перехват обычных цифровых клавиш для прямого ввода частоты по нажатию клавиши Q, эта же клавиша и завершает накопление (ввод частоты) и дает команду на установку новой частоты.

Сам VBS скрипт:
' AD9850M.VBS
' Демонстрационный скрипт контроля DDS AD9850 (HC-SR08) через модуль Arduino Nano (ATMega328p)
' Разработчик: Бадло Сергей Григорьевич aka raxp
' H-page: http://raxp.radioliga.com
' Video: https://www.youtube.com/watch?v=l7jqaMii4LE
'
' Зависимости:
' 1. Win OS 32/64-bit
' 2. COM-сервер DynWrapx.dll
' 3. SAPI (для озвучивания используется интерфейс по-умолчанию)
' 4. Физический RS-232 or virtual COM over USB
' 5. Подключенный модуль Arduino Nano + HC-SR08
'
' Региcтрация интерфейсов (обязательно первично однократно):
' 1. В скрипте задайте порт подключения модуля Arduino
' 2. Запустите regdynwrapx.bat (под ОС Win Vista/7/8/8.1 запускать правой кнопкой мыши от имени Админа)
' 3. Запустите run_AD9850M.cmd
' 4. Контроль клавишами 'W','A','S','D' + 'Space' и прямой ввод частоты цифр-ми клавишами (0..9) по 'Q'
' 5. Для завершения нажмите 'Escape'


' Список команд
freq = 1000000 ' частота по-умолчанию, 1000 Hz
nfreq = 1 ' множитель дельты частоты
tmp = 1

' регистрируем COM объект DynamicWrapperX в тихом режиме
Dim WshShell
Set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.Run ("regsvr32.exe dynwrapx.dll /s"),3, true

' подключаем голосовое сопровождение
' при надоедливости голоса speakon = false
Dim Speak
speakon = true ' разрешаем голосовое сопровождение
Set Speak = CreateObject("sapi.spvoice")

' создаем объект DynamicWrapperX
Set Wrap = CreateObject("DynamicWrapperX.2")
Wrap.Register "user32.dll", "GetAsyncKeyState", "i=l", "f=s", "r=l"

' Настройка порта COM2, на котором Arduino
port = "COM2"
' регистрируем функи для работы с портом
Wrap.Register "KERNEL32.DLL", "CreateFile", "i=sllllll", "r=l"
Wrap.Register "KERNEL32.DLL", "WriteFile", "i=lslll", "r=l" ' ANSI
' вариант с Unicode - Wrap.Register "KERNEL32.DLL", "WriteFile", "i=lllll", "r=l"
Wrap.Register "KERNEL32.DLL", "CloseHandle", "i=l", "r=l"
Wrap.Register "KERNEL32.DLL", "EscapeCommFunction", "i=hu", "r=l"
' забиваем аттрибуты
GENERIC_WRITE = 1073741824
FILE_SHARE_READ = 1
FILE_SHARE_WRITE = 2
OPEN_EXISTING = 3
FILE_ATTRIBUTE_NORMAL = 128
cbWritten = " "
pcbWritten = Wrap.StrPtr(cbWritten)
' открываем нужный нам порт COMn
hFile = Wrap.CreateFile(port, GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
' отключаем сигналы на DTR/RTS
'Wrap.EscapeCommFunction hFile, 4
'Wrap.EscapeCommFunction hFile, 6

' Информируем пользователя о начале работы
if (speakon = true) then
Speak.Speak "Готов измываться над цифровым синтезатором частоты ди-ди-си. A-a, дэ, 98, 50."
End if

Wrap.WriteFile hFile, CStr(freq), Len(CStr(freq)), pcbWritten, 0
if (speakon = true) then
  Speak.Speak "Установлена частота "&freq&" герц"
  Speak.Speak "Установлен множитель дельты частоты "&CStr(nfreq)
End if


p = false
glrec = false


' запускаем бесконечный цикл по условию нажатия 'Escape' с задержкой
Do While Wrap.GetAsyncKeyState(27) = "0"

'задержка на кол-во миллисекунд
WScript.Sleep 1000
' отлавливаем нажатие SPace (пробела)
probel = Wrap.GetAsyncKeyState(32)
' отлавливаем нажатие W
upt = Wrap.GetAsyncKeyState(Asc("W"))
' отлавливаем нажатие S
downt = Wrap.GetAsyncKeyState(Asc("S"))
' отлавливаем нажатие A
leftt = Wrap.GetAsyncKeyState(Asc("A"))
' отлавливаем нажатие D
rightt = Wrap.GetAsyncKeyState(Asc("D"))


' отлавливаем нажатие Q (накопление цифровых нажатий)
tilda = Wrap.GetAsyncKeyState(Asc("Q"))
if (tilda = "1") then
 glrec = not glrec

 if (glrec = false)And(msg<>"") then
  freq = msg
  msg = "" ' подчищаем для нового ввода
  if (freq > 62000000) then ' условие от дурака
   freq = 62000000
   if (speakon = true) then
    Speak.Speak "Больше шестидесяти двух мегагерц нельзя. Ди-ди-си, туды её в качель, с тактовой 125 мегагерц."
   End if
  End if
  ' посылаем
  Wrap.WriteFile hFile, CStr(freq), Len(CStr(freq)), pcbWritten, 0
  if (speakon = true) then
   Speak.Speak "Установлена частота " & freq & " герц"
  End if
 End if

 if (glrec = true) then
  if (speakon = true) then
   Speak.Speak "Ввод частоты."
  End if
 End if
End if
' отлавливаем нажатие 1
van = Wrap.GetAsyncKeyState(Asc("1")) or Wrap.GetAsyncKeyState(97)
if (van = "1") then
 if (glrec = true) then
  msg = msg&"1"
  if (speakon = true) then
   Speak.Speak "1"
  End if
 End if
End if
' отлавливаем нажатие 2
two = Wrap.GetAsyncKeyState(Asc("2")) or Wrap.GetAsyncKeyState(98)
if (two = "1") then
 if (glrec = true) then
  msg = msg&"2"
  if (speakon = true) then
   Speak.Speak "2"
  End if
 End if
End if
' отлавливаем нажатие 3
three = Wrap.GetAsyncKeyState(Asc("3")) or Wrap.GetAsyncKeyState(99)
if (three = "1") then
 if (glrec = true) then
  msg = msg&"3"
  if (speakon = true) then
   Speak.Speak "3"
  End if
 End if
End if
' отлавливаем нажатие 4
four = Wrap.GetAsyncKeyState(Asc("4")) or Wrap.GetAsyncKeyState(100)
if (four = "1") then
 if (glrec = true) then
  msg = msg&"4"
  if (speakon = true) then
   Speak.Speak "4"
  End if
 End if
End if
' отлавливаем нажатие 5
fife = Wrap.GetAsyncKeyState(Asc("5")) or Wrap.GetAsyncKeyState(101)
if (fife = "1") then
 if (glrec = true) then
  msg = msg&"5"
  if (speakon = true) then
   Speak.Speak "5"
  End if
 End if
End if
' отлавливаем нажатие 6
six = Wrap.GetAsyncKeyState(Asc("6")) or Wrap.GetAsyncKeyState(102)
if (six = "1") then
 if (glrec = true) then
  msg = msg&"6"
  if (speakon = true) then
   Speak.Speak "6"
  End if
 End if
End if
' отлавливаем нажатие 7
seven = Wrap.GetAsyncKeyState(Asc("7")) or Wrap.GetAsyncKeyState(103)
if (seven = "1") then
 if (glrec = true) then
  msg = msg&"7"
  if (speakon = true) then
   Speak.Speak "7"
  End if
 End if
End if
' отлавливаем нажатие 8
eight = Wrap.GetAsyncKeyState(Asc("8")) or Wrap.GetAsyncKeyState(104)
if (eight = "1") then
 if (glrec = true) then
  msg = msg&"8"
  if (speakon = true) then
   Speak.Speak "8"
  End if
 End if
End if
' отлавливаем нажатие 9
nine = Wrap.GetAsyncKeyState(Asc("9")) or Wrap.GetAsyncKeyState(105)
if (nine = "1") then
 if (glrec = true) then
  msg = msg&"9"
  if (speakon = true) then
   Speak.Speak "9"
  End if
 End if
End if
' отлавливаем нажатие 0
zero = Wrap.GetAsyncKeyState(Asc("0")) or Wrap.GetAsyncKeyState(96)
if (zero = "1") then
 if (glrec = true) then
  msg = msg&"0"
  if (speakon = true) then
   Speak.Speak "0"
  End if
 End if
End if

if (probel = "1") then
 p = not p

 if (p = false) then
  nfreq = 1
  ' озвучиваем
  if (speakon = true) then
   Speak.Speak "Установлен множитель дельты частоты "&CStr(nfreq)
  End if
 End if

 if (p = true) then
  nfreq = 10
  ' озвучиваем
  if (speakon = true) then
   Speak.Speak "Установлен множитель дельты частоты "&CStr(nfreq)
  End if
 End if
End if



if (leftt = "1") then
 tmp = nfreq*100000
 freq = freq - tmp
 if (freq < 1) then
  freq = 1
  if (speakon = true) then
   Speak.Speak "Вышли за пределы в минус."
  End if
 End if
 if (speakon = true) then
  Speak.Speak "Минус "&CStr(tmp)&" герц"
 End if
 ' посылаем
 Wrap.WriteFile hFile, CStr(freq), Len(CStr(freq)), pcbWritten, 0
 ' озвучиваем
 if (speakon = true) then
  Speak.Speak "Установлена частота " & freq & " герц"
 End if
End if

if (rightt = "1") then
 tmp = nfreq*100000
 freq = freq + tmp
 if (freq > 62000000) then
  freq = 62000000
  if (speakon = true) then
   Speak.Speak "Больше 62 мегагерц нельзя"
  End if
 End if
 if (speakon = true) then
  Speak.Speak "Плюс "&CStr(tmp)&" герц"
 End if
 ' посылаем
 Wrap.WriteFile hFile, CStr(freq), Len(CStr(freq)), pcbWritten, 0
 ' озвучиваем
 if (speakon = true) then
  Speak.Speak "Установлена частота "& freq &" герц"
 End if
End if



if (upt = "1") then
 freq = freq + nfreq
 if (speakon = true) then
 if (nfreq = 1) then ' fix с озвучкой цифры 1 по непонятной причине
  Speak.Speak "Плюс один герц"
 End if
 if (nfreq = 10) then
  Speak.Speak "Плюс 10 герц"
 End if  
 End if
 ' посылаем
 Wrap.WriteFile hFile, CStr(freq), Len(CStr(freq)), pcbWritten, 0
 ' озвучиваем
 if (speakon = true) then
  Speak.Speak "Установлена частота "& freq &" герц"
 End if
End if

if (downt = "1") then
 freq = freq - nfreq
 if (freq < 1) then
  freq = 1
  if (speakon = true) then
   Speak.Speak "Вышли за пределы в минус."
  End if
 End if
 if (speakon = true) then
 if (nfreq = 1) then ' fix с озвучкой цифры 1
  Speak.Speak "Минус один герц"
 End if
 if (nfreq = 10) then
  Speak.Speak "Минус 10 герц"
 End if
 End if
 ' посылаем
 Wrap.WriteFile hFile, CStr(freq), Len(CStr(freq)), pcbWritten, 0
 ' озвучиваем
 if (speakon = true) then
  Speak.Speak "Установлена частота "& freq &" герц"
 End if
End if


Loop ' завершение цикла

' закрываем порт
Wrap.CloseHandle(hFile)
' озвучиваем
if (speakon = true) then
 Speak.Speak "Программа завершена."
End if
Как видите, можно не только осуществлять навигацию частотными поддиапазонами по клавишам 'W', 'A', 'S','D', но и осуществлять прямой ввод частоты цифровыми клавишами 0..9 по маркерной клавише 'Q'. Эта же клавиша задает не только начало, но и конец ввода частоты по логическому флагу которого идет отсылка данных в последовательный порт.

Видеодемонстрация

1.Тестирование на мультиметре-частотомере

Визуализация и измерение на первом этапе проводились с помощью дешевого китайского прибора XB-868 в режиме частотомера по выходу ZQ_OUT платы синтезатора. Что, кстати, и выявило завышенные TTX на коробке мультиметра по параметру максимальной частоты 30 МГц. Ничего подобного! Явно приписан нолик :). Уже на трех мегагерцах на выходе с DDS, а продуктам Analog Devices я привык доверять, хваленный XB-868 сбивался и показывал явную чушь. Не помогала и подстройка подстроечным сопротивлением на плате модуля. Возможно это особенность именно моего мультиметра, не могу ручаться за все.



2. Тестирование на цифровом осциллографе Tektronix TDS3032B


Фотографии в различных режимах (выходные частоты 1, 10, 20, 40 и 62 МГц):






Постскриптум

Тестирование модуля синтезатора HC-SR08 на базе AD9850 выявило две схемотехнических недоработки: первая - недостаточность фильтрации по питанию при запитке от USB (любая импульсная помеха, например включение ламп ЛДС в комнате, приводит к сбросу рабочего режима синтезатора), поэтому требуется хороший помехозащищенный источник питания, а не просто напрямую от БП ПК, либо как вариант надеть ферритовое колечко на питающие провода и добавить суппрессор; вторая - при росте частоты от 40 МГц наблюдается замыливание фронтов, что объяснимо с точки зрения синтеза при тактовой 125 МГц, но также говорит о неоптимальности частоты среза фильтра на выходе для этих частот.

В планах голосовой ввод частоты )

Ресурсы
  1. DATA-SHEET на DDS AD9850 http://www.analog.com/media/en/technical-documentation/data-sheets/AD9850.pdf
  2. DATA-SHEET на модуль EIM377 http://www.eimodule.com/download/EIM377_AD9850_Signal_Generator_Module_V01.pdf 
  3. Скачать полный комплект ресурсов к материалу  
  4. Видео монтажа

Стать подписчиком блога и быть в курсе актуальных разработок

5 комментариев:

  1. Топик обновлен с тестами на цифровом осциллографе Tektronix TDS3032B.

    ОтветитьУдалить
  2. Произведено обновление скрипта управления для прямого ввода частоты выходного сигнала цифровыми клавишами на клавиатуре. Маркерной клавишей начала и окончания ввода назначена клавиша 'Q'.

    ОтветитьУдалить
  3. У меня точно такой же тестер и точно такой-же модуль.
    Но строк кода меньше, правда я PIC18 использовал в HID интерфейсе. EW8SA

    ОтветитьУдалить
  4. Тестер видать популярный ), я свой на али заказывал.

    ОтветитьУдалить

В комментариях уважайте собеседника, внимательно читайте посты и не додумывайте. Просьбы и предложения из разряда: «можно ваш Skype/Viber/телефон», «напишите мне в vk/FB», а также другие им подобные — игнорируются. Выход новых версий ПО, внешняя ссылка, переставшая работать с течением времени и т.п. не является основанием для претензий. Желающие спокойно подискутировать и высказаться — Welcome. Желающие спонсировать блог — Donate. Нарушение этих простых правил ведет к бану и удалению комментариев без предупреждения.