Самодельный USB индикатор светодиодов клавиатуры

Итак, как я писал, у меня есть внешний индикатор светодиодов клавиатуры, что бы боковым зрением видеть, когда включена русская раскладка клавиатуры или когда рабочий стол заблокирован, который сделан на базе модуля выдранного из клавиатуры.

Модуль это конечно хорошо, но мы же хотим полный DIY, верно? Как и полный контроль над процессом отображения нужной нам информации. Например – отображать статус USB, сделать плавное включение диодов и прочее.

Разобрался и сделал самодельное устройство с аппаратным USB на базе копеечной STM32 отладочной платы, которая представляется полноценной USB HID клавиатурой и не требует драйверов для работы.

Код прошивки в итоге получился простой, но пришлось знатно помудиться – документация весьма путанная, так же явно маловато примеров. Это моё первое знакомство с STM32 (с которым очень давно хочу познакомиться), и надо сказать я удивился. Думал будет какой-то SDK с документацией и примерами, но нет – в 2023 году разработчики микроконтроллеров собирают прошивку из кубиков в генераторе кода. Но это конечно не наш метод, сделал в итоге простой проект на одном файле, на связке Zephyr RTOS, которое собирается в PlatformIO под VSCode.

Код проекта как всегда можно найти у меня на Github.

Для пользователей Windows, OSX и прочего: устройство так же будет работать из коробки. Насколько я знаю, переключение и индикацию раскладки можно сделать на CapsLock через PuntoSwitcher, что уже неплохо, а если вам нужны какие-то дополнительные каналы, то их легко можно добавить.

Изыскания

Выбрал именно STM32 – так как давно хотел с ним познакомится, контроллер просто великолепный, богатая периферия, скорость и наличие аппаратного USB.

Заказал на Амазоне несколько плат на тест, с доставкой завтра – цена около 10€ за две штучки, на алишке можно взять значительно дешевле (что-то типа 2€ за штучку).

У меня уже был JTAG адаптер (который купил для отладки Zigbee), что-то типа 15€ на Алишке, он отлично работает и определяется как родной (хотя явно не оригинал). Работает с GDB.

Оригинальная среда от ST – это просто жесть. Она может быть и хороша, но поставить её в современном линуксе не выйдет 🙂 Так как они требуют древний Python 2.7 (У которого конец жизни объявлен 13 лет назад, и окончательно закрыт три года назад). Так же, для создания проекта надо использовать генератор кода. Я их конечно понимаю отчасти, но получается хороший пример, как сделать разработку проприетарной, не делая её проприетарной 🙂

Потыкал немножко более универсальные фреймворки типа Arduino, но там USB-HID реализован через Жогу: так как исторически его поддержки не было в AVR контроллерах, и там всё эмулируют через загрузчики и всё такое. Нативной поддержки STM32 практически нет, USB HID поддержки тоже нет. Кое-какая переферия STM32 доступна, но далеко не вся.

Отдельно ещё отмечу про USB – это очень мудрёная шина, составить дескриптор устройства вручную – задача прямо на любителя.

Zephyr RTOS

Нашёл интересный проект, и удалось сделать на его базе сразу работающий прототип. Это – операционная система реального времени для микроконтроллеров, где устройства настраиваются через DeviceTree (так же как в Linux) и есть нормальная поддержка периферии.

Но, так же как и со ST – примеры есть, но они плохо документированы, однобоки и перегружены часто. Ключевой находкой было репо на Github – какая то реализация клавиатуры. Спасибо тебе большое, автор! Удалось найти внятный и короткий пример.

Аппаратная часть

Как написал в преамбуле, у меня имеется китайский клон JTag дебаггера от Segger с Алишки, который просто подключается и просто работает. Определяется как родной, у него даже прошивка обновляется родной тулзой от Segger.

Платка – так называемая Blue Pill, по ссылке можно посмотреть распиновку и подробности платы. оснащена разъёмом MicroUSB.

Подключаем JTAG <-> Плата:

  • GND <-> GND
  • 3V3 <-> 3V3
  • SWCK <-> SWCK
  • SWIO <-> SWO

, и всё работает из коробки!

Программная часть

Для сборки устанавливаем VSCode и ставим плагин PlatformIO, основная среда для разработки.

После установки PlatformIO заходим в интерфейс плагина через главное меню и устанавливаем:

  • Frameworks -> zephyr
  • Platform -> ststm32

, либо просто откройте мой проект с github и запустите сборку, среда установит все зависимости автоматически.

Можно так же из командной строки:

platformio run --environment bluepill_f103c8

Это так же установит все зависимости.

Конфигурация устройств

Помимо штатных для платы, назначаем ещё три GPIO как дополнительные LEDы. Я выбрал порт A, так как там есть поддержка ШИМ и можно сделать в будущем всякие там эффекты. Создаём файл zephyr/board.layout:

/ {
    aliases {
        led-caps = &led_a0;
        led-num = &led_a1;
        led-scrl = &led_a2;
    };

    leds {
        compatible = "gpio-leds";
        // Define leds, additionally connected to this board
        // A0
        led_a0: led_a0 {
            gpios = < &gpioa 0x0 GPIO_ACTIVE_HIGH >;
        };
        // A1
        led_a1: led_a1 {
            gpios = < &gpioa 0x1 GPIO_ACTIVE_HIGH >;
        };
        // A2
        led_a2: led_a2 {
            gpios = < &gpioa 0x2 GPIO_ACTIVE_HIGH >;
        };
    };

};

Здесь мы назначаем DeviceTree для трёх диодов, порт GPIO-A, пины с 0 по 2.

Далее, в коде можем создать переменные для них:

#define LED_BUILT_IN_NODE DT_ALIAS(led0)
#define LED_CAPS_NODE DT_ALIAS(led_caps)
#define LED_SCROLL_NODE DT_ALIAS(led_scrl)
#define LED_NUM_NODE DT_ALIAS(led_num)

#define GPIO_SPEC(node_id) GPIO_DT_SPEC_GET_OR(node_id, gpios, {0})

static const struct gpio_dt_spec led_caps = GPIO_SPEC(LED_CAPS_NODE);
static const struct gpio_dt_spec led_scroll = GPIO_SPEC(LED_SCROLL_NODE);
static const struct gpio_dt_spec led_num = GPIO_SPEC(LED_NUM_NODE);

Обращаемся через дополнительный #define GPIO_SPEC, который создаст пустое устройство, если у вас другая плата и в DeviceTree нет записи.

Назначаем настройки проекта в zephyr/prj.conf:

# Define device identifier
CONFIG_USB_DEVICE_VID=0xF109
CONFIG_USB_DEVICE_PID=0x0001
CONFIG_USB_DEVICE_MANUFACTURER="PWS"
CONFIG_USB_DEVICE_PRODUCT="LED indicator"
CONFIG_USB_DEVICE_SN="1"
# Enable USB settings
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_HID=y
CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=y
# Enable In/Out callbacks, to get LED's
CONFIG_ENABLE_HID_INT_OUT_EP=y
# Logging (dows not work here, but enabled)
CONFIG_LOG=y
# Enable GPIO pin In/Out control
CONFIG_GPIO=y
# Enable C++
CONFIG_CPLUSPLUS=y
CONFIG_STD_CPP17=y

Тут у нас настройки USB и минимальный набор опций. Здесь можно настроить, как ваше устройство будет представляться.

Теперь о самом важном, USB:

const uint8_t kbd_desc[] = HID_KEYBOARD_REPORT_DESC();

Этот макрос – собственно создаёт нам шаблон дескриптора устройства, который будет у нас говорить USB, что он – клавиатура. Именно этот кусочек спасает вас от геморроя :

const struct hid_ops callbacks = {
        .int_out_ready = output_ready_cb,
    };
    usb_hid_register_device(kbd_dev, kbd_desc, sizeof(kbd_desc),
                            &callbacks);

Здесь назначаем callback для уведомления событий клавиатуры (LED типа CapsLock мы получим именно здесь).

static void output_ready_cb(const device *kbd_dev)
{
    uint32_t report_read = 0;
    static uint8_t report;
    // We have to read whole buffer, otherwise USB may fail
    while (true)
    {
        hid_int_ep_read(dev, &report, sizeof(report), &report_read);
        if (report_read == 0) {
            return; // No data left
        }

        gpio_pin_set_dt(&led_caps, report & HID_KBD_LED_CAPS_LOCK);
        gpio_pin_set_dt(&led_scroll, report & HID_KBD_LED_SCROLL_LOCK);
        gpio_pin_set_dt(&led_num, report & HID_KBD_LED_NUM_LOCK);
    }
}

Собственно реализация логики – здесь мы получаем событие USB HID, в котором отделяем нужные флаги и зажигаем GPIO светодиоды. Подключаем наше устройство по USB:

[Di Dez 19 20:51:58 2023] usb 5-2.3.3.3: new full-speed USB device number 14 using xhci_hcd
[Di Dez 19 20:51:58 2023] usb 5-2.3.3.3: New USB device found, idVendor=f109, idProduct=0001, bcdDevice= 3.05
[Di Dez 19 20:51:58 2023] usb 5-2.3.3.3: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[Di Dez 19 20:51:58 2023] usb 5-2.3.3.3: Product: LED indicator
[Di Dez 19 20:51:58 2023] usb 5-2.3.3.3: Manufacturer: PWS
[Di Dez 19 20:51:58 2023] usb 5-2.3.3.3: SerialNumber: 1
[Di Dez 19 20:51:58 2023] input: PWS LED indicator as /devices/pci0000:00/0000:00:07.3/0000:3b:00.0/0000:3c:02.0/0000:3d:00.0/usb5/5-2/5-2.3/5-2.3.3/5-2.3.3.3/5-2.3.3.3:1.0/0003:F109:0001.000E/input/input37
[Di Dez 19 20:51:58 2023] hid-generic 0003:F109:0001.000E: input,hidraw2: USB HID v1.11 Keyboard [PWS LED indicator] on usb-0000:3d:00.0-2.3.3.3/input0

Всё просто 🙂 Теперь у нас устройство отображает так же, как китайская клавиатура из М-Видео – отображает лампочки Caps, Num и Scroll. Дальше думаю встрою данное устройство в макет железной дороги, что бы отображало статус семафором или чем-то таким.

Leave a Reply

Your email address will not be published. Required fields are marked *