Forum: Mikrocontroller und Digitale Elektronik Frage zu DDS


von Pat F. (breaker87)


Lesenswert?

Hallo,

ich versuche einen Frequenzgenerator auf Basis von DDS zu realisieren. 
Dazu  möchte ich aber kein DDS-IC verwenden, sondern alles über den µC 
(STM32) machen. Das Signal will ich von 1Hz bis 1MHz einstellen können 
(in 1Hz Schritten).

Soweit ich das verstanden habe, muss ich zuerst eine Rampe generieren, 
die ich dann mit einer Werte-Tabelle in das eigentliche Signal "wandle".

Mein Prozessor läuft mit 24MHz - angenommen ich brauche für die 
Rampen-Funktion 10 Zyklen, dann wäre ich auf einer Wandlerfrequenz von 
2,4MHz. Die maximal darstellbare Frequenz ist dann somit 1,2MHz 
(Nyquist...).
Um 1Hz auszugeben müsste ich 2.400.000 Schritte machen, also in jedem 
Zyklus den Wert um 1 erhöhen? Für 1MHz dann 2,4 Schritte?

Wenn nun der Phasenaccumulator 32Bit hat, wären dass insgesammt 2^32 
Schritte.In den Varianten die ich bis jetzt gefunden habe, gibt es 
Wertetabellen von 256-1024 Werten. Wie das ganze dann aber in Verbindung 
mit der Schrittweite funktioniert verstehe ich nicht... Dort werden dann 
nur 8MSB verwendet - wie komme ich dann aber auf die gewünschte 
Frequenz?

Den Analog DDS-Guide habe ich mir bereits durchgelesen - leider verstehe 
ich das trotzdem nicht so recht.

Ich suche hier keine Codes sondern mir geht es in erster Linie ums 
Verständnis.


lg

von trulala (Gast)


Lesenswert?

Du musst auch noch Takte für das lesen der LUT und das ausgeben 
einplanen;)

von Pat F. (breaker87)


Lesenswert?

Auf http://www.myplace.nu/avr/minidds/minidds.asm wurde das ganze in 9 
Takten gelöst - inkl. dem lesen der LUT. Also sollte das mit den 10 
Takten jetzt nicht soo abwegig sein.

von me (Gast)


Lesenswert?


von ich (Gast)


Lesenswert?

Zu dem Phasenakkumulator wird in feste Zeitschritten der Phasenoffset 
addiert. Der Phasenoffset/Zeitschritt ist dabei proportional zu deiner 
Wunschfrequenz. Aus dem Wert des Phasenakkus nimmst du ein paar höhere 
Bits, z.B. 24..31 und gehst damit in die 256-Bit Amplitudentabelle zur 
Erzeugung deines Analogsignals. Die Tabelle repräsentiert die 
Analogwerte für eine Periode deines Signals.

mfg

von Pat F. (breaker87)


Lesenswert?

me schrieb:
> Gugst du hier:
> http://www.myplace.nu/avr/minidds/index.htm

Ja die Seite kenne ich... Hab' ich auch im vorigen Post erwähnt...
Dort finde ich aber keine Erklärung - lediglich den Hinweis, dass der 
ASM Code selbsterklärend ist bzw. sein soll...

von Peter R. (pnu)


Lesenswert?

Bei einem AVR kommt man mit einer 24-Bit-Addition auf eine Schleife von 
8 Takten.

Die Dauer einer Schwingung bestimmt sich aus einem vollen 24-bit Zyklus 
der Summe  von Null bis Überlauf.

Der momentane Spannungswert wird aus der momentan anstehenden Summe (im 
Phasenakku) per lookup Tabelle gebildet. Dabei hat es wenig Sinn, die 
vertikale Achse mit den vollen 24 bit wiederzugeben. In einfachen Fällen 
beschränkt man sich eben auf die oberen 8Bit.

In der Zeitachse (Phasenakku) schreitet der Wert in Schritten voran, die 
dem Summanden entsprechen. Da man vertikal auf 8 bit beschränkt hat, 
nimmt man für die Zeitachse auch nur die oberen 8 bit. Es hat wenig 
Sinn, zeitlich feiner zu unterteilen als spannungsmäßig.

Wenn man größere Summanden hat, ist die Additionsfrequenz so hoch, dass 
in eine Periode des erzeugten Signals weniger als 256 Ausgaben 
entfallen. Dann
werden von einer Addition zur nächsten Schritte in der Tabelle 
ausgelassen. Das Ausgangssignal besteht dann nicht aus 256 sondern aus 
weniger Punkten und der Sinus wird grober angemähert.

von Karl H. (kbuchegg)


Lesenswert?

> Wenn nun der Phasenaccumulator 32Bit hat, wären dass insgesammt
> 2^32 Schritte.In den Varianten die ich bis jetzt gefunden habe,
> gibt es Wertetabellen von 256-1024 Werten. Wie das ganze dann
> aber in Verbindung mit der Schrittweite funktioniert verstehe ich
> nicht...

Zum Phasenaccumulator wird jeweils eine konstanter Wert addiert, der so 
berechnet wurde, dass sich die Frequenz ergibt.
Der Grund warum man da mehr Bits benutzt ist der, dass man ein paar Bits 
für die 'Nachkommastellen' dieses konstanten Wertes braucht.

Mal angenommen wir hätten das alles nicht, sondern benutzen ganz einfach 
eine double Variable

Dann ist dsa Prinzip ja dieses

In regelmässigen Zeitabständen, die immer gleich sind, wird dieser Code 
ausgeführt

double Index;
double Increment;


   Index = Index + Increment
   if ( Index > LUT_TABLE_SIZE )
     Index -= LUT_TABLE_SIZE;

   Ausgabe von  LUT[Index];

das ist das Prinzip. Durch die Wahl von Inkrement kannst du das so 
einstellen, dass du die komplette LUT Tabelle in einem bestimmten 
Zeitraum beliebig oft ausgeben kannst (nahezu). Durch das richtige 
Increment kannst du es so hindrehen, dass Werte aus der LUT Tabelle 
doppelt ausgegeben werden (und damit die komplette Ausgabe langsamer 
erfolgt - die Frequenz sinkt) oder das Wert übersprungen werden (die LUT 
wird insgesamt schneller ausgegeben, die Frequenz steigt)

Beispiel:
Deine LUT Tabelle habe 256 Werte
Der Code wird jede 1ms einmal aufgerufen.
Wie gross muss daher das Increment sein, wenn du 1Hz erreichen willst?

1Hz bedeutet, dass die LUT (mit 1 kompletten Wellenzug) in 1 Sekunde 1 
mal komplett ausgegeben werden muss.

Da der Code 1000 mal in der Sekunde aufgerufen wird und die LUT 256 
Einträge gross ist, bedeutet dass, dass sich Index nur bei jedem 4. 
Aufruf um 1 erhöhen darf. Daraus folgt: Increment muss 0.25 sein.

Soweit so gut.
Nur: An dieser Stelle wollen wir keine Gleitkommarechnung haben. Das 
würde viel zu lange dauern. Also macht man das, was man in solchen 
Fällen immer tut: Anstelle von in Euro, rechnen wir in Cent (also alle 
Werte mal 100).

Das würde dann so aussehen

long Index;
long Increment;


   Index = Index + Increment
   if ( Index > LUT_TABLE_SIZE * 100 )
     Index -= LUT_TABLE_SIZE * 100;

   Ausgabe von  LUT[Index / 100];

Wenn du dir das überlegst, kommt das wieder aufs gleiche raus. Nur: 
Anstelle von 0.25 muss das Inkrement jetzt 25 sein, weil ja alle Werte 
mal 100 genommen wurden. Es bedeutet aber auch, dass es Beschränkungen 
für das Inkrement gibt, da ja das kleinste Inkrement 1 ist.

OK. Weiter.
Jetzt wollen wir da natürlich nicht mit 100 arbeiten, weil 
Multiplikationen bzw. Divisionen damit aufwändig sind. Besser sind 2-er 
Potenzen, die sind einfacher weil sie durch Bit-Shifts gemacht werden 
können. Manche 2-er Potenzen sind sogar noch einfacher, zb 256. Hier 
muss gar nichts geschoben werden. Man nimmt einfach von einem 16 Bit 
Ergebnis nur das High-Byte und hat damit automatisch durch 256 
dividiert.

Und damit sind wir beim Phase-Akku, so wie er implementiert wird. Der 
Teilungsfaktor (die 100 von oben) sind dann zb 65536. Das bedeutet, dass 
man das Inkrement in 1/65536 tel Schritten einstellen kann.

von Pat F. (breaker87)


Lesenswert?

@Peter: Dankeschön!
@Karl Heinz: Vielen Dank - jetzt sieht das ganze schon mal deutlich 
klarer aus. Ich werde jetzt mal versuchen, das ganze in einem 
Flussdiagramm abzubilden - wenn das klappt, sollte ich es verstanden 
haben. ;-)

Nochmals Danke,
lg

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Patrick F. schrieb:
> Die maximal darstellbare Frequenz ist dann somit 1,2MHz (Nyquist...).
Mit diesem Signal kannst du aber nur noch mathematisch was anfangen, in 
der Praxis ist das unbrauchbar. Das ist dir klar?

Denn vor allem wirst du nicht einen so steilen Filter hinbekommen, der 
dir die 2,4MHz abschneiden könnte...

von Pat F. (breaker87)


Lesenswert?

Dass das Signal nicht mehr wirklich gut dargestellt wird ist mir 
bewusst.
Wenn alles läuft, werde ich ja dann sehen, wie weit das Signal noch 
wirklich brauchbar ist. (Aber das erste Ziel ist jetzt mal überhaupt 
irgendeinen Sinus auszugeben. ;-)

lg

von Pat F. (breaker87)


Lesenswert?

Karl Heinz Buchegger schrieb:
> long Index;
> long Increment;
>
>
>    Index = Index + Increment
>    if ( Index > LUT_TABLE_SIZE * 100 )
>      Index -= LUT_TABLE_SIZE * 100;
>
>    Ausgabe von  LUT[Index / 100];

Also angenommen ich habe folgende Daten:
LUT-Size 1.024
Phasenaccu 4.294.967.296 (2^32)
Increment für 250.000Hz --> 536.870.912
(Increment = Fout * Accu / Wandlerfreq)

Wenn ich dieses Beispiel nehme, dann fällt mir ja Praktisch der 
Abfrage-Teil (if...) weg, da ich ja einen Overflow der long variable 
hätte, was das selbe bewirkt, oder?
Dann bleibt mir zur Ausgabe nur mehr das über:
1
long index;
2
long increment;
3
4
index = index + increment;
5
Ausgabe von LUT[index >> 22]

Damit hätte ich dann folgende Werte:
1
INDEX           >>22  LUT[Index>>22]
2
            1     0     512
3
  536.870.913   128     874
4
1.073.741.825   256   1.024
5
1.610.612.737   384     874
6
2.147.483.649   512     512
7
2.684.354.561   640     150
8
3.221.225.473   768       0
9
3.758.096.385   896     150
10
            1     0     512


Habe ich da jetzt einen groben Denkfehler drinnen, oder könnte das so 
funktionieren?

lg

von ulrich (Gast)


Lesenswert?

Im Prinzip wird das mit den Überlauf der LUT auch so gemacht.  Die 
Variablen sollten aber vom Typ unsigned long sein.

von Pat F. (breaker87)


Lesenswert?

ulrich schrieb:
> Im Prinzip wird das mit den Überlauf der LUT auch so gemacht.  Die
> Variablen sollten aber vom Typ unsigned long sein.

Danke für den Hinweis - bei der Auswahl des Phasenaccus hab ich ja daran 
gedacht, aber beim tippen dann nicht mehr... ;-)
4.294.967.296 --> unsigned long

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.