XIAO RP2040 Morse Key

Pseudocode Prompt Used to Generate Code From GPT-5

Define input pin for the capacitive touch pad and output pin for the onboard LED Set timing constants for Morse code: dot length, dash length as three dots, letter gap as three dots, word gap as seven dots Calibrate the baseline by reading the pad multiple times with no touch and averaging the values Begin the main loop that continuously runs Read the pad value by discharging it, charging with pull-up, and counting how long until the pin reads HIGH Apply averaging and filtering to smooth the readings Compare the filtered value against the baseline to decide if the pad is being touched If the pad changes from not touched to touched, record the current time as pressStart and turn the LED ON If the pad changes from touched to not touched, measure the press duration as currentTime minus pressStart, then classify it as a dot if shorter than dash length or as a dash otherwise, append the symbol to the currentCode string, and turn the LED OFF If the pad is idle and the time since the last edge is greater than the letter gap, decode the currentCode string using the Morse table, print the resulting character to the output, and clear currentCode If the pad is idle and the time since the last edge is greater than the word gap, decode and print any unfinished letter, then print a space to mark a word break Repeat the loop so that input continues to be read, dots and dashes continue to be classified, and decoded text continues to appear in real time

Source Code

// XIAO RP2040 Morse key (capacitive pad) with auto-translation
// Pad: bottom-right -> GPIO4  (your "D9")
// LED: onboard green -> GPIO16
// Open Serial Monitor @ 115200

// --------- Pins ---------
const int TOUCH_PIN = 4;   // <-- GPIO for your pad (bottom-right)
const int LED_PIN   = 16;  // onboard green LED

// --------- Morse timing (tweakable) ---------
const unsigned long DOT_MS       = 250;               // base unit
const unsigned long DASH_MS      = DOT_MS * 3;        // duration threshold
const unsigned long LETTER_GAP   = DOT_MS * 3;        // pause between letters
const unsigned long WORD_GAP     = DOT_MS * 7;        // pause between words

// --------- Capacitive read settings ---------
const unsigned long TIMEOUT       = 40000;   // larger => more range
const int DISCHARGE_US            = 200;     // ensure pad discharges
const int SAMPLES                 = 3;
const float FILTER_ALPHA          = 0.30f;
const int TOUCH_MARGIN            = 80;      // above baseline = "touched"

unsigned long baseline = 0;
float filt = 0.0f;
bool isTouched = false;

// --------- Morse table ---------
struct MorseMap { const char* code; char ch; };
const MorseMap MORSE[] = {
  {".-", 'A'},   {"-...", 'B'}, {"-.-.", 'C'}, {"-..", 'D'},  {".", 'E'},
  {"..-.", 'F'}, {"--.", 'G'},  {"....", 'H'}, {"..", 'I'},   {".---", 'J'},
  {"-.-", 'K'},  {".-..", 'L'}, {"--", 'M'},   {"-.", 'N'},   {"---", 'O'},
  {".--.", 'P'}, {"--.-", 'Q'}, {".-.", 'R'},  {"...", 'S'},  {"-", 'T'},
  {"..-", 'U'},  {"...-", 'V'}, {".--", 'W'},  {"-..-", 'X'}, {"-.--", 'Y'},
  {"--..", 'Z'},
  {"-----",'0'}, {".----",'1'}, {"..---",'2'}, {"...--",'3'}, {"....-",'4'},
  {".....",'5'}, {"-....",'6'}, {"--...",'7'}, {"---..",'8'}, {"----.",'9'},
  {NULL, 0}
};

// --------- Capacitive helper ---------
unsigned long touchReadCTM(int pin) {
  pinMode(pin, OUTPUT);
  digitalWrite(pin, LOW);
  delayMicroseconds(DISCHARGE_US);

  pinMode(pin, INPUT_PULLUP);
  unsigned long c = 0;
  while (digitalRead(pin) == LOW && c < TIMEOUT) c++;
  return c;
}
unsigned long readAvg(int pin, int n=SAMPLES){
  unsigned long s=0; for(int i=0;i<n;i++) s += touchReadCTM(pin);
  return s / (unsigned long)n;
}

// --------- Keying state ---------
String currentCode = "";             // collects "." and "-" for current letter
unsigned long lastEdgeTime = 0;      // last touch/release moment
bool keyDown = false;                // current key state

char decodeLetter(const String& code) {
  for (int i=0; MORSE[i].code; i++) {
    if (code.equals(MORSE[i].code)) return MORSE[i].ch;
  }
  return '?'; // unknown pattern
}

void emitLetterIfAny() {
  if (currentCode.length() == 0) return;
  char ch = decodeLetter(currentCode);
  Serial.print(ch);
  currentCode = "";
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);

  // Baseline calibration (hands off the pad for a moment)
  delay(300);
  unsigned long sum=0;
  for (int i=0;i<40;i++){ sum += touchReadCTM(TOUCH_PIN); delay(2); }
  baseline = sum/40;
  filt = baseline;

  Serial.println("\nMorse key ready. Tap for dot, hold for dash.");
  Serial.print("Baseline="); Serial.println(baseline);
}

void loop() {
  // --- capacitive read + smoothing ---
  unsigned long v = readAvg(TOUCH_PIN);
  filt = FILTER_ALPHA * v + (1.0f - FILTER_ALPHA) * filt;

  // hysteresis around baseline
  static bool touched = false;
  int onT  = baseline + TOUCH_MARGIN;
  int offT = baseline + TOUCH_MARGIN/2;
  if (!touched && filt > onT)  touched = true;
  if (touched && filt < offT)  touched = false;

  // --- edge detection ---
  unsigned long now = millis();

  if (touched && !keyDown) {
    // key press
    keyDown = true;
    lastEdgeTime = now;
    digitalWrite(LED_PIN, HIGH);
  } else if (!touched && keyDown) {
    // key release: decide dot or dash
    keyDown = false;
    unsigned long pressDur = now - lastEdgeTime;
    currentCode += (pressDur < (DASH_MS - DOT_MS)) ? "." : "-";
    lastEdgeTime = now;
    digitalWrite(LED_PIN, LOW);
  }

  // --- spacing logic (letter/word boundaries) ---
  if (!keyDown) {
    unsigned long idle = now - lastEdgeTime;
    if (currentCode.length() > 0 && idle >= LETTER_GAP && idle < WORD_GAP) {
      emitLetterIfAny();               // end of letter
      Serial.flush();
    } else if (idle >= WORD_GAP) {
      emitLetterIfAny();               // end of word
      Serial.print(' ');
      Serial.flush();
      lastEdgeTime = now;              // avoid repeated spaces
    }
  }

  delay(4);
}