Hallo,
Mal angenommen meine Formel lautet nun
Y * 2^32
X = -----------
25000000
(AD9832 formel zur Berechnung des Datenwortes für den DDS)
0 < Y < 25000000
Wie kann ich diese Mikrocontrollergerecht lösen?
Ich könnte
a) 2^32/25000000 über den Compiler vorberechnen lassen und dann nur
noch mit Y multiplizieren lassen. Problem: Kommazahl. Fließ/Fixkomma
ist nicht im AVR-GCC implementiert und braucht vermutlich nur unnötig
rechenzeit.
b) Y 32 mal links shiften und durch 25000000 teilen lassen. Problem:
Als größtes Ergebnis habe ich 4294967295 (Y = 24999999). D.h. bei 1Mhz
Taktfrequenz und angenommenen 1 Cycle pro Durchlauf wären wir bei 4294
Sekunden Ausführungsdauer.
c) Formel eintippen und Compiler entscheiden lassen und auf gute
Optimierung hoffen.
Habt ihr noch weitere Vorschläge? Verzweifle sonst.
Ich hatte kürzlich ein ähnliches Problem mit dem AD9851 DDS-Chip Hab's folgendermassen gelösst: (Sorry; bedingt durch die Zeilenümbrüche in diesem Foruum sieht die Listing-Darstellung schrecklich aus!)
1 | void SPI_set_dds(u32 f_dds) // Frequency in Herz |
2 | //---------------------------------------------------
|
3 | // Set DDS-output frequency to [f_dds] Hz
|
4 | // f_dds = 0 will set the DDS into PowerDown Mode
|
5 | // f_dds = 1..45'000'000 Hz set DDS output frequency
|
6 | //---------------------------------------------------
|
7 | #define DDS_SYS_CLK 100000000UL // 100 MHz (as unsigned
|
8 | long costant) |
9 | #define DDS_MPL 0x0100000000000000ULL // 2^56 as unsigned long
|
10 | long
|
11 | // used for decimal point
|
12 | shift
|
13 | {
|
14 | union {u64 reg; u08 byte[8];} freq; |
15 | freq.reg = (f_dds * (DDS_MPL/DDS_SYS_CLK)); // 32 x 32 Bit |
16 | multiplication
|
17 | PORTB &= ~(1<<CS_DDS); // set DDS-Select low |
18 | for (u08 i=3; i<7; i++) |
19 | {
|
20 | SPDR=freq.byte[i]; // send freq.byte[3..6] |
21 | over SPI to DDS |
22 | while(!(SPSR & (1<<SPIF))); // wait until SPI |
23 | transfer is complete |
24 | }
|
25 | if (f_dds) // if frequency > 0 Hz |
26 | then
|
27 | {
|
28 | SPDR=0x00; // DDS-PhaseControlWord |
29 | (Phase=0; PowerDown=0; 6xREFCLK=0) |
30 | }
|
31 | else // else enable PowerDown |
32 | for the DDS |
33 | {
|
34 | SPDR=0x04; // DDS-PhaseControlWord |
35 | (Phase=0; PowerDown=1; 6xREFCLK=0) |
36 | }
|
37 | while(!(SPSR & (1<<SPIF))); // wait until SPI |
38 | transfer is complete |
39 | PORTB |= (1<<CS_DDS); // set DDS-Select high |
40 | }
|
Gruss Peter
float x,y; x=y*171.79869184; liegt bestimmt weit unter 1ms.
Float kannst Du hier total vergessen, das wird ungenau wie die Sau weil die Mantisse von einem Float nur (effektiv) 24 Bit hat. Wenn Du das mit Gleitkomma berechnen willst, musst Du in Double rechnen. Du kannst das auch komplett mit Integerzahlen berechnen. Da musst Du den Faktor entsprechend skalieren. Der Trick an der Geschichte ist, den Rundungsfehler zu kontrollieren.
171,79869184 Wenn du mit long-Werten rechnest kannst du diesen Wert z.B. mit 1024 multiplizieren (bei Programmerstellung). 175921,86044416 = ca. 175922 Während der Laufzeit führst du eine normale Multiplikation aus. Das Ergebnis shiftest du dann um 10 Stellen nach rechts. Ergebnis >> 10 Den Wert des Skalierungsfaktors musst du so wählen, dass für alle möglichen Eingangswerte bei der Multiplikation kein Überlauf passiert und so dass dieser eine Zweierpotenz ist (für die Schiebeoperation). Die obige Variante hätte dann etwa 2-3 Stellen Genauigkeit.
Hallo, wie genau brauchst Du's denn? Multiplizier y einfach mit 171 oder 172 (dann kommen allerdings Zahlen >2^32 raus, btw. bist Du sicher, daß es nicht y<25000000/2 heißen muß?). Ansonsten muß Du wohl mit 64Bit Integer rechnen, y mit (2^64)/25000000 multiplizieren und die oberen 32 Bit benutzen. Cheers Detlef
@Unbekannter double mit avr-gcc ist das selbe wie float, bringt also nix. @Simon Wie genau brauchst du es denn? Mit float bist du auf ca. 6-7 Stellen genau.
Hi Simon Hier ist mein Vorschlag: #define Faktor ((2^32)/25000000) und dann double X,Y=5; X = Y * (uint32_t) Faktor; Gruß Sascha PS: Genauer als uint32_t kannst du bei einem AVR sowieso nicht werden. klar es gibt noch uin64_t aber irgendwo sollte man auch mal beachten, das es sich hier sowieso um einen 8bit prozessor handelt.
Na, dann macht es doch mit Integer. ;-)
Hier mal die Lösung, ihr verschenkt ja alle Genauigkeit bis zum
umwerfen... ;-)
Bei der konkreten Situation mit 25 MHz Taktfrequenz für das DDS
empfiehlt es sich, den Eingangwert etwas zu skalieren. Denn schließlich
beträgt die Genauigkeit des DDS hier rund 0,006 Hz.
Daher bietet es sich an, mit 1/100 Hz Werten zu rechnen. Wenn man also
440 Hz erzeugen möchte, muss man der Formel unten die Integer-Zahl
'44000' in 'freq100' übergeben.
Also, erstmal müssen wir den Faktor skalieren und ausrechnen:
2^32
faktor_dez = -------------- = 1,7179869184
25000000 * 100
So, mit Komma-Werten im Dezimalsystem rechnet es sich schlecht, also
müssen wir diesen Faktor in einen Dualbruch wandeln, damit wir nacher
mit Integerzahlen rechnen können. Der skalierte Dualbruch muss in
diesem Fall 34 Bits lang sein, damit wir keine Genauigkeit verlieren.
Also:
faktor_dual = round( faktor_dez * 2^34 ) = 29514790518
So, diese Zahl hat nun 34 Bits. Wir wollen aber nur mit 32 Bits
multiplizieren, also ziehen wir die oberen beiden Bits ab und bekommen
unseren 32-Bit-Faktor:
faktor32 = faktor_dual - 2^33 - 2^32 = 16629888630
So, nun haben wir alles schön skaliert. Die oberen 2 Bits dürfen wir
aber bei der Berechnung nicht vergessen und wir müssen auch korrekt
runden.
Als Ergebnis bekommen wir:
1.) akku64 = freq100 * faktor32;/* 32 * 32 Bit = 64 Bit */
2.) akku32 = akku64 >> 32; /* untere 4 Bytes weg werfen */
3.) akku32 = akku32 + freq100; /* Bit 2^32 wieder herstellen */
4.) akku32 = akku32 >> 1; /* durch 2 dividieren */
5.) akku32 = akku32 + freq100; /* Bit 2^33 wieder herstellen */
6.) akku32 = akku32 + 1; /* korrektes runden sicherstellen */
7.) akku32 = akku32 >> 1; /* durch 2 dividieren */
So, das war's. ;-)
Die Formel funktioniert ohne Berücksichtigung des Carry-Flags natürlich
nur bis zu 12.5 MHz Ausgabefrequenz. Will man mehr, muss man das in
Assembler implementieren und das Carry-Flag immer mitschleifen.
Wer das in Assembler programmiert, kann die Multiplikation schön
vereinfachen, weil die unteren vier Byte ja einfach weg geworfen
werden.
Hier noch ein Vorschlag: Der Faktor 2^32/2500000 ist 171.79869184 Die Nachkommastellen des Faktors kann man immer als Brüche von a/2^n darstellen wobei darauf zu achten ist, daß der Wert im Zähler kleiner als 171 bleibt (sonst gäbe es bei der Multiplikation überläufe) X = Y *( 171 + 102/2^7 + 119/2^16 + ...) Zum Berechnen muß ausmultipliziert werden: X = Y* 171 + Y*102/2^7 + Y*119/2^16 + ... Die Anäherung in 3 Schritten ergibt hier einen Faktor von 171.798690796 Für eine höhere Genauigkeit müssen einfach noch ein oder zwei weitere Summanden hinzukommen.
Jungs, schaut euch doch meinen Beispiel-Code an! Da wird die ganze
Berechnung mit einer einzigen 32 x 32Bit Integer-Multiplikation
erschlagen, vorausgesetzt man shiftet den 1/f Faktor vorher um genügend
"Kommastellen" nach links!
#define DDS_SYS_CLK 100000000UL // DDS-Clk
#define DDS_MPL 0x0100000000000000ULL // 2^56 => Shift 1<<56
union {u64 reg; u08 byte[8];} freq; // für Byte-zugriff
freq.reg = (f_dds * (DDS_MPL/DDS_SYS_CLK)); // 32 x 32 Bit
(DDS_MPL/DDS_SYS_CLK) sind Konstanten und werden vom Compiler im voraus
berechnet. Das Ergebnis ist 1/DDS-Clk um 56 Binär-Digits nach links
geshiftet. Die verbleibende Run-Time Multiplikation dauert nicht
allzulange!
In meinem Fall hatte ich ein DDS mit 32-Bit Frequenzauflösung. Diese 32
bit befinden ich schlussendlich im freq.byte[3..6] ohne dass ich auch
nur ein Bit an Genauigkeit verschenkte.
Gruss Peter
Hmm, das hat mir jetzt keine Ruhe gelassen... Da oben habe ich mich etwas verrechnet... Und zwar gibt es Rundungsfehler und ausserdem habe ich im 32-Bit Akku einen 34 Bit Wert drin. Das geht also nicht. Und keiner hat's gemerkt... Weiter: Der Ansatz ist der gleich wie oben, also in "freq100" steht die Gewünschte Frequenz in 1/100 Hz. Also für 50 Hz muss da 5000 drinn stehen. So, ohne viel Erkläerungen, hier die Version die nun korrekt sein sollte (hoffentlich): faktor32 = 1872493371 Wie der zustande gekommen ist, ist eine andere Geschichte. Hängt mir der Genauigkeit und so zusammen. ;-) Weiter geht's: 1.) akku64 = freq100 * faktor32; /* 32 Bit * 32 Bit = 64 Bit */ 2.) akku32 = akku64 >> 32; /* Untere 32 Bits ignorieren */ 3.) akku32 = akku32 + 1; /* Rundungs-Korrektur */ 4.) akku32 = akku32 + freq100; /* Ein Bit nachreichen... */ 5.) akku32 = akku32 >> 1; /* runden */ 6.) akku32 = akku32 + freq100; /* Noch ein Bit nachreichen... */ So, 'akku32' ist der Wert der in den DDS-Baustein muss. Das müsste jetzt stimmen. Zumindest stimmt mein Beweis auf Papier... Achja: Nach der Multiplikation passt alles in ein 32-Bit-Register. Es gibt keinen Überlauf! ;-) Viel Spaß damit, Simon! berichte mal, ob es funktioniert!
@Peter: Klar, für grobe Auflösungen kann man das so machen. Ist ja auch keine Kunst mit 1 Hz Genauigkeit. Bei Deinem Code kannst Du eben z.B. nur 2 Hz einstellen. In den tiefen Frequenzen verschenkst Du extrem viel Auflösung Bei meinem Code kannst Du aber auch 2,37 Hz einstellen, und das mit dem kleinsten Rundungsfehler den der DDS-Baustein zulässt.
Uff, hier ist ja ne Menge zusammenbekommen. Das meiste verstehe ich, nur beim Unbekannten komme ich nicht so ganz hinterher. Möchte ich aber gerne, da es ja eine sehr schöne Routine bei ihm zu sein scheint. Ich werde mir das ganze nochmal zu gemüte führen und demnächst (wenn ich alles bei Reichelt besorgt habe. AD9833 habe ich schon hier ;)) ausprobieren. PS: Ich wollte das ganze (der Funktionsgenerator) mit einem Display lösen, bei dem ich mit einem Dreh-Encoder jede Stelle (0-9) verstellen kann. Die Frequenz wird in Hz dargestellt. D.h. vor der Berechnung ist noch ein atoi notwendig, dauert auch nochmal ordentlich Zeit. Zum glück verstellt man ja in der Regel die Frequenz nicht häufiger als 1mal in 5sek. D.h. der µC hat genug Zeit zum Rechnen.
@atoi
Das muss nicht soviel Zeit Kosten.
Du hast einen Drehencoder fuer jede Stelle, kennst also
den numerischen Wert fuer jede Stelle.
Zb 5 3 2 8
wie wird daraus die Zahl 5328 ?
Nun ganz einfach
das ist doch
5 * 1000 +
3 * 100 +
2 * 10 +
8
umgestellt nach dem Horner-Schema:
( ( ( 5 ) * 10 + 3 ) * 10 + 2 ) * 10 + 8
eine Multiplikation mit 10 ist leicht realisiert.
Das ist ganz einfach 2 mal mit 2 multiplizieren (also
2 mal nach links shiften), die Original-Zahl nochmal addieren
und nochmal mit 2 multiplizieren
Bsp 8
8 * 2 -> 16 ( bis jetzt: * 2 )
16 * 2 -> 32 ( bis jetzt: * 4 )
32 + 8 -> 40 ( bis jetzt: * 5 )
40 * 2 -> 80 ( bis jetzt: * 10 )
d.h. um eine 4 Digits in die koressp. 4-stellige Zahl zu
wandeln brauchst du
3 Multiplikationen mit 10
3 Additionen
fuer jede Multiplikation mit 10 kommen dazu
1 pop
1 push
3 links shift
1 addition
so dass Du in Summe brauchst
4 additionen
3 links shift
3 push
3 pop
3 function calls (mult10 wird sinnvollerweise als Unterprogram
ausgefuehrt)
3 ret
das duerfte wohl so ziemlich jeden atoi() schlagen. Vor allem
wenn Du wie in Deinem Fall zuerst mal aus den Drehencodern einen
String zusammenbaust :-)
Ob Dein Compiler die Substitution einer Multiplkation mit
10 durch die shift/add Sequenz selbsttätig macht oder nicht,
musst Du ausprobieren. Viele Compiler haben fuer kleine Konstanten
solche Optimierungen eingebaut.
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.