Microsoft Sculpt Ergonomic Desktop
Microsoft Sculpt Ergonomic Desktop

Попалась мне в руки широко известная в узких кругах эргономичная клава Microsoft Sculpt Ergonomic keyboard. И понравилась она мне: и эргономичная, и ножничная, и компактная. А еще клаву можно положить на колени и удобно набирать, развалившись в кресле. В одном только беда – утерян был донгл от нее. Покопавшись в сети, узнал, что заменить родной донгл другим невозможно. Я решил спасти от помойки сделать из беспроводной клавы проводную. Думаю, тема будет интересна, ведь я не единственный обладатель такого чемодана без ручки. Так что заводим хлебный троллейбус и поехали!

Задача состоит из этапов:

  1. Разобрать клаву.

  2. Прозвонить матрицу клавиш и составить табличку строк и столбцов (подробнее об этом - матричная клавиатура).

  3. Сделать прошивку и залить в контроллер.

  4. Соединить клаву и контроллер.

Сначала я хотел взять контроллер от убитой A4Tech KV300-H и скрестив его с матрицей Ms Sculpt Ergo, сделать конфетку из мусора. Но быстро понял, что уже готовую матрицу не скрестить с уже готовым контроллером. Под готовый контроллер нужно делать матрицу, которая подойдет только под него.

Чтобы переделать клаву в проводную, понадобятся:

  1. Контроллер.

  2. Разъем для подсоединения контроллера к шлейфу матрицы.

  3. Провода. Я использовал шлейф от FDD. Однако, есть более приятные проводки для пайки, тут пример с меня брать не надо)

  4. Мультиметр для прозвонки матрицы.

  5. Пару отверток и пластиковая карточка для разборки клавиатуры.

Контроллер я взял RP2040. Он недорогой, легко и быстро прошивается: нужно просто сбросить файл прошивки на него. У него достаточно пинов для подсоединения к матрице Sculpt Ergonomic. А еще у меня уже был этот контроллер. Есть такие контроллеры еще меньше по размеру - Raspberry Pi RP2040-Zero.

Разборка

Сперва нужно отковырять пять резиновых ножек снизу клавы и открутить 5 шурупов под этими ножками.

Нижняя сторона
Нижняя сторона

Потом начиная с одного из краев поддеть пластиковой картой подставку под запястья. Подставка крепится на защелках. Сначала лучше поддеть с боков, потом нижний край, и в последнюю очередь верх - со стороны клавиш.

Снимаем подставку
Снимаем подставку

После снятия подставки выкручиваем шурупы под подставкой не забыв про шурупы в углублениях в центре.

Подставка снята
Подставка снята

И теперь самый заморочный этап разборки. Нужно пройти по периметру всей клавы пластиковой карточкой, отгибая защелки. Главное – не торопиться. Клаву желательно не перегибать, ведь матрица с ножничным механизмом скреплена пластиковыми столбиками, расплавленными с одной стороны. Они могут отламываться, и если отломать их слишком много, то ремонт будет затруднен. Ножничный механизм клавиатур мало ремонтопригоден.

Поддеваем пластиковой карточкой
Поддеваем пластиковой карточкой

В итоге клава разделяется на две половинки. Откручиваем контроллер и отстегиваем его от шлейфа.

Внутри
Внутри

Прозвонка матрицы

Это самое нудное. В результате получается такая табличка.

Матрица Microsoft Sculpt Ergonomic Desktop
Матрица Microsoft Sculpt Ergonomic Desktop

Какие дорожки ряды, какие колонки – на картинке:

Распиновка шлейфа. Верхняя часть - к клавиатуре, правая - к RP2040
Распиновка шлейфа. Верхняя часть - к клавиатуре, правая - к RP2040

Белым цветом обозначены физические номера дорожек шлейфа. А цветными, нумерация в прошивке, поэтому нумерация и идет от нуля. 30й пин – земля, что сразу видно по родному контроллеру, 29 – катод зеленого светодиода, 28 – анод красного.

Плата Fn свитча
Плата Fn свитча

1й пин - переключатель Fn, который в одном положении замкнут с землей, а в другом замкнут с землей через резистор. Я его оставил не подключенным, об этом ниже.

Сборка

Это не более, чем прототип, так что без колхозинга никак. Разъем для подсоединения подобного шлейфа называется FPC-30P 1.0mm. 30 пинов с шагом 1мм. У меня уже была такая плата со штырьками, которые вставляются в FDD шлейф. Но лучше купить с отверстиями для пайки. Придется больше паять, зато итоговая конструкция займет меньше места внутри клавы. Можно даже обойтись без подобной платки, просто подпаять проводки к пятакам на плате родного контроллера.

Шлейф, родной контроллер и разъем FPC-30p 1.0mm
Шлейф, родной контроллер и разъем FPC-30p 1.0mm

Платку с разъемом пришлось буквально дорабатывать напильником. Дора��отанный напильником (и двусторонним скотчем) разъем FPC-30P и FDD разъем ставятся на место родного контроллера.

FPC-30p 1.0mm после напилинга и FDD шлейф
FPC-30p 1.0mm после напилинга и FDD шлейф
Нижняя часть корпуса и RP2040
Нижняя часть корпуса и RP2040

Контроллер встает на место отсека батареек, в боковой стенке отсека прорезается щель для FDD шлейфа. Разумеется, и корпус клавы тоже подвергается напилингу там, где это нужно. Поэтому лучше использовать более компактный контроллер типа Raspberry Pi RP2040-Zero и не использовать разъем FDD.

Продеваем шлейф FDD в щель и убедившись, что внутрь не попало крошек, обломков и прочего, смыкаем половинки. Вкручиваем шурупы, ставим ладонную подставку на место.

Теперь надо припаять проводки шлейфа, отвечающие за дорожки рядов, колонок, и анода красного диода к GPIO выводам контроллера. У меня получилось ряды 0-7 к выводам GP16, GP17, GP18, GP19, GP20, GP21, GP15, GP14 соответственно. И колонки 0-17 к GP13, GP12, GP11, GP10, GP22, GP26, GP27, GP28, GP29, GP9, GP8, GP7, GP6, GP5, GP4, GP3, GP2, GP1 тоже соответственно. Это соответствие позже будет нужно при конфигурировании прошивки. Земля припаяна к GND, а анод красного диода к GP0.

Собранная клава с припаянным RP2040
Собранная клава с припаянным RP2040

Можно любой пин паять к любому GPIO. Но в файле прошивки нужно четко будет прописать в файле какой пин к какому GPIO. Если у вашего контроллера меньшее количество GPIO, можно объединить некоторые наименее нагруженные столбцы матрицы. Например, альт с шифтом, или левый шифр с правым шифтом. Для этого нужно на один GPIO контроллера припаять 2 пина матрицы. В этом случае будет отличаться не только пайка, но и прошивка)

Fn переключатель куда менее удобный, чем клавиша Fn. Я назначил эту клавишу на место клавиши Контекстного меню. А контекстное меню будет вызываться сочетанием Fn + Calc, об этом ниже в прошивке. Можно сделать и переключатель рабочим. Самый простой вариант: Пин 1 и землю нужно припаять к GPIO, отпаять резистор на платке переключателя. И прописать в прошивке это сочетание как модификатор переключения на FN раскладку. Но я не стал этим заморачиваться, так что пин FN свитча вместе с катодом зеленого светодиода никуда не припаяны.

Кстати, на клаве две клавиши пробела, которые физически независимы. Поэтому можно на один из пробелов поставить любую другую клавишу, например BackSpace, Enter, Модификатор слоя или Клик мыши. Я оставил пробел.

Светодиод заряда я решил использовать в качестве индикатора CapsLock.

Общая схема:

Скрытый текст

Прошивка

Устанавливаем QMK. Как установится, нужно будет создать новую клавиатуру.

Запускаем QMK MSYS и вводим:

qmk new-keyboard

QMK запросит имя клавы (Keyboard Name?), я назвал ее mssculptansi. Далее будет запрос на ваш ник на Гитхабе и ваше имя, вписать можно что угодно). А вот следующий вопрос Default Layout – раскладка по умолчанию. Microsoft Sculpt Ergonomic это типичная TKL, т.е. tkl_ansi, вводим ее номер – 58. Дальше будет вопрос об использовании отладочной платы, отвечаем «нет», (n). И наконец, самый главный вопрос – выбор контроллера. У меня это RP2040, так что мой ответ – 21.

Заготовка прошивки готова, папка с ней будет среди файлов клавиатур QMK. По умолчанию это C:\Users\UserName\qmk_firmware\keyboards\. В папке C:\Users\UserName\qmk_firmware\keyboards\mssculptansi переходим в подпапку keymaps\default. Здесь есть файл keymap.c, изначально выглядит он примерно так:

Скрытый текст
#include QMK_KEYBOARD_H

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
    /*
     * ┌───┐   ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┐
     * │Esc│   │F1 │F2 │F3 │F4 │ │F5 │F6 │F7 │F8 │ │F9 │F10│F11│F12│ │PSc│Scr│Pse│
     * └───┘   └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┴───┘
     * ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───────┐ ┌───┬───┬───┐
     * │ ` │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 0 │ - │ = │ Backsp│ │Ins│Hom│PgU│
     * ├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤ ├───┼───┼───┤
     * │ Tab │ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │ [ │ ] │  \  │ │Del│End│PgD│
     * ├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤ └───┴───┴───┘
     * │ Caps │ A │ S │ D │ F │ G │ H │ J │ K │ L │ ; │ ' │  Enter │
     * ├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤     ┌───┐
     * │ Shift  │ Z │ X │ C │ V │ B │ N │ M │ , │ . │ / │    Shift │     │ ↑ │
     * ├────┬───┴┬──┴─┬─┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤ ┌───┼───┼───┐
     * │Ctrl│GUI │Alt │                        │ Alt│ GUI│Menu│Ctrl│ │ ← │ ↓ │ → │
     * └────┴────┴────┴────────────────────────┴────┴────┴────┴────┘ └───┴───┴───┘
     */
    [0] = LAYOUT_tkl_ansi(
        KC_ESC,           KC_F1,   KC_F2,   KC_F3,   KC_F4,   KC_F5,   KC_F6,   KC_F7,   KC_F8,   KC_F9,   KC_F10,  KC_F11,  KC_F12,     KC_PSCR, KC_SCRL, KC_PAUS,

        KC_GRV,  KC_1,    KC_2,    KC_3,    KC_4,    KC_5,    KC_6,    KC_7,    KC_8,    KC_9,    KC_0,    KC_MINS, KC_EQL,  KC_BSPC,    KC_INS,  KC_HOME, KC_PGUP,
        KC_TAB,  KC_Q,    KC_W,    KC_E,    KC_R,    KC_T,    KC_Y,    KC_U,    KC_I,    KC_O,    KC_P,    KC_LBRC, KC_RBRC, KC_BSLS,    KC_DEL,  KC_END,  KC_PGDN,
        KC_CAPS, KC_A,    KC_S,    KC_D,    KC_F,    KC_G,    KC_H,    KC_J,    KC_K,    KC_L,    KC_SCLN, KC_QUOT,          KC_ENT,
        KC_LSFT,          KC_Z,    KC_X,    KC_C,    KC_V,    KC_B,    KC_N,    KC_M,    KC_COMM, KC_DOT,  KC_SLSH,          KC_RSFT,             KC_UP,
        KC_LCTL, KC_LGUI, KC_LALT,                            KC_SPC,                             KC_RALT, KC_RGUI, KC_APP,  KC_RCTL,    KC_LEFT, KC_DOWN, KC_RGHT
    )
};

keymap.c содержит раскладку или несколько раскладок в виде массивов кодов клавиш - кейкодов, список их тут. В данном случае раскладка LAYOUT_tkl_ansi. Все что вида /* * * * */ — это комментарии, только для удобства пользователя, можно удалить. Комментарием "нарисована" раскладка.

Кейкоды в массиве разделены запятыми, а после кейкода последней в раскладке клавиши идет закрывающая скобка, никаких запятых! Количество пробелов и символов перевода строки между кодами на работу не влияет, это просто для удобства чтения человеком.

Я добавляю в раскладку [0] = LAYOUT_tkl_ansi кейкод KC_CALC, отвечающий за вызов калькулятора. А еще я заменяю кейкод клавиши контекстного меня KC_APP на кейкод Fn - MO(1). Это будет клавиша модификатора, активирующая Fn клавиши. И вот для этих Fn клавиш нужна еще одна раскладка - [1] = LAYOUT_fn. Цифра 1 в кейкоде MO(1) - это номер раскладки (или по-другому слоя), которую включает данный модификатор. Подробнее о слоях тут.

Я просто скопировал и вставил ниже стандартную раскладку [0] = LAYOUT_tkl_ansi и переименовал в [1] = LAYOUT_fn и заменил большинство кейкодов на [1] = KC_NO, чтобы эти клавиши ничего не делали. Только MO(1) остается как и была. А вот F клавиши должны содержать нужные кейкоды. Например Fn + F2 выключает комп, поэтому ее код KC_PWR, а Fn + F9 ставит на паузу воспроизведение видео, поэтому ее код KC_MPLY.

В итоге мой keymap.c получился такой:

Скрытый текст
#include QMK_KEYBOARD_H

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
    /*
     * Esc      F1  F2   F3     F4  F5 F6      F7 F8 F9 F10 F11 F12 PrtScr ScrLk Pause Calc  FnSw
	 * `         1   2    3      4   5  6      7   8 9     0   -   =         BackSpace Del   Home
	 * Tab        Q   W    E      R     T      Y  U I     O   P   [         ]        \ Del    End
	 * CapsLock    A   S    D      F    G      H   J K     L   ;   '             Enter Ins   PgUp
	 * lShift       Z   X    C      V   B      N    M ,     .   /               rShift Up    PgDn 
	 * lCtrl       Gui lAlt lSpace             rSpace rAlt App             rCtrl  Left Down Right
     */
    [0] = LAYOUT_tkl_ansi(
        KC_ESC,  KC_F1,   KC_F2,   KC_F3,  KC_F4,  KC_F5,   KC_F6,  KC_F7,   KC_F8,   KC_F9,   KC_F10,  KC_F11,  KC_F12,  KC_PSCR, KC_SCRL, KC_PAUS, KC_CALC,
        KC_GRV,  KC_1,    KC_2,    KC_3,   KC_4,   KC_5,    KC_6,   KC_7,    KC_8,    KC_9,    KC_0,    KC_MINS, KC_EQL,  KC_BSPC, KC_DEL,  KC_HOME,
        KC_TAB,  KC_Q,    KC_W,    KC_E,   KC_R,   KC_T,    KC_Y,   KC_U,    KC_I,    KC_O,    KC_P,    KC_LBRC, KC_RBRC, KC_BSLS,          KC_END,
        KC_CAPS, KC_A,    KC_S,    KC_D,   KC_F,   KC_G,    KC_H,   KC_J,    KC_K,    KC_L,    KC_SCLN, KC_QUOT, KC_ENT,  KC_INS,  KC_PGUP,
        KC_LSFT, KC_Z,    KC_X,    KC_C,   KC_V,   KC_B,    KC_N,   KC_M,    KC_COMM, KC_DOT,  KC_SLSH, KC_RSFT, KC_UP,   KC_PGDN,
        KC_LCTL, KC_LGUI, KC_LALT, KC_SPC, KC_SPC, KC_RALT, MO(1),  KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT
    ),
	
	[1] = LAYOUT_fn(
        KC_NO, KC_NO, KC_PWR, KC_NO,  KC_NO, KC_WSCH, KC_NO,  KC_NO, KC_CPNL, KC_MPLY, KC_MUTE, KC_VOLD, KC_VOLU, KC_NO, KC_NO, KC_NO, KC_APP,
        KC_NO, KC_NO, KC_NO,  KC_NO,  KC_NO, KC_NO,   KC_NO,  KC_NO, KC_NO,   KC_NO,   KC_NO,   KC_NO,   KC_NO,   KC_NO, KC_NO, KC_NO,
        KC_NO, KC_NO, KC_NO,  KC_NO,  KC_NO, KC_NO,   KC_NO,  KC_NO, KC_NO,   KC_NO,   KC_NO,   KC_NO,   KC_NO,   KC_NO, KC_NO,
        KC_NO, KC_NO, KC_NO,  KC_NO,  KC_NO, KC_NO,   KC_NO,  KC_NO, KC_NO,   KC_NO,   KC_NO,   KC_NO,   KC_NO,   KC_NO, KC_NO,
        KC_NO, KC_NO, KC_NO,  KC_NO,  KC_NO, KC_NO,   KC_NO,  KC_NO, KC_NO,   KC_NO,   KC_NO,   KC_NO,   KC_NO,   KC_NO,
        KC_NO, KC_NO, KC_NO,  KC_NO,  KC_NO, KC_NO,   MO(1),  KC_NO, KC_NO,   KC_NO,   KC_NO
    )
};

А теперь нужно заняться файлом keyboard.json в корневой папке клавиатуры. Изначально он примерно такой:

Скрытый текст
{
    "manufacturer": "2",
    "keyboard_name": "mssculptansi",
    "maintainer": "1",
    "bootloader": "rp2040",
    "diode_direction": "COL2ROW",
    "features": {
        "bootmagic": true,
        "extrakey": true,
        "mousekey": true,
        "nkro": true
    },
    "matrix_pins": {
        "cols": ["C2", "C2", "C2", "C2", "C2", "C2", "C2", "C2", "C2", "C2", "C2", "C2", "C2", "C2", "C2", "C2", "C2", "C2"],
        "rows": ["D1", "D1", "D1", "D1", "D1", "D1"]
    },
    "processor": "RP2040",
    "url": "",
    "usb": {
        "device_version": "1.0.0",
        "pid": "0x0000",
        "vid": "0xFEED"
    },
    "layouts": {
        "LAYOUT_tkl_ansi": {
            "layout": [
                {"matrix": [0, 0], "x": 0, "y": 0},
                {"matrix": [0, 2], "x": 2, "y": 0},
                {"matrix": [0, 3], "x": 3, "y": 0},
                {"matrix": [0, 4], "x": 4, "y": 0},
                {"matrix": [0, 5], "x": 5, "y": 0},
                {"matrix": [0, 6], "x": 6.5, "y": 0},
                {"matrix": [0, 7], "x": 7.5, "y": 0},
                {"matrix": [0, 8], "x": 8.5, "y": 0},
                {"matrix": [0, 9], "x": 9.5, "y": 0},
                {"matrix": [0, 11], "x": 11, "y": 0},
                {"matrix": [0, 12], "x": 12, "y": 0},
                {"matrix": [0, 13], "x": 13, "y": 0},
                {"matrix": [0, 14], "x": 14, "y": 0},
                {"matrix": [0, 15], "x": 15.25, "y": 0},
                {"matrix": [0, 16], "x": 16.25, "y": 0},
                {"matrix": [0, 17], "x": 17.25, "y": 0},
                {"matrix": [1, 0], "x": 0, "y": 1.25},
                {"matrix": [1, 1], "x": 1, "y": 1.25},
                {"matrix": [1, 2], "x": 2, "y": 1.25},
                {"matrix": [1, 3], "x": 3, "y": 1.25},
                {"matrix": [1, 4], "x": 4, "y": 1.25},
                {"matrix": [1, 5], "x": 5, "y": 1.25},
                {"matrix": [1, 6], "x": 6, "y": 1.25},
                {"matrix": [1, 7], "x": 7, "y": 1.25},
                {"matrix": [1, 8], "x": 8, "y": 1.25},
                {"matrix": [1, 9], "x": 9, "y": 1.25},
                {"matrix": [1, 10], "x": 10, "y": 1.25},
                {"matrix": [1, 11], "x": 11, "y": 1.25},
                {"matrix": [1, 12], "x": 12, "y": 1.25},
                {"matrix": [1, 13], "x": 13, "y": 1.25, "w": 2},
                {"matrix": [1, 15], "x": 15.25, "y": 1.25},
                {"matrix": [1, 16], "x": 16.25, "y": 1.25},
                {"matrix": [1, 17], "x": 17.25, "y": 1.25},
                {"matrix": [2, 0], "x": 0, "y": 2.25, "w": 1.5},
                {"matrix": [2, 1], "x": 1.5, "y": 2.25},
                {"matrix": [2, 2], "x": 2.5, "y": 2.25},
                {"matrix": [2, 3], "x": 3.5, "y": 2.25},
                {"matrix": [2, 4], "x": 4.5, "y": 2.25},
                {"matrix": [2, 5], "x": 5.5, "y": 2.25},
                {"matrix": [2, 6], "x": 6.5, "y": 2.25},
                {"matrix": [2, 7], "x": 7.5, "y": 2.25},
                {"matrix": [2, 8], "x": 8.5, "y": 2.25},
                {"matrix": [2, 9], "x": 9.5, "y": 2.25},
                {"matrix": [2, 10], "x": 10.5, "y": 2.25},
                {"matrix": [2, 11], "x": 11.5, "y": 2.25},
                {"matrix": [2, 12], "x": 12.5, "y": 2.25},
                {"matrix": [2, 13], "x": 13.5, "y": 2.25, "w": 1.5},
                {"matrix": [2, 15], "x": 15.25, "y": 2.25},
                {"matrix": [2, 16], "x": 16.25, "y": 2.25},
                {"matrix": [2, 17], "x": 17.25, "y": 2.25},
                {"matrix": [3, 0], "x": 0, "y": 3.25, "w": 1.75},
                {"matrix": [3, 1], "x": 1.75, "y": 3.25},
                {"matrix": [3, 2], "x": 2.75, "y": 3.25},
                {"matrix": [3, 3], "x": 3.75, "y": 3.25},
                {"matrix": [3, 4], "x": 4.75, "y": 3.25},
                {"matrix": [3, 5], "x": 5.75, "y": 3.25},
                {"matrix": [3, 6], "x": 6.75, "y": 3.25},
                {"matrix": [3, 7], "x": 7.75, "y": 3.25},
                {"matrix": [3, 8], "x": 8.75, "y": 3.25},
                {"matrix": [3, 9], "x": 9.75, "y": 3.25},
                {"matrix": [3, 10], "x": 10.75, "y": 3.25},
                {"matrix": [3, 11], "x": 11.75, "y": 3.25},
                {"matrix": [3, 12], "x": 12.75, "y": 3.25, "w": 2.25},
                {"matrix": [4, 0], "x": 0, "y": 4.25, "w": 2.25},
                {"matrix": [4, 2], "x": 2.25, "y": 4.25},
                {"matrix": [4, 3], "x": 3.25, "y": 4.25},
                {"matrix": [4, 4], "x": 4.25, "y": 4.25},
                {"matrix": [4, 5], "x": 5.25, "y": 4.25},
                {"matrix": [4, 6], "x": 6.25, "y": 4.25},
                {"matrix": [4, 7], "x": 7.25, "y": 4.25},
                {"matrix": [4, 8], "x": 8.25, "y": 4.25},
                {"matrix": [4, 9], "x": 9.25, "y": 4.25},
                {"matrix": [4, 10], "x": 10.25, "y": 4.25},
                {"matrix": [4, 11], "x": 11.25, "y": 4.25},
                {"matrix": [4, 12], "x": 12.25, "y": 4.25, "w": 2.75},
                {"matrix": [4, 16], "x": 16.25, "y": 4.25},
                {"matrix": [5, 0], "x": 0, "y": 5.25, "w": 1.25},
                {"matrix": [5, 1], "x": 1.25, "y": 5.25, "w": 1.25},
                {"matrix": [5, 2], "x": 2.5, "y": 5.25, "w": 1.25},
                {"matrix": [5, 3], "x": 3.75, "y": 5.25, "w": 6.25},
                {"matrix": [5, 10], "x": 10, "y": 5.25, "w": 1.25},
                {"matrix": [5, 11], "x": 11.25, "y": 5.25, "w": 1.25},
                {"matrix": [5, 12], "x": 12.5, "y": 5.25, "w": 1.25},
                {"matrix": [5, 13], "x": 13.75, "y": 5.25, "w": 1.25},
                {"matrix": [5, 15], "x": 15.25, "y": 5.25},
                {"matrix": [5, 16], "x": 16.25, "y": 5.25},
                {"matrix": [5, 17], "x": 17.25, "y": 5.25}
            ]
        }
    }
}

Во-первых, секция "matrix_pins": "cols" – это название GPIO, которые соединены с колонками, а "rows" – GPIO соединенных с рядами. Их нужно ввести в том же порядке, который получился при прозвонке и пайке. См. схему выше. У меня эта часть выглядит так:

"matrix_pins": {
   "cols": ["GP13", "GP12", "GP11", "GP10", "GP22", "GP26", "GP27", "GP28", "GP29", "GP9", "GP8", "GP7", "GP6", "GP5", "GP4", "GP3", "GP2", "GP1"],
   "rows": ["GP16", "GP17", "GP18", "GP19", "GP20", "GP21", "GP15", "GP14"]
},

Во-вторых, секция "layouts". Здесь указано по порядку, как в раскладке сочетание каких "rows" и "cols" отвечает за отправку N-го кейкода из раскладки в файле keymap.c. Понятнее на схемке:

Связь файлов keymap.c и keyboard.json в базовой раскладке LAYOUT_tkl_ansi
Связь файлов keymap.c и keyboard.json в базовой раскладке LAYOUT_tkl_ansi

Нулевая строка - сочетание GP20 (4й ряд) и GP5 (13я колонка) отвечает за клавишу Esc, соответствует нулевому элементу массива LAYOUT_tkl_ansi в keymap.c. Первая строка - сочетание GP14 (7й ряд) и GP6 (12я колонка) отвечает за клавишу F1, соответствует первому элементу массива LAYOUT_tkl_ansi в keymap.c. И так далее.

Строение строки на примере нулевой:

{"label": "Esc", "matrix": [4, 13], "x": 13, "y": 4},

  1. "label": "Esc" - это метка, не обязательна, но очень удобна.

  2. "matrix": [rows, cols] – сочетание ряда и колонки, за который отвечает запись под этим номером. Это самый главный параметр, обязателен!

  3. "x": и "y": - координаты клавиши для QMK конфигуратора раскладки. Я им не пользовался, можно тупо ввести сюда значения 1 и 1 у всех записей. Но просто для совместимости я ввел значения по соответствующим координатам. Какие-нибудь значения здесь должны быть введены, иначе прошивка не скомпилируется.

Подробнее здесь.

Поскольку у нас две раскладки, то в keyboard.json нужно добавить вторую раскладку. LAYOUT_fn. Она по тому же принципу будет связана с раскладкой в keymap.c

В итоге мой keyboard.json получился таким:

Скрытый текст
{
    "manufacturer": "2",
    "keyboard_name": "mssculptansi",
    "maintainer": "1",
    "bootloader": "rp2040",
    "diode_direction": "COL2ROW",
    "features": {
        "bootmagic": true,
        "extrakey": true,
        "mousekey": true,
        "nkro": true
    },
    "matrix_pins": {
        "cols": ["GP13", "GP12", "GP11", "GP10", "GP22", "GP26", "GP27", "GP28", "GP29", "GP9", "GP8", "GP7", "GP6", "GP5", "GP4", "GP3", "GP2", "GP1"],
        "rows": ["GP16", "GP17", "GP18", "GP19", "GP20", "GP21", "GP15", "GP14"]
    },
    "processor": "RP2040",
    "url": "",
    "usb": {
        "device_version": "1.0.0",
        "pid": "0x0000",
        "vid": "0xFEED"
    },
    "layouts": {
        "LAYOUT_tkl_ansi": {
            "layout": [
			
				{"label": "Esc", "matrix": [4, 13], "x": 13, "y": 4},
				
				{"label": "F1", "matrix": [7, 12], "x": 12, "y": 7},
				{"label": "F2", "matrix": [7, 11], "x": 11, "y": 7},
				{"label": "F3", "matrix": [1, 11], "x": 11, "y": 1},
				{"label": "F4", "matrix": [4, 11], "x": 11, "y": 4},
				{"label": "F5", "matrix": [1, 10], "x": 10, "y": 1},
				{"label": "F6", "matrix": [1, 15], "x": 15, "y": 1},
				{"label": "F7", "matrix": [7, 9], "x": 9, "y": 7},
				{"label": "F8", "matrix": [7, 7], "x": 7, "y": 7},
				{"label": "F9", "matrix": [7, 5], "x": 5, "y": 7},
				{"label": "F10", "matrix": [7, 8], "x": 8, "y": 7},
				{"label": "F11", "matrix": [7, 3], "x": 3, "y": 7},
				{"label": "F12", "matrix": [1, 3], "x": 3, "y": 1},
				{"label": "PrScr", "matrix": [7, 1], "x": 1, "y": 7},
				{"label": "ScrLock", "matrix": [3, 1], "x": 1, "y": 3},	
                {"label": "Pause", "matrix": [0, 1], "x": 1, "y": 0},
				{"label": "calc", "matrix": [2, 3], "x": 3, "y": 2},
				
				{"label": "grv `", "matrix": [7, 13], "x": 13, "y": 7},
				{"label": "1", "matrix": [0, 13], "x": 13, "y": 0},
				{"label": "2", "matrix": [0, 12], "x": 12, "y": 0},
				{"label": "3", "matrix": [2, 13], "x": 13, "y": 2},
				{"label": "4", "matrix": [1, 13], "x": 13, "y": 1},
				{"label": "5", "matrix": [7, 10], "x": 10, "y": 7},
				{"label": "6", "matrix": [7, 15], "x": 15, "y": 7},
				{"label": "7", "matrix": [0, 9], "x": 9, "y": 0},
				{"label": "8", "matrix": [0, 7], "x": 7, "y": 0},
				{"label": "9", "matrix": [0, 5], "x": 5, "y": 0},
				{"label": "0", "matrix": [0, 4], "x": 4, "y": 0},
				{"label": "Mins -", "matrix": [1, 5], "x": 5, "y": 1},
				{"label": "eql =", "matrix": [7, 4], "x": 4, "y": 7},
				{"label": "BackSpace", "matrix": [0, 8], "x": 8, "y": 0},
				{"label": "Del", "matrix": [0, 3], "x": 3, "y": 0},
				{"label": "Home", "matrix": [2, 1], "x": 1, "y": 2},
				
				{"label": "Tab", "matrix": [0, 10], "x": 10, "y": 0},
				{"label": "Q", "matrix": [0, 11], "x": 11, "y": 0},
				{"label": "W", "matrix": [1, 12], "x": 12, "y": 1},
				{"label": "E", "matrix": [2, 11], "x": 11, "y": 2},
				{"label": "R", "matrix": [2, 10], "x": 10, "y": 2},
				{"label": "T", "matrix": [2, 15], "x": 15, "y": 2},
				{"label": "Y", "matrix": [1, 9], "x": 9, "y": 1},
				{"label": "U", "matrix": [2, 9], "x": 9, "y": 2},
				{"label": "I", "matrix": [2, 7], "x": 7, "y": 2},
				{"label": "O", "matrix": [2, 5], "x": 5, "y": 2},
				{"label": "P", "matrix": [2, 4], "x": 4, "y": 2},
				{"label": "lBrc [", "matrix": [1, 4], "x": 4, "y": 1},
				{"label": "rBrc ]", "matrix": [1, 7], "x": 7, "y": 1},
				{"label": "backSlash", "matrix": [3, 8], "x": 8, "y": 3},
				{"label": "End", "matrix": [5, 1], "x": 1, "y": 5},
				
				{"label": "capsLock", "matrix": [2, 12], "x": 12, "y": 2},
				{"label": "A", "matrix": [3, 13], "x": 13, "y": 3},
				{"label": "S", "matrix": [4, 12], "x": 12, "y": 4},
				{"label": "D", "matrix": [3, 11], "x": 11, "y": 3},
				{"label": "F", "matrix": [3, 10], "x": 10, "y": 3},
				{"label": "G", "matrix": [4, 10], "x": 10, "y": 4},
				{"label": "H", "matrix": [4, 9], "x": 9, "y": 4},
				{"label": "J", "matrix": [3, 9], "x": 9, "y": 3},
				{"label": "K", "matrix": [3, 7], "x": 7, "y": 3},
				{"label": "L", "matrix": [3, 5], "x": 5, "y": 3},
				{"label": "scln ;", "matrix": [3, 4], "x": 4, "y": 3},
				{"label": "Quot '", "matrix": [4, 5], "x": 5, "y": 4},
				{"label": "Enter", "matrix": [3, 3], "x": 3, "y": 3},
				{"label": "Ins", "matrix": [1, 8], "x": 8, "y": 1},
				{"label": "PgUP", "matrix": [1, 1], "x": 1, "y": 1},
				
				{"label": "leftShift", "matrix": [5, 14], "x": 14, "y": 5},
				{"label": "Z", "matrix": [5, 13], "x": 13, "y": 5},
				{"label": "X", "matrix": [5, 12], "x": 12, "y": 5},
				{"label": "C", "matrix": [5, 11], "x": 11, "y": 5},
				{"label": "V", "matrix": [5, 10], "x": 10, "y": 5},
				{"label": "B", "matrix": [6, 10], "x": 10, "y": 6},
				{"label": "N", "matrix": [6, 9], "x": 9, "y": 6},
				{"label": "M", "matrix": [5, 9], "x": 9, "y": 5},
				{"label": "Comm ,", "matrix": [5, 7], "x": 7, "y": 5},
				{"label": "Dot .", "matrix": [5, 5], "x": 5, "y": 5},
				{"label": "slsh /", "matrix": [4, 4], "x": 4, "y": 4},
				{"label": "rightShift", "matrix": [5, 2], "x": 2, "y": 5},
				{"label": "UP", "matrix": [6, 3], "x": 3, "y": 6},
				{"label": "PgDown", "matrix": [5, 3], "x": 3, "y": 5},
				
				{"label": "leftCtrl", "matrix": [6, 17], "x": 17, "y": 6},
				{"label": "lGui (Win)", "matrix": [3, 15], "x": 15, "y": 3},
				{"label": "leftAlt", "matrix": [4, 16], "x": 16, "y": 4},
				{"label": "leftSpace", "matrix": [6, 11], "x": 11, "y": 6},
				{"label": "rightSpace", "matrix": [6, 8], "x": 8, "y": 6},
				{"label": "rightAlt", "matrix": [4, 6], "x": 6, "y": 4},
				{"label": "App (Cont Menu)", "matrix": [4, 3], "x": 3, "y": 4},
				{"label": "rightCtrl", "matrix": [6, 0], "x": 0, "y": 6},
				{"label": "Left", "matrix": [4, 8], "x": 8, "y": 4},
				{"label": "Down", "matrix": [6, 4], "x": 4, "y": 6},
				{"label": "Right", "matrix": [6, 1], "x": 1, "y": 6}
				
            ]
        },
		
		"LAYOUT_fn": {
            "layout": [
			
				{"label": "---", "matrix": [4, 13], "x": 13, "y": 4},
				
				{"label": "---", "matrix": [7, 12], "x": 12, "y": 7},
				{"label": "PowerOFF", "matrix": [7, 11], "x": 11, "y": 7},
				{"label": "---", "matrix": [1, 11], "x": 11, "y": 1},
				{"label": "---", "matrix": [4, 11], "x": 11, "y": 4},
				{"label": "Find", "matrix": [1, 10], "x": 10, "y": 1},
				{"label": "---", "matrix": [1, 15], "x": 15, "y": 1},
				{"label": "---", "matrix": [7, 9], "x": 9, "y": 7},
				{"label": "ControlPanel", "matrix": [7, 7], "x": 7, "y": 7},
				{"label": "PausePlay", "matrix": [7, 5], "x": 5, "y": 7},
				{"label": "Sound OffOn", "matrix": [7, 8], "x": 8, "y": 7},
				{"label": "Sound -", "matrix": [7, 3], "x": 3, "y": 7},
				{"label": "Sound +", "matrix": [1, 3], "x": 3, "y": 1},
				{"label": "---", "matrix": [7, 1], "x": 1, "y": 7},
				{"label": "---", "matrix": [3, 1], "x": 1, "y": 3},	
                {"label": "---", "matrix": [0, 1], "x": 1, "y": 0},
				{"label": "ContexMenu", "matrix": [2, 3], "x": 3, "y": 2},
				
				{"label": "---", "matrix": [7, 13], "x": 13, "y": 7},
				{"label": "---", "matrix": [0, 13], "x": 13, "y": 0},
				{"label": "---", "matrix": [0, 12], "x": 12, "y": 0},
				{"label": "---", "matrix": [2, 13], "x": 13, "y": 2},
				{"label": "---", "matrix": [1, 13], "x": 13, "y": 1},
				{"label": "---", "matrix": [7, 10], "x": 10, "y": 7},
				{"label": "---", "matrix": [7, 15], "x": 15, "y": 7},
				{"label": "---", "matrix": [0, 9], "x": 9, "y": 0},
				{"label": "---", "matrix": [0, 7], "x": 7, "y": 0},
				{"label": "---", "matrix": [0, 5], "x": 5, "y": 0},
				{"label": "---", "matrix": [0, 4], "x": 4, "y": 0},
				{"label": "---", "matrix": [1, 5], "x": 5, "y": 1},
				{"label": "---", "matrix": [7, 4], "x": 4, "y": 7},
				{"label": "---", "matrix": [0, 8], "x": 8, "y": 0},
				{"label": "---", "matrix": [0, 3], "x": 3, "y": 0},
				{"label": "---", "matrix": [2, 1], "x": 1, "y": 2},
				
				{"label": "---", "matrix": [0, 10], "x": 10, "y": 0},
				{"label": "---", "matrix": [0, 11], "x": 11, "y": 0},
				{"label": "---", "matrix": [1, 12], "x": 12, "y": 1},
				{"label": "---", "matrix": [2, 11], "x": 11, "y": 2},
				{"label": "---", "matrix": [2, 10], "x": 10, "y": 2},
				{"label": "---", "matrix": [2, 15], "x": 15, "y": 2},
				{"label": "---", "matrix": [1, 9], "x": 9, "y": 1},
				{"label": "---", "matrix": [2, 9], "x": 9, "y": 2},
				{"label": "---", "matrix": [2, 7], "x": 7, "y": 2},
				{"label": "---", "matrix": [2, 5], "x": 5, "y": 2},
				{"label": "---", "matrix": [2, 4], "x": 4, "y": 2},
				{"label": "---", "matrix": [1, 4], "x": 4, "y": 1},
				{"label": "---", "matrix": [1, 7], "x": 7, "y": 1},
				{"label": "---", "matrix": [3, 8], "x": 8, "y": 3},
				{"label": "---", "matrix": [5, 1], "x": 1, "y": 5},
				
				{"label": "---", "matrix": [2, 12], "x": 12, "y": 2},
				{"label": "---", "matrix": [3, 13], "x": 13, "y": 3},
				{"label": "---", "matrix": [4, 12], "x": 12, "y": 4},
				{"label": "---", "matrix": [3, 11], "x": 11, "y": 3},
				{"label": "---", "matrix": [3, 10], "x": 10, "y": 3},
				{"label": "---", "matrix": [4, 10], "x": 10, "y": 4},
				{"label": "---", "matrix": [4, 9], "x": 9, "y": 4},
				{"label": "---", "matrix": [3, 9], "x": 9, "y": 3},
				{"label": "---", "matrix": [3, 7], "x": 7, "y": 3},
				{"label": "---", "matrix": [3, 5], "x": 5, "y": 3},
				{"label": "---", "matrix": [3, 4], "x": 4, "y": 3},
				{"label": "---", "matrix": [4, 5], "x": 5, "y": 4},
				{"label": "---", "matrix": [3, 3], "x": 3, "y": 3},
				{"label": "---", "matrix": [1, 8], "x": 8, "y": 1},
				{"label": "---", "matrix": [1, 1], "x": 1, "y": 1},
				
				{"label": "---", "matrix": [5, 14], "x": 14, "y": 5},
				{"label": "---", "matrix": [5, 13], "x": 13, "y": 5},
				{"label": "---", "matrix": [5, 12], "x": 12, "y": 5},
				{"label": "---", "matrix": [5, 11], "x": 11, "y": 5},
				{"label": "---", "matrix": [5, 10], "x": 10, "y": 5},
				{"label": "---", "matrix": [6, 10], "x": 10, "y": 6},
				{"label": "---", "matrix": [6, 9], "x": 9, "y": 6},
				{"label": "---", "matrix": [5, 9], "x": 9, "y": 5},
				{"label": "---", "matrix": [5, 7], "x": 7, "y": 5},
				{"label": "---", "matrix": [5, 5], "x": 5, "y": 5},
				{"label": "---", "matrix": [4, 4], "x": 4, "y": 4},
				{"label": "---", "matrix": [5, 2], "x": 2, "y": 5},
				{"label": "---", "matrix": [6, 3], "x": 3, "y": 6},
				{"label": "---", "matrix": [5, 3], "x": 3, "y": 5},
				
				{"label": "---", "matrix": [6, 17], "x": 17, "y": 6},
				{"label": "---", "matrix": [3, 15], "x": 15, "y": 3},
				{"label": "---", "matrix": [4, 16], "x": 16, "y": 4},
				{"label": "---", "matrix": [6, 11], "x": 11, "y": 6},
				{"label": "---", "matrix": [6, 8], "x": 8, "y": 6},
				{"label": "---", "matrix": [4, 6], "x": 6, "y": 4},
				{"label": "---", "matrix": [4, 3], "x": 3, "y": 4},
				{"label": "---", "matrix": [6, 0], "x": 0, "y": 6},
				{"label": "---", "matrix": [4, 8], "x": 8, "y": 4},
				{"label": "---", "matrix": [6, 4], "x": 4, "y": 6},
				{"label": "---", "matrix": [6, 1], "x": 1, "y": 6}
				
            ]
        }
    }
}

Как видно, Fn раскладка у меня такая:

  1. Fn + F2 - выключение

  2. Fn + F5 - поиск

  3. Fn + F8 - Панель управления

  4. Fn + F9 - Старт/Пауза воспроизведения

  5. Fn + F10, Fn + F11, Fn + F12 - выключить, убавить, прибавить звук соответственно.

И последнее, нужен еще светодиод для CapsLock. Для этого нужно в корне папки клавиатуры создать текстовый файл config.h. И вставить туда всего одну строку:

#define LED_CAPS_LOCK_PIN GP0

Где GP0 – это GPIO соединенный с анодом красного светодиода. В моем случае его номер – 0.

Подробная документация же по файлам прошивки на сайте.

Компиляция и загрузка прошивки

Как только все файлы отредактированы, настало наконец время скомпилировать прошивку. Выполняем команду в QMK MSYS:

qmk compile -kb mssculptansi -km default

и если не будет ошибок, то файл mssculptansi_default.uf2 будет лежать в C:\Users\UserName\qmk_firmware. Расширение .uf2 имеют прошивки для RP2040, если контроллер иной, то и расширение будет иным.

Чтобы залить прошивку в RP2040, надо лишь запустить контроллер в загрузочном режиме и скинуть туда файл как на флешку. Для этого удерживая кнопку Boot подключаем RP2040 к USB порту. Либо, если контроллер уже подключен, зажав кнопку Boot нажимаем кнопку Reset.

Все, клава работает. По крайней мере на эту статью её хватило :). Таким же образом можно подключать клавы от ноутбуков к ПК. При этом клавиатурам можно придать дополнительные возможности, которых у QMK много: макросы, управление курсором и мн. др.