DDS Artefakte bei bestimmten Frequenzen

Gast #4898008
Lesenswert?

Hallo,

ich erzeuge mit einem Xmega auf einem DAC einen Sinus, gebe den auf 
einen Kopfhörerverstärker und höre mir das Signal an. Auf dem 
Oszilloskop sehe ich keine Auffälligkeiten ich höre

jedoch (zu meinem sehr sauberen Sinus) immer eine zusätzliche Frequenz, 
abhängig von der gewünschten Sinus Ausgangsfrequenz. Bei Frequenzen, 
dessen Schrittweite ein ganzzahliger

Teiler meiner Samplerate ist, höre ich keine Störungen. Das ist für mich 
ein starkes Indiz, dass es ein Software Problem ist, ich vermutete erst 
ein analoges Problem.

Ich gebe im folgenden erstmal nur relevante Programmzeilen und nicht den 
Code als Ganzes.

Folgende Formel berechnet die Schrittweite des DDS. Da ich die 
Kommastelle möglichst groß mitnehmen will, multipliziere ich das ganze 
mit 4096. Die Frequenz ist in hunderdstel

angegeben.
1
#define SAMPLE_SIZE 256           //Wieviele Sützstellen hat das Audiosample?
2
#define SPS 128000               //Frames per Second Audioausgabe
3

4
struct s_Ton{
5
  BYTE cPos;           //Aktuelle Position im Sample-Array
6
  BYTE cVol;           //Lautstärke des auszugebenden Tones 0-0xFF
7
  WORD wCounter;           //Schrittweite addieren, bei >= 4096 leigt ein Schrittwechsel von cPos vor
8
  WORD wStepSize;           //Schrittweite pro fps
9
  unsigned long wFreqOld;
10
  unsigned long wFreq;   //Frequenz des Tones
11
};
12

13
struct s_Ton g_asToene[2];   //Toneinstellungen, aus denen die Audioausgabe berechnet wird
14

15
for(i = 0; i < 2; i++)
16
  g_asToene[i].wStepSize = ((unsigned long long)4096 * SAMPLE_SIZE * g_asToene[i].wFreq) / (SPS * 100);

Per 2x DMA lasse ich zwei Buffer an den DAC ausgeben. Wenn ein DMA 
fertig ist, wird dessen Ausgabebuffer neu berechnen und der zweite DMA 
wird aktiviert. Der DMA wird von einem

Timer-Überlauf (Event, kein Interrupt) getriggert. Im folgenden der Code 
für einen DMA, der zweite ist identisch, hat nur als Quelle einen 
anderen Buffer.
1
#define F_CPU 32000000UL
2
#define DMA_BUFFER_SIZE 512
3

4
TCC1_CTRLA = 1;              //Timer für Audioausgabe
5
TCC1_PERBUF = F_CPU/SPS;          //Timer auf Framerate bringen z.B. 1/128000 Sekunde
6
EVSYS.CH0MUX = EVSYS_CHMUX_TCC1_OVF_gc;  //Event für DMA, jede 128000. Sekunde z.B.
7

8
DMA.CH0.CTRLA = DMA_CH_SINGLE_bm | DMA_CH_BURSTLEN_2BYTE_gc;  //DMA Block macht bei jedem Trigger 2 Byte (signle)
9
DMA.CH0.CTRLB = 0x02;            //Interrupt Priorität 2      
10
DMA.CH0.ADDRCTRL = DMA_CH_SRCRELOAD_BLOCK_gc      //Nach jedem Block die Quelladresse reseten
11
| DMA_CH_SRCDIR_INC_gc            //Quelladresse immer +
12
| DMA_CH_DESTRELOAD_BURST_gc          //Zieladresse nach jedem Burst (2Byte) neu laden
13
| DMA_CH_DESTDIR_INC_gc;          //Zieladresse immer +
14
DMA.CH0.TRIGSRC = DMA_CH_TRIGSRC_EVSYS_CH0_gc;      //Triggerevtn ist Timer TCC1 overflow
15
DMA.CH0.TRFCNT = DMA_BUFFER_SIZE*2;        //buffer_SIZE*2 Bytes
16
DMA.CH0.REPCNT = 0;            //0 WIederholungen - nach 1 Block ist Transaction fertig
17
DMA.CH0.SRCADDR0 = (((uint16_t) &g_DmaBuffer1)>>0*8) & 0xFF;
18
DMA.CH0.SRCADDR1 = (((uint16_t) &g_DmaBuffer1)>>1*8) & 0xFF;
19
DMA.CH0.SRCADDR2 = 0;
20
DMA.CH0.DESTADDR0 = (((uint16_t)(&DACB.CH0DATAL))>>0)&0xFF;
21
DMA.CH0.DESTADDR1 = (((uint16_t)(&DACB.CH0DATAL))>>8)&0xFF;
22
DMA.CH0.DESTADDR2 = 0;

Im DMA Interrupt wird der jeweils andere DMA aktiviert und der eigene 
Buffer neu befüllt.
1
ISR(DMA_CH0_vect){                          //Der DMA Buffer muss neu berechnet werden
2
  DMA.CH1.CTRLA |= DMA_CH_ENABLE_bm;  //DMA Buffer 2 aktivieren
3
  DMA.CH0.CTRLB |= (1<<4);    //Interrupt zurücksetzen
4
  
5
  asm volatile(
6
  ...Berechnung von Buffer. Grob gesagt:
7
  Schrittweite auf Counter addieren
8
  Positioncounter += Counter / 4096;
9
  counter %= 4096;
10
  position Sinustabelle wert in Ausgangsbuffer schreiben
11
  nächster Wert
12
  );

Der Assemblerim Detail:
1
  asm volatile(
2
  /*variablen laden*/
3
  "lds r19, %[AC0]      \n\t"          //Counter 0 laden
4
  "lds r20, %[AC0]+1      \n\t"          //..
5
  "lds r17, %[AC1]      \n\t"          //Counter 1 laden
6
  "lds r18, %[AC1]+1      \n\t"          //..
7
  "lds r15, %[AS0]      \n\t"          //Stepsize 0 laden
8
  "lds r16, %[AS0]+1      \n\t"          //..
9
  "lds r21, %[AS1]      \n\t"          //Stepsize 1 laden
10
  "lds r22, %[AS1]+1      \n\t"          //..
11
  "lds r14, %[AP0]      \n\t"          //Position 0 laden
12
  "lds r13, %[AP1]      \n\t"          //Position 1 laden
13
  "lds r11, %[M0]        \n\t"
14
  "lds r12, %[M1]        \n\t"
15
  "ldi YH, hi8(%[DM])      \n\t"
16
  "ldi YL, lo8(%[DM])      \n\t"
17
  "ldi r24, lo8(%[SIZE])    \n\t"
18
  "ldi r25, hi8(%[SIZE])    \n\t"
19
  
20
  /*Vorberechnung ausführen*/
21
  "REPEAT2:          \n\t"
22
  "add r19, r15        \n\t"          //Stepsize addieren
23
  "adc r20, r16        \n\t"          //... hier mit carry
24
  "mov __tmp_reg__, r20    \n\t"          //Counter high temporär kopieren
25
  "andi r20, 0x0F        \n\t"          //Module 1024 auf Counter
26
  "lsr __tmp_reg__      \n\t"          //Kopie /2 (eigentlich durch 1024, aber die unteren 8 Byte sind ja schon gelöscht)
27
  "lsr __tmp_reg__      \n\t"          //kopie /2
28
  "lsr __tmp_reg__      \n\t"          //kopie /2
29
  "lsr __tmp_reg__      \n\t"          //kopie /2
30
  "add r14, __tmp_reg__    \n\t"          //Addition zur Position
31
  
32
  "add r17, r21        \n\t"          //Stepsize addieren
33
  "adc r18, r22        \n\t"          //... hier mit carry
34
  "mov __tmp_reg__, r18    \n\t"          //Counter high temporär kopieren
35
  "andi r18, 0x0F        \n\t"          //Module 1024 auf Counter
36
  "lsr __tmp_reg__      \n\t"          //Kopie /2 (eigentlich durch 1024, aber die unteren 8 Byte sind ja schon gelöscht)
37
  "lsr __tmp_reg__      \n\t"          //kopie /2
38
  "lsr __tmp_reg__      \n\t"          //kopie /2
39
  "lsr __tmp_reg__      \n\t"          //kopie /2
40
  "add r13, __tmp_reg__    \n\t"          //Addition zur Position
41
  
42
  /*Ersten Wert laden*/
43
  "ldi XH, hi8(%[SAMPLE])    \n\t"
44
  "ldi XL, lo8(%[SAMPLE])    \n\t"
45
  "ldi r23, 0          \n\t"
46
  "add XL, r14        \n\t"
47
  "adc XH, r23        \n\t"
48
  "add XL, r14        \n\t"
49
  "adc XH, r23        \n\t"
50
  "ld r2, X+          \n\t"
51
  "ld r3, X+          \n\t"
52
  
53
  /*multiplikation erster wert*/
54
  "mul r3, r11        \n\t"
55
  "mov r4, r0          \n\t"
56
  "mov r5, r1          \n\t"
57
  "mul r2, r11        \n\t"
58
  "add r4, r1          \n\t"
59
  "adc r5, r23        \n\t"
60
  
61
  /*zweiten Wert laden*/
62
  "ldi XH, hi8(%[SAMPLE])    \n\t"
63
  "ldi XL, lo8(%[SAMPLE])    \n\t"
64
  "add XL, r13        \n\t"
65
  "adc XH, r23        \n\t"
66
  "add XL, r13        \n\t"
67
  "adc XH, r23        \n\t"
68
  "ld r2, X+          \n\t"
69
  "ld r3, X+          \n\t"
70
  
71
  /*multiplikation zweiter wert*/
72
  "mul r3, r12        \n\t"
73
  "add r4, r0          \n\t"
74
  "adc r5, r1          \n\t"
75
  "mul r2, r12        \n\t"
76
  "add r4, r1          \n\t"
77
  "adc r5, r23        \n\t"
78
  
79
  "st Y+,r4          \n\t"
80
  "st Y+,r5          \n\t"
81
  
82
  "sbiw r24, 1        \n\t"          //Schelife - 1
83
  "brne REPEAT2        \n\t"
84
  
85
  /*variablen speichern*/
86
  "sts %[AC0],r19        \n\t"          //Counter 0 speichern
87
  "sts %[AC0]+1,r20      \n\t"          //..
88
  "sts %[AC1],r17        \n\t"          //Counter 1 speichern
89
  "sts %[AC1]+1,r18      \n\t"          //..
90
  "sts %[AP0],r14        \n\t"          //Position 0 speichern
91
  "sts %[AP1],r13        \n\t"          //Position 1 speichern
92
  
93
  :
94
  : [AC0] "i" (&g_asToene[0].wCounter)
95
  , [AC1] "i" (&g_asToene[1].wCounter)
96
  , [AS0] "i" (&g_asToene[0].wStepSize)
97
  , [AS1] "i" (&g_asToene[1].wStepSize)
98
  , [AP0] "i" (&g_asToene[0].cPos)
99
  , [AP1] "i" (&g_asToene[1].cPos)
100
  , [M0] "i" (&g_asToene[0].cVol)
101
  , [M1] "i" (&g_asToene[1].cVol)
102
  , [DM] "i" (&g_DmaBuffer2[0])
103
  , [SAMPLE] "i" (&g_awSample[0])
104
  , [SIZE] "i" (DMA_BUFFER_SIZE)
105
  : "memory" ,"r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20","r21", "r22","r23","r24","r26","r27","r28","r29", "r2", "r3", "r4", "r5","r11", "r12"
106
  );

Ich hoffe es ist verständlich und genug auskommentiert - leider 
verschiebt das Forum meine Komentare so unschön. Was mache ich falsch, 
bzw. kann ich diese überagerten Frequenzen überhaupt eliminieren? Ich 
hab vor dem Kopfhörer einen Bandpass von 20Hz-20kHz.

Danke euch allen!
Gast #4898015
Lesenswert?

Nils H. schrieb:
> Was mache ich falsch,

Zunächst einmal das - du liest nicht vor dem Posten:

>>Wichtige Regeln - erst lesen, dann posten!
>>
>>   Groß- und Kleinschreibung verwenden
>>   Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang
-----^^^^^^^^^^^^^^^^^^^^

Ich würde noch dazufügen: vermeide unbedingt Tabs als Leerzeichen-
Ersatz. Jeder User hat andere Tab-Einstellungen und meistens
schauen Sourcen, die Tabs enthalten, scheisse aus.
Gast #4898020
Lesenswert?

Nils H. schrieb:
> ich erzeuge mit einem Xmega auf einem DAC einen Sinus, gebe den auf
> einen Kopfhörerverstärker und höre mir das Signal an.

Und wo ist der Tiefpass der dir die Sample-Frequenz unterdrückt?
Der Tiefpass sollte deutlich unter der halben Sampling-Frequenz
liegen sonst bekommst du aus deinem Verstärker Mischprodukte die
du gar nicht hören willst.
Gast #4898033
Lesenswert?

Mitlesa schrieb:
> Nils H. schrieb:
>> ich erzeuge mit einem Xmega auf einem DAC einen Sinus, gebe den auf
>> einen Kopfhörerverstärker und höre mir das Signal an.
>
> Und wo ist der Tiefpass der dir die Sample-Frequenz unterdrückt?
> Der Tiefpass sollte deutlich unter der halben Sampling-Frequenz
> liegen sonst bekommst du aus deinem Verstärker Mischprodukte die
> du gar nicht hören willst.

Bei 128kHz Samplingrate wären das 64kHz. Mein Bandpass hat eigentlich 
einen Tiefpass der nach 20kHz wirkt, wobei das ja sowieso ausserhalb des 
Hörbaren liegt. Oder ist meine Überlegung falsch?

Bitte keine Leerzeochen/Tabs Diskussion :).
#4898072
Lesenswert?

Ohne mir das Ganze im Detail angeguckt zu haben, rate ich mal:
- an mangelndem Tiefpass liegt es nicht,
- Deine Sinus-Tabelle ist mit 256 Samples zu grob,
- dadurch, daß Du die Sample-Position auf diese Tabelle berechnest, und 
das ja nur auf ein Sample genau ist, bekommst Du einen Phasenjitter von 
1/256tel der Periode. Das dürfte schon deutlich zu hören sein.
- Vermutlich würde eine Interpolation zwischen den Samples auch bei nur 
256 Stützstellen reichen, um die Mischprodukte auf ein unhörbares Maß zu 
reduzieren.
- Dürfte mit dem XMega schwierig hinzubekommen sein - ist halt auch kein 
DSP.
Gast #4898082
Lesenswert?

W.S. schrieb:
> Nils H. schrieb:
>> Wie kann es dann sein, dass ich am Ausgangspin vom Xmega genau das
>> gleiche höre, wie am Verstärkerausgang?
>
> Ganz einfach: Dein Tiefpass taugt nichts.
>
> W.S.

Aber Mitlesa schrieb, dass diese Störungen nach (und durch) den 
Verstärker entstehen sollen.

Vor und nach dem Verstärker höre ich bei 125Hz keine Störungen. Bei 123 
Hz z.B. höre ich vor und nach dem Verstärker die gleichen Störungen :/.

Thomas E. schrieb:
> Ohne mir das Ganze im Detail angeguckt zu haben, rate ich mal:
> - an mangelndem Tiefpass liegt es nicht,
> - Deine Sinus-Tabelle ist mit 256 Samples zu grob,
> - dadurch, daß Du die Sample-Position auf diese Tabelle berechnest, und
> das ja nur auf ein Sample genau ist, bekommst Du einen Phasenjitter von
> 1/256tel der Periode. Das dürfte schon deutlich zu hören sein.
> - Vermutlich würde eine Interpolation zwischen den Samples auch bei nur
> 256 Stützstellen reichen, um die Mischprodukte auf ein unhörbares Maß zu
> reduzieren.
> - Dürfte mit dem XMega schwierig hinzubekommen sein - ist halt auch kein
> DSP.

Dann werde ich mal testweise die Samplerate erhöhen, das schafft der 
Prozessor noch.
Gast #4898090
Lesenswert?

Thomas E. schrieb:
> bekommst Du einen Phasenjitter von
> 1/256tel der Periode.

Ja. Und nein. Das ist kein Phasenjitter sondern eine
konstante Frequenz die sich da ausbildet.

Nils H. schrieb:
> Bei 128kHz Samplingrate

.... und 256 Stützwerten ergäbe sich also eine Störlinie
von 500Hz.

Dann lieber nur 2, 4 oder 8 Stützwerte bei 128Khz Samplingrate,
das ist leichter zu filtern.

Trotzdem muss man direkt nach dem DAC filtern/tiefpassen.
#4898111
Lesenswert?

Mitlesa schrieb:
> Ja. Und nein. Das ist kein Phasenjitter sondern eine
> konstante Frequenz die sich da ausbildet.

Phasen"jitter" ist vielleicht das falsche Wort - es ergibt sich eine 
Sägezahnförmige Modulation der Phase. Dieser Sägezahn hat eine konstante 
Frequenz und ist dementsprechend als "Störton" auch höhrbar und liegt 
meistens im Bereich der Nutzfrequenz (deshalb kann man es auch nicht 
wegfiltern). Mit der Tiefpassfilterung des Signals hinter dem 
D/A-Wandler hat das alles jedenfalls nichts zu tun.
Gast #4898118
Lesenswert?

Ich benutze ich 512 Byte Samplegröße. Die Störfrequenzen sind nun noch 
ein gutes Stück lauter geworden.

Diese Frequenzen sind übrigens nie identisch, bzw. wiederholen ich 
periodisch, die Frequenz wandert aber.
#4898335
Lesenswert?

Nils H. schrieb:
> Wenn ich das Ausgangssignal in der Frequenz langsam erhöhe, dann
> verändert sich auch die Frequenz des Störsignals periodisch(!) (wabert
> also Sinusförmig um eine Grundfrequenz).

Ok, das ist dann doch, was ich erwartet hätte. Natürlich hängt die 
Frequenz von der eingestellten Frequenz des Nutzsignals ab. Bei 
konstanter Frequenz des Nutzsignals ist also auch das Störsignal 
konstant - hatte schon befürchtet, Du hättest etwas anderes beobachtet!

Vorhin beim Hundespaziergang hatte ich mal überlegt, wie man es 
vielleicht ohne viel Rechenpower (für eine Interpolation) lösen könnte: 
mit einem digitalen Filter beim Berechnen der Ausgabedaten. Ein simpler 
Tiefpass als IIR-Filter bräuchte ggf. wenig Recourcen und könnte die 
Phasensprünge vielleicht schon ausreichend glätten.
Gast #4898638
Lesenswert?

>Vorhin beim Hundespaziergang hatte ich mal überlegt, wie man es
>vielleicht ohne viel Rechenpower (für eine Interpolation) lösen könnte:
>mit einem digitalen Filter beim Berechnen der Ausgabedaten.

Ich vermute, dass ein lineare Interpolation zwischen zwei Abtastwerden 
reichen würde. Man kann das als gewichtete Summe ausführen.
#4898819
Lesenswert?

ChrisMicro schrieb:
> Ich vermute, dass ein lineare Interpolation zwischen zwei Abtastwerden
> reichen würde. Man kann das als gewichtete Summe ausführen.

Ja, denke ich auch! Dachte erst, man man könnte sich die echte 
Interpolation durch das Filter mit weniger Rechenschritten pro Sample 
sparen, aber bei näherem Hinsehen sieht es so aus, daß ein primitives 
Filter allein nicht wirklich weiterhilft. Mit zwei Filtern (eins auf die 
Phase, eins auf die Amplitude) würde es vielleicht klappen, aber da ist 
die Interpolation direkt wahrscheinlich schon weniger Rechenaufwand. 
Vermutlich kann man mit der Interpolation dann auch die Samplerate am 
Ausgang auf ein "normales" Maß (<50 kHz) reduzieren, was den erhöhten 
Aufwand an Rechenschritten pro Sample ausgleichen könnte.
Gast #4899014
Lesenswert?

Nils H. schrieb:
> Ich habe diese Interferenzen ünbrigens auch bei einem Dreiecksignal.

Ich denke du musst dein Signal einmal spektral betrachten sonst
siehst du nicht woher die Nebenlinien kommen - das Oszilloskop
zeigt ja nur eine Momentaufnahme in der der (vermutlich weit
höherfrequente) Jitter nicht zu sehen ist.

Aufgrund des sauberen Signals kommt aber doch der Verdacht auf
könnte sich um eine Intermodulation ausserhalb des Synthese-
Vorgangs handeln.
Beitrag #4932353 wurde von einem Moderator gelöscht.
Beitrag #4932570 wurde von einem Moderator gelöscht.
Beitrag #4932946 wurde von einem Moderator gelöscht.
Beitrag #4932982 wurde von einem Moderator gelöscht.
Dieser Beitrag ist gesperrt und kann nicht beantwortet werden.