アプリケーションのテスト¶
このチュートリアルでは、最初の独自アプリケーションを作るの my_reef_app を引き続き使います。最終的に、アプリケーションには myapp selftest コマンドと、最後の行が ALL TESTS PASSED または SOME TESTS FAILED になる起動時セルフテストが加わります。セルフテストはコンソールから手動で実行することも、1 つのコマンドや CI スクリプトにボードへの書き込み、リセット、結果の判定を任せることもできます。
前提条件¶
- 最初の独自アプリケーションを作るを完了していること。
apps/custom/my_reef_appのビルドと書き込みができ、myapp statusに応答します。 - USB-C から給電し、U6 に IPRO Debug Probe または USB-UART ケーブルを接続した IPRO7AI Reef EVK。
- 自動化の部分(
tools/serial_monitor.py、build_freertos.sh)には、Python 3.8 以降を備えた macOS または Linux。serial_monitor.pyは POSIX termios を使うため pyserial は不要ですが、Windows では動作しません。 - ホスト側のユニットテストには、コンピューター上の C コンパイラー(
cc)。ボードや RISC-V ツールチェーンは使いません。
手順¶
-
コンソールで手動で確認します。 コンソール(115200 ボー)を開き、次のコマンドを入力してアプリケーションとシステムの状態を確認します。
myapp statusは LED の色、点滅周期、キーの押下回数を表示します。psはmy_appやkeysを含むすべてのタスクを一覧表示します。freeはHeap: total <合計>, free <空き>, min free <最小空き>を表示します。SW3 を押してからmyapp statusを再度実行すると、SW3 の回数が 1 増えています。このような確認は開発中には手軽ですが、毎回だれかが結果を読む必要があります。 -
出荷時ファームウェアでハードウェアを確認します。 ボード自体を疑う場合は、出荷時ファームウェア
ipro7ai_reef_demoを書き込み(ビルドと書き込みは最初の FreeRTOS アプリケーションのとおり)、次のように入力します。約 3 秒で、LED を赤、緑、青に点灯させ、スピーカーで 1 kHz のトーンを鳴らし、SD スロットを確認し、カメラを検出します。
[1/4] RGB LED: red, green, blue [2/4] speaker: 1 kHz, 300 ms, 20% [3/4] SD slot: <something seated | empty>, supply <on | off> [4/4] camera bus: <センサー名> at 0x<アドレス> done - keys: press any and run `btn status`カメラがない場合、最後の項目は
no sensorになります。このスモークテストは目と耳で判定します。ハードウェアに問題がないことがわかったら、独自アプリケーションを再度書き込みます。 -
アプリケーションにセルフテストを追加します。 セルフテストは次の 3 つを行います。確認ごとに 1 行を出力し、最後に決まった判定文字列を出力し、失敗の数を返します。
selftest.cを作成します。apps/custom/my_reef_app/selftest.c/* my_reef_app: self-test that prints a verdict test tools can wait for. */ #include <stdio.h> #include <stdint.h> #include <stdbool.h> #include <FreeRTOS.h> #include <task.h> #include "rgb_led.h" #include "keys.h" #include "my_app.h" #define MIN_FREE_HEAP (16u * 1024u) static bool check(const char *name, bool ok) { printf("[selftest] %s: %s\n", name, ok ? "PASS" : "FAIL"); return ok; } /* LED: show red, green and blue for 300 ms each and read each colour back. */ static bool test_led(void) { static const uint8_t colours[3][3] = { { 40, 0, 0 }, { 0, 40, 0 }, { 0, 0, 40 } }; bool ok = (rgb_led_init() == 0); for (int i = 0; i < 3 && ok; i++) { uint8_t r, g, b; ok = (rgb_led_set(colours[i][0], colours[i][1], colours[i][2]) == 0); rgb_led_get(&r, &g, &b); ok = ok && r == colours[i][0] && g == colours[i][1] && b == colours[i][2]; vTaskDelay(pdMS_TO_TICKS(300)); } (void)rgb_led_set(0, 0, 0); return ok; } /* Keys: the poll task is running and no key reads as held down. */ static bool test_keys(void) { bool ok = (keys_init() == 0); for (int k = 0; k < KEY_COUNT && ok; k++) { ok = !keys_is_pressed((key_id_t)k); } return ok; } int my_app_selftest(void) { size_t free_heap = xPortGetFreeHeapSize(); int failed = 0; failed += !check("led", test_led()); failed += !check("keys", test_keys()); failed += !check("heap", free_heap >= MIN_FREE_HEAP); printf("[selftest] %lu bytes of heap free\n", (unsigned long)free_heap); printf("%s\n", failed ? "SOME TESTS FAILED" : "ALL TESTS PASSED"); return failed; }ALL TESTS PASSEDとSOME TESTS FAILEDは、build_freertos.sh testが待ち受ける 2 つの文字列そのものです。 -
LED の操作はアプリケーションタスクに任せます。 セルフテストは LED を駆動しますが、その LED は
my_appタスクが点滅させています。両者が同時に LED に書き込まないように、シェルコマンドはテストを要求するだけにし、テストはmy_appタスクが実行します。my_app.hの#endifの前に次を追加します。apps/custom/my_reef_app/my_app.h(追加)/* Self-test: one line per check, then ALL TESTS PASSED or SOME TESTS FAILED. * Returns the number of failed checks. Drives the LED: call it from the app * task only; other tasks use my_app_request_selftest(). */ int my_app_selftest(void); void my_app_request_selftest(void);main.cに 4 か所の変更を加えます。s_blink_msの下にタスクハンドルを追加します。my_app_get_blink_ms()の下に、要求用の関数を追加します。apps/custom/my_reef_app/main.c(追加)void my_app_request_selftest(void) { if (s_app_task != NULL) { xTaskNotifyGive(s_app_task); } }app_task()内のblinking the RGB LEDの行からループの終わりまでを、次のコードに置き換えます。起動時にセルフテストを 1 回実行し(.configのオプションで制御)、ループはタスク通知を待つようになるため、要求があると待機を早めに切り上げてテストを実行します。apps/custom/my_reef_app/main.c(app_task の後半)IPRO_LOGI(LOG_TAG, "blinking the RGB LED on GPIO%d", BOARD_RGB_LED_PIN); #ifdef CONFIG_MYAPP_SELFTEST_AT_BOOT (void)my_app_selftest(); #endif for (;;) { uint32_t period = s_blink_ms; on = (period == 0) ? true : !on; led_show(on ? s_rgb : 0); /* Wait one blink step; wake early when `myapp selftest` asks. */ if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(period ? period : 100)) != 0) { (void)my_app_selftest(); } }最後に、
main()でタスクハンドルを保持します。 -
myapp selftestコマンドと起動時オプションを追加します。app_cli.cのcmd_myapp()で、最後のelse分岐を次のものに置き換えます。apps/custom/my_reef_app/app_cli.c(変更)} else if (strcmp(argv[1], "selftest") == 0) { my_app_request_selftest(); } else { printf("myapp [status] | myapp color <r> <g> <b> | myapp blink <ms> | myapp selftest\n"); }CMakeLists.txtのtarget_sourcesにselftest.cを追加します。apps/custom/my_reef_app/CMakeLists.txt(抜粋)target_sources(app PRIVATE ${REEF_DEMO_SRC}/rgb_led.c ${REEF_DEMO_SRC}/keys.c app_cli.c selftest.c ).configの末尾に独自のオプションを追加します。.configの各CONFIG_X=yはグローバルな-DCONFIG_X=1になるため、コードから#ifdefで判定できます。apps/custom/my_reef_app/.config(追加)# my_reef_app: run the self-test once at boot CONFIG_MYAPP_SELFTEST_AT_BOOT=y.configを変更したら、クリーンビルドを行います。 -
書き込み、手動で一度実行します。 最初の独自アプリケーションを作るの手順 6 のとおりに書き込み、SW2 を短く押します。起動時に LED が赤、緑、青に点灯し、コンソールに次のように表示されます。
MYAPP: Hello from my_reef_app MYAPP: blinking the RGB LED on GPIO24 [selftest] led: PASS [selftest] keys: PASS [selftest] heap: PASS [selftest] <空きバイト数> bytes of heap free ALL TESTS PASSEDmyapp selftestと入力すれば、いつでも再実行できます。失敗時の動作を確認するには、SW3 を押したままボードを起動します。keysがFAILになり、最後の行がSOME TESTS FAILEDになります。 -
serial_monitor.pyに結果を判定させます。 ターミナルプログラムを閉じ(2 つのプログラムが同じポートを開くことはできません)、SDK のルートから次を実行します。python3 tools/serial_monitor.py /dev/cu.usbserial-XXXX \ --send "myapp selftest" \ --wait-for "ALL TESTS PASSED|SOME TESTS FAILED" --timeout 15 echo "exit=$?"出力は次のとおりです(シェルのエコーとプロンプトは省略)。
[serial_monitor] Sent: myapp selftest [selftest] led: PASS [selftest] keys: PASS [selftest] heap: PASS [selftest] <空きバイト数> bytes of heap free ALL TESTS PASSED [serial_monitor] MATCHED: ALL TESTS PASSED exit=0serial_monitor.pyはポートを 115200 ボーで開き(--baudで変更可能)、--sendのコマンドを送信して、--wait-forの正規表現に一致する出力を待ちます。終了コードは次のとおりです。終了コード 意味 0 一致したテキストに PASSEDが含まれる1 一致したテキストに PASSEDが含まれない(例:SOME TESTS FAILED)2 --timeout秒以内に何も一致しなかった(--wait-forがない場合は、タイムアウトで必ず 2 で終了)3 ポートを開けなかった、またはコマンドを送信できなかった PASSEDを含まない文字列を待つ場合、終了コード 1 は「見つかった」ことを意味します。たとえばリセット後にアプリケーションが起動することを確認する--send reboot --wait-for "Hello from my_reef_app"は、成功すると 1、タイムアウトすると 2 で終了します。 -
スクリプトや CI に組み込みます。 前の手順をスクリプトにまとめ、終了コードで成否を判定し、
--logで出力全体を保存します。board_selftest.sh#!/bin/sh # Run the on-board self-test of my_reef_app and fail unless it passes. PORT=${IPRO_SERIAL_PORT:?set IPRO_SERIAL_PORT to the Reef console port} python3 tools/serial_monitor.py "$PORT" --send "myapp selftest" \ --wait-for "ALL TESTS PASSED|SOME TESTS FAILED" --timeout 15 --log selftest.log rc=$? case $rc in 0) echo "self-test passed" ;; 1) echo "self-test failed, see selftest.log" ;; 2) echo "no verdict within 15 s, see selftest.log" ;; *) echo "cannot open $PORT" ;; esac exit $rcCI で使う場合は、ジョブを実行するマシンにボードを接続しておく必要があります。
-
ビルド、書き込み、テストを 1 つのコマンドで行います。
build_freertos.sh testは、ビルドと書き込み(先にreboot_dlを送ってボードをダウンロードモードにします)を行い、rebootを送信してALL TESTS PASSED|SOME TESTS FAILEDを待ちます。リセット後にアプリケーションが自分で判定を出力するのは、起動時セルフテスト(手順 5 のCONFIG_MYAPP_SELFTEST_AT_BOOT=y)があるからです。書き込みツールはIPRO_FLASH_TOOLで、パーティションテーブルは-Pで指定します。.configにCONFIG_IPRO7AI_REEF=yがあるため、--chip-variant IPRO7AIはスクリプトが自動で追加します。成功すると、最後に次のように表示されます。
終了コード 0 は合格、1 は一部のテストの失敗、2 は
-t秒(デフォルト 30)以内に判定が出なかったことを意味します。書き込み後にコンソールに何も表示されない場合は、SW2(RESET)を短く押してダウンロードモードを抜けます。テストはタイムアウトまで待ち続けます。build_freertos.sh testは Windows では動作しません。Windows では手動で書き込み、手順 6 のとおりにテストします。 -
ホスト側のユニットテストを実行します。 ハードウェアに触れない純粋なロジック(信号処理、プロトコル解析、データ変換)は、コンピューター上でテストするのが最適です。SDK にはこのようなテストがいくつか含まれており、それぞれ
test_host/run.shがコンピューターのccでコンパイルして実行します。オーディオコンポーネントのテストは、Reef のマイクが使う PDM チェーンを対象とします。LE Audio ヘッドセットアプリケーションの DAC ノイズシェーパーのテストは次のとおりです。
in-band error rms: 32 kHz rounding 0.1916 codes, order 1 0.00521 (31.3 dB better), order 2 0.000653 (49.4 dB better) dac_ns: all tests passedどちらのスクリプトも、いずれかの確認が失敗すると 0 以外で終了するため、そのまま CI に組み込めます。独自モジュールのホストテストを書くときは
components/audio/test_host/に倣います。main()を持つtest_*.cを、テスト対象の.cとともにccでコンパイルします(run.shはメモリエラーを検出するために-fsanitize=address,undefinedを追加します)。
動作確認¶
- リセット後、コンソールの出力が
ALL TESTS PASSEDで終わり、LED は点滅を始める前に赤、緑、青に点灯します。 python3 tools/serial_monitor.py <ポート> --send "myapp selftest" --wait-for "ALL TESTS PASSED|SOME TESTS FAILED"は 0 で終了します。SW3 を押したまま起動した場合は 1 で終了します。./build_freertos.sh … test my_reef_appは✓ All tests passedを表示し、0 で終了します。sh components/audio/test_host/run.shは0 failedで終わる 2 行を表示します。
テストの方法¶
| 方法 | 必要なもの | 判定 | 用途 |
|---|---|---|---|
| コンソールコマンドを手動で実行 | ボード、ターミナル | 人 | 開発中の手早い確認 |
reef selftest(出荷時ファームウェア) |
ボード、出荷時ファームウェア | 人(目視と聴覚) | ハードウェアが正常であることの確認 |
myapp selftest + serial_monitor.py |
ボード、macOS/Linux | 終了コード 0/1/2/3 | スクリプトや CI(再書き込みなし) |
build_freertos.sh test |
ボード、IntelProCube、macOS/Linux | 終了コード 0/1/2 | 変更のたびの全体確認 |
test_host/run.sh |
コンピューター上の cc |
終了コード | 純粋なロジック、最も高速 |
SDK の ipro_unit_test も build_freertos.sh test で実行できますが、Reef ではなく IPRO7 EVB 向けに構成されています。
トラブルシューティング¶
| 症状 | 原因 | 対処 |
|---|---|---|
serial_monitor.py が 3 で終了し、Failed to open … と表示される |
ターミナルプログラムがポートを使用している、またはポート名が正しくない | ターミナルを閉じます。macOS では /dev/tty.* ではなく /dev/cu.* を使います |
終了コード 2 で、出力に Sent: myapp selftest しか表示されない |
アプリケーションが動作していない、または古いビルドである | ターミナルで myapp selftest が応答することを確認し、再度書き込みます |
myapp selftest が使い方の行だけを表示する |
app_cli.c が古い |
手順 5 の selftest 分岐が入っていることを確認し、再ビルドします |
| 起動時にセルフテストの出力がない | クリーンビルドせずに .config を変更した、またはオプション名のつづりが誤っている |
./build_freertos.sh clean build my_reef_app を実行し、CONFIG_MYAPP_SELFTEST_AT_BOOT=y を確認します |
build_freertos.sh test で Flash tool not found と表示される |
IPRO_FLASH_TOOL が未設定で、書き込みツールが PATH 上にない |
IPRO_FLASH_TOOL=<IntelProCube>/ipro_iot_tool_macos(または _linux)を設定します |
Several serial ports, pass -p PORT … |
USB シリアルポートが複数ある(プローブには 2 つある) | -p または IPRO_SERIAL_PORT でコンソールポートを指定します。プローブでは 02 で終わるポートです |
Test timed out (no result pattern seen) |
書き込み後にボードがダウンロードモードのままになっている、または判定が出力されなかった | SW2 を短く押します。-t 60 で時間を延ばします。起動時セルフテストのオプションが有効であることを確認します |
keys: FAIL |
テスト中にキーが押されていた | キーを離して再実行します。失敗が続く場合は、myapp status でどのキーが down になっているかを確認します |
次のステップ¶
- ビルドと書き込みの流れ:
build_freertos.shのその他のアクションとオプション。 - 書き込みとシリアルコンソール:ダウンロードモードに入るすべての方法と、
serial_monitor.pyの全オプション。 - 最初の BLE アプリケーション:Reef 上で GATT リファレンスアプリケーションを実行し、変更します。