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

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

Zephyr OSで温湿度気圧センサーBME280の値を読む

前回に引き続き、Seeeduino XIAOにZephyr OSを搭載して遊んでみましょう。 前回はUSBシリアルに"Hello world!"を出力させてみました。

www.mikan-tech.net

今回は、Raspberry Piにもつないでみた温度・湿度・気圧の3つが測れるBME280という便利なセンサーをつないでみましょう☺️

www.mikan-tech.net

Zephyr OSにはBME280のドライバが含まれているので、簡単にセンサーの値が読めます🥰

サンプルプロジェクトもzephyr/samples/sensor/bme280に含まれています。これが参考になります。 しかし、このサンプルはZephyr OS v2.5.0では、Seeeduino XIAOでは動きません。 (後述の、I2C周りのボード設定をする必要があります)

なので、前回のHello world!を改造して作ってみます。 早速やってみましょう✊

プロジェクトフォルダの作成

前回は~/myprojects/hello-world-usbにプロジェクトを作りましたが、今回は~/myprojects/bme280にしましょう。

センサーの値はUSBシリアルに出力したいと思います。なので、前回のプロジェクトをベースにしましょう。 前回のプロジェクトをコピーします。

$ cp -r ~/myprojects/hello-world-usb ~/myprojects/bme280
$ cd ~/myprojects/bme280

もしbuildディレクトリが残っていたら、削除しておいてください🙅‍♂️

$ rm -rf ./build

BME280ドライバの組み込み

前回はUSBシリアルのドライバを組み込むのに、サンプルのprj.confからコピペしました。

今回は違う方法として、menuconfigを使ってみましょう。既存のドライバを簡単に組み込めるので便利です。

Linux Kernelのビルドでもおなじみの機能ですね😊

次のようにして、menuconfigを起動します。

$ west build -b seeeduino_xiao -t menuconfig

次のような画面が出ます。

このメニューで機能の有効無効や各種設定ができます。指定した基板(seeeduino_xiao)や、既存のprj.confの設定は自動で読み込まれます。 Board Selectionという項目はSeeeduino XIAOに、SoC/CPU/Configuration Selectionという項目は、Atmel SAMD21 MCU(Seeeduino XIAOに載っているマイコン)が選ばれています。

I2Cドライバの追加

それでは、ドライバを追加しましょう。上下キーでDevice Driversという項目を選び、Enterキーを押します。

ドライバの選択画面に移りました。今回はBME280をI2Cでつなぎます。なので、I2CドライバとBME280ドライバの2つを有効にします。 デフォルトではどちらも無効になっています。

上下キーで、I2C Driversを選び、スペースキーを押すとチェックが入り、有効になります。メニューの右側が--->になっている項目は、詳細設定画面があります。Enterで移動できます。

このように、I2C関連の設定ができます。今はSAM0 Series I2C SERCOM driverというものが選択されていますね。 これはSeeeduino XIAO搭載のマイコンSAMD21のI2Cドライバのことです。

今回は特に変更する箇所は無いので前の画面に戻ります。ESCキーで1つ前の画面に戻ります。

スポンサーリンク

BME280ドライバの追加

続いて、BME280を有効にします。実はBME280はセンサードライバの1種なので、まずはセンサードライバを有効にします。Sensor Driversを選びスペースキーでチェックをした後、Enterキーで詳細画面に入ります。

BME280/BMP280 sensorを選び、スペースキーでチェックマークを付けます。これで、BME280ドライバが有効になりました。 Enterキーを押すと、詳細設定ができます。

このような項目があります。 例えば、BME280には、各種センサーの値を1回でなく、2回や16回など繰り返しサンプリングしその平均を取得することで精度を高める機能があります。 これらの設定を変更できます。

特にいじる必要はないので、そのままESCキーで戻ります。

設定の保存

menuconfigで変更した設定は、Sキーで保存ができます。保存先が選べますが、デフォルトの場所 build/zephyr/.config にしておくとビルド時に読み込まれますので、そのまま保存しましょう。

これで保存した設定は、buildディレクトリの中に保存されます。buildディレクトリを消して再ビルドした場合などは、設定も消えてしまいます。

.configファイルの一部だけ抜粋しますが、次のようにBME280ドライバが有効になっていることがわかります。

# CONFIG_AMG88XX is not set
# CONFIG_AMS_IAQ_CORE is not set
# CONFIG_BMA280 is not set
# CONFIG_BMC150_MAGN is not set
CONFIG_BME280=y
CONFIG_BME280_MODE_NORMAL=y
# CONFIG_BME280_MODE_FORCED is not set
# CONFIG_BME280_TEMP_OVER_1X is not set
CONFIG_BME280_TEMP_OVER_2X=y
# CONFIG_BME280_TEMP_OVER_4X is not set
# CONFIG_BME280_TEMP_OVER_8X is not set
# CONFIG_BME280_TEMP_OVER_16X is not set

設定を永続化するには、build/zephyr/.configprj.congに上書きコピーします。しかし、デフォルト設定も含め全設定が書き込まれるので、1000行近くあって冗長ですしわかりにくいです😥

デフォルトからの変更点のみ保存するには、menuconfig画面でDキーを押します。build/zephyr/kconfig/defconfigに保存されますので、このファイルをprj.confに上書きするとよいでしょう☺️

$ cp build/zephyr/kconfig/defconfig prj.conf

prj.confは次のようになりました。

CONFIG_I2C=y
CONFIG_SERIAL=y
CONFIG_SOC_SERIES_SAMD21=y
CONFIG_SOC_ATMEL_SAMD_XOSC32K=y
CONFIG_SOC_PART_NUMBER_SAMD21G18A=y
CONFIG_UART_CONSOLE_ON_DEV_NAME="CDC_ACM_0"
CONFIG_CONSOLE=y
CONFIG_USB_UART_CONSOLE=y
CONFIG_UART_LINE_CTRL=y
CONFIG_PINMUX=y
CONFIG_SENSOR=y
CONFIG_BME280=y
CONFIG_USB=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_PRODUCT="Zephyr USB console sample"
CONFIG_BOOTLOADER_BOSSA=y
CONFIG_BOOTLOADER_BOSSA_ADAFRUIT_UF2=y

もとのprj.confの設定と新しい設定がミックスされて、いい感じになっていますね。

スポンサーリンク

ボード設定ファイルの作成

DTSファイルの作成

BME280をどのポートにつなぐかは、Zephyr OSのDevice Treeの仕組みを使ってDTSファイルで指定します。

プロジェクトフォルダの中に、boardsフォルダの中に、<ボード名>.dtsという名前で作成すると、ビルド時に自動で読み込んでくれます。 今回の場合、myprojects/bme280/boards/seeeduino_xiao.dtsという名前になります。

画像出典: Seeeduino XIAO Wiki

Zephyr OS v2.5.0では、Seeeduino XIAOのI2Cはデフォルトで無効になっています。 ドライバがあるので簡単に有効にできるのですが、ボード設定ファイルでマイコンの設定をしなければなりません。

Seeeduino XIAOのI2Cピン(上図SDA、SCL)は、Seeeduino XIAOの回路図を見ると、 ATSAMD21のSERCOM2の端子であることがわかります。

SERCOM2というのは、Seeeduino XIAO搭載のマイコンATSAMD21の機能で、I2C、UART、SPIなどが使えるシリアル通信インターフェースです。

Seeeduino XIAOピン ATSAMD21ピン 機能
SDA PA8 SERCOM2 (PAD0)
SCL PA9 SERCOM2 (PAD1)

このSERCOM2にI2Cドライバを紐づけて、さらにBME280を接続したことを、DTS形式で記述します。 最初はわかりにくいと思いますが、次のようにします。

&sercom2 {
        status = "okay";
        compatible = "atmel,sam0-i2c";
        clock-frequency = <I2C_BITRATE_FAST>;

        bme280@76 {
                compatible = "bosch,bme280";
                reg = <0x76>;
                label = "BME280";
        };
};

compatible=で、どのドライバに紐づけるかを指定できます。また、BME280のI2Cアドレスをreg=で指定します。

このファイルをmyprojects/bme280/boards/seeeduino_xiao.dtsに保存しましょう。

このDevice Treeの記述方法はなかなか慣れませんが、サンプルが豊富なのと、ドキュメントもしっかりしているので、見よう見まねで頑張りましょう😅

ピンコンフィグファイルの作成

Seeeduino XIAOのI2Cはデフォルトで無効になっており、OS側ではピン設定もされていません。 ATSAMD21のSERCOM2を有効にするには、ピンの設定も手動でする必要があります。

ピン設定は、他のボードのファイルを参考にするとよいでしょう。 zephyr/boards/arm/seeeduino_xiao/pinmux.cのほか、zephyr/boards/arm/adafruit_feather_m0_basic_proto/pinmux.cが参考になります。

これらを参考に、myprojects/bme280/src/pinmux.cを以下の内容で作成します。

#if CONFIG_I2C_SAM0
#include <init.h>
#include <drivers/pinmux.h>
#include <soc.h>

static int bme280_pinmux_init(const struct device *dev)
{
    ARG_UNUSED(dev);

#if ATMEL_SAM0_DT_SERCOM_CHECK(2, atmel_sam0_i2c)
    const struct device *muxa =
        device_get_binding(DT_LABEL(DT_NODELABEL(pinmux_a)));

    /* SERCOM2 on SDA=PA08, SCL=PA09 */
    pinmux_pin_set(muxa, 8, PINMUX_FUNC_D);
    pinmux_pin_set(muxa, 9, PINMUX_FUNC_D);
#endif

    return 0;
}

SYS_INIT(bme280_pinmux_init, POST_KERNEL, CONFIG_PINMUX_INIT_PRIORITY);
#endif /* CONFIG_I2C_SAM0 */

pinmux_pin_set()でピンの機能を指定して、PA08、PA09をデフォルトのGPIOからSERCOM2に変更します。

最後のSYS_INIT()マクロを書いておくと、不思議なことに、Zephyr OSのビルドシステムが認識して、 main()関数が呼ばれる前に指定した関数を実行しておいてくれます。

このような便利な機能が備わっているのがZephyr OSの特徴です😊

スポンサーリンク

プログラムの作成

いよいよBME280を読み取るプログラムを作成しましょう。 とはいえ、ここはサンプルほぼそのままです。

#include <zephyr.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/sensor.h>

#include <usb/usb_device.h>
#include <drivers/uart.h>

#define BME280 DT_INST(0, bosch_bme280)

#if DT_NODE_HAS_STATUS(BME280, okay)
#define BME280_LABEL DT_LABEL(BME280)
#else
#error Your devicetree has no enabled nodes with compatible "bosch,bme280"
#define BME280_LABEL "<none>"
#endif

void main(void)
{
    const struct device *bme280 = device_get_binding(BME280_LABEL);
    const struct device *dev = device_get_binding(CONFIG_UART_CONSOLE_ON_DEV_NAME);
    uint32_t dtr = 0;

    if (usb_enable(NULL)) {
        return;
    }

    /* Poll if the DTR flag was set, optional */
    while (!dtr) {
        uart_line_ctrl_get(dev, UART_LINE_CTRL_DTR, &dtr);
    }

    if (bme280 == NULL) {
        printk("No device \"%s\" found; did initialization fail?\n",
               BME280_LABEL);
        return;
    } else {
        printk("Found device \"%s\"\n", BME280_LABEL);
    }

    while (1) {
        struct sensor_value temp, press, humidity;

        sensor_sample_fetch(bme280);
        sensor_channel_get(bme280, SENSOR_CHAN_AMBIENT_TEMP, &temp);
        sensor_channel_get(bme280, SENSOR_CHAN_PRESS, &press);
        sensor_channel_get(bme280, SENSOR_CHAN_HUMIDITY, &humidity);

        printk("temp: %d.%06d; press: %d.%06d; humidity: %d.%06d\n",
              temp.val1, temp.val2, press.val1, press.val2,
              humidity.val1, humidity.val2);

        k_sleep(K_SECONDS(5));
    }
}

BME280ドライバなどのセンサードライバは、どんなセンサーでも統一的なセンサーAPIでセンサー値の読み取りができます。 sensor_sample_fetch()でセンサーを実際に読み取り、sensor_channel_get()で読み取った値を取得できます。

BME280の場合、SENSOR_CHAN_AMBIENT_TEMPSENSOR_CHAN_PRESSSENSOR_CHAN_HUMIDITYの3つが取得できます。 それぞれ、温度、気圧、湿度です。

読み取った結果は、struct sensor_valueの構造体に格納されています。 BME280の場合、構造体のval1に整数部、val2に小数点部分が格納されているようです。

最後に、読み取ったセンサー値をprintk()で表示しています。

これを5秒ウェイトで繰り返しています。

センサーのAPIの使い方さえわかれば、それほど難しくないですね。 ドライバーを書く必要がないのが便利です☺️

CMakeLists.txt

最後に、CMakeLists.txtを用意しましょう。

これはほぼ前回のそのままですが、pinmux.cを追加でビルドする必要があるので追加します。 面倒なのでワイルドカードにしました。

cmake_minimum_required(VERSION 3.13.1)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(bme280)

FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})
スポンサーリンク

動かしてみよう

最終的に、プロジェクトフォルダmyprojects/bme280は次のようになりました。

$ tree
.
├── CMakeLists.txt
├── boards
│   └── seeeduino_xiao.overlay
├── prj.conf
└── src
    ├── main.c
    └── pinmux.c

プロジェクトの準備ができたら、早速ビルドしてみましょう。

$ west build -b seeeduino_xiao

(略)

-- west build: building application
[1/138] Preparing syscall dependency handling

[138/138] Linking C executable zephyr/zephyr.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:       25152 B       232 KB     10.59%
            SRAM:        9480 B        32 KB     28.93%
        IDT_LIST:          0 GB         2 KB      0.00%

ビルドが成功しました。USBシリアルとBME280ドライバを有効にしたくらいだと、FLASHは25KB弱と10%くらいしか使いません。

ファームウェアのバイナリは、build/zephyr/zephyr.binにできます。 これをSeeeduino XIAOに書き込んであげて、Seeeduino XIAOのUSB-Cポートをパソコンにつないでみましょう。

Windowsパソコンでも大丈夫です。自動的にドライバがインストールされ、COMポートとして認識されます。

Tera Termなどのソフトウェアを使って、認識されたCOMポートに接続してみましょう。

このように、USBシリアルでBME280のセンサー値が出力されています。気温が25.1度くらい、湿度が59.0%くらいでしょうか。 気圧は100.12などとなっていますが…これはキロパスカル(kPa)表記なのかな?🤔だいたい1001.2hPaくらいですね。 手元に気圧計がないですが、部屋に置いてある100均の温湿度計とは同じくらいになっています。

独特の作法が慣れないところですが、多様なドライバ、ファイルシステム、ネットワーク、マルチスレッドなどとても高機能な組み込みOSなので、慣れれば電子工作の幅が広がることは間違いないでしょう。ぜひ試してみてください😊