Community project

Retro Payphone Simulator

Arduino
Photo of Retro Payphone Simulator
Generated with AI

Krisma Prakosa

Published September 7, 2026

This project transforms a vintage payphone into an interactive voice-response system that detects when a visitor approaches, rings to get their attention, and guides them through a recorded message experience. Builders will create a fully functional retro phone that plays pre-recorded prompts, accepts keypad input to select languages, and records visitor responses using the ISD1820 voice module.

The guide provides a complete wiring diagram connecting the Arduino Uno to an HC-SR04 proximity sensor, DFPlayer Mini audio module, 4x4 matrix keypad, hook switch, piezo ringer, and voice recorder. Assembly steps cover mounting the phone body, integrating all sensors and audio components, loading audio tracks onto the microSD card, and testing the full call sequence from detection through goodbye message.

Wiring diagram

Wiring diagram for Retro Payphone Simulator

Gather all the parts

QtyComponent
1

HC-SR04

HC-SR04

Ultrasonic distance measurement sensor

1

DFPlayer Mini MP3 Audio Module

DFPlayer Mini

Small UART-controlled MP3 playback module with a microSD card slot and onboard audio DAC/amplifier interface. The MCU controls playback over serial while speaker/DAC pins connect to the audio output path.

1

4x4 Matrix Keypad

4×4

A 16-button (4 rows × 4 columns) membrane matrix keypad that uses 8 digital I/O lines (4 row + 4 column) to scan all keys. No dedicated power rail is required — rows and columns are driven directly by GPIO. Compatible with the Arduino Keypad library.

1

Micro Switch - Premium Zippy 3-Terminal

hook switch

Higher-quality 3-terminal bump-actuator microswitch for arcade controls and light mechatronics, rated 30 VDC / 250 mA max. Common, normally-open, and normally-closed contacts.

1

Piezo Buzzer

piezo

Passive piezo buzzer element driven by a 3–30 V peak-to-peak square wave; loudest around 4 kHz, usable from 2–10 kHz. Differential drive (swapping which pin is high/low each half-cycle) doubles the volume.

1

8Ω Speaker

8 Ω, 1 W

Generic small 8Ω 0.5-3W loudspeaker (~28mm typical). Pair with an I2S amp (MAX98357A) or class-D amp (TPA3116D2) for usable volume; do not drive directly from a GPIO pin. Audio output for music/voice playback.

1

Resistor

1 kΩ

Through-hole resistor (current-limiting in series with an LED)

1

ISD1820 Voice Recorder Module

10 detik

Modul dengan mikrofon kecil yang merekam jawaban pengunjung hingga sekitar sepuluh detik.

Assemble it in 6 steps

1. Siapkan badan telepon dan catu USB

Pasang Arduino Uno, keypad 4×4, DFPlayer, modul ISD1820, buzzer, dan sensor jarak di dalam atau di belakang badan telepon. Arahkan dua mata sensor HC-SR04 ke area tempat orang berdiri, bukan ke lantai. Nyalakan proyek nanti dari port USB Arduino.

  • Buat lubang kecil di dekat mikrofon ISD1820 agar suara orang dapat masuk.
  • Pastikan sensor tidak tertutup kaca atau kain karena pantulan suaranya tidak akan terbaca.
  • Jangan menyalakan Arduino dari dua sumber daya sekaligus; gunakan USB Arduino saja saat pengujian.

2. Pasang tombol dudukan gagang

Letakkan microswitch di bawah kait dudukan gagang agar gagang yang diletakkan menekan tuas sakelar. Sambungkan COM → GND (ground) dan NO → A0 atau pin bertanda 14 (sinyal). Saat gagang diangkat, tuas tidak lagi ditekan dan Arduino membaca perubahan ini.

  • Uji secara mekanis: gagang harus menekan tuas saat diletakkan, lalu melepas tuas ketika diangkat.
  • Gunakan terminal COM dan NO; terminal NC tidak dipakai.
  • Jangan gunakan kontak NC sebagai pengganti NO, karena perangkat akan menganggap posisi gagang terbalik.

3. Hubungkan sensor, keypad, dan buzzer

Sambungkan HC-SR04: VCC → 5V (power), GND → GND (ground), TRIG → D10 (sinyal), ECHO → D11 (sinyal). Sambungkan keypad: R1 → D2, R2 → D3, R3 → D4, R4 → D5, C1 → D6, C2 → D7, C3 → D8, C4 → D9 (semuanya sinyal tombol). Sambungkan buzzer: Lead 1 → A1 atau pin 15 (bunyi dering), Lead 2 → GND (ground).

  • Tulisan R1 sampai C4 biasanya tercetak di ujung kabel pita keypad; ikuti urutan itu.
  • Buzzer piezo tidak memiliki arah positif-negatif tetap.
  • Jangan menukar VCC dan GND sensor; daya yang terbalik dapat merusak modul.

4. Hubungkan pemutar pesan suara

Sambungkan DFPlayer: VCC → 5V (power), GND → GND (ground), TX → D12 (data masuk ke Arduino). Sambungkan D13 → kaki pertama resistor 1 kΩ (sinyal), lalu kaki kedua resistor → RX DFPlayer (data perintah). Sambungkan SPK1 → POS speaker (suara) dan SPK2 → NEG speaker (suara); kedua kabel speaker langsung ke DFPlayer, tidak ke GND.

  • Pasang speaker di dalam gagang telepon atau di belakang lubang suara pada gagang.
  • Resistor 1 kΩ tidak memiliki arah pemasangan.
  • Jangan menyambungkan salah satu kabel SPK1 atau SPK2 ke GND; keluaran speaker DFPlayer memakai dua kabel aktif dan dapat rusak bila di-ground-kan.

5. Hubungkan perekam jawaban

Sambungkan modul ISD1820: VCC → 5V (power), GND → GND (ground), dan REC → A2 atau pin 16 (sinyal rekam). Letakkan mikrofon modul dekat corong bicara gagang telepon, atau arahkan melalui lubang kecil di badan telepon.

  • ISD1820 biasanya merekam sekitar 10 detik; pengunjung menekan `*` untuk menyelesaikan lebih awal.
  • Pastikan semua modul berbagi jalur GND Arduino yang sama.
  • Jangan memasang speaker ISD1820 bersamaan ke speaker DFPlayer; gunakan mikrofon bawaan ISD1820 untuk merekam saja agar kedua keluaran suara tidak bertabrakan.

6. Isi kartu suara dan uji urutan telepon

Format kartu microSD sebagai FAT32, lalu simpan file MP3 di folder `mp3` dengan nama `0001.mp3` untuk pilihan bahasa, `0002.mp3` untuk pertanyaan Bahasa Indonesia, `0003.mp3` untuk pertanyaan English, dan `0004.mp3` untuk instruksi meletakkan gagang serta pergi. Masukkan kartu ke DFPlayer. Setelah proyek di-Deploy, dekati sensor, angkat gagang saat berdering, tekan 1 atau 2, jawab pertanyaan, lalu tekan `*` dan letakkan gagang.

  • Buat 0002.mp3 dan 0003.mp3 berdurasi kira-kira 14 detik atau ubah angka QUESTION_DURATION_MS di kode agar perekaman mulai setelah pertanyaan selesai.
  • Atur volume rekaman MP3 agar suara jelas namun tidak terlalu keras di gagang.
  • Jangan mencabut kartu microSD ketika DFPlayer dan Arduino masih mendapat daya karena file suara dapat rusak.

Review all connections

1. Connections between "ultrasonic_1" and "Arduino"

Functionultrasonic_1Arduino
powerVCC5V
groundGNDGND
digitalTRIGGPIO 10
digitalECHOGPIO 11

2. Connections between "dfplayer_1" and "Arduino"

Functiondfplayer_1Arduino
powerVCC5V
groundGNDGND
uartTXGPIO 12
uartRXResistor P2EXT
dataSPK18Ω Speaker POSEXT
dataSPK28Ω Speaker NEGEXT

3. Connections between "dfplayer_rx_resistor_1" and "Arduino"

Functiondfplayer_rx_resistor_1Arduino
digitalP1GPIO 13

4. Connections between "keypad_1" and "Arduino"

Functionkeypad_1Arduino
digitalR1GPIO 2
digitalR2GPIO 3
digitalR3GPIO 4
digitalR4GPIO 5
digitalC1GPIO 6
digitalC2GPIO 7
digitalC3GPIO 8
digitalC4GPIO 9

5. Connections between "hook_switch_1" and "Arduino"

Functionhook_switch_1Arduino
groundCOMGND
digitalNOGPIO 14

6. Connections between "ringer_1" and "Arduino"

Functionringer_1Arduino
digitalLead 1GPIO 15
groundLead 2GND

7. Connections between "voice_recorder_1" and "Arduino"

Functionvoice_recorder_1Arduino
powerVCC5V
groundGNDGND
digitalRECGPIO 16

Deploy the firmware

#include <Arduino.h>
#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>
#include <Keypad.h>


// Hoisted type definitions
enum PhoneState { WAITING, RINGING, CHOOSE_LANGUAGE, PLAYING_QUESTION, RECORDING_ANSWER, GOODBYE, WAIT_FOR_CLEAR };


// Forward declarations
bool handsetLifted();
long distanceCm();
void stopRinger();
void updateRinger();
void playTrack(uint8_t track);
void beginState(PhoneState next);

const byte KEYPAD_R1 = 2;
const byte KEYPAD_R2 = 3;
const byte KEYPAD_R3 = 4;
const byte KEYPAD_R4 = 5;
const byte KEYPAD_C1 = 6;
const byte KEYPAD_C2 = 7;
const byte KEYPAD_C3 = 8;
const byte KEYPAD_C4 = 9;
const byte ULTRASONIC_TRIG = 10;
const byte ULTRASONIC_ECHO = 11;
const byte DFPLAYER_TX_TO_UNO = 12;
const byte UNO_TX_TO_DFPLAYER = 13;
const byte HOOK_SWITCH_PIN = 14;
const byte RINGER_PIN = 15;
const byte RECORDER_REC_PIN = 16;

const unsigned int VISITOR_DISTANCE_CM = 80;
const unsigned long SENSOR_INTERVAL_MS = 120;
const unsigned long RING_TOGGLE_MS = 250;
const unsigned long QUESTION_DURATION_MS = 15000;
const unsigned long GOODBYE_DURATION_MS = 5000;

// microSD files: 0001=language menu, 0002=Indonesian question,
// 0003=English question, 0004=put handset back and leave.
const uint8_t TRACK_LANGUAGE_MENU = 1;
const uint8_t TRACK_QUESTION_ID = 2;
const uint8_t TRACK_QUESTION_EN = 3;
const uint8_t TRACK_GOODBYE = 4;

char keyMap[4][4] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};
byte rowPins[4] = {KEYPAD_R1, KEYPAD_R2, KEYPAD_R3, KEYPAD_R4};
byte colPins[4] = {KEYPAD_C1, KEYPAD_C2, KEYPAD_C3, KEYPAD_C4};
Keypad keypad = Keypad(makeKeymap(keyMap), rowPins, colPins, 4, 4);

SoftwareSerial dfSerial(DFPLAYER_TX_TO_UNO, UNO_TX_TO_DFPLAYER);
DFRobotDFPlayerMini dfPlayer;
bool dfPlayerReady = false;


PhoneState state = WAITING;
unsigned long stateStartedAt = 0;
unsigned long lastSensorAt = 0;
unsigned long lastRingToggleAt = 0;
bool ringerOn = false;

bool handsetLifted() {
  // The NO contact closes to GND only when the handset releases the switch.
  return digitalRead(HOOK_SWITCH_PIN) == LOW;
}

long distanceCm() {
  digitalWrite(ULTRASONIC_TRIG, LOW);
  delayMicroseconds(2);
  digitalWrite(ULTRASONIC_TRIG, HIGH);
  delayMicroseconds(10);
  digitalWrite(ULTRASONIC_TRIG, LOW);
  unsigned long pulse = pulseIn(ULTRASONIC_ECHO, HIGH, 25000UL);
  if (pulse == 0) return 999;
  return pulse / 58UL;
}

void stopRinger() {
  noTone(RINGER_PIN);
  ringerOn = false;
}

void updateRinger() {
  if (millis() - lastRingToggleAt < RING_TOGGLE_MS) return;
  lastRingToggleAt = millis();
  ringerOn = !ringerOn;
  if (ringerOn) {
    tone(RINGER_PIN, 1200);
  } else {
    noTone(RINGER_PIN);
  }
}

void playTrack(uint8_t track) {
  if (dfPlayerReady) dfPlayer.play(track);
}

void beginState(PhoneState next) {
  stopRinger();
  digitalWrite(RECORDER_REC_PIN, LOW);
  state = next;
  stateStartedAt = millis();
}

void setup() {
  pinMode(ULTRASONIC_TRIG, OUTPUT);
  pinMode(ULTRASONIC_ECHO, INPUT);
  pinMode(HOOK_SWITCH_PIN, INPUT_PULLUP);
  pinMode(RINGER_PIN, OUTPUT);
  pinMode(RECORDER_REC_PIN, OUTPUT);
  digitalWrite(RECORDER_REC_PIN, LOW);

  dfSerial.begin(9600);
  if (dfPlayer.begin(dfSerial, true, true)) {
    dfPlayerReady = true;
    dfPlayer.volume(23); // 0 to 30
  }
}

void loop() {
  const unsigned long now = millis();
  char key = keypad.getKey();

  if (state == WAITING) {
    if (now - lastSensorAt >= SENSOR_INTERVAL_MS) {
      lastSensorAt = now;
      if (distanceCm() <= VISITOR_DISTANCE_CM) {
        beginState(RINGING);
      }
    }
    return;
  }

  if (state == RINGING) {
    updateRinger();
    if (handsetLifted()) {
      beginState(CHOOSE_LANGUAGE);
      playTrack(TRACK_LANGUAGE_MENU);
    }
    return;
  }

  if (state == CHOOSE_LANGUAGE) {
    if (!handsetLifted()) {
      beginState(WAIT_FOR_CLEAR);
      return;
    }
    if (key == '1' || key == '2') {
      beginState(PLAYING_QUESTION);
      playTrack(key == '1' ? TRACK_QUESTION_ID : TRACK_QUESTION_EN);
    }
    return;
  }

  if (state == PLAYING_QUESTION) {
    if (!handsetLifted()) {
      beginState(WAIT_FOR_CLEAR);
      return;
    }
    // Set QUESTION_DURATION_MS to the actual length of 0002/0003 plus 1 second.
    if (now - stateStartedAt >= QUESTION_DURATION_MS) {
      beginState(RECORDING_ANSWER);
      digitalWrite(RECORDER_REC_PIN, HIGH);
    }
    return;
  }

  if (state == RECORDING_ANSWER) {
    if (!handsetLifted()) {
      beginState(WAIT_FOR_CLEAR);
      return;
    }
    // The ISD1820 stops automatically at its memory limit (normally about 10 seconds).
    if (key == '*') {
      digitalWrite(RECORDER_REC_PIN, LOW);
      beginState(GOODBYE);
      playTrack(TRACK_GOODBYE);
    }
    return;
  }

  if (state == GOODBYE) {
    if (!handsetLifted() || now - stateStartedAt >= GOODBYE_DURATION_MS) {
      beginState(WAIT_FOR_CLEAR);
    }
    return;
  }

  if (state == WAIT_FOR_CLEAR) {
    if (now - lastSensorAt >= SENSOR_INTERVAL_MS) {
      lastSensorAt = now;
      if (distanceCm() > VISITOR_DISTANCE_CM) {
        beginState(WAITING);
      }
    }
  }
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik