Build a DIY Macro Pad with an Arduino Pro Micro
If you type the same key combo dozens of times a day — copy, paste, undo, or a shortcut that opens a favorite app — a small macro pad sitting next to your keyboard saves a surprising amount of friction. The nice part of this project is that you don’t need any specialized hardware: an Arduino Pro Micro board (ATmega32U4 chip) and a few switches are enough to create a real USB HID device that your operating system recognizes exactly like a normal keyboard.
How it works
- The ATmega32U4 on the Pro Micro has a USB controller built directly into the chip, so it can genuinely emulate an HID (Human Interface Device) — unlike an Arduino Uno, where the USB port is just a serial bridge through a secondary chip (ATmega16U2) that can’t send real keyboard events.
- Each switch connects to a digital pin using
INPUT_PULLUP, so no external resistor is needed — the pin readsHIGHwhen released andLOWwhen pressed. - The firmware continuously polls the pin states, debounces them with a timer, and when it detects a valid press, calls the
Keyboard.hlibrary to send the matching key combo to the computer.
Parts list
- Arduino Pro Micro (ATmega32U4, the common 5V/16MHz version)
- 4 mechanical switches (tactile switches or full mechanical keyswitches both work)
- Jumper wires, a breadboard, or perfboard for a permanent build
- A 3D-printed or small plastic enclosure (optional, for a finished look)
- A Micro-USB cable for flashing and the permanent connection
Sample code
#include <Keyboard.h>
const int NUM_KEYS = 4;
const int keyPins[NUM_KEYS] = {2, 3, 4, 5};
bool lastState[NUM_KEYS] = {HIGH, HIGH, HIGH, HIGH};
unsigned long lastDebounceTime[NUM_KEYS] = {0, 0, 0, 0};
const unsigned long DEBOUNCE_DELAY = 25; // ms
void setup() {
for (int i = 0; i < NUM_KEYS; i++) {
pinMode(keyPins[i], INPUT_PULLUP);
}
Keyboard.begin();
}
void loop() {
for (int i = 0; i < NUM_KEYS; i++) {
bool reading = digitalRead(keyPins[i]);
if (reading != lastState[i]) {
lastDebounceTime[i] = millis();
}
// Only treat it as a valid press once the state has been stable past DEBOUNCE_DELAY
if ((millis() - lastDebounceTime[i]) > DEBOUNCE_DELAY) {
if (reading == LOW && lastState[i] == HIGH) {
sendShortcut(i);
}
}
lastState[i] = reading;
}
}
void sendShortcut(int index) {
switch (index) {
case 0: // Key 1: Copy
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press('c');
break;
case 1: // Key 2: Paste
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press('v');
break;
case 2: // Key 3: Undo
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press('z');
break;
case 3: // Key 4: custom combo, e.g. open a favorite app
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press(KEY_LEFT_SHIFT);
Keyboard.press('m');
break;
}
delay(10);
Keyboard.releaseAll();
}
With just 4 keys, straight-wiring like this is simple enough and doesn’t need matrix scanning. If you want to grow to 9 keys or more, switch to a row/column matrix layout so you don’t burn through digital pins — each row connects to an output pin, each column to an input pin, and the firmware scans one row at a time to figure out which key is pressed.
Common pitfalls
- “Board not found” when re-flashing: since
Keyboard.begin()takes over the USB port to act as a keyboard, a buggy sketch that hangs on boot can make the board impossible to reflash normally. The fix: double-tap the reset button quickly to force the bootloader, then flash the fix within the next few seconds. - Skipping debounce sends duplicate keystrokes from a single press, because the mechanical contact bounces for a few milliseconds as it opens and closes.
- Forgetting
Keyboard.releaseAll()leaves a key stuck in the “pressed” state on the OS side, causing it to repeat endlessly or block other keys. - Some operating systems prompt to confirm a new HID device the first time it’s plugged in — don’t panic if the macro pad doesn’t respond in the very first second.
Where to go from here
Add a dedicated “layer” key that, when held, switches to a second set of shortcuts, wire in a rotary encoder for volume control, or move to QMK/VIA firmware entirely if you want to remap keys without reflashing every time.
Comments