Overview
The TinyUSB library underpins many of CircuitPython's USB capabilities. It's what lets CircuitPython act as a storage drive, CDC, HID, MIDI and more. Starting with version 10.3.0-alpha.3 CircuitPython has added support for USB audio types. This guide demonstrates how to use the USB microphone capability to generate tones and output them to a computer or other digital audio equipment. You can connect a basic key to tap in Morse code live, and use the generator function to automatically convert strings into beeps and boops.
Morse Code Key with 3.5mm Jack
Morse keys come in different shapes and styles, but all are electronically similar. They are a basic momentary switch that closes a circuit when you press the key, and opens the circuit when you release the key. They can be found easily in online shops and radio equipment providers, or DIY'd with a 3D printer, spring, and conductive contacts. I used this one.
Page last edited June 29, 2026
Text editor powered by tinymce.
Install CircuitPython
Install or Update CircuitPython
Follow this quick step-by-step to install or update CircuitPython on your Circuit Playground Bluefruit.
usb_audio module was added to CircuitPython in version 10.3.0-alpha.3. Running this project requires using the development release of CircuitPython until there is a 10.3.0 stable release. On the downloads page scroll down to the latest development release panel.
Click the link above and download the latest UF2 file
Download and save it to your Desktop (or wherever is handy)
Plug your Circuit Playground Bluefruit into your computer using a known-good data-capable USB cable.
A lot of people end up using charge-only USB cables and it is very frustrating! So make sure you have a USB cable you know is good for data sync.
Double-click the small Reset button in the middle of the CPB (indicated by the red arrow in the image). The ten NeoPixel LEDs will all turn red, and then will all turn green. If they turn all red and stay red, check the USB cable, try another USB port, etc. The little red LED next to the USB connector will pulse red - this is ok!
If double-clicking doesn't work the first time, try again. Sometimes it can take a few tries to get the rhythm right!
(If double-clicking doesn't do it, try a single-click!)
You will see a new disk drive appear called CPLAYBTBOOT.
Drag the adafruit_circuitpython_etc.uf2 file to CPLAYBTBOOT.
Page last edited June 29, 2026
Text editor powered by tinymce.
Code
Getting the Program's Files
To use the application, you need to obtain code.py with the program, and boot.py to place on the Bluefruit CIRCUITPY drive.
Thankfully, this can be done in one go. Click the Download Project Bundle button below to download the project's code.py and boot.py in a zip file.
Connect your board to your computer via a known good data+power USB cable. The board should show up in your File Explorer/Finder (depending on your operating system) as a flash drive named CIRCUITPY.
Extract the contents of the zip file. Copy the code.py file, as well as boot.py folder to your CIRCUITPY drive. You need to reset the device once after pasting the code files so that the USB configuration in boot.py will take effect. Use the reset button or unplug then re-connect it.
# SPDX-FileCopyrightText: 2026 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""USB audio Morse code paddle. Connect Morse paddle 3.5mm jack to pin A1 and GND.
Connect the board to a host computer and select the CircuitPython USB
microphone as a recording/input device.
Use the paddle to enter Morse code live, update the button messages to
automatically convert and send pre-programmed strings with the built-in
buttons.
"""
import time
import board
import keypad
import synthio
import usb_audio
# Configuration
BTN_A_MESSAGE = "HELLO WORLD"
BTN_B_MESSAGE = "CIRCUITPYTHON"
TONE_NOTE = 60 # synthio note number for the tone pitch (MIDI note, 60 = C4)
# Morse timing is defined in "units". A dot is one unit long; everything else
# is a multiple of that unit. Increase UNIT_SECONDS to slow the code down.
UNIT_SECONDS = 0.085
DOT_DURATION = UNIT_SECONDS # length of a dot
DASH_DURATION = UNIT_SECONDS * 3 # length of a dash
SYMBOL_GAP = UNIT_SECONDS # silence between dots/dashes in one letter
LETTER_GAP = UNIT_SECONDS * 3 # silence between letters
WORD_GAP = UNIT_SECONDS * 7 # silence between words (the " " character)
# Morse code table
MORSE_CODE = {
"A": ".-",
"B": "-...",
"C": "-.-.",
"D": "-..",
"E": ".",
"F": "..-.",
"G": "--.",
"H": "....",
"I": "..",
"J": ".---",
"K": "-.-",
"L": ".-..",
"M": "--",
"N": "-.",
"O": "---",
"P": ".--.",
"Q": "--.-",
"R": ".-.",
"S": "...",
"T": "-",
"U": "..-",
"V": "...-",
"W": ".--",
"X": "-..-",
"Y": "-.--",
"Z": "--..",
"0": "-----",
"1": ".----",
"2": "..---",
"3": "...--",
"4": "....-",
"5": ".....",
"6": "-....",
"7": "--...",
"8": "---..",
"9": "----.",
".": ".-.-.-",
",": "--..--",
"?": "..--..",
"'": ".----.",
"!": "-.-.--",
"/": "-..-.",
"(": "-.--.",
")": "-.--.-",
"&": ".-...",
":": "---...",
";": "-.-.-.",
"=": "-...-",
"+": ".-.-.",
"-": "-....-",
"_": "..--.-",
'"': ".-..-.",
"$": "...-..-",
"@": ".--.-.",
}
# Setup
mic = usb_audio.usb_microphone
synth = synthio.Synthesizer(sample_rate=16000, channel_count=1)
mic.play(synth)
def play_tone(duration):
"""Sound the tone for ``duration`` seconds, then go silent."""
synth.press(TONE_NOTE)
time.sleep(duration)
synth.release(TONE_NOTE)
def play_morse(text):
"""Translate ``text`` to Morse code and play it with synthio."""
words = text.upper().split(" ")
for word_index, word in enumerate(words):
if word_index > 0:
# gap between words
time.sleep(WORD_GAP)
for letter_index, letter in enumerate(word):
pattern = MORSE_CODE.get(letter)
if pattern is None:
# skip characters we don't know how to send
continue
if letter_index > 0:
# gap between letters
time.sleep(LETTER_GAP)
for symbol_index, symbol in enumerate(pattern):
if symbol_index > 0:
# gap between symbols within a letter
time.sleep(SYMBOL_GAP)
if symbol == ".":
play_tone(DOT_DURATION)
else:
play_tone(DASH_DURATION)
btns_builtin = keypad.Keys((board.BUTTON_A, board.BUTTON_B), value_when_pressed=True)
btn_morse_key = keypad.Keys((board.A1,), value_when_pressed=False)
# Main loop
while True:
event = btns_builtin.events.get()
if event is not None:
# built-in A button
if event.pressed and event.key_number == 0:
play_morse(BTN_A_MESSAGE)
# built-in B button
elif event.pressed and event.key_number == 1:
play_morse(BTN_B_MESSAGE)
event = btn_morse_key.events.get()
if event is not None:
if event.pressed and event.key_number == 0:
synth.press(TONE_NOTE)
elif not event.pressed and event.key_number == 0:
synth.release(TONE_NOTE)
Drive Structure
After copying the files, your CIRCUITPY drive should look like the listing below. It can contain other files as well, but must contain these at a minimum.
Page last edited June 29, 2026
Text editor powered by tinymce.
Code Explanation
This project requires two code files, boot.py and code.py.
boot.py
Inside boot.py the USB microphone USB endpoint is enabled and audio settings are configured.
# SPDX-FileCopyrightText: 2026 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import usb_audio
usb_audio.enable(
sample_rate=16000, channel_count=1, bits_per_sample=16, microphone=True
)
Configuration
The first section contains a handful of configuration variables. The pre-loaded message strings BTN_A_MESSAGE and BTN_B_MESSAGE are used for the built-in A and B button hotkey messages, change them to use customized messages.
UNIT_SECONDS is the length in seconds of a single unit in the Morse playback. In standard Morse code, the dot beep lasts one unit of time. Increase the number to make the automatic messages play slower. This value has no effect on live keyed Morse code.
The remaining duration and gap variables are all set relative to UNIT_SECONDS. They use multiples of UNIT_SECONDS based on the standards of International Morse code. Typically you don't want to change them directly, instead just change UNIT_SECONDS. But, you can experiment with different values for them if you don't mind breaking from the protocol specification.
This section contains a Python dictionary that maps letters, numbers, and characters to their Morse code counterparts with dot and dash syntax that uses period and hyphen characters. The same mapping that can be found in Morse code tables.
The dot and dash representation string is then used with synthio to play the appropriate length tone.
Setup
The setup section initializes synthio and usb_microphone and sets synthio to play into the microphone audio stream. Two helper functions are defined:
-
play_tone()plays a tone for a specified duration and then stops -
play_morse()accepts a string of text, automatically converts it to Morse code, and plays it into the USB audio stream.
The buttons are initialized using the keypad module. One instance is of keypad.Keys is used for the built-in A and B buttons, and another instance for the Morse key IO pin.
Page last edited June 29, 2026
Text editor powered by tinymce.
Use
The only wiring required is to connect the 3.5mm cable plug into the Morse code key and connect the alligator clips on the other end of the 3.5mm cable to pins A1 (white wire) and GND (black wire) on the Circuit Playground Bluefruit.
With the project boot.py and code.py files running on the Circuit Playground Bluefruit, the board will appear to the computer as a USB microphone in addition to the usual CIRCUITPY drive and serial console.
The exact steps to select it for a given application will vary. Generally you'll need to either set the CPB as your system default mic in the system settings (Ubuntu Sound input settings screenshot shown here), or set the input within a specific application such as Audacity or Discord.
In Audacity, click Audio Setup -> Recording Devices, then click on Circuit Playground Bluefruit USB Audio in the list of devices.
In Discord click the dropdown arrow on the red microphone button, then click Input Devices -> Circuit Playground Bluefruit Mono
Pressing the Morse code key will generate a tone into the audio stream via USB to the computer. Use a short press for a dot, and a 3x longer press for a dash. Refer to a Morse code chart if you're new to Morse or need a refresher on the alphabet. See if you can decode the short Morse code message keyed with the device from the wave file recording below.
Preprogrammed Messages
The code allows you to have up to two preprogrammed messages. They are stored as string variables in code.py and can be sent with the built-in A and B buttons on the CPB. Change the value of the two variables near the top of the code.
BTN_A_MESSAGE = "HELLO WORLD" BTN_B_MESSAGE = "CIRCUITPYTHON"
Press the A or B button on the CPB to automatically convert the specified message to Morse code and play it into the USB audio stream.
Page last edited June 29, 2026
Text editor powered by tinymce.