ぐっすり寝てると犬に噛まれて殺される。
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 にすれば止まるかと思いましたが無理です。
備考:WDT のタイムアウトのカウンタはとれない
WDT のカウンタがどれぐらいすすんだか? を調べたかったのですが、取得する方法がないようです。