Morse code decoder
Tap Morse on the BOOT button and watch it turn into letters. It learns your speed as you go, and a buzzer adds a sidetone if you have one.
Wiring
| From | To board | Note |
|---|---|---|
| BOOT button | GPIO 0 | Already on the board. It is the Morse key |
| Buzzer + | GPIO 25 | Optional sidetone. A passive buzzer or piezo |
| Buzzer - | GND |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32 Dev Module
// Morse code decoder. The BOOT button is the key: short press = dot, long press = dash.
const int KEY = 0; // the BOOT button
const int BUZZER = 25; // optional sidetone, leave unconnected if you have no buzzer
const int TONE_HZ = 650;
const char* LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const char* CODES[] = {
".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
"-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..",
"-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----.",
};
float unit = 120; // length of one dot in ms; adapts to your speed
String symbol; // dots and dashes of the letter being keyed
bool keyDown = false;
bool inWord = false;
unsigned long changedAt = 0;
char decode(const String& s) {
for (int i = 0; i < 36; i++) {
if (s == CODES[i]) return LETTERS[i];
}
return '?';
}
void setup() {
Serial.begin(115200);
delay(500);
pinMode(KEY, INPUT_PULLUP);
Serial.println("Morse decoder. Key with the BOOT button: short press = dot, long press = dash.");
}
void loop() {
bool down = digitalRead(KEY) == LOW;
unsigned long now = millis();
unsigned long held = now - changedAt;
if (down != keyDown && held > 15) { // 15 ms debounce
if (keyDown) {
// Key released: was it a dot or a dash?
noTone(BUZZER);
if (held < 2 * unit) {
symbol += '.';
unit = (unit * 3 + held) / 4;
} else {
symbol += '-';
unit = (unit * 3 + held / 3.0f) / 4;
}
unit = constrain(unit, 40.0f, 400.0f);
} else {
tone(BUZZER, TONE_HZ);
}
keyDown = down;
changedAt = now;
return;
}
if (!keyDown) {
if (symbol.length() > 0 && held > 3 * unit) {
Serial.print(decode(symbol)); // gap between letters
symbol = "";
inWord = true;
} else if (inWord && held > 7 * unit) {
Serial.printf(" wpm=%d\n", (int)(1200 / unit)); // gap between words
inWord = false;
}
}
delay(2);
}Wiring
| From | To board | Note |
|---|---|---|
| BOOT button | GPIO 0 | Already on the board. It is the Morse key |
| Buzzer + | GPIO 15 | Optional sidetone. A passive buzzer or piezo |
| Buzzer - | GND |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32S3 Dev Module
// Morse code decoder. The BOOT button is the key: short press = dot, long press = dash.
const int KEY = 0; // the BOOT button
const int BUZZER = 15; // optional sidetone, leave unconnected if you have no buzzer
const int TONE_HZ = 650;
const char* LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const char* CODES[] = {
".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
"-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..",
"-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----.",
};
float unit = 120; // length of one dot in ms; adapts to your speed
String symbol; // dots and dashes of the letter being keyed
bool keyDown = false;
bool inWord = false;
unsigned long changedAt = 0;
char decode(const String& s) {
for (int i = 0; i < 36; i++) {
if (s == CODES[i]) return LETTERS[i];
}
return '?';
}
void setup() {
Serial.begin(115200);
delay(500);
pinMode(KEY, INPUT_PULLUP);
Serial.println("Morse decoder. Key with the BOOT button: short press = dot, long press = dash.");
}
void loop() {
bool down = digitalRead(KEY) == LOW;
unsigned long now = millis();
unsigned long held = now - changedAt;
if (down != keyDown && held > 15) { // 15 ms debounce
if (keyDown) {
// Key released: was it a dot or a dash?
noTone(BUZZER);
if (held < 2 * unit) {
symbol += '.';
unit = (unit * 3 + held) / 4;
} else {
symbol += '-';
unit = (unit * 3 + held / 3.0f) / 4;
}
unit = constrain(unit, 40.0f, 400.0f);
} else {
tone(BUZZER, TONE_HZ);
}
keyDown = down;
changedAt = now;
return;
}
if (!keyDown) {
if (symbol.length() > 0 && held > 3 * unit) {
Serial.print(decode(symbol)); // gap between letters
symbol = "";
inWord = true;
} else if (inWord && held > 7 * unit) {
Serial.printf(" wpm=%d\n", (int)(1200 / unit)); // gap between words
inWord = false;
}
}
delay(2);
}Wiring
| From | To board | Note |
|---|---|---|
| BOOT button | GPIO 9 | Already on the board. It is the Morse key |
| Buzzer + | GPIO 4 | Optional sidetone. A passive buzzer or piezo |
| Buzzer - | GND |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32C3 Dev Module
// Morse code decoder. The BOOT button is the key: short press = dot, long press = dash.
const int KEY = 9; // the BOOT button
const int BUZZER = 4; // optional sidetone, leave unconnected if you have no buzzer
const int TONE_HZ = 650;
const char* LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const char* CODES[] = {
".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
"-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..",
"-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----.",
};
float unit = 120; // length of one dot in ms; adapts to your speed
String symbol; // dots and dashes of the letter being keyed
bool keyDown = false;
bool inWord = false;
unsigned long changedAt = 0;
char decode(const String& s) {
for (int i = 0; i < 36; i++) {
if (s == CODES[i]) return LETTERS[i];
}
return '?';
}
void setup() {
Serial.begin(115200);
delay(500);
pinMode(KEY, INPUT_PULLUP);
Serial.println("Morse decoder. Key with the BOOT button: short press = dot, long press = dash.");
}
void loop() {
bool down = digitalRead(KEY) == LOW;
unsigned long now = millis();
unsigned long held = now - changedAt;
if (down != keyDown && held > 15) { // 15 ms debounce
if (keyDown) {
// Key released: was it a dot or a dash?
noTone(BUZZER);
if (held < 2 * unit) {
symbol += '.';
unit = (unit * 3 + held) / 4;
} else {
symbol += '-';
unit = (unit * 3 + held / 3.0f) / 4;
}
unit = constrain(unit, 40.0f, 400.0f);
} else {
tone(BUZZER, TONE_HZ);
}
keyDown = down;
changedAt = now;
return;
}
if (!keyDown) {
if (symbol.length() > 0 && held > 3 * unit) {
Serial.print(decode(symbol)); // gap between letters
symbol = "";
inWord = true;
} else if (inWord && held > 7 * unit) {
Serial.printf(" wpm=%d\n", (int)(1200 / unit)); // gap between words
inWord = false;
}
}
delay(2);
}Wiring
| From | To board | Note |
|---|---|---|
| BOOT button | GPIO 0 | Already on the board. It is the Morse key |
| Buzzer + | GPIO 15 | Optional sidetone. A passive buzzer or piezo |
| Buzzer - | GND |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32S2 Dev Module
// Morse code decoder. The BOOT button is the key: short press = dot, long press = dash.
const int KEY = 0; // the BOOT button
const int BUZZER = 15; // optional sidetone, leave unconnected if you have no buzzer
const int TONE_HZ = 650;
const char* LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const char* CODES[] = {
".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
"-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..",
"-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----.",
};
float unit = 120; // length of one dot in ms; adapts to your speed
String symbol; // dots and dashes of the letter being keyed
bool keyDown = false;
bool inWord = false;
unsigned long changedAt = 0;
char decode(const String& s) {
for (int i = 0; i < 36; i++) {
if (s == CODES[i]) return LETTERS[i];
}
return '?';
}
void setup() {
Serial.begin(115200);
delay(500);
pinMode(KEY, INPUT_PULLUP);
Serial.println("Morse decoder. Key with the BOOT button: short press = dot, long press = dash.");
}
void loop() {
bool down = digitalRead(KEY) == LOW;
unsigned long now = millis();
unsigned long held = now - changedAt;
if (down != keyDown && held > 15) { // 15 ms debounce
if (keyDown) {
// Key released: was it a dot or a dash?
noTone(BUZZER);
if (held < 2 * unit) {
symbol += '.';
unit = (unit * 3 + held) / 4;
} else {
symbol += '-';
unit = (unit * 3 + held / 3.0f) / 4;
}
unit = constrain(unit, 40.0f, 400.0f);
} else {
tone(BUZZER, TONE_HZ);
}
keyDown = down;
changedAt = now;
return;
}
if (!keyDown) {
if (symbol.length() > 0 && held > 3 * unit) {
Serial.print(decode(symbol)); // gap between letters
symbol = "";
inWord = true;
} else if (inWord && held > 7 * unit) {
Serial.printf(" wpm=%d\n", (int)(1200 / unit)); // gap between words
inWord = false;
}
}
delay(2);
}Wiring
| From | To board | Note |
|---|---|---|
| BOOT button | GPIO 9 | Already on the board. It is the Morse key |
| Buzzer + | GPIO 21 | Optional sidetone. A passive buzzer or piezo |
| Buzzer - | GND |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for ESP32C6 Dev Module
// Morse code decoder. The BOOT button is the key: short press = dot, long press = dash.
const int KEY = 9; // the BOOT button
const int BUZZER = 21; // optional sidetone, leave unconnected if you have no buzzer
const int TONE_HZ = 650;
const char* LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const char* CODES[] = {
".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
"-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..",
"-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----.",
};
float unit = 120; // length of one dot in ms; adapts to your speed
String symbol; // dots and dashes of the letter being keyed
bool keyDown = false;
bool inWord = false;
unsigned long changedAt = 0;
char decode(const String& s) {
for (int i = 0; i < 36; i++) {
if (s == CODES[i]) return LETTERS[i];
}
return '?';
}
void setup() {
Serial.begin(115200);
delay(500);
pinMode(KEY, INPUT_PULLUP);
Serial.println("Morse decoder. Key with the BOOT button: short press = dot, long press = dash.");
}
void loop() {
bool down = digitalRead(KEY) == LOW;
unsigned long now = millis();
unsigned long held = now - changedAt;
if (down != keyDown && held > 15) { // 15 ms debounce
if (keyDown) {
// Key released: was it a dot or a dash?
noTone(BUZZER);
if (held < 2 * unit) {
symbol += '.';
unit = (unit * 3 + held) / 4;
} else {
symbol += '-';
unit = (unit * 3 + held / 3.0f) / 4;
}
unit = constrain(unit, 40.0f, 400.0f);
} else {
tone(BUZZER, TONE_HZ);
}
keyDown = down;
changedAt = now;
return;
}
if (!keyDown) {
if (symbol.length() > 0 && held > 3 * unit) {
Serial.print(decode(symbol)); // gap between letters
symbol = "";
inWord = true;
} else if (inWord && held > 7 * unit) {
Serial.printf(" wpm=%d\n", (int)(1200 / unit)); // gap between words
inWord = false;
}
}
delay(2);
}Wiring
| From | To board | Note |
|---|---|---|
| BOOT button | GPIO 9 | Already on the board. It is the Morse key |
| Buzzer + | GPIO 4 | Optional sidetone. A passive buzzer or piezo |
| Buzzer - | GND |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for XIAO_ESP32C3
// Morse code decoder. The BOOT button is the key: short press = dot, long press = dash.
const int KEY = 9; // the BOOT button
const int BUZZER = 4; // optional sidetone, leave unconnected if you have no buzzer
const int TONE_HZ = 650;
const char* LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const char* CODES[] = {
".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
"-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..",
"-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----.",
};
float unit = 120; // length of one dot in ms; adapts to your speed
String symbol; // dots and dashes of the letter being keyed
bool keyDown = false;
bool inWord = false;
unsigned long changedAt = 0;
char decode(const String& s) {
for (int i = 0; i < 36; i++) {
if (s == CODES[i]) return LETTERS[i];
}
return '?';
}
void setup() {
Serial.begin(115200);
delay(500);
pinMode(KEY, INPUT_PULLUP);
Serial.println("Morse decoder. Key with the BOOT button: short press = dot, long press = dash.");
}
void loop() {
bool down = digitalRead(KEY) == LOW;
unsigned long now = millis();
unsigned long held = now - changedAt;
if (down != keyDown && held > 15) { // 15 ms debounce
if (keyDown) {
// Key released: was it a dot or a dash?
noTone(BUZZER);
if (held < 2 * unit) {
symbol += '.';
unit = (unit * 3 + held) / 4;
} else {
symbol += '-';
unit = (unit * 3 + held / 3.0f) / 4;
}
unit = constrain(unit, 40.0f, 400.0f);
} else {
tone(BUZZER, TONE_HZ);
}
keyDown = down;
changedAt = now;
return;
}
if (!keyDown) {
if (symbol.length() > 0 && held > 3 * unit) {
Serial.print(decode(symbol)); // gap between letters
symbol = "";
inWord = true;
} else if (inWord && held > 7 * unit) {
Serial.printf(" wpm=%d\n", (int)(1200 / unit)); // gap between words
inWord = false;
}
}
delay(2);
}Wiring
| From | To board | Note |
|---|---|---|
| BOOT button | GPIO 0 | Already on the board. It is the Morse key |
| Buzzer + | GPIO 4 | Optional sidetone. A passive buzzer or piezo |
| Buzzer - | GND |
Tested on this board
Not run on this board yet. Treat the wiring and code as a first draft.
Firmware, Arduino sketch for XIAO_ESP32S3
// Morse code decoder. The BOOT button is the key: short press = dot, long press = dash.
const int KEY = 0; // the BOOT button
const int BUZZER = 4; // optional sidetone, leave unconnected if you have no buzzer
const int TONE_HZ = 650;
const char* LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const char* CODES[] = {
".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
"-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..",
"-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----.",
};
float unit = 120; // length of one dot in ms; adapts to your speed
String symbol; // dots and dashes of the letter being keyed
bool keyDown = false;
bool inWord = false;
unsigned long changedAt = 0;
char decode(const String& s) {
for (int i = 0; i < 36; i++) {
if (s == CODES[i]) return LETTERS[i];
}
return '?';
}
void setup() {
Serial.begin(115200);
delay(500);
pinMode(KEY, INPUT_PULLUP);
Serial.println("Morse decoder. Key with the BOOT button: short press = dot, long press = dash.");
}
void loop() {
bool down = digitalRead(KEY) == LOW;
unsigned long now = millis();
unsigned long held = now - changedAt;
if (down != keyDown && held > 15) { // 15 ms debounce
if (keyDown) {
// Key released: was it a dot or a dash?
noTone(BUZZER);
if (held < 2 * unit) {
symbol += '.';
unit = (unit * 3 + held) / 4;
} else {
symbol += '-';
unit = (unit * 3 + held / 3.0f) / 4;
}
unit = constrain(unit, 40.0f, 400.0f);
} else {
tone(BUZZER, TONE_HZ);
}
keyDown = down;
changedAt = now;
return;
}
if (!keyDown) {
if (symbol.length() > 0 && held > 3 * unit) {
Serial.print(decode(symbol)); // gap between letters
symbol = "";
inWord = true;
} else if (inWord && held > 7 * unit) {
Serial.printf(" wpm=%d\n", (int)(1200 / unit)); // gap between words
inWord = false;
}
}
delay(2);
}Parts
| Quantity | Part | Where to buy |
|---|---|---|
| 1× | ESP32 dev board | Buy at Amazon |
| 1× | Optional passive buzzer or piezo | Buy at Adafruit |
Amazon buy links may include an Associates tag, and this site may earn a commission from qualifying Amazon purchases at no extra cost to you. Adafruit links are direct.
Libraries
None beyond the ESP32 Arduino core.
Load it
- In the Arduino IDE, install the esp32 board package and pick the board named above the sketch.
- Paste the sketch, choose the board's port and upload. Hold BOOT if the upload stalls at Connecting.
- Close the IDE's serial monitor, then open the bench here to watch the output.
Field notes
- Start slowly and evenly. The decoder takes a dash as anything longer than two dots, and adjusts the dot length after every symbol.
- A gap of three dots ends a letter and seven dots ends a word, as in real Morse. Speed is printed as words per minute at the end of each word.
- Do not hold BOOT while the board resets, or it enters download mode.
- Try SOS (... --- ...), then your name. A real Morse key or any push button wired from the BOOT pin to GND works too.
Not tested yet
- Not tested on hardware yet. Compiles for every board listed with Arduino-ESP32 core 3.3.12.
Built it? Send a build report with your board and what the serial port printed. Working reports become the "Tested on this board" entries above.