みかんのゆるふわ技術ブログ

Raspberry PiやIoT関係のことを書き残していきます

Zephyr OSのサンプルプロジェクトの中身を見てみよう

注意: Zephyr OS v2.7ベースの記事です

前回の記事で、Zephyr OSの開発環境構築と、Seeeduino XIAO用にサンプルファームウェアをビルドしてみました。

www.mikan-tech.net

今回は、そのサンプルプロジェクトの中身に迫ります!👀

プロジェクトの中身

前回ビルドしたサンプルプロジェクト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()で指定できます。ここでは、appsrc/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などの謎のマクロ

しかし、LED0PINFLAGSという変なマクロはなんでしょう?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の機能をさらに解説していきます!

www.mikan-tech.net