Hi Ich möchte mit einem Atmega8, der entweder mit dem internen 1Mhz oszillator oder einem externen 4Mhz Quartz läuft zwei- bis dreistimmige Lieder (über zwei bis drei Lautsprecher (= 1 Lautsprecher pro Ton bzw Frequenz) spielen. Das ganz einfach indem man den Pin in der Richtigen Frequenz an und ausschaltet. Tu mich allerdings mit dem Rechnen was schwer (CPU Frequenz usw...) also ich weiss, dass der Ton a = 440Hz ist und jeder Halbtonschritt genähert (temperiert) 440 * 12.Wurzel aus 2 hoch n (für n-ten Halbtonschritt) ist. Bräuchte also entweder eine Art Beep_(int Freq_inHz) Funktion oder eine andere Möglichkeit. Reicht der Interne oder der 4Mhz Oszillator aus? Sonst müsste ich wieder bestellen und ich wollte es zu Weihnachten fertig haben. Also ich geb mich selbst gleich ans rechnen usw. aber falls jemand sowas schon mal gemacht hat (in ASM oder besser in C), könnt ihr mir ja mal ein paar Tipps geben. Danke im Vorraus. Übrigens (wen es interessiert): Der Plan ist aus 3 defekten Duracell-Hasen (ihr wisst schon, die die sich bewegen und irgendein Lied piepen) ein schönes Trio zu machen dh. in einem ist ein Atmega8 drin der über Kabel mit jew. einem Lautsprecher in jew. einem Hasen verbunden wird und dann "singt" jeder seine Stimme..... mal gespannt wie's klingt und aussieht.
Danke für die schnelle Antwort. Allerdings ist das Problem bei diesem Beispiel, dass durch die Timer Compare nutzung nur ein Lautsprecher funktionieren kann, da der Atmega8 nur einen Timer hat, der diese Funktion hat.
1. Wieso unbedingt mehrere Lautsprecher? 2. >das Problem bei diesem Beispiel, dass durch die Timer >Compare nutzung nur ein Lautsprecher funktionieren kann, da der Atmega8 >nur einen Timer hat, der diese Funktion hat. Ja? Wie wär's mit Timer auf die kleinste gemeinsame Frequenz einstellen und dann runterteilen(mit Hilfsvariablen)?
die o.g. Variante kann EINE Stimme. Nimmst Du das Ganze x3 hast Du 3 Stimmen. Der interne 1MHz-Oszillator sollte völlig ausreichen, es sei denn, Du willst exakt 440,0 Hz erreichen. Ansonsten ist für eine Melodie nur das Verhältnis der Frequenzen interessant, nicht der Absolutwert. Auch das a wird inzwischen meist auf 443 bis 443 Hz gestimmt, je nach Oboe im Orchester.. :-) Es geht auch mit einem Mega8 und 3 Stimmen: In einer Schleife zählst Du 3 Variablen herunter. Immer wenn eine =0 ist, schaltest Du den zugehörigen AusgangsPin um und lädst die Variable wieder aus einer Konstante (der Tonhöhe). Wenn die Variable nicht =0 ist, setzt Du einen Dummy-Befehl. Wichtig dabei ist, daß Deine Hauptschleife immer exakt die gleiche Taktzahl benötigt zum Durchlaufen. Somit hast Du einen Dreiklang an den 3 Ausgängen (Wenn Du sie zusammenmischt). Für den nächsten Ton kannst Du einen Timer verwenden, wo Du die "Konstanten" änderst. Hat schon 1988 mit einem Z80 so funktioniert.
Kann's auch ein bisschen komplizierter sein? http://www.jcwolfram.de/projekte/avr/avrmusicbox/main.php Hat zwei DDS-Stimmen, Hüllkurven, Sequenzer und "Pseudeo 16Bit PWM" Ausgabe. Läuft auf Mega8(8) mit 8MHz internem Takt. Um die zwei Stimmen zu trennen, müsste nur das Ende der Interruptroutine etwas umgebaut werden. Wenn Interesse besteht, schaue ich mir heute abend den Code nochmal an. Gruß Jörg
Also "Alle meine Entchen" als Sinus mit Nachhall auf einem Mega16 (8er geht auch) waren kein Problem. Die Klangberechnung erfolgt ganz einfach - man nehme: Für jeden Kanal eine 16-Bit-Variable für die Phase Für jeden Kanal eine 16-Bit-Variable für die Frequenz Für jeden Kanal eine 16-Bit-Variable für die Dauer des Tons Ein Zeiger auf eine Tabelle mit Frequenz- und Dauerwerten Ein Timerinterrupt, der folgendes tut:
1 | - Temp = 0 |
2 | - für alle Kanäle in i |
3 | - Tondauer[i] verringern |
4 | - Wenn <0 (oder == 0, wenn du alles richtig machst) |
5 | - Tondauer[i] = Tondauer aus Tabelle, Zeiger++ |
6 | - Frequenz[i] = Frequenz aus Tabelle, Zeiger++ |
7 | - Ende Wenn |
8 | - Phase[i] += Frequenz[i] |
9 | - Signed-Bit (MSB) in Temp schieben: Temp <<= 1; Temp |= ((Phase[i] >= 128) ? 1 : 0) ... oder so |
10 | - nächster Kanal |
11 | - PORTx = Temp (Bit 0 ist Kanal 3, Bit 1 ist Kanal 2, Bit 3 ist Kanal 1) |
Wenn ich nichts auf die Schnelle vergessen habe, dann sollte das ohne Weiteres gehen. Ist nur Pseudocode! Ich habe ein paar Details außer Acht gelassen. Die 'Frequenzen' kannst du dir einfach ausrechnen. Wie du die Melodietabelle aufbaust, ist dir überlassen... wahrscheinlich sind drei einzelne Tabellen einfacher. So wie oben beschrieben wäre der Aufbau der Tabelle recht schwierig. Auch das Ende der Tontabelle ist im obigen Algorithmus nicht berücksichtigt. Stille erzeugst du mit einer Phase von 0, wobei du den Lautsprecher dann kapazitiv koppeln solltest um nicht zu viel Strom zu verbraten. Sollte sogar in C locker zu schaffen sein. Das Ganze geht auch ohne Timer einfach in einer Schleife, aber du musst dann für eine konstante Schleifendauer sorgen, was in C meines Wissens nach prinzipbedingt nicht geht. Ich habe jetzt mal ein Rechtecksignnal vorgesehen, kannst du ja einfach glätten - Sinus ist minimal schwieriger (einfach die Phase als Lookup-Wert für eine Sinus-Tabelle verwenden), sollte aber dann über ein R2R-Netzwerk ausgegeben werden, was für einen Mega8 recht viele Ports verbraucht. Ausgabe mittels PWM sehe ich aber nicht mehr vor Weihnachten, wenn überhaupt sinnvoll möglich ;) Der Sinus würde auch die Lautstärkeregulierung und damit einen einfachen Nachhall erlauben (Lookup-Wert einfach mit Tondauer[i] multiplizieren - exponentieller Lautstärkeabfall ist zu aufwändig für den Nutzen) - dann wird's mit C evtl. knapp. Hoffe, das hilft die ein wenig weiter und ich bin mal gespannt, was da am Ende rauskommt ;) Gruß Kai
>Stille erzeugst du mit einer Phase von 0, ...
Lötzinn! Die Frequenz (Winkelgeschwindigkeit) muss 0 sein, die Phase und
damit die Elongation bleiben dann konstant.
Nachhall? Du meinst wohl Nachklang(Sustain)? Ein Hall(Nachhall) ist äuserst komplex mit zufälliger Frequenz/Amplituden- und Phasenmodolation.
Also so kompliziert muss es garnicht sein. Mir würde eine einfache Rechteckspannung vollkommen ausreichen. Auch auf die ganze Tontabelle würd ich gerne verzichten. Am besten wär (zumindest für mein Projekt, da es relativ schnell gehen muss) einfach eine Funktion
1 | void Beep(double Frequenz1, double Frequenz2, double Frequenz3, int DauerMS) |
2 | {
|
3 | /*Blockierende Routine (wie die WindowsAPI Funktion Beep,
|
4 | nur dass für jeden der drei Pins eine Frequenz angegeben wird)
|
5 | , die auf den Pins die entsprechende Rechteckspannung erzeugen.*/
|
6 | }
|
7 | //bzw.
|
8 | void Toggle(int CPUTakte1, int CPUTakte2, int CPUTakte3, int DauerMS) |
9 | {
|
10 | /*Blockierende Routine, die eine Rechteckspannung mit der
|
11 | Periodendauer entspr der CPUTakte erzeugt*/
|
12 | }
|
Komm nur mit dem Inline-asm und volatile usw noch nicht so gut klar, sonst hätte ich es bestimmt schon selbst geschafft (bin ja nicht faul, hab im Moment nur leider wenig Zeit) Ist so zwar mühsam die Lieder einzutippen aber das ist kein Problem. Ich weiss, es ist nicht sonderlich elegant... aber das kann man ja später noch alles machen. Hab mir die Frequenzen, die entsprechenden Prozessortaktzahlen bei gegebener CPU-Frequenz (1 bzw 4 Mhz) und die daraus resultierende Abweichung von der eigentlichen Frequenz schon in eine schöne Exceltabelle gebaut, die ich leider grad nicht zur Hand habe.. (ich nehm A trotzdem als 440Hz, auch wenn es die Orchester anders machen.... ) Hab bei 1 Mhz nur Abweichungen < 0.07%, also für das Projekt mehr als ausreichend. Könnte mir jemand evtl diese Routine fertig machen, der etwas fit mit Inline-Asm bzw. den Taktzyklen der einzelnen Befehle ist? Danke im Vorraus und schon mal Frohe Weihnachten. PS: Großes Lob an die Community. Ihr macht es für Einsteiger richtig leicht reinzukommen usw.
Jaja... Nachklang... Aber wenn wir eh gerade eh gerade im Klugscheißermodus sind ;) >Ein Hall(Nachhall) ist äuserst komplex mit zufälliger >Frequenz/Amplituden- und Phasenmodolation. - komplex ist das eigentlich nicht. Das Originalsignal kann einfach mit der Raumimpulsantwort gefaltet werden... ist nur in der vollen Ausprägung sehr rechenintensiv. - zufälliger ist daran eigentlich auch nichts - lässt sich alles berechnen - Frequenzen werden da meines Wissens nach nicht moduliert Eine einfache Umsetzung passt auch in einen Mikrocontroller: Ringpuffer mit einfacher arithmetischer Glättung. Ach ja: Nachklang ist übrigens nur ein Spezialfall des Nachhalls. Einen hab ich noch: "Modulation", nicht "Modolation" Quitt? ;) @Thomas Oster Bin leider zeitlich auf zu kurz angebunden um dir das mal eben runterzutippen... zudem arbeite ich bis jetzt nur in Assembler - mit C-Code kann ich nicht dienen. Aber wo genau ist dein Problem den obigen Pseudocode zu übersetzen? Das Mit der Tonkalibierung kannst du später machen - Hauptsache das tutet erstmal. Die Tonhöhen kannst du auch pi mal Daumen machen. Hauptsache die Relationen zwischen den Frequenzen stimmen (Faktor ungefähr! 1,0594630943592952645618252949463 also 2^1/12) - aber das scheinst du ja eh schon zu wissen ;) bei 4 MHz soll das laufen? Ach ja... die zweite Variante deiner Funktion ist besser... double kannst du niemals in der notwendigen Geschwindigkeit verarbeiten! Gruß Kai
Wie wäre es denn, wenn Du mal die große Foren-Suche aktivieren würdest? http://www.mikrocontroller.net/search Stichworte wie Ton*, Sinus*, Mehrton*, DTMF, DDS*, Musik*, Midi* bringen mehr als genug Treffer. Und das ganze von 2 auf 4-stimmig auszubauen dürfte doch zu schaffen sein. Da gibt es doch diese Erweiterungsmodule für Soundkarten, das sind komplette Midi-Synthesizer, die sehr einfach anzusteuern sind.
So, da ja schon fast Weihnachten ist: Lass dir das nicht zur Gewohnheit werden!
1 | .include "m8def.inc" |
2 | |
3 | ; Der Port muss logischer Weise als Ausgang definiert werden. |
4 | |
5 | .equ DURATION_DECREMENT = 1 |
6 | |
7 | .equ AUDIO_PORT = PORTD ; Bitte Ausgabeport korrekt einstellen! |
8 | .equ AUDIO_IO_1 = PIND0 ; Bitte angeben, an welchem Pin der erste Speaker hängt |
9 | .equ AUDIO_IO_2 = PIND1 ; Bitte angeben, an welchem Pin der zweite Speaker hängt |
10 | .equ AUDIO_IO_3 = PIND2 ; Bitte angeben, an welchem Pin der dritte Speaker hängt |
11 | |
12 | |
13 | ; Übergabeparameter - jeweils uint16 |
14 | .def frequency1Low_reg = r16 |
15 | .def frequency1High_reg = r17 |
16 | .def frequency2Low_reg = r18 |
17 | .def frequency2High_reg = r19 |
18 | .def frequency3Low_reg = r20 |
19 | .def frequency3High_reg = r21 |
20 | .def durationLow_reg = r22 |
21 | .def durationHigh_reg = r23 |
22 | |
23 | ; Zwischenwerte |
24 | .def phase1Low_reg = r0 |
25 | .def phase1High_reg = r1 |
26 | .def phase2Low_reg = r2 |
27 | .def phase2High_reg = r3 |
28 | .def phase3Low_reg = r4 |
29 | .def phase3High_reg = r5 |
30 | .def portMask_reg = r6 |
31 | .def toggleMask1_reg = r24 |
32 | .def toggleMask2_reg = r25 |
33 | .def toggleMask3_reg = r26 ; Nimm nach Möglichkeit einen anderen Register - kollidiert mit XL |
34 | .def delay_reg = r27 ; Nimm nach Möglichkeit einen anderen Register - kollidiert mit XH |
35 | |
36 | |
37 | ; Testinitialisierung (muss durch deine Übergabeparameter befüllt werden) |
38 | |
39 | .equ TEST_FREQUENCY_1 = 100 << 1 |
40 | .equ TEST_FREQUENCY_2 = 440 << 1 |
41 | .equ TEST_FREQUENCY_3 = 10000 << 1 |
42 | .equ TEST_DURATION = 65535 |
43 | |
44 | ldi frequency1Low_reg, low(TEST_FREQUENCY_1) |
45 | ldi frequency1High_reg, high(TEST_FREQUENCY_1) |
46 | ldi frequency2Low_reg, low(TEST_FREQUENCY_2) |
47 | ldi frequency2High_reg, high(TEST_FREQUENCY_2) |
48 | ldi frequency3Low_reg, low(TEST_FREQUENCY_3) |
49 | ldi frequency3High_reg, high(TEST_FREQUENCY_3) |
50 | ldi durationLow_reg, low(TEST_DURATION) |
51 | ldi durationHigh_reg, high(TEST_DURATION) |
52 | |
53 | ; <<<<<<<<<<<<<<<<<< START >>>>>>>>>>>>>>>>>>>> |
54 | |
55 | ; Phasen auf 0 initialisieren (ist eigentlich egal womit die starten, aber ordentlicher) |
56 | |
57 | cli ; Alle Interrupts aus |
58 | |
59 | clr phase1Low_reg |
60 | clr phase1High_reg |
61 | clr phase2Low_reg |
62 | clr phase2High_reg |
63 | clr phase3Low_reg |
64 | clr phase3High_reg |
65 | |
66 | ; alte I/O-Settings einlesen |
67 | in portMask_reg, AUDIO_PORT |
68 | |
69 | ; Toggle-Masken setzen |
70 | ldi toggleMask1_reg, 1 << AUDIO_IO_1 |
71 | ldi toggleMask2_reg, 1 << AUDIO_IO_2 |
72 | ldi toggleMask3_reg, 1 << AUDIO_IO_3 |
73 | |
74 | soundLoop: |
75 | |
76 | ; Verzögerung, damit die Hauptschleife exakt 61 Takte braucht |
77 | ldi delay_reg, 14 |
78 | delay: |
79 | subi delay_reg, 1 |
80 | brne delay |
81 | nop |
82 | nop |
83 | |
84 | ; Phase 1 weiterzählen und bei Überlauf den zugehörigen I/O umschalten (toggle) |
85 | add phase1Low_reg, frequency1Low_reg |
86 | adc phase1High_reg, frequency1High_reg |
87 | brcc noToggle1 |
88 | eor portMask_reg, toggleMask1_reg |
89 | noToggle1: |
90 | |
91 | ; Phase 2 weiterzählen und bei Überlauf den zugehörigen I/O umschalten (toggle) |
92 | add phase2Low_reg, frequency2Low_reg |
93 | adc phase2High_reg, frequency2High_reg |
94 | brcc noToggle2 |
95 | eor portMask_reg, toggleMask2_reg |
96 | noToggle2: |
97 | |
98 | ; Phase 3 weiterzählen und bei Überlauf den zugehörigen I/O umschalten (toggle) |
99 | add phase3Low_reg, frequency3Low_reg |
100 | adc phase3High_reg, frequency3High_reg |
101 | brcc noToggle3 |
102 | eor portMask_reg, toggleMask3_reg |
103 | noToggle3: |
104 | |
105 | ; Den Lautsprechern etwaige Änderungen mitteilen |
106 | out AUDIO_PORT, portMask_reg |
107 | |
108 | ; while (--duration > 0) |
109 | subi durationLow_reg, low(DURATION_DECREMENT) |
110 | sbci durationHigh_reg, high(DURATION_DECREMENT) |
111 | brne soundLoop |
112 | |
113 | sei ; Es darf wieder unterbrochen werden |
114 | |
115 | ; <<<<<<<<<<<<<<<<<< ENDE >>>>>>>>>>>>>>>>>>>> |
116 | |
117 | die: |
118 | rjmp die; |
Nicht wirklich schwer, oder? Ist jetzt nur im Simulator getestet, tut aber was es soll. Der Code kann nicht abstürzen. Als Dauer einen Wert von 0 - 65535 eintragen: - 1 -> 15,26 µs (1 / 65536) - n -> (n / 65536) s - 0 = 1 s Die Frequenzen musst du verdoppeln. Also 880 für 440 Hz. Im Anhang habe ich kurz die Werte für die temperierte Tonskala mit A' = 440 Hz angegeben. Ist definitiv genau genug für deine Fälle. Das Script verlässt sich auf dir 4 MHz! Bei 2 MHz werden alle Töne um eine Oktave tiefer. Beim Einbau in C kann ich dir leider nicht helfen, aber ich denke, dass das kein Problem ist. Ggf. musst du alle Register retten und so'n Kram. Ich kalibriere jetzt meinen Adventskalender...
Hi, Also erstmal ein riesen Dankeschön für den Code, er funktioniert super. Bin gerade dabei die Sachen fertig zu bauen evtl kann ich nachher ein Video reinstellen. Hat leider nicht vor Weihnachten geklappt.... aber dann gibt's das Geschenk halt zu Neujahr. Hab mir überlegt, die Sache erstmal in ASM zu lassen, aber den Code in C einzubauen dürfte auch nicht so lange dauern... Naja, also ich meld mich sobald ich fertig bin. Dankeschön
Noch mehr Methoden, zweistimmige Lieder zu erzeugen: http://www.roboterclub-freiburg.de/asuro/software/sound/ASURO_DDS_Sound.zip ( Hier werden 2 Motoren als Lautsprecher benutzt, man kann das Signal aber genausogut addieren und als PWM ausgeben. Es wird übrigens nur 1 Timer für das gesammte Systemtiming verwendet ) http://www.roboterclub-freiburg.de/atmega_sound/atmegaSID.html ( 3 stimmiger Synthesizer )
@Christoph H. Das gefällt mir sehr gut! (Der 2.Link) Nur eine Frage, ich will den "Fred" nicht shanghaien: Ich würde gerne nur einen Dreiklanggong für meine Eisenbahnanlage haben wollen, reicht es dafür, in der Datei "sound.c" die anderen Sounds rauszuwerfen und das Ganze neu zu übersetzen? (Die Sprache C ist nicht mein Gebiet) MfG Paul
@Christoph H. lol Der ATmega SID ist genial. Vor allem der minimalistische Aufbau mit dem ATmega als plattgefahrenem "Hundertfüßler" kann schon fast als moderne Kunst bezeichnet werden. Und euer Roboterclub ist auch eine schöne Idee. lob Wenn ich mehr Zeit hätt...
@Maximilian >Und euer Roboterclub ist auch eine schöne Idee. >Wenn ich mehr Zeit hätt... Vielen Dank, das höre ich gerne. Die Robterclubs sind halt das, was vor 25 Jahren die ersten Computerclubs waren ... >Vor allem der minimalistische Aufbau mit dem >ATmega als plattgefahrenem "Hundertfüßler Wobei ich allerdings zugeben muß, dass ich einen ähnlichen Aufbau vorher schon mal gesehen habe: nämlich einen Atmega, 2xAA Batterie und 2 LEDs ( keine Vorwiderstände ) @Paul >reicht es dafür, in der Datei >"sound.c" die anderen Sounds rauszuwerfen und das Ganze neu zu >übersetzen? Ja, der sound ist vollsändig in sound.c definiert. Mit den dort eingeführten #defines sollte es einfach sein, eine eigene Melody zu erzeugen.
@Christoph H. Danke für die Auskunft. Na, da wollen wir mal sehen, was wir hören. ;-) MfG Paul
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.