Arduino Project

Arduino Morse Code Telegraph: Build a Sender and a Key Decoder

Build a working Morse code telegraph with an Arduino Uno, a push button, a piezo buzzer and an LED. Sketch 1 turns text you type into Morse sound and light. Sketch 2 reads your key presses and prints the letters. Both follow the ITU timing rules, and we tested them end to end: the decoder reads back everything the sender sends, including numbers.

What You Will Build

Morse sender

Type a message in the Arduino Serial Monitor and the board sends it as a 600 Hz tone and a flashing LED at 15 WPM.

Key decoder

Press a button or a real straight key. The board times each press, decides dot or dash, and prints the decoded text.

A modern telegraph

Together they repeat what Samuel Morse and Alfred Vail did in 1844: a key, a timed signal and a receiver that turns it back into letters.

Parts You Need

PartQtyNotes
Arduino Uno R3 (or Nano)1Any 5 V board that supports tone()
Passive piezo buzzer1Needed for tone(). An active buzzer plays one fixed pitch
LED1Or use the built-in LED on pin 13
220 Ω resistor1Limits current through the LED
Momentary push button1Or a straight key with two terminals
Breadboard and jumper wires1 setMale-to-male wires are enough

Total cost is usually the price of the Arduino plus a few small parts from any starter kit.

Wiring

ComponentArduino pinOther side
Buzzer +D8Buzzer − to GND
LED long leg (through 220 Ω)D13LED short leg to GND
Push button or keyD2Other leg to GND

The button uses INPUT_PULLUP, so pin 2 reads HIGH when the button is open and LOW when it is pressed. No external resistor is needed. Sketch 1 needs only the buzzer and LED; sketch 2 uses all three.

Sketch 1: Text to Morse Code Sender

Upload this sketch, open the Serial Monitor at 9600 baud, set the line ending to Newline, type a message and press Enter. Letters and numbers are sent; other characters are skipped.

morse_sender.ino
// QuickMorse: Arduino Morse code sender
// Type a message in the Serial Monitor (9600 baud) and press Enter.

const int LED_PIN = 13;      // LED with 220 ohm resistor, or the built-in LED
const int BUZZER_PIN = 8;    // passive piezo buzzer
const int TONE_HZ = 600;     // sidetone pitch in hertz
const int WPM = 15;          // sending speed in words per minute
const unsigned long UNIT = 1200UL / WPM;   // one unit in milliseconds (80 ms at 15 WPM)

// Morse code for A to Z, then 0 to 9 (ITU-R M.1677-1)
const char* LETTERS[] = {
  ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---",
  "-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.", "...", "-",
  "..-", "...-", ".--", "-..-", "-.--", "--.."
};
const char* DIGITS[] = {
  "-----", ".----", "..---", "...--", "....-",
  ".....", "-....", "--...", "---..", "----."
};

void setup() {
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(9600);
  Serial.println("Type a message and press Enter.");
}

// One dot (1 unit) or dash (3 units), followed by the 1-unit gap inside a letter
void keyDown(unsigned long units) {
  digitalWrite(LED_PIN, HIGH);
  tone(BUZZER_PIN, TONE_HZ);
  delay(units * UNIT);
  digitalWrite(LED_PIN, LOW);
  noTone(BUZZER_PIN);
  delay(UNIT);
}

void sendCode(const char* code) {
  for (int i = 0; code[i] != '\0'; i++) {
    keyDown(code[i] == '.' ? 1 : 3);
  }
  delay(2 * UNIT);           // 1 + 2 = 3-unit gap between letters
}

void loop() {
  if (Serial.available() > 0) {
    String msg = Serial.readStringUntil('\n');
    msg.toUpperCase();
    for (unsigned int i = 0; i < msg.length(); i++) {
      char c = msg[i];
      if (c >= 'A' && c <= 'Z') {
        sendCode(LETTERS[c - 'A']);
      } else if (c >= '0' && c <= '9') {
        sendCode(DIGITS[c - '0']);
      } else if (c == ' ') {
        delay(4 * UNIT);     // 3 + 4 = 7-unit gap between words
      }
    }
    Serial.print("Sent: ");
    Serial.println(msg);
  }
}

The key line is UNIT = 1200UL / WPM. A dot is 1 unit, a dash 3, the gap inside a letter 1, between letters 3 and between words 7, exactly as in the Morse timing guide.

Sketch 2: Telegraph Key Decoder

This sketch turns the Arduino into a receiver for your own keying. Hold the button for a short beat for a dot and about three times longer for a dash.

morse_key_decoder.ino
// QuickMorse: Arduino Morse code key decoder
// Press the button like a telegraph key. Decoded text appears in the Serial Monitor.

const int KEY_PIN = 2;       // push button or straight key to GND (internal pull-up)
const int LED_PIN = 13;
const int BUZZER_PIN = 8;    // passive piezo buzzer for sidetone
const int TONE_HZ = 600;
const int WPM = 12;                        // the speed you key at
const unsigned long UNIT = 1200UL / WPM;   // 100 ms at 12 WPM
const unsigned long DEBOUNCE_MS = 15;      // ignore contact bounce shorter than this

const char* LETTERS[] = {
  ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---",
  "-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.", "...", "-",
  "..-", "...-", ".--", "-..-", "-.--", "--.."
};
const char* DIGITS[] = {
  "-----", ".----", "..---", "...--", "....-",
  ".....", "-....", "--...", "---..", "----."
};

char pattern[8];             // dots and dashes of the current letter
int len = 0;
bool keyWasDown = false;
bool spaceSent = true;
unsigned long changedAt = 0;

void setup() {
  pinMode(KEY_PIN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(9600);
  Serial.println("Start keying...");
}

char decode(const char* p) {
  for (int i = 0; i < 26; i++) {
    if (strcmp(p, LETTERS[i]) == 0) return 'A' + i;
  }
  for (int i = 0; i < 10; i++) {
    if (strcmp(p, DIGITS[i]) == 0) return '0' + i;
  }
  return '?';
}

void loop() {
  bool keyDown = (digitalRead(KEY_PIN) == LOW);
  unsigned long now = millis();

  if (keyDown != keyWasDown) {
    if (now - changedAt < DEBOUNCE_MS) return;
    unsigned long held = now - changedAt;
    if (keyDown) {                       // key pressed: start the tone
      tone(BUZZER_PIN, TONE_HZ);
      digitalWrite(LED_PIN, HIGH);
    } else {                             // key released: was it a dot or a dash?
      noTone(BUZZER_PIN);
      digitalWrite(LED_PIN, LOW);
      if (len < 7) pattern[len++] = (held < 2 * UNIT) ? '.' : '-';
    }
    keyWasDown = keyDown;
    changedAt = now;
    spaceSent = false;
    return;
  }

  if (!keyDown) {
    unsigned long gap = now - changedAt;
    if (len > 0 && gap > 2 * UNIT) {             // gap longer than 2 units: letter finished
      pattern[len] = '\0';
      Serial.print(decode(pattern));
      len = 0;
    } else if (len == 0 && !spaceSent && gap > 5 * UNIT) {   // longer than 5 units: word finished
      Serial.print(' ');
      spaceSent = true;
    }
  }
}

The decoder measures time instead of listening for exact lengths. A press shorter than 2 units is a dot, longer is a dash. A pause longer than 2 units ends a letter, and longer than 5 units adds a space. Those thresholds sit halfway between the ideal values, so normal human timing still decodes.

Morse Timing Used in the Code

SpeedOne unit (dot)DashLetter gapWord gap
5 WPM240 ms720 ms720 ms1680 ms
10 WPM120 ms360 ms360 ms840 ms
12 WPM100 ms300 ms300 ms700 ms
15 WPM80 ms240 ms240 ms560 ms
20 WPM60 ms180 ms180 ms420 ms
25 WPM48 ms144 ms144 ms336 ms

Beginners usually key at 10 to 13 WPM. If you are slower than the sketch expects, your dots start reading as dashes, so set WPM in sketch 2 to match yourself.

Troubleshooting

No sound

Check that the buzzer is passive and on pin 8. On the Uno, tone() also disables PWM on pins 3 and 11, so do not use those pins for the buzzer.

Letters come out as ?

Your keying speed and the WPM setting do not match. Slow down, or lower WPM in sketch 2.

Extra dots appear

Cheap buttons bounce. Raise DEBOUNCE_MS from 15 to 25.

Nothing in the Serial Monitor

Set the baud rate to 9600 and the line ending to Newline, then press the reset button on the board.

From the 1844 Telegraph to Arduino

Washington to Baltimore, 1844

On 24 May 1844 Morse sent “What hath God wrought” over an experimental line between Washington, D.C. and Baltimore. Read more in the history of Morse code.

From paper tape to sound

Early receivers marked dots and dashes on paper tape. Operators soon learned to read the clicks of the sounder by ear, which is what sketch 2 imitates with timing.

Same code, new hardware

Your Arduino uses the international code standardised by the ITU, the version still used by radio amateurs today.

Frequently Asked Questions

Q.

Can I use an active buzzer instead of a passive one?

Yes, but replace tone() and noTone() with digitalWrite(BUZZER_PIN, HIGH) and LOW. An active buzzer has its own oscillator, so it beeps at one fixed pitch.

Q.

How do I change the Morse code speed on Arduino?

Change the WPM constant. The sketch calculates one unit as 1200 divided by WPM, so 15 WPM gives 80 ms per dot and 240 ms per dash.

Q.

Why does the decoder print question marks?

A ? means the pattern did not match any letter. Usually you are keying much faster or slower than the WPM set in the sketch. Set WPM close to your real speed.

Q.

Can I connect a real telegraph key to the Arduino?

Yes. A straight key is just a switch: connect one terminal to pin 2 and the other to GND. The internal pull-up resistor does the rest.

Q.

Can this Arduino sketch decode Morse code from audio?

No. The decoder reads a key or button. To decode Morse from a microphone or a recording, use the QuickMorse audio decoder.

Q.

What is the PARIS standard in Morse code timing?

The word PARIS is exactly 50 units long, so sending it once per minute is 1 WPM. That is where the formula unit = 1200 / WPM milliseconds comes from.

Similar Posts