前回の記事では、Devicetree(デバイスツリー)の基本的な仕組みや、JSONに似たツリー構造の全体像について解説しました。
しかし、実際の組み込みLinuxやZephyr OSのボードポーティング(基板対応)を行うと、「#address-cells って何?」「ほかのノードをどうやって参照するの?」「ピンの割り当て(pinctrl)はどう書くの?」といった具体的な疑問にぶつかります。
今回は「実践編」として、実際の開発で頻繁に登場するDTS特有の構文と、より高度なプロパティの書き方について、一歩踏み込んで解説します。
1. アドレス指定のルール(#address-cells と #size-cells)
DTSを読んでいると、必ずと言っていいほど目にするのが #address-cells と #size-cells というプロパティです。これらは、子ノードの reg プロパティ(メモリアドレスやレジスタサイズ)を「32ビット(4バイト)の数値をいくつ使って表現するか」を定義する重要なルールです。
soc {
/* アドレスは32bit(1セル)、サイズも32bit(1セル)で表現する */
#address-cells = <1>;
#size-cells = <1>;
serial@101f1000 {
compatible = "arm,pl011";
/* reg = <アドレス(1セル) サイズ(1セル)> */
reg = <0x101f1000 0x1000>;
};
i2c@40003000 {
compatible = "fsl,imx-i2c";
reg = <0x40003000 0x4000>;
/* I2Cバス上のデバイスにはアドレスしかない(サイズという概念がない) */
#address-cells = <1>;
#size-cells = <0>;
sensor@5c {
compatible = "vendor,sensor";
/* reg = <スレーブアドレス(1セル)> ※サイズ指定なし */
reg = <0x5c>;
};
};
};このように、親ノードでセル数(1セル = 32bit)を宣言し、子ノードはそのルールに従って reg を記述します。64bitアーキテクチャで広大なメモリ空間を扱う場合は、#address-cells = <2>;(32bit × 2 = 64bit)となることもあります。
2. ノード間の参照(ラベルと Phandle)
Devicetreeでは、あるデバイスが別のデバイス(GPIO、クロック、割り込みコントローラなど)に依存しているケースが多々あります。この「紐付け」を行うのがラベルと参照(&)です。
コンパイル時、参照されたノードには一意のID(phandleと呼ばれます)が自動的に割り当てられ、バイナリ上ではそのIDを使ってリンクされます。
/* GPIOコントローラ(参照元としてラベル「gpio1」をつける) */
gpio1: gpio@20000000 {
compatible = "vendor,gpio-controller";
reg = <0x20000000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
};
/* LEDノード(gpio1を参照する) */
leds {
compatible = "gpio-leds";
led_red {
label = "Red LED";
/* <&ラベル ピン番号 アクティブ状態> の形式で参照 */
gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
};
};gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>; の部分は、「gpio1 の 5番ピンを使い、Highになった時にON(アクティブ)とする」という意味になります。GPIO_ACTIVE_HIGH はマクロ定義であり、実態は数値です。
3. 実践:ピンマルチプレクス(pinctrl)の書き方
SoCの物理ピンは、GPIO、I2C、UARTなど複数の機能を切り替えて使えるようになっています(ピンマルチプレクス)。どのピンに何の機能を割り当てるかを定義するのが pinctrl ノードです。
基板固有の .dts ファイルで最もよく編集するのがこの部分です。
/* 1. ピンの設定を定義する(通常はSoC固有の記述ルールに従う) */
&iomuxc {
pinctrl_uart1: uart1grp {
fsl,pins = <
/* ピンの物理ID 機能割り当て プルアップ等の設定値 */
MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1
MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1
>;
};
};
/* 2. デバイス側からピン設定を参照する */
&uart1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>; /* 定義したラベルを参照 */
status = "okay";
};pinctrl-names = "default"; と指定することで、デバイスドライバがロードされた(プローブされた)瞬間に、自動的に pinctrl-0 で指定したピン設定が適用されます。スリープ時用の設定("sleep")などを複数持たせることも可能です。
4. 割り込み(Interrupts)のルーティング
デバイスがCPUに対してイベントを通知するための「割り込み(IRQ)」も、Devicetreeで配線を定義します。
sensor@5c {
compatible = "vendor,sensor";
reg = <0x5c>;
/* どの割り込みコントローラに繋がっているか */
interrupt-parent = <&gpio1>;
/* 割り込み番号とトリガー条件(例:8番ピン, 立ち下がりエッジ) */
interrupts = <8 IRQ_TYPE_EDGE_FALLING>;
};ここでも interrupt-parent で親となる割り込みコントローラ(またはGPIOコントローラ)を参照し、interrupts でそのコントローラ内での番号やトリガーの条件(エッジトリガーか、レベルトリガーか等)を指定します。
5. OSの挙動を制御する特殊なノード(aliases と chosen)
Devicetreeには、純粋なハードウェアの記述ではなく、OSやブートローダに情報を渡すための特殊なノードが存在します。
aliasesノード
デバイスに分かりやすい「通し番号」を割り当てます。例えば、複数のUARTがある場合、どれを /dev/ttyS0 にするかを固定できます。
aliases {
serial0 = &uart1;
serial1 = &uart2;
};
chosenノード
ブートローダ(U-Bootなど)からカーネルへ、動的なパラメータ(起動引数など)を渡すためのノードです。Zephyr OSの場合は、コンソール出力をどのシリアルポートに行うかなどをここで指定します。
chosen {
/* Linuxの場合:カーネルの起動引数 */
bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 rw";
/* Zephyr OSの場合:標準出力先を割り当て */
zephyr,console = &uart1;
zephyr,shell-uart = &uart1;
};まとめ
今回はDTSの実践的な書き方として、アドレス指定のルール、Phandleによる参照、ピンマルチプレクス(pinctrl)、割り込み、特殊ノード(aliases / chosen)について解説しました。
#address-cells/#size-cells:regプロパティの配列要素数を決定する重要なルール。- ラベルと参照(
&): 物理的な配線の繋がり(GPIOや割り込みなど)をソフトウェア上で表現する仕組み。 - pinctrl: 一つの物理ピンが持つ複数の機能の中から、基板設計に合わせた機能を選択・固定する。
- chosen / aliases: ハードウェア構成だけでなく、OSのシステム設定(ブート引数やコンソール指定)を行う。
Devicetreeは単なる設定ファイルではなく、ハードウェアのデータシートをソフトウェアの言語に翻訳した設計図です。最初はプロパティの多さに圧倒されるかもしれませんが、データシートと見比べながらDTSを読み解くことで、ハードウェアとOS(Linux/Zephyr)の密接な連携が見えてくるはずです。ぜひ実際のプロジェクトで .dts をカスタマイズする際の参考にしてください。

コメント