ncs: 致命エラーの取り扱い
2024/11/11
ncs は Zephyr OS を使っているが、組み込みアプリであることに変わりは無い。
Linux などであれば実行してエラーになるとコンソールに戻ったり、OS のエラーログに保存できたり、なんやかや補助が期待できる。
が、組み込みだとエラー時の挙動もコーディングが必要だろう。
どうしようもなかったらリセットするだろうけど、リセットを繰り返すようだったらどうするかも考えるだろう。
リセットするレベルじゃなくて設定が足りていないとかかもしれないし、ハードウェアに異常があるのかもしれない。
ただ開発中はまだそこまでやってられなくて、取りあえずエラーになったことがあからさまにわかるだけでよかったりする。
私だと LED を高速点滅させることが多い。
GPIO の操作くらいはできていることが多いからね。
で、そういう関数を作ったとして、呼び出すのにわざわざ#includeを書いていくのがめんどくさい。
Liux とかだと異常終了を意図的に起こすこともできるので、Zephyr でそういう手段が用意されていないだろうか。
abort()
まずは abort() だろう。
名前からしても中断させる気に満ちあふれている。
gcc の Aborting a Program (The GNU C Library) でも abort() が載っているし。
SIGABRTシグナルを起こす、というのが UNIX 系での挙動になっているが、Zephyr にはたぶんそういう機能は無かろう。
そもそもシグナルが POSIX だけだろうから、Windows でも違う動作になると思う。
ではどうなっているか。
C11 には _Noreturn という識別子があるが、Zephyr では FUNC_NORETURN というマクロを使うようだ。
FUNC_NORETURN void abort(void)
そして説明が無い。。。
実際に呼び出してみるとこういうログ出力になり、勝手に再起動した。
少しログにゴミがあったりずれていたところがあるので、再起動には少し余裕を持ってほしいが難しいか。
ちなみに、これは次の節で行う CONFIG_ASSERT を設定した後に行っている。
abort()
[00:00:00.249,450] <err> os: r0/a1: 0x00000004 r1/a2: 0x00000000 r2/a3: 0x00000004
[00:00:00.249,450] <err> os: r3/a4: 0x00000000 r12/ip: 0x20000000 r14/lr: 0x000044d5
[00:00:00.249,450] <err> os: xpsr: 0x69000000
[00:00:00.249,481] <err> os: Faulting instruction address (r15/pc): 0x000044e0
[00:00:00.249,511] <err> os: >>> ZEPHYR FATAL ERROR 4: Kernel panic on CPU 0
[00:00:00.249,542] <err> os: Current thread: 0x20001498 (unknown)
Fatal Errors
Zephyr に Fatal Errors の項目があった。
- Fatal Errors — Zephyr Project Documentation
- ソフトウェアのエラー
CONFIG_ASSERTを ON にしてCONFIG_ASSERT_LEVELにレベル値を設定する__ASSERT_ONにレベル値を設定する
- 例外
- Fatal Error Handling
- API Reference
- ソフトウェアのエラー

エラーの原因はハードウェアかもしれないが、ともかくソフトウェアで識別できるエラーという意味だろう。
レベルは 1 か 2 で、これは ASSERT を有効にしていることに対してコンパイラの警告を出すか出さないかの違いのようだ(2だと出さない)。
prj.conf に CONFIG_ASSERT=y を追加。
main.cに __ASSERT(0, "Something Happen!!"); として無理やりエラー状態にしてみるとログが出力された。
ASSERTION FAIL [0] @ CMAKE_SOURCE_DIR/src/main.c:46
Something Happen!!
void assert_post_action(const char *file, unsigned int line) を実装することでメッセージ出力後に呼び出す処理も実装できる。
LED 点滅なので初期化後に __ASSERT()するようにしたところ、ちゃんと点滅した。
気付きやすいのだが実にうっとうしいので、LED 高速点滅はあまりよくなかったかもしれない。
おわりに
ASSERT 系は本番運用では使わないようにするなら、今回の機能はデバッグ専用になる。
そう割り切れば、LOG_ERR()と__ASSERT(0)をセットにしてしまいたいところだが __ASSERT() は固定文字列しか取らないのでそうもいかない。
面倒だが使い分けがいるな。