Die Sound-Klassen

Neben visuellen Informationen an den Benutzer sind akustische Signale in eingebetteten Systemen wichtige Mittel in der Gestaltung von Mensch-Maschine-Schnittstellen. Das Generieren von einfachen Tönen ist noch vergleichsweise trivial aber Tonfolgen und Melodien erfordern dagegen eine recht aufwendige Programmierung. Neben der Tonfrequenz ist auch die Tondauer zu realisieren und das möglichst ohne gleich zwei Timer für den Tongenerator zu blockieren. Die Klasse Sound bietet dem Anwendungsentwickler die Möglichkeit einfache Töne, Tonfolgen und Melodien zu erzeugen. Im Folgenden die UML-Darstellung der Klasse Sound:

Auffällig ist, dass die Soundklasse als abstrakt gekennzeichnet ist. Da die von einem Timer generierte Tonfrequenz an einem PWM-Kanal ausgegeben wird, sind die Möglichkeiten, wo ein Speaker angeschlossen werden kann, begrenzt. Für den beim ATmega8 verwendeten Timer1 sind das die zwei PWM-Kanäle OC1A an Port B.1 und OC1B an Port B.2. Diese beiden verfügbaren Kanäle können als vollständig vorgefertigt und konfigurierte Klassen verwendet werden. Damit ist es nicht mehr nötig, diese auf ein bestimmtes Pin zu konfigurieren.

Play it again Sam, again and again

Die Klasse Sound basiert auf der Verarbeitung einfacher Melodieskripte. Sie belegt einen PWM-fähigen 16 bit Timer. In der Regel Timer1. Es darf immer nur eine Instanz der Finalklassen SoundChanelX angelegt werden. Die Töne, Tonfolgen und die Melodie werden vollständig asynchron abgespielt. Das bedeutet, dass mit dem Aufruf der Operation play(…) dem Tongenerator nur das Tonskript als String übergeben wird und die Anwendung weiter arbeiten kann, während der Sound ausgegeben wird. Eine Ausnahme bildet die Operation morse. Diese arbeitet synchron, was heißt, die rufende Methode wartet, bis die Funktion vollständig abgearbeitet ist. Daraus folgt, dass morse nicht in Ereignishandlern (innerhalb eines Interrupt) aufgerufen werden darf, sondern immer nur aus onWork heraus.

Beispiel:
   protected: SoundChanelA speaker;	
   ...
   speaker.play( FLASHSTR("^^^/ klmnopqrstuv") );
   ...
   speaker.play( FLASHSTR("v d,e,f,g,aa,aa, h,h,h,h,aa,.. h,h,h,h,aa,.. g,g,g,g,ff,ff, a,a,a,a,dd" ));

Soundskript:
   Ton = Buchstabe, Großbuchstabe höhere Oktave
   	cdefgah = ton
   	c´ = cis (des) =w
   	e´ = es (dis)  =x
   	f´ = fis (ges) =y
   	a´ = as (gis)  =z
   	b  = (ais)
   Ton = Buchstabe, Großbuchstabe höhere Oktave
   	k...v = c...h inclusive cis,es,fis,as,b
        /  ab jetzt 1 oktave höher
	\  ab jetzt 1 oktave tiefer
	,  kurze pause 1/4 Notenlänge
	.  lange pause 1/1 Notenlänge
	Standard = 1/8-Note
	^  ab jetzt schneller 2x  (z.B. 1/16 Note)
	v  ab jetzt langsamer 1/2 (z.B. 1/4 Note)
	Leerzeichen = keine Bedeutung und keine Auswirkung

einfache Tonfolgen generieren

Als erste Sound-Übung soll das System beim Start eine akustische Meldung in Form einer aufsteigenden Tonfolge ausgeben. Zunächst die myAVR C++ Quellcodevorlage für eine einfache Anwendung mit dem Gedankenmodell in Form von Kommentaren. Übrigens kann oder besser sollte Überflüssiges aus der Vorlage entfernt werden. Um wertvollen SRAM zu sparen, soll das Soundskript im FLASH bleiben. Dazu verwenden wir das Makro FLASHSTR(„…“) welches dafür sorgt, dass der angegebene String vom Kompiler aus nicht in den SRAM kopiert wird.

// -------------------------------------------------------------
// ENTWURF Beispiel: Sound 1, gibt beim Start eine Tonfolge aus
// -------------------------------------------------------------
class Application : public Controller
{
  // Instanz der Klasse SoundChanelA anlegen
  public: void onStart()
  {
     // Sound abspielen
  }
 
  public: void onWork()
  {
     // bleibt leer
  }
 
} app; 

Danach kann die Anwendung realisiert werden. Verbinden Sie zuvor den Speaker mit Port B Bit1 (OC1A, PWM-Chanel A).

// -------------------------------------------------------------
// Beispiel: Sound 1, gibt beim Start eine Tonfolge aus
// -------------------------------------------------------------
class Application : public Controller
{
  protected: SoundChanelA speaker; // Instanz der Klasse SoundChanelA anlegen
 
  public: void onStart()
  {
     // Sound abspielen
     speaker.play(FLASHSTR("abcdefg"));
  }
 
  public: void onWork()
  {
     // bleibt leer
  }
 
} app; 

Melodien generieren

In der nächsten Übung sollen die Möglichkeiten der Klasse Button mit der der Klasse Sound gemeinsam genutzt werden. Die Aufgabe besteht darin, eine kleine Melodie auf Knopfdruck abzuspielen. Zunächst der Entwurf.

// -----------------------------------------------------------------
// Entwurf Beispiel: Sound 2
// -----------------------------------------------------------------
class Application : public Controller
{
  // Instanzen für Speaker und Button
 
  public: void onStart()
  {
     // Initialisierung    
  }
 
  public: void onWork()
  {
     // bleibt leer
  }
 
  public: void onEvent(const Object& sender, uint8 data)
  {
     // WENN sender == button
     // und WENN data == Click
     // DANN spiele mir das Lied
  }
 
} app; 

Danach erst, wie gehabt, die Realisierung.

// -----------------------------------------------------------------
// Beispiel: Sound 2
// -----------------------------------------------------------------
class Application : public Controller
{
  protected: SoundChanelA speaker;
  protected: Button button;
 
  public: void onStart()
  {
     // Initialisierung    
     button.config(portD,bit2);
  }
 
  public: void onWork()
  {
     // bleibt leer
  }
 
  public: void onEvent(const Object& sender, uint8 data)
  {
    if (sender==button)
    {
      if (data==Button::Click)
      {
         speaker.play(FLASHSTR("vfff,,fff,,fff,,dd,a,ff.dd,a,ffff..CCC,,CCC,,CCC,,DD,C,ff.dd,a,ffff"));
      }
    }
  }
 
} app; 

Übersetzen, übertragen und testen Sie das Programm… Und, Soundtrack erkannt?

Videozusammenfassung

Und weil es so schön war hier das Ganze noch mal als Video.

Nächstes Thema