Nachdem es im ersten Teil um die Grundfunktionen gegangen ist, werden in diesem Teil weitere Module hinzugefügt. Es werden zusätzlich zu den Bauteilen aus dem ersten Teil folgende Produkte benötigt:
Hardware
Als Nächstes werfen wir einen Blick auf die Hardware. Stecken Sie nun die neuen Bauteile auf das Breadboard aus dem vorherigem Teil und verkabeln diese wie folgt:
Mikrocontroller |
Modul |
D 4 |
LED Ring - IN |
A 4 |
OLED - SDA |
A 5 |
OLED - SCL |
Software
Für den LED Ring und das OLED Display müssen noch zusätzliche Bibliotheken installiert werden. Diese können wie gewohnt über den integrierten Library-Manager oder als .zip Datei in der Arduino IDE installiert werden:
Adafruit SSD1306
Adafruit NeoPixel
Das Ziel ist es auf dem Display den aktuellen Titel und bei der Programmierung der Karten die Dateinummer anzuzeigen. Der LED-Ring soll einen Fortschrittsbalken und den Status durch Farbänderung darstellen. Da wir diese Daten nicht durch einen Befehl am DFPlayer auslesen können, müssen wir sie im Programm hinterlegen. Hierfür werden zwei Arrays initialisiert, in das erste wird die Dauer jeder einzelnen Datei in Sekunden angegeben, im zweiten der Name, welcher auf dem Display angezeigt werden soll. Zu beachten ist, dass die Positionen im Array verschoben sind, aber mit der Dateibenennung übereinstimmen müssen (001.mp3 = [0]). Aus Gründen des geringen Speicherplatzes sollten möglichst kurze Namen ohne Umlaute gewählt werden.
Für das Display und den LED-Ring werden noch die zugehörigen Libraries eingebunden. Für das Display wird zusätzlich noch die Wire library, welche für die Kommunikation benötigt wird, und die GFX Library für die grafischen Berechnungen eingebunden.
Kopieren oder laden Sie den Quellcode in die Arduino IDE (Download):
#include <Arduino.h> #include <SPI.h> #include <MFRC522.h> #include "DFRobotDFPlayerMini.h" #include <SoftwareSerial.h> #include <Adafruit_NeoPixel.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define RST_PIN 9 #define SS_PIN 10 #define UP 7 #define OK 6 #define DWN 5
#define LED 4
#define SW 8
Für die IIC Adresse des Displays definieren wir hier noch eine zusätzliche symbolische Konstante.
#define SCREEN_ADDRESS 0x3C
Danach werden die globalen Variablen und Objekte mit dem entsprechenden Konstruktor erstellt. Einige der folgenden Methoden übernehmen wir aus dem ersten Teil.
Adafruit_NeoPixel ring(12, LED, NEO_GRB + NEO_KHZ800); Adafruit_SSD1306 display(128, 32, &Wire, -1); SoftwareSerial softSerial(2, 3); DFRobotDFPlayerMini myDFPlayer; MFRC522 mfrc522(SS_PIN, RST_PIN); MFRC522::MIFARE_Key key; MFRC522::StatusCode status; byte sector = 1; //Position im Speicher byte blockAddr = 4; byte dataBlock[] = { //Buffer zum beschreiben 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; byte buffer[18]; //Buffer zum lesen byte trailerBlock = 7; bool State = true; const int FileCount = 4; //!!!Anzahl an Dateien, bitte anpassen!!!
Zusätzlich werden noch die Arrays für den Titel und die Dauer initialisiert und auch die Variablen für die Ermittlung der abgespielten Zeit.
String titel[FileCount] = {"txt1", "txt2", "txt3", "txt4"}; //anzuzeigender Titel int duration[FileCount] = {234, 231, 234, 123}; //Dauer in Sekunden int stopTime = 0; int startTime = 0; int correctionTime = 0; int currFile = 0; byte Volume = 20; void readCard() { //Lesen des RfID Stacks -> Buffer if ( ! mfrc522.PICC_ReadCardSerial()) return; //Karte kann nicht gelesen werden -> Abbruch byte size = sizeof(buffer); status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid)); if (status != MFRC522::STATUS_OK) { Serial.print(F("PCD_Authenticate() failed: ")); Serial.println(mfrc522.GetStatusCodeName(status)); return; } status = (MFRC522::StatusCode) mfrc522.MIFARE_Read(blockAddr, buffer, &size); if (status != MFRC522::STATUS_OK) { Serial.print(F("MIFARE_Read() failed: ")); Serial.println(mfrc522.GetStatusCodeName(status)); } mfrc522.PICC_HaltA(); mfrc522.PCD_StopCrypto1(); } void writeCard() { //schreiben des dataBlock -> RfID Stack if ( ! mfrc522.PICC_ReadCardSerial()) return; //Karte kann nicht gelesen werden -> Abbruch status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid)); if (status != MFRC522::STATUS_OK) { Serial.print(F("PCD_Authenticate() failed: ")); Serial.println(mfrc522.GetStatusCodeName(status)); return; } status = (MFRC522::StatusCode) mfrc522.MIFARE_Write(blockAddr, dataBlock, 16); if (status != MFRC522::STATUS_OK) { Serial.print(F("MIFARE_Write() failed: ")); Serial.println(mfrc522.GetStatusCodeName(status)); } mfrc522.PICC_HaltA(); mfrc522.PCD_StopCrypto1(); }
Diese Funktion wird später für das anzeigen von Text auf dem Display aufgerufen:
void displayText(String text){ display.clearDisplay(); display.setTextSize(2); display.setTextColor(SSD1306_WHITE); display.setCursor(5,10); display.println(text); display.display(); }
Die Funktion startCard() entspricht weitgehend der alten, nur dass während des Programmiervorgangs der Dateiindex und bei einer gespeicherten Karte der hinterlegte Name auf dem Display angezeigt werden. Außerdem leuchtet während des Beschreibevorgangs der Karte der Ring blau und nach erfolgreichem Beschreiben wird dieser wieder ausgeschaltet.
void startCard() { readCard(); //neue Karte if(buffer[0] != 1) { //Marker nicht vorhanden myDFPlayer.play(1); int i = 1; displayText((String)i); while(true) { if(!digitalRead(UP)) { while(!digitalRead(UP)) delay(20); if(i < FileCount) { i+=1; myDFPlayer.next(); } else { i = 1; myDFPlayer.play(1); } displayText((String)i); } if(!digitalRead(DWN)) { while(!digitalRead(DWN)) delay(20); if(i > 1) { i-=1; myDFPlayer.previous(); } else { i = FileCount; myDFPlayer.play(FileCount); } displayText((String)i); } if(!digitalRead(OK)) { //bestätigen und beschreiben displayText("Schreiben"); while(!digitalRead(OK)) delay(20); dataBlock[0] = 1; dataBlock[1] = i; ring.fill(ring.Color(0, 0, 255), 0, 12); ring.show(); while(!mfrc522.PICC_IsNewCardPresent()) delay(20); writeCard(); displayText((titel[i - 1])); ring.fill(ring.Color(0, 0, 0), 0, 12); ring.show(); return; } } } //registrierte Karte else { currFile = buffer[1]; displayText((titel[currFile - 1])); myDFPlayer.play(currFile); startTime = millis()/1000; correctionTime = 0; } }
Die folgende Methode stellt den Fortschrittsbalken auf dem LED-Ring dar. Zu Beginn wird der Staus des DFPlayers abgefragt. Falls nichts abgespielt wird, wird die Methode beendet und wenn nicht pausiert wurde, werden die LEDs ausgeschaltet. Danach wird die gespielte Zeit berechnet und daraus die Anzahl an LEDs, die leuchten sollen, über die map() Funktion bestimmt. Am Ende wird der Ring gedimmt oder, falls noch nicht das Ende der Datei erreicht worden ist, wird die zuvor berechnete Anzahl an LEDs angezeigt.
void progress() { if(myDFPlayer.readState() != 1) { if(State == 1) { ring.fill(ring.Color(0, 0, 0), 0, 12); ring.show(); } return; //Spielt nicht ab -> Abbruch } for(int i=0; i<12; i++) { ring.setPixelColor(i, ring.Color(0, 0, 0)); } int played = (millis()/1000) - startTime - correctionTime; byte Led = map(played, 0, duration[currFile - 1], 0, 13); if(Led > 12) { displayText("Ende"); display.stopscroll(); for(int i = 255; i>=0; i--) { for(int n=0; n<12; n++) { ring.setPixelColor(n, ring.Color(0, i, 0)); } ring.show(); delay(10); } Led = 0; } else { for(int i=0; i<Led; i++) { ring.setPixelColor(i, ring.Color(0, 255, 0)); } ring.show(); } }
Die nächste Funktion ist der Ersatz für die früheren Tasterabfragen im loop(). Wenn kein Taster gedrückt wird, folgt das Beenden durch ein earlyreturn. Während ein Taster gedrückt wird, wird die Lautstärke in 2er Schritten erhöht oder verringert. Entsprechend der neu eingestellten Lautstärke wird ein orangener Kreissektor angezeigt.
void volume() { int Led; if(digitalRead(UP) && digitalRead(DWN)) return; for(int i=0; i<12; i++) { ring.setPixelColor(i, ring.Color(0, 0, 0)); } while(!digitalRead(UP)) { if(Volume<30) Volume += 2; myDFPlayer.volume(Volume); Led = map(Volume, 1, 30, 0, 12); for(int i=0; i<Led; i++) { ring.setPixelColor(i, ring.Color(255, 255, 0)); } ring.show(); delay(700); } while(!digitalRead(DWN)) { if(Volume>0) Volume -= 2; myDFPlayer.volume(Volume); Led = map(Volume, 1, 30, 0, 12); for(int i=0; i<12; i++) { ring.setPixelColor(i, ring.Color(0, 0, 0)); } for(int i=0; i<Led; i++) { ring.setPixelColor(i, ring.Color(255, 255, 0)); } ring.show(); delay(700); } for(int i = 255; i>=0; i--) { for(int n=0; n<Led; n++) { ring.setPixelColor(n, ring.Color(i, i, 0)); } ring.show(); delay(2); } }
Im setup() werden die Startfunktionen ausgeführt, der Player initialisiert, der RFID Sensor (genau wir im ersten Teil), der LED-Ring und das Display.
void setup() { softSerial.begin(9600); Serial.begin(115200); while(!myDFPlayer.begin(softSerial, false, false)) { Serial.println(F("DFPlayer unable to begin")); delay(1000); } SPI.begin(); mfrc522.PCD_Init(); for (byte i = 0; i < 6; i++) { key.keyByte[i] = 0xFF; } pinMode(UP, INPUT_PULLUP); pinMode(OK, INPUT_PULLUP); pinMode(DWN, INPUT_PULLUP); pinMode(SW, INPUT_PULLUP); myDFPlayer.volume(Volume); //0-30 ring.begin(); ring.show(); ring.setBrightness(75); ring.fill(ring.Color(0, 0, 0), 0, 12); ring.show(); if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1306 allocation failed")); } display.clearDisplay(); display.display(); displayText("Start"); }
Im loop() werden die Methoden progress() und volume() dauerhaft wiederholt. Wenn der mittlere Taster gedrückt wird, wird die aktuelle Zeit in einer Variable gespeichert, beim Fortsetzen von der Zeit abgezogen und als Korrekturfaktor gespeichert.
void loop() { if (mfrc522.PICC_IsNewCardPresent()) { //Karte erkannt Serial.println("Karte erkannt"); startCard(); } volume(); progress(); if(!digitalRead(OK)) { while(!digitalRead(OK)) delay(20); //Warten bis Taster losgelassen wurde if(State) { myDFPlayer.pause(); State = 0; stopTime = millis()/1000; } else { myDFPlayer.start(); State = 1; correctionTime += abs(stopTime - (millis()/1000)); } } if(!State) { ring.fill(ring.Color(120, 0, 0), 0, 12); ring.show(); } }
Fazit
In diesem Teil haben wir folgende Funktionen zum TonUINO-Set ergänzt:
- Anzeige des Fortschritts
- Erleichterung in der Bedienung bei der Programmierung der Karten
- Verbesserung der Lautstärkeeinstellung
- Darstellung der Lautstärke auf dem LED-Ring
- Anzeige des Titels auf dem Display
Im nächsten Teil wird das Projekt zum Abschluss in ein 3D gedrucktes Gehäuse gebaut und ein Rotary Encoder als alternative Steuerung der Lautstärke hinzugefügt.
Viel Spaß beim Nachbauen :)
7 Kommentare
Andreas Wolter
@Rudolf Gerlinger: der Nano wurde aus Platzgründen gewählt. Er hat aber (wie Sie schon sagten) weniger Programmspeicher. Der MEGA sollte genauso funktionieren. Sie müssen nur darauf achten, SDA und SCL korrekt anzuschließen. Auf dem Nano sind das A4 und A5. Der MEGA hat dafür separate Pins. Schauen Sie dafür in das Pinout.
Eventuell müssen Sie auch für die restlichen Komponenten andere Pins verwenden. Dann müssen Sie das im Quellcode umschreiben.
Grüße,
Andreas Wolter
AZ-Delivery Blog
Rudolf Gerlinger
Hallo an die Spezialisten,
kann ich auch den Arduino Mega nehmen, er hat wohl mehr Speicherplatz als ein Nano
Kann ich die Programmierung vom Nano übernehmen.
Mit besten Dank
Rudolf
Konrad
Hi,
das mit den Titeln habe ich inzwischen herausgefunden.
Man muss die Titel einzeln hochkopieren. Dann klappt es.
@Andreas: Das mit den Track auf einmal habe ich ziemlich oft geprüft und hat nicht geklappt.
Danke trotzdem für die Geduld.
Konrad
Andreas Wolter
@Konrad: ist die Reihenfolge genau um 1 verschoben, oder sind die Titel komplett durcheinander?
Bastian Brumbi hatte im Text darauf hingewiesen, dass alle Tracks zusammen auf die Karte kopiert werden müssen.
Grüße,
Andreas Wolter
AZ-Delivery Blog
Konrad
Hallo nochmal, diesmal in diesem Blog.
Kurze Info: Ich habe das Gehäuse aus Blog 3 ausgedruckt und Alles zusammen hingebracht; – halt ohne Encoder (siehe meine Probleme im Kommentar Blog3) mit dem Code komplett aus Blog 2. Bis hier läuft technisch Alles einwandfrei.
Vielen Dank an den Autoren! Mein Enkel wird es danken und die Box sieht echt toll aus.
Eine komische Erfahrung musste ich aber machen:
Die Titel habe ich auf die SD-Karte wie beschreiben kopiert (001.mp3, 002.mp3, etc.). Im Code habe ich dann, – wie oben beschreiben die
String titel[FileCount] = {"1- Logical", “2- Breakfast”, “…….”};
geändert (Wie man erkennen kann: Lieder von Supertramp).
Den FileCount habe ich auf 12 gesetzt und in den Arrays geachtet, dass es auch 12 Inhalte gibt.
Problem: Die Liednummerierung passt nicht beim Abspielen. So ist z. B. Lied “2-Breakfast” (angezeigt im Display), plötzlich z.B. der Logical Song.
Warum ist das so?
Das Ganze mühsam raushören und dann im Code neu eingeben ist ziemlich mühsam.
Was mache ich da falsch?
Danke schon mal und ich hoffe ich belästige den Auto nicht so. Bin gerade Strohwitwer und kann mich also austoben.
Konrad
Bastian Brumbi
Vielen Dank für das ausführliches Feedback und die wertvollen Hinweise!
Ich habe mir die angesprochenen Punkte noch einmal genau angeschaut und die erwähnten Fehler behoben.
Dieses Projekt soll das bisherige Tonuino Projekt erweitern, sodass auch Einsteiger das Programm nachvollziehen können. Falls sie die Wiedergabefunktionen des Offiziellen Tonuino Projekts (von Thorsten Voss) benötigen, können sie einen Blick auf dieses Werfen, welches jedoch deutlich komplexer ist.
Nochmals danke für das Feedback; Ich hoffe, dass die Verbesserungen helfen werden!
Grüße,
Bastian Brumbi
Bernd-Steffen
Hallo zusammen, die vorgestellten Erweiterungen zum TonUINO von Thorsten Voss sind ganz nett für die Erleichterung der Bedienung (Nr. der Wiedergabedatei oder Lautstärke-Niveau anzeigen) allerdings vermisse ich die vielfältigen Funktionen (z.B. freie Wahl eines Ordners, Wiedergabemodus wie Hörbuch-, Hörspiel- oder Party-Modus, Admin-Funktionen) des o.a. Projektes! Der Autor hätte auch in der Beschreibung darauf hinweisen können, dass die Anzahl der wiederzugebeneden Dateien fix im Sketch einzugeben sind, ebenso die zugehörigen Texte und Dauer der Wiedergabe. Das empfinde ich als schwerwiegende Einschränkung gegenüber der Voss-Version! Außerdem sind einige Fehler im Sketch (auch in der Download-Datei aus der Cloud!) enthalten: die Konstanten LED (s. Zeile 22) und SW (s. Zeile 256) sind nicht definiert und führen zu Fehlern beim Compilieren. Im Sketch des 3. Teils sind die Definitionen dann vorhanden, (#define LED 4 und #define SW 8)
Ansonsten ein schöne Erweiterung – super wäre, wenn die genannten Funktionen des Voss-Projektes mit den hier vorgestellten Erweiterungen kombiniert würden. Auch sollte dieses Projekt als Quelle erwähnt werden!
Viele Grüße von Bernd-Steffen