ウェビナー:Zephyr ワークショップ - デバイス ドライバの作成

Zephyr

ウェビナー開催日:2025年4月24日

重要なポイント

このワークショップでは、Shawn HymelがZephyr向けのI2C温度センサ用デバイスドライバを作成する手順を解説します。デバイスドライバはZephyrの基本的な構成要素の1つであり、自分でドライバを作成することで、Cコード、CMake、Kconfig、そしてDevicetreeを扱う経験が得られます。これらの言語や仕組みを理解することは、Zephyrでアプリケーションを開発するうえで不可欠です。

この講座に沿って学習を進める場合、以下のハードウェアの準備をお勧めします

よくある質問

ZephyrとBLEを使った NRF5340 のプロジェクトをいくつか提案していただけますか?

  • BLE対応マイクロコントローラであなたが思いついたものはほぼ何にでもご利用いただけます。例えば、スマートウォッチ、スマートホームセンサ、ウェアラブル歩数計、電子メモによるToDoリストなどがあります。

Zephyrをフレームワークと考えてもよいでしょうか?

  • 「フレームワーク」という用語は、組み込みやプログラミングの世界ではやや曖昧です。しかし本質的にはそのとおりですが、ZephyrはRTOS、ベンダーのHALと連携するための抽象化レイヤ、ビルドシステム、ミドルウェアライブラリ、そしてさまざまなテストおよびデバッグツールを含むフレームワークと言えます。

なぜ FreeRTOSではなくZephyrを使うべきなのでしょうか。

  • FreeRTOSの中核は単なるスケジューラです。AmazonはIoT開発を容易にするため、長年にわたりいくつかのネットワークスタックを追加してきました。しかし、FreeRTOSにはZephyrが提供するようなデバイス非依存の抽象化レイヤがありません。そのためFreeRTOSを使う場合、特定のハードウェアを動作させるには依然としてシリコンベンダーのHALを利用する必要があります。Zephyrでアプリケーションを書くと、本当にデバイス非依存にできるため、他のハードウェアへの移植がより容易になります。

ドライバのスタブをセットアップするためのスクリプトはありますか?

  • スタブをセットアップするスクリプトを私は知りませんが、以下のNordicのページが良い出発点になるかもしれません。

次回のZephyrのウェビナーはいつ開催されますか?

  • 現在、Zephyrに関するウェビナーの開催は予定しておりませんが、ご興味をお持ちいただけて大変嬉しく思います。これらのトピックをより深く掘り下げたZephyr入門の動画シリーズもぜひご覧ください。

Zephyrはどれくらいのオーバーヘッドが必要ですか?

  • 単純な点滅の例では、約16kBのRAMのオーバーヘッドと32~64kBのフラッシュのオーバーヘッドが見込まれます。コンテキストスイッチには数百CPUサイクルを想定してください。より多くの機能/ドライバ(WiFi、Bluetooth、LVGLなど)を有効にすると、これらはすべて増加するのは明らかです。ZephyrとRIOT-OSのベンチマーク結果がよくまとめられた、以下の記事が参考になります

どのような場面でsample_fetch()とchannel_get()のコールバックが呼び出されるのですか?何によって呼び出されるのですか?

  • ウェビナーで明確でなかった場合は申し訳ありません。sample_fetch()とchannel_get()は、必ずしもコールバック関数ではありません。これらは「API」構造体に関数ポインタとして割り当てられています。ドライバがブート時にカーネル内で初期化された後、メインアプリケーションが(このAPI構造体を介して)直接呼び出します。

なぜZephyr Projectは、Cの中でC++の仕組みをコピーして使っているのですか?

  • 正確な理由は分かりませんが、Zephyrチームは、従来のCコンパイラのみのマイクロコントローラであっても、ほぼすべてのマイクロコントローラでコンパイルできることを保証したかったの だろうと思います。

WSL2上でZephyr開発用のコンテナを動かしています。デバッグやシリアルコンソールを確認するために、各デバイスを明示的に指定したり、新しいコンテナを作成したりせずに、USB/ACM デバイスをコンテナに渡すにはどうすればよいでしょうか。

  • これはホストのOSが異なる環境では一貫して動作させることができていません。USB-IPを試してみることをお勧めします。この件に関する相応しい議論は以下のとおりです。

リモートコンテナの拡張機能を使用してコンテナを実行しています。ACMデバイスをデバッグしたり、シリアルコンソールの表示を行うには、どのようにACMデバイスを渡せばよいのでしょうか。1つの方法は、特定のデバイスを引数として渡して新しいコンテナを作成することです。他に良い方法はありますか?

  • あまり詳しくありませんが、USB-IP(ホストOSがサポートしていれば)が最善の策かもしれません。デバッグには、ネットワークプロトコルを使うので、コンテナ境界を越えてgdbをOpenOCDに接続できます。以下のように、Zephyr入門の動画で説明されています。

存在しないセンサのチャンネルを追加することはできますか?

  • そのためにはZephyrのソースコードを修正する必要があります。最も良い方法は、既存のチャンネルを使うか、Zephyrの「device」型に準拠した独自のセンサのようなインターフェースを作成することです。

HiFive1 Rev Bを使っていますが、私のボードでもあなたが説明した手順で同じように実行できますか?

  • 私はそのボードを使用した経験がありません。以下のZephyrの公式ドキュメントは良い出発点になると思います。

https://docs.zephyrproject.org/latest/boards/sifive/hifive1/doc/index.html

この変換は負の温度を適切に扱えますか?(val1が負でval2が正だと思います。)

  • 私はこれをテストしていませんが、理論的には符号ビットを見ているため、負の温度を扱えるはずです。もし動作しなかったら、GitHubリポジトリにissueかpull requestを提出してください。

Zephyrのバージョンを管理する最良の方法は何でしょうか?たとえばGitHubのタグを使うのでしょうか。通常はどのようにバージョンを設定しますか?

  • 個人的には、Dockerfileの中でバージョンを固定するのが好きです。教育用コンテンツを作りやすくなります。Zephyrは、プロジェクト内でバージョンや依存関係(Zephyr自身を含む)を扱うために 「west」メタツールを使うことを推奨しています。westについての詳細はドキュメントを参照してください。

どのような種類のGUIをZephyrに統合できますか?

Node MCUの.dtsiファイルをここで見つけることはできますか?

  • Node MCUボードはZephyrによって公式にサポートされていないと思われるため、そこに .dtsiファイルは見つからないでしょう。ESP8266はサポートされています(ESP-8266 Modules — Zephyr Project Documentation)。そのため、Node MCUボードをサポートするには、カスタムボード定義モジュールを作成する必要があります。ボードの移植については、以下を参照してください。

このドライバの検索は、実行時に行われるのですか、それともビルド時に行われるのですか?

  • Zephyrのビルドシステムが「compatible」文字列を照合して、ドライバのソースコードをDevicetreeと対応付ける処理のことに言及されているのだと思います。その場合、この処理はビルド時に行われます。

モジュールはSDKの一部ですか?

  • ご質問の意味がよくわかりません。SDKとはツール、ライブラリ、ドキュメント、サンプルなどの集合体のことです。この定義で言えば、Zephyr自体もSDKと見なすことができます。モジュール自体は、Zephyrが理解できる方法で記述されたソフトウェアライブラリにすぎません。もし、そのライブラリと一緒に他のツール、ドキュメント、サンプルなどを作成した場合、そのモジュール(またはモジュールの集合体)はSDKの一部と考えることができます。

別のデバイスドライバに依存し、コールを行うデバイスドライバを書くことはできますか?

  • はい。この例では、I2Cドライバを使用する(に依存する)ドライバを書きました。参考になれば幸いです。

つまり、i2c_write/read_dtはESPのベアメタルドライバにリンクされてるという理解で正しいですか?

  • はい。drivers/i2cフォルダ(zephyr/drivers/i2c at main · zephyrproject-rtos/zephyr · GitHub)を見ると、抽象化されたI2Cヘルパ関数(i2c_write/readなど)がどのようにハードウェアへ接続されているかが分かります。各ベンダー(または、ベンダーをまとめているZephyrプロジェクトの代表者)が、それぞれのハードウェアドライバを管理しています。Devicetreeでは(特定のボード/チップとI2Cバスに接続した場合)、高レベルのi2c_write関数が、そのチップ向けの特定の低レベルドライバ関数(i2c_esp32_transmit()など)を呼び出すように指定されます。参考になれば幸いです。

最新の組み込みC++のチュートリアルシリーズをリリースすることは考えていますか?

  • 現時点では予定していませんが、いずれ取り組みたいとは思っています。組み込みの分野ではC++とRustの人気が高まっており、将来的には扱いたいテーマです。

このドライバファイルmcp9808.hのドライバコードを書く場合、レジスタレベルまでコードを書くためにデータシートを読み込む必要がありますか?それとも、Zephyrが提供する書き方に従ってZephyrのドライバを書き、espressifが提供するAPIを呼び出すのでしょうか?

  • データシートを確認する必要があります。デバイスアドレス、レジスタアドレス、読み書きするビット、実装したい機能などを把握するためです。

そのZephyrビルドは最終的にどれくらいのサイズになりましたか?

  • 138kBです。単なるI2C読み取りデモとしてはかなり大きいサイズです。Zephyrは多くのオーバーヘッドを必要とします。

Zephyrは JESD204C ドライバを実装し、サポートしていますか?

  • 私の知る限り、Zephyrは JESD204C を実装していません。

Zephyrは、LinuxのFileOpsの構造に似ていますか?

  • はい。ZephyrとLinuxの間には多くの類似点があることに気づくと思います。

このファイル構造をテンプレート化することは可能ですか?

  • Zephyrにそのようなテンプレート機能が組み込まれているとは思いませんが、簡単なBashスクリプトやPythonスクリプトで実現できるはずです。

Zephyrで動的Cライブラリを呼び出すにはどうすればよいですか?例えば、センサデータ解析用の独自のAIモジュール(C/C++)があるのですが。

  • KconfigでCONFIG_CPPを有効にする必要があります。これにより、Zephyrがあなたのライブラリをビルドし、リンクできるようになります。詳細は以下のページを参照してください。

メイン関数の中で、各種類のデバイスをチェックするのは一般的なパターンですか?

  • はい。各デバイスがデバイスツリーに存在することを確認し(ヌルポインタチェック)、そのうえで、ブート時にカーネル内で初期化されていること(つまり、device_is_ready() 関数を使用)を確認するのは一般的なやり方です。

クリックして問題のタブに戻ってもらえますか?

  • ライブセッション中にこれに気づかず申し訳ありません。表示されていた問題は、ビルドプロセス中に確かに利用可能であったにもかかわらず、IntelliSenseが一部のシンボル(CONFIG_SYS_CLOCK_TICKS_PER_SECなど)を検出できなかったというものでした。IntelliSenseは 100%完全には動作しませんが、私が行っているZephyrの開発や講習には十分なレベルで動作します。

この簡単なコードで50KBのRAMを使っています…バックグラウンドで相当いろいろ追加されていますね。

  • はい、そのとおりです。Zephyrには多くの抽象化があり、いくつかのタスク(workqueueやloggingなど)を管理するために、バックグラウンドで1つ以上のデフォルトスレッドを実行しています。

完成したZephyrプロジェクトを任されましたが、拡張できるようにする必要があります。Zephyrの学習曲線とプロジェクトの両方があり、全く途方に暮れています。これを拡張できるようになるには、どのように始めるのがよいでしょうか?

  • Zephyrの使い方をより深く理解するために、Zephyr入門シリーズのYouTubeをすべて注意深く視聴し、課題に実際に挑戦してみることをお勧めします。

  • その後、検討しているプロジェクトを構成要素ごとに分解していきます。mainはどこですか?どの関数を呼んでいますか?それらの関数をさらに掘り下げて、どのドライバが使われているのかを確認します。特定のボード向けにDevicetreeとKconfigがどのように設定されていますか?Zephyrプロジェクトは非常に複雑ですので、理解するのに数日から数週間かかることもありますが、時間をかけて中身を把握してください。コードの一部をChatGPTやClaudeにコピー&ペーストして、何をしているのか説明させることを恐れないでください。参考になれば幸いです。

デバッグにはVSを使うのがよいでしょうか?それともJLinkのEmbedded Studioのような別のIDEを使うべきでしょうか?

  • 最も使い慣れているIDEを使うのがよいと思います。私はVS Codeに慣れているので好きですし、人気もあるようです。Zephyrはデバッグにgdbを使用しており、OpenOCDとも問題なく連携します。多くのIDEはgdbを使ったステップ実行やメモリ参照などの設定が可能です。詳細は以下の動画を参照してください。

T3(Forestトポロジ)だけを使うのが良いですか?

  • 私は講習ではT3に近いものを使っていますが、プロセスを簡略化するため(受講者向けに)westをリポジトリ管理には使いませんでした。複数のプロジェクトがあり、Zephyrをほぼそのまま使いたい場合にはT3は非常に優れた方法です(ワークショップやYouTubeシリーズでもそうしました)。大規模で複雑なプロジェクトにはT1が推奨されているようです。

usbipdを使ってJLinkをDockerコンテナにパススルーできました。動かすまでに少し手間はかかりますが、コンテナからWest Flashを使えるようになります。Westは素晴らしいです!

  • それは素晴らしいですね!使える環境なら、私もWestで書き込むのが大好きです。ただ残念ながら、主要なOSすべてでUSBパススルーを設定することができていません(macOS は今も私を悩ませています)。この講習では、主要なOSすべてで動作する方法が必要なのです。

コンパイラはこれらの関数ポインタ呼び出しをインライン化できますか?それとも実行時に多くの間接呼び出しが発生しますか?

  • ドライバ内でAPI関数ポインタをAPI構造体に割り当てる場合、これらはインライン化できません。そのため、実行時には相当量の間接呼び出しが発生します。これは、Zephyrがより良い抽象化を提供するための残念なトレードオフの1つです。

Zephyrは初めてです。これだけ抽象化が多いと、リアルタイム性や決定性はどうなるのでしょうか?

  • 関数の間接呼び出しが必要になるため、多少のプロセッサのオーバーヘッドが発生します(特に API構造体に関数ポインタを割り当てる場合)。しかしZephyrは、まず第一にRTOSとして設計されており、多くの部分で決定性が保証されています。ただし、一部の機能(ネットワークスタック、ログ、動的メモリ割り当て、ワークキューなど)は決定的ではないことに注意してください。

あるファイルでは、「my-mcp9808」がハイフンで、別のファイルではアンダースコアで定義されているのはなぜですか?

  • これは、ZephyrがDevicetree内の文字列に一致させるために文字置換を行う方法を示すためのものでした(恐らく、ライブウェビナーで言い忘れたと思います)。これは「互換性のある」文字列/トークンの処理方法と似ています。Devicetreeのエイリアスラベルは慣例としてアンダースコアではなくハイフンを使います。しかしC言語のマクロトークンではハイフンが使えないため、Zephyrのビルドシステムは一致を探す際にハイフンをアンダースコアに置き換えることを理解しています。参考になれば幸いです。

driversがすでにMCP9808のサブフォルダなのに、driversの下にMCP9808フォルダを置く意味は何ですか?

  • これはZephyrのソースコードにおける慣例ですが、厳密に必須というわけではありません。公式Zephyrドライバを詳しく調べると、そのドライバのソースコードが<device_name>/drivers/<device_name>に保存されていることがよくあります。しかしZephyrがこれをチェックしているわけではありません。バインディングYAMLファイルが正しい場所にあり、最上位フォルダにzephyr/module.yamlがあり、CMakeLists.txtがドライバのターゲットソースを正しくビルドしていれば、ドライバは問題なくビルドされ、リンクされます。

ドライバのバインディングファイル名で、アンダースコアを使用せずにカンマを使うのはなぜですか?

  • これはLinux由来のZephyrの慣例で、compatibleの文字列は一般に「,」を使います。C(特にマクロトークン)での扱いをあまり考慮せずにこうなっています。アンダースコアを使っても動作するはずですが、Zephyrの慣例ではカンマが使われます。

この文脈でのマクロとは何ですか?

  • Cにおけるマクロとは、プリプロセッサ(コンパイラの前段)によって展開されるコード片のことです。通常は#ifや#defineなどが該当します。

つまり、CONFIG_NAME_MCP9809=yのように設定名をカスタムで付けられるということですか?

  • ほぼそのとおりです。Zephyrのビルドシステムに認識されるカスタムKconfigシンボルを、Kconfigファイル内で定義できます。ウェビナーの例では、modules/mcp9808/drivers/mcp9808/Kconfigに「MCP9808」というカスタムKconfigシンボルを定義しました。これにより、Cコード内でCONFIG_MCP9808としてその状態を参照できます。詳しくはIntro to ZephyrシリーズのKconfig Tutorialを参照することをお勧めします。

I2Cを割り込みで使うことはできますか。

  • はい。これは通常、Devicetreeで設定され、I2C(または他のペリフェラル)が割り込みコントローラに接続されます。多くのボードやマイクロコントローラのベンダーは、(ポーリングではなく)割り込みをデフォルトで使うように設定しています。ESP32S3のI2C0ノードがデフォルトで割り込みコントローラ(intc)を使用するように設定されている様子は、以下で確認できます。

アプリケーションはどのように整理しますか?すべてのアプリを1つのワークスペースにまとめますか、それとも複数のワークスペースに分けますか?

  • これには複数の方法があります。Zephyrでは、扱っているものが大規模で複雑であることを前提に、一般的に「アプリケーションごとに1つのワークスペース」を推奨しています。これはZephyrのドキュメントで「T1」トポロジと呼ばれています。私は講習では「T3」トポロジに近いもの(多数の小さなプロジェクトを1つのワークスペースにまとめる形)を使っています。これらの推奨されるワークスペーストポロジの詳細については、以下をご覧ください。

依存しているなら「select I2C」を使わないのはなぜですか?この方法ではなく、別の方法を使う理由があるのですか?

  • そのとおりです。「select I2C」を使うほうが、自動的にI2Cを有効にできるので、おそらくより良い方法です。私はただ、依存関係がどのように動作するか(特にmenuconfigにおいて、時間があれば)を示したかったのです。

ZephyrのUSBスタックについてどう思いますか?同じUSBラインで複数のクラスを実装するためのドキュメントはあまりないように思います。TinyUSBを統合することを勧めますか?

  • 正直なところ、私はZephyrのUSBスタックを扱ったことがないので、あまりコメントできません。ただ、ほとんどのUSBクラスはサポートされているようです。

本日のテーマではないかもしれませんが、ztestフレームワークについて簡単に説明してもらえますか?何のために、どのように使うのかなどです。作成したファームウェアをテストすることで、Zephyrの学習をさらに深めることはできますか?

  • 正直なところ、私はztestを使った経験がありません。将来的には取り上げたいと思っていますが、今回のワークショップやシリーズではZephyrのテストフレームワークを深く扱う時間がありませんでした。一般的には、実際に製品に使う予定のコードについてはテスト駆動開発を行うことをお勧めします。コードを書く手間は増えますが、現場で問題が起きるずっと前にバグを見つけることができます。私が知っている範囲では、ユニットテストをztestで書き、テスト実行はtwisterツールで管理することになると思います。

ディレクトリツリーにMCP9808が2回出てくる必要があるのはなぜですか?親ディレクトリで暗黙されていないのですか?

  • これはZephyrのソースコードにおける慣例ですが、厳密に必須というわけではありません。公式Zephyrドライバを詳しく調べると、そのドライバのソースコードが<device_name>/drivers/<device_name>に保存されていることがよくあります。しかしZephyrがこれをチェックしているわけではありません。バインディングYAMLファイルが正しい場所にあり、最上位フォルダにzephyr/module.yamlがあり、CMakeLists.txtがドライバのターゲットソースを正しくビルドしていれば、ドライバは問題なくビルドされ、リンクされます。

センサ読み取りでfetchとgetを分ける利点は何ですか?なぜそれをアプリケーション側に隠さず、公開する必要があるのでしょうか?

  • 正直なところ、Zephyrチームがfetchとgetを分けた正確な理由は分かりません。私の推測では、これらの呼び出しを別々のスレッド/タスクに分けられるようにするためだと思います。つまり、fetchをメインアプリケーションコードの外側で実行でき、アプリケーション側は最新の読み取り値を取得するだけで済みます(長いI2Cの書き込み/読み取りサイクルを待つ必要がありません)。

sensor_channel列挙型に含まれていない特殊なセンサを使う場合でも、「sensor」テンプレートを使うべきでしょうか?

  • 必ずしも使う必要はありません。汎用的な「デバイス」型/インターフェースで十分でしょう。「センサ」インターフェースには、get/fetchの分離や、データの保存場所が事前に設定されているなどの利点があります。また、sensor_channel列挙型からプレースホルダチャンネルを使用することもできます。これで動作するはずです。

スーパーループは、ループを回るごとに全デバイスを呼び出すのですか?

  • そうではありません。Zephyrのビルドプロセスは、ドライバコードの末尾にある巨大なマクロを展開し、Devicetree内の各インスタンスに対してドライバ構造体のコピーを作成します。開発者であるあなたは、必要であれば各デバイスに対してget/fetch関数を呼び出すことができます。

このスーパーループはその1つのアドレスしか呼び出しません。他のデバイスは別のアドレスなので、スーパーループに追加が必要ということですか?

  • はい、そのとおりです。

sensorインターフェースが提供する範囲を超えて、デバイス固有の機能とやり取りしたい場合、sensorインターフェースを回避したやり取りのために参考になるチュートリアルはありますか?

  • YouTubeシリーズの「How to Write a Device Driver」では「sensor」型ではなく、汎用的な「device」型のシンプルなドライバを作成する方法が示されています。

そのファイル階層がこのようになっている理由について、良い参考資料はありますか?その領域ではnode_idとinstの項目を区別する価値があります。両者を行き来できることは非常に重要です。

  • フォルダ構造の背後にある「理由」について明確に説明しているものは見たことがありませんが、以下の資料はドライバをゼロから作成する際に役立つはずです。

このワークショップで、どのようにZephyrがクロスプラットフォームコードを書くかについて質問しても良いですか?プリプロセッサディレクティブを使って実現されるのですか?

  • Zephyrでクロスプラットフォームアプリケーションを作成する方法はいくつかあります。1つは、多くのプリプロセッサディレクティブを使う方法です(ドライバコードで見たように)。もう1つはDevicetreeを使う方法です。ビルドシステムに特定のボードを引数として渡すと、Zephyrはアプリケーション用(.overlay)、ボード用(.dtsと.dtsi)、コントローラ用(通常 .dtsi)のコンパイル済みDevicetreeを生成します。これは、そのボードとコントローラで使用したいすべてのハードウェアペリフェラルの一覧です。Zephyrはその情報をもとに、対応するペリフェラルを扱うために必要なソースコード(ESP32のI2Cドライバなど)を探します。共通インターフェース(fetch/getで見たもの)や関数ポインタを使うことで、Zephyrはそのドライバコードに対応する適切な関数を呼び出します。もしDevicetreeを変更して別のボード(STM32 Nucleoなど)を使うように指定すれば、ビルド時にZephyrはSTM32のI2Cドライバを探し、I2C関数はSTが提供するドライバ関数を指すようになります。多くの場合、ハードウェアのベンダーがこの下層ドライバコードを提供するため、開発者であるあなたが気にする必要はありません。理想としては、アプリケーションコードと、未サポートのハードウェア(センサなど)のための必要なドライバに集中できるようにすることです。これにより、Devicetree設定を変更した .overlayファイルを用意するだけで、別のマイクロコントローラのベンダーへ容易に移行できます。参考になれば幸いです。

「zephyr/drivers/sensor.h」はユーザー定義のヘッダファイルなので、<> ではなく “” で囲むべきではないですか?

  • そのincludeディレクティブで「zephyr」から始まるものは、公式のZephyrソースコードの一部です。そのため、それらは「システム」検索パス(つまり、プロジェクトやモジュール内ではなく、公式のZephyrのincludeから取得される)に属すると判断し、引用符 " " ではなく < > を使いました。公式の Zephyrアプリでも同じようにしている例が見られます。

紹介ウェビナーリンク

このウェビナーについてさらに情報が必要な場合は、以下の詳細をご確認ください。

Martin LampacherによるZephyr Devicetreeに関するMemfaultシリーズ

Zephyr公式ドキュメント:ビルドシステム(CMake)

Zephyr公式ドキュメント:Devicetreeバインディング

Gerard Marull Paretas によるMastering Zephyr Driver Development

DigiKeyのウェビナーセンター

DigiKeyのTechForumのウェビナーポスト




オリジナル・ソース(English)