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
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.
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
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...
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.
> 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.
@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
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...
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
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
Im Prinzip wird das mit den Überlauf der LUT auch so gemacht. Die Variablen sollten aber vom Typ unsigned long sein.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.