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);
}