Tonuino - alternative Firmware

Projekt-Steckbrief

  • Difficulty: Experte 5/5
  • Kosten: 0€
  • Zeitaufwand: ~400h

Galerie

Image: birdshouse open Image: inside of the bird's house Image: Guitar Amp design Image: inside of Guitar Amp
Einige Anwendungsbeispiele von Tonuino - alternative Firmware.

Image: C++ Logo In disem Projekt entstand eine Software für eine NFC-Tag gesteuerte Jukebox. Sie ist für das Arduino-Framework optimiert und verbindet die Hardware eines Mp3-Players (mit SD-Karte), ein NFC-Tag-Lesegerät und ein paar Bedienelemente.

Ein NFC-Tag wird mit einem Ordner auf der SD-Karte verknüpft, welcher Audiodateien enthält. Das Tag speichert auch Informationen über den gewünschten Wiedergabemodus für dieses Album, z. B. “Zufällig”. Sobald ein verknüpfter Tag in die Nähe der Jukebox gebracht wird, wird der konfigurierte Ordner im entsprechenden Wiedergabemodus abgespielt. Mit den Bedienelementen kann der Titel ausgewählt, die Lautstärke geändert und sogar durch ein Sprachmenü navigiert werden, was das Löschen, Verknüpfen und Konfigurieren eines Tags sowie Sperren/Entsperren der Eingabetasten ermöglicht.

Motivation

Idee und fantastische Umsetzung hatte ich auf Thorsten Voß’ blog gesehen. Es begann mit der Funktionalität (encoder support), die ich hinzufügen wollte. Leider war der ursprüngliche Code ein zusammenhängender Block mit mehreren tausend Zeilen Code und für mich schlechter Lesbarkeit, so dass es mir schwer fiel, meine Änderungen einzubringen.

Also habe ich den Code von Grund auf neu geschrieben, um eine bessere Struktur, Lesbarkeit, Wartbarkeit und Erweiterbarkeit zu erreichen. Parallel dazu las ich einige Bücher über objektorientiertes Design in C++, Clean Code und Software Design Patterns und wandte einige davon dort an, wo ich sie für geeignet hielt. Auf diese Weise konnte ich sowohl die Modularität als auch die Lesbarkeit des Codes verbessern und habe außerdem gelernt, wie man Code in einem “größeren” Projekt schreibt.

Schwierigkeiten

Dies war mein erstes eigenes C++-Projekt, und vielleicht war es für den Anfang ein wenig zu groß. All die für mich neuen Konzepte, das Schreiben an eine Schnittstelle, die erstmalige Verwendung von Platformio als IDE, die Verwendung des googletest unit test framework, die Einsatz von Coding Patterns wie factory oder Dependency Injection kostete mich Abend um Abend über fast ein Jahr, bis ich dieses Projekt schließlich fertigstellen konnte. Und es gibt immer noch etwas zu überarbeiten und zu verbessern.

Übersicht

  • Unit-Test-Suite mit über 250 Testfällen
  • Serielle Debug-Ausgabe konfigurierbar
  • Lose gekoppelte, objektorientierte Architektur in C++
  • Einzelne Module mit klaren Aufgaben, skalierbar und leicht zugänglich für zukünftige Funktionen
  • benutzerdefinierte Hardware-Abstraktionsschicht; der größte Teil des Codes sollte ohne Änderungen auf andere MCUs portierbar sein
  • eigenes Dependency Injection Framework (Loader Klasse)

Funktionen

  • Konfigurierbare Benutzereingabe (Tasten oder Encoder)
  • Auto-Poweroff, wenn für eine konfigurierbare Zeit keine Taste gedrückt wird
  • Einschalten durch Drücken der “Play”-Taste
  • Autoplay-Funktion
  • Status-Led-Funktion
  • Sperren/Entsperren der Benutzereingabe
  • Mehrere Wiedergabemodi [Album, Zufall, Speichern des Titelfortschritts, Nur ein Titel] pro Nfc-Tag verfügbar
  • Sprachmenüs zum Verknüpfen oder Löschen von Nfc-Tags
  • Optimiert für Batterieanwendungen (z.B. Powerbank) unter Verwendung von Ruhezuständen
  • Niedriger Stromverbrauch @5V: ~40mA im Leerlauf, ~75mA bei mittlerer Lautstärke
  • Konfigurationsdatei für Startlautstärke, Einschlaf-Timer etc.
  • Automatische Wiederherstellung bei Eingabeaufforderung, die niemand bedient
  • Sprachansagen für die häufigsten Fehlermeldungen

Nicht enthaltene Funktionen

  • Keine Erkennung des Ladezustands der Powerbank
  • Kein Konfigurationsmenü (Startlautstärke, Dauer des Einschlaf-Timers, Standby-Dauer usw.)

Dokumentation

Das Projekt ist für das Arduino Framework erstellt - getestet auf einem Arduino nano Board - unter Verwendung der PlatformIO IDE. Die folgenden Abschnitte zeigen das Design.

Projekt Modulübersicht

Ordnername in /lib Zweck
Arduino minimalistische hardwarenahe Implementierungen, nicht Unit-testbar
Arduino_HardwareAbstraction Hardware-Abstraktion, um Portabilität und Testen zu ermöglichen
Config Systemkonfigurationsparameter
Folder Geschäftslogik für Wiedergabeliste und Wiedergabemodus
Loader Dependency Injection Framework
MessageHandler Systemnachrichten und Debug-Framework
Mp3 Mp3-Steuerung (Status, Ordner, Sprachausgabe, Anzeigen)
Nfc Tag-Steuerung (Status, Lesen, Schreiben, Löschen)
PowerManager Steuerung von Status-LED und Ruhezustand abhängig vom Systemstatus
Tonuino Haupt-Task-Scheduler
UserInput Verarbeitung von Tasten- oder Encoder-Eingaben
Utilities Timer, Led-Steuerung, Pin-Steuerung
VoiceMenu Geschäftslogik für das Menü Link / Löschen / Konfiguration

Verwendete externe Bibliotheken

Automatische Installation durch Platformios “Library Dependency Finder” beim erstem Kompiliervorgang

Klassendiagramme

Klassendiagramme wären zu viel nicht-automatisierte Arbeit gewesen. Stattdessen habe ich das hier vorbereitet:

Software-Modulübersicht

Sie sollte ein genaues Verständnis dafür vermitteln, wie die Softwaremodule zusammenwirken und welche APIs die Module anbieten.

Los geht’s!

Zuerst muss mein repository, geklont werden - entweder per git clone, Download als zip-Datei oder per https. Das README.md Dokument bietet einen leichten Einstieg. Ich empfehle die Verwendung von PlatformIO für dieses Projekt. In den folgenden Abschnitten sind die Teilfunktionen des Programms erläutert, und wie deren korrekte Funktion sichergestellt wird.

Testen

Unit-Tests

Unit-Tests wurden mit dem gtest C++ Unit-Test-Framework geschrieben. Sie befinden sich im Ordner test/desktop. Zu beachten ist, dass googletest die Installation von gcc mit einigen Bibliotheken voraussetzt. Die Anleitung dazu findet man in einem meiner anderen Projekte. Sobald diese Voraussetzungen erfüllt sind und ein einfacher ASSERT_TRUE(false);-Test mit der gtest-Umgebung erwartungsgemäß fehlschlägt, können die Unit-Tests des Projekts erstellt und mit pio test -e desktop -f desktop, in der PlatformIO CLI (Terminal) ausgeführt werden.

Akzeptanztests

Obwohl diese Tests automatisiert werden könnten, ist es viel einfacher, diese Tests von Hand durchzuführen, nachdem der Mikrocontroller programmiert und alle elektronischen Komponenten zusammengesteckt wurden. Jede Zeile in den folgenden Tabellen ist ein eigenständiger Testfall. Die Testsuiten (“Überschrift Name”) haben eine System Pre: Eigenschaft, die vor der Ausführung der einzelnen Testfälle wiederhergestellt werden muss. Wenn die Erwartungsklausel erfüllt ist, ist der Test BESTANDEN. Ich habe die Tests auf Englisch belassen, weil sie recht eng mit dem Code korrespondieren.

Switching ON

System Pre: System is OFF

Action Expectation
press Play/Pause button LED flashes slowly?
press Play/pause button Welcome prompt plays?

Switching OFF

System Pre: System is ON

Action Expectation
Playback on pause, no button input System switches off after a time?
System shutdown Farewell prompt plays?
System shutdown LED switched off?

Play Help Prompt

System Pre: System is ON

Action Expectation
Long press Play/Pause button Help prompt plays?
Help prompt playing Can be interrupted with any button press?

Behavior without Tag

System Pre: System is ON, no Tag present

Action Expectation
press Play/Pause button prompts “couldn’t find track” error?
press Next button prompts “couldn’t find track” error?
press Prev button prompts “couldn’t find track” error?
doubleclick Play/Pause button Delete Menu prompt played?

Behavior with Tag

System Pre: System is ON, linked Tag available

Precondition Action Expectation
no Tag placed place known Tag starts Playback of correct Folder?
active playback press Next button plays next track?
active playback press Next button next track in accordance with selected playMode of Folder?
active playback long press Next button increases volume?
active playback press Prev button plays previous track?
active playback press Prev button previous track in accordance with selected playMode of Folder?
active playback long press Prev button decreases volume?
active playback press Play/Pause button pauses track?
paused playback press Play/Pause button resumes track?
active playback doubleclick Play/Pause button Lock prompt played?
active playback doubleclick Play/Pause button Playback resumes after Lock prompt played?
active playback doubleclick Play/Pause button locks button input?
locked button input doubleclick Play/Pause button Unlock prompt played?
locked button input doubleclick Play/Pause button Playback resumes after Unlock prompt played?
locked button input doubleclick Play/Pause button unlocks button input?
paused playback doubleclick Play/Pause button Playback resumes after Lock prompt played?

Behavior with unlinked Tag

System Pre: System is ON

Action Expectation
place unlinked Tag plays LinkMenu Prompt?
navigate through LinkMenu plays Configuration Success Prompt?
place Tag again plays linked folder?

Hardware

Materialliste

Ich habe versucht mit möglichst wenigen Einzelteilen auszukommen. Für ein Projekt dieser Größe ist die Liste denke ich recht kurz geblieben.

Menge Artikel Zweck
1 Arduino (z.B. Nano) Gehirn
1 Dfplayer Mini Mp3 Player Mund
1 Micro SD Karte Speicher
1 MFRC522 Nfc-Leser Psi-Sense
1 Bistabiles Relais 5V, z.B. HFE20 Kaffee - kein Schlaf
2 Dioden z.B. 1n4007 Venenklappe
1 PNP-Transistor z. B. BC327 Kaffeemaschinenschalter
2 Widerstände 1k Blutstromaufbereiter
1 Widerstand 220 Blinkstärke dimmen
1 LED, Farbe nach Wahl blinken
1 Powerbank/ 5V Versorgung Lebensmittel
3 Drucktasten, z.B. Cherry MX Tasten Drucksensoren
1 Lautsprecher, z.B. 5W@4Ohms Stimmbänder
1 USB-A-Stecker Strohhalm
1 Gehäuse Körper

Zusätzliche Bauteile und Tipps

Überbrückungsdrähte, Platine, Buchsen und Verbrauchsmaterial nach eigenem Ermessen Wie beim “Original”-Projekt; alternativ einen Drehgeber anstelle der Drucktasten kaufen und das Projekt so konfigurieren, dass er statt der Taster ausgelesen wird. Da das System für den Batteriebetrieb optimiert ist, nehmen Sie ein bi-stabiles Relais oder einen JFET-Transistor (mit niedriger Gate-Spannung und niedrigem Source-Drain-Spannungsabfall) für die KeepAlive-Funktionalität, die den zusätzlichen Strom durch die ständig eingeschalteten Relaisspulen vermeidet. Eine billige Powerbank reicht für viele Stunden Betrieb. Alternativ können z.B. 3xAA-Batterien verwendet werden - allerdings macht der Dfmini-Player ordentlich Lärm, wenn die Spannung zu sehr einbricht. Er kann sogar bei wiederholten Neustarts durch Stromstöße zerstört werden, also Vorsicht bei nachlassender Akkuladung.

Schaltplan

Stromlaufplan