아두이노 64키 프로토타이핑 키보드 매트릭스 - 시프트 레지스터로 핀 절약하기
프로젝트 동기
임베디드 기기에 내장형 키보드가 필요할 때, USB 키보드를 쓰기엔 오버스펙이고 기존 아두이노 키보드 라이브러리는 커스터마이징이 제한적입니다. 74HC595 시프트 레지스터로 행(Row) 8개를 3핀으로 제어해, 총 11개 GPIO(행 3핀 + 열 8핀)로 64키 매트릭스를 구동하는 프로토타입 전용 PCB 키보드를 설계했습니다.
키보드 매트릭스 원리
왜 매트릭스가 필요한가?
64개 키를 개별 연결하려면 64개 GPIO 필요. 매트릭스는 행×열 = 8×8 구조로 √64 = 8 + 8 = 16핀으로 줄이고, 시프트 레지스터로 행 8핀을 3핀(데이터, 래치, 클록)으로 압축해 최종 11핀만 사용.
스캔 방식 (로우 액티브 + 풀업)
- 모든 행을 HIGH(비활성)로 유지
- 대상 행만 LOW로 설정 (시프트 레지스터로 1비트만 0 전송)
- 8개 열 핀(INPUT_PULLUP) 읽기: LOW인 열 = 해당 행/열 교차점 키 눌림
- 다음 행으로 이동, 초당 200~250회 전체 스캔
다이오드(1N4148)의 역할: 고스팅 방지 & N-키 롤오버
다이오드 없이 동시 키 입력 시 전류 역류로 미눌림 키가 눌린 듯 인식되는 고스팅(Ghosting) 발생. 각 키 직렬에 다이오드 배치로 전류 단방향 제한 → 동시 입력 개수 제한 없는 N-키 롤오버(NKRO) 구현.
PCB 설계 및 제작 (KiCad)
회로도: 64개 키 심볼 복제 → 행/열 넷 연결 → 핀헤더 2개(행/열) 배치
풋프린트 주의점
KiCad 기본 다이오드 심볼 핀 번호와 풋프린트 핀 번호가 달라 초판 PCB에서 다이오드 극성 역결선 발생. 커스텀 풋프린트로 핀 번호 스왑 후 재제작 필수.
레이아웃
Fusion 360으로 보드 외곽 DXF 생성 → KiCad Edge.Cuts 레이어 임포트. 키캡 간격 표준(19.05mm 피치) 준수. 비아로 2층 배선 처리, 구리 Pour로 노이즈 저감.
제작
거버 파일 ZIP으로 JLCPCB 등 업로드 주문. 부품 실장: 다이오드→버튼→핀헤더 순 권장. 핀헤더를 보드 하단에 장착하면 브레드보드 직결 가능.
아두이노 연결 배선
| 키보드 측 | 아두이노 핀 | 비고 |
| 열 1~6 | A0~A5 | 아날로그 핀을 디지털 입력으로 사용 |
| 열 7 | D5 | |
| 열 8 | D6 | |
| 시프트 레지스터 데이터 | D2 | 74HC595 핀 14 (DS) |
| 시프트 레지스터 래치 | D3 | 74HC595 핀 12 (ST_CP) |
| 시프트 레지스터 클록 | D4 | 74HC595 핀 11 (SH_CP) |
| 시프트 레지스터 출력 Q0~Q7 | 행 1~8 | 순서 주의: Q0, Q1, Q2, Q3, Q4, Q5, Q6, Q7 → 행 1~8 |
※ D0/D1(시리얼) 사용 안 함. 시프트 레지스터 VCC/GND는 5V/그라운드 공통, OE 핀은 GND(출력 활성), MR 핀은 5V(리셋 비활성).
펌웨어: 완전 커스터마이징 가능한 키맵
버전 1.2 (2021/1/30 업데이트) 핵심 개선
- 전체 키보드 상태 비교 방식으로 변경: 이전 "마지막 키 추적" 방식은 동시 입력/연속 동일 키 입력 시 오작동
- 각 모디파이어(Shift, Caps, Ctrl, Alt, Fn, Spcl)별 독립 키맵 배열 제공
- 프로그램 메모리 3532바이트(10%), 동적 메모리 605바이트(29%)로 최적화
- 마이크로초 단위 인터벌(기본 500µs)로 고속 MCU(ARM Cortex-M4 등)에서도 동일 동작 보장
키맵 배열 구조 (ASCII 코드 기준)
// 기본 키맵 (모디파이어 없음)
const char key[] = {
0, '1','2','3','4','5','6','7','8','9','0','-','=', 0, '\t',
'q','w','e','r','t','y','u','i','o','p','[',']','\\', 0,
'a','s','d','f','g','h','j','k','l',';','\'', 0, 0,
'z','x','c','v','b','n','m',',','.','/', 0, 0, 0, 0,
' ', 0, 0, 0, 0, 0, 0, 0
};
// Shift + Caps 동시 활성화용 별도 배열
const char capsShiftKey[] = { ... };
// Shift만 활성화
const char shiftKey[] = { ... };
// Ctrl, Alt, Fn, Spcl, Caps 전용 배열 각각 정의
배열 인덱스 = 행×8 + 열 (0~63). 0은 미할당/기능 키. Caps Lock 토글은 키 코드 7(위치 행1/열13)에 할당.
메인 스캔 루프 (간소화)
void checkKeyboard() {
updateShiftRegister(shiftRow[rowToCheck]); // 대상 행만 LOW
// 8개 열 읽어 rowState[rowToCheck] 비트맵 갱신
updateShiftRegister(0xFF); // 모든 행 HIGH로 복구 (블리딩 방지)
if (++rowToCheck > 7) { // 8행 완료 시
checkPressedKeys(); // 이전 상태와 비교 → 키 이벤트 발생
rowToCheck = 0;
}
}
void checkPressedKeys() {
// 모디파이어 키 상태 읽기 (Shift: 행5열1|행6열4 등)
// capsShift = shift && caps 조합 처리
for (int r=7; r>=0; r--)
for (int c=7; c>=0; c--)
if (bitRead(rowState[r],c) && !bitRead(prevRowState[r],c)) {
int idx = r*8 + c;
char ch = selectKeymap(idx); // 모디파이어 조합에 따라 배열 선택
processKey(ch);
}
// prevRowState 갱신
}
void processKey(char ch) {
if (ch == 7) caps = !caps; // Caps Lock 토글
else Serial.print(ch); // 실제 프로젝트에선 display.print() 등으로 치환
}
자신만의 키보드로 커스터마이징하기
- 상단 키맵 배열의 ASCII 코드 수정: 원하는 문자/제어 코드로 변경
processKey()에서 특수 기능 추가: 백스페이스(8), 엔터(10), 펑션 키 등
- 시리얼 출력 대신 OLED/LCD 출력:
Serial.print → display.print 일괄 치환
- USB HID 키보드 구현:
Keyboard.press() 호출로 PC 인식 키보드로 변환 가능 (Leonardo/Micro/Pro Micro 필요)
성능 및 한계
- 스캔 주기: 8행 × 500µs = 4ms (250Hz) — 디바운싱 및 실시간성 충분
- 시프트 레지스터 출력 전환 시간(약 100ns) 고려해 행 간 간격 확보
- 열 입력에 풀업만 사용 → 긴 케이블/노이즈 환경에선 외부 풀업/필터 권장
- 입력용 시프트 레지스터(74HC165 등) 미사용: 핀 여유 시 확장 가능
부품 리스트 요약
- 커스텀 PCB (거버 파일 제공)
- 6×6×5mm 택트 스위치 × 64
- 1N4148 다이오드 × 64
- 74HC595 시프트 레지스터 × 1
- 1×8 핀헤더 × 2 (암/수 선택)
- 아두이노 우노 또는 호환 보드, 브레드보드, 점퍼 와이어
거버 파일 및 소스 코드
프로젝트 페이지의 Custom parts 섹션에 PCB 거버 ZIP 파일(keyplots_4xipOSLHzg.zip), Code 섹션에 ProtoKeyboardV1.1-Shifted.ino(최신 버전)와 ProtoKeyboardV1-Bits.ino(구 버전) 제공.