Musikalischer ATTiny

Für ein Projekt habe ich eine kleinen Treiber geschrieben, um Musik abzuspielen. Eigentlich sollte es schnell gehen und ich wollte auf etwas fertiges zurückgreifen, aber es stellte sich heraus, dass keine ordentlichen Treiber existieren oder ich nur keine gefunden habe. Die die ich finden konnte hatten keine Optionen, um verschiedene Songs in den Flash abzulegen. Die üblichen Arduino Libs blockierten auch mit Delays den ganzen Prozessor.

Der Anspruch war, dass damit auch Töne auf einem ATTiny25 abgespielt werden können. Also der Flash- und RAM-Verbrauch minimal sein muss.

Da ich nicht besonders musikalisch bin, musste ich erst einmal lernen, wie das mit den Noten und den Pausen überhaupt funktioniert. Da die Lieder leider nur mit Noten vorliegen und nicht in PWM und Wartezeiten, kommt man da leider nicht drum herum. Eine schöne Seite die mir alles schnell erklären konnte findet ihr hier: http://www.musikzeit.info/theorie/

Zum Treiber:

  • zwei Oktaven wählbar (24 Noten)
  • Oktaven-Offset pro Song wählbar
  • Interruptbetrieb, Song läuft automatisch (non-blocking) bis zum Ende
  • Timer bei der dem Overflow-Wert gesetzt werden kann ist nötig (Timer1 bei ATTiny25)
  • eine Note/Pause nur 2 Byte Flash
  • auf 1 MHz optimiert

Wie wird es verwendet?

Anhand von Noten kann ein neuer Song implementiert werden. Dabei wird nur die Note und die Wartezeit in einem Array notiert. Gibt es eine Pause wird einfach die spezielle Note „PAUSE“ hinzugefügt. Für ein Lied das mit einer Note startet und danach eine Pause folgt, würde dies so aussehen:

const uint8_t PROGMEM fuer_elise[] = {
    //note //wait
    //first row
    E2, WAIT_8,
    PAUSE, WAIT_8,

Ist der Song komplett, muss er zur Songliste hinzugefügt werden. Diese liegt aktuell noch im RAM, weil dafür Platz war. Sollte es dort zu eng werden, kann diese natürlich ebenfalls in den Flash gelegt werden.

const uint16_t songlist[] = {
    // pointer to the near flash    //length of track    //play in dur 1 - 6 (1 MHz optimized)
    (uint16_t)fuer_elise,   sizeof(fuer_elise), 3,

Der Aufruf des Songs aus der main loop sieht so aus:

// Play a songe
 playTune(1); //first song

Bevor der Song nicht bis zum Ende abgespielt wurde, kann mit playTune kein Neuer begonnen werden. Es ist aber möglich mit playTune(0); Den aktuellen Song zu stoppen. Natürlich funktioniert das nur im Interrupt-Modus. Per define kann der Treiber auf den blockierenden-Modus umgeschalten werden. Das hat aber eher einen akademischen Hintergrund und keine anderen Vorteile, außer einen zweiten Weg aufzuzeigen.

Wie funktioniert es?

Eigentlich recht einfach… Das versetzen des Timerendwertes verändert die Tonhöhe. Damit ein Duty-Cycle von 50% entsteht, wird immer der entsprechende Compare-Wert hälftig gesetzt. In den Pausen ist der Timer aktiv, aber der Ausgangspin wird zu low gesetzt. Bevor eine neue Note im Interrupt gesetzt wird, wird die Haltezeit errechnet. Da bei jeder Note der Timer unterschiedlich schnell überläuft, muss die Haltezeit entsprechend angepasst werden:

duration = ((uint32_t)pgm_read_word(&waits[pgm_read_byte(((uint8_t*)songlist[play_song-2]) + i)])) * 32 / tone * (1 << ((songlist[play_song] & 0x07) - 1));

Und jetzt in verständlich:

Wartezeit = Wartezeit der Note * 32 (Wartezeit aufweiten = numerische Fehler reduzieren) / Note (= Overflowwert des Timers) * Oktave des Songs (Timer Vorteiler)

Die Formel führt zu einheitlichen Pausenzeiten, obwohl die Periode des Timers stark schwankt. Damit die Noten nicht direkt aneinander gereiht werden, wird zwischen jeder Note noch eine kleine Pause vom Treiber eingefügt.

Ich habe einen anderen Prozessor oder Takt, was ist zu tun?

Eine Portierung auf einen anderen Mikrocontroller sollte kein Problem darstellen. Angepasst werden müssen natürlich die timerspezifischen Aufrufe und die Interruptroutine.

Wird mit einem anderen Prozessortakt gearbeitet, müssen die Wartezeiten und die Oktaventeiler angepasst werden. Die Library ist auf 1 MHz optimiert. Wird der Tiny beispielsweise mit 8 MHz betrieben, sind folgende Einstellungen nötig:

#define DELAY_NOTE_INTERRUPT 40

wird zu

#define DELAY_NOTE_INTERRUPT 5

Außerdem sollte die Oktave des Songs um den Wert drei tiefer gespielt werden. Evtl. muss dazu die maximale Oktavenanzahl von 8 auf 16 erweitert werden.

Quellcode wie immer auf github:

https://github.com/Counterfeiter/ATTiny_Song_Driver

 

Advertisements
Dieser Beitrag wurde unter Allgemein veröffentlicht. Setze ein Lesezeichen auf den Permalink.

2 Antworten zu Musikalischer ATTiny

  1. Stefan Schreiter schreibt:

    Cool schöne Sache 🙂

  2. Pingback: Weihnachtslieder zum Fest | Electronic Stuff

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s