Program the Arduino Micro with Atmel Studio + AVRISP mkII

Introduction
Probably you have the same problem like me: Some days ago i have bought a tiny Arduino Micro board to play a little bit around with them. I installed the arduino IDE (currently 1.0.5) under Windows7 (64bit). Then i have tried to install the Windows USB driver which are delivered with the arduino IDE. Finally after a lot of attempts the driver were successful installed, but evertime after i’ve connected the PC with the Arduino board (by micro USB cable), the virtual Arduino COM port appeared for a short moment in the hardware manager and after few seconds the COM port disappeares and shows instead an ‚unknown device‘.

To save time and nerves, i decided to use the AVRISP mkII programmer – a device which is able to program AVR devices over the SPI interface. This device has also a lot of other features. In my opinion a small, powerful and not really expensive device.

So i tried to transfer a small test program with help of the AVRISP and the arduino IDE to my Arduino. But everytime i’ve got the error message „avrdude: usbdev_open() did not find any usb device usb“. Because i didn’t want to invest too much time into get the avrdude running i decided eventually to use the Atmel Studio with the AVRISP.

As following i will explain you how to deal with Atmel Studio and the AVRISP mkII ISP programmer to transfer programs to the Arduinos flash. By the way each Arduino board (with an ISP connector) should be programmable in this way. This is not restricted to the Arduino Micro.

Prerequesites

  • a installed Atmel Studio (current version: 6.1.x)
  • an AVRISP mkII programmer device
  • a micro USB cable (connected to a power source)

Workflow
1. Connect the micro USB cable with your Arduino board. Then connect your AVRISP with your PC and the board:
arduino_micro_musb_avrispmkii

2. Start the Atmel Studio software and open the menu „Tools“ -> „Device Programming“:
as_menu_devprog

3. Choose for Tool the „AVRISP mkII“, for the device „ATmega32U4 and for interface „ISP“ and press „Apply“:
as_devprog_apply

4. After configuration of the device we want to read the device information. Click on the „Device information“ bar on the left side and press the „Read“ button:
as_devprog_read

5. If the software could read your device information you are ready to program the Arduino! But before please read how to backup the Arduino bootloader.
as_devprog_read_done

Attention: Backup your Arduino Bootloader
If you want to program your Arduino with the Atmel Studio you will overwrite the Arduino bootloader. Therefore if you need the bootloader later, you can save them (before the first programming) or download them from the Arduino site.

To save the default bootloader open the menu „Tools“ and open again the entry „Device Programming“. Choose as described some sentences above your device, click on „Apply“ and then on „Memories“. Now you can use „Read:“ to write the Flash (or EEPROM) content of the ATmega into a file:

as_devprog_read_flash

Links

Have fun!

Advertisements
Tagged with: , , , , ,
Veröffentlicht in Arduino

Digitale Signale visualisieren

Einleitung

In diesen Artikel werde ich mich damit beschäftigen, wie Geräusche (z.B. Musikstücke, Maschinen und ähnliches) digital analysiert und das Ergebnis visualisiert werden kann.

Der Artikel soll Grundlagen erläutern, die notwendig sind, um diese Geräusche so zu transformieren, dass die für uns interessanten Eigenschaften sichtbar werden. Diese Art der Transformation zählt zu dem Gebiet der Digitalen Signalverarbeitung (im Englischen: Digital Signal Processing – DSP) und wird noch näher erläutert.

Die so gewonnenen Eigenschaften werden dann zum Ende des Artikels mit Hilfe eines Beispielprogrammes grafisch als einfacher Frequenzmonitor dargestellt. Zum Beispiel stellen bekannte Mediaplayer wie Winamp (und andere) so einen Monitor dar, um dem Hörer ein visuelles Feedback des aktuell laufenden Songs zu geben.

Einfacher Equalizermonitor von Winamp

Einfacher Equalizermonitor von Winamp

Diesem Beispielprogramm kann eine WAV oder OGG/Vorbis-Datei übergeben werden und als Resultat wird eine Videodatei (AVI) erzeugt, welche synchron zu dem Inhalt der Eingabedatei ausgewählte Frequenzen darstellt.

Das Beispielprogramm aus dem Artikel in Aktion

Das Beispielprogramm aus dem Artikel in Aktion

Die digitale Signalverarbeitung

Eine kurze Einführung

Im Folgenden werde ich die für das Beispielprogramm notwendigen Grundlagen erläutern. Dabei werde ich dies immer aus Sicht des Anwenders und nicht des Mathematikers darstellen. Für eine detaillierte Einführung in das Thema digitale Signalverarbeitung empfehle ich das Buch „Understanding Digital Signal Processing“. Das Buch stellt auf der einen Seite den praktischen Teil der Anwendung dar, lässt aber nicht die mathematischen Grundlagen außer Acht.

Am Ende des Artikels ist eine Liste von Offline- und Onlinequellen zu dem Thema zu finden.

Abtastfrequenz

Um ein analoges Signal (digital) verarbeiten zu können, muss es von seiner analogen Form in die digitale, diskrete Form überführt werden. Dies geschieht durch Abtasten des analogen Signales in einem vordefinierten festen Zeitabstand und der Umwandlung in diskrete Werte eines vordefinierten Amplitudenbereiches. Weil so ein diskretes Signal im Gegensatz zu dem analogen Original nur ein endlichen Wertebereich hat, kann es nur eine Annäherung an das analoge Vorbild sein.

Dieser zeitliche Abstand zwischen zwei Abtastzeitpunkten des analogen Signals wird als Abtastintervall T bezeichnet. Er bestimmt die sogenannte Abtastfrequenz fA, welche zum Beispiel in Hz, kHz oder MHz angegeben wird. Beträgt die Abtastfrequenz des analogen Signals zum Beispiel 22050 Hz, so errechnet sich das Abtastintervall wie folgt:

equation_01_t_1_div_fa_22050

Als Resultat dieser Abtastung erhält man eine Folge von 22050 Werten pro Sekunde. Ein Signal wird als Sequenz einer Folge von Signalwerten im Intervall [0,n] wie folgt beschreiben:

equation_02_sequence_0_to_n_real

Ein digitales Signal bestehend aus acht Werten könnte demnach wie folgt aussehen:

x(n) = {0.224, 0.555, 0.75, 0.8, 0.75, 0.555, 0.224, -0.224}

Diskrete Fourier Transformation (DFT)

Um das digitale Signal analysieren zu können, benötigen wir die Hilfe der diskreten Fourier Transformation (DFT). Die DFT ist wohl mit eine der am meisten benutzen Methoden in der digitalen Signalverarbeitung. Mit ihrer Hilfe kann eine Signalfolge in ihren Frequenzbereich analysiert, aber auch verändert werden. Ich werde mich aber in diesem Artikel auf die Analyse im Frequenzbereich beschränken.

Die DFT wird benutzt, um die in einer diskreten Signalsequenz enthaltene Frequenzen zu ermitteln und abzubilden. In ihrer Expotentialform stellt sich die DFT Gleichung wie folgt dar:

equation_05_dft_expotential_form

Wobei:

  • x(n) = die diskrete Signalsequenz ist
  • X(m) = die diskrete Sequenz der DFT im Frequenzbereich ist
  • N = die Anzahl der Werte der Ein- und Ausgabe der DFT ist

Wie genau sieht das Ergebnis der DFT nun aber aus? Einfach gesagt, stellt jeder Wert von X(m) den Betrag und die Phase einer bestimmten Frequenz dar. Für welche Frequenz genau der Index m steht, wird durch die Abtastfrequenz des Eingangssignals und die Breite N der DFT bestimmt. Beträgt zum Beispiel die Abtastfrequenz 22050 Hz und die Breite der DFT N=256 Elemente, so errechnet sich die Frequenz des zehnten (n=9) Wertes aus X(m) wie folgt:

equation_06_dft_freq_bin_index9

Der zehnte Wert der Ausgabe der DFT stellt also den Betrag und die Phase der Frequenz von 775,2 Hz dar.

Der Betrag und der Phasenwinkel in X(m) werden als komplexe Zahl dargestellt:

equation_07_Xm_real_imag

Der Betrag einer komplexen Zahl wird wie folgt berechnet:

equation_08_dft_magnitude

Der Phasenwinkel einer komplexen Zahl wird wie folgt berechnet:

equation_09_dft_phase_angle

Eigentlich würde es schon reichen, die Beträge der jeweiligen Elemente der DFT-Ausgabe zu berechnen und für den jeweiligen Elementindex dessen Frequenz zu ermitteln. Die daraus resultierenden Werte sind das Frequenzspektrum des Eingangssignals.

Dennoch gibt es einige besondere Eigenschaften einer DFT, welche bei der Interpretation des Frequenzspektrums beachtet werden müssen.

Symmetrie

Eine DFT kann Eingangswerte aus dem reellen Zahlenbereich oder auch komplexe Zahlen entgegennehmen. In diesem Artikel benutzen wir diskrete Signalsequenzen aus dem reellen Zahlenbereich. Als Konsequenz daraus, bilden die Ausgangswerte der DFT eine Symmetrie in der Form, dass Werte ab dem Index m=1 bis m=(N/2) – 1 für m > (N/2) gespiegelt werden. Da diese gespiegelten Werte keine neuen Informationen über das Frequenzspektrum liefern, werden diese in den meisten Fällen weggelassen. Deshalb liefert FFTW beim Aufruf der DFT als Resultat nur die Werte der Indizes 0 bis (N/2) + 1 zurück.

Das folgende Beispiel zeigt die DFT eines Signals, welches aus einer 50 Hz Sinuswelle (Amplitude = 1,0) und einer 70 Hz Sinuswelle (Amplitude = 0,25) besteht und mit 200 Hz abgetastet wird:

Beispiel für Symmetrie des Resultats einer DFT

Beispiel für Symmetrie des Resultats einer DFT

Deutlich ist die horizontale Symmetrie der X-Achse über der Mitte (N / 2 + 1) des Bildes erkennbar. Die Frequenz von 50 Hz wurde auf 150 Hz und die Frequenz von 70 Hz auf 130 Hz gespiegelt.

Dieses Verhalten hat aber noch einen weiteren Effekt: Hat zum Beispiel eine DFT N=512 reelle Eingangswerte, so sind tatsächlich nur (N / 2) + 1 = (512 / 2) + 1 = 257 Werte für die Analyse verwertbar. Wurde das Eingangssignal beispielsweise mit 22050 Hz abgetastet, so liegt das Frequenzspektrum effektiv nicht von 0 bis 22050 Hz. Stattdessen halbiert sich das Frequenzspektrum auf nur noch 0 bis:

equation_11_effective_frequency_bandwidth

Abtasttheorem

Das Abtasttheorem, auch als Nyquist-Shannon-Abtasttheorem oder Nyquist-Theorem, bekannt beschreibt eine wichtige Eigenschaft in der Signalverarbeitung. Es besagt, dass die Abtastfrequenz eines analogen Signals mehr als doppelt so groß sein muss, als die höchste im Signal enthaltene Frequenz:

equation_10_nyquist_theorem

Nur wenn Abtastfrequenz mehr als doppelt so groß ist, ist sichergestellt, dass dieses Signal vollständig abgetastet und auch wieder rekonstruiert werden kann. Ist die Abtastfrequenz zu gering, führt dies dazu, dass bestimmte Frequenzen sich gegenseitig überdecken und damit nicht mehr erkennbar sind. Dieser Effekt wird auch Aliasing genannt.

Sollen zum Beispiel aus einem Signal die Frequenzen von 0 bis 8 kHz per DFT analysiert werden, so sollte das analoge Signal mit einer Frequenz von z.B.

8 kHz * 2.5 = 20 kHz

abgetastet werden.

Zum Vergleich wurde für die beiden folgenden Bilder dasselbe Eingangssignal erzeugt. Das Signal besteht aus drei Sinuswellen aus je 10, 20 und 50 Hz. Im ersten Beispiel erfolgte eine Abtastung mit 180 Hz (180 Hz > 2 * 50 Hz) und im zweiten Beispiel eine Abtastung mit nur 50 Hz (70 Hz < 2 * 50 Hz).

Frequenzspektrum dreier Sinuswellen (10 Hz, 20 Hz, 50 Hz, alle mit Amplitude = 1,0), Abtastfrequenz = 180 Hz

Frequenzspektrum dreier Sinuswellen (10 Hz, 20 Hz, 50 Hz, alle mit Amplitude = 1,0), Abtastfrequenz = 180 Hz

Frequenzspektrum dreier Sinuswellen (10 Hz, 20 Hz, 50 Hz, alle mit Amplitude = 1,0), Abtastfrequenz = 70 Hz

Frequenzspektrum dreier Sinuswellen (10 Hz, 20 Hz, 50 Hz, alle mit Amplitude = 1,0), Abtastfrequenz = 70 Hz

Im dem Beispiel mit der zu geringen Abtastfrequenz von 70 Hz, ist nur noch die Frequenz von 10 Hz erkennbar. Die anderen beiden Frequenzen (20 Hz, 50 Hz) sind verschwunden. Die gut erkennbare Veränderung des absoluten Betrages des 10 Hz Signals im Gegensatz zu dem korrekt abgetasteten Signal kommt dadurch zustande, weil sich die Abtastfrequenz und damit die Anzahl der Eingangswerte verändert haben.

Leck Effekt

Wie in dem Artikel schon erwähnt wurde, ist für jeden Index der Ausgabewerte der DFT festgelegt, welcher Frequenzanteil in ihm enthalten ist. Hat die DFT einen Breite von 512 Elementen und wurde das Eingangssignal mit 8000 Hz abgetastet, so enthält jeder Index n den Frequenzanteil eines Vielfachen von f_n = 8000 Hz / 512 = 15,625 Hz.

Liegt nun ein Frequenzanteil in dem Signal nicht genau auf einem Vielfachen von f_n (was sehr wahrscheinlich ist), so kommt ein unerwünschter Effekt zum Tragen. Denn in diesem Fall werden Anteile der tatsächlichen Frequenz an Frequenzen in deren Umgebung addiert. Daraus resultiert auch eine Abschwächung des Frequenzanteiles. Dieses Phänomen ist in bei der Anwendung der DFT ein großes Problem.

10 Hz (blau) und 10,4 Hz (grün) Signal mit 100 Hz abgetastet

10 Hz und 10,4 Hz Signal mit 100 Hz abgetastet

Als Beispiel habe ich ein 10 und 10,4 Hz Signal mit 100 Hz abgetastet. Die Frequenzen sind in dem Diagramm grün und blau dargestellt. Die DFT hat eine Breite von 100 Elementen, somit liegt jeder Index auf einem Vielfachen von 1 Hz. Sehr gut erkennbar ist, dass das grüne Signal (10,4 Hz) seine Frequenzanteile an dessen Umgebung verteilt und dadurch dessen Betrag verringert wird. Durch diese neu entstandenen (Pseudo)-Frequenzen, wird das tatsächliche Signal verfälscht.

Diesen Effekt versucht man in der digitalen Signalverarbeitung mit Anwendung von sogenannten Fensterfunktionen (Windows) abzuschwächen.

Fensterfunktionen

Um die unerwünschten Leckeffekte abzuschwächen, wird auf dem Eingangssignal vor Durchführung der DFT, jeweils eine Fensterfunktion angewandt. Dabei werden die Amplituden des Eingangssignal verändert, um die Leckeffekte zu minimieren.

Es gibt verschiedene Fensterfunktionen – die bekanntesten dürften aber die Rechteck-, die Dreieck- die Hamming- und die Hanning-Fensterfunktion sein. Für den Softwareequalizer verwende ich die letztere Fensterfunktion.

Als Beispiel soll eine 10 Hz Frequenz in einem Eingangssignal dienen. Die DFT soll 200 Elemente breit sein.

10 Hz Eingangssignal

10 Hz Eingangssignal

Dasselbe Eingangssignal nach Anwendung der Hanning-Fensterfunktion

Dasselbe Eingangssignal nach Anwendung der Hanning-Fensterfunktion

Das erste Diagramm zeigt das Original-Eingangssignal – im zweiten Diagramm wurde die Hanning-Fensterfunktion auf die 200 Elemente angewandt.

In dem nächsten Diagramm wurde die Hanning-Fensterfunktion auf das Eingangssignal aus dem Abschnitt über die Leckeffekte angewandt. Das Eingangssignal enthält Frequenzanteile von 10 Hz und 10,4 Hz. In dem Diagramm ist der grüne Verlauf die 10,4 Hz Frequenz, welche ohne Fensterfunktion Leckeffekte verursacht. Der blaue Verlauf wurde mit der Hanning-Fensterfunktion erzeugt und unterbindet das Abgeben von Frequenzanteilen an die Umgebung von 10,4 Hz. Wiederum ist eine weitere Abschwächung der 10,4 Hz Frequenz durch die Fensterfunktion zu erkennen. Durch Anwendung einiger Fensterfunktionen (z.B. Hanning, Hamming), werden die Frequenzamplituden abgeschwächt, was nach der DFT zu verminderten Frequenzbeträgen führt.

Hanning-Fensterfunktion auf 10,4 Hz Signal angewandt

Hanning-Fensterfunktion auf 10,4 Hz Signal angewandt (grün = Leck Effekt, rot = abgeschwächter Leck Effekt)

FFT – die schnelle Version der DFT

Die DFT in ihrer am Anfang des Artikels dargestellten Form wäre zu langsam um damit in Echtzeit das Frequenzspektrum eines Signals analysieren und auch modifizieren zu können. Aus diesem Grund wird in der Praxis eine modifizierte Version der DFT angewandt, welche als schnelle Fourier Transformation (FFT) bezeichnet wird. In der Praxis gibt es mehrere Versionen der FFT.
Genaue Beschreibungen der einzelnen Umsetzungen (z.B. Radix-4) gibt es in der einschlägigen Literatur und füllt dort teilweise mehrere Kapitel. Ich wollte hier nur einen kurzen Hinweis auf das Thema geben.

weiterführende Links
http://de.wikipedia.org/wiki/Diskrete_Fourier-Transformation
http://de.wikipedia.org/wiki/Abtasttheorem
http://de.wikipedia.org/wiki/Leck-Effekt
http://de.wikipedia.org/wiki/Fensterfunktion
http://de.wikipedia.org/wiki/Schnelle_Fourier-Transformation

Beispiel: Der Frequenzmonitor

Wie schon zum Anfang des Artikels erläutert, habe ich für die Umsetzung des Frequenzmonitors mehrere 3rd-Party-Bibliotheken verwendet, welche im Folgenden näher erklärt werden.

FFTW („Fastest Fourier Transform in the West“)

Das Herzstück des Monitors ist die DFT. Auch wenn die Umsetzung einer DFT als Programmcode nicht so schwierig erscheint, kostet es sehr viel Zeit, diesen Code fehlerfrei, speichersparend und vor allem schnell zu machen. Die Bibliothek FFTW stellt unter anderen genau diese DFT mit den angesprochenen Anforderungen zur Verfügung. Sie wurde am MIT von Matteo Frigo entwickelt und steht unter GNU General Public License (GPL).

Die Bibliothek bietet eine einfach zu verwendende C-API, welche bei Bedarf aber auch angepasst werden kann. FFTW unterteilt die Schnittstellen in Basic, Advanced und Guru Interfaces. Weiterhin bietet FFTW die Möglichkeit, die DFT während der Laufzeit auf maximale Performanz zu trimmen. Es ist aber auch möglich, stattdessen die Verarbeitung auf eine geringe Speicherverwendung zu optimieren. Dies geht dann aber mit einem Performanzverlust einher.

FFTW kann unter Ubuntu mit folgendem Befehl installiert werden:

$ sudo apt-get install libfftw3-dev

Die Headerdateien sind unter Ubuntu im Verzeichnis

/usr/include

und die Libraries im Verzeichnis

/usr/lib

zu finden.

Link: http://www.fftw.org/

libsndfile

Um Sounddateien einlesen zu können, verwende ich die libsndfile-Bibliothek. Die Bibliothek kann viele Soundformate wie WAV, AIFF, VOC, FLAC und OGG/Vorbis einlesen. Sie bietet zwar keine Funktionalität um MP3-Dateien einzulesen, dies ist für diesen Artikel aber auch nicht notwendig. Im Notfall können über Audiocity MP3-Dateien nach OGG/Vorbis konvertiert werden.

Das OGG/Vorbis-Format sollte ohnehin MP3 vorgezogen werden, weil es eine bessere Soundqualität bietet und geringe Dateigröße hat. Für Signale von kurzer Dauer bieten sich WAV-Dateien, für längere Signale eher OGG/Vorbis-Dateien an.

Ähnlich wie FFTW bietet libsndfile eine einfach C-API mit verschiedenen Abstufungen im Detailgrad an. Auf der untersten Ebene erlaubt libsndfile das Lesen einzelner Bytes. Auf einer abstrakteren Ebene ist das Lesen von Frames möglich. Die Bibliothek passt die eingelesenen Daten auch automatisch auf den gewünschten Datentyp (short, int, float, double) an.

libsndfile bietet eine Lizensierung unter der GNU Lesser General Public License (LGPL) in Version 2.1 und Version 3 an.

LIBSNDFILE kann unter Ubuntu mit folgendem Befehl installiert werden:

$ sudo apt-get install libsndfile1-dev

Die Headerdateien sind unter Ubuntu im Verzeichnis

/usr/include

und die Libraries im Verzeichnis

/usr/lib

zu finden.

Link: http://www.mega-nerd.com/libsndfile/

OpenCV (Open Source Computer Vision Library)

OpenCV (OCV) ist eine umfassende Bibliothek um Probleme aus den Bereichen der Bildverarbeitung, Maschinenlernen, der linearen Algebra und vielen mehr lösen zu können. Die Bibliothek wird auch im Bereich der Robotik eingesetzt, um Mustererkennung durchzuführen. OCV ist in C/C++ geschrieben und auf Echtzeit-Anwendungen hin optimiert. Sie kann in C, C++, Java und Python eingebunden werden und unterstützt Linux, Windows, Mac und sogar Android.

Im Gegensatz zu FFTW und libsndfile ist OCV eine recht große API mit vielen Modulen, welche teilweise nur C oder C++-Schnittstellen oder beide anbietet. Sie ist also schwergewichtiger, kann aber durch weglassen von nicht benötigten Modulen zugeschnitten werden.

In diesem Artikel werde ich OCV verwenden, um die grafische Darstellung der Frequenzen als Bildabfolge zu erzeugen und in einer Videodatei zu speichern. OCV bietet primitive Operationen zum Zeichnen von gefüllten Polygonen an (mehr braucht es für den Artikel nicht) und kann AVI-Dateien erzeugen. Es mag andere geeignete Bibliotheken geben, die diese Funktionalitäten bieten, aber da ich mich viel OCV beschäftigt habe, fiel die Wahl auf diese Bibliothek.

OpenCV wird unter BSD lizensiert.

Die Installation von OCV ist leider nicht trivial und bietet viele Möglichkeiten der Anpassung. Auch kann OCV je nach Konfiguration von vielen anderen Bibliotheken abhängen. Für das Beispielprogramm ist FFMPEG Voraussetzung, damit die Videodatei erzeugt werden kann. Für eine detaillierte Installationsanweisung kann ich diese beiden Wiki-Links empfehlen: InstallGuide – OpenCV Wiki, FFMPEG – OpenCV Wiki.

Link: http://opencv.org

Sourcecode

Der Sourcecode ist auf der Plattform sourceforge.net gehostet und kann einfach als tar.gz-Archiv heruntergeladen werden.

Kompiliert wird das kleine Projekt durch Aufruf des make-Kommandos. Vorher müssen aber die Pfade zu den Header-Dateien von FFTW, libsndfile und OpenCV in der Datei makefile in Zeile 3 angepasst werden.

Ein Beispiel:

INCFLAGS=-I../OpenCV-2.4.3/modules/core/include -I../OpenCV-2.4.3/modules/highgui/include -I../OpenCV-2.4.3/modules/imgproc/include

Zu guter Letzt müssen auch die korrekten Pfade zu den Bibliotheken in Zeile 4 angegeben werden.

Ein Beispiel:

LDFLAGS=-L../OpenCV-2.4.3/debug/lib -L/usr/lib -lsndfile -lfftw3 -lopencv_core -lopencv_highgui

main.cpp

Der Monitor ist sehr einfach aufgebaut und besteht aus vier cpp-Dateien und drei Klassen (GraphEqualizer, DftUtils, Utils). Die main.cpp nimmt die Kommandozeilenparameter entgegen und wertet diese aus. Danach wird die Quell-Audiodatei eingelesen, die Videowriter-Klasse instanziiert und dann die Methode Utils::calcFrequencyBandHistograms(..) aufgerufen. Diese Methode führt die eigentliche Arbeit der Frequenzanalyse und der grafischen Aufbereitung durch.

// process commandline arguments
...
// read the audio input file and return a real number samples vector
Utils::readAudioFile(audioInputFile, &sfInfo, samples);

// create the video file for writing
VideoWriter vidWriter(videoOutputFile, CV_FOURCC('D', 'I', 'V', '3'), videoFps, Size(videoWidth, videoHeight), true);
if (!vidWriter.isOpened()) {
  throw runtime_error("VideoWriter failed to open output file!");
}

// iterate over the input sample vector and generate images of the frequency spectrum
Utils::calcFrequencyBandHistograms(samples, sfInfo, vidWriter, videoFps, videoWidth, videoHeight);
..

Leider ist das Einlesen von Stereo-Audiodateien zur Zeit noch etwas fehlerbehaftet. Ich konnte bis jetzt nicht ermitteln, ob der Fehler beim Einlesen über libsndfile oder später passiert. Der Fehler stellt sich so dar, dass das Eingangssignal immer zeitlich doppelt verzögert analysiert wird. Die Frequenzanalyse funktioniert aber dennoch korrekt. Nur die Eingangsdaten sind verzögert.

Utils::calcFrequencyBandHistograms

Am interessantesten ist die Methode calcFrequencyBandHistograms. Diese Methode iteriert über das Eingangssignal und errechnet für jeweils 512 Werte mit Hilfe der DFT ein Frequenzspektrum. Über dieses nun 257 Werte breite Spektrum wird ein Histogramm von 32 Werten gebildet. Dieses Histogramm wird dann normalisiert und über OpenCV in ein Bild gerendert. Dieses Bild wird dann der Instanz der Klasse VideoWriter übergeben.

Die Hauptschleife sieht wie folgt aus:

unsigned int numOfFpsSlices = <number of slices to analyze>
vector samples = <audio input vector of real numbers>

for (unsigned int sliceIdx = 0; sliceIdx < numOfFpsSlices; sliceIdx++) {
  // DFT generates vector of magnitudes (length = (dftSize / 2) + 1 )
  vector magVec = DftUtils::calcDftWithOffset(samples, sliceIdx * numOfSamplesPerFrame, dftSize);

  // generate a normalised histogram from the calced frequency magnitudes
  generateHistogram(magVec, magVecLenToDisplay, magnitudeHistogram);

  // generate the graphical frequency representation and store the frame in the video stream
  vidWriter << graphEqualizer.generateEqualizerImageFromHistogram(magnitudeHistogram);
}

Auf ein wichtiges Detail in Bezug auf den Aufruf der Generierung des Histogramms möchte ich noch eingehen. Die Beispiele und Tests habe ich mit Musikstücken durchgeführt. Dabei stellte sich heraus, dass Amplitudenveränderungen im Frequenzspektrum im Bereich von 0 – 6000 Hz stattfinden – meistens sogar nur von 0 bis 4000 Hz. Deshalb ignoriert das Histogramm alle Frequenzen welche höher als 4000 Hz liegen. Dieser Grenzwert kann aber einfach über eine Konstante in der Methode calcFrequencyBandHistograms angepasst werden.

DftUtils::calcDftWithOffset

Dank FFTW ist die Anwendung der DFT so einfach gestaltet, so dass die Methode calcDftWithOffset sehr unspektakulär aussieht. Zuerst wird Speicher für die Aufnahme der Eingangswerte mit Hilfe der FFTW-Methoden angelegt. Dies ist notwendig, damit FFTW den Zugriff auf den Speicher optimieren kann.

unsigned int N = numOfSamples;
double *dftIn = fftw_alloc_real(N);

unsigned int outN = N / 2 + 1;
fftw_complex* dftOut = fftw_alloc_complex(outN);

Als nächstes wird ein sogenannter Plan angelegt. Dieser Plan beschreibt die durchzuführende Operation und die Länge der Eingangswerte. FFTW bietet auch Methoden an, welche statt reeller Zahlen, komplexe Zahlen als Eingangswerte entgegennehmen. Auch die Ausführungsgeschwindigkeit der DFT kann optimiert werden, indem statt FFTW_ESTIMATE zum Beispiel FFTW_MEASURE verwendet wird (mehr Details dazu hier).

Als weitere Optimierungsmaßnahme sollte ein Plan nur einmal erzeugt und dann wiederverwendet werden. Dies ist aber nur dann möglich, wenn sich die Anzahl der Eingangswerte und dessen Wertetyp (reell, komplex) nicht ändert. In dem Beispielcode ist die Performance aber unwichtig, so dass deshalb der Plan bei jedem Aufruf erzeugt und am Ende zerstört wird:

fftw_plan dftPlan = fftw_plan_dft_r2c_1d(N, dftIn, dftOut, FFTW_ESTIMATE);

Um die Leckeffekte zu minimieren wird als nächstes die Hanning-Fensterfunktion auf die Eingangswerte angewandt. Für Testzwecke ist es möglich per #define USE_HANNING_WINDOW die Fensterfunktion auszukommentieren um die Auswirkungen auf das Resultat der DFT sehen zu können.

#define USE_HANNING_WINDOW
#ifdef USE_HANNING_WINDOW    
    applyHanningWindow(samples, startOffset, N, dftIn);
#else    
    for (unsigned int i=0;i<N;i++) {
        dftIn[i] = samples[startOffset + i];
    }
#endif

Als nächstes wird die DFT über den vorher erzeugten Plan ausgeführt und dann aus dem Vektor komplexer Ausgangswerte ein Vektor von Frequenzbeträgen generiert.

fftw_execute(dftPlan);

vector magVec(outN);
for (unsigned int i=0;i<outN;i++) {

    double real = dftOut[i][0];
    double imag = dftOut[i][1];
    double mag = sqrt((real * real) + (imag * imag));
        
    magVec[i] = mag;
}

Zu guter Letzt wird der Plan zerstört und der benutze Speicher zurückgegeben:

fftw_destroy_plan(dftPlan);

fftw_free(dftIn);
fftw_free(dftOut);

Utils::generateHistogram

Um das per DFT errechnete Frequenzspektrum darstellen zu können, wird ein Histogramm erstellt. Dazu wird ein Histogramm mit einer bestimmten Größe (z.B. 32) definiert und die Werte der jeweiligen Frequenzanteile zu dem entsprechenden Index addiert. Nachdem das vollständige Frequenzspektrum akkumuliert wurde, wird es für die grafische Darstellung normalisiert. Der Normalisierungs-Faktor ist unter anderen von der Breite der DFT und der Parameter der Fensterfunktion abhängig.

Normalerweise würde jeder Histogrammwert normalisiert werden, indem er durch den größten im Histogramm vorkommenden Wert dividiert wird. So wäre es aber nicht möglich, leise Passagen im Eingangssignal von lauten unterschiedlich darstellen zu können. Deshalb wird als Kompromiss ein fester Divisor verwendet (hier: 30), welcher durch Testen verschiedener Musikstücke ermittelt wurde.

// accumulate histogram of magnitudes for each frequency band
std::fill(histogram.begin(), histogram.end(), 0.0);
for (unsigned int i=0;i<magVecSize;i++) {
    int idx = i * ((double)histogram.size() / magVecSize);
    histogram[idx] += fabs(magVec[i]);
}

for (unsigned int i=0;i<histogram.size();i++) {
    histogram[i] /= 30.0;
}

GraphEqualizer::generateEqualizerImageFromHistogram

Die grafische Darstellung des Histogramms übernimmt die Methode generateEqualizerImageFromHistogram. Unter Verwendung der Methode fillPoly von OpenCV wird für jeden Wert des Histogramms eine gefärbte „Säule“ dargestellt.

Anhand von mehreren konstanten Farbwerten wird per lineare Interpolation ein Farbwert der jeweiligen Säule ermittelt und diese damit gefüllt. Die Säule hat ihren Y-Ursprung jeweils bei der Hälfte der Höhe des Bildes und dehnt sich nach oben und unten mit gleicher Länge aus.

Um einen schöneren Effekt des Frequenzverlaufs zu erzielen, wird zwischen hohen und darauf folgenden niedrigen Frequenzwerten nicht sofort gewechselt, sondern sich zeitverzögert vom höheren Frequenzwert zum niedrigen angenähert.

// generate an empty (black colored) image
Mat image = Mat::zeros(imageHeight, imageWidth, CV_8UC3);
..    
Point polyPntArr[1][4];
const Point * polyPnt[1] = {polyPntArr[0]};
const int numPoints[] = {4};

for (unsigned int i=0;i<histogram.size();i++) {
    double barHeight;
    if (histogram[i] > 0.05) {
        barHeight = maxBarHeight * histogram[i];
    } else {
        barHeight = minBarHeight;
    }
    barHeight /= 2.0;
        
    // smooth the bar height based on the previous bar height
    barHeights[i] -= 4.0;
    if (barHeights[i] < barHeight) {
        barHeights[i] = barHeight;
    } else {
        barHeight = barHeights[i];
    }

    // calc the bar color
    ..
    const Scalar& predColor = barColors[predColorIdx];
    const Scalar& succColor = barColors[predColorIdx + 1];

    int diffR = (predColor[0] - succColor[0]) / histogram.size();
    int diffG = (predColor[1] - succColor[1]) / histogram.size();
    int diffB = (predColor[2] - succColor[2]) / histogram.size();

    Scalar col(predColor[2] + diffB, predColor[1] + diffG, predColor[0] + diffR);

    // draw filled polygon bar
    double x0 = (i * barWidth) + (barWidth / 2.0) - finalBarHalfWidth;

    polyPntArr[0][0] = Point(x0, halfHeight - barHeight);
    polyPntArr[0][1] = Point(x0, halfHeight + barHeight);
    polyPntArr[0][2] = Point(x0 + finalBarWidth, halfHeight + barHeight);
    polyPntArr[0][3] = Point(x0 + finalBarWidth, halfHeight - barHeight);

    fillPoly(image, polyPnt, numPoints, 1, col);
}

Bücher und URLs zum Thema „digitale Signalverarbeitung“

Tagged with: , , , , ,
Veröffentlicht in Digitale Signalverarbeitung

Den Music Player Daemon (mpd) per Fernbedienung steuern (Teil3)

Dies ist der letzte Teil dieses dreiteiligen Tutorials (Teil1, Teil2) und es geht darum, wie mpd konfiguriert werden muss, damit es mit dem Kontrollprogramm zusammenarbeitet. Und letztendlich geht es natürlich um das besagte Kontrollprogramm in C++.

Konfiguration des mpd
Wie vorher schon einmal erwähnt, kann das Kontrollprogrammm zwischen den Radio Streams wechseln, in dem es in einer Playlist zwischen den Songs wechselt. Die Playlist ist eine Textdatei, welche URLs der entsprechenden Streams enthält. Von dem Kontrollprogramm wird nur die erste Playlist verwendet, welche mpd zurückliefert. Es wäre zwar auch möglich weitere Playlists zu verwenden, dies ist für unseren Anwendungsfall aber nicht notwendig.

Auf meinem RPi liegt die Playlist Streams.m3u im Verzeichnis des Standardusers /home/pi/mpd/playlists. Die Playlist kann zum Beispiel folgende Einträge enthalten:
http://92.48.107.35:8000

http://directe-http.emissio.catradio.cat:8000/catradio.cat/cat_radio
http://directe-http.emissio.catradio.cat:8000/catradio.cat/mediterradio
http://stream.hoerradar.de:80/antennemv-mp3

Der Name der Playlist kann beliebig gewählt werden, wichtig ist nur, dass mpd diese Playlist auf Anfrage per lsinfo-Kommando als ersten Eintrag zurückliefert. Dies ist übrigens auch ein Punkt, an dem das Programm verbessert werden kann, in dem der Name der gewünschten Playlist als Argument übergeben und dann explizit in der List der Playlists gesucht wird.

Das Kontrollprogramm

Sourcecode
Der Sourcecode ist auf der Plattform sourceforge.net gehostet und kann auf verschiedenen Wegen heruntergeladen werden.

(A) Entweder per GIT (git):

git clone git://git.code.sf.net/p/blogtransbits/code blogtransbits-code/raspberrypi/mpd-remote-control

(B) oder GIT (https)

git clone https://transietbits@git.code.sf.net/p/blogtransbits/code blogtransbits-code/raspberrypi/mpd-remote-control

(C) Oder einfach als tar.gz-Archiv direkt von Sourceforge.net.

 

Die vom Kontrollprogramm verwendeten Klassen

Die vom Kontrollprogramm verwendeten Klassen

Beteiligte Klassen
Das Programm besteht aus der Startklasse, welche die main()-Methode beherbergt und drei Klassen.

MPDConnection
Die Klasse MPDConnection kümmert sich um den Verbindungsaufbau mit mpd und stellt Methoden zum Senden der Kommandos zur Verfügung. Weil die Klasse eine statische Factorymethode besitzt, ist sie zum Teil eine Factory und zum Teil eine normale Klasse. Dies ist nicht unbedingt ein gutes Pattern, der Code sollte aber so einfach wie möglich gehalten werden, so dass auf dedizierte Factories verzichtet wurde.

InputDevice
Die Klasse InputDevice enthält den Programmcode zum Öffnen eines Gerätes über seinen Pfad. Die Methode handleKeys() delegiert bei Tastendruck den Keycode und das KeyValue an die Programmlogik in Form der Klasse RemoteControlLogic weiter. Der Einfachheit halber wurde an dieser Stelle kein „Observer“-Pattern verwendet um so wenig wie möglich Klassen zu erzeugen.

RemoteControlLogic
RemoteControlLogic ist eigentlich nichts weiter als ein Zustandsautomat, welcher die gedrückte Tasten von InputDevice auswertet und entsprechend die Radio Streams wechselt, oder das Playback stoppt bzw. startet.

Die main()-Methode besteht im Kern nur aus wenigen Programmzeilen:

MPDConnection* mpdConn = NULL;
InputDevice* inputDev = NULL;
try {
  // etablish a connection to the MPD server
  mpdConn = MPDConnection::initMpdServerConnection(argv[1], portNo);

  // open the input device
  inputDev = InputDevice::initDevice(argv[3]);

  // start the endless loop for handling the pressed keys...
  RemoteControlLogic logic(mpdConn);
  inputDev->handleKeyEvents(logic);
} catch (const exception& e) {
  ..
}

Kompilieren des Quellcodes
In dem tar.gz-Archiv ist eine makefile-Datei enthalten, welche im besten Fall einfach per:

$ make

im korrekten Verzeichnis aufzurufen ist und eine ausführbare Datei namens RRMpdControl erzeugt. Außer dem GNU g++-Kompiler und den Standardbibliotheken sind keine weiteren Abhängigkeiten vorhanden.

Einklinken in den Linux-Bootprozess
Damit das Program sofort nach dem Start des RPi verfügbar ist, ist es in der Datei /etc/rc.local wie folgt verknüpft:

# start the MPD remote control program
/home/pi/RaspiInetRadio/RRMpdControl raspi2 6600 /dev/input/event2 > /home/pi/RaspiInetRadio/RRMpdControl.log 2>&1 &

In dem Beispiel wird das Kontrollprogramm im Hintergrund gestartet und auf den mpd-Server auf http://raspi2:6600 verbunden. Die Logausgaben der Standard- und Fehlerkonsole werden in die Datei RRMpdControl.log umgeleitet.

Probleme mit mpd
Während des Betriebes mit dem mpd sind einige Fehler aufgetreten, auf die ich hinweisen möchte:

  • „Das Kontrollprogramm kann das Stream-Playback nicht starten“: Hin- und wieder passiert es, dass der mpd-Server zwar eine TCP-Verbindung akzeptiert, dann aber keine Antwort liefert. In diesen Fall funktioniert das Playback nicht. Hier hilft oft nur ein Neustart des mpd und danach des Kontrollprogrammes.
  • „Nach Wechsel des Radio Streams, erfolgt kein Playback“: Hier ist nicht klar, ob dies ein Problem des mpd-Servers oder des Servers ist, welcher den Radio Stream zur Verfügung stellt.

Verbesserungen

Derzeit wird die read()-Funktion im blocking-Modus aufgerufen. Dies führt dazu, dass sich die Anwendung „aufhängt“ wenn der mpd nicht mehr antwortet, d.h. unerwartet keine Daten liefert. Dieser Modus kann über fcntl(sock, F_SETFL, flags | O_NONBLOCK) angepasst werden. Dieser Modus ist noch nicht implementiert.

Beim Wechseln zwischen den Radio Streams ist ein so gut wie immer ein lautes Knacken zu hören. Es gibt hierfür im Internet eine Lösungsvorschläge, die noch nicht im Kontrollprogramm implementiert sind.

Da oft nicht klar ist, auf welchen Radio Stream man sich gerade befindet, wäre die Ausgabe auf einen LCD oder Bildschirm notwendig. Solche Ausgaben haben aber meistens den Umfang eines eigenen kleinen Projektes.

Alternativ könnte per Sprachausgabe der Radio Stream angesagt werden.

Und zu guter Letzt könnte der Programmcode robuster und schöner gestaltet werden.

Links zum Thema:

Tagged with: , , , , , ,
Veröffentlicht in Raspberry Pi

Den Music Player Daemon (mpd) per Fernbedienung steuern (Teil2)

Dies ist der zweite Teil des Tutorials „Den Music Player Daemon (mpd) per Fernbedienung steuern“. Den vorherigen Teil könnt ihr hier finden.

In diesem Tutorial soll es darum gehen, wie der mpd über TCP gesteuert wird. Es wäre alternativ auch möglich, den mpd per Aufruf des Kommandozeilentools mpc zu steuern. Dieses ist aber nicht so mächtig und liefert so gut wie keine Rückantworten an den Aufrufer zurück und ist somit unbrauchbar. Außerdem ermöglicht der Ansatz über TCP, dass das Kontrollprogramm nicht auf dem selben Gerät wie der mpd laufen muss. So könnte zum Beispiel der Raspberry Pi (RPi) nur zum Auswerten der auf der Fernbedienung gedrückten Tasten dienen und entfernt über TCP den mpd steuern.

Das MPD Kommunikationsprotokoll
Wie schon angesprochen, ist es möglich den mpd über TCP zu steuern. Der Client (unser Kontrollprogramm) sendet ein Kommando an den mpd-Server und erhält (meistens) eine Antwort von diesem. Diese Antwort enthält entweder ein OK oder eine Fehlermeldung in der Form „ACK + Fehlermeldung“ zurück. Die Details zum Protokoll des mpd-Servers sind hier einsehbar.

Die MPD Kommandos

Der mpd-Server kennt insgesamt 6 verschiedene Kommandotypen (Details):

  • Admin
  • Database
  • Miscellaneous
  • Playback Commands
  • Playlist Commands
  • Scope specifiers

Für die Fernbedienung interessieren uns nur die Admin, Playlist und die Playback Kommandos.

Die Kommunikation mit dem mpd-Server gestaltet sich wie folgt:

  1. Verbindung zum Server öffnen und ein ‚OK‘ erwarten.
  2. Ein möglicherweise laufendes Playback per Stop-Kommando anhalten und die Playlist des mpd per Clear-Kommando leeren.
  3. Die Liste aller Playlists aufrufen, welche beim mpd-Server registriert sind. Wir werden nur die erste Playlist für die Sammlung von Internet Radio Streams verwenden.
  4. Die erste Playlist in den mpd-Server laden.
  5. Wenn der Benutzer über die Fernbedienung den Stream wechselt, erst per Stop-Kommando das Playback anhalten und dann per playid-Kommando den neuen Stream auswählen und das Abspielen starten.
  6. Wenn der Benutzer länger als drei Sekunden eine Taste gedrückt halt, so wird das Playback per Stop-Kommando angehalten.

Das folgende UML-Diagramm zeigt den Ablauf der Session-Initialisierung mit dem MPD (Punkte 1-4):

Initialisierung der MPD-Server Session

Initialisierung der MPD-Server Session

Und das nächste Diagramm zeigt vereinfacht, wie das Kontrollprogramm das USB-Gerät einliest und in Abhängigkeit der gedrückten Tasten das Playback stoppt und den vorherigen oder folgenden Radio Stream aus der Playlist abspielt. Der Client aus dem vorherigen Diagramm ist hier aufgeschlüsselt in ‚main‘ und ‚inputDeviceUSB‘.

Vereinfachte Darstellung der Kontrolllogik

Vereinfachte Darstellung der Kontrolllogik.

Auf die Darstellung des Zustandsautomaten der Steuerung zum Wechseln zwischen den Radio Streams verzichte ich, weil dieser nur aus wenigen Zuständen besteht.

Beispielcode
Nach all der Theorie kommen wir zu dem Beispielcode in C++.

1. Verbindung mit dem mpd-Server aufbauen

Um eine Verbindung zum mpd-Server aufzubauen, wird ein Socket im IPv4 (AF_INET)-Modus und als Stream-Socket geöffnet.

int sockFd;
// open the socket
sockFd = socket(AF_INET, SOCK_STREAM, 0);
if (sockFd < 0) {
     // error handling
}

Dann wird versucht, den Host über seinen Namen (z.B. raspi1, localhost, usw.) aufzulösen und die IP-Adresse zu ermitteln.

struct hostent *server = NULL;
// resolve the IP address
server = gethostbyname(mpdServerUrl);
if (server == NULL) {
  // error handling
}

Hat dies geklappt, folgt der finale Schritt, um den geöffneten Socket mit dem Server zu verbinden.

int portNo = 6600;
struct sockaddr_in serverAddr;

bzero((char *) &serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
bcopy((char *)server->h_addr, (char *)&serverAddr.sin_addr.s_addr, server->h_length);
serverAddr.sin_port = htons(portNo);

if (connect(sockFd,(struct sockaddr *) &serverAddr,sizeof(serverAddr)) < 0) {
  // error handling
}

2. Kommandos senden und Serverantwort lesen
Das Absetzen eines Kommandos an mpd erfolgt dann einfach über das Schreiben per write() auf den verbundenen Socket. Kommandos an mpd müssen immer mit einer leeren Zeile abgeschlossen werden, so dass z.B. das Kommando stop wie folgt im Sourcecode aussehen könnte.

// write command to mpd
const char* cmd = "stop\n";
int writtenBytes = write(sockFd, cmd, strlen(cmd));
if (writtenBytes < 1) {
  // error handling
}

Direkt danach wird die Response eingelesen und dann auf ‚OK‘ geprüft.

const int BUF_SIZE = 2048;
char buf[BUF_SIZE];

int readBytes = read(sockFd, buf, BUF_SIZE - 1);
if (readBytes < 0) {
  // error handling
}
buf[readBytes] = ''; // set null terminator at end of the response

const char mpdOkStr[] = "OK";
if (strstr(buf, mpdOkStr) != NULL) {
  // response is ok
} else {
  // error handling
}

Hier ist zu beachten, dass vorher der Socket in den non-blocking Mode versetzt werden sollte, weil sonst das Lesen auf dem Socket blockieren könnte. Außerdem ist es sinnvoll, read() so oft aufzurufen, bis keine Bytes mehr gelesen worden. In dem Beispielcode werden nur maximal die ersten 2047 Bytes eingelesen. Das letzte Byte wird für den Nullterminator verwendet, damit strstr() das Ende des Strings erkennt.

Im dritten und letzten Teil des Tutorials stelle ich das Kontrollprogramm vor und wie dieses im Zusammenspiel mit mpd zum Laufen gebracht wird.

Links zum Thema

Tagged with: , , , , , ,
Veröffentlicht in Raspberry Pi

Den Music Player Daemon (mpd) per Fernbedienung steuern (Teil1)

Das folgende, aus mehreren Teilen bestehende Tutorial, dreht sich um die Frage, wie der Raspberry Pi (RPi) per Fernbedienung gesteuert werden kann. Genauer gesagt, soll das Abspielen von einfachen Internet Radio Streams über den Music Player Daemon (mpd) mit Hilfe der Fernbedienung beeinflusst werden können. Dazu habe ich ein kleines Kontrollprogramm (in C++) geschrieben, welches auf dem RPi läuft, die Eingaben der Fernbedienung entgegennimmt und entsprechend den mpd Anweisungen gibt.

In den ersten Teilen des Tutorials verwende ich entweder Pseudocode oder entsprechende Diagramme, der Programmcode wird dagegen erst am Schluss analysiert.

Bitte entschuldigt, wenn dieser Post noch nicht so professionell aussieht, wie ihr es vielleicht gewohnt seid. Dies ist meiner erster Post und ich muss mich erst einmal in WordPress einarbeiten. Es kann nur besser werden…

Die Fernbedienung

Als erster Punkt war mir wichtig, dass die Fernbedienung auf keine direkte Sichtverbindung zu dem RPi angewiesen ist. Denn ich wollte, dass weder der RPi noch ein Empfängersensor sichtbar ist. Somit fielen schon mal alle Infrarot-Fernbedienungen weg.

Der zweite Punkt war der Preis, aber auch die Anzahl der Bedienelemente. Denn mehr als die Sender wechseln und den Player zu aktivieren oder deaktivieren, war nicht notwendig. Auch wollte ich weder ein Android-Gerät, Iphone, Ipad, usw. verwenden, weil die Bedienung so einfach wie möglich sein sollte, unabhängig von irgendwelchen Apps.

Nach einiger Suche habe ich mich für eine „Targus Presenter AMP18EU“ Fernbedienung entschieden, welche in Präsentationen benutzt wird, um zwischen Folien wechseln zu können. Diese hat drei Tasten: zwei zum wechseln der Folie und eine Taste zum Ein- und Ausschalten der Fernbedienung. Auch ist der Preis mit knapp 25,- EUR akzeptabel und die Fernbedienung ist auf den gängigen Einkaufsportalen im Internet verfügbar.

Die Fernbedienung wird mit dem RPi über einen USB-Dongle verbunden. Der USB-Dongle registriert sich als mehrere USB-Geräte, welcher unter /dev/input/.. abgefragt werden können. Um herauszufinden, über welchen Pfad tatsächlich die Fernbedienung abgefragt werden kann, habe ich die Programme ‚lsinput‘ und ‚input-events‘ aus dem Package ‚input-utils‘ verwendet (die folgenden Kommandos mit vorangestellten $ werden in der Kommandozeile ausgeführt):

Das Package wird (wenn nicht schon vorhanden) wie folgt installiert:

$ sudo apt-get install input-utils

Nach der Installation wird das Program lsinput aufgerufen, um herauszufinden, welche Eingabegeräte unter /dev/input registriert sind…

$ lsinput

… und erhält zum Beispiel folgende Ausgabe (Auszug):

..
/dev/input/event2
bustype : BUS_USB
vendor : 0x1048
product : 0x7d4
version : 273
name : "Targus Presentation Remote"
phys : "usb-bcm2708_usb-1.3/input2"
uniq : ""
bits ev : EV_SYN EV_KEY EV_REL EV_ABS EV_MSC EV_REP
..

Auf meinem RPi hat sich der Targus USB-Dongle also unter anderen auf /dev/input/event2 registriert. In der Ausgabe können wir sehen, dass in den Event-Bits das EV_KEY und EV_RELBit gesetzt ist. Damit können wir einerseits das Drücken (EV_KEY), als auch das Loslassen (EV_REL) der Tasten auslesen.

Weil aber mehrere Geräte mit dem Namen „Targus Presentation Remote“ registriert sind (hier sind es drei), müssen wir im nächsten Schritt den Gerätepfad ermitteln, über den wir die Tasten letztendlich auslesen können.

Dies geschieht über das Durchprobieren der per lsinput aufgelisteten Geräte. Dabei wird die Nummer des Gerätes z.B. /dev/input/event2 = 2 dem Programm input-events übergeben. Das Programm öffnet ein Gerät auf dem entsprechenden /dev/input/-Pfad und wartet einige Sekunden auf Eingaben des Gerätes. Können wir nach dem Drücken der Tasten auf der Fernbedienung entsprechende Event-Logging sehen, so haben wir das richtige Gerät gefunden. Beispielhaft folgt die Ausgabe des Gerätes 2, nach dem Start von input-events:

$ input-events 2
/dev/input/event2
bustype : BUS_USB
vendor : 0x1048
product : 0x7d4
version : 273
name : "Targus Presentation Remote"
phys : "usb-bcm2708_usb-1.3/input2"
uniq : ""
bits ev : EV_SYN EV_KEY EV_REL EV_ABS EV_MSC EV_REP
waiting for events
...
10:09:53.310608: EV_MSC MSC_SCAN 458830
10:09:53.310632: EV_KEY KEY_PAGEDOWN (0x6d) pressed
10:09:53.310639: EV_SYN code=0 value=0
...

Einlesen der Tasten in C++
Um die Tasten der Fernbedienung per C++-Programm einlesen zu können, genügt es, die Datei auf dem entsprechenden Pfad zu öffnen und aus dieser zu lesen. Der Programmcode sind dann beispielhaft wie folgt aus:

1. Gerät öffnen

std::string deviceName = "/dev/input/event2";
int deviceFd = open(deviceName, O_RDONLY);
if (deviceFd == -1) {
  // Fataler Fehler
}

2. Gerät initialisieren

fd_set set;
FD_ZERO(&set);
FD_SET(deviceFd, &set);
ret = select(deviceFd + 1, &set, NULL, NULL, NULL);
if (ret == -1) {
  // Fataler Fehler
}

3. Nach dem Öffnen und der Initialisierung des Gerätes kann dieses nun ausgelesen werden:

if (FD_ISSET(deviceFd, &set)) {
ret = read(deviceFd, &event, sizeof (event));
if (ret == -1) {
  // Fehler: vom Gerät kann nicht gelesen werden
} else if (ret == 0) {
  // Fehler: EOF erreicht
}
switch (event.type) {
  case EV_KEY:
    // event.code = Keycode der Taste
    // wenn event.value = 0, dann Taste losgelassen, sonst Taste gedrückt
  break;
}
...

Wie in dem Codesnippet zu sehen ist, wird der Code der gedrückten Taste in event.code gespeichert. Ob die Taste losgelassen oder gedrückte wurde, kann in event.value erkannt werden.

Im zweiten Teil werde ich auf die Steuerung des mpd über TCP eingehen.

Links zum Thema

Tagged with: , , , , , , ,
Veröffentlicht in Raspberry Pi