RP2040 vband CW key adapter
Ham Radio Solutions sells a $30 device that lets you connect your CW key to your phone or computer. It essentially acts as a keyboard that sends a left control or right control based on which side of a paddle you connect to.
I wanted to be able to use my CW key with my Android phone for practicing on the go, but I didn’t really want to spend $30 to do that.
I thought that I could build an adapter pretty cheaply so I got a $10 Seeed Studio RP2040 board (Amazon link), and a $1.30 3.5mm TRS jack (Amazon link), and whipped together some Circuit Python Code.
Now, using a Dual-Core ARM Cortex chip clocked at 133MHz is absolute overkill for this project. I’m sure someone with more electrical engineering experience than myself is scoffing at this and saying, “I could have done that with a 50 cent 555 timer”. Yeah, well, you know what? You’re a real engineer, and I’m just a software engineer.
The schematic is pretty simple. I wired the Tip, Ring, and Sleeve to corresponding pins on the board:
| PIN | TRS Contact | CW Key |
|---|---|---|
| D1 | Tip | Dit |
| D2 | Ring | Dah |
| GND | Sleeve | Ground |
I used Circuit Python to program the board, because again, who cares about performance, we’re just pressing keyboard keys fast enough for CW. I’m not fast enough at CW to where I’m outperforming the Python code.
To install Circuit Python onto the board, I followed the Seeed Studio XIAO RP2040 Circuit Python instructions to install the firmware onto the device. The RP2040 board then will became a drive that I can copy code onto.
In order to make the device act like a keyboard, I needed to copy the adafruit_hid library from the Adafruit Circuit Python Bundle into the lib directory. Just download the latest release and copy the directory into the device’s lib directory.
The code is pretty straight-forward. Read the pins, decide what actions to perform based on the pin states, and then do those actions.
import time
import board
import digitalio
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
# Prepare the PIN for input
def setup_pin(pin):
key_pin = digitalio.DigitalInOut(pin)
key_pin.direction = digitalio.Direction.INPUT
key_pin.pull = digitalio.Pull.UP
return key_pin
# A helper that returns true if the keypress is down
def io_pin_down(pin) -> bool:
return not pin.value
# Setup the DIT variables
dit_pin = setup_pin(board.D1)
dit_keycode = Keycode.LEFT_BRACKET
# Setup the DAH variables
dah_pin = setup_pin(board.D2)
dah_keycode = Keycode.RIGHT_BRACKET
# Setup the Keyboard object
keyboard = Keyboard(usb_hid.devices)
# PressAction is returned when handle_pin decides that the keycode for
# the pin should be pressed
class PressAction:
pass
# ReleaseAction is returned when handle_pin decides that the keycode
# for the pin should be pressed
class ReleaseAction:
pass
# A state handler that compares the previous state of the pin with its
# current state
#
# The return value is the current state of the pin, and one of three
# possible actions to perform:
#
# 1. PressAction - Press the key
# 2. ReleaseAction - Release the key
# 3. None - Do nothing
def handle_pin(
previous_state, current_state
) -> (bool, PressAction | ReleaseAction | None):
if previous_state == current_state:
return current_state, None
elif current_state:
return current_state, PressAction
else:
return current_state, ReleaseAction
# The effects handler. This performs the action if an action is needed
#
def io_act_on_pin(
keyboard, keycode, action: PressAction | ReleaseAction | None
):
if action == PressAction:
keyboard.press(keycode)
elif action == ReleaseAction:
keyboard.release(keycode)
# Initialize the pin states
dit_state = False
dah_state = False
#
# This code follows the "Functional Core, Imperative Shell" pattern
# Made famous by Gary Bernhardt's Boundaries talk <https://www.youtube.com/watch?v=eOYal8elnZk>
#
# All the IO occurs in this loop, with the pure handle_pin function
# providing the logic that decides what action to perform.
#
while True:
# Read the current states and determine what actions to perform
dit_state, dit_action = handle_pin(dit_state, io_pin_down(dit_pin))
dah_state, dah_action = handle_pin(dah_state, io_pin_down(dah_pin))
# Perform the actions for each pin
io_act_on_pin(keyboard, dit_keycode, dit_action)
io_act_on_pin(keyboard, dah_keycode, dah_action)
# Sleep a little
time.sleep(0.001)