必要なもの

arm-none-eabi-* とsrecord が必要。platformio を使ってるなら arm-none-eabi は ~/.platformio/packages/toolchain-gccarmnoneeabi/bin/ に入ってるので、パスを通すか Makefile を修正すればいい。

srecord は brew からインストールできる。

brew install srecord

エクスポートと make

"GCC (ARM Embedded)" でエクスポートする。

とりあえず make してみると、リンク以外はうまくいった。

vfprintf.c:(.text.__ssputs_r+0x46): undefined reference to `__wrap__malloc_r'
vfprintf.c:(.text.__ssputs_r+0x66): undefined reference to `__wrap__realloc_r'
vfprintf.c:(.text.__ssputs_r+0x72): undefined reference to `__wrap__free_r'

と言われてリンクできなかった。

Makefile のうち -Wl,--wrap,_malloc_r -Wl,--wrap,_free_r -Wl,--wrap,_realloc_r を消せばうまくいった。

Makefile は標準的な mbed とリンクしていることを想定していて、うまくいかないところがある。

mbed の代わりに mbed-dev をインポートしている場合以下の書きかえが必要

# SOFTDEVICE = mbed/TARGET_RBLAB_BLENANO/TARGET_NORDIC/TARGET_MCU_NRF51822/Lib/s130_nrf51822_1_0_0/s130_nrf51_1.0.0_softdevice.hex
SOFTDEVICE = ../mbed-dev/targets/hal/TARGET_NORDIC/TARGET_MCU_NRF51822/Lib/s130_nrf51822_1_0_0/s130_nrf51_1.0.0_softdevice.hex

これで、make merge すると .build/combined.hex ができる。これを書きこめば使える。

メモリを32kb使えるようにする

mbed のオンラインコンパイラは BLE Nano v1.5 に対応しておらず、常に RAM を 16kb でしか使えない。実際にアプリケーションで使えるのは6kb程度となっていて、おそろしくキツい。

エクスポートしたらやりたいほうだいなので、リンカスクリプトを以下にように書きかえる。

--- mbed-dev/targets/cmsis/TARGET_NORDIC/TARGET_MCU_NRF51822/TOOLCHAIN_GCC_ARM/TARGET_MCU_NRF51_16K_S130/NRF51822.ld	2016-09-01 13:30:12.000000000 +0900
+++ NRF51822.ld	2016-09-01 22:39:00.000000000 +0900
@@ -3,7 +3,7 @@
 MEMORY
 {
   FLASH (rx) : ORIGIN = 0x0001C000, LENGTH = 0x24000
-  RAM (rwx) :  ORIGIN = 0x20002800, LENGTH = 0x1800
+  RAM (rwx) :  ORIGIN = 0x20002800, LENGTH = 0x5800
 }
 
 OUTPUT_FORMAT ("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")

メモリ量チェック用に main の最初にこんなのを書いている。

	{
		const uint32_t reason = NRF_POWER->RESETREAS;
		NRF_POWER->RESETREAS = 0xffffffff; // clear reason
		// reset cause should be shown everytime
		serial.printf("init [%x] sp:%x\r\n", reason, GET_SP());
	}

以下にようになった

# LD スクリプト変更前
init [4] sp:20003fd0
# LD スクリプト変更後
init [4] sp:20007fd0

16kb のときメモリの最後は 0x20004000、32kb のときメモリの最後は 0x20008000 になるはずなので、main の最初の sp がこの値なら問題なく 32kb 使えているはず。

オンラインコンパイラでなんとかできないか

試行錯誤したけどダメだった。ARM は 0x00000000 から読んで MSP として初期化されるので、ここを書きかえれば初期スタックポインタの位置を移動できるはずだけど、生成された hex ファイルから該当部分だけを書きかえてもダメだった。

書きかえてブートさせてみると main までこないので、何か完全にハズれているらしい。リンカの段階でほかにも値を計算して書いてるのかもしれない。

  1. トップ
  2. tech
  3. BLE Nano のオンラインプロジェクトをエクスポートして GCC でコンパイルして RAM 32kB 使えるようにする

ぐっすり寝てると犬に噛まれて殺される。

BLE で接続を維持しつつ、waitForEvent ( sd_app_evt_wait() ) でイベントが起きるまで寝ているケースで、予期せず Watch Dog Timer が発動するという罠にひっかかったので共有いたします。

前提

  • SoftDevice を使って BLE 接続を確立している
  • sd_app_evt_wait() してアプリケーションイベントをずっと待っている
  • WDT を SLEEP 時には PAUSE になるように設定している
  • 途中でアプリケーションの割り込みが入らない (タイマーとかを使っていない)

Pause する設定というのは以下のようなことです。

NRF_WDT->CONFIG = WDT_CONFIG_SLEEP_Pause << WDT_CONFIG_SLEEP_Pos;

予期しないWDTのタイムアウト

SLEEP 時には WDT を止まるようにしていて、アプリケーションは sd_app_evt_wait() で寝ています。よってWDTは働かないことを期待しましたが、本来の WDT の設定時間よりも遥かに長い時間を経過したあと、WDT によりアプリケーションがリセットされました。

原因

結論からいうと sd_app_evt_wait() で寝ていても、SoftDevice は無線アクティビティのために極めて短時間ですが CPU を起こして活動しているため、WDT の設定の「SLEEP 中は PAUSE」の状況にあてはまらない時間が発生します。おかげで、長い時間をかけて本来のタイムアウトに近付き、WDT がタイムアウトします。

対策

アプリケーションレベルでも、十分に短い間隔で割り込みを発生させて、明示的に WDT をリセットする。

普通はメインループで WDT のリセットを書いてますから、単に sd_app_evt_wait() の直前にワンショットなタイマーをかけて一定時間後に割り込みをかければ、割り込み関数で何もしなくても目的を達成できます。


今回3秒のタイムアウトを設定していましたが、このケースで実際に WDT が発生するまでには10分〜30分以上の時間 (無線アクティビティの頻度による) かかりました。なのでかなり安全目にふって1分ごとに起きてWDTをリセットするようなコードにしたところ、問題が発生することはなくなりました。

ケースが限定されていますが非常に見つけにくいバグだと思いました。悩みすぎて死ぬかと思った。

備考:WDT は一度動くと止められない

一度 TASKS_START = 1 すると止められません。STOP するタスクはありません。

RREN を一時的に 0 にすれば止まるかと思いましたが無理です。

The watchdog must be configured before it is started. After it is started, the watchdog’s configuration registers, which comprises registers CRV, RREN, and CONFIG, will be blocked for further configuration.

備考:WDT のタイムアウトのカウンタはとれない

WDT のカウンタがどれぐらいすすんだか? を調べたかったのですが、取得する方法がないようです。

  1. トップ
  2. tech
  3. BLE Nano (nRF51822)、waitForEvent ( sd_app_evt_wait() ) 中に予期せず WDT が発動する場合