Send secret messages with a Morse code signal lamp

Type a message on your computer and the printed signal lamp flashes and beeps it in Morse code. Tap the button yourself, and the UNO decodes your dots and dashes back into letters.
  • Level:Beginner
  • Duration: 45 min
  • Ages 10+
  • Platform: Arduino

Tools for this lesson

Helpful for this build. Open a tool to see tips and more projects that use it.

Send secret messages with a Morse code signal lamp – EEMENTOR STEM™ lesson

Before radio could carry voices, ships and soldiers talked with flashes of light and beeps, using a code invented by Samuel Morse. Every letter is a short pattern of dots and dashes: S is three dots, O is three dashes, so SOS is · · · – – – · · ·. In this lesson the UNO becomes a signal lamp. Type a message in the Serial Monitor and it flashes and beeps it in Morse code. Then try it the other way round: tap a message on the button, and the UNO works out the letters and prints them. Can a friend read your message from the flashes?

What you'll learn

  • How Morse code turns letters into dots and dashes
  • How to read text that you type in the Serial Monitor
  • How to store a code table in an array of strings
  • How to measure how long a button is held with millis()

What you need

  • UNO R3 board and USB cable (UNO R3 Starter Kit)
  • White LED and 1 resistor, 220 Ω (UNO R3 Starter Kit)
  • Active buzzer (UNO R3 Starter Kit)
  • 1 push button (UNO R3 Starter Kit)
  • The printed parts from the 3D printing box below (signal lamp), or the cardboard version described there
  • Breadboard and jumper wires (UNO R3 Starter Kit)

Wiring

Breadboard wiring diagram: Send secret messages with a Morse code signal lamp
Breadboard view. Check each connection against the wiring table below.
From To
UNO pin 9 220 Ω resistor, then the LED's long leg
LED short leg GND
Active buzzer + (long leg) UNO pin 8
Active buzzer − GND
Push button, one leg UNO pin 2
Push button, the diagonally opposite leg GND

The sketch

const int LED_PIN = 9, BUZZER_PIN = 8, KEY_PIN = 2;
const int UNIT = 150;          // one dot in ms when the UNO sends
const int KEY_UNIT = 200;      // one dot in ms when you tap (a bit slower)

// A to Z, then 0 to 9
const char* CODES[36] = {".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--",
  "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..",
  "-----", ".----", "..---", "...--", "....-", ".....", "-....", "--...", "---..", "----."};

void signal(bool on) {
  digitalWrite(LED_PIN, on);
  digitalWrite(BUZZER_PIN, on);
}

const char* codeFor(char c) {
  c = toupper(c);
  if (c >= 'A' && c <= 'Z') return CODES[c - 'A'];
  if (c >= '0' && c <= '9') return CODES[26 + c - '0'];
  return nullptr;                // no code for this character
}

void sendText(String text) {
  for (unsigned int i = 0; i < text.length(); i++) {
    if (text[i] == ' ') {          // space between words: 7 units in total
      delay(UNIT * 4);
      Serial.print("/ ");
      continue;
    }
    const char* morse = codeFor(text[i]);
    if (morse == nullptr) continue;
    Serial.print(morse);
    Serial.print(' ');
    for (int k = 0; morse[k] != 0; k++) {
      signal(true);
      delay(morse[k] == '.' ? UNIT : UNIT * 3);   // a dash is 3 dots long
      signal(false);
      delay(UNIT);                 // gap inside a letter
    }
    delay(UNIT * 2);               // gap between letters: 3 units in total
  }
  Serial.println();
}

char decode(String morse) {
  for (int i = 0; i < 36; i++) {
    if (morse == CODES[i]) return i < 26 ? 'A' + i : '0' + (i - 26);
  }
  return '?';
}

String tapped = "";              // dots and dashes of the letter you are tapping
unsigned long pressedAt = 0, releasedAt = 0;
bool wasDown = false, spaceDone = true;

void setup() {
  Serial.begin(9600);
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(KEY_PIN, INPUT_PULLUP);
  Serial.println("Type a message and press Enter, or tap on the button.");
}

void loop() {
  if (Serial.available()) {                       // a message from the Serial Monitor
    String text = Serial.readStringUntil('\n');
    text.trim();
    if (text.length() > 0) {
      Serial.print(text + ": ");
      sendText(text);
    }
  }

  bool down = digitalRead(KEY_PIN) == LOW;
  unsigned long now = millis();
  if (down && !wasDown) {                         // just pressed
    pressedAt = now;
    signal(true);
  }
  if (!down && wasDown) {                         // just let go: was it a dot or a dash?
    signal(false);
    unsigned long held = now - pressedAt;
    if (held > 20) {                              // ignore contact bounce
      tapped += held < KEY_UNIT * 2 ? '.' : '-';
      releasedAt = now;
      spaceDone = false;
    }
  }
  wasDown = down;

  if (!down && tapped.length() > 0 && now - releasedAt > KEY_UNIT * 3) {   // a pause ends the letter
    Serial.print(decode(tapped));
    tapped = "";
  }
  if (!down && !spaceDone && now - releasedAt > KEY_UNIT * 7) {            // a long pause ends the word
    Serial.print(' ');
    spaceDone = true;
  }
  delay(5);
}

Try it

  1. Open the Serial Monitor at 9600 baud and set the line ending to Newline.
  2. Type SOS and press Enter. The lamp flashes and beeps · · · – – – · · ·, and the Serial Monitor shows the code.
  3. Now tap on the button: a quick tap is a dot, a longer press (about half a second) is a dash. Tap · · · · then · and pause: the Serial Monitor prints HE.
  4. Send a secret message to a friend across the room. They write down the dots and dashes and use the table below to read it.

The Morse alphabet

Letter Code Letter Code Letter Code
A · – J · – – – S · · ·
B – · · · K – · – T –
C – · – · L · – · · U · · –
D – · · M – – V · · · –
E · N – · W · – –
F · · – · O – – – X – · · –
G – – · P · – – · Y – · – –
H · · · · Q – – · – Z – – · ·
I · · R · – · 0–9 five signs each

How it works

The whole Morse alphabet lives in one array of 36 strings, from A to Z and then 0 to 9. To find the code for a letter, the sketch subtracts 'A' from it: B minus A is 1, so B is CODES[1]. Computers store letters as numbers, which makes that little trick work.

Morse code is all about timing. A dash lasts three dots, the gap inside a letter lasts one dot, the gap between letters three dots and the gap between words seven. Change UNIT to send faster or slower.

Decoding works the other way round. millis() remembers when the button was pressed and let go. A short press adds a dot to tapped, a long one a dash. When you pause for three units, the letter is finished: the sketch looks it up in the same array and prints it.

Try this next

  • Add a second LED in another colour that only lights for dashes.
  • Make the UNO send a reply: when you tap a question mark (· · – – · ·), it answers OK.
  • Use the passive buzzer and tone() for a nicer beep.

Troubleshooting

  • Nothing happens when you press Enter: set the Serial Monitor's line ending to Newline and the speed to 9600 baud.
  • Every tap is a dash: tap more quickly, or make KEY_UNIT bigger.
  • Letters come out as ?: leave a clear pause between letters, and keep the gaps inside a letter short.
  • A printed part is too tight or too loose: printers differ by a few tenths of a millimetre. Sand or file a tight hole, and fix a loose part with a drop of hot glue or a strip of tape.

Build it yourself

Everything used in this lesson comes in one kit, with parts tested by our engineers.

UNO R3 Starter Kit | Arduino IDE Compatible | Free Tutorials $56.00

Shop the kit