A cheap membrane keypad plus a Leonardo is a USB macro pad: one key for copy, one for paste, one to save, and a numpad for laptops that have none. It is a good first HID project because nothing happens unless you press a key.
The keypad’s eight wires go to D4–D11. Digits type themselves; A–D send Ctrl+C, Ctrl+V, Ctrl+Z and Ctrl+S; * is Alt+Tab and # is Enter. Change the switch to your own shortcuts.
Wiring
| Arduino Leonardo | 4×4 matrix membrane keypad | Wire | Notes |
|---|---|---|---|
| D4 | R1 | orange | |
| D5 | R2 | orange | |
| D6 | R3 | orange | |
| D7 | R4 | orange | |
| D8 | C1 | blue | |
| D9 | C2 | blue | |
| D10 | C3 | blue | |
| D11 | C4 | blue |
No supply — passive switch matrix part, 5 V board. The keypad has no electronics, so it works at 3.3 V or 5 V. It does need eight GPIOs; on ESP32 avoid the strapping pins (0, 2, 5, 12, 15) and the input-only pins (34–39) for the row outputs.
Board quirks
- D4–D11 are used so that D0/D1 (Serial1) and D2/D3 (I2C) stay free — add an OLED on D2/D3 later to show what each key does.
- The Leonardo appears as a standard USB keyboard. Shortcuts follow the PC’s layout: Ctrl+C on Windows/Linux; on a Mac change
KEY_LEFT_CTRLtoKEY_LEFT_GUI. - Keys are only sent when a key is pressed, so the sketch can’t run away. Avoid sketches that type in
loop()without a button. - Install both the Keypad library and Arduino’s Keyboard library; the Keyboard library only compiles for native-USB boards like the Leonardo.
Code
// 4x4 keypad as a USB macro pad / numpad on an Arduino Leonardo (native USB keyboard).
// R1-R4 → D4, D5, D6, D7; C1-C4 → D8, D9, D10, D11. D0/D1 (Serial1) and D2/D3 (I2C) stay free.
// Digits type themselves, A = Ctrl+C, B = Ctrl+V, C = Ctrl+Z, D = Ctrl+S, * = Alt+Tab, # = Enter.
// Keys are only sent when you press one, so this sketch can't lock you out of the PC.
#include <Keypad.h>
#include <Keyboard.h>
const byte ROWS = 4, COLS = 4;
char keys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'},
};
byte rowPins[ROWS] = {4, 5, 6, 7};
byte colPins[COLS] = {8, 9, 10, 11};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
void chord(uint8_t modifier, char key) {
Keyboard.press(modifier);
Keyboard.press(key);
delay(20);
Keyboard.releaseAll();
}
void setup() {
Serial.begin(115200);
while (!Serial && millis() < 3000) {} // native USB: wait up to 3 s, never forever
Keyboard.begin();
Serial.println(F("Macro pad ready"));
}
void loop() {
char k = keypad.getKey();
if (!k) return;
Serial.print(F("Key "));
Serial.println(k);
switch (k) {
case 'A': chord(KEY_LEFT_CTRL, 'c'); break;
case 'B': chord(KEY_LEFT_CTRL, 'v'); break;
case 'C': chord(KEY_LEFT_CTRL, 'z'); break;
case 'D': chord(KEY_LEFT_CTRL, 's'); break;
case '*': chord(KEY_LEFT_ALT, KEY_TAB); break;
case '#': Keyboard.write(KEY_RETURN); break;
default: Keyboard.write(k); // digits
}
}// 4x4 keypad as a USB macro pad / numpad on an Arduino Leonardo (native USB keyboard).
// R1-R4 → D4, D5, D6, D7; C1-C4 → D8, D9, D10, D11. D0/D1 (Serial1) and D2/D3 (I2C) stay free.
// Digits type themselves, A = Ctrl+C, B = Ctrl+V, C = Ctrl+Z, D = Ctrl+S, * = Alt+Tab, # = Enter.
// Keys are only sent when you press one, so this sketch can't lock you out of the PC.
#include <Keypad.h>
#include <Keyboard.h>
const byte ROWS = 4, COLS = 4;
char keys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'},
};
byte rowPins[ROWS] = {4, 5, 6, 7};
byte colPins[COLS] = {8, 9, 10, 11};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
void chord(uint8_t modifier, char key) {
Keyboard.press(modifier);
Keyboard.press(key);
delay(20);
Keyboard.releaseAll();
}
void setup() {
Serial.begin(115200);
while (!Serial && millis() < 3000) {} // native USB: wait up to 3 s, never forever
Keyboard.begin();
Serial.println(F("Macro pad ready"));
}
void loop() {
char k = keypad.getKey();
if (!k) return;
Serial.print(F("Key "));
Serial.println(k);
switch (k) {
case 'A': chord(KEY_LEFT_CTRL, 'c'); break;
case 'B': chord(KEY_LEFT_CTRL, 'v'); break;
case 'C': chord(KEY_LEFT_CTRL, 'z'); break;
case 'D': chord(KEY_LEFT_CTRL, 's'); break;
case '*': chord(KEY_LEFT_ALT, KEY_TAB); break;
case '#': Keyboard.write(KEY_RETURN); break;
default: Keyboard.write(k); // digits
}
}Library: Keypad (Mark Stanley, Alexander Brevig) — compiled with Keypad 3.1.1.
Expected serial output
Macro pad ready
Key 4
Key A
Key #Macro pad ready Key 4 Key A Key #
Common errors
‘Keyboard’ not found. Does your sketch include the line #include <Keyboard.h>?
Board set to Uno instead of Leonardo, or the Keyboard library missing. Select Arduino Leonardo.
Wrong characters on a non-US keyboard layout
The Keyboard library sends US key codes. Set the PC’s input language to English (US). Recent Keyboard library versions also accept a layout in Keyboard.begin().