Maker.io main logo

Touch Tone Phone Dial-a-Song

2022-09-06 | By Adafruit Industries

License: See Original Project Programmers Adafruit Feather RP2040

Courtesy of Adafruit

Guide by John Park

Overview

 

 

Build a self-contained Dial-a-Song using a Western Electric 2500DM ‎telephone and a Feather RP2040 + mono amplifier. People can dial a ‎number to hear a song or message played through the handset ‎earpiece. The CircuitPython keypad library makes it simple to read ‎the Touch Tone keypad matrix.‎

It's a fun conversation piece -- especially with its accurate dial tone, ‎touch tones, and other teleco messages -- and perfect for escape ‎rooms or other puzzle games.‎

I'm not going to tell you how to live your life. However, should you ‎choose to put some They Might Be Giants songs on there, you'll be a ‎hero.‎

Parts

You'll also need a 2.2µF electrolytic capacitor for the RC noise filter ‎circuit, although in a bind you can use this 10µF one.‎

Western Electric 2500DM Telephone

The classic! Grab a vintage Touch Tone phone out of your ‎attic/basement or hit a flea market or yard sale. In a bind, you can ‎find them on an online auction site for a reasonable amount.‎

Some older phones will come with a mechanical switching matrix, in ‎which case you'll need to get a "newer" (early-1980s) electronic ‎keypad, such as this one.‎

I haven't opened up the modern Cortelco ITT 2500, so I can't say if it ‎can be wired the same way the older phones could.‎

phone_1

Install CircuitPython

CircuitPython is a derivative of MicroPython designed to simplify ‎experimentation and education on low-cost microcontrollers. It ‎makes it easier than ever to get prototyping by requiring no upfront ‎desktop software downloads. Simply copy and edit files on ‎the CIRCUITPY drive to iterate.‎

CircuitPython QuickStart

Follow this step-by-step to quickly get CircuitPython running on your ‎board.‎

Download the latest version of CircuitPython for this board via ‎circuitpython.org

Click the link above to download the latest CircuitPython UF2 file.‎

Save it wherever is convenient for you.‎

file_2

board_3

To enter the bootloader, hold down the BOOT/BOOTSEL ‎button (highlighted in red above), and while continuing to hold it ‎‎(don't let go!), press and release the reset button (highlighted in ‎blue above). Continue to hold the BOOT/BOOTSEL button until the ‎RPI-RP2 drive appears!‎

If the drive does not appear, release all the buttons, and then repeat ‎the process above.‎

You can also start with your board unplugged from USB, press, and ‎hold the BOOTSEL button (highlighted in red above), continue to ‎hold it while plugging it into USB, and wait for the drive to appear ‎before releasing the button.‎

A lot of people end up using charge-only USB cables and it is very ‎frustrating! Make sure you have a USB cable you know is good for ‎data sync.‎

You will see a new disk drive appear called RPI-RP2.‎‎

‎Drag the adafruit_circuitpython_etc.uf2 file to RPI-RP2.‎

drive_4

drive_5

The RPI-RP2 drive will disappear, and a new disk drive ‎called CIRCUITPY will appear.‎

That's it, you're done! :)‎

disk_6

Safe Mode

You want to edit your code.py or modify the files on ‎your CIRCUITPY drive but find that you can't. Perhaps your board ‎has gotten into a state where CIRCUITPY is read-only. You may have ‎turned off the CIRCUITPY drive altogether. Whatever the reason, safe ‎mode can help.‎

Safe mode in CircuitPython does not run any user code on startup ‎and disables auto-reload. This means a few things. First, safe ‎mode bypasses any code in boot.py (where you can ‎set CIRCUITPY read-only or turn it off completely). Second, it does ‎not run the code in code.py. And finally, it does not automatically ‎soft-reload when data is written to the CIRCUITPY drive.‎

Therefore, whatever you may have done to put your board in a non-‎interactive state, safe mode gives you the opportunity to correct it ‎without losing all of the data on the CIRCUITPY drive.‎

Entering Safe Mode in CircuitPython 6.x

This section explains entering safe mode on CircuitPython 6.x.‎

mode_7

To enter safe mode when using CircuitPython 6.x, plug in your board ‎or hit reset (highlighted in red above). Immediately after the board ‎starts up or resets, it waits 700ms. On some boards, the onboard ‎status LED (highlighted in green above) will turn solid yellow during ‎this time. If you press reset during that 700ms, the board will start up ‎in safe mode. It can be difficult to react to the yellow LED, so you ‎may want to think of it simply as a slow double click of the reset ‎button. (Remember, a fast double click of reset enters the ‎bootloader.)‎

Entering Safe Mode in CircuitPython 7.x

This section explains entering safe mode on CircuitPython 7.x.‎

To enter safe mode when using CircuitPython 7.x, plug in your board ‎or hit reset (highlighted in red above). Immediately after the board ‎starts up or resets, it waits 1000ms. On some boards, the onboard ‎status LED (highlighted in green above) will blink yellow during that ‎time. If you press reset during that 1000ms, the board will start up in ‎safe mode. It can be difficult to react to the yellow LED, so you may ‎want to think of it simply as a slow double click of the reset button. ‎‎(Remember, a fast double click of reset enters the bootloader.)‎

In Safe Mode

Once you've entered safe mode successfully in CircuitPython 6.x, the ‎LED will pulse yellow.

‎If you successfully enter safe mode on CircuitPython 7.x, the LED will ‎intermittently blink yellow three times.

‎If you connect to the serial console, you'll find the following message.‎

Copy Code
Auto-reload is off.
Running in safe mode! Not running saved code.

CircuitPython is in safe mode because you pressed the reset button during boot. Press again to exit safe mode.

Press any key to enter the REPL. Use CTRL-D to reload.

You can now edit the contents of the CIRCUITPY drive. ‎Remember, your code will not run until you press the reset button, or ‎unplug and plug in your board, to get out of safe mode.‎

Flash Resetting UF2‎

If your board ever gets into a really weird state and doesn't even ‎show up as a disk drive when installing CircuitPython, try loading ‎this 'nuke' UF2 which will do a 'deep clean' on your Flash ‎Memory. You will lose all the files on the board, but at least you'll be ‎able to revive it! After loading this UF2, follow the steps above to re-‎install CircuitPython.‎

Download flash erasing "nuke" UF2‎

Code the Dial-a-Song

Text Editor

Adafruit recommends using the Mu editor for editing your ‎CircuitPython code. You can get more info in this guide.‎

Alternatively, you can use any text editor that saves simple text files.‎

Download the Project Bundle

Your project will use a specific set of CircuitPython libraries, folders ‎of .wav file assets, and the code.py file. To get everything you need, ‎click on the Download Project Bundle link below, and uncompress ‎the .zip file.‎

Drag the contents of the uncompressed bundle directory onto your ‎Feather board's CIRCUITPY drive, replacing any existing files or ‎directories with the same names, and adding any new ones that are ‎necessary.‎

bundle_8

‎Download Project Bundle‎

Copy Code
# SPDX-FileCopyrightText: 2022 John Park for Adafruit Industries
#
# SPDX-License-Identifier: MIT
#
# DTMF keypad phone Dial-a-Song
import time
import random
import board
import keypad
from audiocore import WaveFile
from audiopwmio import PWMAudioOut as AudioOut  # for RP2040 etc
import audiomixer

# time.sleep(3)  # let USB settle during development, remove when on battery

km = keypad.KeyMatrix(
    # 2500 phone ignoring first column store/redial/memory. reverse mount on Feather RP2040
    column_pins=(          board.A3, board.A2, board.A1,),
    row_pins=(
                board.D24,
                board.D25,
                board.SCK,
                board.MOSI,
                 ),
)

numbers = {
            "8675309" : "songs/beepbox.wav",
            "6358393" : "songs/streetchicken.wav",
            "5551212" : "songs/carpeter.wav",
            "7654321" : "songs/daisy.wav"
}

ringing = "songs/full_ring.wav"
wrong_number = "songs/blank_number.wav"
dial_tone = "songs/dial_tone_loop.wav"
busy_signal = "songs/busy_loop.wav"

button_tones = [
                "dtmf/tt_1.wav", "dtmf/tt_2.wav",  "dtmf/tt_3.wav",
                "dtmf/tt_4.wav", "dtmf/tt_5.wav", "dtmf/tt_6.wav",
                "dtmf/tt_7.wav", "dtmf/tt_8.wav", "dtmf/tt_9.wav",
                "dtmf/tt_star.wav", "dtmf/tt_0.wav", "dtmf/tt_pound.wav"
]

digits_entered = 0  # counter
dialed = []  # list of digits user enters to make one 7 digit number
dialed_str = ""  # stores the phone number string for dictionary comparison

audio = AudioOut(board.TX)  # PWM out pin
mixer = audiomixer.Mixer(
    voice_count=4,
    sample_rate=22050,
    channel_count=1,
    bits_per_sample=16,
    samples_signed=True,
)
audio.play(mixer)
mixer.voice[0].level = 1.0  # dial tone voice
mixer.voice[1].level = 1.0  # touch tone voice
mixer.voice[2].level = 0.0  # song/message voice
mixer.voice[3].level = 0.0  # busy signal

wave_file0 = open(dial_tone, "rb")
wave0 = WaveFile(wave_file0)
mixer.voice[0].play(wave0, loop=True)  # play dial tone

wave_file2 = open(wrong_number, "rb")
wave2 = WaveFile(wave_file2)

wave_file3 = open(busy_signal, "rb")
wave3 = WaveFile(wave_file3)
mixer.voice[3].play(wave3, loop=True)  # play dial tone


def reset_number():
    # pylint: disable=global-statement
    global digits_entered, dialed, dialed_str
    digits_entered = 0
    dialed = []
    dialed_str = ""
    km.events.clear()


while True:

    event = km.events.get()  # check for keypad presses
    if event:
        if event.pressed:
            mixer.voice[0].level = 0.0  # mute the dial tone
            wave_file1 = open(button_tones[event.key_number], "rb")  # play Touch Tone
            wave1 = WaveFile(wave_file1)
            mixer.voice[1].play(wave1)
            if event.key_number == 9 or event.key_number == 11:  # check for special keys
                if event.key_number == 9:  # pressed the '*' key
                    reset_number()   # or make some cool new function for this key
                if event.key_number == 11:  # pressed the '#' key
                    reset_number()  # or make some cool new function for this key

            else:  # number keys
                if digits_entered < 7:  # adding up to full number
                    # convert event to number printed on the keypad button, append to string
                    if event.key_number < 9:  # 1-9 on keypad
                        dialed.append(event.key_number+1)
                    if event.key_number == 10:  # the 0 key, ignore '*' and "#'
                        dialed.append(0)
                    dialed_str = "".join(str(n) for n in dialed)
                    digits_entered = digits_entered + 1  # increment counter

                if digits_entered == 7:  # a full number has been entered
                    if not mixer.voice[2].playing:
                        dialed_str = "".join(str(n) for n in dialed)
                        if dialed_str in numbers:  # check if dialed string is one in the directory
                            value = numbers[dialed_str]
                            time.sleep(0.6)

                            wave_file2 = open(ringing, "rb")  # ring before it answers
                            wave2 = WaveFile(wave_file2)
                            mixer.voice[2].level = 1.0
                            mixer.voice[2].play(wave2, loop=True)

                            time.sleep(random.uniform(4.0, 9.5))  # random ring before "answer"

                            wave_file2 = open(value, "rb")  # answered
                            wave2 = WaveFile(wave_file2)
                            mixer.voice[2].level = 1.0
                            mixer.voice[2].play(wave2, loop=True)

                        else:  # number is not in directory
                            time.sleep(0.5)
                            weighted_coin_toss = random.randint(0, 4)
                            if weighted_coin_toss < 3:  # favor the "not in service" message
                                mixer.voice[2].level = 1.0
                                mixer.voice[2].play(wave2)
                            else:
                                mixer.voice[3].level = 1.0

                        reset_number()

                    if mixer.voice[2].playing:
                        reset_number() # stop #s dialed during message play from doing anything

View on GitHub

Custom Phone Numbers

Create your own directory of valid numbers and their ‎associated .wav files in the numbers dictionary:‎

Download File

Copy Code
numbers = {
            "8675309" : "songs/beepbox.wav",
            "6358393" : "songs/streetchicken.wav",
            "5551212" : "songs/carpeter.wav",
            "7654321" : "songs/daisy.wav"
}

Custom Songs and Messages

To create your own song and message files (great for ‎granting/denying entrance to your secret speakeasy) convert your ‎audio files to 16-bit mono WAV files at 22KHz sample rate. This ‎guide shows how.‎

Build the Dial-a-Song Circuit

diagram_9

The Circuit

The circuit above is the breadboarded version of the circuit you'll ‎build on a FeatherWing Doubler. You may want to test it on a ‎breadboard before moving to soldering it together.‎

The sections of the circuit are:‎

  • Feather RP2040 A1-M0 connected to keypad matrix

  • Feather RP2040 TX pin connects to audio RC filter circuit to ‎reduce noise, then to A+ input of the amp breakout

  • Amp output connect to phone handset speaker

  • Phone switch hook connected to Feather En and GND

  • USB cable breakout power and GND to Feather USB and GND

circuit_10

FeatherWing Proto Area Circuit

Transfer the circuit from breadboard to the proto area of the ‎FeatherWing Doubler.‎

The seven-position header will be soldered to the underside of the ‎board so it can be plugged into the back of the matrix keypad later ‎‎(see below).‎

Solder two black lead wires with ferrule crimped ends to the Doubler ‎GND.‎

Solder another wire to En (this will enable/disable the board, ‎essentially powering it off when the handset is in the cradle).‎

Solder a red wire with ferrule end to the USB pin on the Doubler. This ‎will be connected to USB power for battery charging.‎

proto_11

proto_12

proto_13

proto_14

Fit the amp and Feather onto the Doubler.‎

doubler_15

Keypad Preparation

The keypad assembly is composed of two assemblies -- the DTMF ‎circuit board, and the keypad matrix itself. We don't need the DTMF ‎board, so we'll desolder the ribbon cable and then add header pins ‎to connect to the FeatherWing.‎

Use a solder sucker and/or desoldering wick to remove the ribbon ‎cable cleanly.‎

clean_16

clean_17

clean_18

clean_19

clean_20

clean_21

Here you can see how the keypad works -- the plastic buttons press ‎down on the elastomer pads, causing the conductive rubber pills to ‎short the associated row/column traces.‎

Note, the pcb is single sided with the exception of the row two and ‎row three bridges routed as wires through the back of the board!‎

bridges_22

bridges_23

Keypad Header Pins

Solder in the header pins -- the matrix pcb has eight pads, but we ‎only need to connect to the first seven, based on how the matrix is ‎designed, using three columns and four rows.‎

keypad_24

Attach the Boards

Plug in a LiPo battery to the Feather, then fit it behind the keypad.‎

Press the Doubler socket in to the first seven pins of the keypad.‎

attach_26

attach_25

attach_27

boards_28

Next, you'll integrate the Feather board with the rest of the phone.‎

Assemble the Dial-a-Song

The 2500 has a network block (potted bolus of electronics that make ‎it tick) with all of the wiring we need attached via screw terminals.‎

You'll disconnect some of these wires to connect the switch hook ‎and receiver earpiece to the Feather.‎

Earpiece Connection

Locate the wires running to the earpiece from the RJ9 jack. You can ‎unscrew the handset mouthpiece or earpiece to see which wires run ‎to the speaker, in this case, green and white wires.‎

Unscrew those two wires from the phone's network block and screw ‎them into the terminal block on the amplifier board.‎

connection_30

connection_31

connection_32

connection_33

connection_29

Switch Hook Connection

Use a multimeter to find two connection points on the network block ‎that are opened and closed when the switch hook is engaged.‎

Run two blue wire leads with ferrule connectors on their ends to ‎these two points and screw them in place. (Fragile bits of plastic ‎snapped off on mine, hence the Kapton tape).‎

Use the Euro block connector to connect the two blue switch hook ‎wires to the two black En/GND wires.‎

switch_34

switch_35

switch_36

connection_33a

connection_38

USB Power

You'll run USB power to the Feather in order to charge the LiPo ‎battery when needed.‎

Prepare a black jumper wire and a red jumper wire with ferrule ends ‎that can be used in the Euro block on one end and DuPont ‎connector pins on the other (premium silicone jumper wires work ‎well for this).‎

Run the header end of the USB breakout cable through the RJ11 jack, ‎then connect the two power extension wires. You can ignore the ‎USB data lines.‎

Connect the power and ground lines to their respective leads on the ‎Feather Doubler using the Euro block.‎

usb_39

usb_40

usb_41

usb_42

usb_43

power_44

power_45

The receiver and USB power are now connected to the Feather, ‎which is plugged into the keypad. You should test the following:‎

  • switch hook works to enable/disable the Feather

  • audio comes through the earpiece speaker

  • keypad entry is properly registered

  • USB charges the battery

You can now start re-assembling the phone.‎

Mount the Keypad

Secure the keypad in the metal brackets, then tighten the screws.‎

mount_47

mount_46

keypad_48

Shell

Place the shell onto the base, making sure the RJ jacks fit their ‎respective places.‎

Tighten the two captive screws in the base.‎

shell_49

shell_50

shell_51

phone_52

cable_53

Use the Dial-a-Song

Using the phone is simple! Pick up the receiver, wait for the dial tone, ‎and then call one of the seven-digit numbers in the directory.‎

The phone will ring a randomized number of times and then "pick ‎up", playing the song you wanted!‎

If you dial a wrong number, you'll hear either a busy signal or the ‎‎"not in service" message.‎

 

 

制造商零件编号 4884
ADAFRUIT FEATHER RP2040
Adafruit Industries LLC
制造商零件编号 2890
FEATHERWING DOUBLER - PROTOTYPIN
Adafruit Industries LLC
制造商零件编号 2130
EVAL BOARD FOR PAM8302A
Adafruit Industries LLC
制造商零件编号 5131
WIRE FERRULE KIT - 800 PIECES
Adafruit Industries LLC
制造商零件编号 4448
CBL ASSY USB M-CB W/JUMPER 0.98'
Adafruit Industries LLC
制造商零件编号 4482
SILICONE M-M JUMPER 200MM X 40
Adafruit Industries LLC
Add all DigiKey Parts to Cart
TechForum

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.

Visit TechForum