Привет, Хабр!
Так бывает, что иногда клавиатуры ломаются и если мембранные клавиатуры обычно представляют малую ценность и проще просто купить новую, то вот механические заметно дороже и их имеет смысл восстанавливать. Когда речь о поломках механических клавиатур и их ремонте, то как правило всё сводится к замене вышедших из строя переключателей, но вот что делать если вышел из строя контроллер?
Как и почему
Началось всё с того, что у товарища сломалась клавиатура ViewSonic KU520 в полноразмерной версии на 104 клавиши (есть ещё TKL версия с тем же названием), в которой просто отгнила ножка контроллера. Был ли там активный флюс не отмыт или ещё что, я не знаю, но как потом выяснилось она была не одна, а ещё несколько ножек тоже были готовы отвалиться. Может кто-то и умеет подпаиваться к хвостикам торчащим из чипа меньше чем на миллиметр, но я нет. Да и речь не об этом, в конце концов контроллер может выйти из строя по разным причинам. Можно наверное было поискать такой же, потом поискать как и чем его прошивать и возможно кто-то когда-то напишет об этом на Хабре. Контроллер был Holtek HT68FB560. Я же покажу более универсальный способ подходящий для гораздо большего числа клавиатур.
Восстанавливать работоспособность я буду с помощью QMK и микроконтроллера RP2040. Вообще речь заходит про QMK обычно в контексте создания кастомных клавиатур или в контексте подключения каких-нибудь нестандартных старых клавиатур к современному ПК, а не в плане ремонта и восстановления работоспособности обычных современных механических клавиатур. QMK (QuantumMechanicalKeyboard) — прошивка с открытым исходным кодом для микроконтроллеров клавиатур с архитектурой AtmelAVR и некоторых ARM, т.е. различные ATmega, STM32, RP2040 и т.д. Выбирать можно то, что вам больше подходит по количеству выводов, ну и больше нравится. В моём случае это Raspberry Pi RP2040 в исполнении SuperMini.
Микроконтроллер
Почему собственно RP2040 именно в таком исполнении, ведь есть же версии, которые чуть побольше и соответственно паять их поудобнее? Из-за размеров.
Платка идеально ложится на свободное место на клавиатуре где нет кнопок.
Вообще они бывают немного разные даже в таком исполнении. Может быть 2 или одна кнопка, дополнительные светодиоды и самое главное разное расположение выводов, так что смотреть нужно внимательно. На сайте QMK есть предупреждение, что могут не совпадать надписи с выводами. У меня на платке шелкография такова, что 29-й вывод можно спутать с 24-м.
Спецификации RP2040:
Dual-core Arm Cortex-M0+ processor, flexible clock running up to 133 MHz
264kB on-chip SRAM
2 × UART, 2 × SPI controllers, 2 × I2C controllers, 16 × PWM channels
1 × USB 1.1 controller and PHY, with host and device support
8 × Programmable I/O (PIO) state machines for custom peripheral support
Operating temperature -40°C to +85°C
Drag-and-drop programming using mass storage over USB
Low-power sleep and dormant modes
Temperature sensor
Accelerated integer and floating-point libraries on-chip
Если посмотреть на спецификации RP2040 можно подумать, что применять его для клавиатуры это как стрелять из пушки по воробьям, но при сопоставимой (даже чуть меньшей) стоимости с Holtek HT68FB560 почему бы и нет.
Пайка
Прежде чем что-то паять (да и выбирать контроллер тоже) нужно прозвонить клавиатуру, дабы понять какая здесь матрица. О том, что такое матрица клавиатуры можно почитать например здесь. Вооружившись мультиметром составляем схему столбцов и строк, попутно смотря как подключены диоды.
Получилась матрица 17х8, а значит нам потребуется 25 выводов непосредственно на клавиши и плюс ещё 3 на светодиодные индикаторы. Итого 28. Товарищ сказал, что подсветка ему не нужна, он всё равно её выключает, так что её восстановлением я не занимался, хотя QMK поддерживает различные виды подсветки. Часть выводов находится с обратной стороны платки.
Припаиваем все провода к платке, а затем к столбцам и строкам на плате клавиатуры, не забывая про светодиоды индикаторы. На 25-ом выводе на платке припаян красный светодиод на котором можно проверить работу индикации Num Lock например. Причем выход 25 есть отдельно, так что светодиод клавиатуры можно подпаять, оставив этот на своём месте, пусть освещает своим красным светом мрак внутри клавиатуры. Светодиоды припаивал через резисторы. Чтобы нигде ничего не двигалось приклеил термоклеем плату и все провода на плате где они могли двигаться.
Качество пайки можете не оценивать, я знаю что оно "великолепное". Но если даже я, с довольно посредственными навыками пайки, справился, то и у вас получится. Аппаратная часть готова, приступим к программной.
Прошивка
Для создания прошивки под Windows можно использовать QMK MSYS. Поскольку RP2040 прошивается просто закидыванием прошивки на него в режиме флешки, драйвера при установке QMK MSYS можно никакие не ставить.
Запускаем QMK MSYS и при первом запуске нужно выполнить команду:
qmk setup
Далее создаём новую клавиатуру:
qmk new-keyboard
Здесь последовательно отвечая на вопросы задаём название клавиатуры, раскладку, тип контроллера. Поскольку у меня обычная полноразмерная клавиатура, раскладка будет fullsize_ansi. Контроллер promicro_rp2040. После чего сформируется папка с названием клавиатуры, в моём случае это viewsonic_ku520, где нас интересует файл keyboard.json, который нужно отредактировать в соответствии с матрицей, выводами на светодиоды и т.п. Тут нужно указать так же diode_direction, в моём случае это ROW2COL.
Diode_direction может быть настроено в двух направлениях:
ROW2COL - диоды направлены от строк к столбцам (катоды подключены к столбцам). COL2ROW - диоды направлены от столбцов к строкам (катоды подключены к строкам).
Так же здесь можно включить bootmagic, это кнопка на клавиатуре, которая будет выполнять ту же функцию, что и физическая кнопка BOOT на платке, а именно переводить RP2040 в режим флешки. Обычно её назначают на Esc.
Полученный результат:
keyboard.json
{
"manufacturer": "ViewSonic",
"keyboard_name": "ku520",
"maintainer": "Ivan",
"development_board": "promicro_rp2040",
"diode_direction": "ROW2COL",
"features": {
"bootmagic": true,
"command": false,
"console": false,
"extrakey": true,
"mousekey": false,
"nkro": true
},
"indicators": {
"caps_lock": "GP27",
"num_lock": "GP25",
"scroll_lock": "GP28"
},
"matrix_pins": {
"cols": ["GP0", "GP1", "GP2", "GP3", "GP4", "GP5", "GP6", "GP7", "GP8", "GP9", "GP10", "GP11", "GP12", "GP13", "GP14", "GP15", "GP26"],
"rows": ["GP17", "GP18", "GP19", "GP20", "GP21", "GP22", "GP23", "GP24"]
},
"url": "",
"usb": {
"device_version": "1.0.0",
"max_power": 100,
"pid": "0x0000",
"vid": "0xFEED"
},
"bootmagic": {
"matrix": [0, 0]
},
"layouts": {
"LAYOUT_fullsize_ansi": {
"layout": [
{"label": "Esc", "matrix": [0, 0], "x": 0, "y": 0},
{"label": "F1", "matrix": [0, 1], "x": 2, "y": 0},
{"label": "F2", "matrix": [0, 2], "x": 3, "y": 0},
{"label": "F3", "matrix": [0, 3], "x": 4, "y": 0},
{"label": "F4", "matrix": [0, 4], "x": 5, "y": 0},
{"label": "F5", "matrix": [0, 5], "x": 6.5, "y": 0},
{"label": "F6", "matrix": [0, 6], "x": 7.5, "y": 0},
{"label": "F7", "matrix": [0, 7], "x": 8.5, "y": 0},
{"label": "F8", "matrix": [0, 8], "x": 9.5, "y": 0},
{"label": "F9", "matrix": [0, 9], "x": 11, "y": 0},
{"label": "F10", "matrix": [0, 10], "x": 12, "y": 0},
{"label": "F11", "matrix": [0, 11], "x": 13, "y": 0},
{"label": "F12", "matrix": [0, 12], "x": 14, "y": 0},
{"label": "PrtSc", "matrix": [1, 13], "x": 15.25, "y": 0},
{"label": "ScrLock", "matrix": [1, 14], "x": 16.25, "y": 0},
{"label": "Pause", "matrix": [1, 15], "x": 17.25, "y": 0},
{"label": "`", "matrix": [1, 0], "x": 0, "y": 1.25},
{"label": "1", "matrix": [1, 1], "x": 1, "y": 1.25},
{"label": "2", "matrix": [1, 2], "x": 2, "y": 1.25},
{"label": "3", "matrix": [1, 3], "x": 3, "y": 1.25},
{"label": "4", "matrix": [1, 4], "x": 4, "y": 1.25},
{"label": "5", "matrix": [1, 5], "x": 5, "y": 1.25},
{"label": "6", "matrix": [1, 6], "x": 6, "y": 1.25},
{"label": "7", "matrix": [1, 7], "x": 7, "y": 1.25},
{"label": "8", "matrix": [1, 8], "x": 8, "y": 1.25},
{"label": "9", "matrix": [1, 9], "x": 9, "y": 1.25},
{"label": "0", "matrix": [1, 10], "x": 10, "y": 1.25},
{"label": "-", "matrix": [1, 11], "x": 11, "y": 1.25},
{"label": "+", "matrix": [1, 12], "x": 12, "y": 1.25},
{"label": "Backspace", "matrix": [3, 12], "x": 13, "y": 1.25, "w": 2},
{"label": "Ins", "matrix": [2, 13], "x": 15.25, "y": 1.25},
{"label": "Home", "matrix": [2, 14], "x": 16.25, "y": 1.25},
{"label": "PgUp", "matrix": [2, 15], "x": 17.25, "y": 1.25},
{"label": "NumLock", "matrix": [0, 13], "x": 18.5, "y": 1.25},
{"label": "/", "matrix": [0, 14], "x": 19.5, "y": 1.25},
{"label": "*", "matrix": [0, 15], "x": 20.5, "y": 1.25},
{"label": "-", "matrix": [0, 16], "x": 21.5, "y": 1.25},
{"label": "Tab", "matrix": [2, 0], "x": 0, "y": 2.25, "w": 1.5},
{"label": "Q", "matrix": [2, 1], "x": 1.5, "y": 2.25},
{"label": "W", "matrix": [2, 2], "x": 2.5, "y": 2.25},
{"label": "E", "matrix": [2, 3], "x": 3.5, "y": 2.25},
{"label": "R", "matrix": [2, 4], "x": 4.5, "y": 2.25},
{"label": "T", "matrix": [2, 5], "x": 5.5, "y": 2.25},
{"label": "Y", "matrix": [2, 6], "x": 6.5, "y": 2.25},
{"label": "U", "matrix": [2, 7], "x": 7.5, "y": 2.25},
{"label": "I", "matrix": [2, 8], "x": 8.5, "y": 2.25},
{"label": "O", "matrix": [2, 9], "x": 9.5, "y": 2.25},
{"label": "P", "matrix": [2, 10], "x": 10.5, "y": 2.25},
{"label": "{", "matrix": [2, 11], "x": 11.5, "y": 2.25},
{"label": "}", "matrix": [2, 12], "x": 12.5, "y": 2.25},
{"label": "|", "matrix": [4, 12], "x": 13.5, "y": 2.25, "w": 1.5},
{"label": "Del", "matrix": [3, 13], "x": 15.25, "y": 2.25},
{"label": "End", "matrix": [3, 14], "x": 16.25, "y": 2.25},
{"label": "PgDn", "matrix": [3, 15], "x": 17.25, "y": 2.25},
{"label": "7", "matrix": [5, 13], "x": 18.5, "y": 2.25},
{"label": "8", "matrix": [5, 15], "x": 19.5, "y": 2.25},
{"label": "9", "matrix": [2, 16], "x": 20.5, "y": 2.25},
{"label": "+", "matrix": [1, 16], "x": 21.5, "y": 2.25, "h": 2},
{"label": "CapsLock", "matrix": [3, 0], "x": 0, "y": 3.25, "w": 1.75},
{"label": "A", "matrix": [3, 1], "x": 1.75, "y": 3.25},
{"label": "S", "matrix": [3, 2], "x": 2.75, "y": 3.25},
{"label": "D", "matrix": [3, 3], "x": 3.75, "y": 3.25},
{"label": "F", "matrix": [3, 4], "x": 4.75, "y": 3.25},
{"label": "G", "matrix": [3, 5], "x": 5.75, "y": 3.25},
{"label": "H", "matrix": [3, 6], "x": 6.75, "y": 3.25},
{"label": "J", "matrix": [3, 7], "x": 7.75, "y": 3.25},
{"label": "K", "matrix": [3, 8], "x": 8.75, "y": 3.25},
{"label": "L", "matrix": [3, 9], "x": 9.75, "y": 3.25},
{"label": ";", "matrix": [3, 10], "x": 10.75, "y": 3.25},
{"label": "'", "matrix": [3, 11], "x": 11.75, "y": 3.25},
{"label": "Enter", "matrix": [5, 12], "x": 12.75, "y": 3.25, "w": 2.25},
{"label": "4", "matrix": [6, 13], "x": 18.5, "y": 3.25},
{"label": "5", "matrix": [6, 15], "x": 19.5, "y": 3.25},
{"label": "6", "matrix": [3, 16], "x": 20.5, "y": 3.25},
{"label": "LShift", "matrix": [4, 0], "x": 0, "y": 4.25, "w": 2.25},
{"label": "Z", "matrix": [4, 2], "x": 2.25, "y": 4.25},
{"label": "X", "matrix": [4, 3], "x": 3.25, "y": 4.25},
{"label": "C", "matrix": [4, 4], "x": 4.25, "y": 4.25},
{"label": "V", "matrix": [4, 5], "x": 5.25, "y": 4.25},
{"label": "B", "matrix": [4, 6], "x": 6.25, "y": 4.25},
{"label": "N", "matrix": [4, 7], "x": 7.25, "y": 4.25},
{"label": "M", "matrix": [4, 8], "x": 8.25, "y": 4.25},
{"label": "<", "matrix": [4, 9], "x": 9.25, "y": 4.25},
{"label": ">", "matrix": [4, 10], "x": 10.25, "y": 4.25},
{"label": "?", "matrix": [4, 11], "x": 11.25, "y": 4.25},
{"label": "RShift", "matrix": [5, 11], "x": 12.25, "y": 4.25, "w": 2.75},
{"label": "Up", "matrix": [4, 14], "x": 16.25, "y": 4.25},
{"label": "1", "matrix": [7, 14], "x": 18.5, "y": 4.25},
{"label": "2", "matrix": [7, 15], "x": 19.5, "y": 4.25},
{"label": "3", "matrix": [4, 16], "x": 20.5, "y": 4.25},
{"label": "Enter", "matrix": [6, 16], "x": 21.5, "y": 4.25, "h": 2},
{"label": "LCtrl", "matrix": [5, 0], "x": 0, "y": 5.25, "w": 1.25},
{"label": "Win", "matrix": [5, 1], "x": 1.25, "y": 5.25, "w": 1.25},
{"label": "LAlt", "matrix": [5, 2], "x": 2.5, "y": 5.25, "w": 1.25},
{"label": "Space", "matrix": [5, 4], "x": 3.75, "y": 5.25, "w": 6.25},
{"label": "RAlt", "matrix": [5, 7], "x": 10, "y": 5.25, "w": 1.25},
{"label": "Fn", "matrix": [5, 8], "x": 11.25, "y": 5.25, "w": 1.25},
{"label": "Menu", "matrix": [5, 9], "x": 12.5, "y": 5.25, "w": 1.25},
{"label": "RCtrl", "matrix": [5, 10], "x": 13.75, "y": 5.25, "w": 1.25},
{"label": "Left", "matrix": [4, 13], "x": 15.25, "y": 5.25},
{"label": "Down", "matrix": [5, 14], "x": 16.25, "y": 5.25},
{"label": "Right", "matrix": [4, 15], "x": 17.25, "y": 5.25},
{"label": "0", "matrix": [6, 14], "x": 18.5, "y": 5.25, "w": 2},
{"label": ".", "matrix": [5, 16], "x": 20.5, "y": 5.25}
]
}
}
}
Теперь в QMK MSYS нужно выполнить команду:
qmk compile -kb viewsonic_ku520 -km default
чтобы сформировалась прошивка viewsonic_ku520_default.uf2, которая будет лежать в папке qmk_firmware. После чего подключаем нашу разобранную клавиатуру к USB порту ПК с зажатой кнопкой BOOT на платке, закидываем прошивку на флешку. Процесс прошивки должен запуститься сразу, после чего перезагрузка и если все сделано правильно, клавиатура должна заработать как надо.
Проверить работоспособность можно в QMK Toolbox, в котором имеется Key Tester.
QMK Toolbox может пригодиться и для прошивки, если используется какой-то другой контроллер.
Вот собственно и всё. Можно собирать клавиатуру и пользоваться.
Заключение
Хотелось просто обратить внимание на то, что такой ремонт возможен, так как в отличии от тех кто делает кастомные клавиатуры, люди которые просто столкнулся с необходимостью ремонта, возможно даже не будут знать о существовании QMK. Так же хотелось описать, что-то простое. Слои, макросы VIA, это всё можно уже сделать потом если понадобится, тем более, что для прошивки теперь достаточно просто подключить клавиатуру с зажатой клавишей Esc даже не разбирая её. Надеюсь кому-то это действительно поможет оживить их клавиатуру, а на этом у меня всё.