WebAssembly Micro RuntimeでRustのアプリを動かす!
はじめに
WebAssembly Micro Runtimeでは、64KB程度のRAMが搭載されたマイコンであれば、wasmアプリケーションを動かすことができます。
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]
アトリビュートを使用するため、std
のC言語の型定義が使えません。
そこで、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でのデモアプリが公開されています。
リファレンスが普段触っている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さんがヒントをくれました。
ば、倍プッシュは冗談ですからねっ!?ざわ…ざわ…
— nodamushi (@nodamushi) 2019年6月13日
私の所為で余計な調査をさせてしまったのなら申し訳ない。でも待ってます(オイ
15分程ざっくり眺めてみた限りですが、メモリのページが64KB単位になってるぽいです。つまり、128KBって2ページになりますね。0ページ目はVM専用とかと予想(?)
|> 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でのデモアプリが公開されています。
リファレンスが普段触っている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を搭載しているものですね…。
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_cycles
、pulse_cycles
を引数に与えます。
pulse_cycles / period_cycles
がデューティ比になります。
次は、PWM出力を停止する方法です。 ZephyrのPWM Driver APIは、PWMを停止する方法を提供していません。
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
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の問題っぽいですね…。
どうもどこかのバージョンからバグったみたいですね…。
Zephyrのブートログが出力されていません。 上記設定では、printkが出力されていません。
UARTコンソールが有効になっているせいかと思いきや、そうでもないようです。
LOG_PRINTK
を設定しないとダメ?
お試して、LOG機能を有効化して、バックエンドをRTTにします。
ダメですね…。
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とログ、どちらかしか使えないってこと…?