Freitag, 1. Juli 2011

A small and proprietary AVR C-Library to play Wavefiles...

Projekte bei denen Wavedateien abgespielt werden gibt es viele. Bei den meisten ist die dazu implementierte Funktionalität jedoch ein fester Bestandteil der Firmware und dadurch nur oft schwer zu verstehen und oft auch nicht gerade einfach in seine eigenes Projekt zu übernehmen. Zudem ist je nach verwendeten Mikrocontroller die Ausgabemöglichkeit der Audiodaten unterschiedlich, was aber unter Umständen nicht auf den ersten Blick ersichtlich ist. Die erste Alpha-Version der hier vorgestellten Library, welche auf GitHub verfügbar ist, besitzt im Moment die im folgenden beschriebenen Eigenschaften.

Eigenschaften der Library:
  1. Die Library läuft momentan nur auf AVR-Controllern der Mega-Reihe (getestet mit ATMega328p).
  2. Es werden nur Wave-Daten unterstützt die im Flash oder EEPROM abgelegt sind (muss per Preprozessor-Einstellung festgelegt werden).
  3. Die Wave-Daten müssen im PCM-Format (8Bit, Mono, 8kHz Sampling-Rate) und ohne den 44Byte Header im Flash oder EEPPROM abgelegt worden sein.
  4. Die Daten werden per Pulse Width Modulation (8Bit) an den Pins PD5 und PD6 ausgegeben. Die Basisfrequenz liegt hier bei 62,5 kHz.
  5. Timer0 wird dazu verwendet das PWM-Signal (Fast PWM Modus) zu generieren.
  6. Timer2 wird dazu verwendet die Wave-Daten Byte-weise, im Takt der Sampling-Rate, an die beiden Output Compare Register (A/B) des Timer0 zu übergeben. 
Funktionen der Library:
 
Die Library besitzt zudem Funktionen mit denen es möglich ist, die Hard- und Software zu initialisieren und den Abspielvorgang zu starten, stoppen und pausieren. Wenn die Preprozessor-Einstellung für Flash angegeben wurde, kann ein im Repository liegendes Shellscript im tools Verzeichnis dazu verwendet die benötigte C-Datei für die Firmware zu erzeugen (es wird SoX benötigt). Zu beachten ist dabei, dass aufgrund des in C intern verwendeten Datentyps (16Bit signed integer) für den Index von Arrays, die Maximale Anzahl von Samples in der Wave-Datei den maximal möglichen Index von 32767 nicht überschreiten darf. 

Ablauf der Ausgabe:

Der Vorgang zum abspielen gestaltet sich wie folgt. Der Timer0 (8Bit) wird so eingestellt, dass dieser mit einen Vorteiler von 1 (voller CPU-Takt) im nicht invertierenden Fast PWM-Modus läuft und der TOP-Wert für den Zähler des Timers ist dabei der Wert 0xff. Der Counter des Timers läuft also immer von 0 bis 255, was eine Frequenz von 62,5kHz, ergibt.
Ist bei einem Zählvorgang der TOP-Wert erreicht bzw. überschritten, findet ein Überlauf statt, der veranlasst, dass beiden Pins PD5/6 auf High gesetzt werden. Der Counter fängt wieder bei 0 an hoch zu zählen.
Auf Low werden die beiden Pins gesetzt wenn jeweils ein Vergleichswert zum aktuellen Counter-Wert erreicht wird. Diese beiden verwendeten Vergleichswerte sind im Fall des Timer0 die Register OCR0A/B. Durch diesen Vergleichswert kann also die Zeit in 256 Schritten festgelegt werden, wie lange das PWM-Signal, innerhalb einer Periode, auf High oder Low gesetzt ist.

Der zweite Timer (Timer2) ist auch ein 8Bit Timer, der jedoch nich im PWM-Modus läuft. Er wurde so eingestellt, dass er mit einem Prescaler von 8 auf 2MHz läuft und bei einem Vergleichswert von 250 (eingestellt über das Register OCR2A, was exakte 8kHz ergeben und somit die Unterstützte Samplingrate der Wave-Daten von 8kHz entspricht) einen Interrupt auslöst der eine Interruptroutine aufruft. Gleichzeitig wird der Counter des Timer wieder auf 0 gesetzt, der darauf wieder hoch zählt. In der eben genannten Interrupt-Routine wird je nach Konfiguration der Lib, ein Byte nach dem anderen, entweder aus den Wave-Daten aus dem Flash oder EEPROM nachgeladen und in die beiden oben genannten Vergleichsregister OCR0A/B geschrieben. Dies führt dazu, das der Timer0 nachdem nächsten Überlauf, durch erreichen des Counter-Wertes 0x00, das Tastverhältnis direkt ausgibt.

Dieser Wechsel des PWM-Verhältnisses, im Takt der Sampling-Rate, ergibt eine Veränderung der mittleren Ausgangsspannung des PWM-Signals und ist damit (wenn auch recht kantigen) das wiederhergestellte Ausgangssignal aus dem die PCM-Kodierte Wavedatei erzeugt wurde. Dies liegt daran, da bei der Pulse Code Modulation, ein Audiosignal mit einer festen Sampling-Rate (in unserem Fall 8kHz) abgetastet wird. Die Abtasten bedeutet schlicht, dass in einem festgelegten Takt der momentan anliegende Spannungswert an der Datenleitung (in unserem Fall 8Bit-Wert breit) abgefragt wird. Es ergibt sich also eine folge von Werten, aus denen das erfasste Signal wieder rekonstruiert werden kann.

Das ganze oben beschriebene Vorgang ist natürlich nur recht kurz und simpel beschrieben. Er soll lediglich nur einen Überblick über den Vorgang geben. Fehler und Ungenauigkeiten können also enthalten sein und ich bin über jede Anregung oder Korrektur dankbar.

Verwendete Hardware:

Als Hardwarebasis zum testen verwende ich ein Minimexle der Version 3, bei dem ich ein anderes Quarz (16MHz statt 18,432MHz) und einen anderen AVR (ATMega328p statt einem ATMega88) verwende. Durch den AVR mit größerem Flash-Speicher (32kB) sind die weiteren Möglichkeiten in der Firmware wesentlich weniger eingeschränkt als beim Mega88. Zudem besitzt das Minimexle ein Display mehrere Taster und einen Summer. So kann hier die Funktionalität der Lib durch ein simples Benutzerinterface angeboten und auch ohne angeschlossene Kopfhörer etc. die Wave-Daten abgespielt werden können.

Und dadurch, dass beim Minimexle jeder der beiden verwendeten PWM-Kanäle mit einem RC-Tiefpass (erster Ordnung) ausgestattet ist, bevor diese an 3,5" Klinken-Buchsen ausgeführt werden, können einfache Lautsprecher, wie man sie aus PC-Gehäusen als PC-Speaker kennt, oder sehr einfache Kopfhörer sollten ohne Probleme an diese Buchse angeschlossen werden.

Warnhinweis:

Von der Verwendung von aktiven Kopfhörern, Stereoanlagen, Verstärkern oder ähnlichem rate ich jedoch aufgrund der Signaleigenschaften des PWM-Signals dringend ab. Es versteht sich von selbst, dass ich keinerlei Verantwortung für Schäden jeder Art durch Verwendung dieser Software übernehme.

Ausblick:

Für die Zukunft ist angedacht, den Header der Wave-Dateien zur Konfiguration der Timer heranzuziehen, weitere Sampling-Raten zu unterstützen und die Anzahl der verwendeten Timer auf einen zu reduzieren. Zudem soll der zu verwendende Timer auswählbar sein und falls die PWM-Pins, dieses Timers, anderweitig verwendet wurden, ein Software PWM-Modus verfügbar sein der für beliebige Pins, an einem beliebigen Port, betrieben werden kann, usw...

Weitere Links:


Keine Kommentare:

Kommentar veröffentlichen

UpConverter fixed

Vor einiger Zeit berichtete ich darüber das mein Versuch einen UpConverter für mein rad1o zu bauen leider fehlgeschlagen ist. Es lag an der...