TonUINO Set erweitern - Teil 3 - AZ-Delivery

Nachdem wir im zweiten Teil die Bedienung aus visueller Perspektive verbessert haben, werden wir nun für die einfachere Bedienung einen Rotary-Encoder hinzufügen, um die Lautstärke einzustellen.

Hardware

Folgende Produkte werden aus den vorherigen Teilen benötigt:

Zusätzlich werden noch benötigt:

An Werkzeug brauchen Sie einen Lötkolben mit Zubehör und einen 3D-Drucker.

Damit der Rotaryencoder zuverlässig funktioniert, müssen der CLK und DIR Pin an einem Interrupt-Pin des Mikrocontrollers angeschlossen sein. Unser Nano Mikrocontroller verfügt über zwei Interrupt-fähige Pins, D2 und D3.

Als Erstes löten wir zurechtgeschnittene Buchsenleisten für den Mikrocontroller und das MP3-Modul auf das PCB, gefolgt von Stiftleisten für die externen Bauteile.

Die gelben Leitungen werden unter dem PCB verlötet und die orangen auf der Oberseite.
Kontrollieren Sie, bevor Sie die Schaltung in Betrieb nehmen, dass keine Kurzschlüsse durch das Löten auf dem Lochraster entstanden sind.

Als Nächstes schließen wir die anderen Bauteile mit Dupontkabeln an. Damit die Verkabelung ordentlicher wird, ist es empfehlenswert, die Kabel in der richtigen Länge selbst zu krimpen. Wenn Sie über kein entsprechendes Werkzeug verfügen, können Sie auch die vorgekrimpten F2F Kabel benutzen.

Für die Übersichtlichkeit sind alle Spannungs- und Masseverbindungen oben nicht direkt eingezeichnet, diese müssen auf dem PCB oben rechts mit den drei Stiftleisten verbunden werden. Das RFID Modul benötigt eine Betriebsspannung von 3.3V, hierfür befindet sich unten links eine Stiftleiste.

Für das Gehäuse müssen die beiden Dateien (Gehäuse 1, Gehäuse 2) mit einem 3D-Drucker ausgedruckt werden. Das Teil mit dem großem ovalen Loch muss in Weiß oder einer anderen Lichtdurchlässigen Farbe gedruckt werden, da der LED Ring dahinter befestigt wird.

Wenn die beiden Teile gedruckt wurden, werden als Nächstes mit einem heißen Lötkolben die Gewindeeinsätze in die größeren 6 Löcher gepresst. Zum Ende werden alle Komponenten mit entsprechenden (M2 oder M3) Schrauben befestigt.

Software

Folgende Libraries müssen installiert sein:

MFRC522
DFRobot DFPlayerMini
Adafruit SSD1306
Adafruit NeoPixel

Zusätzlich zu den Libraries aus den vorherigen Teilen benötigen wir nun noch die Encoder library.

Die Libraries können wie gewohnt über den integrierten Library-Manager oder als .zip Datei in der Arduino IDE installiert werden.

Kopieren oder laden Sie nun den folgenden Code in die Arduino IDE, wählen Sie das richtige Board und Port aus und laden Sie das Programm durch Drücken des Upload Buttons auf das Board (Download).

Als erstes werden die verwendeten Bibliotheken eingebunden.

#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>
#include <Encoder.h>

Als Nächstes werden symbolische Konstanten, die Objekte der Libraries erstellt und globale Variablen erstellt. Einige der nachfolgenden Methoden werden aus den vorangegangenen Blogbeiträgen übernommen.

#define RX 15
#define TX 14
#define LED 4
#define DWN 5
#define OK 6
#define UP 7
#define RST_PIN 9
#define SS_PIN 10
#define CLK 2
#define DT 3
#define SW 8

#define SCREEN_ADDRESS 0x3C

Encoder encoder(DT,CLK);
Adafruit_NeoPixel ring(12, LED, NEO_GRB + NEO_KHZ800);
Adafruit_SSD1306 display(128, 32, &Wire, -1);
SoftwareSerial softSerial(RX, TX);
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!!!

Die Arrays titel und duration müssen wie im vorherigen Teil mit den aktuellen Daten gefüllt werden.

String titel[FileCount] = {"txt1", "txt2", "txt3", "txt4"}; //anzuzeigender Titel
int duration[FileCount] = {293, 191, 354, 123}; //Dauer in Sekunden

int stopTime = 0;
int startTime = 0;
int correctionTime = 0;
int currFile = 0;

byte Volume = 20;
long Position;


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();
}

Folgende Methode wurde noch um das Scrollen des Textes nach rechts erweitert:

void displayText(String text){
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(5,10);
  display.println(text);
  display.display();
  display.startscrollright(0x00,0x0F);
}

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;
  }
  
}

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();
  }
}

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);
  }
}

Die Funktion volumeRot() liest die Position des Rotary Encoders aus und verändert die Lautstärke entsprechend der Drehrichtung. Die Lautstärke wird dabei wie gewohnt auf dem LED-Ring abgebildet.

void volumeRot() {
  if (encoder.readAndReset() == 0 || digitalRead(SW)) return;
  int Led = 0;
  while(true) {
    long Position = encoder.readAndReset();
    if(Position > 0 && Volume <= 28) Volume += 1;
    else if(Position < 0 && Volume >= 2) Volume -= 1;
    myDFPlayer.volume(Volume);
    delay(250);
    Serial.println(Volume);
    Led = map(Volume, 1, 30, 0, 12);
    ring.fill(ring.Color(0,0,0),0,12);
    for(int i=0; i<Led; i++) {
      ring.setPixelColor(i, ring.Color(255, 255, 0));
    }
    ring.show();
    if(digitalRead(SW)) break;
  }

  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);
  }
}

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");
  display.stopscroll();
  Serial.println("Start");
}

Der Aufruf für den Rotary Encoder wird in der loop() am Ende ergänzt und damit zyklisch aufgerufen.

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();
  }
  volumeRot();
}

Der Drehencoder erfüllt die gleiche Funktion wie die äußeren Taster. Wenn Sie den Encoder nicht verwenden wollen, können Sie den Funktionsaufruf im loop() löschen.

Am Ende werden die beiden 3D-gedruckten Teile noch verschraubt. Unser Projekt ist damit komplett.

Bedienung

Legen Sie eine NFC Karte auf die Oberseite der Box, wenn die Karte bereits beschrieben ist, wird die hinterlegte Datei direkt abgespielt. Wenn eine formatierte Karte aufgelegt wird, können Sie über die äußeren Taster die Dateinummer auswählen. Wenn Sie die richtige Datei gefunden haben, können Sie über Drücken des mittleren Tasters die Nummer auf die Karte schreiben. Entfernen Sie die Karte und legen Sie diese wieder auf. Wenn der Ring nicht mehr blau leuchtet, ist die Karte beschrieben und kann ab sofort verwendet werden. Um die Lautstärke einzustellen, können Sie entweder die äußeren Taster drücken, oder den Rotary Encoder gedrückt halten und dabei drehen. Beim Drücken des mittleren Tasters wird die Datei pausiert.

Viel Spaß beim Nachbauen :)

Alle benötigten Dateien noch einmal im Überblick:

Sketch: TonUINO

3D-Druck: Gehäuse 1, Gehäuse 2

DisplaysFür arduinoProjekte für anfänger

2 comentarios

Bastian Brumbi

Bastian Brumbi

Hallo Bernd-Steffen,
es freut mich zu hören, dass ihnen mein Blogbeitrag gefallen hat. Den Link für die Encoder Library habe ich inzwischen eingefügt. Die Pindefinition im softwareserial Konstruktor sollte stimmen (Arduino und DFPlayer müssen wie im Schaltplan verbunden werden: RX-TX TX-RX). Das das Display ab einer Anzahl von 20 Dateien keinen Text ausgibt, kann am begrenzten Speicher des Arduinos liegen. Hier könnte eine externe Speicherkarte, von welcher die Informationen abgerufen werden, Abhilfe schaffen.
Grüße,
Bastian

Bernd-Steffen

Bernd-Steffen

Hallo Bastian, noch eine schöne Erweiterung der kleinen TonUINO-Variante. Die rollende Anzeige gefällt mir gut, die Lautstärke-Regelung mit dem Encoder-Steller ist m.E. entbehrlich, da das mit den Tasten gut funktioniert, aber das ist ja Geschmacksache. Sehr gut gefallen hat mir das Gehäuse aus dem 3D-Drucker. Da habe ich gleich meinen „angeworfen“ und die zwei Teile in weiß gedruckt. Mit den schwarzen Schrauben sieht das Ganze sehr schick aus!
Nun zu zwei Kritikpunkten:
1. die encoder-library zu finden war eine Herausforderung. In der Bibliotheksverwaltung der Arduino IDE war dazu etliches zu finden, aber nicht die richtige. Erst die Suche im Netz ließ mich fündig werden. Da wäre es hilfreich für potenzielle Nachbauer, die Quelle als Link im Beitrag anzugeben, so wie das bei den libraries in Teil1 und 2 der Fall war.
2. die Parameter Rx und Tx (als SSt des DF-Players zum Nano A0 und A1) sind im Sketch vertauscht:
SoftwareSerial softSerial(RX, TX);
Wie man im Teil1 und 2 sieht, kommt in der Definition erst Tx und dann Rx. Der Befehl müsste also richtig heißen:
SoftwareSerial softSerial(TX, RX);
… sonst kommt kein Ton aus dem Lautsprecher. ;0)
Was mich noch irritiert hat, ist der Umstand, dass das Display dunkel bleibt, wenn man mehr als 20 Dateien mit den entsprechenden Texten und Zeiten im Sketch angibt! Das war bei Teil 2 nicht der Fall. Warum das so ist, hat sich mir auch nach der Analyse des Sketches noch nicht erschlossen.
Genug der Kritik, denn alles in allem eine schöne Erweiterung des Projektes, das beim Nachbau viel Spaß gemacht hat. Schade, dass man hier keine Bilder posten kann, sonst würde ich meinen weißen Musik-Würfel hier mal zeigen. Viele Grüße von Bernd-Steffen

Deja un comentario

Todos los comentarios son moderados antes de ser publicados