Arduino 64-клавишная прототипная клавиатура: матрица на сдвиговых регистрах
Компоненты и материалы
| Компонент | Количество |
|---|---|
| Arduino UNO | 1 |
| Диоды 1N4148 (быстрые коммутационные) | 64 |
| Тактильные кнопки 6x6x5 мм (верхнее нажатие) | 64 |
| Пин-хэдеры (разъёмы) | 1 комплект |
| Сдвиговый регистр 74HC595 | 1 |
Инструменты
- Паяльник
Программное обеспечение
- Arduino IDE
О проекте

Обновление: код улучшен, см. раздел «Обновление» в конце.
Работаю над проектом со встроенной клавиатурой, что создало проблему: как включить клавиатуру в прототип платы разработки? Нельзя использовать USB-клавиатуру или готовую Arduino-клавиатуру, так как в реальном проекте клавиатура подключена напрямую к микроконтроллеру, управляющему всеми остальными функциями. Поэтому я спроектировал эту базовую 64-клавишную прототипную матрицу клавиатуры на печатной плате.
Эта PCB не содержит никаких микросхем. Ряды и столбцы матрицы подключены напрямую к пин-хэдерам, чтобы клавиатуру можно было подключить к Arduino или любому другому микроконтроллеру. Идеально для прототипирования проектов со встроенной клавиатурой.
Прилагается подробный, тщательно прокомментированный код для работы с любой Arduino-совместимой платой, имеющей достаточно I/O пинов — требуется 11 пинов. Клавиатура имеет 64 клавиши, включая модификаторы Shift, Caps, Ctrl, Alt, Fn и «Special». Также есть 6 дополнительных клавиш для любых нужд. Функция каждой отдельной клавиши может быть определена индивидуально, включая поведение при активном модификаторе. На мой взгляд, это значительно полезнее существующего кода для клавиатур, который сильно ограничивает возможности настройки.
Предоставленный код выводит текст в Serial. Это легко изменить, если нужно направить вывод куда-то ещё.
Примечание о размере программы:
Код довольно большой, так как не использует никаких библиотек. Написан полностью с нуля для обеспечения полной гибкости каждой клавиши с каждым модификатором. Также учитывает реальное использование клавиатуры: например, удержание Shift при включённом Caps Lock даёт строчную букву, как и должно быть. По умолчанию удержание Fn + Esc ничего не делает, но это поведение полностью настраиваемо.
На Arduino UNO: 9100 байт (28%) программной памяти и 394 байта (19%) динамической памяти.
Комплектующие:
- Печатная плата (кастомная)
- Тактильные кнопки 6x6x5 мм (x64)
- Диоды 1N4148 (x64)
- Пин-хэдеры 1x8, мама или папа (x2)
- Сдвиговый регистр 74HC595
- Перемычки
- Макетная плата
- Arduino Uno или совместимая плата разработки
Шаг 1: Как работает матрица клавиатуры


Зачем нужна матрица клавиатуры?
64 клавиши при прямом подключении потребовали бы 64 I/O пина — это много, больше, чем есть у большинства плат. Матрица снижает потребность до числа пинов, равного квадратному корню из количества клавиш (с округлением вверх).
Устройство матрицы: все клавиши в ряду соединены вместе, все клавиши в столбце — вместе. Чтобы опросить клавиатуру, мы «активируем» первый ряд, затем проверяем каждый столбец. Если столбец активен — нажата клавиша на пересечении этого ряда и столбца. Затем деактивируем ряд 1, активируем ряд 2, снова проверяем все столбцы. После последнего ряда начинаем сначала.
Как сканируем матрицу:
«Активировать» означает установить ряд в LOW или HIGH. Здесь используем LOW, потому что на пинах столбцов используем встроенные pull-up резисторы микроконтроллера. Без pull-up/pull-down входной пин ведёт себя непредсказуемо из-за помех, фиксируя ложные нажатия.
ATmega328P (Arduino UNO) не имеет встроенных pull-down, только pull-up. Поэтому используем их. Pull-up подключают каждый входной пин к 5V, гарантируя HIGH до нажатия кнопки.
Все ряды тоже обычно в HIGH, что предотвращает соединение столбцов с рядами независимо от нажатий. Но когда проверяем ряд, устанавливаем его в LOW. Если в этом ряду нажата кнопка, образуется путь к земле — соответствующий столбец читается как LOW.
Итог: устанавливаем ряд в LOW, проверяем, какие столбцы стали LOW — это нажатые клавиши. Процесс происходит очень быстро, сканируя всю клавиатуру сотни раз в секунду. Мой код ограничивает 200 сканирований в секунду — баланс между производительностью, дребезгом и гарантией отлова каждого нажатия.
Диоды, «призрачные» нажатия и N-key rollover:
Диоды в схеме предотвращают ложные срабатывания при одновременном удержании определённых комбинаций. Диоды пропускают ток только в одном направлении, устраняя «призраки». Без диодов нажатие трёх соседних клавиш могло вызвать срабатывание четвёртой, не нажатой (показано на упрощённой схеме). Диоды устраняют это и обеспечивают «N-key rollover» — можно нажимать сколько угодно клавиш в любой комбинации без проблем.
Экономия пинов со сдвиговым регистром:
Вы заметили: матрица требует корень из числа клавиш пинов, но я сказал, что нужно 11 пинов, а не 16. Потому что используем 74HC595. Этот регистр позволяет управлять до 8 выходных пинов всего тремя пинами Arduino. Три пина передают байт (8 бит) в регистр, который устанавливает свои 8 выходов в HIGH/LOW. Используя регистр для выходных пинов рядов, экономим 5 I/O пинов!
«А почему не использовать сдвиговый регистр и для входов?» — для входа нужен другой тип регистра, которого у меня не было. К тому же это усложняет чтение столбцов и может вызывать проблемы с шумами и дребезгом. Это головная боль, которой я предпочёл не заниматься.
Шаг 2: Проектирование PCB



Схемотехника
Разместил символ кнопки и диода, скопировал до 64 клавиш. Добавил два 1x8 пин-хэдера — для рядов и столбцов. Одна сторона кнопок соединена в столбцы, другая — в ряды.
Футпринты и номера пинов
В KiCAD есть проблема: номера пинов в символе диода не совпадают с фуутпринтом. Это приводит к перевёрнутым диодам — критично из-за полярности. Я не заметил и выбросил первую партию плат. Во второй ревизии создал кастомный фуутпринт диода с поменянными номерами пинов.
Топология платы
Контур платы 만든 в Autodesk Fusion 360, экспорт DXF, импорт в KiCAD на слой Edge Cuts. Основная работа — расстановка кнопок в привычную раскладку. Трассировка стала немного хаотичной из-за несоответствия схемы и реальной раскладки, пришлось использовать виасы (переходы между слоям) на двухслойной плате. Добавил полигоны заземления — хорошая практика.
Изготовление PCB
Вывел все слои в Gerber, заархивировал. Архив можно загрузить напрямую в сервис изготовления PCB (например, JLCPCB).
Ссылка на Gerber-файлы: Google Drive
Шаг 3: Сборка PCB

Самый простой, но самый утомительный этап. Просто припаяйте все компоненты. Все выводные, паяются легко. Внимательно следите за ориентацией диодов. Метка на диоде должна совпадать с меткой на плате.
Удобнее: зафиксируйте плату третьей рукой, вставьте все диоды, переверните, припаяйте, подрежьте ножки. Потом все кнопки, припаяйте. Потом пин-хэдеры. Можно использовать маму или папу — на ваш выбор. Если папу поставить снизу платы, шаг совпадает с breadboard — можно воткнуть прямо в макетку.
Шаг 4: Подключение клавиатуры к Arduino



Проводка кажется сложной, но на самом деле нет — следите за назначением.
8 перемычек от заголовка столбцов прямо в Arduino:
- Столбец 1 → A0
- Столбец 2 → A1
- Столбец 3 → A2
- Столбец 4 → A3
- Столбец 5 → A4
- Столбец 6 → A5
- Столбец 7 → 5
- Столбец 8 → 6
Сдвиговый регистр 74HC595 на breadboard через центральный разрыв. Ориентация важна: точка = пин 1.
Питание: у регистра два пина на 5V и два на GND (см. схему).
Три провода управления регистром:
- Shift (Clock) пин 11 → Arduino 4
- Shift (Latch) пин 12 → Arduino 3
- Shift (Data) пин 14 → Arduino 2
Выходы регистра → ряды клавиатуры (обратите внимание на нестандартный порядок пинов 74HC595):
- Ряд 1 → Shift Q0 (пин 15)
- Ряд 2 → Shift Q1 (пин 1)
- Ряд 3 → Shift Q2 (пин 2)
- Ряд 4 → Shift Q3 (пин 3)
- Ряд 5 → Shift Q4 (пин 4)
- Ряд 6 → Shift Q5 (пин 5)
- Ряд 7 → Shift Q6 (пин 6)
- Ряд 8 → Shift Q7 (пин 7)
Пины Arduino 0 и 1 не используются — они заняты Serial-портом и вызывают конфликты.
Шаг 5: Загрузка кода в Arduino

Прошейте Arduino предоставленным кодом. Ничего особенного — обычная загрузка.
В коде подробные комментарии. Вкратце: пины настраиваются как входы/выходы. Основной цикл содержит таймер. Каждые 5 мс вызывается функция сканирования клавиатуры. Она вызывает отдельную функцию установки сдвигового регистра перед проверкой каждого столбца. Нажатые клавиши выводят своё значение в Serial.
Чтобы изменить, что печатается при нажатии — поменяйте Serial.print("_"); в соответствующем if. Например, можно задать поведение Fn+N. То же для любой клавиши с любым модификатором.
Многие клавиши в этом коде ничего не делают, так как вывод только в Serial. Backspace не работает — из Serial Monitor нельзя удалить уже принятые данные. Но вы свободны это изменить.
Использование клавиатуры в своих проектах
Вывод в Serial — не главная цель. Эта клавиатура для прототипирования сложных проектов. Легко менять функционал. Если хотите выводить текст на OLED — замените все Serial.print( на display.print( или нужное для вашего дисплея. Инструмент «Заменить всё» в Arduino IDE делает это за секунду.
Спасибо за чтение, надеюсь, эта клавиатура поможет в ваших проектах!
Обновление от 30.01.2021
Новый код полностью переписан и работает лучше оригинала. Главная проблема старого алгоритма: он проверял, не была ли клавиша последней нажатой. Это вызывало проблемы: если удерживать 2+ клавиши, вводилось что-то вроде «fgfgfgfgfgfg». Также нельзя было быстро нажать одну клавишу дважды (как две «м» в «bummer»).
Новый код решает обе проблемы и элегантнее. Вместо отслеживания последней клавиши — сравнивается состояние всей клавиатуры с состоянием на прошлом цикле. Цикл может работать быстрее, можно быстро нажимать одну клавишу многократно. Производительность драматически улучшена. Все символы вынесены в массивы вверху — легко найти и изменить. Есть независимые массивы для каждого модификатора. Код стал короче.
Единственный минус — больше динамической памяти, но меньше программной. На Arduino Uno: 3532 байта (10%) программной памяти и 605 байт (29%) динамической.
Бонус: код одинаково хорошо работает на быстрых МК вроде ARM Cortex-M4. Таймер опроса в микросекундах, поэтому производительность одинакова на любой плате. Легко менять частоту опроса. По умолчанию цикл каждые 500 мкс. 8 циклов на полное сканирование = 4000 мкс (4 мс, 250 раз в сек) — может быть дольше, если МК не успевает.
Код
ProtoKeyboardV1.1-Shifted.ino (оригинал)
Нет превью (только скачивание).
ProtoKeyboardV1-Bits.ino (обновлённый код)
/* Sketch for Prototyping Keyboard V1.2
* by Cameron Coward 1/30/21
* Tested on Arduino Uno. Requires custom PCB and 74HC595 shift register.
* More info: https://www.hackster.io/cameroncoward/64-key-prototyping-keyboard-matrix-for-arduino-4c9531
*/
const int rowData = 2; // Data pin shift register для рядов
const int rowLatch = 3; // Latch pin
const int rowClock = 4; // Clock pin
// Пины столбцов-входов. Пины 0 и 1 не используются (TX/RX)
const int colA = A0;
const int colB = A1;
const int colC = A2;
const int colD = A3;
const int colE = A4;
const int colF = A5;
const int colG = 5;
const int colH = 6;
// Байты для активации каждого ряда через shift register
const byte shiftRow[] = {
B01111111, B10111111, B11011111, B11101111,
B11110111, B11111011, B11111101, B11111110
};
byte rowState[] = {0,0,0,0,0,0,0,0};
byte prevRowState[] = {0,0,0,0,0,0,0,0};
// ASCII коды клавиш БЕЗ модификаторов. Модификаторы = 0 (проверяются отдельно)
const char key[] = {
0, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 45, 61, 0, 9,
113, 119, 101, 114, 116, 121, 117, 105, 111, 112, 91, 93, 92, 7,
97, 115, 100, 102, 103, 104, 106, 107, 108, 59, 39, 0, 0,
122, 120, 99, 118, 98, 110, 109, 44, 46, 47, 0, 0, 0, 0,
32, 0, 0, 0, 0, 0, 0, 0
};
// Shift + Caps активен
const char capsShiftKey[] = {
0, 33, 64, 35, 36, 37, 94, 38, 42, 40, 41, 95, 43, 0, 9,
113, 119, 101, 114, 116, 121, 117, 105, 111, 112, 123, 125, 124, 7,
97, 115, 100, 102, 103, 104, 106, 107, 108, 58, 22, 0, 0,
122, 120, 99, 118, 98, 110, 109, 44, 46, 47, 0, 0, 0, 0,
32, 0, 0, 0, 0, 0, 0, 0
};
// Shift нажат
const char shiftKey[] = {
0, 33, 64, 35, 36, 37, 94, 38, 42, 40, 41, 95, 43, 0, 9,
81, 87, 69, 82, 84, 89, 85, 73, 79, 80, 123, 125, 124, 7,
65, 83, 68, 70, 71, 72, 74, 75, 76, 58, 22, 0, 0,
90, 88, 67, 86, 66, 78, 77, 44, 46, 47, 0, 0, 0, 0,
32, 0, 0, 0, 0, 0, 0, 0
};
// Ctrl нажат
const char ctrlKey[] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0, 9,
0,0,0,0,0,0,0,0,0,0,0,0,0, 0,
0,0,0,0,0,0,0,0,0,0,0, 0, 0,
0,0,0,0,0,0,0,0,0,0,0, 0, 0, 0, 0,
32,0,0,0,0,0,0,0
};
// Special (spcl) нажат
const char spclKey[] = { 0 }; // все нули (64 элемента)
// Alt нажат
const char altKey[] = { 0 }; // все нули
// Fn нажат
const char fnKey[] = { 0 }; // все нули
// Caps Lock активен (без Shift)
const char capsKey[] = {
0, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 45, 61, 0, 9,
81, 87, 69, 82, 84, 89, 85, 73, 79, 80, 91, 93, 92, 7,
65, 83, 68, 70, 71, 72, 74, 75, 76, 59, 39, 0, 0,
90, 88, 67, 86, 66, 78, 77, 44, 46, 47, 0, 0, 0, 0,
32, 0, 0, 0, 0, 0, 0, 0
};
long previousKeyboardMicros = 0;
long keyboardInterval = 500; // мкс между проверками
int rowToCheck = 0;
bool caps = false;
bool shift = false;
bool capsShift = false;
bool ctrl = false;
bool spcl = false;
bool alt = false;
bool fn = false;
void setup() {
Serial.begin(9600);
pinMode(colA, INPUT_PULLUP);
pinMode(colB, INPUT_PULLUP);
pinMode(colC, INPUT_PULLUP);
pinMode(colD, INPUT_PULLUP);
pinMode(colE, INPUT_PULLUP);
pinMode(colF, INPUT_PULLUP);
pinMode(colG, INPUT_PULLUP);
pinMode(colH, INPUT_PULLUP);
pinMode(rowLatch, OUTPUT);
pinMode(rowClock, OUTPUT);
pinMode(rowData, OUTPUT);
updateShiftRegister(B11111111); // все ряды HIGH
}
void loop() { mainTimer(); }
void mainTimer() {
unsigned long currentMicros = micros();
if (currentMicros - previousKeyboardMicros > keyboardInterval) {
previousKeyboardMicros = currentMicros;
checkKeyboard();
}
}
void updateShiftRegister(byte row) {
digitalWrite(rowLatch, LOW);
shiftOut(rowData, rowClock, MSBFIRST, row);
digitalWrite(rowLatch, HIGH);
}
void checkKeyboard() {
updateShiftRegister(shiftRow[rowToCheck]);
if (digitalRead(colA) == LOW) bitSet(rowState[rowToCheck], 0); else bitClear(rowState[rowToCheck], 0);
if (digitalRead(colB) == LOW) bitSet(rowState[rowToCheck], 1); else bitClear(rowState[rowToCheck], 1);
if (digitalRead(colC) == LOW) bitSet(rowState[rowToCheck], 2); else bitClear(rowState[rowToCheck], 2);
if (digitalRead(colD) == LOW) bitSet(rowState[rowToCheck], 3); else bitClear(rowState[rowToCheck], 3);
if (digitalRead(colE) == LOW) bitSet(rowState[rowToCheck], 4); else bitClear(rowState[rowToCheck], 4);
if (digitalRead(colF) == LOW) bitSet(rowState[rowToCheck], 5); else bitClear(rowState[rowToCheck], 5);
if (digitalRead(colG) == LOW) bitSet(rowState[rowToCheck], 6); else bitClear(rowState[rowToCheck], 6);
if (digitalRead(colH) == LOW) bitSet(rowState[rowToCheck], 7); else bitClear(rowState[rowToCheck], 7);
updateShiftRegister(B11111111); // все HIGH между циклами
rowToCheck++;
if (rowToCheck > 7) {
checkPressedKeys();
rowToCheck = 0;
}
}
void checkPressedKeys() {
// Модификаторы
shift = bitRead(rowState[5], 1) | bitRead(rowState[6], 4);
ctrl = bitRead(rowState[6], 5) | bitRead(rowState[7], 3);
spcl = bitRead(rowState[6], 6) | bitRead(rowState[7], 2);
alt = bitRead(rowState[6], 7) | bitRead(rowState[7], 1);
fn = bitRead(rowState[7], 4);
capsShift = (shift && caps);
for (int i = 7; i >= 0; i--) {
for (int j = 7; j >= 0; j--) {
bool newBit = bitRead(rowState[i], j);
bool prevBit = bitRead(prevRowState[i], j);
if (newBit && !prevBit) { // нажатие (переход 0→1)
int thisChar = (i * 8) + j;
if (capsShift) processKey(capsShiftKey[thisChar]);
else if (shift) processKey(shiftKey[thisChar]);
else if (ctrl) processKey(ctrlKey[thisChar]);
else if (alt) processKey(altKey[thisChar]);
else if (spcl) processKey(spclKey[thisChar]);
else if (fn) processKey(fnKey[thisChar]);
else if (caps) processKey(capsKey[thisChar]);
else processKey(key[thisChar]);
}
if (newBit) bitSet(prevRowState[i], j); else bitClear(prevRowState[i], j);
}
}
}
void processKey(char receivedKey) {
if (receivedKey == 7) { // спец-функция (Caps Lock toggle)
caps = !caps;
} else {
Serial.print(receivedKey);
}
}
Файлы для PCB
Загрузите весь .zip архив в сервис изготовления печатных плат.
Схемы
(см. вложения)
Производственный процесс
- Панель управления полётным симулятором 2020 на Arduino Nano: LCD и CDI-дисплеи
- Панель переключателей и LED для Microsoft Flight Simulator 2020 на Arduino
- Превратите MIDI-клавиатуру в интерактивного репетитора по фортепиано с Arduino, LED-лентой и MuseScore
- Мастерство 8×8 LED-матрицы с Arduino Uno: Пошаговое руководство
- Умная система мониторинга температуры для школ: точно, в реальном времени, безопасно
- Библиотека 8-битного порта ввода-вывода для Arduino — расширение цифрового управления
- TFT Shield 2.8" 320×240 SPI для Arduino Nano — высокопроизводительный дисплей
- Гальванически разделённый аналоговый вход для Arduino на LM331 и оптопаре 4N25
- Калькулятор на Arduino с 16x2 LCD и клавиатурой 4x4
- Набор для создания робота с умной внутренней навигацией на базе Arduino