01
3개의 모델링 파일 인쇄하기
키캡 16개, 상판 1개, 하판 1개를 3D 프린터로 출력합니다.
02
16개의 키캡과 축을 같은 방향으로 상판에 끼우기
스위치 축을 상판에 끼우고 키캡을 씌웁니다. 방향이 모두 같아야 다음 단계의 배선이 깔끔하게 맞습니다.
03
4줄 × 4줄 격자로 납땜해 8개의 선 만들기
가로 4줄(ROW)과 세로 4줄(COL)을 각각 이어 총 8개의 선을 만듭니다.
04
ESP32 양쪽 핀에 4개씩 연결하기
가로 4선은 한쪽, 세로 4선은 반대쪽 핀에 순서대로 연결합니다. 순서가 코드의 배열 순서와 같아야 키 번호가 맞습니다.
가로 ROWR1 → 25R2 → 32R3 → 33R4 → 27
세로 COLC1 → 4C2 → 16C3 → 17C4 → 5
숫자는 ESP32 보드의 GPIO 번호예요. ROW는 입력(내부 풀업), COL은 스캔 출력으로 쓰입니다.
05
ESP32를 하판에 넣기
보드의 짧은 쪽을 하판 벽에 붙여 가운데에 놓습니다. USB-C 포트가 벽의 구멍과 딱 맞아야 케이블을 꽂을 수 있어요.
06
상판과 하판 고정하기
상판을 하판 위에 맞춰 올리고 네 모서리의 기둥에 고정합니다.
07
펌웨어를 ESP32에 업로드하기
USB-C로 컴퓨터에 연결하고 Arduino IDE에서 보드를 'ESP32 Dev Module'로 선택한 뒤, 아래 코드를 업로드합니다. 업로드가 끝나면 'ESP32-Keypad'라는 이름으로 블루투스 연결을 기다립니다.
07 · FIRMWARE
ESP32 펌웨어 코드
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#define DEVICE_NAME "ESP32-Keypad"
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHAR_UUID "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
const int COLS[4] = {4, 16, 17, 5};
const int ROWS[4] = {25, 32, 33, 27};
const unsigned long DEBOUNCE_MS = 20;
BLEServer* pServer = nullptr;
BLECharacteristic* pChar = nullptr;
bool deviceConnected = false;
bool stableState[16] = {false};
bool lastRaw[16] = {false};
unsigned long changedAt[16] = {0};
class ServerCB : public BLEServerCallbacks {
void onConnect(BLEServer*) {
deviceConnected = true;
}
void onDisconnect(BLEServer*) {
deviceConnected = false;
BLEDevice::startAdvertising();
}
};
void sendKey(int keyNum) {
if (!deviceConnected) return;
char buf[8];
int len = snprintf(buf, sizeof(buf), "%d", keyNum);
pChar->setValue((uint8_t*)buf, len);
pChar->notify();
}
void updateKey(int idx, bool raw, unsigned long now) {
if (raw != lastRaw[idx]) {
lastRaw[idx] = raw;
changedAt[idx] = now;
}
if (now - changedAt[idx] >= DEBOUNCE_MS && raw != stableState[idx]) {
stableState[idx] = raw;
if (raw) sendKey(idx + 1);
}
}
void setup() {
for (int r = 0; r < 4; r++) pinMode(ROWS[r], INPUT_PULLUP);
for (int c = 0; c < 4; c++) pinMode(COLS[c], INPUT);
BLEDevice::init(DEVICE_NAME);
pServer = BLEDevice::createServer();
pServer->setCallbacks(new ServerCB());
BLEService* service = pServer->createService(SERVICE_UUID);
pChar = service->createCharacteristic(CHAR_UUID, BLECharacteristic::PROPERTY_NOTIFY);
pChar->addDescriptor(new BLE2902());
service->start();
BLEAdvertising* adv = BLEDevice::getAdvertising();
adv->addServiceUUID(SERVICE_UUID);
adv->setScanResponse(true);
BLEDevice::startAdvertising();
}
void loop() {
unsigned long now = millis();
for (int c = 0; c < 4; c++) {
pinMode(COLS[c], OUTPUT);
digitalWrite(COLS[c], LOW);
delayMicroseconds(30);
for (int r = 0; r < 4; r++) {
bool pressed = (digitalRead(ROWS[r]) == LOW);
updateKey(r * 4 + c, pressed, now);
}
pinMode(COLS[c], INPUT);
}
delay(2);
}