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

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

This page (Code the Rotary Dialer) was last updated on May 31, 2023.

Text editor powered by tinymce.