WebAssembly Micro RuntimeでRustのアプリを動かす!

はじめに

WebAssembly Micro Runtimeでは、64KB程度のRAMが搭載されたマイコンであれば、wasmアプリケーションを動かすことができます。

github.com

Rustはwasmをターゲットにビルドできます。

後は、わかるね?

アプリケーションの作り方

WebAssembly Micro Runtimeのサンプルコードを修正します。 下のようにおもむろにwasmのバイナリが埋め込まれているので、これを自作のバイナリに差し替えます。

|> src/test_wasm.h

unsigned char wasm_test_file[] = { 0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00,
        0x00, 0x00, 0x0D, 0x06, 0x64, 0x79, 0x6C, 0x69, 0x6E, 0x6B, 0xC0, 0x80,
        0x04, 0x04, 0x00, 0x00, 0x01, 0x13, 0x04, 0x60, 0x01, 0x7F, 0x00, 0x60,
// ...
        0x70, 0x69, 0x6E, 0x67, 0x55, 0x52, 0x4C, 0x0E, 0x61, 0x2E, 0x6F, 0x75,
        0x74, 0x2E, 0x77, 0x61, 0x73, 0x6D, 0x2E, 0x6D, 0x61, 0x70 };

将来的にはよりスマートな方法が必要ですが、当面は良いでしょう。

Rustツールチェイン

wasmのターゲットをインストールするだけです。

rustup target add wasm32-unknown-unknown

wasmバイナリ作成

cargo new hello_wasm --lib

|> Cargo.toml

[package]
name = "hello_wasm"
version = "0.1.0"
authors = ["tomoyuki-nakabayashi <tomo.wait.for.it.yuki@gmail.com>"]
edition = "2018"

[dependencies]
cty = "0.2.0"

[lib]
crate-type = ["cdylib"]

crate-typeはcdylibになるようです。 wasmバイナリのサイズを小さくするために、#![no_std]アトリビュートを使用するため、stdC言語の型定義が使えません。 そこで、ctyクレートを使って、C言語の型を表現します。

WebAssembly Micro Runtimeの方でprintfのbindingが用意されている(要確認)ので、一旦FFIを手動で用意して、呼び出します。

|> src/lib.rs

#![no_std]
use cty;

extern "C" {
    pub fn printf(fmt: *const cty::c_char, ...) -> cty::c_int;
}

#[no_mangle]
pub extern "C" fn main() -> i32 {
    unsafe {
        printf(b"Hello from Rust\0".as_ptr() as *const cty::c_char)
    }
}

use core::panic::PanicInfo;
#[panic_handler]
#[no_mangle]
pub extern "C" fn panic(_: &PanicInfo) -> ! {
    loop {}
}
cargo build --target wasm32-unknown-unknown --release

この時点で約300バイトと十分に小さいですが、念の為、wasm-stripを使ってさらにバイナリを小さくします。

$ ls -lha target/wasm32-unknown-unknown/release/
-rwxr-xr-x 2 tomoyuki tomoyuki  306 Jun 15 14:12 hello_wasm.wasm
wasm-strip target/wasm32-unknown-unknown/release/hello_wasm.wasm

strip後のサイズです。180バイトまで小さくなります。

$ ls -lha target/wasm32-unknown-unknown/release/
-rwxr-xr-x 2 tomoyuki tomoyuki  180 Jun 15 14:13 hello_wasm.wasm

wasm-objdumpを使って逆アセンブルすると、次のような感じです。

$ wasm-objdump -x target/wasm32-unknown-unknown/release/hello_wasm.wasm 

hello_wasm.wasm:        file format wasm 0x1

Section Details:

Type[3]:
 - type[0] (i32, i32) -> i32
 - type[1] () -> nil
 - type[2] () -> i32
Import[1]:
 - func[0] sig=0 <env.printf> <- env.printf
Function[2]:
 - func[1] sig=1
 - func[2] sig=2 <main>
Table[1]:
 - table[0] type=funcref initial=1 max=1
Memory[1]:
 - memory[0] pages: initial=17
Global[3]:
 - global[0] i32 mutable=1 - init i32=1048576
 - global[1] i32 mutable=0 <__heap_base> - init i32=1048592
 - global[2] i32 mutable=0 <__data_end> - init i32=1048592
Export[4]:
 - memory[0] -> "memory"
 - global[1] -> "__heap_base"
 - global[2] -> "__data_end"
 - func[2] <main> -> "main"
Code[2]:
 - func[1] size=2
 - func[2] size=16 <main>
Data[1]:
 - segment[0] size=16 - init i32=1048576
  - 0100000: 4865 6c6c 6f20 6672 6f6d 2052 7573 7400  Hello from Rust.

hexdumpを使って、適当にC言語に埋め込める形式を出力します。

$ hexdump wasm.wasm -e '16/1 "0x%02x, "' -e '"\n"'
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0e, 0x03, 0x60, 0x02, 0x7f, 0x7f, 0x01,
0x7f, 0x60, 0x00, 0x00, 0x60, 0x00, 0x01, 0x7f, 0x02, 0x0e, 0x01, 0x03, 0x65, 0x6e, 0x76, 0x06,
...

これを、WebAssembly Micro Runtimeのサンプルコードに埋め込みます。

|> src/test_wasm.h

unsigned char wasm_test_file[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0e, 0x03, 0x60, 0x02, 0x7f, 0x7f, 0x01,
0x7f, 0x60, 0x00, 0x00, 0x60, 0x00, 0x01, 0x7f, 0x02, 0x0e, 0x01, 0x03, 0x65, 0x6e, 0x76, 0x06,
// ...
};

実行

qemu_x86をターゲットに実行します。 OSはZephyrです。

$ ninja run
SeaBIOS (version rel-1.12.0-0-ga698c8995f-prebuilt.qemu.org)
Booting from ROM..***** Booting Zephyr OS zephyr-v1.14.0 *****
Hello from Rust

無事動きました! と言いたいところですが、少しごまかしが入っています。

data segmentの置き場所問題

どうもRustでwasmをターゲットにビルドすると、データセグメントが0x10_0000 (1MB)から始まる領域に置かれるようです。

$ wasm-objdump -x target/wasm32-unknown-unknown/release/hello_wasm.wasm 
...
Data[1]:
 - segment[0] size=16 - init i32=1048576
  - 0100000: 4865 6c6c 6f20 6672 6f6d 2052 7573 7400  Hello from Rust.

一方(?)、サンプルアプリケーションの方では、wasm用のヒープ領域を512KB確保しています。

|> src/main.c

static char global_heap_buf[512 * 1024] = { 0 };

この状態でアプリケーションを実行するとメモリ確保が失敗します。

Booting from ROM..***** Booting Zephyr OS zephyr-v1.14.0 *****
Instantiate memory failed: allocate memory failed.

どうもランタイムでは、データセグメントも含めて1つのメモリ領域に割り当てているように見えます。

データセグメントが1MBから始まるので、ヒープ領域を2MB確保すると、上述の通り、アプリケーションが動作します。 intelさんとこは8MBもRAMが載っていてようござんすねぇ

|> src/main.c

static char global_heap_buf[2048 * 1024] = { 0 };

ということで、後は、Rustでサンプルアプリケーションをビルドする際にデータセグメントのアドレスを指定できれば、 64KB程度のRAMが載ったマイコンボードで動きそうです!

WebAssembly Micro Runtimeお試し②

はじめに

少し前に、組込みで使えるWebAssembly Micro Runtimeが公開されました。 また、いつの間にかSTMでのデモアプリが公開されています。

github.com

リファレンスが普段触っているZephyrなので、少し動かしてみます。

前回のおさらい

試しに、qemu_cortex_m3をターゲットにビルドすると、RAM不足でビルドが失敗していました。

$ cmake -GNinja -DBOARD=qemu_cortex_m3 ..
$ ninja run
Memory region         Used Size  Region Size  %age Used
           FLASH:       69112 B       256 KB     26.36%
            SRAM:      535984 B        64 KB    817.85%
        IDT_LIST:         120 B         2 KB      5.86/opt/zephyr-sdk/arm-zephyr-eabi/bin/../lib/gcc/arm-zephyr-eabi/8.3.0/../../../../arm-zephyr-eabi/bin/ld: zephyr/zephyr_prebuilt.elf section `bss' will not fit in region `SRAM'
/opt/zephyr-sdk/arm-zephyr-eabi/bin/../lib/gcc/arm-zephyr-eabi/8.3.0/../../../../arm-zephyr-eabi/bin/ld: section .intList VMA [0000000020010000,0000000020010077] overlaps section bss VMA [0000000020000000,0000000020080354]
/opt/zephyr-sdk/arm-zephyr-eabi/bin/../lib/gcc/arm-zephyr-eabi/8.3.0/../../../../arm-zephyr-eabi/bin/ld: region `SRAM' overflowed by 470448 bytes
collect2: error: ld returned 1 exit status
%
ninja: build stopped: subcommand failed.

64KBくらいRAMがあれば動いて欲しいところなので、なんとか動かせないか試してみます。

利用RAMサイズを減らす

twitterで@nodamushiさんがヒントをくれました。

|> core/iwasm/runtime/vmcore-wasm/wasm.h

#define MaxMemoryPages 65536
#define MaxTableElems UINT32_MAX
#define NumBytesPerPage 65536
#define NumBytesPerPageLog2 16
#define MaxReturnValues 16

wasmに詳しくないので、ページの概念が何を表現しているのかわかりませんが、とりあえず1ページ4KBくらいでよくない?ということで、定義を修正します。

#define NumBytesPerPage 4096
#define NumBytesPerPageLog2 12

main.cで確保されるヒープサイズを32KBにします。

|> core/iwasm/products/zephyr/simple/src/main.c

static char global_heap_buf[32 * 1024] = { 0 };
$ ninja run
[9/14] Linking C executable zephyr/zephyr_prebuilt.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:       69112 B       256 KB     26.36%
            SRAM:       44464 B        64 KB     67.85%
        IDT_LIST:         120 B         2 KB      5.86%
[13/14] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-m3
qemu-system-arm: warning: nic stellaris_enet.0 has no peer
***** Booting Zephyr OS zephyr-v1.14.0 *****
Hello world!
buf ptr: 0x40000178
buf: 1234

お、動きますね! OS、アプリケーションとラインタイム合わせて、RAMの使用量は44KBくらいです。

ちなみに、global_heap_bufは26KBまで小さくしても動作します。 25KBからはRuntimeのインスタンス化に失敗します。 Runtimeに最低でも26KB程度は必要、ということでしょう。

memory_usage.txtに、RAM利用量について、次の記述がありました。

Current memory usage, take samples/littlevgl in Zephyr for example:
(1) WASM app binary:                        142K for littlevgl ui_app.wasm
(2) WASM app memory space:                  64K for littlevgl ui_app.wasm
(3) WASM app heap space:                    8K by default
(4) WASM app thread native stack:           4K by default
(5) WASM interpreter stack:                 8K by default
(6) WASM block address hash cache:          3K
(7) timer thread stack:                     4K
(8) sensor thread stack:                    4K
(9) touch screen thread stack:              4K
(10) others: vm, app mgr, queue, native lib: ~22K

Total memory usage: ~263K

(10) others: vm, app mgr, queue, native lib: ~22Kとなっているので、プラスαで何かがあって、26KBが最低ライン、というのは有り得そうです。 次回以降で、もう少し詳しく内部を見ていきましょう。

WebAssembly Micro Runtimeお試し①

はじめに

少し前に、組込みで使えるWebAssembly Micro Runtimeが公開されました。 また、いつの間にかSTMでのデモアプリが公開されています。

github.com

リファレンスが普段触っているZephyrなので、少し動かしてみます。

準備

ライブラリをインストールします。

sudo apt install lib32gcc-5-dev g++-multilib

ソースコードをビルドします。

git clone https://github.com/intel/wasm-micro-runtime.git
cd wasm-micro-runtime/core/iwasm/products/linux/
cmake
make

iwasmというバイナリができました。

$ ls
CMakeCache.txt  cmake_install.cmake  libiwasm.so  Makefile
CMakeFiles      iwasm                libvmlib.a

Zephyrで起動してみましょう。

cd core/iwasm/products/zephyr/simple
source <zephyr>/zephyr-env.sh
ln -s ../../../ iwasm
ln -s ../../../../shared-lib shared-lib
mkdir build && cd $_
cmake -GNinja -DBOARD=qemu_x86 ..

<iwasm_dir>core/iwasmディレクトリです。 <shared_lib_dir>wasm-micro-runtime/core/shared-libディレクトリです。

実行します。

$ ninja run
SeaBIOS (version rel-1.12.0-0-ga698c8995f-prebuilt.qemu.org)
Booting from ROM..***** Booting Zephyr OS zephyr-v1.14.0 *****
Hello world!
buf ptr: 0x40000180
buf: 1234

動くやん!お手軽ですねー。

次に、試しにqemu_cortex_m3にターゲットを移してみましょう。

$ cmake -GNinja -DBOARD=qemu_cortex_m3 ..
$ ninja run
Memory region         Used Size  Region Size  %age Used
           FLASH:       69112 B       256 KB     26.36%
            SRAM:      535984 B        64 KB    817.85%
        IDT_LIST:         120 B         2 KB      5.86/opt/zephyr-sdk/arm-zephyr-eabi/bin/../lib/gcc/arm-zephyr-eabi/8.3.0/../../../../arm-zephyr-eabi/bin/ld: zephyr/zephyr_prebuilt.elf section `bss' will not fit in region `SRAM'
/opt/zephyr-sdk/arm-zephyr-eabi/bin/../lib/gcc/arm-zephyr-eabi/8.3.0/../../../../arm-zephyr-eabi/bin/ld: section .intList VMA [0000000020010000,0000000020010077] overlaps section bss VMA [0000000020000000,0000000020080354]
/opt/zephyr-sdk/arm-zephyr-eabi/bin/../lib/gcc/arm-zephyr-eabi/8.3.0/../../../../arm-zephyr-eabi/bin/ld: region `SRAM' overflowed by 470448 bytes
collect2: error: ld returned 1 exit status
%
ninja: build stopped: subcommand failed.

SRAMのサイズが足りずにリンクで死んだ!!! なんでしょうこのSRAM使用量…。500KB超えてますが…。

STMのデモ

じゃあSTMで動いているデモは一体何なのでしょうか? ということで、デモアプリのREADMEを読んでみます。

|> wasm-micro-runtime/samples/littlevgl/README.md

Since ui_app incorporated LittlevGL source code, so it needs more RAM on the device to install the application. It is recommended that RAM SIZE greater than 512KB.

512KB以上のRAMが推奨環境! かなり高性能なマイコンを要求していますね…。

デモで使用しているマイコンNUCLEO-F767ZIという512KB RAMを搭載しているものですね…。

os.mbed.com

simpleデモアプリのソースコードを見る

希望を捨てずに、デモアプリで何をしているか、少し覗いてみましょう。 アプリで使うRAMサイズが減れば、動くかもしれないですし!

|> wasm-micro-runtime/core/iwasm/products/zephyr/simple/src/main.c

static char global_heap_buf[512 * 1024] = { 0 };

ん?きみぃ?何だいこの有無を言わさず512KB確保するstatic変数の定義は!

static char global_heap_buf[16 * 1024] = { 0 };

とりあえず、16KBくらいにしたろ。

$ ninja run
[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-m3
qemu-system-arm: warning: nic stellaris_enet.0 has no peer
***** Booting Zephyr OS zephyr-v1.14.0 *****
Instantiate memory failed: allocate memory failed.

うん。QEMU起動まではいきましたが、ランタイムで落ちてます。

|> wasm-micro-runtime/core/iwasm/products/zephyr/simple/iwasm/runtime/vmcore-wasm/wasm_runtime.c

static WASMMemoryInstance*
memory_instantiate(uint32 init_page_count, uint32 max_page_count,
                   uint32 addr_data_size, uint32 global_data_size,
                   uint32 heap_size,
                   char *error_buf, uint32 error_buf_size)
{
    WASMMemoryInstance *memory;
    uint32 total_size = offsetof(WASMMemoryInstance, base_addr) +
                        NumBytesPerPage * init_page_count +
                        addr_data_size + global_data_size;

    /* Allocate memory space, addr data and global data */
    if (!(memory = wasm_malloc(total_size))) {
        set_error_buf(error_buf, error_buf_size,
                      "Instantiate memory failed: allocate memory failed.");
        return NULL;
    }

この辺りかな? 少しデバッガで見ていますが、サクッと解決できなさそうなので、また次回。

ZephyrのI2C scannerサンプルを動かしてみる

はじめに

I2Cデバイスを使いたいので、手始めにZephyrのI2C scannerサンプルを動かしてみます。 ターゲットSoCはnRF52840です。

サンプルプロジェクトを確認

|> zephyr/samples/drivers/i2c_scanner

nRF52x系は、overlayする設定ファイルが用意されています。

|> overlay-nrf52.nrf

CONFIG_I2C_NRFX=y
CONFIG_I2C_0=y

今回は、I2C1を使うので、I2C1に変更しておきます。

CONFIG_I2C_NRFX=y
CONFIG_I2C_1=y

ソースコードをざっと確認します。

#include <errno.h>
#include <zephyr.h>
#include <misc/printk.h>
#include <device.h>
#include <i2c.h>

#ifdef ARDUINO_I2C_LABEL
#define I2C_DEV ARDUINO_I2C_LABEL
#else
#define I2C_DEV "I2C_1"
#endif

/**
 * @file This app scans I2C bus for any devices present
 */

void main(void)
{
    struct device *i2c_dev;

    printk("Starting i2c scanner...\n");

    i2c_dev = device_get_binding(I2C_DEV);
    if (!i2c_dev) {
        printk("I2C: Device driver not found.\n");
        return;
    }

    for (u8_t i = 4; i <= 0x77; i++) {
        struct i2c_msg msgs[1];
        u8_t dst;

        /* Send the address to read from */
        msgs[0].buf = &dst;
        msgs[0].len = 0U;
        msgs[0].flags = I2C_MSG_WRITE | I2C_MSG_STOP;

        if (i2c_transfer(i2c_dev, &msgs[0], 1, i) == 0) {
            printk("0x%2x FOUND\n", i);
        }
    }
}

全アドレス順番にreadしていって、値が読めたら、見つかった判定しています。 まぁこんなもんでしょう。

動作確認

動かしてみましょう。

Starting i2c scanner...
[00:00:10.125,366] <err> i2c_nrfx_twi: Error 195952641 occurred for message 0
[00:00:22.562,713] <err> i2c_nrfx_twi: Error 195952641 occurred for message 0
[00:00:25.425,384] <err> i2c_nrfx_twi: Error 195952641 occurred for message 0
[00:00:29.490,722] <err> i2c_nrfx_twi: Error 195952641 occurred for message 0
[00:00:31.948,394] <err> i2c_nrfx_twi: Error 195952641 occurred for message 0
[00:00:33.013,885] <err> i2c_nrfx_twi: Error 195952641 occurred for message 0
...
[00:01:22.722,534] <err> i2c_nrfx_twi: Error 195952641 occurred for message 0
--- 18 messages dropped ---
[00:01:22.725,891] <err> i2c_nrfx_twi: Error 195952641 occurred for message 0
...

ありゃ、途中でRTTのメッセージがdropしていますね。 I2Cのアドレスを一通り舐めていて、デバイスが見つからなかった時のエラーが多すぎる、ということなのでしょう。

一応見落としがないようにするため、GDBを使って手動でループを回します。

...
[00:00:14.157,165] <err> i2c_nrfx_twi: Error 195952641 occurred for message 0
0x51 FOUND
[00:00:14.360,198] <err> i2c_nrfx_twi: Error 195952641 occurred for message 0
...
[00:00:16.207,885] <err> i2c_nrfx_twi: Error 195952641 occurred for message 0
0x64 FOUND
[00:00:16.414,978] <err> i2c_nrfx_twi: Error 195952641 occurred for message 0
...

今、繋がっているI2Cデバイスのアドレスは、0x51と0x64なので、OKですね!

ZephyrでPWM

はじめに

ZephyrでPWMしてみます。 ターゲットSoCはnRF52840です。

Zephyrプロジェクト設定

Zephyr PWM driver

CONFIG_PWMを有効にします。 nRF52840のPWM0を利用するので、CONFIG_PWM_0を有効にします。

|> prj.conf

# PWM
CONFIG_PWM=y
CONFIG_PWM_0=y

device tree

nRF5xシリーズでは、ハードウェアPWMとソフトウェアPWMが利用できます。 今回は、ハードウェアPWMを利用します。

ハードウェアPWMでは、割り当てたGPIOにPWM信号を出力します。 今回、GPIO0の27番ピンにPWMオーディオデバイスが搭載されているので、device treeを次のようにします。

&pwm0 {
    status = "ok";
    ch0-pin = <27>;
};

参考までに、大本のdevice treeノードを掲載します。

|> zephyr/soc/arm/nordic/nrf52840.dtsi

     pwm0: pwm@4001c000 {
            compatible = "nordic,nrf-pwm";
            reg = <0x4001c000 0x1000>;
            interrupts = <28 1>;
            status = "disabled";
            label = "PWM_0";
        };

nRFの仕様書を見ると、PWMペリフェラルが4つ搭載されており、1つのPWMペリフェラルにつき4チャネル利用できます。 chx-invertedを設定すると、負論理になります。

&pwm0 {
    status = "ok";
    ch0-pin = <13>;
    ch0-inverted;
};

アプリケーション

適当に4kHz出力すれば鳴るでしょう、という安易な考えでアプリケーションを作ります。

#include <zephyr.h>
#include <misc/printk.h>
#include <device.h>
#include <pwm.h>

#define PWM_DRIVER DT_NORDIC_NRF_PWM_PWM_0_LABEL
#define PWM_CHANNEL DT_NORDIC_NRF_PWM_PWM0_CH0_PIN

/* in milli second */
#define PWM_4000_HZ    (MSEC_PER_SEC / 4U)

void main(void)
{
    struct device *pwm_dev;
    u32_t period = PWM_4000_HZ;

    printk("Start PWM buzzer.\n");

    pwm_dev = device_get_binding(PWM_DRIVER);
    if (!pwm_dev) {
        printk("Cannot find %s!\n", PWM_DRIVER);
        return;
    }

    if (pwm_pin_set_usec(pwm_dev, PWM_CHANNEL,
                    period, period / 2U)) {
        printk("pwm pin set fails\n");
        return;
    }
    k_sleep(MSEC_PER_SEC);

    printk("Stop PWM buzzer.\n");
    if (pwm_pin_set_usec(pwm_dev, PWM_CHANNEL,
                    period, 0)) {
        printk("pwm pin set fails\n");
        return;
    }
}

これでピーっと鳴ります。

PWM出力を開始するAPIは下記です。

pwm_pin_set_usec(pwm_dev, PWM_CHANNEL,  period, period / 2U)

Driverオブジェクト、channel、period_cyclespulse_cyclesを引数に与えます。 pulse_cycles / period_cyclesがデューティ比になります。

次は、PWM出力を停止する方法です。 ZephyrのPWM Driver APIは、PWMを停止する方法を提供していません。

github.com

Driverの実装を見ていると、デューティ比を0%にすると、GPIOピンをclearしてくれるようになっています。 (上記issueにもそう書いてあります)

ということで、次のpwm_pin_set_usecの呼び出しでPWM出力が停止します。

pwm_pin_set_usec(pwm_dev, PWM_CHANNEL, period, 0)

westを使ったZephyrプロジェクトの管理

はじめに

Zephyr 1.14からwestというメタツールが公式なツールになりました。 面倒ですがさらっと使っておきましょう。

ソースコードやドキュメントは、できるだけ1.14を参照するようにします。 Zephyr 1.14はLTSという位置づけで、2年間はセキュリティアップデートが保証されます。

Getting Started

docs.zephyrproject.org

SDKのバージョンが更新されており、古いSDKではビルドできないので、新しいものをインストールします。

wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.10.0/zephyr-sdk-0.10.0-setup.run
cd <sdk download directory>
sh zephyr-sdk-0.10.0-setup.run
$ cat ~/.zephyrrc 
export ZEPHYR_TOOLCHAIN_VARIANT=zephyr
export ZEPHYR_SDK_INSTALL_DIR="/opt/zephyr-sdk"

次に、westのインストールです。

pip3 install --user west

westを使ってZephyr(と関連ツール)レポジトリを取得します。

west init zephyrproject
cd zephyrproject
west update

$ ls
modules  net-tools  zephyr

Zephyrのversionを指定する場合は、init時に指定します。

west init -m https://github.com/zephyrproject-rtos/zephyr --mr v1.14.0 zephyrproject
cd zephyrproject
west update

ビルド

westを使ってビルドしてみます。

$ west -v build -b nrf52840_pca10056 samples/hello_world

$ ls build/
app             CMakeFiles           rules.ninja  zephyr_modules.txt
build.ninja     cmake_install.cmake  tinycbor
CMakeCache.txt  Kconfig.modules      zephyr

なるほど、これでビルドできるようです。

デフォルトのターゲットを設定することもできます。

west config build.board nrf52840_pca10056
$ west build samples/hello_world/
ERROR: this looks like a new or clean build, please provide --board
FATAL ERROR: refusing to proceed without --force due to above error

あれぇ〜、ダメじゃん?

デバッグ

JLinkデバッガのラッパーコマンドもあります。

west debug --runner jlink
west debugserver --runner jlink

ZephyrでRTTコンソール

はじめに

久々のZephyrです。

JLinkには、JTAG/SWD経由でシリアル入出力をする機能があります。 UARTを使ったコンソールも良いのですが、物理配線が増えたり、簡単にUARTを引き出せない場合、RTTが便利です。

Zephyrコンフィグ

NordicのZephyrフォークを参考に、必要そうなコンフィグを有効化していきます。

  • CONFIG_USE_SEGGER_RTT=y
  • CONFIG_SHELL_BACKEND_RTT=y

上の2つあたりが必須そうです。 次の内容で設定ファイルを作成します。

|> zephyr/samples/subsys/shell/shell_module/prj_minimal_rtt.conf

CONFIG_SHELL=y
CONFIG_KERNEL_SHELL=n
CONFIG_SHELL_BACKEND_SERIAL=n
CONFIG_OBJECT_TRACING=y
CONFIG_THREAD_MONITOR=y
CONFIG_INIT_STACKS=y
CONFIG_BOOT_BANNER=n
CONFIG_THREAD_NAME=y
CONFIG_LOG=n
CONFIG_SHELL_HISTORY=n
CONFIG_SHELL_STACK_SIZE=1024
CONFIG_SHELL_CMD_BUFF_SIZE=128
CONFIG_SHELL_WILDCARD=n
CONFIG_SHELL_HELP_ON_WRONG_ARGUMENT_COUNT=n
CONFIG_SHELL_STATS=n
CONFIG_SHELL_CMDS=n
CONFIG_CONSOLE=y

#enable RTT shell
CONFIG_USE_SEGGER_RTT=y
CONFIG_SHELL_BACKEND_RTT=y

ビルドしてターゲットボードに書き込みます。

mkdir build && cd $_
cmake -GNinja -DBOARD=nrf52840_pca10056 -DCONF_FILE="prj.conf prj_minimal_rtt.conf" ..
ninja flash

RTTクライアントを立ち上げます。 まず、JLinkの接続から。

JLinkExe -device nrf52840_xxaa -speed 4000 -if SWD
J-Link>connect

RTTクライアントを起動します。

$ JLinkRTTClient
###RTT Client: ************************************************************ 
###RTT Client: *               SEGGER Microcontroller GmbH                * 
###RTT Client: *   Solutions for real time microcontroller applications   * 
###RTT Client: ************************************************************ 
###RTT Client: *                                                          * 
###RTT Client: *       (c) 2012 - 2016  SEGGER Microcontroller GmbH       * 
###RTT Client: *                                                          * 
###RTT Client: *     www.segger.com     Support: support@segger.com       * 
###RTT Client: *                                                          * 
###RTT Client: ************************************************************ 
###RTT Client: *                                                          * 
###RTT Client: * SEGGER J-Link RTT Client   Compiled Mar 27 2019 17:12:05 * 
###RTT Client: *                                                          * 
###RTT Client: ************************************************************ 

###RTT Client: -----------------------------------------------
###RTT Client: Connecting to J-Link RTT Server via localhost:19021  Connected.


llss^?^?###RTT Client: Connection lost. Going to reconnect.
###RTT Client: Connecting to J-Link RTT Server via localhost:19021 .. Connected.
SEGGER J-Link V6.44d - Real time terminal output
J-Link OB-SAM3U128-V2-NordicSemi compiled Jan  7 2019 14:07:15 V1.0, SN=683516743
Process: JLinkExe



rtt:~$

Zephyrのshell rtt:~$が立ち上がっています。 Tabでの補完も効きます。

rtt:~$
  demo      device    dynamic   gpio      log_test  version

version情報を見てみましょう。

rtt:~$ vvveeerrrsssiiiooonnn


Zephyr version 1.13.99

あん?何故かキー入力に対して、3回文字が出力されます。 Zephyr側はコマンドを受け付けているので、RTTClientの問題っぽいですね…。

どうもどこかのバージョンからバグったみたいですね…。

forum.segger.com

Zephyrのブートログが出力されていません。 上記設定では、printkが出力されていません。

UARTコンソールが有効になっているせいかと思いきや、そうでもないようです。

github.com

LOG_PRINTKを設定しないとダメ? お試して、LOG機能を有効化して、バックエンドをRTTにします。

ダメですね…。

194.19.86.155

1.14のリリースノートで解決済みになっているようですが、1.14でもprintkでの出力は出ていないような…?

下のサンプルだと、printkが出力されることがわかりました。

|> zephyr/samples/subsys/logging/logger

Process: JLinkExe
***** Booting Zephyr OS zephyr-v1.14.0-1039-gc3ccbbbdc3ca *****
Module logging showcase.
[00:00:00.125,030] <inf> sample_module: log in test_module 11
[00:00:00.125,061] <inf> sample_module: Inline function.
Disabling logging in the sample_module module
Function called again but with logging disabled.
Instance level logging showcase.
[00:00:00.125,213] <inf> sample_instance.inst1: Inline call.
[00:00:00.125,213] <inf> sample_instance.inst1: counter_value: 0
[00:00:00.125,213] <wrn> sample_instance.inst1: Example of hexdump:
01 02 03 04             |....    
[00:00:00.125,213] <inf> sample_instance.inst2: Inline call.
[00:00:00.125,213] <inf> sample_instance.inst2: counter_value: 0
[00:00:00.125,244] <wrn> sample_instance.inst2: Example of hexdump:
01 02 03 04             |....    
Changing filter to warning on sample_instance.inst1 instance.
[00:00:00.125,305] <wrn> sample_instance.inst1: Example of hexdump:
01 0m

設定ファイルを見てみます。

$ cat prj_rtt.conf
CONFIG_LOG=y
CONFIG_LOG_RUNTIME_FILTERING=y
CONFIG_LOG_BUFFER_SIZE=2048
CONFIG_LOG_PRINTK=y
CONFIG_LOG_PROCESS_TRIGGER_THRESHOLD=0
CONFIG_LOG_BACKEND_RTT=y
CONFIG_USE_SEGGER_RTT=y
CONFIG_COVERAGE=n

ふむふむ。LOG_BACKEND_RTTを有効にするようですね。

では、これをshellの方の設定に継ぎ足して…

$ ninja
zephyr/subsys/shell/shell_rtt.c:7:
../../../../../include/toolchain/gcc.h:28:37: error: static assertion failed: "Conflicting log RTT backend enabled on the same channel"
 #define BUILD_ASSERT_MSG(EXPR, MSG) _Static_assert(EXPR, MSG)
                                     ^~~~~~~~~~~~~~
zephyr/subsys/shell/shell_rtt.c:12:1: note: in expansion of macro 'BUILD_ASSERT_MSG'
 BUILD_ASSERT_MSG(!(IS_ENABLED(CONFIG_LOG_BACKEND_RTT) &&
 ^~~~~~~~~~~~~~~~
[64/130] Building C object zephyr/CMak...phyr.dir/subsys/logging/log_core.c.obj
ninja: build stopped: subcommand failed.

ああん? まさか、shellとログ、どちらかしか使えないってこと…?