Browser-Based Morse Practice Oscillator (and Why Morse Timing Is a Protocol-Design Masterclass)
Project summary: A pocket-sized Morse code practice oscillator you can build in an evening — an Arduino Nano, a piezo buzzer, one push button, and the correct ITU timing — plus a browser-based decoder to check your sending. Everything fits in a mint tin. Total cost under $10.
1. Why bother learning Morse in 2026?
Morse code turns 190 in 2026 (depending on which milestone you count — American Morse in 1836, International Morse standardised by the ITU in 1865). It is still tested in niche amateur-radio licensing paths, still used by QRP portable operators who can get a 5-watt signal across an ocean when voice modes fail, and still the recommended low-bandwidth fallback in emergency-communication plans.
But this project isn't really about emergency preparedness. It's about timing as protocol design. Morse code is arguably the first widely deployed variable-length digital encoding: before Huffman coding (1952), before the ITU standardised it, telegraph engineers in the 1840s had already solved the core problem — the most frequent letters should have the shortest codes. E is one dit. T is one dah. That's compression by statistics, worked out with brass keys and ears.
If you write software or build hardware, Morse is worth learning because it forces you to internalise what a protocol is: an agreement about timing, framing, and error handling, implemented in the cheapest medium available. A practice oscillator is the "hello world" of that agreement — and it's genuinely fun to build.
2. The timing specification (the part everyone gets wrong)
Most hobby oscillators get the timing subtly wrong, which means you train your ear on the wrong protocol. Here is the actual standard — the International Morse timing from ITU Recommendation M.1677 — stated in units, where one unit ("dit length") is the fundamental atom:
- Dit (·): 1 unit, key down.
- Dah (−): 3 units, key down.
- Gap between dits and dahs within one character: 1 unit, key up.
- Gap between characters: 3 units, key up.
- Gap between words: 7 units, key up.
The speed of Morse is measured in words per minute (WPM), anchored to the standard word PARIS: 50 units. At 20 WPM, one unit is exactly 60 ms; at 5 WPM (a comfortable learning speed), one unit is 240 ms. These ratios — 1:3:1:3:7 — are the whole protocol. Get them right and everything else follows; get them wrong and you're speaking a dialect no one decodes.
The practical implication for the build: the microcontroller should own the timing, not your finger. Beginners' biggest problem is inconsistent gaps. So this project has two operating modes: manual keying (you send, the firmware measures you against the spec and shows your error) and trainer mode (the Arduino sends correctly timed characters and you learn the sound by ear).
3. What you'll need
Parts
- Arduino Nano (any ATmega328P board works; a Nano Every or classic Nano is fine)
- Piezo buzzer, active or passive (passive lets you set the sidetone frequency — 600–700 Hz is standard for practice)
- Tactile push button (this is your key)
- 10 kΩ resistor (button pull-down), 220 Ω resistor (optional, for an LED)
- LED (visual feedback, doubles as the "light signaling" demo)
- Breadboard or perfboard, jumper wires
- Power: USB cable for the bench; a 9 V battery + barrel jack (or a phone power bank) for the "mint tin" portable version
- Optional: second button or a small potentiometer for speed control
Software
- Arduino IDE (or arduino-cli)
- A browser with a Morse decoder for verifying your sending — I use the free practice tools at https://morsezone.com/ (their translator doubles as a sending checker: type your message, play the audio, and compare it against what you keyed)
Total cost: under $10 if you have an Arduino in a drawer; under $25 with a new Nano clone and a mint tin.
4. The circuit
This is deliberately minimal:
-
Key (tactile button) between D2 and GND, with a 10 kΩ pull-down to GND so D2 isn't floating (or use
INPUT_PULLUPand skip the resistor entirely — see firmware below). -
Piezo buzzer on D8. If passive, you'll drive it with
tone(). If active, a plaindigitalWrite()works;tone()on an active buzzer is harmless. - LED (with 220 Ω series resistor) on D13 — flashes with the key, so you can practice "light Morse" (flashlight signaling uses exactly the same timing).
- Optional speed pot on A0: maps to 5–25 WPM.
- Optional second button on D3: short-press toggles trainer mode; long-press replays the last trainer character.
Wiring diagram (text version — draw it on paper before you breadboard):
Nano D2 ----[button]---- GND (use INPUT_PULLUP)
Nano D8 ----[piezo +] piezo - → GND
Nano D13 ----[220R]------[LED+] LED - → GND
Nano A0 ----[pot wiper] (optional speed)
That's the whole schematic. If you've built anything with an Arduino before, this takes ten minutes.
5. The firmware
The firmware is the interesting part. It has three jobs: generate correct timing, measure your timing, and report the error. Here's the core — a complete sketch you can paste into the Arduino IDE:
// Morse Practice Oscillator — morse-practice.ino
// Timing per ITU M.1677: dit=1, dah=3, intra-char gap=1, char gap=3, word gap=7
const int KEY_PIN = 2;
const int BUZZER_PIN = 8;
const int LED_PIN = 13;
const int SIDETONE = 650; // Hz — standard practice sidetone
int wpm = 12; // start slow
unsigned long unitMs() { return 1200UL / wpm; } // PARIS = 50 units
void keyDown(unsigned long ms) {
tone(BUZZER_PIN, SIDETONE);
digitalWrite(LED_PIN, HIGH);
delay(ms);
noTone(BUZZER_PIN);
digitalWrite(LED_PIN, LOW);
}
void sendDit() { keyDown(unitMs()); }
void sendDah() { keyDown(3 * unitMs()); }
void gap(int units) { delay(units * unitMs()); }
// Send one character from a compact table (A-Z, 0-9)
void sendChar(char c) {
// ... lookup table of dits/dahs, then:
// for each element: sendDit() or sendDah(); gap(1) between elements
// gap(3) after the character
}
void trainerLoop() {
// picks random letters, sends them at your WPM, pauses for you to guess
}
The full sketch — including the A–Z/0–9 lookup table, the serial monitor that prints what the trainer sent (so you can check your ear), and the self-measurement mode — is linked in the project's files. The measurement mode is the feature I'm proudest of: it timestamps your key-down and key-up events with millis(), compares each dit/dah/gap against the 1:3:1:3:7 ratios at your chosen WPM, and prints a report over serial like:
KEYED: .-- target: .-- dit err +8% dah err -3% gap err +21% <-- watch your gaps!
Beginners almost always rush the gaps. Seeing "+21% gap error" in the serial monitor fixes in an evening what months of casual practice won't.
Why the firmware owns the timing, not you
Human timing jitter on a hand key is enormous — studies of straight-key operators show 20–30% variance even in experienced hands. That's fine on the air (the human ear is a magnificent adaptive decoder), but when you're learning, you want a reference implementation first and improvisation later. Trainer mode gives you the reference; measurement mode shows you the delta. This is the same loop as learning any protocol: implement the spec, diff against the spec, converge.
6. Assembly: the mint-tin portable
For the portable version:
- Transfer the breadboard circuit to a small piece of perfboard. Solder the Nano's headers, the buzzer (or mount it in the tin lid as a speaker grille), the button (drill a hole in the lid — this is your key), and the LED.
- Power: a 9 V battery fits in an Altoids tin alongside the perfboard. Add a slide switch in the battery lead.
- Label the lid with a felt-tip pen: DIT / DAH reference card. (Print the Morse alphabet from any reference — or use an online translator to generate a cheat sheet.)
- Glue four small rubber feet to the bottom so the tin doesn't dance across the desk when you're keying at 20 WPM.
Total build time, soldering included: about two hours. Difficulty: beginner — if you can blink an LED, you can build this.
7. The practice routine (15 minutes a day)
Hardware without a routine becomes desk decoration. Here's the routine I used, adapted from how CW operators actually learn:
- Days 1–3 — ears first, never the chart. Trainer mode at 8 WPM sends random letters; you guess, serial monitor confirms. Never look at a dots-and-dashes chart while listening — the goal is sound → letter, not sound → dots → letter. That's the single most important rule in Morse pedagogy.
- Days 4–7 — add the key. Manual mode at 8 WPM. Send what the trainer just sent. Watch the gap-error report; keep gaps honest.
- Days 8–14 — speed up the character rate, keep spacing wide. This is the Farnsworth method: send characters at 15 WPM but stretch the gaps between characters. It teaches the rhythm of fast code without the panic. The optional speed pot maps to exactly this.
- Days 15+ — copy real text. Have a friend (or a second Arduino running a different sketch) send words. Or decode practice audio from the web and verify your copy against the source text.
To check your work against a reference, play the target text through an online translator's audio and compare it with your own sending — the translator at the link in the parts section works well as a neutral referee because it implements the same 1:3:1:3:7 timing.
8. What this teaches about protocol design
Build this and you'll have felt, in your hands, four ideas that show up in every communication protocol you'll ever design:
- Variable-length encoding beats fixed-length when frequencies are skewed. E=·, T=−. Huffman proved it optimal in 1952; telegraphers shipped it in the 1840s.
- Timing is the framing. There are no packet headers in Morse — the 1/3/7-unit gaps are the framing layer. UART start/stop bits, Ethernet inter-frame gaps, and USB SOF packets are the same idea in silicon.
- Error detection lives at the human layer. There's no checksum in Morse; the operator's brain does forward error correction from context. Every protocol eventually leans on the layer above it.
- The spec is the product. This project's firmware is ~200 lines; the timing table it implements is 5 ratios. The value isn't the code, it's conformance to the agreement.
9. Going further
- Koch method trainer: add an SD card or store word lists in PROGMEM; the Koch method (two letters at full speed, add a letter at 90% accuracy) is the fastest documented path to 20 WPM.
- Light signaling: the LED already implements it — try "flashlight Morse" across a dark room. Same timing, different medium. (The 7-unit word gap is what makes light signaling readable at distance.)
- QRP transmitter: replace the buzzer with a keyed oscillator stage and you have the conceptual core of every CW transmitter ever built. Don't transmit without a licence — but the keying circuit is identical to what you'd key.
- Decode side: point a microphone at the buzzer and write the decoder (Goertzel algorithm for tone detection + a state machine for the 1:3:1:3:7 timing). Encoding was this project; decoding is the natural sequel.
Project files: full Arduino sketch (with lookup table, trainer mode, and self-measurement mode), a printable Morse reference card, and photos of the mint-tin build will be attached to this project.
License: CERN-OHL-S for hardware, MIT for firmware — build it, fork it, teach with it.
About the author: I build free web tools for learning Morse code, including the translator/checker referenced above. This project exists because the best way to understand a 180-year-old protocol is to implement it with your own hands.
Top comments (0)