Хабр, привет. Это мой первый пост — сильно камнями не кидаться.
Цель: собрать максимально бюджетный макропад и изучить технологии, применяемые для его сборки.
Требования: минимальный бюджет, количество клавиш — минимум 10, механический тип клавиатуры, удобная эргономика, полное понимание внутренних процессов, возможность смены прошивки и назначения для клавиш своих функций. Ну и хочется это сделать быстро, не за 3 месяца.
Макропад — это компактная программируемая мини‑клавиатура с небольшим количеством клавиш, предназначенная для автоматизации рутинных задач путем назначения на каждую кнопку различных команд, горячих клавиш или макросов (последовательностей действий).

Изучив рынок маркетплейсов, китайских магазинов и б\у техники, я обнаружил для себя несколько проблем:
Цена за такие огрызки клавиатур несоизмеримо большая, начиная от 1000₽ для приемлемых вариантов.
Бюджетные варианты имеют малое количество клавиш и\или не имеют возможности прошивки и настройки нажатия клавиш.
Неизвестно, имеет ли устройство возможность настройки нажатия клавиш и изменения прошивки. И есть вероятность в случае потери\удаления прошивки превратить устройство в камушек, т.к. не факт, что получится найти нужную прошивку для него.
Не все устройства имеют удобную эргономику.
Покопавшись в интернетах, посмотрев ролики-туториалы и посоветовавшись с искусственным интеллектом, я понял, что собрать свое устройство не так уж и сложно.
Я написал список материалов и действий:
Корпус.
Плата-контроллер.
Свитчи для механической клавиатуры.
Кейкапы.
Спаять все это дело.
Запрограммировать.
Собрать в корпус.
Я решил не использовать плату для свитчей из-за ее цены (да и найти ее или сделать под случайно найденный корпус довольно проблематично), а спаять их напрямую с помощью медной проволоки.
Так же можно было вытравить плату самому, что вышло бы довольно бюджетно и свитчи сидели бы крепко. А еще можно было бы использовать сокеты для свитчей на плате, это электронный компонент припаиваемый на плату, который позволяет просто вставлять свитч в гнездо без пайки, и можно было бы их быстро и просто поменять. Ну и имея плату можно было впаять RGB светодиод под каждый свитч, что добавило бы яркую и полностью управляемую подсветку.

Комплектующие
Корпус будет печататься на 3D-принтере, в интернете можно найти бесплатные модели или сделать самому. Я нашел первый попавшийся мне удобный корпус на 10 клавиш. Печать небольшого корпуса мне обошлась в ~150₽, заказал услугу у частника по объявлению на маркетплейсе. Печать вышла почти по себестоимости, но некоторые частники и конторы могут сильно завышать цену для своей выгоды.


Сюда можно установить 10 клавиш, и здесь довольно неплохая эргономика под левую руку, минимум движений кистью.
Для платы контроллера я выбрал китайский вариант Rapsberry Pi Pico Black c Type-C разъемом и 4 МБ памяти на борту. И еще она имеет встроенный RGB светодиод, что позволит добавить подсветку для устройства. 150₽, заказал с китайского популярного маркетплейса.
Есть ряд контроллеров которые стоят еще дешевле и имеют меньший размер, например ESP32 S2 Mini. А так же есть контроллеры с встроенными модулями WiFi\BT на плате, и с добавлением аккумулятора и контроллера зарядки можно сделать устройство беспроводным.
Свитчи я брал самые бюджетные MX, мне было все равно на их тип, качество, шум и цвет.
Вышло по 10₽ за шт * 10 = 100₽. Заказал с китайского популярного маркетплейса.
Кейкапы лично я для себя не брал, использовал со своей старой клавиатуры, но цена самых дешевых, также ~10₽ за шт * 10 = 100₽. Заказать можно с китайского популярного маркетплейса.
Кабель Type-C с передачей данных может тоже выйти в районе 100₽.
Суммарно 600₽.
Также нам понадобится паяльник с расходниками, медная проволока, провода и прямые руки.
Пайка
У меня 10 кнопок, на каждой по два контакта, если паять их напрямую линейно к плате, то мы займем 20 пинов. Данная плата это позволяет, но я решил использовать матрицу кнопок. Это когда у тебя есть матрица из проводов, состоящая из рядов и колонок, каждое пересечение ряда и колонки — это одна кнопка, ее замыкание вызывает протекание тока с определенного ряда и на определенную колонну. С помощью номеров этих рядов и колонн мы сможем понимать, какая кнопка нажата. Например, у меня 10 кнопок, матрица 3х3 будет маленькой, берем 3х4 — это 3 ряда и 4 колонны. В сумме получаем 7 контактов на плате вместо 20. Неплохо ужались.
По‑хорошему нужна диодная защита, это когда при зажатии нескольких кнопок одновременно может возникнуть ситуация, при которой ток течёт через неожиданные пути. Это происходит из‑за того, что без диодов все линии (ряды и колонки) электрически соединены через зажатые кнопки, что может «зажечь» дополнительные кнопки, которых ты не нажимал. Но для себя я этого не делал.
После пайки матрицы припаиваем провода от каждого ряда и колонки на пины платы.
Я паял колонки на пины 1, 2, 4, 6. Ряды — на 3, 5, 7.
Для себя я также использовал два светодиода для подсветки на пинах 8 и 9.
Если кто‑то столкнется с такой платой, то знайте: изначально встроенный светодиод на ней не подключен к плате. Светодиод имеет пин 23, который изначально работает как обычный пин. Чтобы подключить светодиод к нему, надо спаять перемычку на плате, где написано «RGB» или «R68». Я долго искал эту информацию.



Программирование
Я пробовал разные IDE, прошивки, языки программирования и библиотеки. Сначала я пытался использовать PlatformIO, но он не видел мою плату после прошивки. Пытался использовать С++ для программирования, но так и не разобрался, как ставить библиотеки. После недолгого сражения с софтом мы с ИИ смогли найти решение.
Использовать программу Thonny, она довольно простая, поддерживает установку библиотек и может автоматически установить прошивку.
Использование языка программирования Python. Проще, чем C++, и имеет много библиотек.
Использовать прошивку от CircuitPython, у меня таким образом можно получить доступ к внутренней памяти и файлам, просто подключив устройство к ПК. В отличие от MycroPython, где я не могу получить обычный доступ к внутрянке.
Я использовал библиотеку usb_hid от CircuitPython, которая позволяет эмулировать нажатия клавиш на клавиатуре.
Я набросал для себя часто используемые клавиши и комбинации, и понял, что 10 кнопками не обойдусь. Я решил использовать 2 слоя на макропаде. Первый слой — это обычные нажатия клавиш, второй слой — это нажатия на клавиши с уже нажатой клавишей слоя. Это позволяет увеличить кол-во возможных комбинаций с 10 до 18.
Вот такая раскладка вышла:

Первая строка в каждой клавише — это первый слой, вторая строка — это второй слой.
Кнопка 9 — переключение слоя, кнопка 10 на втором слое — переключение подсветки.
Мой код. Сгенерированный ИИ.
import time
import board
import digitalio
import pwmio
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
import config
# Пины для CircuitPython
ROWS = [digitalio.DigitalInOut(getattr(board, f"GP{i}")) for i in [3, 5, 7]]
for row in ROWS:
row.switch_to_output()
COLS = [digitalio.DigitalInOut(getattr(board, f"GP{i}")) for i in [1, 2, 4, 6]]
for col in COLS:
col.switch_to_input(pull=digitalio.Pull.UP)
# Инициализация клавиатуры
kbd = Keyboard(usb_hid.devices)
# Состояние слоя
current_layer = config.layer1 # По умолчанию первый слой
layer_switch_button = 9 # Кнопка для переключения слоя
# Список для хранения текущего состояния нажатий и времени
current_presses = {} # {кнопка: время_зажатия}
last_presses = set()
# Параметры автоповтора
INITIAL_DELAY = 0.5 # Начальная задержка перед повтором (0.5 секунды)
REPEAT_DELAY = 0.03 # Интервал повтора (30 мс)
# Управление подсветкой
led1 = pwmio.PWMOut(board.GP8, frequency=1000) # Первый цвет (минус на GP8)
led2 = pwmio.PWMOut(board.GP9, frequency=1000) # Второй цвет (минус на GP9)
led1.duty_cycle = 0 # Начальное значение (выкл)
led2.duty_cycle = 0 # Начальное значение (выкл)
# Режимы подсветки (0-4)
LIGHT_MODES = [
lambda: (65535, 65535), # 0: Всё горят (GP8, GP9)
lambda: (65535, 0), # 1: Первый горит (GP8)
lambda: (0, 65535), # 2: Второй горит (GP9)
lambda: (0, 0), # 3: Всё выкл (GP8, GP9)
lambda: (0, 0) # 4: Перелив (обновляется асинхронно)
]
current_mode = 0
# Ограничение значений duty_cycle
def clamp_duty(duty):
return min(max(int(duty), 0), 65535)
# Сканирование матрицы
def scan_matrix():
global current_presses, last_presses, current_layer, current_mode
current_time = time.monotonic()
# Сканирование текущего состояния
new_presses = set()
for i, row in enumerate(ROWS):
row.value = False
for j, col in enumerate(COLS):
if not col.value:
btn_num = i * len(COLS) + j + 1
if i == 2 and j > 1:
if j == 2: btn_num = 9
elif j == 3: btn_num = 10
new_presses.add(btn_num)
row.value = True
# Обновление состояния нажатий
for btn in new_presses:
if btn not in current_presses:
current_presses[btn] = current_time
if btn in current_layer:
kbd.send(*current_layer[btn])
print(f"Отправлено: {btn} -> {current_layer[btn]}")
# Автоповтор
for btn in list(current_presses.keys()):
if btn in new_presses:
press_time = current_time - current_presses[btn]
if press_time > INITIAL_DELAY and (press_time - INITIAL_DELAY) % REPEAT_DELAY < 0.005:
if btn in current_layer:
kbd.send(*current_layer[btn])
print(f"Повтор: {btn} -> {current_layer[btn]}")
else:
del current_presses[btn]
# Переключение слоя при зажатии 9
if layer_switch_button in new_presses and layer_switch_button not in last_presses:
current_layer = config.layer2
print("Переключено на второй слой")
elif layer_switch_button not in new_presses and layer_switch_button in last_presses:
current_layer = config.layer1
print("Вернулись на первый слой")
# Смена режима подсветки при нажатии 10 только на втором слое
if 10 in new_presses and 10 in current_layer and current_layer == config.layer2 and 10 not in last_presses:
current_mode = (current_mode + 1) % len(LIGHT_MODES)
duty1, duty2 = LIGHT_MODES[current_mode]()
led1.duty_cycle = clamp_duty(duty1)
led2.duty_cycle = clamp_duty(duty2)
print(f"Режим подсветки: {current_mode} (duty1={duty1}, duty2={duty2})")
last_presses = new_presses.copy()
# Обновление перелива для режима 4
def update_fade():
if current_mode == 4:
value = int(65535 * abs(0.5 - (time.monotonic() % 2) / 2))
led1.duty_cycle = clamp_duty(value)
led2.duty_cycle = clamp_duty(65535 - value)
# Основной цикл с обработкой прерываний
try:
while True:
scan_matrix()
update_fade() # Асинхронное обновление перелива
time.sleep(0.01) # Уменьшаем частоту обновления для плавности
except KeyboardInterrupt:
print("Программа прервана пользователем")
И содержание файла с конфигом config.py
from adafruit_hid.keycode import Keycode
# Первый слой
layer1 = {
1: [Keycode.ESCAPE], # Escape
2: [Keycode.TAB], # Tab
3: [Keycode.UP_ARROW], # Стрелка вверх
4: [Keycode.BACKSPACE], # Backspace
5: [Keycode.DELETE], # Delete
6: [Keycode.LEFT_ARROW], # Стрелка влево
7: [Keycode.DOWN_ARROW], # Стрелка вниз
8: [Keycode.RIGHT_ARROW], # Стрелка вправо
10: [Keycode.ENTER] # Enter
}
# Второй слой (активируется при зажатии 9)
layer2 = {
1: [Keycode.CONTROL, Keycode.A], # Ctrl+A
2: [Keycode.CONTROL, Keycode.X], # Ctrl+X
3: [Keycode.CONTROL, Keycode.C], # Ctrl+C
4: [Keycode.CONTROL, Keycode.V], # Ctrl+V
5: [Keycode.CONTROL, Keycode.ALT, Keycode.DELETE], # Ctrl+Alt+Del
6: [Keycode.CONTROL, Keycode.S], # Ctrl+S
7: [Keycode.CONTROL, Keycode.Z], # Ctrl+Z
8: [Keycode.CONTROL, Keycode.Y], # Ctrl+Y
10: [] # Смена режима подсветки
}
Ну и автоповтор добавил, чтоб при зажатии кнопочки она повторяла свои действия в быстром режиме.
Подключаем, прошиваем, заливаем код, и после танцев с бубном оно заработало!
Собираем все в корпус, ставим красивые кейкапы и готово.


На пайку и программирование ушло 2 вечера.
Итог: я смог быстро собрать бюджетный макропад с 10 кнопками, удобной эргономикой, легко изменяемым конфигом. И оно работает при подключении к любому ПК.
Следующая цель - самодельная эргономичная сплит клавиатура.