Devicetree (дерево устройств) в Zephyr
Один и тот же драйвер I2C должен работать на десятках плат с разными адресами регистров, линиями прерываний и датчиками на шине. Zephyr не зашивает это в код и не плодит #ifdef: описание железа лежит в дереве устройств, а из него на этапе сборки генерируются макросы.
Обновлено Редакция GAW
Devicetree (дерево устройств) — иерархическая структура данных, описывающая аппаратуру; её исходный текстовый формат DTS и двоичное представление определены спецификацией Devicetree. Zephyr описывает деревом аппаратуру каждой поддерживаемой платы и её начальную конфигурацию, то есть использует его и как язык описания железа, и как язык настройки. Сборка превращает дерево в C-заголовок с макросами, а код обращается к ним через API devicetree.h, все макросы которого начинаются с DT_.
Узлы и свойства
Файл начинается строкой /dts-v1/;, корневой узел обозначается /, пути узлов пишутся через косую черту, как в файловой системе. Узел может иметь метку (i2c1:), по которой на него ссылаются. Свойства — пары «имя — значение»; числовые значения в угловых скобках состоят из ячеек (cell) по 32 бита. Иерархия повторяет физическую: датчики на I2C — дочерние узлы контроллера шины, а часть имени после @ — адрес в адресном пространстве родителя: база регистров (i2c@40003000), адрес на I2C (apds9960@39), номер chip select для SPI, начало области памяти или смещение раздела флеш.
| Свойство | Назначение |
|---|---|
compatible | имя устройства вида "vendor,device"; по нему сборка находит привязку (binding) и драйвер |
reg | пары «адрес, длина»: регистры блока, адрес на I2C без длины, chip select для SPI |
status | "okay" или отсутствие свойства — узел включён; любое другое значение Zephyr считает выключенным, и struct device для него не создаётся |
interrupts | спецификаторы прерываний устройства |
Типы значений: string, int, boolean (свойство без значения), array, uint8-array, string-array, phandle, phandles, phandle-array. Свойство label в новых привязках использовать не рекомендуется.
Файлы на входе и выходе
Входных файлов четыре типа: .dts платы, .dtsi с описанием SoC и общих блоков (их включает BOARD.dts), .overlay и привязки .yaml, которые описывают содержимое узлов и типы данных. На все файлы запускается препроцессор C, поэтому включения пишут через #include. Overlay — такой же DTS, который сборка дописывает после BOARD.dts; его определения переопределяют базовые, и так приложение включает выключенную периферию или добавляет свой датчик, не меняя файлы платы. Скрипты dtlib.py, edtlib.py и gen_defines.py создают zephyr.dts.pre, итоговое объединённое дерево zephyr.dts (удобно для отладки) и devicetree_generated.h; штатный dtc, если установлен, запускается только ради проверки — его вывод не используется. Сгенерированный заголовок напрямую не включают — только devicetree.h.
Доступ из кода
В отличие от Linux, который разбирает двоичное дерево во время работы, Zephyr обрабатывает его при сборке: двоичный блок и код разбора не помещаются в малые устройства. Идентификатор узла — макрос, его не сохранить в переменную; получают его через DT_PATH(), DT_NODELABEL(), DT_ALIAS(), DT_INST(), DT_CHOSEN(), DT_PARENT()/DT_CHILD(). Дефис и @ в именах становятся подчёркиванием, буквы — строчными: i2c@40002000 превращается в i2c_40002000, clock-frequency — в clock_frequency. Свойства читают DT_PROP() (булево даёт 0 или 1, массив — инициализатор), наличие проверяют DT_NODE_HAS_PROP(), длину — DT_PROP_LEN(); для reg есть DT_REG_ADDR(), DT_REG_SIZE() и варианты _BY_IDX с литеральным индексом, для прерываний — DT_NUM_IRQS(). Это замена таблицам адресов из CMSIS-заголовков, где каждый периферийный блок — константа, а не узел с описанием.
Чем отличается от Kconfig
Правило Zephyr: devicetree — про железо и его состояние при загрузке, Kconfig — про программные функции в образе. Наличие двух UART, их регистры, приоритет прерывания и скорость описаны в дереве; собирать ли драйвер UART — решает Kconfig. Экземпляры драйверов включают только через status, отдельных опций Kconfig для них нет. Исключения: параметры драйверов вроде размера буфера допускаются в дереве со свойствами с префиксом zephyr,, а chosen выбирает экземпляр для роли — например, UART консоли. Для каждого compatible генерируется символ DT_HAS_<compatible>_ENABLED, равный y, если в дереве есть включённый узел с таким compatible, — от него зависят символы драйверов. Что дерево не описывает — раскладку памяти и код старта, — задают скрипты компоновщика и стартовый код. Поток очереди работ и другие объекты ядра к дереву отношения не имеют: это программная, а не аппаратная конфигурация. О выборе версии Zephyr — в ответе о LTS-версиях.
Частые вопросы
Чем overlay отличается от .dts платы?
BOARD.dts описывает плату и включает .dtsi с описанием SoC. Overlay — такой же файл DTS, который сборка дописывает после BOARD.dts, поэтому его узлы переопределяют базовые. Приложение использует overlay, чтобы включить выключенную по умолчанию периферию (status = "okay") или добавить свой датчик на шину, не трогая файлы платы. Файлы .overlay подхватываются из известных мест автоматически либо перечисляются в переменной DTC_OVERLAY_FILE.
Почему Zephyr не читает devicetree во время работы, как Linux?
Linux разбирает двоичное дерево при загрузке и по нему инициализирует драйверы. Для микроконтроллеров Zephyr такой двоичный блок и код его разбора слишком велики, поэтому дерево обрабатывается при сборке: скрипт gen_defines.py с привязками генерирует devicetree_generated.h, а код обращается к нему через макросы devicetree.h — DT_NODELABEL(), DT_PROP(), DT_REG_ADDR() и другие. Идентификатор узла — это макрос, в переменную его не сохранить.
Что писать в devicetree, а что в Kconfig?
В devicetree — то, что относится к железу и его состоянию при загрузке: какие периферийные блоки есть, их адреса регистров, линии прерываний, частоты, скорость UART, какой экземпляр включён (status). В Kconfig — что собирать в образ: нужен ли драйвер UART вообще, стек Bluetooth или 802.15.4. Параметры драйверов, которые Kconfig не может задать по экземплярам (размер внутреннего буфера), допускаются в devicetree со свойствами с префиксом zephyr,.
Источники
- Zephyr Project. Zephyr Project Documentation (latest): Devicetree — Scope and purpose; Syntax and structure; Input and output files, 2026 — стр. Scope and purpose: devicetree — иерархическая структура данных, описывающая аппаратуру; спецификация Devicetree определяет исходное и двоичное представления; Zephyr описывает им аппаратуру поддерживаемых плат и её начальную конфигурацию; два типа входных файлов — исходники и привязки (bindings) с типами данных; сборка генерирует C-заголовок, абстрагированный API devicetree.h; все макросы API начинаются с DT_, у сгенерированных есть строчные буквы; стр. Syntax: текстовый формат DTS, строка /dts-v1/;, корневой узел /, узлы, метки узлов, пути через косую черту; свойства — пары имя/значение, ячейка (cell) — 32-битное целое; узлы отражают физическое устройство (периферия I2C — дети контроллера шины); адрес узла после @ — база регистров, адрес на I2C, номер chip select SPI, начальный адрес памяти, смещение раздела флеш; важные свойства: compatible вида "vendor,device" (string-array, по нему сборка находит привязку), reg — пары (адрес, длина), status — узел включён при "okay" или без свойства, любое другое значение Zephyr считает выключенным, без включённого узла struct device драйвера не создаётся; interrupts; свойство label не рекомендуется; типы значений: string, int, boolean, array, uint8-array, string-array, phandle, phandles, phandle-array; стр. Input and output files: .dts (плата), .dtsi (SoC и общее), .overlay, .yaml (привязки); BOARD.dts включает .dtsi; на все файлы запускается препроцессор C; overlay дописывается последним и переопределяет BOARD.dts, подхватывается автоматически или через DTC_OVERLAY_FILE; скрипты dtlib.py, edtlib.py, gen_defines.py; dtc только для проверки; выходные файлы zephyr.dts.pre, devicetree_generated.h, zephyr.dts — итоговое дерево для отладки; сгенерированный заголовок напрямую не включать
- Zephyr Project. Zephyr Project Documentation (latest): Devicetree access from C/C++, 2026 — в отличие от Linux, Zephyr не разбирает двоичное дерево во время работы — оно слишком велико для малых устройств, данные доступны через макросы; идентификатор узла — макрос, его нельзя сохранить в переменную; способы получить: DT_PATH(), DT_NODELABEL(), DT_ALIAS(), DT_INST(), DT_CHOSEN(), DT_PARENT()/DT_CHILD(); дефис и @ в именах заменяются подчёркиванием, имена приводятся к строчным; DT_NODE_HAS_PROP(), DT_PROP() для int, boolean, string и массивов (булево даёт 0 или 1), DT_PROP_LEN(); reg читают DT_REG_ADDR()/DT_REG_SIZE() и _BY_IDX, DT_NUM_REGS(); прерывания — DT_NUM_IRQS(); индекс должен быть литералом
- Zephyr Project. Zephyr Project Documentation (latest): Devicetree versus Kconfig, 2026 — devicetree описывает аппаратуру и её конфигурацию на момент загрузки — периферию платы, частоты, линии прерываний, скорость UART; Kconfig — какую программную поддержку собрать: сеть, драйверы; пример с двумя UART: их наличие и настройки в devicetree, включать ли драйвер — Kconfig; опции Kconfig для отдельных экземпляров драйверов убраны, экземпляр включают через status; исключения: параметры драйверов вроде размера буфера с префиксом zephyr,; ключевое слово chosen; для каждого compatible генерируется символ DT_HAS_<compatible>_ENABLED, который равен y, если есть узел с этим compatible и status okay
Нашли ошибку? ·
Смотрите также
- KconfigСистема настройки сборки из ядра Linux: символы CONFIG_ с типами и зависимостями, меню, файлы prj.conf и sdkconfig
- Очередь работ Zephyr (workqueue)Объект ядра Zephyr: выделенный поток выполняет элементы работы в порядке FIFO; способ вынести обработку из ISR
- CMSISСтандарт Arm: заголовки ядра Cortex-M, драйверы, RTOS и описание устройств
- Скрипт компоновщика GNU ldMEMORY, SECTIONS, VMA и LMA, KEEP, стек и куча, map-файл, переполнение
- Код запускаТаблица векторов, Reset_Handler, .data и .bss, VTOR, FPU, .initN avr-libc
- Какую версию Zephyr выбрать: LTS или последнюю стабильную?Изделие на годы — LTS 3.7 (до 27.07.2029); новые платы и функции — 4.5 (до 28.10.2027)