Forum: Mikrocontroller und Digitale Elektronik Ausführen von DDS


von maccy (Gast)


Lesenswert?

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

von Gast (Gast)


Lesenswert?

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

von maccy (Gast)


Lesenswert?

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?

von Hagen R. (hagen)


Lesenswert?

Betrachte den Phasenakkumulator als Fixpoint. Setze nun vor deinem FCW 
mal eine 0.FCW und betrachte diesen ebenfalls als Fixpoint. Fixpoint = 
Festkommazahl.

Gruß Hagen

von Gast (Gast)


Lesenswert?

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

von Hans L. (hansl)


Lesenswert?

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

von maccy (Gast)


Lesenswert?

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.

von Hagen R. (hagen)


Lesenswert?

@Hans L:

sauber erklärt was Fixpoint, eg. Festkomma Zahlendarstellung ist und was 
eine Skalierung bedeutet ;)

von Hagen R. (hagen)


Lesenswert?

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

von maccy (Gast)


Lesenswert?

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?

von Hans L. (hansl)


Lesenswert?

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

von maccy (Gast)


Lesenswert?

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.

von Hans L. (hansl)


Lesenswert?

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

von maccy (Gast)


Lesenswert?

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.

von maccy (Gast)


Lesenswert?

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