В статье приведен мой опыт знакомства с Zephyr RTOS на примере создания беспроводной клавиатуры с тремя кнопками (Enter, Copy, Paste). Затронуты ключевые аспекты: настройка BLE-стека и HID-сервиса, управление энергопотреблением (STOP2, параметры соединения, программный сон), работа с Devicetree (overlay и правки dtsi), отладка через RTT, а также типичные проблемы (прошивка CPU2, EXTI-конфликты, требования HID к шифрованию). Проект полностью воспроизводим и содержит ссылки на полный исходный код и готовую прошивку.
Введение
Zephyr RTOS выглядит крайне привлекательной для домашних поделок взамен того же Arduino, и по данной ОС много теоретических и обзорных материалов/курсов (например, платный пятидневный курс за 3500$ от Linux Foundation или бесплатный - от Nordic). Но практических примеров немного и из-за этого трудно сложить однозначное представление о ней, особенно с учетом быстрого развития. Лично у меня от неё большие ожидания.
Цель - ознакомиться с процессом разработки на Zephyr RTOS и понять её применимость для изделий с аккумуляторным питанием.
Подход - сделать памятный сувенир.
Подготовка инструментов
В качестве аппаратной платформы выбрана плата от WeAct на STM32WB55 (двухъядерный чип с интегрированным BLE-радио). Плата поддерживается Zephyr и подходит для широкого круга беспроводных DIY-задач. Плата представляет собою современное и интересное продолжение широко полюбившейся платы blue pill (на stm32f103), поэтому возможность поэкспериментировать с ней для меня была самоценной и тут речи нет про техническую оптимальность выбора.
Изначально я попробовал поставить средства для сборки прошивок на Zephyr RTOS на свой ПК и некоторые компоненты оказались несовместимы с моей старенькой macOS Monterey, поэтому я переключился на сборку в Docker.
Toolchain в docker'e
Скачать образ со всем необходимым можно по ссылке:
docker pull zephyrprojectrtos/zephyr‑build:latest
Далее следует перейти в папку, где хотите располагать свои проекты, и там выполнить:
docker run ‑rm ‑it ‑v $(pwd):/workdir zephyrprojectrtos/zephyr‑build:latest bash
Далее развернуть репозиторий Zephyr:
west init ‑m https://github.com/zephyrproject‑rtos/zephyr ‑mr main
После этого можно будет собирать свой проект на выбранной плате с помощью команды
west build ‑b weact_stm32wb55_core ‑p auto
Для отладки проекта использовался VS Code с расширением Cortex-Debug и отладчик J-Link. Так как сборка выполнялась в Docker-контейнере, а отладка - на хост-машине напрямую через SWD, то потребовалась корректная подстановки путей к исходным файлам, поскольку внутри контейнера рабочая директория отличается от локальной.
Подстановка путей (substitutePath)
Из-за того, что пути к исходным файлам внутри контейнера (/workdir/...) отличаются от локальных путей на хост-машине, GDB не может найти исходники для пошаговой отладки, если не указать соответствие.
В добавленном к данной статье репозитории приведён конфигурационный файл .vscode/launch.json с помощью которого можно "совмещать" предсобранный elf-файл с имеющимися исходниками.
Поле "substitutePath" задаёт замену префиксов: все пути, начинающиеся с /workdir/zephyr, заменяются на локальный путь к исходникам Zephyr и т.д.
Внимание! в .vscode/launch.json указаны настройки для конкретно моего ПК, но любая нейронка на основе этого примера легко подскажет, что и как скорректировать именно вам.
Кнопки
Были взяты простейшие механические свитчи, которые работают по принципу замыкания цепи:

В моём наборе разброс времени дребезга был от 0,2 до 1,2 мс:

Пишут, что типовое время дребезга для свитчей такого типа составляет до 30 мс, поэтому в проекте задана именно такая величина. Выглядит много, но на практике такая задержка незаметна.
Подключение кнопок.
Обработку нажатий можно было бы сделать через периодическое сканирование, но по некоторым соображениям в данном случае для меня было принципиально разобраться именно с отложенной обработкой прерываний.
Первая кнопка очень легко подключилась на пин boot_button из базового DTS платы:

Первые проблемы
Но далее я случайным образом выбрал PA0 и PA1 и не угадал. Сначала возникла проблема с тем, что именно эти два вывода уже заняты АЦП:

Но даже после того, как я напрямую отключил adc1 в overlay - всё равно осталась коллизия по каналу EXTI, с которой я с ходу не разобрался. Пока что выбрал другие пустые пины.
Две другие кнопки заняли свободные пины PB6 и PB7:
buttons { compatible = "gpio-keys"; sw1: button_1 { gpios = <&gpiob 6 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>; label = "Copy Button (Ctrl+C)"; }; sw2: button_2 { gpios = <&gpiob 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>; label = "Paste Button (Ctrl+V)"; }; };
Работа с кнопками в итоге выглядит так:

Для отладки и проверки каждый раз при нажатии любой из клавиш зажигается встроенный светодиод на выводе PE4.
Отладочная схема:

Bluetooth
Принцип работы BLE в Zephyr.
Zephyr реализует Host-контроллерную архитектуру, где:
Host — всё, что работает на CPU1 (HCI-драйвер, L2CAP, ATT, GATT, SMP).
Controller — физический уровень, LL, PHY (реализовано в прошивке CPU2).
В Zephyr для STM32WB55 используется драйвер st,stm32wb-rf, который через IPCC передаёт HCI-команды и данные между CPU1 и CPU2.
Настройка BLE.
В overlay ничего добавлять не нужно, т. к. DTS на SoC уже есть:

Но вprj.conf довольно много компонентов операционной системы связано с BLE:
Фрагмент prj.conf проекта связанный с BLE
# ================================================================== # # Bluetooth LE HID-устройство ( stm32_button ) # # # # Радиомодуль STM32WB55 (CPU2) управляется через IPCC/IPM-драйвер # # (drivers/bluetooth/hci/ipm_stm32wb.c). Узел ble_rf уже включён # # в stm32wb.dtsi, а zephyr,bt-hci = &ble_rf — в chosen. # # Поэтому CONFIG_BT_STM32_IPM включается автоматически. # # ================================================================== # # Базовый стек Bluetooth LE (Host + Controller-драйвер IPM) CONFIG_BT=y # Отладочный лог HCI-драйвера IPM (ipm_stm32wb.c): показывает запуск CPU2, # ошибки IPCC/клоков, таймаут C2_STARTED и т.п. Временно для диагностики. # CONFIG_BT_HCI_DRIVER_LOG_LEVEL_DBG=y # Периферийная роль (обходимся как HID-клавиатура/пульт) CONFIG_BT_PERIPHERAL=y # Менеджер безопасности (SMP): HID требует шифрованного канала CONFIG_BT_SMP=y # Хранение ключей спаривания и ID-адреса во flash (NVS). # ВКЛЮЧЕНО: сохраняет bonding между перезагрузками (ключи, IRK, CSRK). # ВАЖНО: при CONFIG_BT_SETTINGS=y стек НЕ создаёт ID-адрес сам. # Приложение обязано вызвать settings_load() ПОСЛЕ bt_enable() # (в callback'е bt_ready в ble.c), иначе реклама падает с -EAGAIN (-11): # "No ID address. App must call settings_load()". CONFIG_BT_SETTINGS=y CONFIG_SETTINGS=y CONFIG_FLASH=y CONFIG_FLASH_MAP=y CONFIG_NVS=y # Имя устройства, которое увидит хост (телефон/ПК) при сканировании CONFIG_BT_DEVICE_NAME="Badge" # Внешний вид (Appearance): 0x03C1 = 961 = HID Generic Keyboard CONFIG_BT_DEVICE_APPEARANCE=961 # Увеличиваем стек системной очереди работ (settings/BT требуют больше) CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 # ================================================================== # # Энергосбережение BLE (connection parameters) # # # # CPU1 теперь засыпает (CONFIG_PM + TICKLESS, см. верх файла). # # Дополнительно минимизируем активность радио CPU2 через # # большие connection interval + slave latency: # # радио просыпается лишь раз в ~4 секунды. # # # # Параметры передаются хосту через GAP; хост применяет их # # ПОСЛЕ успешного спаривания. # # ================================================================== # CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS=y # interval: единица 1.25 мс. # MIN=320, MAX=320 → 400 мс между connection events. CONFIG_BT_PERIPHERAL_PREF_MIN_INT=320 CONFIG_BT_PERIPHERAL_PREF_MAX_INT=320 # slave latency: пропуск 9 events → радио спит ~4 сек (9*400 мс) CONFIG_BT_PERIPHERAL_PREF_LATENCY=9 # supervision timeout: единица 10 мс. 600 = 6 сек. # Требование BLE: timeout > (1+latency)*interval*2 → 6 > (1+9)*0.4*2=8 ❌ # Поэтому при latency=9 interval=400мс нужен timeout > 8 сек → ставим 100 (10с). CONFIG_BT_PERIPHERAL_PREF_TIMEOUT=1000
Ранее я никогда с BLE не работал и опыт с Zephyr показался мне весьма интуитивно понятным. Вероятно, я просто не понимаю, что упускаю из-за отсутсвия опыта, но внешне всё выглядит работающим. Вопросы были только с повторным переподключением к ПК из-за того, что при перепрошивке МК содержимое flash-памяти с ключами безопасности было пустым, но это поправилось простой проверкой.
Требования к прошивке CPU2.
Прошивка второго ядра должна поддерживать HCI-интерфейс (Host Controller Interface). Но на плате изначально установлена Full Stack прошивка от ST, которая несовместима с Zephyr.
Для работы с Zephyr необходимо заменить её на HCI Layer прошивку:
Скачать файл
stm32wb5x_BLE_HCILayer_extended_fw.binиз репозитория ST.Установить эту прошивку с помощью STM32CubeProgrammer через раздел работы с FUS по адресу 0x080DC000.
Запустить эту прошивку (требуется только один раз).
Энергопотребление
Изначально замеренное энергопотребление с включенным BLE у меня составило примерно от 6 до 12 мА. Что для аккумуляторного устройства значит не более одного дня работы. На текущей версии прошивки у меня получилось достичь среднего тока потребления ~900 мкА на полностью рабочем устройстве, что уже немного лучше, но всё равно на два порядка больше желаемого.

Ключевые меры, принятые для достижения указанной величины тока:
CPU1 засыпает (CONFIG_PM=y, CONFIG_PM_DEVICE=y, CONFIG_TICKLESS_KERNEL=y)
Дополнительно минимизируем активность радио CPU2 через большие connection interval + slave latency.
Через несколько секунд бездействия клавиатура уходит в сон и по нажатии на клавишу снова подключается к ПК.
Сборка устройства
Преимущества кокосовой скорлупы для DIY-электроники очевидны:
Крепко.Экологично.Эргономично.Передаёт дух vibe-кодинга.
Создаёт рабочие места для молодёжи.
Процесс сборки верхней половины корпуса








Преимущества сухих строительных смесей для DIY-электроники очевидны:
Крепко.Экологично.Эргономично.Передаёт дух vibe-кодинга.
Создаёт рабочие места для молодёжи.
Процесс сборки верхней половины корпуса


К сожалению на этапе лепки всё было в грязи и было не до фотографий.
Исходники
https://github.com/RomanBashmakov/coconut_vibe
Конечно, можно программно задать какие-то полезные комбинации по нажатии вроде ввода пароля от ПК или команды "git log --graph --oneline --all" и таким образом сделать девайс более "полезным", но зачем. В любом случае все исходники есть по ссылке и они будут постепенно дополняться, так как есть намерение и дальше разбираться с этой ОС.
