测试你的应用¶
本教程延续创建第一个自己的应用中的 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/的做法:一个包含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。
测试方式比较¶
| 方式 | 需要 | 判定 | 适用场景 |
|---|---|---|---|
| 控制台手动命令 | 开发板、终端程序 | 人工 | 开发过程中随手确认 |
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 参考应用。