測試你的應用¶
本教學延續建立第一個自己的應用的 my_reef_app。完成後,應用會有一個 myapp selftest 指令與開機自我測試,最後一行印出 ALL TESTS PASSED 或 SOME TESTS FAILED;您可以在主控台手動執行,也可以用一行命令或 CI 腳本自動燒錄、重置並判定結果。
前置需求¶
- 完成建立第一個自己的應用,
apps/custom/my_reef_app能建置、燒錄並回應myapp status。 - IPRO7AI Reef EVK 以 USB-C 供電,U6 接 IPRO Debug Probe 或 USB 轉 UART 線。
- 自動化的部分(
tools/serial_monitor.py、build_freertos.sh)需要 macOS 或 Linux 與 Python 3.8 以上:serial_monitor.py使用 POSIX 的 termios,不需要 pyserial,但不能在 Windows 上執行。 - 主機端單元測試需要電腦上的 C 編譯器(
cc),不需要板子與 RISC-V 工具鏈。
步驟¶
-
在主控台手動檢查:開啟主控台(115200 baud),依序輸入下列指令,確認應用與系統狀態:
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`沒接相機時第 4 項是
no sensor。這是看的、聽的硬體冒煙測試,結果要由人判斷;確認硬體正常後,再燒回自己的應用。 -
在應用中加入自我測試:自我測試要做三件事:逐項印出結果、最後印出固定的判定字串、傳回失敗項數。建立
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等待的兩個字串。 -
讓 LED 只由應用任務驅動:自我測試會操作 LED,而 LED 平常由
my_app任務閃爍。為了不讓兩者同時寫 LED,shell 指令只「請求」測試,由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中做四處修改。在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那行之後到迴圈結束的部分改成下面這樣:開機時執行一次自我測試(由.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 PASSED之後隨時可以在主控台輸入
myapp selftest再跑一次。開機時按住 SW3,keys一項會是FAIL,最後一行變成SOME TESTS FAILED,可以用這個方法確認失敗路徑也正確。 -
用
serial_monitor.py自動判定:關掉終端程式(同一個序列埠不能同時被兩個程式開啟),在 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=$?"輸出如下(省略 shell 的指令回顯與提示字元):
[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 開啟序列埠(--baud可改),送出--send的指令,再等待符合--wait-for正規表示式的輸出。結束碼:結束碼 意義 0 符合的文字含有 PASSED1 符合的文字不含 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 $rc在 CI 上執行時,板子要接在執行工作的那台機器上。
-
一行命令完成建置、燒錄與測試:
build_freertos.sh test依序建置、燒錄(先送reboot_dl讓板子進入下載模式)、送出reboot,再等待ALL TESTS PASSED|SOME TESTS FAILED。開機自我測試(步驟 5 的CONFIG_MYAPP_SELFTEST_AT_BOOT=y)就是讓應用在重置後自己印出判定。燒錄工具以IPRO_FLASH_TOOL指定,分割表以-P指定,--chip-variant IPRO7AI由腳本依.config的CONFIG_IPRO7AI_REEF=y自動帶入:成功時最後印出:
結束碼 0 為通過、1 為有測試失敗、2 為
-t秒內(預設 30)沒看到判定。燒錄完成後若主控台沒有任何輸出,按一下 SW2(RESET)讓板子離開下載模式,測試會繼續等到逾時為止。Windows 上沒有build_freertos.sh test,請以手動燒錄加上步驟 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/的做法:一個test_*.c含main(),與受測的.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。
測試方式比較¶
| 方式 | 需要 | 判定 | 適合 |
|---|---|---|---|
| 主控台手動指令 | 板子、終端程式 | 人 | 開發中隨手確認 |
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 執行,但它的設定是 IPRO7 EVB,不是 Reef。
疑難排解¶
| 症狀 | 原因 | 處理 |
|---|---|---|
serial_monitor.py 結束碼 3,訊息 Failed to open … |
序列埠被終端程式占用,或埠名錯誤 | 關掉終端程式;macOS 用 /dev/cu.*,不要用 /dev/tty.* |
結束碼 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 序列埠(例如探針的兩個埠) | 用 -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 參考應用。