Hallo, Ich habe ein paar probleme bei der Programmierung eines Phasenakkumulators. Ich habe eine Tabelle mit 256 Werten (für verschiedene Funktionen), also 8 Bit. Ich verwende hierzu einen ATmega8 (also 3.6864MHz) und den 16 Bit Timer. (Zählt von 0 bis 65536) Um einen Wert aus der gewünschten Tabelle zu lesen muss man vorher das Frequenzkontrollwort bestimmen (FCW): FCW = Ausgangsfrequenz * 256 / Taktfrequenz. Wobei sich die Taktfrequenz aus der Frequenz des CPUs und dem Höchsten Wert des Timers zusammensetzt, oder? (zB 3.6864MHz / 10000). Und soweit ich das verstanden habe, wird das Frequenzkontrollwort immer zu der aktuellen Phasenposition hinzuaddiert und der entsprechende Wert dann aus der Tabelle gelesen. Der Programmablauf müsste also ungefähr so aussehen: 1) Tabellen initialisieren. 2) Frequenzkontrollwort für die gewünschte Ausgangsfrequenz brechnen. 3) In der Interruptfunktion des Timers (Timer1), das Frequenzkontrollwort zur aktuellen Position dazuaddieren, die bei 0 anfängt und beim Überschreiten von 256 wieder zurückgesetzt wird. Dieser Wert wird dann dazu verwendet um aus einer Tabelle zu lesen. Dazu müsste man aber doch das Frequenzcontrollwort und die Phasenposition als floats/doubles definieren und nacher zu einem Integer umwandeln weil das Tabellenarray ja nur Integer verwenden kann, oder? Wie soll man das genau anstellen, und treten dabei nicht fehler auf? Danke, MfG
Du hast es fast. Du berechnest zuerst Dein Frequenzkontrollwort. Dann addierst Du es auf den Phasenakkumulator(bei Dir 16bit?) und danach benutzt Du die oberen 8bit des Phasenakkumulators um in Deiner look-up Tabelle die entsprechende Amplitude nachzuschauen. Der Phasenakkumulator läuft halt irgendwann einfach über und Du bist wieder irgendwo am Anfang deine look-up Tabelle. Wozu Du jetzt floats brauchst ist mir nicht klar. Der Trick ist doch gerade das ganze über einen Großen Phasenakkumulator zu machen. Dann schieben und meinetwegen nach int casten. Grüße
also müsste ich theoretisch meinen Tabellen Array als 8bit int array definieren, und einen Phasenakkumulator also als 16 bit int? in C dann ungefähr: [c]// Variablen definieren uint16_t Phasenposition = 0; // Fängt bei 0 an uint32_t Frequenkontrollwort; // 32 Bit ok? oder als float/double? votalite wert = 0; // Wert für das Array . . . // Frequenzkontrollwort berechnen, Ausgangsfrequenz 20Hz, Takfrequenz 369Hz (3.6864MHz geteilt durch eine willkürlich festgelegte obere Grenze für den 16Bit timer, hier 10000). Frequenkontrollwort = 20 * 256 / (369Hz); . . . // Folgendes in der Timer-Interrupt-Routine: Phasenposition = Phasenpotion + Frequenzkontrollwort; Phasenposition = Phasenposition >> 8; //Um 8-Bit verschieben wert = Tabelle[Phasenposition]; Wert ist dann ne globale Variable mit der dann Weitergearbeitet werden kann. Ich komm mit den Größen der einzelnen Variablen hier noch nicht ganz zurrecht, passt das so in etwa?
Betrachte den Phasenakkumulator als Fixpoint. Setze nun vor deinem FCW mal eine 0.FCW und betrachte diesen ebenfalls als Fixpoint. Fixpoint = Festkommazahl. Gruß Hagen
Bei der DDS versucht man nur mit int Werten auszukommen. Du bestimmst die Frequenz mit der Dein Phasenakkumulator hochzählt über deine Taktfrequenz von 3,6864 MHz und den Teiler 10000 zu 368,64 Hz. Damit ergibt sich Deine untere Grenzfrequenz bei einem gewählten Phasenakkumulator von 16 bit zu 368,64 Hz/65536 = 5,625 mHz (Der Phasenakkumulator wird bei jedem Interrupt 1 hochgezählt). Jetzt möchtest Du aber 20 Hz Ausgangsfrequenz. Du musst also deinen Phasenakkumulator 3555,6 mal so schnell hochzählen -> das ist dein Frequenzkontrollwort(der Wert den Du pro Interrupt zum Phasenakkumulator dazuaddierst; (20 Hz * 16 bit) / 368,64 Hz. Damit reduziert sich aber auch deine Auflösung der look-up Tabelle auf ca 4 bit, Du musst zuviele Werte überspringen um auf deine 20 Hz zu kommen. Du müsstest also deinen Teiler von 10000 anpassen oder die Taktfrequenz deutlich erhöhen um bei 20 Hz noch eine 8 bit look-up Tabelle nutzen zu können
Hallo, du must as System on DDS richtig kapieren. Deine Tabelle hat 256 Einträge (hier Sinuswerte für eine Periode). Dein Timer macht ein festes Zeitfenster (CTC mit reload). Nehmen wir an, das Zeitfenster ist 39,0625 µS (Nachladewert 144 TZ) und du gibst jedesmal denn nächsten Tabellenwert aus bedeutet das für eine Periode (Tabelle 0-255) 10 ms (256*39,0625 µs) => eine Frequenz von 100 Hz. Das Bedeutet du hast eine Grundfrequenz von 100 Hz. Du willst jetzt aber 300 Hz ausgeben. Dann mußt du bei jedem Interrupt nicht eine sondern 3 Positionen weiterspringen. Dein Additionswert ist jetzt 3. -> Tabellenposition += Additionswert im Interrupt mit Additionswert = Ausgabefrequenz / Gundfquenz Was ist aber bei 25 HZ? Wir brauchen Nachkommastellen. Additionswert = 25 / 100 = 0,25 Ein UINT16 besteht aus 16 Bit. Wir denken uns jetzt ein Komma zwischen HighByte und LowByte. Für die 3 von oben steht dann binär 0000 0011 (,) 0000 0000 und für unsere 0,25 binär 0000 0000 (,) 0100 0000 Unser Tabellenzähler ist jetzt natürlich auch UINT16, das Highbyte ist dabei unsere Tabellenposition für den Sinuswert. Wenn man das sauber weiterrechnet kommt man auf die einfache Endformel (bei 256 Werten): Additionswert = Ausgabefrequenz Aulösung_max Nachladewert_Timer/Fosc hier Auflösung_max = 2^16 (16Bit Zählerbreite) Je höher die Auflösung umso feiner kann abgestuft werden, ein schneller Fosc sorgt für saubere Signale bei größerer Ausgabefrequenz. Ich hoffe, so ist es verständlich erklärt. Gruß Hansl
Ok 10000 wären da eher ungünstig, aber ich hab bei dem timer ja zwischen 1 und 16bit ja die Wahl. Würde ich zB 10 wählen, würden die Timer-interrupts viel schneller kommen. Also wäre mein Frequenzkontrollwort: FKW = (20hz * 16bit) / (3.6864MHz / 10) = 3,556 Wäre es nicht eher ideal einen Wert unter 1 zu haben für das Frequenzkontrollwort? Wenn dies zB 0.11 betragen würde, müsste der Timer 10 mal aufgerufen werden bis der Wert des Phasenakkumulators um 1 größer ist als vorher, und somit die "Adresse" für den Array wert auch um 1 weiter geht, oder? (Phasenposition = Phasenpotion + Frequenzkontrollwort; Phasenposition entspricht doch hier dem Wert des Phasenakkumulators oder?) Was ich aber zudem nicht so ganz verstanden habe, ist warum man für den Phasenakkumulator 16 bit (oder mehr) verwendet.
@Hans L: sauber erklärt was Fixpoint, eg. Festkomma Zahlendarstellung ist und was eine Skalierung bedeutet ;)
>Wäre es nicht eher ideal einen Wert unter 1 zu haben für das >Frequenzkontrollwort? Korrekt, bedeutet aber das der eigentliche Takt umso höher sein muß.
Danke Hans, ich glaub jetzt hab ichs richtig verstanden. Wenn ich also eine gerade Zahl dann ausgeben möchte, muss ich (hier bei 16 bit) 8 bit schieben, sodass nur noch das Highbyte vorhanden ist?
Da deine Tabelle 256 Einträge hat benötigst du 8 Bit für die
Addresierung. Wenn du einen 16 Bit Tabellenzähler immer um einen
16 Bit Additionswert erhöhst kommt irgendwann wenn ein Wert der
>= 65536 ist. Diesen Überlauf beachten wir nicht, das ist wie ein
Rücksetzen auf den Tabellenstart.
Im Highbyte haben wir daher z.b. bei 3 die Folge
2,5,8,11,...,251,254,1,4,.. und bei 0,25 2,2,2,2,3,3,3,3,4,4,4,4...
Das sind unsere Positionen in der Tabelle.
Du kannst den Tabellenzähler bei 16 Bit daher einfach durch 256
Teilen (ist wie >>8) um die Tabellenposition zu erhalten (C optimiert
das meist passend). In Assebler ist die ganze Geschichte viel leichter
und schneller zu lösen (z.B. direkte Verwendung von Z-Low-Register
als HighByte!).
Gruß HansL
danke, habs jetzt glaub ich fast, aber irgendwie spinnt mein Programm immernoch rum. Müsste ja im Grundegenommen so aussehen: [c] // Tabelle initialisieren . . // Definitionen volatile uint16_t Additionswert; volatile uint16_t Tabellenposition; volatile int c = 0; . . . // Interrupt-Routine festlegen SIGNAL(TIMER1_COMPA_vect) { uint16_t i; Tabellenposition += Additionswert; i = Tabellenposition >> 8; // 8 Bits schieben c = Dreieck_Tab[i]; } . . . // In der main() Funktion // Ports, timer etc. initialisieren Additionswert = (uint16_t) 100 256 144 / 3686400; // Hauptschleife do { Tabelle( c ); // Tabellenfunktion: Bekommt den Wert c // --> PORTB = c; } while(true); Manchmal bekomme ich auch den Fehler "warning: overflow in implicit constant conversion" in der Zeile wo der additionswert berechnet werden muss. Übrigens, müsste in der Formel "Additionswert = Ausgabefrequenz Aulösung_max Nachladewert_Timer/Fosc" der Wert für Auflösung_Max hier nicht 2^8 betragen anstatt 2^16? Sonst stimmen diese Formel, und die andere ("Additionswert = Ausgangsfrequenz / Grundfrequenz") nicht überein.
Die Formel ist wie angegeben korrekt. In deinem Beispiel willst du 100 Hz. Mit Timerzyklus 144 TZ hast du eine Grundfrequenz von 100 Hz. Mit meiner Formel erhälst 256 -> 0000 0001 (,) 0000 0000 Wenn du an das gedachte Komma denkst genau richtig. Die Ausgabe auf den Port direkt in die Interruprroutine setzen (dort dann z.B. ein R2R-Wandler). So in der Hauptschleife wird der Port die ganze Zeit neu gesetzt. Beim Timer beachten, daß dieser ab 0 zählt. 144 TZ -> 0 bis 143 Deine Interruptroutine muß in weniger als diesen 144 TZ abgearbeitet sein, der Rest ist die Rechenzeit für das Hauptprogramm. Damit die Ausgabe über Interrupt sauber läuft sollten während der Ausgabe keine anderen Interrupts freigegeben sein. Gruß HansL
danke, das Signal sieht ganz gut aus, leider ein kleiner letzter Fehler. Irgendwie bekomme ich anstatt 100Hz (hab den Additionswert jetzt einfach mal auf 1 gesetzt) 0.1 Hz. Gebe ich für den Additionswert 1000 an, ist die Signalschärfe immer noch die selbe und ich bekomme 100Hz. Ich glaube da habe ich bei der Definition von irgendeiner Variabel etwas vermasselt. Ich finde den fehler aber nicht, weiß irgend jemand was da schief gelaufen sein kann? In meinem vorigen Post kann man sehen wie ich die einzelnen variablen definiert habe.
ok hab ein paar Änderungen vorgenommen und deine Formel verwendet, und bis ca 100 Hz funktioniert das auch. Danach bekomme ich Warnungen vonwegen Overflow oder so, ich denke mal weil der 16-Bit Kasten auf dem ich den Additionswert berechne, dann zu klein ist. Ich habe das ganze jetzt erstmal für 6-Bit ausprobiert, schaut ungefähr so aus:
1 | volatile uint16_t Additionswert; |
2 | volatile int Ausgabefrequenz = 100; |
3 | volatile uint16_t Tabellenposition; |
4 | |
5 | SIGNAL(TIMER1_COMPA_vect) |
6 | {
|
7 | uint16_t i; |
8 | Tabellenposition += Additionswert; |
9 | i = Tabellenposition >> 10; |
10 | PORTB = Tabelle[i]; |
11 | }
|
12 | |
13 | void initTimer1() |
14 | {
|
15 | TCCR1A = 3; |
16 | TCCR1B = 25; |
17 | OCR1A = 576; |
18 | TIMSK = 16; |
19 | TIFR = 16; |
20 | }
|
21 | |
22 | void initPorts() |
23 | {
|
24 | DDRB = 0xFF; |
25 | PORTB = 0; |
26 | }
|
27 | |
28 | main () |
29 | {
|
30 | initPorts(); |
31 | initTimer1(); |
32 | sei(); |
33 | |
34 | Additionswert = (uint16_t)100 * 65536 * 576 / 3686400; |
35 | |
36 | do { |
37 | // Noch nichts
|
38 | } while (true); |
39 | }
|
40 | //----------------------------------------------------------------------
|
gebe ich für die Ausgangsfrequenz (hier in der Formel 100), mehr als 100 ein, kommt es entweder zu keiner Änderung, oder es kommt irgendwann garnichts mehr. Würde man das Problem lösen können, wenn man die Bit-Breite von dem Additionswert vergrößert?
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.