Built-in project · ESP32-C3-DevKitM-1

Morse sender on an ESP32-C3

An ESP32-C3 sends a word in Morse on a lamp and a piezo at once, and a button picks the next message. The whole build, an ESP32-C3 and 4 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

BeginnerRuns in your browser. Free, and no account needed.

Morse sender · ESP32-C3-DevKitM-1live0.000 s 0.00x
Hold the button
The circuit itself, running here on the simulator. Press what can be pressed; click anything else to open it in the editor.

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · ESP32-C3-DevKitM-1
// Morse: a word sent as light and sound, one element at a time.
//
//   GPIO 8  the lamp, through 220 ohm to GND
//   GPIO 5  a passive piezo straight to GND
//   GPIO 9  a button to GND, internal pull-up: send the next message
//
// Morse is built out of one unit of time. A dot is one unit, a dash is three,
// the gap inside a letter is one, between letters three, between words seven.
// Everything below is that table and nothing else, so changing UNIT_MS changes
// the speed of the whole thing and every proportion stays right.
//
// Twenty words a minute is the usual yardstick, and the word it is measured on
// is PARIS, which is fifty units long: 60 000 / (20 x 50) is 60 ms a unit. The
// sketch starts slower than that, at 120 ms, because it is meant to be read
// off the lamp by eye.
//
// The table is one string a letter, dots and dashes, indexed by the letter
// itself. Anything not in it is skipped, so punctuation in a message costs
// nothing but is not sent either.

#include "mokxi_tone.h"

const uint8_t LAMP = 8;
const uint8_t PIEZO = 5;
const uint8_t BUTTON = 9;

const uint16_t UNIT_MS = 120;
const uint16_t PITCH_HZ = 700;

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

const char *const MESSAGES[] = {"SOS", "MOKXI", "HELLO WORLD"};
const int MESSAGE_COUNT = 3;

int which = 0;

static const char *codeFor(char c) {
  if (c >= 'a' && c <= 'z') {
    c = (char)(c - 'a' + 'A');
  }
  if (c >= 'A' && c <= 'Z') {
    return MORSE[c - 'A'];
  }
  if (c >= '0' && c <= '9') {
    return MORSE[26 + (c - '0')];
  }
  return 0;
}

// One element: the lamp and the note together, for one unit or three.
static void element(bool dash) {
  uint16_t ms = (uint16_t)(dash ? UNIT_MS * 3u : UNIT_MS);
  digitalWrite(LAMP, HIGH);
  mokxiTone(PIEZO, PITCH_HZ, ms);
  digitalWrite(LAMP, LOW);
  delay(UNIT_MS);  // the gap inside a letter
}

static void sendLetter(const char *code) {
  for (const char *p = code; *p; p++) {
    element(*p == '-');
  }
  delay(UNIT_MS * 2u);  // one unit is already spent, three between letters
}

static void send(const char *text) {
  Serial.print("sending ");
  Serial.println(text);
  for (const char *p = text; *p; p++) {
    if (*p == ' ') {
      delay(UNIT_MS * 4u);  // three are already spent, seven between words
      continue;
    }
    const char *code = codeFor(*p);
    if (!code) {
      continue;
    }
    Serial.print("  ");
    Serial.print(*p);
    Serial.print(" ");
    Serial.println(code);
    sendLetter(code);
  }
}

void setup() {
  pinMode(LAMP, OUTPUT);
  pinMode(PIEZO, OUTPUT);
  pinMode(BUTTON, INPUT_PULLUP);
  Serial.begin(115200);
  Serial.println("Mokxi ESP32-C3: Morse sender");
  Serial.println("press the button to move to the next message");
}

void loop() {
  send(MESSAGES[which]);

  // a long gap, and the button read through it
  unsigned long start = millis();
  while (millis() - start < 1500UL) {
    if (digitalRead(BUTTON) == LOW) {
      which = (which + 1) % MESSAGE_COUNT;
      Serial.println("next message");
      while (digitalRead(BUTTON) == LOW) {
        delay(10);
      }
      break;
    }
    delay(10);
  }
}

Parts list

6 parts, plus the jumper wires. Every one is in the editor's parts bin.

How it is wired

5 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.

  • Ground: ESP32-C3-DevKitM-1 pin GND; LED, yellow pin C; Piezo speaker pin 2; Pushbutton pin 2a; Pushbutton pin 2b
  • ESP32-C3-DevKitM-1 pin 5; Piezo speaker pin 1
  • ESP32-C3-DevKitM-1 pin 8; Resistor, 220 Ω pin 1
  • ESP32-C3-DevKitM-1 pin 9; Pushbutton pin 1a; Pushbutton pin 1b
  • Resistor, 220 Ω pin 2; LED, yellow pin A

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.