The rotary phone dial is the pinnacle of user interface for satisfying, slow number entry! Originally designed for dialing phone numbers, you can now re-purpose this beautiful piece of machinery as a USB number pad to use with your computer or mobile device.

With the KB2040 Kee Boar and a bit of CircuitPython code, you can read the mechanical dial pulses and convert them to USB HID number key messages. You can also modify the code to type letters, or go even further and make your phone send MIDI messages.

This project is non-destructive and fully reversible, should you decide to restore your phone to it's intended purpose.

Parts

Rotary Dial Phone

A model 500DM telephone or equivalent with rotary pulse dialing mechanism. Designed by Henry Dryfus, and manufactured by Western Electric, with millions of them made between 1950-1980s. You can find them in basements, attics, closets, garage sales, thrift/vintage shops, and online auctions.

Licensed models later made by ITT/Cortelco, Northern Electric/Northern Telecom, and Stromberg-Carlson will work just fine.

Angled shot of short black microcontroller.
A wild Kee Boar appears! It’s a shiny KB2040! An Arduino Pro Micro-shaped board for Keebs with RP2040. (#keeblife 4 evah) A lot of folks like using Adafruit...
$8.95
In Stock
Angled shot of an assembled USB DIY Connector Shell with a Type A Male Plug. The male plug faces the camera at an angle.
Make your own USB connections without slicing apart a USB cable and soldering those thin wires inside. These DIY "USB shells" are available in
$0.95
In Stock
2 x Terminal Block
4 pin Euro-style
Silicone Cover Stranded-Core Wire - 26AWG in Various Colors
Silicone-sheathing wire is super-flexible and soft, and it's also strong! Able to handle up to 200°C and up to 600V, it will do when PVC covered wire wimps out. We like this...
Out of Stock

RJ11 Cable

If you want to create an unholy "telephone-to-USB" cable, you'll need some four-conductor phone wire with a typical RJ11 connector on one end.

The connector should be a 6P4C type.

Note: 6P4C (six position, four conductor) RJ11 jacks are used to plug the phone base into the wall and are wider than the 4P4C (four position, four conductor) RJ9/10 jacks that are used to connect the handset to the phone base.

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.

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

Save it wherever is convenient for you.

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.

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

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

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

To enter safe mode when using CircuitPython, 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

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

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

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.

Once you've finished setting up your KB2040 with CircuitPython, you can access the code and necessary libraries by downloading the Project Bundle.

To do this, click on the Download Project Bundle button in the window below. It will download as a zipped folder.

# SPDX-FileCopyrightText: 2022 Tod Kurt & John Park for Adafruit Industries
#
# SPDX-License-Identifier: MIT
#
# Rotary phone USB keypad

import time
import board
import digitalio
import microcontroller
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
from adafruit_debouncer import Debouncer
import neopixel


dial_in = digitalio.DigitalInOut(board.RX)  # normally closed pulse dial switch
dial_in.pull = digitalio.Pull.UP
dial = Debouncer(dial_in)

receiver_in = digitalio.DigitalInOut(board.D2)  # normally open receiver switch
receiver_in.pull = digitalio.Pull.UP
receiver = Debouncer(receiver_in)


# check if usb_hid has been enabled in boot.py
if len(usb_hid.devices) == 0:
    on_hook = True
    print("on hook")
else:
    kbd = Keyboard(usb_hid.devices)
    on_hook = False
    print("off hook")

keymap = [
          Keycode.ONE,
          Keycode.TWO,
          Keycode.THREE,
          Keycode.FOUR,
          Keycode.FIVE,
          Keycode.SIX,
          Keycode.SEVEN,
          Keycode.EIGHT,
          Keycode.NINE,
          Keycode.ZERO
]

def read_rotary_dial_pulses(timeout=0.2):  # 0.2 is proper timing for pulses
    dial.update()
    if not dial.rose:  # NC dial pin is pulled low normally, high when open
        return 0
    pulse_count = 1
    last_pulse_time = time.monotonic()

    while time.monotonic() - last_pulse_time < timeout:  # count pulses that are within 0.2sec
        dial.update()
        if dial.rose:
            pulse_count = pulse_count+1
            last_pulse_time = time.monotonic()

    return pulse_count

pixel = neopixel.NeoPixel(board.NEOPIXEL, 1)


print("Rotary phone USB keypad")


while True:
    receiver.update()
    if receiver.fell:  # only dial when receiver is off hook
        print("Off hook")
        pixel[0] = 0x00ff00
        microcontroller.reset()  # the boot.py enables usb_hid if off hook

    if receiver.rose:
        print("On hook")
        pixel[0] = 0xff0000
        microcontroller.reset()  # the boot.py disables usb_hid if on hook

    # if not on_hook:
    num_pulses = read_rotary_dial_pulses()
    if num_pulses:
        print("pulse count:", num_pulses)
        if not on_hook:
            kbd.send(keymap[num_pulses-1])

Upload the Code and Libraries to the KB RP2040

After downloading the Project Bundle, plug your KB2040 into the computer USB port. You should see a new flash drive appear in the computer's File Explorer or Finder (depending on your operating system) called CIRCUITPY. Unzip the folder and copy the following items to the KB2040's CIRCUITPY drive. 

  • lib folder
  • code.py
  • boot.py

boot.py

This project is a bit unique in that is uses an extra file called boot.py to enable a special functionality. This page has lots of great details on using the boot sequence to enable and disable USB functions.

When a USB HID keyboard is used on a mobile device, the on-screen keyboard will typically disappear. In order to bring it back, you need to unplug the HID device, which is annoying. So, we've solved this by using the phone handset switch hook as an automatic enable/disable switch!

import usb_hid
import board
import digitalio

# set a pull-up
# If not pressed, the key will be at +V (due to the pull-up)
button = digitalio.DigitalInOut(board.D2)
button.pull = digitalio.Pull.UP

# Disable devices only if button is not pressed
# Phone receiver is normally open when handset is in place
if button.value:
    print("USB HID disabled")
    usb_hid.disable()

The boot.py code sets up the D2 pin on the KB2040 as an input pull up. When the phone handset is in the cradle during reset, the board starts up with USB HID disabled. If the phone handset is out of the cradle during reset, USB HID starts as usual.

This is initiated by the use of the D2 pin in the code.py as a trigger for the command microcontroller.reset()

Disassembly

To open up the phone, use a flat-head screwdriver to loosen the two screw in the base. The will not need to be fully removed, just unscrewed enough to release the shell.

Lift the shell off of the phone base.

Dial Pulse Switch

When a number is dialed on the rotary phone, the normally-closed pulse switch is opened and closed once per number dialed as the dial governor rotates back to the stop position. Dial a '1' and one pulse is sent. Dial a '7' for seven pulses. The '0' sends ten pulses.

The interval of these pulses in the US is a 100ms period, with 60ms closed and 40ms open. This can vary with phones from other countries.

Loosen the two screws holding the dial assembly to the mounting arms. Then, gently pull the dial free from the base, being careful not to pull on any wiring.

Use a multimeter to check the wiring -- one pair is connected to the speaker disconnect switch (which prevents the dialing pulse sounds from being heard in the earpiece), the other is connected to the pulse switch.

The blue pair on this dial is the pulse switch. Loosen the two screws on the network block and remove the blue wires -- you'll later connect them to the KB2040 to read the pulses.

Receiver Switch Hook

You'll use the receiver switch hook to enable or disable USB HID. This way, when the phone is hung up the virtual keyboard on a mobile device will be available, and when the phone receiver is picked up the dial will work as a number entry device.

Using a multimeter, determine which terminals of the network block are opened and closed by the switch hook. You'll run a wire from each of these terminal positions to the KB2040 later.

Optional RJ11-to-USB Cable Build

The simple thing to do is run a regular USB-C cable to the KB2040, snaking it through a gap in the phone shell. HOWEVER! You aren't here for simple, you're here for EXTRA. So to be totally extra, you're going to build a custom RJ11-to-USB cable, and then hijack the phone's RJ11 jack to run the wiring to the Kee Boar.

Cable Build

Cut one end off of your phone cable (make sure it is a four conductor RJ11 cable). A three foot length should work well (the stubby piece in the photo here is for illustration purposes only!)

Carefully remove the outer cable insulation, then strip and tin each of the four wires.

Solder the cable ends into the DIY USB-A plug in the order shown here.

USB Jack Wiring

In order to run the USB-to-RJ11 cable into the phone's RJ11 jack, you'll unscrew the three terminal screws connecting the yellow, green, and red wires from the network block. These will be connected to the KB2040, along with an added black wire, in case your phone's original jack didn't contain a fourth conductor. Different phones may have different numbers, colors, and positions of jack wires.

Wire colors and positions can vary, so it's important to use the continuity checker on your multimeter to connect the USB conductors to their associated pads on the KB2040.

These connections are:

  • Vcc to RAW
  • GND to GND
  • D- to D-
  • D+ to D+

KB2040 Wiring

Use terminal blocks (either a barrier block as shown here or Two Euro-style connector blocks) to run the phone wiring to the KB2040. Solder short lengths of wire to the KB2040 to make the connections.

The connections are shown on the Fritzing diagram above.

Euro-Style Terminal block with 4 pins
Connect this to that without soldering using these quick terminal blocks. They allow connecting of two sets of four wires together using just a flat-head screwdriver. These are...
$2.50
In Stock

Kee Boar Attachment

Use insulating Kapton tape or another material (I used adhesive felt) to protect the back of the Kee Boar from shorts.

Use a pair of zip ties to secure the Kee Boar to the base of the phone, making sure the terminal block and wiring is clear of any moving parts.

Re-Assembly

Place the dial assembly back into it's brackets and tighten the two screws.

Tuck any wires out of the way, then place the shell back on, making sure the jacks are secured against the housing.

Re-tighten the bottom screws and you're ready to go!

Plug the USB cable into your computer or mobile device. With the receiver handset in the cradle it will not show up as a USB HID device.

Lift the receiver. The board will restart, this time with USB HID enabled, thanks to the boot.py file code. On a mobile device such as the iPad, this will cause the virtual keyboard to disappear, indicating that the device is ready to receive "keyboard" input.

Dial numbers on the rotary dial, and they will be sent to the device over USB!

Hang up the phone when you're done to resume virtual key entry.

This guide was first published on Feb 08, 2022. It was last updated on Jan 31, 2022.