1. はじめに
本記事では、 Zephyr の特徴でかつ最大の鬼門だと思われる Devicetree について触れていきます。
まずは序章として、主要なファイルの配置や設定を変更する基本的な流れについて案内していきます。
なお、本記事では実機がなくても確認可能です。
2. 環境の準備
適宜 west build
ができるところまで環境を準備してください。
環境構築は下記リポジトリの README.md に沿って行うことも可能です。
今回は下記を対象に話を進めます。
board | Zephyr 環境 | ビルド対象のアプリ |
---|---|---|
nucleo_f401re | ~/zephyrproject | zephyr/sample/basic/button |
ビルドできることを確認しておきます。
(ビルドできない場合は環境を見直すこと)
cd ~/zephyrproject
source .venv/bin/activate
cd zephyr/samples/basic/button/
west build -b nucleo_f401re .
[3/141] Generating include/generated/zephyr/version.h
-- Zephyr version: 4.1.0 (/home/taka/zephyrproject/zephyr), build: v4.1.0
[141/141] Linking C executable zephyr/zephyr.elf
Memory region Used Size Region Size %age Used
FLASH: 16354 B 512 KB 3.12%
RAM: 4544 B 96 KB 4.62%
IDT_LIST: 0 GB 32 KB 0.00%
Generating files from /home/taka/zephyrproject/zephyr/samples/basic/button/build/zephyr/zephyr.elf for board: nucleo_f401re
3. Devicetree の役割
Devicetree とは、大雑把に説明すると H/W 構成を書くためのフォーマットで、ピン配やクロック、pull up/down などを設定していく仕組みを提供しています。
これを採用している最も有名なプロダクトは Linux kernel だと思いますが、採用に至った経緯は下記です。
これは、クロック設定などの些細な違いだけでボード毎のソースコードが乱立したために、「定義と実装は分けろ(超意訳)」という背景で採用に至っています。
つまり、Devicetree は C/C++ におけるヘッダーファイル的な役割で、定義だけを独立させ、実装(ソースコード)と分離する仕組みを提供しているということになります。
そして Zephyr もこの仕組みを採用しているわけですが、Linux は起動時に読み込んでデバイス構成を動的に決定するのに対し、Zephyr はビルド時に静的にデバイス構成を決定させるという大きな違いがあります。
Zephyr に於ける Devicetree は、下記ファイル群から構成されます。
ファイル名 | 概要 |
---|---|
*.dts | Devicetree source. これにベースに下記ファイルが連結されていく |
*.dtsi | dts から include して定義を追加できるようにしたファイル |
*.overlay | dts の定義を上書きで変更を加えるためのファイル |
*.yaml | dts / overlay 内で利用する左辺値(プロパティ名)の定義 |
*.h | dts / overlay 内で利用する右辺値(プロパティ値)の定義 |
zephyr.dts | ビルド時に上記ファイル群が結合され、最終的な dts として生成 |
次の章からは、これら各ファイルの配置や中身のデータ、変更方法などに触れていきます。
4. オリジナルの dts と 最終的に生成される dts
まずは、ベースとなる dts
を確認します。このファイルはボード毎に配置されています。
命名ルールも決まっており、ターゲットのボード名 nucleo_f401re
がそのまま dts
のファイル名になっています。
$ ls ~/zephyrproject/zephyr/boards/st/nucleo_f401re
Kconfig.defconfig board.cmake nucleo_f401re.dts st_morpho_connector.dtsi
Kconfig.nucleo_f401re board.yml nucleo_f401re.yaml support
arduino_r3_connector.dtsi doc nucleo_f401re_defconfig
その中の user_button
は以下の設定になっています。
gpio_keys {
compatible = "gpio-keys";
user_button: button {
label = "User";
gpios = <&gpioc 13 GPIO_ACTIVE_LOW>;
zephyr,code = <INPUT_KEY_0>;
};
};
続いて 2. 環境の準備
でビルドした結果を確認していきます。
west build
した際、最終的な dts
が build/zephyr/zephyr.dts
として生成されます。
gpio_keys {
compatible = "gpio-keys";
user_button: button {
label = "User";
gpios = < &gpioc 0xd 0x1 >;
zephyr,code = < 0xb >;
};
};
両者を比較すると、<> で囲われたラベルが16進数の値になっている事以外は、オリジナルの dts の内容と基本的に同じであることがわかります。
また、最終的な zephyr.dts は nucleo_f401re.dts よりも遥かに巨大なファイルになっていますが、これは dtsi などすべてが連結された結果となります。
5. overlay を用いた設定の上書き
5.1. dts と overlay の関連性
では、続いて dts
の設定を変更してこのボタンを無効化してみます。
ただし、nucleo_f401re.dts
を直接編集することも可能なのですが、お作法としてそれは推奨されません。
推奨される方法は boards/[board名].overlay
というファイルを用意して、変更したい要素だけを記述していく事です。
今回でいうと、ターゲットは nucleo_f401re なので、boards/nucleo_f401re.overlay
となります。
その状態で west build
すると、その overlay
が参照されて上書きや追加されるという仕組みとなっています。
実は overlay
のファイル名やパスは色々と調整可能です。overlay
の詳しい検索条件は以下を参照。
https://docs.zephyrproject.org/latest/build/dts/howtos.html#set-devicetree-overlays
5.2. overlay の記述方法
記述方法として、先述の overlay に対し上書きしたい対象のノード名に &
を付けて指定し、変更を加えたいパラメータの設定値を記述します。
今回は user_button
が対象のノード名で、変更を加えたいパラメータは status = "disabled";
で、これにより user_button
を無効にする事ができます。
(逆に有効にする場合は status = "okay";
を用います)
実際に overlay として下記のように保存しておきます。
&user_button {
status = "disabled";
};
5.3. overlay を反映した結果の確認
では、この状態で改めて zephyr/sample/basic/button
をビルドしてみます。
$ cd ~/zephyrproject/zephyr/sample/basic/button
$ rm -rf build; west build -b nucleo_f401re .
:
zephyr/samples/basic/button/src/main.c:26:2: error: #error "Unsupported board: sw0 devicetree alias is not defined"
26 | #error "Unsupported board: sw0 devicetree alias is not defined"
| ^~~~~
[37/141] Building C object zephyr/arch/arch/...ore/CMakeFiles/arch__arm__core.dir/nmi.c.obj
ninja: build stopped: subcommand failed.
FATAL ERROR: command exited with status 1: /usr/bin/cmake --build /home/taka/zephyrproject/zephyr/samples/basic/button/build
ビルドエラーが起きました。エラー発生箇所である zephyr/samples/basic/button/src/main.c:26
は以下の通り。
24 #define SW0_NODE DT_ALIAS(sw0)
25 #if !DT_NODE_HAS_STATUS_OKAY(SW0_NODE)
26 #error "Unsupported board: sw0 devicetree alias is not defined"
27 #endif
28 static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios,
つまり、sw0 が STATUS_OKAY ではないためにエラーを発生させている事がわかります。
では実際に、生成された dts を確認してみます。
gpio_keys {
compatible = "gpio-keys";
user_button: button {
label = "User";
gpios = < &gpioc 0xd 0x1 >;
zephyr,code = < 0xb >;
status = "disabled";
};
};
ビルドが通っていた時と比較し status = "disabled";
が追加されている事がわかります。
さらに build/zephyr/zephyr.dts
内で user_button
を検索すると、ファイル冒頭の aliases
で sw0 = &user_button;
として割り当てられている事がわかります。
aliases {
led0 = &green_led_2;
sw0 = &user_button;
pwm-led0 = &green_pwm_led;
watchdog0 = &wwdg;
die-temp0 = &die_temp;
volt-sensor0 = &vref;
volt-sensor1 = &vbat;
};
つまり sw0
の参照先である user_button
の status
が disabled
(つまり OKAY ではない) になっていて、ビルドエラーを発生させるプリプロセッサが機能したという事がわかります。
6. まとめ
今回は、敢えてビルドエラーを起こす方向で Devicetree
の構成や変更方法についてご紹介しました。
本記事では以下の内容について触れています。
- Devicetree の役割
- ボード毎に用意されている dts の配置
- ビルド時に生成される最終型の
build/zephyr/zephyr.dts
の把握 - 機能の有効/無効の切り替えを overlay で変更する方法
これで既存の設定を変更する際、どこを参照して、どうやって変更するかの大まかな理解は得られるかと思います。
ただし、Devicetree では非常にたくさんの左辺値と右辺値があり、実用的に運用するためには、それらを把握した上で各種パラメータを設定していく必要があります(ここが鬼門)。
その辺の踏み込んだ内容については、次回の Devicetree の記事で解説していきたいと思います。
※ 国内の観測史上最高気温を出した日に、🎄2024年アドベントカレンダー🎅の投稿になっちゃいました。