avrmusicbox: Spieluhr mit 5 Melodien
V0.40 (c) 2006-2011 Jörg Wolfram


1 Allgemeines

Das Programm unterliegt der GPL (GNU General Public Licence) Version 3 oder höher, jede Nutzung der Software/Informationen nonkonform zur GPL oder ausserhalb des Geltungsbereiches der GPL ist untersagt! Die Veröffentlichung dieses Programms erfolgt in der Hoffnung, dass es Ihnen von Nutzen sein wird, aber OHNE IRGENDEINE GARANTIE, auch ohne die implizite Garantie der MARKTREIFE oder der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK.

2 Geschichte

Als unser Sohn im Mai geboren wurde, haben wir ihm auch eine mechanische Spieluhr gekauft, ein niedliches Schaf. Leider war die Musik etwas zu laut und zu kurz zum Einschlafen, und spätestens beim Aufziehen war der Kleine wieder hellwach.
Zu dieser Zeit habe ich mich etwas intensiver mit den AVR-Mikrocontrollern beschäftigt und nun gab es eine Idee und einen guten Grund für ein kleines Projekt.


3 Konzept und Realisierung

Kernstück des Ganzen ist ein ATmega8 oder 88, wobei letzterer eine geringere Stromaufnahme im Sleep-Modus hat. Gerade bei Geräten mit Batteriebetrieb ein nicht zu unterschätzender Vorteil. Allerdings hat sich die Adressierung einiger Hardware-Register gegenüber den ATmega8 geändert und so kommt relativ viel bedingte Assemblierung zum Einsatz.
Zur Tonausgabe soll ein Lautsprecher mittels PWM an den AVR angeschlossen werden. Die meisten Konzepte, die ich im Internet gefunden habe, arbeiten mit einem Auskoppelkondensator zur gleichstrommäßigen Entkopplung des Lautsprechers. Diese Vorgehensweise hat aber einen großen Nachteil bei Batteriebetrieb, sie braucht Strom um den Kondensator umzuladen, unabhängig von der Signalamplitude. Mein Konzept braucht zwar mehr Ausgänge, der Lautsprecher wird direkt angeschlossen und der Stromvergrauch der Ausgangsstufe ist in etwa proportional zur Amplitude des Ausgangssignals.

  • Ohne Signal wechseln beide PWM-Ausgänge in der Mitte der Periode ihre Polarität.
  • Bei positiver Signalamplitude wechselt der Ausgang OCR1A vor Mitte der Periode auf LOW, Ausgang OCR1B nach Mitte der Periode.
  • Bei negativer Signalamplitude wechselt der Ausgang OCR1B vor Mitte der Periode auf LOW, Ausgang OCR1A nach Mitte der Periode.
Das wird dadurch erreicht, dass das PWM-Register für Kanal A mit 127+Signal und das PWM für Kanal B mit 127-Signal geladen wird.
Im praktischen Betrieb reichen aber nach ersten Tests 8 Bits für die Signalamplitude nicht aus, da besonders bei kleinen Amplituden relativ starke Verzerrungen auftreten. Um die Auflösung zu erhöhen, kann leider nicht die PWM-Auflösung erhöht werden, da ansonsten die PWM-Frequenz in den hörbaren Bereich gerät. Also gibt es einen weiteren PWM-Ausgang, der einen Strom von ungefähr 1/256 des Hauptausganges auf die Seite von OCR1A einspeist. Rein theoretisch könnte somit die Auflösung auf 16 Bits hochgeschraubt werden, praktisch liegt die erreichbare Auflösung irgendwo bei 10-12 Bits. Durch Verändern von R2 können die Verzerrungen weitesgehend minimiert werden.



3.1 Die Interruptroutine

Ein wichtiger Teil der Programms ist die Interruptroutine für die Signalerzeugung. Sie läuft synchron mit der PWM-Frequenz, um Interferenzen und Jitter-Effekte zu vermeiden. Dadurch fällt natürlich die Möglichkeit aus, die Abtastrate mit der Tonfrequenz zu ändern. Aber es gibt eine einfache Lösung für das Problem, indem die Amplitude einer virtuellen Schwingung zum Samplezeitpunkt berechnet wird. Diese Technik ist auch als DDS (Direct digitally Syntheseis) bekannt. Bei jedem Aufruf der Interruptroutine wird ein Zeiger um einen Wert erhöht, der aus einer Notentabelle ausgelesen wird. Da sich die Wellenform nicht ändert, folgt jeder Schwingung eine identische weitere Schwingung, und somit wird ein etwaiger Überlauf des Zeigers ignoriert. Mathematisch lässt sich das so ausdrücken:
Zeiger(t)=(Zeiger(t-1)+offset) modulo 65536)
Der so erhaltene Zeiger wird gespeichert und als Ausgangszeiger für den nächsten Interrupt verwendet. Im aktuellen Interrupt wird der Zeiger durch 32 geteilt (eigentlich durch 64, aber die Wellenformwerte sind 2 Bytes groß), um dann mittels einer Wellenformtabelle mit 1024 Einträgen den aktuellen Signalwert zu bestimmen. Dieser wird aber nicht ausgegeben, sondern bedarf noch etwas Weiterbearbeitung.
Mit jedem Aufruf der Interruptroutine wird ein Prescaler hochgezählt und bei jedem Überlauf wird das Envelope-Bit gesetzt. Wird dies im weiteren Verlauf der Interruptroutine erkannt, wird der Envelope-Zeiger um einen Offset erhöht und das Envelope-Flag wieder zurückgesetzt. Ist sich nun der Envelopezeiger größer als die Anzahl der Einträge in der Envelope-Tabelle, wird der Kanal abgeschaltet. Andernfalls wird nun der aktuelle Lautstärkewert aus der Envelopetabelle ausgelesen und mit dem Signalwert aus der Wellenform multipliziert. Und weil wir gerade so schön beim Multiplizieren sind, wird der so erhaltene Wert mit einem 8-Bit Wert für die Kanallautstärke multipliziert. Zuguterletzt werden die Signalamplituden der beiden Kanäle addiert und die Steuerwerte für die PWM-Register berechnet.

3.2 Das Hauptprogramm

Das Haptprogramm besteht aus zwei Programmteilen, den Player mit Melodieauswahl und Power-Down und den Sequenzer. Letzterer interpretiert die Songdaten und erzeugt daraus die Parameter für die Interruptroutine.
Die Songdateien bestehen nur aus .db Statements, die einfach beim Assemblieren mitübersetzt werden und ein Definitionsfile sorgt dafür, dass für Noten etc. relativ plausible Symboliken verwendet werden können. Alle diese Symboliken sind genau 6 Zeichen lang, so lassen sich Fehler schneller finden.

3.3 Melodien programmieren

Um eine neue Melodie zu programmieren ist es am einfachsten, eine vorhandene Songdatei als Basis zu verwenden. Am Anfang jeder Songdateien sind die verwendeten Symboliken kurz als Kommentar erklärt. Hier dazu noch ein paar Beispiele:

_dummy Füllbyte, da jede .db-Zeile eine gerade Anzahl von Bytes enthalten muß
__ende Ende der Noten
_endvs Ende des Anspielens im Vorschaumodus
takt16 wartet 1/16 Note, bis weitere Daten gelesen werden
c2___1 spielt die Note C-2 im Kanal 1
n108_1 set setzt die Hüllkurvengeschwindigkeit für Kanal 1 auf 1/8 Note
vol__1,xxx Lautstärkewert für Kanal 1 setzen
ver__2,xxx setzt den Halbtonverstz für Knal 2 auf xxx (0...255), der wirklich gespielte Notenwert ist dann Note=(Note+Versatz) modulo 64
wad__1,lll,hhh setzt den Beginn der Wellenformtabelle für Kanal 1 auf die folgende Adresse Beispiel: wad_1,LOW(wtab1*2),HIGH(wtab1*2)
ead__2,lll,hhh setzt den Beginn der Hüllkurventabelle für Kanal 2 auf die folgende Adresse Beispiel ead_2,LOW(etab1*2),HIGH(etab1*2)


4 Aufbau

Das Ganze ist auf einer kleinen Platine (hier mit der SMD-Version) untergebracht:

Die folgenden zwei Bilder zeigen den Aufbau der Platine mit den externen Komponenten.



5 Bedienung

Die Bedienung der Spieluhr ist sehr einfach, als Bedienelemente gibt es nur einen Druckknopf und ein Potentiometer für die Lautstärke.
  • Um die zuletzt gewählte Melodie erneut abzuspielen, muß der Knopf nur kurz gedrückt werden
  • Um eine der programmierten Melodien zu wählen, muß der Knopf beim Einschalten länger als 1 Sekunde gedrückt gehalten werden. Danach werden alle Melodien nacheinander in einer Art "Schnelldurchlauf" angespielt. Bei der gewünschten Melodie wird der Knopf einfach losgelassen, kurz darauf schaltet die Spieluhr in den normalen Abspielmodus.
  • Bei den zwei letzten Strophen wird die Lautstärke verringert (soweit bei der Programmierung aktiviert). Nach dem Spielen schaltet sich die Spieluhr automatisch ab.
  • Während des Spielens kann die Spieluhr durch kurzes Drücken des Knopfes abgeschaltet werden.


6 Projektstatus

V0.40 etwas fehlerbereinigte Version


created with latex2web.pl v0.61 © 2006-2010 Joerg Wolfram