Forum: Compiler & IDEs Formel Mikrocontrollergerecht machen


von Simon K. (simon) Benutzerseite


Lesenswert?

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.

von Peter (Gast)


Lesenswert?

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

von Peter (Gast)


Angehängte Dateien:

Lesenswert?

Nochmals als Attachment...

Gruss Peter

von Fritz G. (fritzg)


Lesenswert?

float x,y;

  x=y*171.79869184;

liegt bestimmt weit unter 1ms.

von Unbekannter (Gast)


Lesenswert?

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.

von Alex (Gast)


Lesenswert?

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.

von Alex (Gast)


Lesenswert?

... Kommastellen Genauigkeit :)

von Detlef A (Gast)


Lesenswert?

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

von Fritz G. (fritzg)


Lesenswert?

@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.

von Sascha (Gast)


Lesenswert?

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.

von Unbekannter (Gast)


Lesenswert?

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.

von Werner Hoch (Gast)


Lesenswert?

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.

von Peter (Gast)


Lesenswert?

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

von Unbekannter (Gast)


Lesenswert?

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!

von Unbekannter (Gast)


Lesenswert?

@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.

von Simon K. (simon) Benutzerseite


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

@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
Noch kein Account? Hier anmelden.