注意: Zephyr OS v2.7ベースの記事です
前回の記事で、Zephyr OSの開発環境構築と、Seeeduino XIAO用にサンプルファームウェアをビルドしてみました。
今回は、そのサンプルプロジェクトの中身に迫ります!👀
プロジェクトの中身
前回ビルドしたサンプルプロジェクトbasic/blinky
の中身を改めて見てみましょう。
全体のソースツリーはこちら。
$ cd ~/zephyrproject/zephyr/samples/basic/blinky $ tree . ├── CMakeLists.txt ├── README.rst ├── prj.conf ├── sample.yaml └── src └── main.c
src/main.c
がプロジェクトのソースコードですが、それ以外にもいろいろなファイルがありますね。
このうち、README.rst
は説明書で、sample.yaml
はZephyr OSのテストフレームワークで使われるファイルです。
それ以外のファイルがZephyr OSのビルドに使われるファイルです。
以下のファイル構成がZephyr OSの一番単純なプロジェクトの構成になります。
- CMakeLists.txt
- prj.conf
- src/main.c
なんと3つだけでOK🙆
これらのファイルの中身と役割を見ていきましょう!😊
CMakeLists.txt
CMakeがビルドに使うファイルで、必須のファイルです。このファイルがビルドシステムのトップレベルになり、 Zephyr OSのCMakeビルドシステムと自分で作ったプロジェクトとを結びつけ、ファームウェアをビルドできるようにしてくれます。
CMakeLists.txt
の中身は次のようになっています。
cmake_minimum_required(VERSION 3.13.1) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(blinky) target_sources(app PRIVATE src/main.c)
find_package(Zephyr)
でZephyr OSのビルドシステムの機能を取り込みます。
Zephyr OSのビルドシステムではapp
というCMakeターゲット名が定義されており、このapp
にアプリケーションのソースコードを指定します。
ソースコードはCMakeのtarget_sources()
で指定できます。ここでは、app
にsrc/main.c
というファイルを指定していますね。
スペースや改行で区切って、複数のソースコードを指定することもできます。
最低限これだけ書けば、ファームウェアのビルドができるようになります🥰
CMakeに慣れていない人でも、プロジェクト名やファイル名だけ変更すれば、すぐにファームウェアがビルドできるようになりますよ✌️
Zephyr OSのCMakeビルドシステムについて詳しく知りたい方はこちら↓
https://docs.zephyrproject.org/latest/guides/zephyr_cmake_package.htmldocs.zephyrproject.org
prj.conf
Kconfigの設定ファイルで、Zephyr OSの機能を設定するファイルです。
Zephyr OSではLinux Kernelと同じく、Kconfigで様々な機能の有効/無効や設定を変更できます。 例えば、シェル機能を有効にするとか、プロトコルスタックの選択、I2Cなどのドライバの有効化などを行います。
prj.conf
の中身を見てみましょう。
CONFIG_GPIO=y
この1行だけです。GPIOドライバを有効にしています。 これだけで、ビルドのときにGPIOドライバも一緒にビルドしてくれます。
中身は1行ずつCONFIG_<symbol name>=<value>
という文法で指定します。
有効/無効の2値で設定する項目は、valueのところにy
(有効)かn
(無効)で設定します。
他に、数値や文字列で設定する項目もあります。
prj.conf
に書いた設定は自動でビルドシステムに読み込まれますが、menuconfigで手動で上書きもできます。
menuconfigはこのような画面でコンフィグレーションができます。
Kconfigについて詳しく知りたい方はこちら↓
https://docs.zephyrproject.org/latest/guides/kconfig/setting.htmldocs.zephyrproject.org
ソースコード
最後に、アプリケーションのソースコードです。これは普通のC言語のソースコードですね。
/* * Copyright (c) 2016 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include <zephyr.h> #include <device.h> #include <devicetree.h> #include <drivers/gpio.h> /* 1000 msec = 1 sec */ #define SLEEP_TIME_MS 1000 /* The devicetree node identifier for the "led0" alias. */ #define LED0_NODE DT_ALIAS(led0) #if DT_NODE_HAS_STATUS(LED0_NODE, okay) #define LED0 DT_GPIO_LABEL(LED0_NODE, gpios) #define PIN DT_GPIO_PIN(LED0_NODE, gpios) #define FLAGS DT_GPIO_FLAGS(LED0_NODE, gpios) #else /* A build error here means your board isn't set up to blink an LED. */ #error "Unsupported board: led0 devicetree alias is not defined" #define LED0 "" #define PIN 0 #define FLAGS 0 #endif void main(void) { const struct device *dev; bool led_is_on = true; int ret; dev = device_get_binding(LED0); if (dev == NULL) { return; } ret = gpio_pin_configure(dev, PIN, GPIO_OUTPUT_ACTIVE | FLAGS); if (ret < 0) { return; } while (1) { gpio_pin_set(dev, PIN, (int)led_is_on); led_is_on = !led_is_on; k_msleep(SLEEP_TIME_MS); } }
LEDを点滅させるだけですし、main()
関数だけのシンプルなプログラムです。
Zephyr OSのドライバを使うには、device_get_binding()
で使いたいドライバの構造体を取得して使います。
ここではLED0のドライバ構造体を取得しています。
これをGPIO APIのgpio_pin_configure()
に渡して、PINというマクロも使ってGPIOピンを出力に設定しています。
その後のwhile(1)ループでgpio_pin_set()
でLED0をON/OFF交互に設定し、
k_msleep()
というZephyr OSのスリープAPIで1秒のウェイトを入れています。
単純なアプリですが、ドライバの使い方に慣れるまで、マニュアルとにらめっこになりそうですですね😅
なお、gpio_pin_set()
などZephyr OSのGPIOドライバのAPIを使うには、先ほどのprj.conf
でGPIOドライバを有効にしておく必要があります。
LED0などの謎のマクロ
しかし、LED0
、PIN
、FLAGS
という変なマクロはなんでしょう?DT_GPIO_PIN()などのAPIが使われているようですが…🤔
これらはデバイスツリーの機能を利用しているもので、例えば
#define PIN DT_GPIO_PIN(LED0_NODE, gpios)
とありますが、このDT_GPIO_PIN()
でLED0のピン番号が取得できます。
このDT_xxxx
というマクロがZephyr OSのデバイスツリー関連のAPIとなっています。
この仕組みはLinux Kernel由来のものですが、なかなか独特です。 せっかくなので、デバイスツリーについても紹介しておきます。
デバイスツリーとは?
デバイスツリーはハードウェアの構成を定義するファイルです。 例えばこの開発ボードはGPIOとI2CとSPIがあって、ピン設定はこうなっていて…というようなものをDTS形式で記述します。
Seeeduino XIAOのボードDTSファイルを見てみましょう。長いので一部を抜粋しています。 全体はこちら。
/ { model = "Seeeduino XIAO"; compatible = "seeedstudio,seeeduino-xiao"; chosen { zephyr,console = &sercom4; zephyr,shell-uart = &sercom4; zephyr,sram = &sram0; zephyr,flash = &flash0; zephyr,code-partition = &code_partition; }; leds { compatible = "gpio-leds"; led: led_0 { gpios = <&porta 17 0>; label = "LED"; }; rx_led: led_1 { gpios = <&porta 18 0>; label = "RX_LED"; }; tx_led: led_2 { gpios = <&porta 19 0>; label = "TX_LED"; }; }; /* These aliases are provided for compatibility with samples */ aliases { led0 = &led; led1 = &rx_led; led2 = &tx_led; }; };
Seeeduino XIAOは基板上に4つのLEDが搭載されていて、うち1つは電源LED、あとの3つがマイコンから制御できるLEDです。 ここでは、3つのLEDが定義されていて、どのGPIOに繋がっているかなどの情報が記述されています。
Seeeduino XIAOのDTSだけでなく、マイコン(SAMD21)のDTS、CPU(Cortex-M0+)のDTSなどもあります。 自作のアプリケーションのプロジェクトにDTSファイルを含め、設定をオーバーライドすることもできます。 ビルドの時に全てがマージされて最終的なDTSが生成され、C言語からアクセスできるようにヘッダファイルも自動で作ってくれます。
これにより、例えばled0
というエイリアス名が定義されているボードなら、
マイコンの違いやGPIOのポートの違いなどを気にせず、先ほどのサンプルアプリを動かせます。
この仕組みはLinux Kernel由来のものですが、マイコンの開発という特性に合わせて大きく改変されており、LinuxとはAPIなど多くの点で違いがありますのでご注意ください。
ビルドしてみよう
ビルドするのは簡単です。
$ cd ~/zephyrproject/zephyr/samples/basic/blinky $ west build -b seeeduino_xiao
プロジェクトフォルダに入り、west build
でビルドできます。-b seeeduino_xiao
でターゲットボードを指定します。
ファームウェアの書き換えは、Seeeduino XIAOを書き換えモードにしてから
$ west flash
とするだけ。便利ですね🥰
ファームウェアや中間ファイルはプロジェクトフォルダ内にbuild/
というフォルダができて、そこに出力されます。
マージされた最終的なDTSファイルなどもここにできています。
主な生成物を紹介しておきます。
build/zephyr/.config
- Kconfigの最終的なマージ結果build/zephyr/zephyr.dts
- DTSファイルの最終的なマージ結果build/zephyr/zephyr.bin
- ファームウェア(バイナリ)build/zephyr/zephyr.elf
- ファームウェア(ELF形式)build/zephyr/zephyr.stat
- ELFヘッダ情報、セクション情報build/zephyr/zephyr.map
- メモリマップbuild/zephyr/zephyr.lst
- ELFの逆アセンブル結果
これらはビルドシステムが自動で出力してくれます。デバッグの時などに便利ですね。
続いてはマイコンの他の機能を使った独自プロジェクトを作りつつ、Zephyr OSの機能をさらに解説していきます!