Hi!
Ich will mit dem Atmega8 ein paar Töne erzeugen, soll ganz einfach
gehalten werden.
(Ja, ich hab die Suchfunktion benutzt, auch Google, und den ganzen Tag
damit zugebracht, mich einzulesen, ich habe auch schon verschiedene
Artikel über Wavetable-Synthesizer und DDS gelesen, aber eigentlich will
ich nur ein paar versch. Töne erzeugen und gebe mich mit schlechtem
gameboy-gepiepse zufrieden.)
Ich will die Töne per pwm erzeugen und gebe sie direkt auf nen kleinen
laptop-lautsprecher. Mein bisheriger Code befindet sich im Ahang.
Der Atmega läuft mit 8Mhz, 8bit timer, kein Prescaler.
fPWM = 8MHz / 256, also ist die pwm-frequenz grob 32khz.
( ich habs auch mal mit prescaler 2 ausprobiert, aber da kam fast nur
brummen raus).
Ich habe mir mal eine Sinuskurve vorgenommen, diese in 256 (wegen
8bit-timer) diskrete Werte zerlegt und ein "lookup"-array erstellt.
(Wertebereich von 0 bis 256).
Per Interrupt-Funktion lese ich nun nacheinander die einzelnen Werte
dieses arrays in OCR1A ein und erzeuge so die verschieden langen
high-phasen der PWM.
Beim resultierenden Ton hört man nun deutlich Pausen, es hört sich fast
an wie eine Sirene.
Jetzt zu meinen Fragen:
1. Woher kommen diese Pausen, es kann doch nicht sein, dass diese 256
Werte so langsam ausgelesen werden, dass man "die Sinus-Kurve hören"
kann?
2. Die Frequenz der PWM ändert sich ja nie, nur die Frequenz der
Sinuskurve, die aus den gemittelten PWM-Phasen entsteht.
Welche Frequenz hat jetzt also mein Ton?
3. Wie kann ich ohne viel Aufwand andere Töne erzeugen?
Kann ich dazu einfach die Sinuskurve "stauchen" oder "ziehen"?
Und wie kann ich dann Töne bestimmter Frequenz "erstellen"?
Und müsste ich dann für jeden Ton, den ich verwenden will, eine eigene
Sinuskurve erstellen und in ein lookup-table packen?
4. Ist es unbedingt notwendig einen Tiefpass vor den Lautsprecher zu
bauen?
>const uint8_t sinewave[1][256] PROGMEM=>...ist für mich 'n seltsames Array. Aber möglicherweise verstehe ich>wieder mal nix.
Richtig, ist halt ein zweidimensionales Array. Ich denke man, dass er es
später um weitere Töne erweitern will. Das PROGMEM schont den
Arbeitsspeicher weil die werte direkt aus dem Flash gelesen werden. Evtl
ist das auch schon das Problem, da der Flash nicht so schnell ist. Darum
hört man auch Pausen. Vielleicht würde ich es erstmal ohne Progmem
testen und später dann immer die Töne im RAM cachen.
danke für den Tip, die Pausen sind jetzt weg, allerdings hört sich der
Ton jetzt etwas verzerrt an..
Ein ganz einfacher Tiefpass wird aus 22nF und 100k-Widerstand gebaut,
oder?
(hab ich so mehrfach im web zum glätten von pwm gesehen).
So, hab langsam doch noch etwas mehr verstanden..
Da die PWM eine Frequenz von ca. 32khz hat,
und ich den 8-bit timer benutze,
habe ich doch, wenn ich genau eine sinuswelle in 256 Werte packe,
32khz/256=125, also 125 Sinuswellen pro Sekunde, richtig?
Das wären dann 125 Hz, richtig?
Habe mir dazu ein kleines prog geschrieben, mit dem man Sinuskurven
manipulieren kann (stauchen,strecken), und das die Sinuskurve i.d.
positiven Bereich verschiebt (Werte sollen ja für PWM herhalten), und
die Werte auf 0-256 normiert. Mithilfe der obigen Formel kann ich ja nun
versch. Frequenzen erzeugen und speichern.
Habe mir jetzt drei Frequenzen, 500Hz, 600Hz und 700Hz generiert, aber
das hört sich ziemlich mies an, immer noch entfernt von klaren tönen.
Verrechnet habe ich mich hoffentlich nicht, ich hab das ganze mal
geplottet, ist im Anhang zu finden (geplottet hab ich die 256 Werte, die
errechnet werden für 600Hz).
Um verschiedene Töne zu erzeugen, bringen verschiedene Array-Indizes
nichts. Du brauchst eine Sinus-Tabelle, durch die Du verschieden
schnell steppst, d.h. Du überspringst ab und zu Werte (bei hohen
Frequenzen) oder wiederholst Werte (bei niedrigen Werten).
Genau das macht eine DDS. Hast Du dieses DDS-Prinzip verstanden?
Du inkrementierts nicht jedes mal (i++), sondern addierst eine
(Fixed-Point-)Konstante (und nimmts nur die oberen 8 Bit des
16-Bit-Ergebnisses als Index ins Array).
Das schöne: die ausgegebene Frequenz ist direkt proportional zu dieser
Konstanten.
Ich habe hier im Forum schon etliche Beispiele und Litertur-Links
gepostet.
Viel Erfolg! Du bist schon ganz nah am Ziel.
ich hab schon versucht, die schrittweise z.B. zu verdoppeln, mit magerem
Ergebnis..
wenn ich hier nach DDS suche finde ich fast nur Beiträge über
Hardware-Bausteine oder in Verbindung mit einem Widerstandsnetzwerk..
"sondern addierst eine
(Fixed-Point-)Konstante (und nimmts nur die oberen 8 Bit des
16-Bit-Ergebnisses als Index ins Array)"
Das verstehe ich leider gerade überhaupt nicht..
Aber nochmal, wenn ich verschiedene sinus-frequenzen in je eine
lookup-tabelle packe, kann ich doch mit den versch. lookup-tabellen dann
unterschiedl. töne erzeugen, weil ein sinus mit hoher frequenz doch
einen höheren ton erzeugt als einer mit langsamer frequenz, oder?
ich habs jetzt schon soweit geschafft, dass das, was rauskommt sich
wirklich wie töne anhört, und zwar astreines gameboy-gepiepse :-)
Hab zwar keine Ahnung, was er oben mit 16bit und 8bit abschneiden
gemeint hat, aber ich hab jetzt mal, wie er gesagt hat, statt i++ zu
wählen,
i += scale
gemacht, und scale erhöhe/erniedrige ich im Programm, dadurch kommen
schonmal unterschiedliche Töne raus, weil er ja die sinuskurve mit hohem
scale-wert schneller abläuft.
Aber irgendwas fehlt noch, wenn ich scale die ganze zeit erhöhe werden
die Töne nicht immer höher, manchmal sind auch ziemlich schräge, etwas
tiefere töne dabei (vllt wegen überlauf oder so)..
Ich werd mich morgen oder übermorgen nochmal dransetzen und mal
versuchen, ne tabelle mit "sauberen" tönen zu erstellen, dass man auch
mal melodien dudeln kann..
Ich hab mal meine bisherigen Ergebnisse hier in einem Artikel
zusammengefasst:
http://www.infolexikon.de/blog/atmega-music/
Noch nicht ganz fertig, aber ich setz mich wie gesagt morgen/übermorgen
nochmal dran und machs fertig!
ich hab in deinen blog nur einmal kurz drübergeschaut, aber du redest
immer bei 8 bit von 0 bis 256
es sind zwar 256 werte aber der wertebereich ist trotzdem von 0 bis
255!!!!
jo, hast recht, sorry, werds gleich ändern..
aber nochmal zu dem 8bit - 16bit problem..ich versteh immer noch net,
welche 8bit ich da wie abschneiden soll?
Soll ich, wenn ich den sinus z.B. mit 1.5 abtasten will, nen float
addieren, und der hat 16 bit, und ich schneide dann das nachkomma ab
oder wie?
Ok, hab jetzt mal versucht, das zu realisieren, die Tonhöhen ändern sich
jetzt zwar entsprechend, Töne werden höher wenn ich scale erhöhe,
aber hören sich jetzt auch wieder sehr mies an..woran könnte das liegen?
mhm...die abtast-rate kann ich ja jetzt feiner einstellen..einige der
Töne hören sich "gut" an, also nicht schräg oder verzerrt...
gibts eine Möglichkeit, die "sauberen" Töne auszurechnen, Formel oder
Richtwerte, oder muss ich jetzt jede einzelne Möglichkeit durchhören und
aufschreiben??
ich habs immer noch nicht gerafft..
mein index-counter hat jetzt 16bit, ist ja uint16_t.
d.h. er kann Werte zwischen 0 und 32767 annehmen.
indem ich aber nur die oberen 8 bit davon benutze, kann mein counter
also wieder nur werte zwischen 0 und 256 annehmen.
Das hiesse, dass ich scale dann um 32767/256 = 128 erhöhen muss, wenn
ich den Wert des Counters im Endeffekt um 1 erhöhen will, oder?
Ich hab hier mal ne Tabelle, von Tönen die ich benutzen möchte:
c'' = 523.25 Hz
d'' = 587.33 Hz
e'' = 659.26 Hz
f'' = 698.46 Hz
g'' = 783.99 Hz
a'' = 880.00 Hz
h'' = 987.77 Hz
Wenn ich jetzt als Sinus-Tabelle einen Sinus mit 100 Hz nehme,
wie muss ich dann Scale verändern, um auf die obigen Hz-Freqs zu kommen?
Bsp.: c'':523.25 Hz = 5.2325 * 100Hz => 5.2325 * 128 = 670
Das haut aber auch nicht hin.
Kann mir bitte jemand helfen?
1. Ein Byte kann immernoch nur Werte zwischen 0 un 255 annehmen.
2. Zwei Bytes können Werte von 0 bis 65535 annehmen (nicht nur bis
32767)
3. Deine Sinustabelle hat keine Frequenz. Die 256 verschiedenen Werte
des Arrays bilden genau eine komplette Sinuskurve.
Die Frequenz errechnet sich dann folgendermaßen:
f = (31259Hz * scale) / 65536
d.h. hiesse dann also,
dass ich den scale zu meiner gewünschten freq mit
scale = 65536/31259*f = 2.096 * f
berechnen kann.
Also wieder für c'' = 523.25 Hz:
scale = 2.096*523.25 = 1096,732,
also müsste ich dann für scale 1096 einstellen.
Hab ich gemacht, da kam dann wieder Gebrumme raus..
Wahrscheinlich hab ich wieder irgendeinen saublöden fehler im Code
gemacht und finde ihn nicht..
Für die Hilfe zeige ich mich gern erkenntlich und schreibe nen Artikel
fürs Wiki, sobald ichs hinbekommen hab!
Nimm die Sinustabelle aus deinem ersten Beitrag.
Die Sachen mit den 100Hz und 300Hz Tabellen ist Murks! Die ergeben
nämlich wenn mann sie aneinander setzt keinen Sinus mehr. Da spring der
Wert beim Übergang vom letzten auf den ersten Wert plötzlich und das
hört sich Scheiße an.
alles klar, hab jetzt wieder die sinus-tabelle aus dem ersten beitrag.
geändert hat sich leider immer noch nicht viel.
ich hab auch das gefühl, dass ichs immer noch nicht verstanden hab.
also zuerst mal sind mein counter und mein scale jetzt beide 16bit,
haben also einen Wertebereich von 0 bis 65536.
Nachdem ich scale zum Index-Counter addiert habe, nehme ich vom Counter
dann jeweils die oberen 8bit, habe dann also wieder Werte zwischen 0 und
256, oder?
Inwieweit hilft mir das jetzt, dadurch verfeinere ich doch nichts?
Wenn ich scale immer um 256 erhöhe (also im Endeffekt ja dann dem
Index-Counter immer 1 aufaddiere) bekomme ich klare töne, aber nicht
proportional zum scale-wert, in nicht erkennbarer anordnung..
Eigentlich steigt doch die Frequenz mit steigendem scale-Wert stetig an,
d.h. die Töne müssten immer höher werden?
>>Nachdem ich scale zum Index-Counter addiert habe, nehme ich>>vom Counter dann jeweils die oberen 8bit, habe dann also>>wieder Werte zwischen 0 und 256, oder?
also mit den wertebereichen musst dir echt mal einprägen, ist zwar nicht
weiter wichtig, kann dir aber bei berechnungen extrem auf die füße
fallen:
8bit: 0 bis 255, aber 256 werte
16bit: 0 bin 65535, aber 65536 werte
ok, das hier ist das soundfile zum code meines letzten posts.
ich erhöhe scale also die ganze zeit um 256, aber die Töne folgen
aufeinander ohne erkennbaren zusammenhang..
Das liegt am Skalieren und an Fixedpoint-Arithmetik.
Du kannst Dir das obere Byte von i als Stellen vor dem Komma und das
untere Byte als Stellen nach dem Komma vorstellen.
Wenn der Wert i jetzt z.B. 400 ist, bedeutet das, Du erhöhst scale um
400/256=1,5625.
D.h. beim ersten Mal ändert sich der scale>>8 um eins, beim zweiten Add
aber um 2 , da 800 / 256 = 3,125 und vorher schon um 1 erhöht wurde.
Im Mittel eben um 1,5625
Beachte: die Frequenz von Halbton zu halbton: mit 12. Wurzel aus 2 =
1,059... multiplizieren.
Zu den DDS-Beiträgen:
Wenn es um AD9xxx-Bausteine geht, ist eine Hardware-DDS (meist HF bis
xxx MHz) gemeint.
Wenn ein R-Netzwerk vorkommt, bist Du schon richtig, denn es ist egal,
ob Du die Werte über DA-Wandler oder PWM ausgist. Das DDS-Prinzip ist
das selbe, nur die Ausgabe ist jeweils anders.
Noch zum Lesen:
Beitrag "Musik machen in Assambler"Beitrag "Sinus generieren - welcher AVR am besten?"
www.myplace.nu/avr/minidds/index.htm
Suce auch nach DTFM, das wird häufig mit Software-DDS gemacht.
Oder nach Tonerzeugung oder Sinuston oder Sinusgenerator.
>>fPWM = 8MHz / 256, also ist die pwm-frequenz grob 32khz
ach so, ist die pwm frequenz nicht:
Ausgangsfrequenz = (Quarzfrequenz/Prescale ) /(Timerauflösung*2)
also bei dir 8000000/1/512=15,6KHz (statt deiner angegebenen 32kHz)
da ja einmal raufzählen und wieder runterzählen eine periode ist
(0..255..0)
ist aber auch nebensächlich
ISR(TIMER1_COMPA_vect){
OCR1A=pgm_read_byte(&sinewave[tone][(i>>8)]);
i += scale;
}
geht kompakter so:
OCR1A=pgm_read_byte(&sinewave[tone][(i += scale>>8)]);
Außerdem würde ich i als Union gestalten, das hat den Vorteil, dass man
ohne Bit-Geschiebe auf ein einzelnes Byte zugreifen kann:
s.a. Beitrag "Re: In uint16_t HB und LB schreiben !? In C"
#define lo 0 // Abhängig von der Endianess des Systems!!
#define hi 1 // kann bei ATMEL anders herum sein ?!?
union {uint16_t u16;uint8_t u8[2];} i;
jetzt kann die obere Zeile so heißen:
OCR1A=pgm_read_byte(&sinewave[tone][i.u8[hi]]);//high-byte
Zu den komischen Tönen: ein Grund ist, dass bei Dir das scale zu große
Werte annimmt. Dadurch verändert sich scale während einer
Sinus-Schwingung zu stark (hat extreme Frequenzmodulation).
Die anderen Gründe hat Telekatz ja schon genannt.
Außerdem gibt es eine Frequenzspiegelung, wenn scale größer als 32767
wird, da dann das "Phase-Wheel" rückwärts läuft - gut, bei Sinus merkt
man das nicht gleich, aber bei einer anderen Signalform (Sägezahn) läuft
das Signal andersherum.
Erklärung: da die Summe wiederum nur 16 Bit hat, wird die Summe (scale)
kleiner statt größer. z.B. 50000 + 40000 = 90000 , passt aber nicht in
16 Bit rein, wird also auf 90000 - 65536 = 24464 abgeschnitten.
Zu Variablennamen: ich würde statt i ddsSum und statt scale ddsAdd
verwenden.
Zur Hardware: hast Du einen DC-Entkopplungskondensator am Ausgang
eingebaut?
Sonst kann es sein, dass die Membran des LS durch den Gleichstrom
anschlägt und deswegen Verzerrungen auftreten.
Suche mal bei Analog-Devices nach
"A Technical Tutorial on Digital Signal Synthesis", damit habe ich es
damals gelernt.
Ach ja, der 16bit-Add heißt dann
ddsSum.u16 += ddsAdd;
Die ISR ist am effektivsten in ASM programmiert.
Evtl. an eine höhere PWM-Frequenz oder eine kürzere Tabelle (64 oder
128)denken. Könnte ein schöneres Ausgangssignal geben, vor allem bei
höheren Frequenzen.
Ob das volatile notwendig ist?
Es wird ja in der ISR nicht verändert, und die ISR kann nicht davon
ausgehen, dass i noch in einem Register steht.
Mit DDS kann man leicht mehrstimmige Melodien erzeugen, für jeden Ton
eine eigene ddsSum, und alle gelookten Werte addieren.
zu www.myplace.nu/avr/minidds/index.htm :
hier wird die DDS in der Main gemacht, damit die komplette
Prozessorleistung dafür verwendet wird.
Die Main besteht nur aus 6 ASM-Befehlen, welche 9 Cyclen brauchen. Die
DDS läuft also bei ihm mit 11059200/9=1228800 Hz.
Resolution = fCPU/150994944 (9*2**24) 24=Breite des Phasenaccus
Die serielle Kommunikation (Frequenz- und Form-Änderung) geschieht im
IRQ.
Ist wirklich einfach und straigt forward.
Andreas Schwarz hat ja als Facharbeit einen Generator gebaut (findest Du
auch im hier bei den Artikeln), hast Du die Beschreibung gelesen?
Dort ist ein Schreibfehler: die ISR wird nicht 256000, sondern
6553600/(24+1)=262144 mal pro Sekunde aufgerufen.
Deshalb ist für 1kHz das ddsAdd fa00 (64000):
262144*64000/256/65536=1000.
Korrektur:
geht kompakter so:
OCR1A=pgm_read_byte(&sinewave[tone][(i += scale>>8)]);
ist falsch geklammert:
OCR1A=pgm_read_byte(&sinewave[tone][(i += scale)>>8]);
Und wenn Du einen linearen Sweep ("Accelerando") erzeugen willst:
#define i ddsSum
#define scale ddsAdd
#define sweep ddsAcc
OCR1A=pgm_read_byte(&sinewave[tone][(ddsSum += ddsAdd += ddsAcc)>>8]);
sweep muss natürlich viel kleiner als scale sein.
Der Wertebereich von ddsAdd muss eingeschränkt werden (wie oben
beschrieben) z.B. auf 16 Bit.
Hier könnte für eine feine sweep-Einstellung ein Umstieg auf 24 oder
32-bit breites ddsSum angezeigt sein.
Das mit der kleineren Tabellengröße nehme ich zurück: eine große Tabelle
(256) hat keine Nachteile, sondern nur Vorteile (automatischer
Wrap-Around).
Hallo zusammen und erstmal entschuldigung, dass ich diesen Thread wieder
hervorkramen muss.
Ich beschäftige mich nun schon länger mit dem Erzeugen eines Sinus aus
einer Funktionstabelle und dem skalieren /Frequenz verändern.
Hierzu habe ich mich an den Vorschlag:
ISR(TIMER1_COMPA_vect){
counter += rate;
OCR1A=pgm_read_byte(&sinewave[(counter>>8)]);
}
gehalten. Dies funktioniert bei mir auch recht gut. Allerdings bin ich
mir noch nicht sicher, wie ich die Rate genau (1.) berechne um eine
bestimmte Frequenz zu erhalten.
2. Ich habe Fotos hinzugefügt, welche meine Problematik von einer
fallenden Amplitude beim Erhöhen der Frequenz zeigen. Da ich mein Signal
über einen Audioverstärker verstärken möchte, darf die Amplitude nicht
zu groß/klein werden. Außerdem "zappelt" der Sinus und die Töne sind
nicht so sauber.
3. Als Alternative möchte ich die einzelnen Werte in der Sinustabelle
öfters wiedergeben, bevor ich zum nächsten Wert weitergehe. Dafür möchte
ich Timer0 im Overflow Betrieb benutzen, mit TCNT0 würde ich dann die
Frequenz einstellen wollen:
ISR(TIMER1_COMPA_vect){
OCR1A=pgm_read_byte(&sinewave[i]);
}
ISR(TIMER0_OVF_vect)
{
TCNT0=200;
i++;
}
mit:
// initial OCR1A value
OCR1A=128; //80;
//Output compare OC1A 8 bit non inverted PWM
TCCR1A=145; // =0x91;
//start timer without prescaler
TCCR1B=1;
//enable output compare interrupt for OCR1A
TIMSK=66; // timer0 interrupt overflow enable
TCCR0B=0b00000001; // kein Vorteiler
TCNT0=1;
//enable global interrupts
sei();
Leider führt dies zu keinem Sinus mehr und der Ausgang bleibt eine Zeit
lang auf high und schaltet nur kurz etwas verauschtes am Pin.
Ich verwende einen Attiny2313, der mit einem Quarz auf 12Mhz getaktet
wird.
Vielen Dank schonmal für eure Tipps,
dan