В статье приведен мой опыт знакомства с 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=yCONFIG_SETTINGS=yCONFIG_FLASH=yCONFIG_FLASH_MAP=yCONFIG_NVS=y# Имя устройства, которое увидит хост (телефон/ПК) при сканированииCONFIG_BT_DEVICE_NAME="Badge"# Внешний вид (Appearance): 0x03C1 = 961 = HID Generic KeyboardCONFIG_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=320CONFIG_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» и таким образом сделать девайс более «полезным», но зачем. В любом случае все исходники есть по ссылке и они будут постепенно дополняться, так как есть намерение и дальше разбираться с этой ОС.
ссылка на оригинал статьи https://habr.com/ru/articles/1064054/