Forum: Mikrocontroller und Digitale Elektronik Mehrstimmige Lieder mit Atmega8


von Thomas O. (thommyoster)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?


von thommyoster (Gast)


Lesenswert?

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.

von uninteressent (Gast)


Lesenswert?

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)?

von Aufnehmer (Gast)


Lesenswert?

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.

von Joerg W. (joergwolfram)


Lesenswert?

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

von Kai G. (runtimeterror)


Lesenswert?

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

von Kai G. (runtimeterror)


Lesenswert?

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

von Messknecht (Gast)


Lesenswert?

Nachhall? Du meinst wohl Nachklang(Sustain)?

Ein Hall(Nachhall) ist äuserst komplex mit zufälliger 
Frequenz/Amplituden- und Phasenmodolation.

von Thomas Oster (Gast)


Lesenswert?

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.

von Kai G. (runtimeterror)


Lesenswert?

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

von eProfi (Gast)


Lesenswert?

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.

von Kai G. (runtimeterror)


Angehängte Dateien:

Lesenswert?

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

von Kai G. (runtimeterror)


Lesenswert?

Eine Rückmeldung wäre nicht schlecht, danke!

von Thomas Oster (Gast)


Lesenswert?

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

von Christoph H. (Gast)


Lesenswert?

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 )

von Paul Baumann (Gast)


Lesenswert?

@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

von Maximilian K. (laplace)


Lesenswert?

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

von Christoph H. (Gast)


Lesenswert?

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

von Paul Baumann (Gast)


Lesenswert?

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