TonUINO Set erweitern - Teil 2 - AZ-Delivery

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 :)

DisplaysFür arduinoProjekte für anfänger

2 Reacties

Bastian Brumbi

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

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

Laat een reactie achter

Alle opmerkingen worden voor publicatie gecontroleerd door een moderator

Aanbevolen blogberichten

  1. ESP32 jetzt über den Boardverwalter installieren - AZ-Delivery
  2. Internet-Radio mit dem ESP32 - UPDATE - AZ-Delivery
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1 - AZ-Delivery
  4. ESP32 - das Multitalent - AZ-Delivery