DDS Signalgenerator mit AD9850

Die „Direkte Digitale Synthese“ (kurz DDS) ist ein beliebtes Verfahren zur Erzeugung periodischer Signale beliebiger Kurvenform mit theoretisch unbegrenzter Frequenzauflösung. Mit dem hier verwendeten DDS-Chip AD9850 von Analog Devices können von nahezu 0Hz bis max. 40MHz sinusförmige Signale erzeugt werden. Das Sinussignal ist in digitaler Form auf dem Chip hinterlegt und wird über einen schnellen 10-bit DA-Wandler kontinuierlich ausgegeben. Je nach Aufbau verändern sich oberhalb der 30MHz Grenze allerdings die Kurvenform und Amplitude deutlich. Bis etwa 40MHz lassen sich aber durchaus akzeptable Ergebnisse erzielen. Bei 40MHz halbiert sich jedoch bereits die unbelastete Ausgangsspannung auf etwa 500mVss. Bei 50MHz sindAD9850_Frontseite es gerade mal noch etwa 400mVss! Ein geregelter 50Ohm Pufferverstärker könnte an dieser Stelle daher noch Sinn machen. Darüber hinaus können theoretisch bis zu 62,5MHz erzeugt werden; das Signal ist dann allerdings weit weg von einer Sinusform, außerdem nimmt die Amplitude rapide weiter ab.

Rechtecksignale können bis zu 1MHz mit manuell einstellbarem Puls- Pausenverhältnis generiert werden. Mehr dazu kann auch dem Datenblatt AD9850 entnommen werden. Die Auflösung auf diesem Board beträgt bei 125MHz Takt genau 0,0291Hz.

AD9850_Mit_Kabel Im www sind für wenige US-$ im einstelligen Bereich im Wesentlichen 2 unterschiedliche Boardversionen zu finden. Das von mir verwendete Board hat an der einen Querseite eine doppelreihige 2*7-pol. Stiftleiste und an der gegenüberliegenden Seite eine einreihige 7-pol. Stiftleiste. Dem Datenblatt des Moduls kann weiteres an Details auch  zur Pinbelegung entnommen werden.

Die Kommunikation und Steuerung mit dem Board erfolgt über 5 Register. Es kann sowohl 8-bit breit parallel als auch seriell über jeweils 40-bit Kommandosequenzen erfolgen.

Technische Daten
Betriebsspannung:                5V
Frequenzbereich Sinus:        0-40 MHz / 1Vss fix oder variable über I-R Pin
Max. 62,5MHz bei extremer Abweichung von der Sinusform
Frequenzbereich Recheck: 0-1 MHz / TTL (über 1 MHz ändert sich die Kurvenform)
Tiefpassfilter:                       70 MHz
Auflösung:                           0,0291Hz bei 125MHz Takt
Ansteuerung:                        8-bit parallel oder seriell über 4 Pins
Potentiometer:                     Tastgrad (duty cycle) Rechtecksignal
Takt:                                   125MHz Quarz Oszillator Modul
Abmessungen:                     30mm * 42mm

 AD9850_AnschlüsseAuf dem Board befinden sich jeweils 2 gegenphasige Ausgänge für das Sinussignal und das Rechtecksignal. Der Sinusausgang Sine1 durchläuft zuvor ein 70MHz Tiefpassfilter. Die Grenzfrequenz habe ich nicht überprüfen können. An der 2-pol. Stiftleiste Sine2 kann das Sinussignal direkt vom DDS-Chip entgegengenommen werden. Es ist in der Phase um 180° gegenüber dem Sine1 Ausgang invertiert. Unbelastet stehen hier 1Vss zur Verfügung.

An den Ausgängen Square1 und Square2 stehen gegenphasige Rechtecksignale mit TTL-Pegel bis zu 1MHz an. Über die Pins Comparator IN und REF kann der Tastgrad eingestellt werden, alternativ auch über das auf dem Board befindliche Poti (Duty cycle manual adjustment).

Auf der gegenüberliegenden Seite befinden sich die Pins D0 bis D7 für die parallele Datenübertragung. Für die serielle Datenübertragung werden die Pins D7, WCLK, FQUP und RESET benötigt. Dazu muss außerdem der Jumper J1 gesteckt sein!

Über den Pin I-R kann mittels eines externen Potis das Sinussignal in der Amplitude beeinflusst werden. Dazu muss allerdings unbedingt erst der Jumper J3 I-R entfernt werden! Über Vcc wird das Board mit 5V versorgt, GND liegt wie üblich an Masse.

Internet Tool

Zwecks Überprüfung der im Sketch berechneten Werte zur Übergabe der Frequenz an den DDS-Chip, habe ich auf der Webseite von Analog Devices ein interessantes Online Designtool ADIsimDDS entdeckt. Damit lässt sich sogar die zu erwartende Kurvenform überprüfen. Zuerst wird der gewünschte Chip ausgewählt, hier der AD9850. Die Taktrate wird passend dazu angezeigt, kann aber auch überschrieben werden. Im nächsten Feld darunter wird nun die gewünschte Frequenz eingegeben, die der DDS-Chip ausgeben soll. Einen Taktvervielfacher gibt es hier nicht, daher kann das Feld übersprungen werden. Nun müssen wir uns noch für eine entsprechende Ausgabe in Dezimal, Hex oder Binär entscheiden und dann den Button „Run Model“ anklicken. Sehr schön ist auch sofort die tatsächlich ausgegebene Frequenz zu sehen.

Die Programmierung

Programmiert wird der DDS-Chip über ein 40-bit Register. 32-bit davon sind für das Abstimmwort in den Registern W1 bis W4. Im ersten Register W0 sind 5-bit für die Phasenlage und die letzten 3-bit für den PowerDown und 2 Kontrollbits reserviert. Die beiden Kontrollbits sollen auf LOW bleiben, da sie firmenintern für Tests genutzt werden.

8-Bit Parallel Load Data/Control Word Functional Assignment
Word Data[7] Data[6] Data[5] Data[4] Data[3] Data[2] Data[1] Data[0]
  (MSB)             (LSB)
W0 Phase-b4 Phase-b3 Phase-b2 Phase-b1 Phase-b0 Power-Down Control Control
 (MSB)
W1 Freq-b31 Freq-b30 Freq-b29 Freq-b28 Freq-b27 Freq-b26 Freq-b25 Freq-b24
W2 Freq-b23 Freq-b22 Freq-b21 Freq-b20 Freq-b19 Freq-b18 Freq-b17 Freq-b16
W3 Freq-b15 Freq-b14 Freq-b13 Freq-b12 Freq-b11 Freq-b10 Freq-b9 Freq-b8
W4 Freq-b7 Freq-b6 Freq-b5 Freq-b4 Freq-b3 Freq-b2 Freq-b1 Freq-b0

Die Abhängigkeiten von Ausgangsfrequenz, Referenztakt und Abstimmwort sind in folgender Formel laut Datenblatt definiert:

fOUT = (Δ Phase × CLKIN) / 232      

Die Formel entsprechend umgestellt, kann das Abstimmwort passend berechnet werden

Δ Phase = fOUT  x 232 / CLKIN

Darin bedeuten:
Δ Phase:          Wert eines 32-bit Abstimmwortes
CLKIN:           Referenztaktfrequenz in MHz, hier 125MHz
fOUT:              Ausgangsfrequenz des AD9850 in MHz
(232 oder auch 2^32 entspricht dezimal 4.294.967.296)

In den Arduino C++ Code übertragen wird das Abstimmwort für den DDS-Chip wie folgt erzeugt:

unsigned long tuningWord = (frequency * pow(2, 32)) / DDSCLK;

Anschließend wird das Register FQUP auf LOW zurückgesetzt.

digitalWrite (FQUP, LOW);                        // Switch off the frequency upload latch

 Das so berechnete Abstimmwort muss nun nur noch auf 4 Bytes wie folgt verteilt werden und das Kontrollbyte Register W0 geladen werden.

  shiftOut(SERIALDATA, CLK, LSBFIRST, tuningWord);                        // Register W1
  shiftOut(SERIALDATA, CLK, LSBFIRST, tuningWord >> 8);
  shiftOut(SERIALDATA, CLK, LSBFIRST, tuningWord >> 16);
  shiftOut(SERIALDATA, CLK, LSBFIRST, tuningWord >> 24);
  shiftOut(SERIALDATA, CLK, LSBFIRST, 0x0);                                        // Register W0
  digitalWrite (FQUP, HIGH);             // Latch up the uploaded frequency data

Nach stöbern in vielen Foren, Datenblättern und Application Notes habe ich noch interessante Hinweise gefunden.

Ein Reset des DDS-Chips setzt NICHT die Schieberegister zurück, sondern nur das FQ_UD Register und den internen Phasenakkumulator auf COS(0). Daher ist davon auszugehen, dass nach einem Reset zufällige Werte im Schieberegister zu finden sind. Insbesondere kann daher auch in reservierten Blöcken ungültiger Code enthalten sein und den DDS-Chip zu nicht erklärbarem Verhalten bringen. Aus diesem Grund darf KEIN FQ_UD Kommando gesendet werden bevor im Schieberegister gültige Werte vorliegen.

Der Phasenwinkel

Wikipedia Phasenwinkel http://commons.wikimedia.org/wiki/File:Rot.-Zeiger2.svg

Sinussignale haben einen Phasenverlauf zwischen 0 und 2Pi. Am simpelsten kann man sich das so vorstellen, dass mit einem einzigen 360° Umlauf ein vollständiger Durchlauf eines Sinussignals abgebildet werden kann. Mehr dazu auch bei Wikipedia.

Gemäß Datenblatt stehen die 5 höchstwertigen bits des Registers W0 bereit, um die Phasenlage des Signals zu beeinflussen. In der nachfolgenden Binärtabelle sind links diese 5 bits mit ihren Wertigkeiten zu finden, in der Spalte daneben das Äquivalent in dezimaler Form. Mit Excel lässt sich nun recht einfach jedem dieser Dezimalwerte ein Winkel berechnen und zuordnen, um schneller ein Gefühl für die nächsten Programmierschritte des Arduinos zu erhalten.

Winkelschritt = 360° / 32 = 11,25°

Also jedem dieser 5 bits kann entsprechend der dezimalen Wertigkeit von 0 bis 31 jeweils ein Vielfaches eines Winkels von 11,25° zugeordnet werden. Übergeben wir an das W0 Register beispielsweise den binären Wert B1000 0000 entsprechend der Tabelle unten in dezimaler Form den Wert 16, so entspricht dies einem Winkel von 180°.

MSB       LSB Binärwert

32

16

8

4

2

1

Dezimalwert

360

4

3

2

1

0

 Wert Phasenwinkel

0

0

0

0

0

0

0

0

0

0

0

1

1

11,25

0

0

0

1

0

2

22,5

0

0

0

1

1

3

33,75

0

0

1

0

0

4

45

0

0

1

0

1

5

56,25

0

0

1

1

0

6

67,5

0

0

1

1

1

7

78,75

0

1

0

0

0

8

90

0

1

0

0

1

9

101,25

0

1

0

1

0

10

112,5

0

1

0

1

1

11

123,75

0

1

1

0

0

12

135

0

1

1

0

1

13

146,25

0

1

1

1

0

14

157,5

0

1

1

1

1

15

168,75

1

0

0

0

0

16

180

1

0

0

0

1

17

191,25

1

0

0

1

0

18

202,5

1

0

0

1

1

19

213,75

1

0

1

0

0

20

225

1

0

1

0

1

21

236,25

1

0

1

1

0

22

247,5

1

0

1

1

1

23

258,75

1

1

0

0

0

24

270

1

1

0

0

1

25

281,25

1

1

0

1

0

26

292,5

1

1

0

1

1

27

303,75

1

1

1

0

0

28

315

1

1

1

0

1

29

326,25

1

1

1

1

0

30

337,5

1

1

1

1

1

31

348,75

Für den Arduino Microcontroller lässt sich obige Erkenntnis wie nachfolgend beschrieben umsetzen.

byte controlByte = (phase & B00011111)<<3 | (powerDown & B00000001)<<2 | controlBits & B00000011;

Über die Variable phase kann ein Phasenwert zwischen 0 und 31 eingelesen und über den Binärwert B00011111 ausmaskiert und der Variablen controlByte zugewiesen werden. Da die Phasenwerte binär die Positionen LSB 0 bis MSB 4 haben, müssen wir diese vor Zuweisung noch an ihre eigentlichen Positionen  um 3 bit nach links zu bit 3 bis bit 7 verschieben, was über den bitshift left Operator <<3 geschieht.

Nachfolgend nun der gesamte Sketch. Ich habe ihn bisher nur mit einem Leonardo getestet, es sollte aber auch jeder Uno oder Duemilanove funktionieren. Der Sketch sollte jetzt zusammen mit den Kommentaren innerhalb des Sketches weitgehend selbsterklärend sein.

Gleich am Anfang werden diverse Variablen und Konstanten deklariert, die hier noch nicht alle benötigt werden. Da ich diese jedoch für die spätere Erweiterung benötige, habe ich Sie gleich mit definiert. Es gibt 3 Funktionen, die ich ausgelagert habe, um den Code etwas übersichtlicher zu gestalten. Das sind DDSsetFrequency() um die 5 W-Register des AD9850 mit der Frequenz und der Phasenlage zu beschreiben sowie DDSinit() und DDSreset(), die nur einmal im Setup aufgerufen werden, um den DDS-Chip zu initialisieren. Beim Kopieren in den eigenen Sketch bitte eventuelle Zeilenumbrüche unbedingt beachten!

/**************************************************************************/
/**************************************************************************/
/***  Declaration of global constants and initialization of variables. Include libraries  ***/
/**************************************************************************/
/***  Software release and date  ***/
const char* sketchname             =  „Arduino DDS 9850“;
const char* sketchname2           =  “    Leonardo    „;
const char* revision                   =  „R.0.1“;
const char* author                     =  „Olaf Meier“;
const char* date                         =  „2014/01/27“;
 
/**************************************************************************/
/***  Declare constants and variables for the DDS-Chip  ***/
/***  Pin connection of the AD9850 board to the Arduino  ***/
const byte SERIALDATA             =  A2;           // D7
const byte CLK                            =  A3;           // WCLK
const byte FQUP                         =  A4;           // FQ_UP
const byte RESET                        =  A5;           // RESET
 
const unsigned long DDSCLK    =  125000000;  // Reference clock 125MHz with the AD9850 chip
unsigned long VFO_A_Freq       =  10000;        // 10kHz
byte Phase_A                               =  16;           // Phase = 0 to 31, 11.25° per step; 180°
unsigned long VFO_B_Freq       =  10000;        // 10kHz
byte Phase_B                               =  4;            // Phase = 0 to 31, 11.25° per step; 45°
 
/**************************************************************************/
/**************************************************************************/
void setup()
{
  pinMode (SERIALDATA,  OUTPUT);
  pinMode (CLK, OUTPUT);
  pinMode (FQUP,  OUTPUT);
  pinMode (RESET, OUTPUT);
  DDSinit();
  DDSreset();
}
/**************************************************************************/
/**************************************************************************/
void loop()
{
  /***  For better visibility of the phase jumps at your scope, some short delays added  ***/           
  DDSsetFrequency(Phase_A, VFO_A_Freq);
  delay(1);
  DDSsetFrequency(Phase_B, VFO_A_Freq);
  delay(1);
}
/**************************************************************************/
/**************************************************************************/
/***  AD9850 specific functions  ***/
/***  Set phase and frequency of the DDS-Chip  ***/
void DDSsetFrequency(byte phase, unsigned long frequency)
{
  /***  Set controlByte binary BxxxxxP00: Phase xxxxx, PowerUp P=LOW / Down=HIGH, ControlBits=00  ***/
  byte controlBits = B00000000;                 // BxxxxxP00 only allowed bits are: xxxxxP!!
  boolean powerDown;                               // 1=Power down
  /***  Build controlByte for the register W0  ***/
  byte controlByte = (phase & B00011111)<<3 | (powerDown & B00000001)<<2 | controlBits & B00000011;
 
  unsigned long tuningWord = (frequency * pow(2, 32)) / DDSCLK;
  digitalWrite (FQUP, LOW);                        // Switch off the frequency upload latch
 
  shiftOut(SERIALDATA, CLK, LSBFIRST, tuningWord); // Register W1
  shiftOut(SERIALDATA, CLK, LSBFIRST, tuningWord >> 8);
  shiftOut(SERIALDATA, CLK, LSBFIRST, tuningWord >> 16);
  shiftOut(SERIALDATA, CLK, LSBFIRST, tuningWord >> 24);
  shiftOut(SERIALDATA, CLK, LSBFIRST, controlByte);// Register W0
  digitalWrite (FQUP, HIGH);                       // Switch on and latch up the uploaded frequency data
}
/**************************************************************************/
void DDSinit()
{
  digitalWrite(RESET, LOW);
  digitalWrite(CLK, LOW);
  digitalWrite(FQUP, LOW);
  digitalWrite(SERIALDATA, LOW);
}
/**************************************************************************/
/***  According data sheet following reset sequence can be used  ***/
void DDSreset()
{
  // Set CLK and FQUP to LOW
  // Create High Pulse to RESET 
  // Create HIGH Pulse to CLK
  // Set SERIALDATA to ZERO and finally create a HIGH pulse to FQUP
  /***  Leonardo does not need any delays to settle outputs. AD9850 is fast enough anyway  ***/
  digitalWrite(CLK, LOW);
  digitalWrite(FQUP, LOW);
 
  digitalWrite(RESET, LOW);
  //delayMicroseconds(2);
  digitalWrite(RESET, HIGH);                       // Create a pulse for FQUP pin
  //delayMicroseconds(2);
  digitalWrite(RESET, LOW);
  //delayMicroseconds(2);
 
  digitalWrite(CLK, LOW);
  //delayMicroseconds(2);
  digitalWrite(CLK, HIGH);                         // Create a pulse for CLK pin
  //delayMicroseconds(2);
  digitalWrite(CLK, LOW);
  //delayMicroseconds(2);
 
  digitalWrite(SERIALDATA, LOW);          // Ensure SERIALDATA D7 pin is LOW
 
  digitalWrite(FQUP, LOW);
  //delayMicroseconds(2);
  digitalWrite(FQUP, HIGH);                       // Create a pulse for FQUP
  //delayMicroseconds(2);
  digitalWrite(FQUP, LOW);
}                                                                              // End of void DDSreset
/***  End of functions  ***/
/**************************************************************************/
/**************************************************************************/

Advertisements

Ein Gedanke zu “DDS Signalgenerator mit AD9850

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s