/** * This code configures an ESP32C3 as a Bluetooth keyboard with a 15-switch matrix */ #include BleKeyboard bleKeyboard; // Define GPIO pins for rows and columns const int rows[] = {2, 3, 4, 5, 6}; const int cols[] = {20, 8, 9}; // Define a 2D array to represent the switch matrix const int numRows = sizeof(rows) / sizeof(rows[0]); const int numCols = sizeof(cols) / sizeof(cols[0]); bool keyStates[numRows][numCols]; // To store the state of each key unsigned long lastPressTime[numRows][numCols]; // For timing multiple presses int pressCount[numRows][numCols]; // To track multiple presses const unsigned long resetTime = 700; // Shortened reset time for faster response const unsigned long debounceDelay = 50; // Debounce delay in milliseconds unsigned long debounceStart[numRows][numCols]; // Track debounce timing bool awaitingSend[numRows][numCols]; // Track if a key is waiting to send bool shiftActive = false; // Track if Shift should apply to the next key int lastRow = -1, lastCol = -1; // Track the last pressed key void setup() { Serial.begin(115200); // Initialize BLE Keyboard bleKeyboard.begin(); // Configure row pins as outputs and set them to HIGH for (int i = 0; i < numRows; i++) { pinMode(rows[i], OUTPUT); digitalWrite(rows[i], HIGH); } // Configure column pins as inputs with pull-ups for (int j = 0; j < numCols; j++) { pinMode(cols[j], INPUT_PULLUP); } Serial.println("Bluetooth Keyboard matrix initialized, waiting for connection..."); } void loop() { static unsigned long lastKeepAlive = 0; // Tracks last keep-alive signal time const unsigned long keepAliveInterval = 60000; // Send keep-alive every 60 seconds // Ensure BLE is connected if (bleKeyboard.isConnected()) { for (int i = 0; i < numRows; i++) { // Activate the current row digitalWrite(rows[i], LOW); for (int j = 0; j < numCols; j++) { bool currentState = !digitalRead(cols[j]); // LOW means pressed unsigned long currentTime = millis(); // Handle debounce and new key press if (currentState && !keyStates[i][j] && (currentTime - debounceStart[i][j] > debounceDelay)) { debounceStart[i][j] = currentTime; // Start debounce timer keyStates[i][j] = true; pressCount[i][j]++; lastPressTime[i][j] = currentTime; // Immediately send pending key for the last pressed button if (lastRow != -1 && lastCol != -1 && !(lastRow == i && lastCol == j)) { sendFinalKeyPress(lastRow, lastCol, pressCount[lastRow][lastCol]); pressCount[lastRow][lastCol] = 0; // Reset previous key press count awaitingSend[lastRow][lastCol] = false; } // Update the last pressed key lastRow = i; lastCol = j; // Send Row 0 keys immediately if (i == 0) { sendFinalKeyPress(i, j, pressCount[i][j]); pressCount[i][j] = 0; // Reset press count for Row 0 keys } else { awaitingSend[i][j] = true; } } // Handle key release if (!currentState && keyStates[i][j] && (currentTime - debounceStart[i][j] > debounceDelay)) { keyStates[i][j] = false; debounceStart[i][j] = currentTime; } // Send key if timeout occurs if (awaitingSend[i][j] && (currentTime - lastPressTime[i][j] > resetTime)) { sendFinalKeyPress(i, j, pressCount[i][j]); pressCount[i][j] = 0; awaitingSend[i][j] = false; lastRow = -1; // Reset last key after timeout lastCol = -1; } } // Deactivate the current row digitalWrite(rows[i], HIGH); } // --- KEEP-ALIVE SIGNAL --- if (millis() - lastKeepAlive > keepAliveInterval) { bleKeyboard.print(""); // Send a harmless empty signal lastKeepAlive = millis(); } } } void sendFinalKeyPress(int row, int col, int pressCount) { // Ensure row and col are within valid bounds if (row < 0 || row >= numRows || col < 0 || col >= numCols) { Serial.println("Invalid row or column index!"); return; // Exit function if indices are out of bounds } static const char* row1_col0[] = {"1"}; static const char* row1_col1[] = {"a", "b", "c", "2"}; static const char* row1_col2[] = {"d", "e", "f", "3"}; static const char* row2_col0[] = {"g", "h", "i", "4"}; static const char* row2_col1[] = {"j", "k", "l", "5"}; static const char* row2_col2[] = {"m", "n", "o", "6"}; static const char* row3_col0[] = {"p", "q", "r", "s", "7"}; static const char* row3_col1[] = {"t", "u", "v", "8"}; static const char* row3_col2[] = {"w", "x", "y", "z", "9"}; // Handle Row 0 special keys if (row == 0) { bleKeyboard.releaseAll(); // Release previous keys delay(10); // Small delay to stabilize if (col == 0) { bleKeyboard.write(KEY_LEFT_ARROW); } else if (col == 1) { bleKeyboard.press(KEY_CAPS_LOCK); delay(50); bleKeyboard.press(' '); delay(50); bleKeyboard.release(' '); bleKeyboard.release(KEY_CAPS_LOCK); } else if (col == 2) { bleKeyboard.write(KEY_RIGHT_ARROW); } delay(10); // Ensure sufficient buffer before next press lastRow = -1; // Clear last pressed key state lastCol = -1; return; } // Handle multi-press for Row 4 if (row == 4) { if (col == 0) bleKeyboard.write(pressCount == 2 ? '*' : KEY_BACKSPACE); else if (col == 1) bleKeyboard.print(pressCount == 3 ? "0" : (pressCount == 2 ? "." : " ")); else if (col == 2) { if (pressCount == 1) shiftActive = true; else bleKeyboard.print("#"); } return; } // Apply shift if active if (shiftActive) { bleKeyboard.press(KEY_LEFT_SHIFT); } // Send multi-press options for Rows 1-3 switch (row) { case 1: if (col == 0) sendMultiPress(row1_col0, pressCount, 1); else if (col == 1) sendMultiPress(row1_col1, pressCount, 4); else if (col == 2) sendMultiPress(row1_col2, pressCount, 4); break; case 2: if (col == 0) sendMultiPress(row2_col0, pressCount, 4); else if (col == 1) sendMultiPress(row2_col1, pressCount, 4); else if (col == 2) sendMultiPress(row2_col2, pressCount, 4); break; case 3: if (col == 0) sendMultiPress(row3_col0, pressCount, 5); else if (col == 1) sendMultiPress(row3_col1, pressCount, 4); else if (col == 2) sendMultiPress(row3_col2, pressCount, 5); break; } // Release shift if active if (shiftActive) { bleKeyboard.release(KEY_LEFT_SHIFT); shiftActive = false; } } void sendMultiPress(const char* options[], int pressCount, int numOptions) { int index = (pressCount - 1) % numOptions; bleKeyboard.print(options[index]); Serial.print("Key sent: "); Serial.println(options[index]); }