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.