/* serial-display-debounced — QPad XIAO RP2040 ────────────────────────────────────────────────────────────── Bi-directional serial interface for the QPad board: PC → Arduino (key 0): "0,\n" Arduino → PC (key 1): "1,,<0|1>\n" button_index 0=A 1=B 2=DOWN 3=LEFT 4=RIGHT 5=UP state 0=released 1=pressed Baud rate: 9600 Libraries: Adafruit SSD1306, Adafruit GFX Board: Seeed XIAO RP2040 (install via earlephilhower/arduino-pico) Change from serial-display.ino: Asymmetric debounce: a press is registered immediately on the first detected touch so response feels instant. Release is only confirmed after DEBOUNCE_COUNT consecutive "not touched" readings (~60 ms), so brief noise dips while holding cannot re-fire a press. */ #include #include #include // ── Display ──────────────────────────────────────────────── #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // ── Touch buttons ────────────────────────────────────────── // Order: A B DOWN LEFT RIGHT UP const int TOUCH_PINS[] = {3, 4, 2, 27, 1, 26}; const char* BTN_NAMES[] = {"A", "B", "DOWN","LEFT","RIGHT","UP"}; const int NUM_BUTTONS = 6; // Capacitive touch threshold (lower = more sensitive). // Raise if getting false positives; lower if pads feel unresponsive. const int TOUCH_THRESHOLD = 30; // Consecutive "not touched" readings required to confirm a release. // Press fires immediately; only release is debounced. // At ~50 Hz: 3 × 20 ms = ~60 ms hold-off against noise while held. const int DEBOUNCE_COUNT = 3; bool btnCurrent[NUM_BUTTONS]; // confirmed (debounced) state bool btnLast[NUM_BUTTONS]; // previous confirmed state int btnDebounce[NUM_BUTTONS]; // counts consecutive "not touched" readings // ── State ────────────────────────────────────────────────── String displayText = "Ready.\nSend text via\nserial port."; // ── Touch reading ────────────────────────────────────────── // Matches the algorithm in qpad-xiao/code/Arduino/test_touch_RP2040. // The pad capacitance is discharged, then we count raw loop iterations // until the pin rises through INPUT_PULLUP. A finger on the pad adds // capacitance and slows the rise → higher count → touch detected. // digitalWriteFast / digitalReadFast skip Arduino overhead for accuracy. int touchValue(int pin) { int t = 0; const int T_MAX = 200; pinMode(pin, OUTPUT); digitalWriteFast(pin, LOW); delayMicroseconds(25); pinMode(pin, INPUT_PULLUP); while (!digitalReadFast(pin) && t < T_MAX) { t++; } return t; } void update_touch() { for (int i = 0; i < NUM_BUTTONS; i++) { bool reading = touchValue(TOUCH_PINS[i]) > TOUCH_THRESHOLD; btnLast[i] = btnCurrent[i]; if (!btnCurrent[i]) { // Currently released: accept a press immediately. if (reading) { btnCurrent[i] = true; btnDebounce[i] = 0; } } else { // Currently pressed: only release after DEBOUNCE_COUNT consecutive // "not touched" readings, so noise dips cannot re-fire a press. if (!reading) { btnDebounce[i]++; if (btnDebounce[i] >= DEBOUNCE_COUNT) { btnCurrent[i] = false; btnDebounce[i] = 0; } } else { btnDebounce[i] = 0; // still touched — reset release counter } } } } // ── Display helper ───────────────────────────────────────── void updateDisplay() { display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setTextWrap(true); display.setCursor(0, 0); display.print(displayText); display.display(); } // ── Setup ────────────────────────────────────────────────── void setup() { Serial.begin(9600); while (!Serial) delay(10); Wire.begin(); if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { // Blink built-in LED if display init fails pinMode(LED_BUILTIN, OUTPUT); while (true) { digitalWrite(LED_BUILTIN, LOW); delay(150); digitalWrite(LED_BUILTIN, HIGH); delay(150); } } // Initialise button state for (int i = 0; i < NUM_BUTTONS; i++) { btnCurrent[i] = false; btnLast[i] = false; btnDebounce[i] = 0; } updateDisplay(); } // ── Loop ─────────────────────────────────────────────────── void loop() { // ── Read serial commands ────────────────────────────── // Expected format: "0,\n" // The text may contain spaces and punctuation but not commas. // Use '|' as a manual line-break character if desired. if (Serial.available()) { String line = Serial.readStringUntil('\n'); line.trim(); int comma = line.indexOf(','); if (comma > 0) { int key = line.substring(0, comma).toInt(); if (key == 0) { // Replace the pipe character with a real newline for multi-line display String text = line.substring(comma + 1); text.replace("|", "\n"); displayText = text; updateDisplay(); } } } // ── Read touch buttons ──────────────────────────────── // Send a message on every confirmed state change (press and release). update_touch(); for (int i = 0; i < NUM_BUTTONS; i++) { if (btnCurrent[i] != btnLast[i]) { Serial.print("1,"); Serial.print(i); Serial.print(","); Serial.println(btnCurrent[i] ? 1 : 0); } } delay(20); // ~50 Hz poll rate }