May 24, 2025
Description
Note: This macro-pad was originally designed for personal use, so a few things were tailored to my specific setup:
- All 3D-printed parts were modeled for a 0.8mm nozzle and 0.4mm layer height. If you’re using different settings, clearances may vary.
- The board used is the Adafruit ItsyBitsy M0 Express, simply because that’s what I had.
- It is up to you how you bind the key press/release events on your computer to actual macros (if that is what you want). The firmware provided can only simulate the pressing and releasing of standard keys.
/*
Firmware for my 3D printed 4-switch macropad/keyboard.
*/
#include "Keyboard.h"
#include <Adafruit_DotStar.h>
#include <SPI.h>
// Uncomment the following to log keypresses to the serial port.
// #define DEBUG
// For the RGB LED on the ITSY BITSY M0
#define NUMPIXELS 1
#define DATAPIN 41
#define CLOCKPIN 40
Adafruit_DotStar strip(NUMPIXELS, DATAPIN, CLOCKPIN, DOTSTAR_BRG);
// Utility function to update the colour of the LED
void updatePixel(uint8_t red, uint8_t green, uint8_t blue) {
strip.setPixelColor(0, green, red, blue);
strip.show();
}
#define NUM_SWITCHES 4
// The GPIO pin numbers
const uint8_t switchPin[NUM_SWITCHES] = { 13, 12, 11, 10 };
// The last instantaneous (raw) state of the input
static uint8_t lastState[NUM_SWITCHES] = { HIGH, HIGH, HIGH, HIGH };
// The last debounced (filtered) state of the input
static uint8_t lastDebouncedState[NUM_SWITCHES] = { HIGH, HIGH, HIGH, HIGH };
// The timestamp for when the last raw input transition took place
unsigned long lastTransitionAt[NUM_SWITCHES] = { 0, 0, 0, 0 };
// How long the input state must be sustained before it is 'accepted'
unsigned long debounceDelay = 25;
void setup() {
#ifdef DEBUG
Serial.begin(115200);
while (!Serial) {};
Serial.println("Starting...");
#endif
// Configure all the input pins
for (uint8_t i = 0; i < 4; i++) {
pinMode(switchPin[i], INPUT_PULLUP);
lastState[i] = digitalRead(switchPin[i]);
lastDebouncedState[i] = lastState[i];
}
Keyboard.begin();
strip.begin();
strip.show();
}
void loop() {
uint8_t currState;
// Loop through all the inputs and handle them sequentially
for (uint8_t i = 0; i < 4; i++) {
currState = digitalRead(switchPin[i]);
if (currState != lastState[i]) {
lastTransitionAt[i] = millis();
}
if ((millis() - lastTransitionAt[i]) > debounceDelay) {
if (currState != lastDebouncedState[i]) {
#ifdef DEBUG
Serial.print("Switch ");
Serial.print(i);
Serial.print(" (debounced): ");
currState ? Serial.println("RELEASED") : Serial.println("PRESSED");
Serial.flush();
#endif
lastDebouncedState[i] = currState;
if (currState == LOW) {
// Keypress event...
switch (i) {
// You can send function keys
case 0:
updatePixel(255, 0, 0);
Keyboard.press(KEY_F1);
break;
// Or chords such as CTRL+C for copy...
case 1:
updatePixel(0, 255, 0);
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press('c');
break;
// ...and CTRL+V for paste
case 2:
updatePixel(0, 0, 255);
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press('v');
break;
// Or any other non-character key too.
case 3:
updatePixel(255, 255, 0);
Keyboard.press(KEY_DELETE);
break;
}
} else {
updatePixel(0, 0, 0);
// Key release event
switch (i) {
case 0:
Keyboard.release(KEY_F1); // Release F1 key
break;
case 1:
Keyboard.releaseAll(); // Release CTRL + C
break;
case 2:
Keyboard.releaseAll(); // Release CTRL + V
break;
case 3:
Keyboard.release(KEY_DELETE); // Release DELETE key
break;
}
}
}
}
// Update the last filtered state
lastState[i] = currState;
} // for (uint8_t i = 0; i < 4; i++)
delay(1);
}And that's it! Enjoy your super simple mini macropad!
License:
Creative Commons — Attribution — Noncommercial — Share Alike