Genarator DDS na AD9833
Prosty generator funkcyjny na module AD9833.
Generator zbudowany jest na gotowym module (Chiny – ok. 7 USD) z układem AD9833, sterowanym Arduino NANO V3, a nastawy prezentowane są na wyświetlaczu alfanumerycznym LCD 2×16 znaków (I2C). Układ generuje przebiegi sinusoidalne, trójkątne i prostokątne – bez regulacji poziomu sygnału wyjściowego. Manipulatorem jest enkoder, który służy do wyboru (wciśnięcie) i regulacji (pokrętło) nastaw.
Schemat układu (odręczny) – poniżej :
Układ generuje przyzwoite przebiegi w zakresie do ok. 2 MHz – powyżej tej częstotliwości jakość i poziom sygnału drastycznie spada, choć sam generator nadal pracuje nawet do ok. 10 MHz.
Kod programu jest dosyć rozbudowany, ale podstawowe procedury – obsługi AD9833 – są łatwe do aplikacji w dowolnym układzie. Sporo miejsca w kodzie zajmują procedury obsługi Menu, zmiany nastaw i wykrywania wartości granicznych oraz prezentacji wyników na LCD. Sama obsługa AD9833 (sposób zapisu, rejestry, sterowanie) to fragmenty kodu znalezione w Necie – nie chciałem się już zagłębiać w dokumentację układu, a gotowej biblioteki nie znalazłem. Soft nie został też gruntownie przetestowany i nie wykluczam jakiś błędów – jak coś znajdziecie, proszę pisać w komentarzach …
Kod :
#include <Wire.h> #include <LCD.h> #include <LiquidCrystal_I2C.h> #include <SPI.h> #include <Encoder_Polling.h> #define FSYNC 6 //definicja PIN D6 - FSYNC AD9833 // POZOSTAŁE WYKORZYSTANE PINY DLA AD9833 Z ARDUINO NANO V3 // D11 - MOSI - SDATA // D13 - SCK - SCLK // PINY DLA MAGISTRALI I2C // A4 - SDA // A5 - SCL // LiquidCrystal_I2C lcd(adres,En,Rw,Rs,D4,D5,D6,D7,polaryzacja); LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); #define Light 3 // podswietlanie - pin 3 //********** definicja pinow dla Encodera int encoderPin_A = 4; int encoderPin_B = 3; int PushButt = 2; // pin encodera PushButton //********** parametry startowe i zmienne long czastota = 500000; // ustawienie czestotliwosci startowej String czestotliwosc; long krok = 10; // krok poczatkowy int sinus = 0x2000; int trojkat = 0x2002; int prostokat = 0x2020; int typprzebiegu; int znacznik = 10; // pomocniczy znacznik - uzywany przy MENU void setup() { lcd.begin (16,2); // def. LCD lcd.setBacklightPin(Light,POSITIVE); // def. rodzaj LCD lcd.setBacklight(HIGH); // wlacz podsw. LCD pinMode(FSYNC, OUTPUT); // konfiguracja pinu FSYNC SPI.begin(); // start SPI digitalWrite(FSYNC, HIGH); // ustawienie pinu FSYNC SPI.setDataMode(SPI_MODE2); // tryb pracy SPI encoder_begin(encoderPin_A, encoderPin_B); // start Encoder pinMode(PushButt, INPUT); // konfiguracja pinu PushButtona typprzebiegu = sinus; // wystartuj z przebiegiem SINUS } //*********************** Glowna petla programu ********************* void loop() { obsluga_buttona(); // sprawdx button w encoderze obsluga_encodera(); // sprawdz obrot w encoderze WriteFrequencyAD9833(czastota); // zapisz/odswiez f w AD9833 prezentacja(); // wyswietl wszystko na LCD delay(100); // pauza aby LCD nie mrugal // mozna badac flage zajetosci w LCD // ale istotnie komplikuje to soft } //*********************** Koniec glownej petli ********************* //*********************** Procedury ****************************** void obsluga_encodera() { if (znacznik == 10) // znacznik 10 zmiana sygnalu sin/prost/trojk { int dir = encoder_data(); // sprawdz Encoder if(dir > 0) // sprawdzam tylko w prawo { if(typprzebiegu == sinus) // jesli jest sin to zmien {typprzebiegu = trojkat; // na trojk delay(80);} // odczekaj - drgania else if (typprzebiegu == trojkat) //jesli trojk ustaw prost {typprzebiegu = prostokat;delay(80);} else if (typprzebiegu == prostokat)//jesli prost ustaw sin {typprzebiegu = sinus;delay(80);} else {} } else {} } //*****************koniec warunku dla znacznik == 10 if (znacznik == 20) // warunek dla znacznik 20 - zmiana kroku { int dir = encoder_data(); // sprawdz Encoder if(dir > 0) // jeśli w prawo to ... { if (krok < 1000000) // jesli krok mniejszy niz 1MHz {krok = krok * 10;delay(100);} // zwieksz krok-mnozenie razy 10 else // jesli 1MHz nie rob nic {} } else if (dir <0 ) // jeśli w lewo to ... { if (krok >= 10) // dodatkowy warunek // sprawdzenie czy nie dziele 0 {krok = krok / 10; // zmniejsz krok - dzielenie przez 10 delay(100); } else {krok = 1;delay(100);} // ustaw krok 1 Hz } else {} } //****************koniec warunku dla znacznik == 20 else if (znacznik == 30) // warunek dla znacznik 30 - zmiana czestotliwosci { int dir = encoder_data(); // sprawdz Encoder if(dir > 0) // jeśli w prawo to ... {czastota = czastota + krok;} // zwieksz f o krok else if (dir <0 ) // jeśli w lewo to ... if ((czastota-krok) >= 0) { {czastota = czastota - krok;} // zmniejsz f o krok } else {} else {} } //********************koniec warunku dla znacznik == 30 else {} } //****************koniec procedury obslugi encodera void obsluga_buttona() { if (digitalRead(PushButt)) {} // nie wcisniety - wyjscie else { // wcisniety - ustalam w ktorej // pozycji menu i co zmienic if (znacznik == 10) // jesli znacznik = 10 ... {znacznik = 20;delay(120);} // ... zmien na 20 else if (znacznik == 20) // jesli znacznik = 20 ... {znacznik = 30;delay(120);} // ... zmien na 30 else {znacznik = 10;delay(120);} // jesli nie 10 i nie 20 to musi // byc 30 wiec ustaw znow 10 } } void prezentacja() // wyswietlenie wszystkich wartosci { lcd.clear(); // wyczysc LCD if(znacznik == 10) // znacznik = 10 {lcd.print("*");} // ... wyswietl "*" przed else {lcd.print(" ");} // znacznik <> 10 - spacja if(typprzebiegu == sinus) // jaki aktualnie mamy przebieg {lcd.print("sinus");} // jesli sinus - wyswietl else if (typprzebiegu == prostokat) {lcd.print("prost");} // jesli prostokat - wyswietl else if (typprzebiegu == trojkat) {lcd.print("trojk");} // jesli trojkat - wyswietl else {} if(znacznik == 20) // jesli znacznik = 20 { lcd.print(" *");} else {lcd.print(" ");} lcd.print("K="); if(krok == 1000000) { lcd.print(krok / 1000000); lcd.print("MHz"); } else if(krok >= 1000) { lcd.print(krok / 1000); lcd.print("KHz"); } else { lcd.print(krok); lcd.print("Hz"); } lcd.setCursor(0, 1); if(znacznik == 30) // jesli znacznik = 30 { lcd.print("*"); } else { lcd.print(" "); } // ponizej troche zakrecone zabiegi aby na LCD wyswietlana czestotliwosc // wygladala przyzwoicie - "F=xx.xxx.xxx Hz" czestotliwosc = String(czastota); //zamiana typu danych na string int dlugosc = czestotliwosc.length(); // ustalam dlugosc ciagu if (dlugosc == 6) // jesli dlugosc 6 - mniej niz 1 MHz { czestotliwosc = "0" + czestotliwosc; // to dopisz zero przed } else if(dlugosc == 5) // jesli dlugosc 5 - dziesiatki KHz { czestotliwosc = "00" + czestotliwosc; } else if(dlugosc == 4) // jesli dlugosc 4 - jednostki KHz { czestotliwosc = "000" + czestotliwosc; } else if(dlugosc == 3) // jesli dlugosc 3 - setki Hz { czestotliwosc = "0000" + czestotliwosc; } else if(dlugosc == 2) // jesli dlugosc 2 - dziesiatki Hz { czestotliwosc = "00000" + czestotliwosc; } else if(dlugosc == 1) // jesli dlugosc 1 - jednostki Hz { czestotliwosc = "000000" + czestotliwosc; } else {} lcd.print(" F=" + czestotliwosc.substring(0,1) + "." + czestotliwosc.substring(1,4) + "." + czestotliwosc.substring(4,7) + " Hz"); } // procedura obslugi AD9833 - zassana z Net`u - delikatnie zmodyfikowana // void WriteFrequencyAD9833(long frequency) { int MSB; int LSB; int phase = 0; long calculated_freq_word; float AD9833Val = 0.00000000; AD9833Val = (((float)(frequency)) / 25000000); // zegar DDS 25 MHz calculated_freq_word = AD9833Val * 0x10000000; MSB = (int)((calculated_freq_word & 0xFFFC000) >> 14); LSB = (int)(calculated_freq_word & 0x3FFF); LSB |= 0x4000; MSB |= 0x4000; phase &= 0xC000; WriteRegisterAD9833(0x2100); WriteRegisterAD9833(LSB); WriteRegisterAD9833(MSB); WriteRegisterAD9833(phase); WriteRegisterAD9833(typprzebiegu); } void WriteRegisterAD9833(int dat) { digitalWrite(FSYNC, LOW); SPI.transfer(highByte(dat)); SPI.transfer(lowByte(dat)); digitalWrite(FSYNC, HIGH); }