3.2 Wählscheibentelefon steuert DFPlayer Mini (🚧)

Synopsis: Exit-Room Wählscheiben-Telefon mit Arduino Uno und DFPlayer Mini

Wählscheibentelefone mit 📖 Impulswahl sind eine alte Technik.
Im Bild ist ein ausgemustertes Amtsstubentelefon meines Vaters zu sehen. Eine Wählscheibe erzeugt entsprechend der Ziffer Impulse, die mit einer recht einfachen Schaltung ausgewertet für verschiedene Anwendungen verwendbar sind z.B.

  • Exit-Room
  • Geo-Caching
  • Rätsel

Die Audioausgabe geschieht über einen 🔗DFPlayer Mini.

Wählscheibentelefon an Arduino
Wählscheibentelefon an Arduino

🚨 Tastentelefone funktionieren mit dieser Schaltung nicht! Auch nicht wenn sie auf Impulswahl umgestellt wurden. Sie benötigen eine höhere Spannung als 5V.

Auswerteschaltung für Wählscheiben-Telefone mit Impulswahl

Das Prinzip der alten Technik wird hier kurz angerissen:

  • Mit zwei Leitungen (a/b) wird das Telefon angeschlossen. Bei Kabel ohne Stecker findet man oft die Leitungsfarben Weiß a, Braun b, gelb E und grün bzw. blau. für W. Nimm die weiße und braune Leitung als a und b.
  • Liegt der Hörer drauf wartet das Telefon auf eine Wechselspannung zum Klingeln.
  • Wird der Hörer abgenommen fließt ein Sprechstrom über die Leitungen a/b.
  • Funktion der Wählscheibe siehe 📖Nummernschalter
Wählscheibentelefon an Arduino Testaufbau
Wählscheibentelefon mit Arduino UNO. Telefon an Telefon a/b anschließen

Ein Wählscheibentelefon an Telefon a/b anschließen, dabei ist es egal ob La mit lila bzw. schwarz und Lb mit schwarz bzw. lila verbunden wird.
Wenn kein Stecker und eine Dose verwendet wird nimm die weiße und braune Leitung.
Bei einem 📖 TAE-Stecker an La und Lb in der Dose verkabeln.

NummerBezeichnungBeschreibungFarbe
1La oder a1a-Leitungsaderweiß
2Lb oder b1b-Leitungsaderbraun
3WExterner Wecker/Klingelgrün oder blau
4EErde für Nebenstellegelb

Wie die Software die gewählte Ziffer zu erkennen versucht

Wählscheibentelefon 3 gewählt Oszi
Wählscheibentelefon 3 gewählt Oszi

Mit einem Oszilloskop aufgezeichnet:
So sieht das Signal bei der Wahl der Ziffer 3 beim alten Amtsstubentelefon aus.

Beim Loslassen und Zurückdrehen der Wählscheibe wird die Verbindung der Leitungen a und b drei mal unterbrochen, danach wird auf den Sprechstrom geschaltet, siehe 📖 Kabeltelefone.

Alte Telefone haben teilweise schlechte Kontakte, ein simulierter Tiefpass glättet die Messungen. Der kurze 0-Impuls am Ende hätte 4 statt 3 ergeben. Deshalb verwende ich zur Auswertung den Analog-Digital-Wandler des Arduino und glätte die Messwerte.

Wählscheibentelefon 3 gewählt mit Software-Glättung
Wählscheibentelefon 3 gewählt mit Software-Glättung

Das Telefon bekommt über den Widerstand R3 mit 330Ω 5V Spannung. Wenn der Hörer abgenommen ist sinkt die Spannung auf den Wert für das Sprechen. Beim Wählen wechselt die Spannung zwischen 0V und 5V. Ein AD-Wandler digitalisiert die Spannung auf den Wertebereich 0..1023. Eine zusätzliche mathematische Glättung filtert Kontaktstörungen aus.

Messwerte nach der Glättung.

#define ADC_ABGEHOBEN 900    // wenn unter diesem Wert, ist Hoerer abgehoben
#define ADC_WAEHLSCHEIBE 200 // wenn unter diesem Wert, ist Wahlkontakt geschlossen

Library für DFPlayer Mini in Arduino laden und SD-Karten bespielen

Über den Arduino Bibliotheksverwalter die DFPlayerMini_Fast Library laden.

Details: DFPlayer Mini und Library DFPlayer Mini Fast

Tondateien für Beispielcode auf SD-Karte spielen: 

DFPlayerMini_Fast Library
SD-Karte mit Test-Dateien bespielen
SD-Karte mit Test-Dateien bespielen

Software für Exit-Room

Alle 2ms digitalisiert der AD-Wandler die Spannung an Pin A0. Verrechnet mit den letzten Messwerten in adcWert gespeichert.

adcAkt = analogRead(P_TELEFON);   // AD-Wert einlesen
adcWert = (adcWert*3+adcAkt)/4;   // und glaetten ca. 120µs

Nach dem Abheben des Hörers ertönt ein Wählton (MP3_WAEHLTON).
Ein Array nummer[20] speichert die gewählten Ziffern.
Wird 2s (WAHL_TIMEOUT) keine Ziffer mehr gewählt, wird die Nummer ausgeben und in der Variable wahl gespeichert.
Im Abschnitt NUMMER_AUSWERTEN kann dann entweder der Wert von wahl oder das Feld nummer[] (hier bleiben führende Nullen erhalten) ausgewertet werden.

Wählscheibentelefon Zustandsdiagramm
Wählscheibentelefon Zustandsdiagramm
/* ExitRoomTelefon V2.4 (cc-by-sa) Oliver Mezger 27.10.2025 
 * die gewaehlte Nummer wird in char-Array nummer[20], als uint-Wert wahl (geht nur bis 65535) 
 * und als String textNummer gespeichert. Sollte die Auswertung erleichtern..
 */

#include <SoftwareSerial.h>
#include <DFPlayerMini_Fast.h>
//#define SERIAL_PLOTTER       // Signalverlauf auf Serial Plotter ausgeben bei Problemtelefonen
#define P_TELEFON A0         // Arduino Pin für Telefon
#define P_BOARD_LED 13       // Board LED zum Testen
#define ADC_ABGEHOBEN 900    // wenn unter diesem Wert, ist Hoerer ABGEHOBEN
#define ADC_WAEHLSCHEIBE 200 // wenn unter diesem Wert, ist Wahlkontakt geschlossen
#define ZIFFER_TIMEOUT 200   // nach dieser Zeit kommt kein weiterer Impuls mehr
#define WAHL_TIMEOUT 2000    // nach dieser Zeit wird keine Ziffer mehr gewaehlt
#define MP3_WAEHLTON 12      // Das MP3 fuer den Waehlton, spielt 0012.mp3
#define MP3_RICHTIG 13       // Das MP3 fuer richtige Loesung
#define MP3_FALSCH 14        // Das MP3 fuer falsche Loesung

SoftwareSerial mySerial(10, 11); // RX, TX, fuer DF-Player
DFPlayerMini_Fast myMP3;
unsigned char nummerLaenge=0;    // Laenge der gewaehlten Nummer
unsigned char nummer[20];        // gewaehlte Nummer
String textNummer="";            // Nummer als String
String loesung="123123";         // richtige Nummer

void spieleMP3(int n){
  myMP3.playFromMP3Folder(n);    // spiele MP3
}
void setup() {
  Serial.begin(115200);         // Ausgabe ueber serieller Monitor
  mySerial.begin(9600);         // Anbindung des DF-Player
  myMP3.begin(mySerial);
  #ifndef SERIAL_PLOTTER
  Serial.println(F("Setting volume to 20"));
  #endif
  myMP3.volume(20); // 0..30
  delay(20);
  pinMode(P_BOARD_LED,OUTPUT);
}
typedef enum {RUHE,ABGEHOBEN,IMPULS_0,IMPULS_1,NAECHSTE_ZIFFER,NUMMER_AUSWERTEN,NUMMER_VORLESEN} wZustandtyp;
wZustandtyp wZustand = RUHE;

void loop() {
  static unsigned char wImpulse = 0; // Impulszaehler
  static unsigned long wMillis;      // Zeitmarken fuer TimeOuts
  static unsigned int adcAkt;        // ADC-Wert aktuell
  static unsigned int adcWert=1023;  // ADC-Wert an P_TELEFON geglättet
  static unsigned int wahl=0;        // Nummer als Integer
  static unsigned char i=0;          // fuer Zaehlvorgaenge
  delay(2);                          // alle 2 ms ADC-Abtastung
  //digitalWrite(P_BOARD_LED,HIGH);  // Testen wie lange Wandlung dauert
  adcAkt = analogRead(P_TELEFON);    // AD-Wert einlesen
  adcWert = (adcWert*3+adcAkt)/4;    // und glaetten ca. 120µs
  #ifdef SERIAL_PLOTTER
  static unsigned int plott=0;
  static bool messen=false;
  if(messen){ // Signalverlauf ausgeben
    if(plott++%5==0){ // weniger Messungen übertragen
      Serial.print(F("adc:"));
      Serial.println(adcAkt);
    }
  }
  #endif
  //digitalWrite(P_BOARD_LED,LOW);
  switch (wZustand){                 // ZustandsAutomat
    case RUHE:                       // Warten auf Hoerer abnehmen
      if(adcWert < ADC_ABGEHOBEN){
        wZustand=ABGEHOBEN;
        #ifndef SERIAL_PLOTTER
        Serial.print(F("\nAbgehoben-ADCWert: "));
        Serial.print(adcAkt);
        Serial.print(F(" = "));
        Serial.print(adcAkt*5000UL/1024); // umrechnen in mV
        Serial.println(F(" mV"));
        #endif
        spieleMP3(MP3_WAEHLTON); // spiele Waehlton
      }
      break;
    case ABGEHOBEN:                   // Waehlton spielen und auf Impulse warten
      if(adcWert >= ADC_ABGEHOBEN+50){// wieder aufgelegt?
        myMP3.pause();
        wZustand = RUHE;
        #ifndef SERIAL_PLOTTER
        Serial.println(F("Aufgelegt"));
        Serial.print(F("RUHE:"));
        Serial.println(adcAkt);
        #endif
      }
      if(adcWert < ADC_WAEHLSCHEIBE){ // Waehlscheibe betaetigt?
        wZustand = IMPULS_0;
        wImpulse = 0;
        nummerLaenge=0;
        myMP3.pause();
      }
      break;
    case IMPULS_0:                     // Waehlscheibenkontakt geschlossen
      if(adcWert > ADC_WAEHLSCHEIBE){ // Impuls Waehlscheibenkontakt geoeffnet?
        wMillis=millis(); // Zeitmarke setzen
        wImpulse++; // Impuls zaehlen
        wZustand = IMPULS_1;
        #ifdef SERIAL_PLOTTER
        messen=true;
        #endif
      }
      break;
    case IMPULS_1:                     // Waehlscheibenkontakt offen
      if(adcWert < ADC_WAEHLSCHEIBE){ // Kontakt wieder geschlossen?
        wZustand = IMPULS_0;
      }
      if(millis()-wMillis > ZIFFER_TIMEOUT){ // kein weiterer Impuls
        wMillis=millis(); // Zeitmarke setzen
        wZustand = NAECHSTE_ZIFFER;
        nummer[nummerLaenge++]=wImpulse%10; // 10 Impulse -> Ziffer 0
        #ifdef SERIAL_PLOTTER
        messen=false;
        #else
        Serial.print(F("Ziffer: "));
        Serial.println(wImpulse);
        Serial.print(F("NAECHSTE_ZIFFER, ADC-Wert:"));
        Serial.println(adcAkt);
        #endif
      }
      break;
    case NAECHSTE_ZIFFER:
      if(adcWert < ADC_WAEHLSCHEIBE){  // Waehlscheibe betaetigt?
        wZustand = IMPULS_0;
        wImpulse = 0;
      }
      if(millis()-wMillis > WAHL_TIMEOUT){ // kein weitere Ziffer
        wZustand=NUMMER_AUSWERTEN;
        wahl=0;
        textNummer="";
        for(i=0;i<nummerLaenge;i++){
          wahl=wahl*10 + nummer[i];
          textNummer+= (char)('0'+nummer[i]); // Werte in ASCII-Zeichen umwandeln
        }
        #ifndef SERIAL_PLOTTER
        Serial.print(F("\nGewaehlte Nummer: "));
        for(i=0;i<nummerLaenge;i++){
          Serial.print(nummer[i]);
          Serial.println(); 
        }
        #endif
      }
      break;
    case NUMMER_AUSWERTEN: // Aktion mit der gewaehlten Nummer
      /* Beispiel fuer einfach nur das gewaehlte Stueck spielen
      myMP3.playFromMP3Folder(wahl); // einfach Stueck spielen
      wZustand=ABGEHOBEN;
       */
      // Bei einstelligen Nummern wird Stueck 0 bis 9 gespielt, sonst wird mit loesung verglichen
      if (nummerLaenge == 1){ // Nummer ist einstellig?
        spieleMP3(wahl); // einfach Stueck spielen, bei 0 -> 0000.mp3
      }
      else if(textNummer==loesung){
        #ifdef SERIAL_PLOTTER
        Serial.println(F("Richtig"));
        #endif
        spieleMP3(MP3_RICHTIG); // Richtig
      }
      else {
        #ifdef SERIAL_PLOTTER
        Serial.println(F("Falsch"));
        #endif
        spieleMP3(MP3_FALSCH); // Falsch
      }
      wZustand=ABGEHOBEN;
      /*  Beispiel fuer gewaehlte Nummer vorlesen
      i=0;
      wZustand=NUMMER_VORLESEN;
      */
      break;
    case NUMMER_VORLESEN: // Vorlesen der Nummer bis Waehlscheibe oder aufgelegt
      if(i >= nummerLaenge || adcAkt < ADC_WAEHLSCHEIBE || adcAkt >= ADC_ABGEHOBEN+50 ){
        wZustand=ABGEHOBEN;
        break;
      }
      if(nummer[i]==0){ // Bei Ziffer 0 soll Stueck 10 spielen
        spieleMP3(10);
      }
      else spieleMP3(nummer[i]);
      i++;
      delay(600);   // Warten bis Player in Gang kommt
      do{
        delay(350); // nicht zu schnell hintereinander isPlaying abfragen
      } while(myMP3.isPlaying());  
      break;
  }
}

Testsoftware mit Ausgaben auf dem Seriellen Monitor des Arduino

Testsoftware
/* Telefon-Test V1.0 (cc-by-sa) Oliver Mezger 3.11.2025 
 * die gewaehlte Nummer wird in char-Array nummer[20], gespeichert.
 */

#include <SoftwareSerial.h>
#include <DFPlayerMini_Fast.h>
//#define SERIAL_PLOTTER       // Signalverlauf auf Serial Plotter ausgeben bei Problemtelefonen
#define P_PIEZO  3           // Piezo-Lautsprecher
#define P_TELEFON A0         // Arduino Pin für Telefon
#define P_BOARD_LED 13       // Board LED zum Testen
#define ADC_ABGEHOBEN 900    // wenn unter diesem Wert, ist Hoerer ABGEHOBEN
#define ADC_WAEHLSCHEIBE 200 // wenn unter diesem Wert, ist Wahlkontakt geschlossen
#define ZIFFER_TIMEOUT 200   // nach dieser Zeit kommt kein weiterer Impuls mehr
#define WAHL_TIMEOUT 2000    // nach dieser Zeit wird keine Ziffer mehr gewaehlt
#define MP3_WAEHLTON 12      // Das MP3 fuer den Waehlton, spielt 0012.mp3
#define ANZ_ZIFFERN 30       // Maximale Anzahl der Ziffern der Nummer

SoftwareSerial mySerial(10, 11); // RX, TX, fuer DF-Player
DFPlayerMini_Fast myMP3;

unsigned char nummerLaenge=0;      // Laenge der gewaehlten Nummer
unsigned char nummer[ANZ_ZIFFERN]; // gewaehlte Nummer

typedef enum {RUHE_ENTRY,RUHE,ABGEHOBEN,IMPULS_0,IMPULS_1,NAECHSTE_ZIFFER_ENTRY,NAECHSTE_ZIFFER,NUMMER_AUSWERTEN,EINSTELLUNGEN} wZustandtyp;
wZustandtyp wZustand = RUHE_ENTRY;

void spieleMP3(int n){
  myMP3.playFromMP3Folder(n);    // spiele MP3
}

void setup() {
  Serial.begin(115200);         // Ausgabe ueber serieller Monitor
  mySerial.begin(9600);         // Anbindung des DF-Player
  myMP3.begin(mySerial);
  #ifndef SERIAL_PLOTTER
  Serial.println(F("Setting volume to 20"));
  #endif
  myMP3.volume(20); // 0..30
  delay(20);
  pinMode(P_BOARD_LED,OUTPUT);
  tone(P_PIEZO,500,200); 
}

void loop() {
  static unsigned char wImpulse = 0; // Impulszaehler
  static unsigned long wMillis;      // Zeitmarken fuer TimeOuts
  static unsigned int adcAkt;        // ADC-Wert aktuell
  static unsigned int adcWert=1023;  // ADC-Wert an P_TELEFON geglättet
  static unsigned int wahl=0;        // Nummer als Integer
  static unsigned char i=0,k=0;      // fuer Zaehlvorgaenge
  delay(2);                          // alle 2 ms ADC-Abtastung
  //digitalWrite(P_BOARD_LED,HIGH);  // Testen wie lange Wandlung dauert
  adcAkt = analogRead(P_TELEFON);    // AD-Wert einlesen
  adcWert = (adcWert*3+adcAkt)/4;    // und glaetten ca. 120µs
  #ifdef SERIAL_PLOTTER
  static unsigned int plott=0;
  static bool messen=false;
  if(messen){ // Signalverlauf ausgeben
    if(plott++%5==0){ // weniger Messungen übertragen
      Serial.print(F("adc:"));
      Serial.println(adcAkt);
    }
  }
  #endif
  //digitalWrite(P_BOARD_LED,LOW);
  switch (wZustand){                 // ZustandsAutomat
    case RUHE_ENTRY:                 // wird beim Eintreten des Zustands einmal ausgeführt
      myMP3.pause();                 // MP3 Player stoppen
      #ifndef SERIAL_PLOTTER
      Serial.print(F("Aufgelegt, "));
      Serial.print(F("AD-Wert: "));
      Serial.println(adcAkt);
      #endif
      wZustand=RUHE;
      break;
    case RUHE:                       // Warten auf Hoerer abnehmen
      if(adcWert < ADC_ABGEHOBEN){   // Gabelkontakt geschlossen, Sprechstrom
        wZustand=ABGEHOBEN;
        #ifndef SERIAL_PLOTTER
        Serial.print(F("\nAbgehoben-ADCWert: "));
        Serial.print(adcAkt);
        Serial.print(F(" = "));
        Serial.print(adcAkt*5000UL/1024); // umrechnen in mV
        Serial.println(F(" mV"));
        #endif
        spieleMP3(MP3_WAEHLTON); // spiele Waehlton
      }
      break;
    case ABGEHOBEN:                   // Waehlton spielen und auf Impulse warten
      if(adcWert >= ADC_ABGEHOBEN+50){// wieder aufgelegt?
        wZustand = RUHE_ENTRY;
      }
      if(adcWert < ADC_WAEHLSCHEIBE){ // Waehlscheibe betaetigt?
        wZustand = IMPULS_0;
        wImpulse = 0;
        nummerLaenge=0;
        myMP3.pause();                // Wählton beenden
      }
      break;
    case IMPULS_0:                    // Waehlscheibenkontakt geschlossen
      if(adcWert > ADC_WAEHLSCHEIBE){ // Impuls Waehlscheibenkontakt geoeffnet?
        wMillis=millis();             // Zeitmarke setzen für Ziffer-Timeout
        wImpulse++;                   // Impuls zaehlen
        wZustand = IMPULS_1;
        #ifdef SERIAL_PLOTTER
        messen=true;
        #else
        Serial.print(F("Impuls1: "));
        Serial.println(adcAkt);
        #endif
      }
      break;
    case IMPULS_1:                    // Waehlscheibenkontakt offen
      if(adcWert < ADC_WAEHLSCHEIBE){ // Kontakt wieder geschlossen?
        wZustand = IMPULS_0;
        #ifndef SERIAL_PLOTTER
        Serial.print(F("Impuls0: "));
        Serial.println(adcAkt);
        #endif
      }
      if(millis()-wMillis > ZIFFER_TIMEOUT){ // kein weiterer Impuls
        wZustand = NAECHSTE_ZIFFER_ENTRY;
      }
      break;
    case NAECHSTE_ZIFFER_ENTRY:
      wMillis=millis();                    // Zeitmarke setzen für Nummer-Timeout
      nummer[nummerLaenge++]=wImpulse%10; // 10 Impulse -> Ziffer 0
      wZustand = NAECHSTE_ZIFFER;
      #ifdef SERIAL_PLOTTER
      messen=false;
      #else
      Serial.print(F("Ziffer: "));
      Serial.println(wImpulse);
      Serial.print(F("NAECHSTE_ZIFFER, ADC-Wert :"));
      Serial.println(adcAkt);
      #endif
      break;
    case NAECHSTE_ZIFFER:
      if(millis()-wMillis > WAHL_TIMEOUT){  // Keine weitere Ziffer mehr gewählt
        wZustand = NUMMER_AUSWERTEN;
      }
      if(adcWert < ADC_WAEHLSCHEIBE){     // Waehlscheibe betaetigt?
        wZustand = IMPULS_0;
        wImpulse = 0;
      }
      if(adcWert >= ADC_ABGEHOBEN+50){    // wieder aufgelegt?
        wZustand = RUHE_ENTRY;
      }
      break;
    case NUMMER_AUSWERTEN: // Aktion mit der gewaehlten Nummer
      tone(P_PIEZO,1500,200);
      #ifndef SERIAL_PLOTTER
      Serial.print(F("\gewählte Nummer: "));
      for(i=0;i<nummerLaenge;i++){
        Serial.print(nummer[i]);
      }
      Serial.println();
      #endif
      wZustand=ABGEHOBEN;
      break;
  }
}

Die Testsoftware ist auskunftsfreudig und kann dadurch bei Problemen mit dem Telefon helfen. Dazu die Baudrate auf 115200 einstellen.

Nach dem Abheben des Hörers sollte ein Wahlton vernehmbar sein und im Seriellen Monitor wird der gewandelte Spannungswert, hier 766 = 3740 mV angezeigt. Mit einem Spannungsmessgerät könnte man nun zwischen den Telefonleitungen a und b 3.139 V messen.

Setting volume to 20
Aufgelegt, AD-Wert: 1023

Abgehoben-ADCWert: 766 = 3740 mV
Impuls1: 615
Impuls0: 30
Impuls1: 578
Impuls0: 25
Impuls1: 690
Ziffer: 3
NAECHSTE_ZIFFER, ADC-Wert :693
gewählte Nummer: 3
Aufgelegt, AD-Wert: 995

Wenn #define SERIAL_PLOTTER aktiv ist werden beim Wählen einer Ziffer die Spannungswerte für den Seriellen Plotter ausgegeben. Dies lässt Rückschlüsse über den Zustand des Nummernschalters (Verschleißteil) zu.

Telefonwahl 3 auf Seriellem Plotter
Telefonwahl 3 auf Seriellem Plotter

🚧 Erweitern für Spiele

Mit NeoPixeln (WS2812B) einem Schalter, Piezo-Piper, LDR und Poti können schon einige Spiele mit Wählscheibentelefon realisiert werden.
NeoPixel (WS2812B) braucht pro Pixel bis zu 50mA.

Wählscheibentelefon an Schaltung mit Neopixeln
Wählscheibentelefon an Schaltung mit Neopixeln
Wählscheibentelefon an Schaltung mit Neopixeln Verkabelung
Wählscheibentelefon an Schaltung mit Neopixeln Verkabelung
Wählscheibentelefon an Schaltung mit Neopixeln Schaltplan
Wählscheibentelefon an Schaltung mit Neopixeln Schaltplan
Wählscheibentelefon an Schaltung mit Neopixeln Steckplatine
Wählscheibentelefon an Schaltung mit Neopixeln Steckplatine