diff --git a/.gitignore b/.gitignore index a0ab1720..a7437147 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ *.hex *.map *.lnp -*.sct *.dep *.build_log.htm *.htm diff --git a/.opencode/command/download.md b/.opencode/command/download.md new file mode 100644 index 00000000..86135fa5 --- /dev/null +++ b/.opencode/command/download.md @@ -0,0 +1,20 @@ +--- +description: Download (flash) the program to the STM32 via Keil MDK. Use ONLY when the user asks to flash, download, program, or burn the firmware to the MCU. +agent: build +--- + +Run the Keil MDK flash download by executing the download script with the bash tool. Use the absolute path, NOT relative cd: + +```powershell +& "D:\Code\DTU 程序\STM32F4-Base\MDK-ARM\download.bat" +``` + +Then read the tail of `MDK-ARM/download_log.txt` and report the result. + +This compiles the project then flashes the target via the configured debugger (ST-Link). + +Exit code interpretation: +- 0 = download successful — PASS +- nonzero = download/build failed — see `MDK-ARM/download_log.txt` for details + +Note: Since flashing overwrites the running firmware, confirm the debug probe is connected and the user is ready to flash before running this. If the user only wants to verify compilation (not flash), use `@build` instead. diff --git a/.opencode/command/test.md b/.opencode/command/test.md new file mode 100644 index 00000000..58f3b871 --- /dev/null +++ b/.opencode/command/test.md @@ -0,0 +1,74 @@ +--- +description: Build → flash (download) → capture USART1 debug log → verify boot/test markers → iterate. The full automated test loop for the STM32F4-Base target. +agent: build +--- + +Run the full automated test loop: **build → download → capture serial debug → verify markers → report/iterate**. + +Prerequisites (checked in step 0): +- `serial-mcp-server` CLI installed at `%LOCALAPPDATA%\serial-mcp-server\serial-mcp-server.exe` (Adancurusul/serial-mcp-server, v0.3.0). +- ST-Link probe connected; target powered. The USB-serial (CH340) may stay idle while flashing — ST-Link download does not conflict with the serial port. + +## Procedure + +### Step 0 — Pre-flight: serial-mcp-server binary + port + +Verify the capture binary and target port exist: + +```powershell +Test-Path "$env:LOCALAPPDATA\serial-mcp-server\serial-mcp-server.exe" +& "$env:LOCALAPPDATA\serial-mcp-server\serial-mcp-server.exe" list-ports --json +``` + +Read `.opencode/serial_config.json` for the authoritative `portName` (COM6) and match it in the list. If the binary is missing, build it from source (see `docs/测试说明/serial-mcp-server_串口调试.md`). + +### Step 1 — Build verify + +Run the build script (absolute path): + +```powershell +& "D:\Code\DTU 程序\STM32F4-Base\MDK-ARM\build.bat" +``` + +Interpretation (per AGENTS.md): exit code 0 = PASS (0 errors, 0 warnings). 1/2+ = FAIL. If it fails, read `MDK-ARM/build_log.txt`, fix, and rerun `@build` until exit code 0 **before** touching the hardware. + +### Step 2 — Flash + capture boot log (one orchestrated call) + +Use `test/run_boot_test.ps1` which starts the capture (waits for first byte), +then flashes, then collects and verifies — all in one call so the capture never +times out before flashing: + +```powershell +powershell -ExecutionPolicy Bypass -File "D:\Code\DTU 程序\STM32F4-Base\test\run_boot_test.ps1" ` + -Port COM6 -BaudRate 115200 ` + -DurationMs 30000 -InitialTimeoutMs 90000 -IdleTimeoutMs 8000 -MaxBytes 65536 ` + -FlashCmd "D:\Code\DTU 程序\STM32F4-Base\MDK-ARM\download.bat" ` + -Required "App init OK;Network Init;net init: OK" ` + -Forbidden "FAIL;ERROR;HardFault" ` + -OutDir "C:\Users\16241\AppData\Local\Temp\opencode" +``` + +Exit codes: **0 = PASS**, 1 = FAIL (marker miss / forbidden hit), 2 = capture/flash/parse error. + +### Step 3 — Iterate + +- **PASS** → report the verdict and which markers matched; done. +- **FAIL** → the `[MISS]`/`[HIT]` lines + the log tail show what went wrong. Fix the code, then loop again from **Step 1**. Do not skip the build step. + +## Alternative: manual MCP capture (interactive debug) + +For interactive command/response (not boot-log capture): + +1. `serial_list_ports` → match `portName` from `.opencode/serial_config.json` (COM6). +2. `serial_open` (port=COM6, baud_rate=115200) → note the returned `connection_id`. +3. `serial_write` / `serial_read` (connection_id, timeout_ms) for command-response. +4. `serial_close` to release the port. + +## Notes + +- **串口端口**:以 `.opencode/serial_config.json` 为准(本机为 COM6)。每次连接前先读它。 +- **⚠️ 无历史缓冲**:serial-mcp-server 的 `read` 只能读调用后的字节。抓开机日志必须走 `run_boot_test.ps1`(先采集等 first-byte → 再烧录触发)。 +- **⚠️ EOF 陷阱**:COM 刚释放立即重开会报 `UnexpectedEof`(tokio-serial 读 0 字节)。`serial_capture.ps1` 内置自动重试;别裸跑 CLI capture。 +- **参数**:`IdleTimeoutMs` 需 ≥ boot 自然静默间隙(本机 ~3-4s,用 8000);`InitialTimeoutMs` 需覆盖编译+烧录耗时(用 90000)。 +- UI (LVGL) task `ui_task` 增加 `UI port init: OK` / `UI menu init: OK` 打印后,即可用 `-Required "UI port init: OK;UI menu init: OK"` 自动回归 UI 初始化。 +- Full capture/analysis details: `docs/测试说明/serial-mcp-server_串口调试.md` and `docs/测试说明/自动化测试流程.md`. diff --git a/.opencode/mcp/serial_studio_bridge.py b/.opencode/mcp/serial_studio_bridge.py deleted file mode 100644 index c6dc58d8..00000000 --- a/.opencode/mcp/serial_studio_bridge.py +++ /dev/null @@ -1,125 +0,0 @@ -"""opencode <-> Serial Studio MCP bridge. - -Serial Studio exposes a native MCP server on TCP port 7777 speaking -JSON-RPC 2.0 (newline-delimited over a raw TCP socket), NOT over HTTP. - -opencode's "remote" MCP type expects HTTP (streamableHttp / SSE), so it -cannot talk to Serial Studio directly. This script is a thin stdio -bridge: opencode launches it as a "local" MCP server, reads newline- -delimited JSON-RPC from stdin, forwards each request over TCP to Serial -Studio on port 7777, and writes the response back to stdout. - -Usage: - python serial_studio_bridge.py [host] [port] -Defaults: host=127.0.0.1, port=7777 -""" - -import json -import socket -import sys - -DEFAULT_HOST = "127.0.0.1" -DEFAULT_PORT = 7777 - - -def main() -> int: - if len(sys.argv) > 1: - host = sys.argv[1] - else: - host = DEFAULT_HOST - if len(sys.argv) > 2: - port = int(sys.argv[2]) - else: - port = DEFAULT_PORT - - sock = socket.create_connection((host, port), timeout=10) - sock.settimeout(30) - buf = b"" - - def send_json(obj) -> None: - line = (json.dumps(obj, separators=(",", ":")) + "\n").encode("utf-8") - sock.sendall(line) - - def read_response(target_id) -> dict: - nonlocal buf - while True: - while b"\n" in buf: - line, buf = buf.split(b"\n", 1) - line = line.strip() - if not line: - continue - try: - resp = json.loads(line.decode("utf-8")) - except Exception: - continue - if resp.get("id") == target_id or target_id is None: - return resp - sock.settimeout(30) - data = sock.recv(65536) - if not data: - raise ConnectionError("Serial Studio closed connection") - buf += data - - try: - for raw in sys.stdin: - raw = raw.strip() - if not raw: - continue - try: - req = json.loads(raw) - except Exception as e: - err = { - "jsonrpc": "2.0", - "id": None, - "error": {"code": -32700, "message": f"Parse error: {e}"}, - } - sys.stdout.write(json.dumps(err) + "\n") - sys.stdout.flush() - continue - - if isinstance(req, list): - results = [] - for sub in req: - try: - send_json(sub) - results.append(read_response(sub.get("id"))) - except Exception as e: - results.append( - { - "jsonrpc": "2.0", - "id": sub.get("id"), - "error": {"code": -32000, "message": str(e)}, - } - ) - sys.stdout.write(json.dumps(results) + "\n") - sys.stdout.flush() - continue - - if req.get("jsonrpc") == "2.0" and not req.get("id"): - send_json(req) - continue - - try: - send_json(req) - resp = read_response(req.get("id")) - sys.stdout.write(json.dumps(resp) + "\n") - sys.stdout.flush() - except Exception as e: - err = { - "jsonrpc": "2.0", - "id": req.get("id"), - "error": {"code": -32000, "message": str(e)}, - } - sys.stdout.write(json.dumps(err) + "\n") - sys.stdout.flush() - finally: - try: - sock.close() - except Exception: - pass - - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/.opencode/serial_config.json b/.opencode/serial_config.json new file mode 100644 index 00000000..7b96eef6 --- /dev/null +++ b/.opencode/serial_config.json @@ -0,0 +1,6 @@ +{ + "portName": "COM6", + "portHint": "USB 串行设备", + "baudRate": 115200, + "note": "MCU 调试串口(USART1 → CH340 → 本机枚举为 COM6)。执行 @test 前先读取本文件,再用 serial_list_ports 匹配 portName 选择端口;如本机端口变化,请人工修改此文件。" +} diff --git a/AGENTS.md b/AGENTS.md index 153a3212..a8858107 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -74,6 +74,23 @@ Src/main.c → 外设初始化 → app_main_init → gd5f2gq5ue_init → tpafe51 - `GD5F2GQ5UE_Trap_Records.md` — GD5F2GQ5UE 已知陷阱 - `CH395F_Test_Guide.md` — 网络测试说明 +## 串口调试(serial-mcp-server MCP/CLI) + +通过 **Adancurusul/serial-mcp-server** 自主读取/控制 **USART1 串口**(PA9 TX / PA10 RX,115200bps)的打印流。 + +- **串口端口单一配置源:`.opencode/serial_config.json`**(当前本机为 `COM6`)。端口变化只改这个文件。 +- 用法、采集窗口模式、已知陷阱等详见 **`docs/测试说明/serial-mcp-server_串口调试.md`** + +## 自动化测试(AI 迭代闭环) + +改完代码后运行 **`@test`** 可自动执行完整闭环:**构建验证 → 串口捕获武装(serial-mcp-server 采集窗口)→ 烧录(`MDK-ARM/download.bat`,下载后 Reset & Run)→ 解析捕获 JSON → `test/verify_log.ps1` 标记校验 → PASS/FAIL 判定并迭代**。 + +- **推荐一键命令**:`test/run_boot_test.ps1` = 采集(含 EOF 重试)→ 烧录 → 提取日志 → verify 全流程,退出码 0=PASS/1=FAIL/2=错误 +- 判定标准:`@test` 内部先 `@build`(0 错误 0 警告),再 `@download`(退出码 0),日志用 `verify_log.ps1` 校验(默认 Required:`App init OK`/`Network Init`/`net init: OK`;Forbidden:`FAIL`/`ERROR`/`HardFault`) +- **采集必须在烧录前武装**(采集进程先打开 COM 等 first-byte,烧录触发复位后才抓得到开机日志);`run_boot_test.ps1` 的 `-FlashCmd` 分支已保证顺序 +- 每次按模块定制标记(网络/存储/RTC/ADC/RS485)见 `docs/测试说明/自动化测试流程.md` +- UI(`App/task/ui_task.c`)当前无 DBG 打印,无法从串口自动验证;如需 UI 回归,先在固件加 `UI port init: OK` / `UI menu init: OK` 打印再纳入 Required + ## 注意 - `Inc/` 和 `Src/` 中 CubeMX 生成的文件带有 `USER CODE BEGIN`/`END` 标记,自定义代码写在这些区域之间 diff --git a/App/task/ui_task.c b/App/task/ui_task.c index 104a60df..6ce44d53 100644 --- a/App/task/ui_task.c +++ b/App/task/ui_task.c @@ -12,6 +12,9 @@ #include "ui_menu.h" #include "ui_net_sink.h" +#define DBG_TAG "[UI_TASK]" +#include "dbg_log.h" + static void ui_task_wait_net_ready(void) { if (sys_evtHandle != NULL) { @@ -24,15 +27,26 @@ void ui_task_func(void *argument) { (void)argument; + DBG_INFO("UI task: started, wait net ready"); ui_task_wait_net_ready(); + DBG_INFO("UI task: net ready, init port"); + DBG_INFO("UI task: ui_port_init enter"); if (ui_port_init() != 0) { + DBG_ERROR("UI port init: FAIL"); for (;;) { osDelay(1000); } } + DBG_INFO("UI port init: OK"); + DBG_INFO("UI task: ui_menu_init enter"); if (ui_menu_init() != 0) { + DBG_ERROR("UI menu init: FAIL"); for (;;) { osDelay(1000); } } - ui_net_sink_start(UI_NET_SINK_PORT); + DBG_INFO("UI menu init: OK"); + if (ui_net_sink_start(UI_NET_SINK_PORT) != 0) { + DBG_ERROR("UI net sink: start FAIL"); + } + DBG_INFO("UI net sink: started (port %u)", (unsigned)UI_NET_SINK_PORT); while (1) { ui_port_step(10); diff --git a/Inc/FreeRTOSConfig.h b/Inc/FreeRTOSConfig.h index b2e0eee4..c3507e40 100644 --- a/Inc/FreeRTOSConfig.h +++ b/Inc/FreeRTOSConfig.h @@ -68,7 +68,7 @@ #define configTICK_RATE_HZ ((TickType_t)1000) #define configMAX_PRIORITIES ( 56 ) #define configMINIMAL_STACK_SIZE ((uint16_t)128) -#define configTOTAL_HEAP_SIZE ((size_t)30720) +#define configTOTAL_HEAP_SIZE ((size_t)49152) #define configMAX_TASK_NAME_LEN ( 16 ) #define configUSE_TRACE_FACILITY 1 #define configUSE_16_BIT_TICKS 0 diff --git a/MDK-ARM/STM32F407-Demo.uvprojx b/MDK-ARM/STM32F407-Demo.uvprojx index 5b099d93..c3cd6185 100644 --- a/MDK-ARM/STM32F407-Demo.uvprojx +++ b/MDK-ARM/STM32F407-Demo.uvprojx @@ -212,7 +212,7 @@ 0 0 1 - 1 + 0 0 @@ -302,12 +302,12 @@ 0 0x20000000 - 0x1c000 + 0x20000 0 - 0x2001c000 - 0x4000 + 0x0 + 0x0 @@ -362,16 +362,16 @@ - 1 + 0 0 0 0 1 - 0 + 1 - + STM32F407-Demo\STM32F407-Demo.sct diff --git a/MDK-ARM/STM32F407-Demo/STM32F407-Demo.sct b/MDK-ARM/STM32F407-Demo/STM32F407-Demo.sct new file mode 100644 index 00000000..8d1bd88b --- /dev/null +++ b/MDK-ARM/STM32F407-Demo/STM32F407-Demo.sct @@ -0,0 +1,21 @@ +; ************************************************************* +; *** Scatter-Loading Description File (manual) *** +; *** RW_IRAM3(SRAM3) for ucHeap big block. DO NOT let uVision *** +; *** regenerate this file (keep "Use Memory Layout" unchecked)*** +; ************************************************************* + +LR_IROM1 0x08000000 0x00100000 { ; load region size_region + ER_IROM1 0x08000000 0x00100000 { ; load address = execution address + *.o (RESET, +First) + *(InRoot$$Sections) + .ANY (+RO) + .ANY (+XO) + } + RW_IRAM1 0x20000000 0x00020000 { ; RW data + .ANY (+RW +ZI) + } + RW_IRAM3 0x20040000 0x00010000 { ; SRAM3 for ucHeap + heap_4.o (+RW +ZI) + .ANY (+RW +ZI) + } +} diff --git a/MDK-ARM/STM32F407-Demo/STM32F407-Demo_sct.Bak b/MDK-ARM/STM32F407-Demo/STM32F407-Demo_sct.Bak new file mode 100644 index 00000000..8d1bd88b --- /dev/null +++ b/MDK-ARM/STM32F407-Demo/STM32F407-Demo_sct.Bak @@ -0,0 +1,21 @@ +; ************************************************************* +; *** Scatter-Loading Description File (manual) *** +; *** RW_IRAM3(SRAM3) for ucHeap big block. DO NOT let uVision *** +; *** regenerate this file (keep "Use Memory Layout" unchecked)*** +; ************************************************************* + +LR_IROM1 0x08000000 0x00100000 { ; load region size_region + ER_IROM1 0x08000000 0x00100000 { ; load address = execution address + *.o (RESET, +First) + *(InRoot$$Sections) + .ANY (+RO) + .ANY (+XO) + } + RW_IRAM1 0x20000000 0x00020000 { ; RW data + .ANY (+RW +ZI) + } + RW_IRAM3 0x20040000 0x00010000 { ; SRAM3 for ucHeap + heap_4.o (+RW +ZI) + .ANY (+RW +ZI) + } +} diff --git a/MDK-ARM/build.bat b/MDK-ARM/build.bat index 94488815..90b93617 100644 --- a/MDK-ARM/build.bat +++ b/MDK-ARM/build.bat @@ -7,13 +7,13 @@ set LOG=%~dp0build_log.txt set TARGET=STM32F407-Demo "%UV4%" -r "%PROJ%" -t%TARGET% -j0 -o "%LOG%" -set RET=%ERRORLEVEL% -if %RET% equ 0 ( - echo BUILD_OK: 0 Error(s), 0 Warning(s) -) else ( - echo BUILD_FAIL: exit code %RET% - if exist "%LOG%" type "%LOG%" +findstr /C:"0 Error(s), 0 Warning(s)" "%LOG%" >nul +if %ERRORLEVEL% equ 0 ( + echo BUILD_OK: 0 errors, 0 warnings + exit /b 0 ) -exit /b %RET% +echo BUILD_FAIL: no success marker in log +type "%LOG%" +exit /b 1 diff --git a/MDK-ARM/build_log.txt b/MDK-ARM/build_log.txt index b2f25c58..67facdaf 100644 --- a/MDK-ARM/build_log.txt +++ b/MDK-ARM/build_log.txt @@ -1,279 +1,279 @@ *** Using Compiler 'V5.06 update 7 (build 960)', folder: 'C:\Keil_v5\ARM\ARMCC\Bin' Rebuild target 'STM32F407-Demo' assembling startup_stm32f407xx.s... -compiling ui_task.c... -compiling spi.c... -compiling time_sync.c... -compiling stm32f4xx_hal_tim_ex.c... -compiling main.c... -compiling stm32f4xx_hal_timebase_tim.c... -compiling net_task.c... -compiling sys_clock.c... -compiling stm32f4xx_hal_tim.c... -compiling usart.c... compiling dma.c... -compiling stm32f4xx_hal_flash.c... -compiling stm32f4xx_it.c... -compiling stm32f4xx_hal_flash_ramfunc.c... +compiling i2c.c... +compiling stm32f4xx_hal_timebase_tim.c... +compiling stm32f4xx_hal_dma_ex.c... compiling gpio.c... compiling stm32f4xx_hal_msp.c... -compiling stm32f4xx_hal_rcc.c... +compiling stm32f4xx_it.c... +compiling stm32f4xx_hal_dma.c... +compiling spi.c... +compiling sys_clock.c... compiling stm32f4xx_hal_flash_ex.c... +compiling stm32f4xx_hal_flash.c... +compiling stm32f4xx_hal_pwr.c... +compiling stm32f4xx_hal_pwr_ex.c... compiling stm32f4xx_hal_rcc_ex.c... -compiling i2c.c... +compiling stm32f4xx_hal_flash_ramfunc.c... compiling stm32f4xx_hal_gpio.c... -compiling app_main.c... compiling freertos.c... -compiling stm32f4xx_hal_dma_ex.c... +compiling usart.c... +compiling app_main.c... +compiling main.c... +compiling stm32f4xx_hal_rcc.c... +compiling time_sync.c... +compiling ui_task.c... +compiling net_task.c... +compiling stm32f4xx_hal_tim_ex.c... compiling lftpd_string.c... compiling croutine.c... compiling event_groups.c... compiling list.c... +compiling stm32f4xx_hal_tim.c... compiling queue.c... -compiling stm32f4xx_hal_dma.c... compiling stream_buffer.c... -compiling stm32f4xx_hal_pwr.c... compiling timers.c... -compiling tasks.c... compiling heap_4.c... compiling stm32f4xx_hal_cortex.c... -compiling stm32f4xx_hal_pwr_ex.c... +compiling tasks.c... +compiling port.c... +compiling map.c... +compiling journal.c... +compiling ff.c... compiling stm32f4xx_hal.c... compiling system_stm32f4xx.c... -compiling stm32f4xx_hal_exti.c... -compiling port.c... +compiling test_config.c... +compiling ch395f_test_task.c... compiling stm32f4xx_hal_i2c_ex.c... -compiling journal.c... -compiling map.c... -compiling rs485.c... +compiling net_test_task.c... +compiling gd5f_test_task.c... +compiling cmsis_os2.c... compiling sd2506.c... +compiling rs485.c... +compiling stm32f4xx_hal_exti.c... +compiling nand_ftl.c... compiling tpafe5160.c... compiling lftpd_io.c... -compiling stm32f4xx_hal_uart.c... -compiling net_select.c... -compiling lftpd_inet.c... -compiling gd5f2gq5ue.c... -compiling nand_ftl.c... compiling ch395f.c... -compiling ff.c... -compiling stm32f4xx_hal_spi.c... -compiling lftpd.c... compiling lv_obj_class.c... -compiling lv_group.c... -compiling lv_obj_id_builtin.c... +compiling lftpd_inet.c... compiling lv_obj_event.c... +compiling lv_group.c... compiling lv_obj_draw.c... +compiling net_select.c... +compiling stm32f4xx_hal_spi.c... compiling lv_obj.c... -compiling net_socket.c... +compiling gd5f2gq5ue.c... +compiling stm32f4xx_hal_uart.c... compiling lv_obj_property.c... -compiling stm32f4xx_hal_i2c.c... +compiling net_socket.c... +compiling lv_obj_id_builtin.c... +compiling lftpd.c... compiling lv_draw_3d.c... -compiling lv_obj_pos.c... -compiling lv_obj_style_gen.c... compiling lv_obj_tree.c... +compiling lv_obj_style_gen.c... +compiling stm32f4xx_hal_i2c.c... compiling lv_obj_scroll.c... compiling lv_draw.c... -compiling lv_obj_style.c... compiling lv_draw_arc.c... -compiling lv_draw_mask.c... +compiling lv_obj_style.c... +compiling lv_obj_pos.c... compiling lv_refr.c... compiling lv_draw_line.c... -compiling lv_draw_image.c... +compiling lv_draw_mask.c... compiling lv_display.c... -compiling lv_draw_triangle.c... +compiling lv_draw_image.c... compiling lv_draw_buf.c... -compiling lv_draw_rect.c... -compiling lv_draw_label.c... compiling lv_draw_vector.c... +compiling lv_draw_rect.c... +compiling lv_draw_triangle.c... +compiling lv_draw_label.c... compiling lv_draw_sw_blend.c... compiling lv_image_decoder.c... compiling lv_draw_sw_blend_to_al88.c... +compiling lv_draw_sw_blend_to_argb8888_premultiplied.c... +compiling lv_draw_sw_blend_to_argb8888.c... compiling lv_draw_sw_grad.c... compiling lv_draw_sw_arc.c... compiling lv_draw_sw.c... -compiling lv_draw_sw_blend_to_argb8888_premultiplied.c... -compiling lv_draw_sw_border.c... compiling lv_draw_sw_fill.c... +compiling lv_draw_sw_border.c... compiling lv_draw_sw_letter.c... -compiling lv_draw_sw_blend_to_l8.c... -compiling lv_draw_sw_blend_to_argb8888.c... -compiling lv_draw_sw_blend_to_i1.c... -compiling lv_draw_sw_blend_to_rgb888.c... -compiling lv_draw_sw_box_shadow.c... -compiling lv_draw_sw_img.c... -compiling lv_draw_sw_line.c... -compiling lv_draw_sw_blend_to_rgb565.c... compiling lv_draw_sw_mask_rect.c... -compiling lv_draw_sw_blend_to_rgb565_swapped.c... -compiling cmsis_os2.c... -compiling lv_draw_sw_vector.c... -compiling lv_draw_sw_triangle.c... +compiling lv_draw_sw_blend_to_l8.c... +compiling lv_draw_sw_blend_to_i1.c... compiling lv_draw_sw_utils.c... +compiling lv_draw_sw_triangle.c... +compiling lv_draw_sw_line.c... +compiling lv_draw_sw_vector.c... +compiling lv_draw_sw_blend_to_rgb888.c... +compiling lv_draw_sw_img.c... +compiling lv_draw_sw_box_shadow.c... compiling lv_font.c... compiling lv_draw_sw_mask.c... -compiling gd5f_test_task.c... -compiling test_config.c... -compiling lv_binfont_loader.c... compiling lv_draw_sw_transform.c... +compiling lv_draw_sw_blend_to_rgb565.c... +compiling lv_draw_sw_blend_to_rgb565_swapped.c... +compiling storage_test_task.c... +compiling lv_binfont_loader.c... +compiling sd2506_test_task.c... compiling lv_font_dejavu_16_persian_hebrew.c... -compiling ch395f_test_task.c... -compiling net_test_task.c... compiling lv_font_fmt_txt.c... compiling lv_font_montserrat_10.c... compiling lv_font_montserrat_12.c... compiling lv_font_montserrat_14.c... -compiling storage_test_task.c... compiling lv_font_montserrat_14_aligned.c... compiling lv_font_montserrat_16.c... compiling lv_font_montserrat_18.c... -compiling sd2506_test_task.c... -compiling lv_font_montserrat_20.c... compiling lv_font_montserrat_22.c... +compiling lv_font_montserrat_20.c... compiling lv_font_montserrat_24.c... -compiling lv_font_montserrat_28_compressed.c... compiling lv_font_montserrat_26.c... compiling lv_font_montserrat_28.c... +compiling lv_font_montserrat_28_compressed.c... compiling lv_font_montserrat_30.c... -compiling lv_font_montserrat_34.c... compiling lv_font_montserrat_32.c... +compiling lv_font_montserrat_34.c... compiling lv_font_montserrat_36.c... compiling lv_font_montserrat_38.c... compiling lv_font_montserrat_40.c... +compiling lv_font_montserrat_44.c... compiling lv_font_montserrat_42.c... compiling lv_font_montserrat_46.c... -compiling lv_font_montserrat_44.c... compiling lv_font_montserrat_48.c... -compiling lv_indev_gesture.c... compiling lv_font_montserrat_8.c... compiling lv_font_simsun_14_cjk.c... -compiling lv_font_unscii_16.c... -compiling lv_font_source_han_sans_sc_14_cjk.c... compiling lv_font_simsun_16_cjk.c... +compiling lv_font_source_han_sans_sc_14_cjk.c... +compiling lv_indev_gesture.c... +compiling lv_font_unscii_16.c... compiling lv_font_source_han_sans_sc_16_cjk.c... compiling lv_font_unscii_8.c... -compiling lv_indev_scroll.c... compiling lv_cache_lru_ll.c... compiling lv_cache_lru_rb.c... -compiling lv_layout.c... -compiling lv_flex.c... -compiling lv_init.c... compiling lv_cache.c... -compiling lv_indev.c... +compiling lv_layout.c... +compiling lv_init.c... +compiling lv_indev_scroll.c... compiling lv_image_cache.c... -compiling lv_grid.c... +compiling lv_image_header_cache.c... compiling lv_cache_entry.c... compiling lv_bin_decoder.c... -compiling lv_image_header_cache.c... +compiling lv_grid.c... compiling lv_anim_timeline.c... -compiling lv_array.c... -compiling lv_anim.c... +compiling lv_flex.c... +compiling lv_indev.c... compiling lv_async.c... compiling lv_bidi.c... +compiling lv_array.c... +compiling lv_anim.c... compiling lv_circle_buf.c... compiling lv_color_op.c... -compiling lv_grad.c... -compiling lv_color.c... -compiling lv_iter.c... -compiling lv_ll.c... -compiling lv_log.c... compiling lv_area.c... +compiling lv_color.c... +compiling lv_grad.c... +compiling lv_iter.c... +compiling lv_log.c... +compiling lv_ll.c... compiling lv_event.c... compiling lv_lru.c... -compiling lv_fs.c... compiling lv_matrix.c... -compiling lv_palette.c... +compiling lv_fs.c... compiling lv_math.c... compiling lv_templ.c... compiling lv_rb.c... -compiling lv_tree.c... -compiling lv_text_ap.c... -compiling lv_cmsis_rtos2.c... -compiling lv_freertos.c... -compiling lv_utils.c... -compiling lv_linux.c... -compiling lv_mqx.c... +compiling lv_palette.c... compiling lv_style_gen.c... -compiling lv_text.c... -compiling lv_style.c... -compiling lv_os_none.c... +compiling lv_text_ap.c... compiling lv_profiler_builtin.c... +compiling lv_style.c... +compiling lv_cmsis_rtos2.c... +compiling lv_tree.c... +compiling lv_text.c... +compiling lv_utils.c... +compiling lv_freertos.c... +compiling lv_linux.c... compiling lv_timer.c... +compiling lv_mqx.c... +compiling lv_os_none.c... compiling lv_pthread.c... -compiling lv_os.c... -compiling lv_rtthread.c... compiling lv_sdl2.c... compiling lv_windows.c... -compiling lv_mem_core_clib.c... -compiling lv_sprintf_clib.c... -compiling lv_string_builtin.c... -compiling lv_string_clib.c... -compiling lv_mem_core_micropython.c... +compiling lv_rtthread.c... +compiling lv_os.c... compiling lv_mem_core_builtin.c... -compiling lv_mem_core_rtthread.c... +compiling lv_string_builtin.c... compiling lv_sprintf_builtin.c... +compiling lv_sprintf_clib.c... +compiling lv_mem_core_clib.c... +compiling lv_string_clib.c... +compiling lv_tlsf.c... +compiling lv_mem_core_micropython.c... +compiling lv_mem_core_rtthread.c... compiling lv_sprintf_rtthread.c... compiling lv_string_rtthread.c... -compiling lv_tlsf.c... compiling lv_mem_core_uefi.c... compiling lv_mem.c... compiling lv_3dtexture.c... -compiling lv_theme_default.c... -compiling lv_theme.c... -compiling lv_tick.c... compiling lv_animimage.c... +compiling lv_tick.c... +compiling lv_theme_default.c... compiling lv_arc.c... +compiling lv_theme.c... +compiling lv_theme_simple.c... +compiling lv_theme_mono.c... compiling lv_button.c... +compiling lv_bar.c... compiling lv_calendar_chinese.c... compiling lv_calendar_header_arrow.c... compiling lv_calendar_header_dropdown.c... -compiling lv_theme_mono.c... -compiling lv_theme_simple.c... compiling lv_canvas.c... -compiling lv_bar.c... compiling lv_chart.c... -compiling lv_checkbox.c... -compiling lv_calendar.c... -compiling lv_dropdown.c... compiling lv_buttonmatrix.c... +compiling lv_checkbox.c... +compiling lv_dropdown.c... +compiling lv_calendar.c... compiling lv_keyboard.c... -compiling lv_led.c... compiling lv_imagebutton.c... -compiling lv_line.c... -compiling lv_list.c... -compiling lv_lottie.c... +compiling lv_led.c... compiling lv_image.c... -compiling lv_label.c... +compiling lv_line.c... +compiling lv_lottie.c... +compiling lv_list.c... compiling lv_objx_templ.c... +compiling lv_label.c... compiling lv_dropdown_properties.c... compiling lv_msgbox.c... -compiling lv_menu.c... compiling lv_animimage_properties.c... -compiling lv_style_properties.c... -compiling lv_keyboard_properties.c... +compiling lv_menu.c... compiling lv_image_properties.c... -compiling lv_roller_properties.c... -compiling lv_obj_properties.c... -compiling lv_slider_properties.c... +compiling lv_keyboard_properties.c... compiling lv_label_properties.c... +compiling lv_obj_properties.c... +compiling lv_style_properties.c... +compiling lv_roller_properties.c... +compiling lv_slider_properties.c... compiling lv_textarea_properties.c... compiling lv_roller.c... compiling lv_scale.c... compiling lv_slider.c... -compiling lv_spinbox.c... compiling lv_span.c... +compiling lv_spinbox.c... compiling lv_switch.c... compiling lv_table.c... -compiling lv_spinner.c... compiling ui_net_sink.c... +compiling lv_spinner.c... compiling lv_tileview.c... compiling lv_tabview.c... compiling lv_win.c... -compiling ui_font_cn_12.c... -compiling ui_port.c... compiling ui_menu.c... +compiling ui_port.c... +compiling ui_font_cn_12.c... compiling lv_textarea.c... linking... -Program Size: Code=212836 RO-data=9684 RW-data=440 ZI-data=106204 +Program Size: Code=213444 RO-data=9684 RW-data=440 ZI-data=124640 FromELF: creating hex file... ".\STM32F407-Demo\STM32F407-Demo.axf" - 0 Error(s), 0 Warning(s). -Build Time Elapsed: 00:00:20 +Build Time Elapsed: 00:00:26 diff --git a/MDK-ARM/download.bat b/MDK-ARM/download.bat new file mode 100644 index 00000000..1d186c3b --- /dev/null +++ b/MDK-ARM/download.bat @@ -0,0 +1,19 @@ +@echo off +setlocal + +set UV4=C:\Keil_v5\UV4\UV4.exe +set PROJ=%~dp0STM32F407-Demo.uvprojx +set LOG=%~dp0download_log.txt +set TARGET=STM32F407-Demo + +"%UV4%" -f "%PROJ%" -t%TARGET% -j0 -o "%LOG%" + +findstr /C:"Application running" "%LOG%" >nul +if %ERRORLEVEL% equ 0 ( + echo DOWNLOAD_OK: flashed and running + exit /b 0 +) + +echo DOWNLOAD_FAIL: no application-running marker in log +type "%LOG%" +exit /b 1 diff --git a/MDK-ARM/download_log.txt b/MDK-ARM/download_log.txt new file mode 100644 index 00000000..ed7fc35e --- /dev/null +++ b/MDK-ARM/download_log.txt @@ -0,0 +1,3 @@ +Load "D:\\Code\\DTU ³ÌÐò\\STM32F4-Base\\MDK-ARM\\STM32F407-Demo\\STM32F407-Demo.axf" +Erase Done.Programming Done.Verify OK.Application running ... +Flash Load finished at 15:05:40 diff --git a/STM32F407-Demo.ioc b/STM32F407-Demo.ioc index 4abea453..a36c7bb6 100644 --- a/STM32F407-Demo.ioc +++ b/STM32F407-Demo.ioc @@ -70,12 +70,12 @@ Dma.USART1_TX.0.PeriphInc=DMA_PINC_DISABLE Dma.USART1_TX.0.Priority=DMA_PRIORITY_LOW Dma.USART1_TX.0.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode FREERTOS.Events01=sys_evt,Dynamic,NULL -FREERTOS.FootprintOK=true +FREERTOS.FootprintOK=false FREERTOS.IPParameters=Tasks01,configENABLE_FPU,FootprintOK,configTOTAL_HEAP_SIZE,Queues01,Events01 FREERTOS.Queues01=netMsgQueue,8,net_msg_t,0,Dynamic,NULL,NULL -FREERTOS.Tasks01=defaultTask,24,512,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;netTask,24,1024,StartNetTask,Default,NULL,Dynamic,NULL,NULL;ftpTask,24,1024,StartFtpTask,As weak,NULL,Dynamic,NULL,NULL;testTask,16,4096,start_test_task,As weak,NULL,Dynamic,NULL,NULL +FREERTOS.Tasks01=defaultTask,24,512,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;netTask,24,1024,StartNetTask,Default,NULL,Dynamic,NULL,NULL;ftpTask,24,1024,StartFtpTask,As weak,NULL,Dynamic,NULL,NULL;testTask,16,4096,start_test_task,As weak,NULL,Dynamic,NULL,NULL;uiTask,16,4096,StartUiTask,Default,NULL,Dynamic,NULL,NULL FREERTOS.configENABLE_FPU=1 -FREERTOS.configTOTAL_HEAP_SIZE=30720 +FREERTOS.configTOTAL_HEAP_SIZE=49152 File.Version=6 GPIO.groupedBy=Group By Peripherals KeepUserPlacement=false diff --git a/Src/freertos.c b/Src/freertos.c index 6a89bbba..3bf4bea8 100644 --- a/Src/freertos.c +++ b/Src/freertos.c @@ -63,13 +63,6 @@ const osThreadAttr_t timeSyncTask_attributes = { .stack_size = 512 * 4, .priority = (osPriority_t) osPriorityNormal, }; -/* UI 任务:LVGL 离屏渲染 + RemoDispBus 网络推流(优先级低于 netTask) */ -osThreadId_t uiTaskHandle; -const osThreadAttr_t uiTask_attributes = { - .name = "uiTask", - .stack_size = 1024 * 4, - .priority = (osPriority_t) osPriorityBelowNormal, -}; /* USER CODE END Variables */ /* Definitions for defaultTask */ osThreadId_t defaultTaskHandle; @@ -99,6 +92,13 @@ const osThreadAttr_t testTask_attributes = { .stack_size = 4096 * 4, .priority = (osPriority_t) osPriorityBelowNormal, }; +/* Definitions for uiTask */ +osThreadId_t uiTaskHandle; +const osThreadAttr_t uiTask_attributes = { + .name = "uiTask", + .stack_size = 4096 * 4, + .priority = (osPriority_t) osPriorityBelowNormal, +}; /* Definitions for netMsgQueue */ osMessageQueueId_t netMsgQueueHandle; const osMessageQueueAttr_t netMsgQueue_attributes = { @@ -119,6 +119,7 @@ void StartDefaultTask(void *argument); void StartNetTask(void *argument); void StartFtpTask(void *argument); void start_test_task(void *argument); +void StartUiTask(void *argument); void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */ @@ -165,6 +166,9 @@ void MX_FREERTOS_Init(void) { /* creation of testTask */ testTaskHandle = osThreadNew(start_test_task, NULL, &testTask_attributes); + /* creation of uiTask */ + uiTaskHandle = osThreadNew(StartUiTask, NULL, &uiTask_attributes); + /* USER CODE BEGIN RTOS_THREADS */ #ifdef ENABLE_NET_LAYER_TESTS /* NET 层测试模式(阶段 6~10):完整网络栈,netTask 负责轮询与消息处理 */ @@ -173,9 +177,6 @@ void MX_FREERTOS_Init(void) { /* 本地时间同步任务(PC 推送 TCP):依赖 netTask 处理消息队列 */ timeSyncTaskHandle = osThreadNew(timeSyncTask, NULL, &timeSyncTask_attributes); - - /* UI 任务:LVGL 离屏渲染 + RemoDispBus 推流(依赖 netTask 收发) */ - uiTaskHandle = osThreadNew(ui_task_func, NULL, &uiTask_attributes); /* USER CODE END RTOS_THREADS */ /* Create the event(s) */ @@ -298,6 +299,20 @@ __weak void start_test_task(void *argument) /* USER CODE END start_test_task */ } +/* USER CODE BEGIN Header_StartUiTask */ +/** +* @brief Function implementing the uiTask thread. +* @param argument: Not used +* @retval None +*/ +/* USER CODE END Header_StartUiTask */ +void StartUiTask(void *argument) +{ + /* USER CODE BEGIN StartUiTask */ + ui_task_func(argument); + /* USER CODE END StartUiTask */ +} + /* Private application code --------------------------------------------------*/ /* USER CODE BEGIN Application */ diff --git a/docs/测试说明/serial-mcp-server_串口调试.md b/docs/测试说明/serial-mcp-server_串口调试.md new file mode 100644 index 00000000..29f37b2b --- /dev/null +++ b/docs/测试说明/serial-mcp-server_串口调试.md @@ -0,0 +1,106 @@ +# serial-mcp-server 串口调试指南 + +通过 **Adancurusul/serial-mcp-server**(Rust,MIT)自主读取/控制 **USART1 串口**(PA9 TX / PA10 RX,115200bps)的打印流(MCU printf)。 + +## 项目与安装 + +- 仓库:https://github.com/Adancurusul/serial-mcp-server +- 本机安装:`%LOCALAPPDATA%\serial-mcp-server\serial-mcp-server.exe`(自源码 `cargo build --release` 编译,v0.3.0) +- 两种形态: + - **MCP stdio server**(`serve` 子命令)—— opencode.json 中 `mcp.serial`,工具带 `serial_` 前缀 + - **CLI**(`list-ports` / `open` / `read` / `write` / `close` 等)—— 适合脚本化/一次性抓取 +- **串口端口单一配置源:`.opencode/serial_config.json`**(当前本机 `COM6` / 115200)。端口变化只改这个文件 + +## 核心能力边界(重要) + +**⚠️ 该项目没有"读取历史消息/历史缓冲"功能**(对比 qarnet/serial-mcp 有常驻环形缓冲): + +- MCP `read` 只能读到**调用之后**到达的字节,之前的数据直接丢弃 +- 连接打开期间**没有后台 ring buffer**(源码 `src/serial/connection.rs` 只持有流,无历史) +- 抓开机日志必须用 **采集窗口模式**(`duration_ms` + `start_trigger=first-byte`):**先启动采集等第一字节,再触发设备输出**,否则错过 + +## 采集窗口模式(capture) + +这是该项目抓完整连续输出的唯一方式,等价于"有边界的持续读"。 + +### CLI 参数 + +``` +serial-mcp-server read --port COM6 --baud 115200 \ + --duration-ms 30000 \ # 采集窗口时长(从触发点起算) + --start-trigger first-byte \ # 触发方式:first-byte(默认)/ immediate + --initial-timeout-ms 90000 \ # first-byte 模式最多等多久来第一字节(超时空返回) + --idle-timeout-ms 8000 \ # 采集开始后连续静默多久提前结束 + --max-bytes 65536 \ # 累计字节硬上限 + --json +``` + +### 结束原因(completion_reason) + +| 值 | 含义 | +|---|---| +| `initial_timeout` | 等 first-byte 超时,返回空(不算错误) | +| `duration_elapsed` | 从触发点采满 `duration-ms` | +| `idle_timeout` | 采集中途静默超过 `idle-timeout-ms` 提前收工 | +| `max_bytes` | 累计达 `max-bytes` | + +### 返回结构 + +`read` 对象含:`data`(合并完整字节)、`bytes_read`、`status`(ok/timeout)、`completion_reason`、`waited_ms`(等到首字节耗时)、`elapsed_ms`(采集时长)、`chunks[]`(每块 offset/bytes_read/waited_ms/elapsed_ms)。 + +## 抓取 MCU 开机日志(@test 核心场景) + +**顺序必须:先启动采集(打开 COM 等 first-byte)→ 再烧录触发复位 → 采集自动抓取。** + +```powershell +# 一键:采集(含 EOF 重试)→ 烧录 → 提取日志 → verify 校验 +powershell -ExecutionPolicy Bypass -File "D:\Code\DTU 程序\STM32F4-Base\test\run_boot_test.ps1" ` + -Port COM6 -BaudRate 115200 -DurationMs 30000 -InitialTimeoutMs 90000 ` + -IdleTimeoutMs 8000 -MaxBytes 65536 ` + -FlashCmd "D:\Code\DTU 程序\STM32F4-Base\MDK-ARM\download.bat" ` + -Required "App init OK;Network Init;net init: OK;PHY: 100M FULL" ` + -Forbidden "FAIL;ERROR;HardFault" +``` + +退出码:0=PASS / 1=FAIL / 2=采集或烧录错误。 + +### 底层脚本(`test/serial_capture.ps1`) + +- **`-FlashCmd` 分支**:一个调用内完成 稳定启动采集 → 烧录 → 等采集结束,避免工具调用间隔导致采集超时 +- **EOF 自动重试**:COM 被上一个进程释放后立即重开会报 + `UnexpectedEof: serial stream returned zero bytes during capture`(tokio-serial 读 0 字节,第三方项目 capture.rs 视为致命错误)。脚本检测到后退避 1.5s 重开,最多 3 次 +- 采集稳定启动判据:进程存活超过 `-StabilityCheckMs`(默认 1.5s) + +### 参数经验值(本机固件) + +| 参数 | 值 | 理由 | +|---|---|---| +| `idle-timeout-ms` | 8000 | boot 阶段有自然静默间隙(defaultTask 与 netTask 之间约 3~4s),太小会在网络初始化前提前结束 | +| `initial-timeout-ms` | 90000 | 容错:烧录编译+下载耗时可能 40s+,采集必须等得住 | +| `duration-ms` | 30000 | 覆盖完整开机流程(boot ~5s + net init ~3s + FTP 延迟 7s) | + +## 交互式调试(MCP) + +用于主动下发命令、读取响应(非开机日志抓取): + +1. `serial_list_ports` → 找 `COM6`(读 `.opencode/serial_config.json` 确认) +2. `serial_open`(port=COM6, baud_rate=115200)→ 返回 `connection_id`(UUID) +3. `serial_write`(connection_id, data, encoding=utf8/hex/base64) +4. `serial_read`(connection_id, timeout_ms=1000)→ 单次读,只能读到调用后的字节 +5. `serial_close` + +MCP `read` 也支持采集窗口字段:`duration_ms`、`start_trigger`、`initial_timeout_ms`、`idle_timeout_ms`(传 `duration_ms` 即进入 capture 模式)。 + +## 常见陷阱 + +| 现象 | 原因/处理 | +|---|---| +| `UnexpectedEof: serial stream returned zero bytes` | COM 刚被释放立即重开(tokio-serial 读 0 字节)。用 `serial_capture.ps1`(含重试)或间隔几秒再开 | +| 抓到日志缺少 `Network Init` | `idle-timeout-ms` 太小,在网络初始化前的静默间隙提前结束;调到 8000ms | +| 烧录后无数据 | 采集必须在烧录**前**启动;`initial-timeout-ms` 太短导致采集先超时 | +| `Error: Connection ID ... not found` | connection_id 是每次 open 生成的 UUID,读/写/关都要用它 | +| 端口被占用 | 先 `serial_close` 释放;Keil ST-Link 下载不占 COM,可并存 | + +## 工具历史 + +本项目是 Serial Studio MCP 的替代方案(更轻量、无 GUI 依赖、无导出文件定位问题)。Serial Studio 相关配置、bridge 与文档已全部清理。 diff --git a/docs/测试说明/自动化测试流程.md b/docs/测试说明/自动化测试流程.md new file mode 100644 index 00000000..59e9bd0b --- /dev/null +++ b/docs/测试说明/自动化测试流程.md @@ -0,0 +1,79 @@ +# 自动化测试流程(AI 迭代闭环) + +> 目标:AI 改完代码后,可自动完成「构建 → 烧录 → 抓取串口调试信息 → 校验 → 迭代」,无需人工开终端/串口助手。 + +## 总体流程 + +``` +@test 命令触发 + │ + ├─ Step 0 预检 serial-mcp-server 二进制 + list-ports 确认 COM + ├─ Step 1 构建验证 MDK-ARM/build.bat (0 错误 0 警告才继续) + ├─ Step 2 烧录 + 采集(一条命令编排,先采集等 first-byte 再烧录) + │ test/run_boot_test.ps1 (内含 serial_capture.ps1 + download.bat + verify_log.ps1) + ├─ Step 3 判定: + │ PASS → 报告,结束 + │ FAIL → 依据 [MISS]/[HIT] + 日志尾部,修改代码后回到 Step 1 +``` + +## 命令与脚本清单 + +| 名称 | 位置 | 作用 | +|---|---|---| +| `@build` | `.opencode/command/build.md` | 仅编译验证(0 错误 0 警告) | +| `@download` | `.opencode/command/download.md` | 编译并烧录(ST-Link) | +| `@test` | `.opencode/command/test.md` | 完整闭环:build → capture+download → verify → iterate | +| `test/run_boot_test.ps1` | `test/run_boot_test.ps1` | **一键闭环**:采集(含 EOF 重试)→ 烧录 → 提取日志 → verify,退出码 0=PASS/1=FAIL/2=错误 | +| `test/serial_capture.ps1` | `test/serial_capture.ps1` | 底层采集编排:稳定启动采集 → 烧录 → 等采集结束;内置 EOF 自动重试 | +| `test/verify_log.ps1` | `test/verify_log.ps1` | 对日志做标记校验,输出 PASS/FAIL 并返回退出码 | +| serial-mcp-server | `%LOCALAPPDATA%\serial-mcp-server\serial-mcp-server.exe` | CLI/MCP 串口工具(Adancurusul/serial-mcp-server) | + +## 判定标准 + +- **构建**:`@build` 退出码 0(0 错误 0 警告),否则不进入硬件环节。 +- **烧录**:`@download` 退出码 0(`run_boot_test.ps1` 内部调用)。 +- **日志**:`verify_log.ps1` 退出码 + - `0` = PASS:所有 `-Required` 标记均出现,且无 `-Forbidden` 模式命中 + - `1` = FAIL:存在缺失标记(`[MISS]`)或违规模式(`[HIT]`) + - `2` = 日志文件不可读 + +### 默认开机标记(来自固件) + +| 标记 | 来源 | +|---|---| +| `App init OK` | `App/app_main.c` | +| `RS485 init: OK` | `App/app_main.c` | +| `NAND init:` | `App/app_main.c` | +| `ADC init:` | `App/app_main.c` | +| `SD2506 ID:` / `time:` / `temp:` / `battery:` | `App/app_main.c` | +| `Network Init` / `net init: OK` / `MAC:` / `PHY:` | `App/task/net_task.c` | + +### 失败模式应对 + +| 现象 | 处理 | +|---|---| +| `serial-mcp-server` 不存在 | 需先编译安装(见 `docs/测试说明/serial-mcp-server_串口调试.md`) | +| 采集报 `UnexpectedEof: zero bytes` | 已由 `serial_capture.ps1` 自动重试处理;若仍失败,检查 COM 是否被其他程序占用 | +| 抓到日志缺 `Network Init` | `IdleTimeoutMs` 太小(boot 阶段有 ~3-4s 静默间隙),调到 8000ms | +| 烧录后无数据 | 采集必须在烧录**前**启动(`run_boot_test.ps1` 已保证);`InitialTimeoutMs` 需覆盖编译+烧录耗时(90000) | +| 端口被占用 | 先 `serial_close` / 结束残留 `serial-mcp-server` 进程;Keil ST-Link 不占 COM | +| 日志中无 UI 相关输出 | 见下节「UI 自动化」 | + +## UI 屏幕自动化(当前缺口) + +`App/task/ui_task.c` 目前没有 DBG 打印,串口无法确认 UI 是否成功初始化。建议在固件中补充: + +```c +if (ui_port_init() != 0) { DBG_ERROR("UI port init: FAIL"); } else { DBG_INFO("UI port init: OK"); } +if (ui_menu_init() != 0) { DBG_ERROR("UI menu init: FAIL"); } else { DBG_INFO("UI menu init: OK"); } +``` + +加完后把 `UI port init: OK` / `UI menu init: OK` 加入 `@test` 的 `-Required` 标记,即可自动回归 UI 初始化。屏幕上的**内容**仍需人工目检或后续接入截屏/控制类方案。 + +## 扩展用法 + +- **单模块回归**:`@test` 后接 `-Required` 定制,例如网络回归用 + `-Required "App init OK;Network Init;net init: OK;PHY: 100M FULL"`。 +- **测试套件构建**:`test/test_config.h` 开启某个 `TEST_SUITE_*` 后,把套件打印的 + `=== ... Test:` 标题作为 Required 标记,配合 `test/` 下各用例输出判断。 +- **长时间采集**:serial-mcp-server 支持 `--duration-ms` 长窗口采集(MCP `read` 传 `duration_ms` 即进入 capture 模式)。 diff --git a/opencode.json b/opencode.json new file mode 100644 index 00000000..942fc7e3 --- /dev/null +++ b/opencode.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://opencode.ai/config.json", + "mcp": { + "serial": { + "type": "local", + "command": [ + "C:\\Users\\16241\\AppData\\Local\\serial-mcp-server\\serial-mcp-server.exe", + "serve" + ], + "enabled": true, + "timeout": 30000 + } + } +} diff --git a/test/run_boot_test.ps1 b/test/run_boot_test.ps1 new file mode 100644 index 00000000..e11d6812 --- /dev/null +++ b/test/run_boot_test.ps1 @@ -0,0 +1,113 @@ +<# +run_boot_test.ps1 - Full automated boot-log capture + verify in one call. + +Orchestrates: + 1. serial_capture.ps1 (with -FlashCmd): stable-start capture -> flash -> collect + 2. Extract the captured text from the JSON + 3. verify_log.ps1 on the extracted boot log + +Usage: + powershell -ExecutionPolicy Bypass -File run_boot_test.ps1 ` + [-Port COM6] [-BaudRate 115200] ` + [-DurationMs 30000] [-InitialTimeoutMs 90000] [-IdleTimeoutMs 8000] ` + [-FlashCmd "D:\...\download.bat"] ` + [-Required "App init OK;Network Init;net init: OK;PHY: 100M FULL"] ` + [-Forbidden "FAIL;ERROR;HardFault"] ` + [-OutDir C:\temp] [-ShowTailLines 15] + +Exit codes: + 0 = PASS (captured + all required markers + no forbidden) + 1 = FAIL (marker miss / forbidden hit) + 2 = capture or flash error + 3 = verify file error + +NOTE: ASCII-only on purpose - project source files contain Chinese and must +never be rewritten by encoding-sensitive PowerShell. Keep this script ASCII. +#> + +[CmdletBinding()] +param( + [string]$Port = "COM6", + [int]$BaudRate = 115200, + [int]$DurationMs = 30000, + [int]$InitialTimeoutMs = 90000, + [int]$IdleTimeoutMs = 8000, + [int]$MaxBytes = 65536, + [string]$FlashCmd = "D:\Code\DTU 程序\STM32F4-Base\MDK-ARM\download.bat", + [string]$Required = "App init OK;Network Init;net init: OK", + [string]$Forbidden = "FAIL;ERROR;HardFault", + [string]$OutDir = "$env:TEMP\opencode", + [int]$ShowTailLines = 15 +) + +$ErrorActionPreference = 'Stop' + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$captureScript = Join-Path $scriptDir "serial_capture.ps1" +$verifyScript = Join-Path $scriptDir "verify_log.ps1" + +if (-not (Test-Path -LiteralPath $captureScript)) { + Write-Output "RUN_TEST_ERROR: serial_capture.ps1 not found" + exit 2 +} +if (-not (Test-Path -LiteralPath $verifyScript)) { + Write-Output "RUN_TEST_ERROR: verify_log.ps1 not found" + exit 2 +} + +if (-not (Test-Path -LiteralPath $OutDir)) { + New-Item -ItemType Directory -Path $OutDir -Force | Out-Null +} + +$stamp = Get-Date -Format "yyyyMMdd_HHmmss" +$capJson = Join-Path $OutDir "boot_capture_$stamp.json" +$bootTxt = Join-Path $OutDir "boot_log_$stamp.txt" + +Write-Output "=== Boot test $stamp ===" +Write-Output "Capture: $capJson" + +& $captureScript -OutFile $capJson -Port $Port -BaudRate $BaudRate ` + -DurationMs $DurationMs -InitialTimeoutMs $InitialTimeoutMs ` + -IdleTimeoutMs $IdleTimeoutMs -MaxBytes $MaxBytes ` + -FlashCmd $FlashCmd -MaxRetries 3 -RetryDelayMs 1500 +$capRc = $LASTEXITCODE +if ($capRc -ne 0) { + Write-Output "RUN_TEST_ERROR: capture failed (rc=$capRc)" + exit 2 +} + +if (-not (Test-Path -LiteralPath $capJson)) { + Write-Output "RUN_TEST_ERROR: no capture JSON at $capJson" + exit 2 +} + +try { + $json = Get-Content -LiteralPath $capJson -Raw -Encoding UTF8 | ConvertFrom-Json +} catch { + Write-Output "RUN_TEST_ERROR: cannot parse capture JSON: $_" + exit 2 +} + +if (-not $json.read) { + Write-Output "RUN_TEST_ERROR: capture JSON has no 'read' object" + exit 2 +} + +Write-Output ("Capture: bytes_read={0} completion_reason={1} waited_ms={2} elapsed_ms={3}" -f ` + $json.read.bytes_read, $json.read.completion_reason, $json.read.waited_ms, $json.read.elapsed_ms) + +$data = [string]$json.read.data +if ([string]::IsNullOrEmpty($data)) { + Write-Output "RUN_TEST_ERROR: capture returned no data (device silent?)" + Write-Output "Hint: raise -InitialTimeoutMs if flashing is slow, or check the device boots." + exit 2 +} + +[System.IO.File]::WriteAllText($bootTxt, $data, [System.Text.Encoding]::UTF8) +Write-Output "Boot log: $bootTxt" + +& $verifyScript -LogPath $bootTxt -Required $Required -Forbidden $Forbidden ` + -ShowTailLines $ShowTailLines +$verifyRc = $LASTEXITCODE +Write-Output "RUN_TEST_RC=$verifyRc" +exit $verifyRc diff --git a/test/serial_capture.ps1 b/test/serial_capture.ps1 new file mode 100644 index 00000000..a4dae639 --- /dev/null +++ b/test/serial_capture.ps1 @@ -0,0 +1,159 @@ +<# +serial_capture.ps1 - Capture a serial boot log with serial-mcp-server CLI. + +Workflow (deterministic boot-log capture after flashing): +1. Open COM via serial-mcp-server `read` CAPTURE mode and WAIT for the first + byte (up to -InitialTimeoutMs). This starts BEFORE flashing. +2. Flash the target (download.bat) -> MCU resets and prints the boot log. +3. The running capture picks up the first byte, then collects for -DurationMs. +4. The process exits and writes a single structured JSON file at -OutFile. + +Known trap (Adancurusul/serial-mcp-server, fixed here by retry): + When COM is released by a previous process and reopened immediately, + tokio-serial read returns 0 bytes and the project's capture.rs turns that + into a fatal `UnexpectedEof` error (serial stream returned zero bytes). + This is transient: a short backoff then reopening succeeds. This script + detects that error, backs off, and restarts the capture. + +Usage: + powershell -ExecutionPolicy Bypass -File serial_capture.ps1 ` + -OutFile C:\temp\boot.json ` + [-Port COM6] [-BaudRate 115200] ` + [-DurationMs 20000] [-InitialTimeoutMs 60000] ` + [-IdleTimeoutMs 1500] [-MaxBytes 65536] ` + [-FlashCmd "D:\...\download.bat"] ` + [-MaxRetries 3] [-RetryDelayMs 1500] + +Modes: + -FlashCmd: orchestrates the whole sequence in ONE call: (retry-stable) + start capture, run the flash/download command, then wait for capture to + finish. Use this to avoid tool-call gaps letting the capture timeout before + flashing. + No -FlashCmd: starts the capture, waits for it to be stable (survives + -StabilityCheckMs), prints the PID, and returns. Caller flashes, then calls + with -WaitOnly to reap. + +Exit codes: + 0 = capture completed (ok or clean timeout) + 1 = capture failed to start after retries + 2 = capture error status in JSON + +NOTE: ASCII-only on purpose - project source files contain Chinese and must +never be rewritten by encoding-sensitive PowerShell. Keep this script ASCII. +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [string]$OutFile, + + [string]$Port = "COM6", + [int]$BaudRate = 115200, + [int]$DurationMs = 20000, + [int]$InitialTimeoutMs = 60000, + [int]$IdleTimeoutMs = 1500, + [int]$MaxBytes = 65536, + [string]$FlashCmd = "", + [int]$MaxRetries = 3, + [int]$RetryDelayMs = 1500, + [int]$StabilityCheckMs = 1500 +) + +$ErrorActionPreference = 'Stop' + +$exe = "$env:LOCALAPPDATA\serial-mcp-server\serial-mcp-server.exe" +if (-not (Test-Path -LiteralPath $exe)) { + Write-Output "CAPTURE_ERROR: serial-mcp-server binary not found: $exe" + exit 1 +} + +$outDir = Split-Path -Parent $OutFile +if ($outDir -and -not (Test-Path -LiteralPath $outDir)) { + New-Item -ItemType Directory -Path $outDir -Force | Out-Null +} + +$baseArgs = @( + "read", + "--port", $Port, + "--baud", ([string]$BaudRate), + "--duration-ms", ([string]$DurationMs), + "--start-trigger", "first-byte", + "--initial-timeout-ms", ([string]$InitialTimeoutMs), + "--idle-timeout-ms", ([string]$IdleTimeoutMs), + "--max-bytes", ([string]$MaxBytes), + "--json" +) + +function Start-CaptureProcess { + param([int]$attempt) + $out = $OutFile + $err = "$OutFile.err" + if (Test-Path -LiteralPath $out) { Remove-Item -LiteralPath $out -Force } + if (Test-Path -LiteralPath $err) { Remove-Item -LiteralPath $err -Force } + if ($attempt -gt 0) { + Write-Host "RETRY: capture attempt $attempt (backoff $RetryDelayMs ms)" + Start-Sleep -Milliseconds $RetryDelayMs + } + $p = Start-Process -FilePath $exe -ArgumentList $baseArgs ` + -RedirectStandardOutput $out -RedirectStandardError $err ` + -WindowStyle Hidden -PassThru + if ($p -eq $null -or $p.HasExited) { + Write-Output "CAPTURE_ERROR: process failed to start (attempt $attempt)" + return $null + } + return $p +} + +function Test-CaptureErrored { + $err = "$OutFile.err" + if (Test-Path -LiteralPath $err) { + $e = Get-Content -LiteralPath $err -Raw -ErrorAction SilentlyContinue + if ($e -match "UnexpectedEof|zero bytes|Error:") { return $true } + } + return $false +} + +$proc = $null +for ($i = 0; $i -le $MaxRetries; $i++) { + $proc = Start-CaptureProcess -attempt $i + if ($proc -eq $null) { continue } + + # Wait a moment: if the process died with EOF/error immediately, retry. + Start-Sleep -Milliseconds $StabilityCheckMs + if (-not $proc.HasExited) { break } # stable, keep it + if (-not (Test-CaptureErrored)) { break } # exited cleanly, no error + Write-Host "CAPTURE_EOF: transient EOF on attempt $i, retrying..." + $proc = $null +} + +if ($proc -eq $null) { + Write-Output "CAPTURE_ERROR: could not start a stable capture after retries" + exit 1 +} + +Write-Output "CAPTURE_PID: $($proc.Id)" + +if ($FlashCmd) { + if (-not (Test-Path -LiteralPath $FlashCmd)) { + Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue + Write-Output "CAPTURE_ERROR: flash command not found: $FlashCmd" + exit 1 + } + Write-Output "FLASH_START: $FlashCmd" + & $FlashCmd + $flashRc = $LASTEXITCODE + Write-Output "FLASH_RC: $flashRc" + $proc.WaitForExit() + Write-Output "CAPTURE_DONE" + if (Test-Path -LiteralPath $OutFile) { + $content = Get-Content -LiteralPath $OutFile -Raw -ErrorAction SilentlyContinue + if ($content -match '"status":\s*"error"') { + Write-Output "CAPTURE_ERROR: capture returned error status" + Write-Output $content + exit 2 + } + } + exit 0 +} + +exit 0 diff --git a/test/verify_log.ps1 b/test/verify_log.ps1 new file mode 100644 index 00000000..32294206 --- /dev/null +++ b/test/verify_log.ps1 @@ -0,0 +1,102 @@ +<# +verify_log.ps1 - Check a captured MCU debug log against boot/test markers. + +Used by the @test loop: after flashing, the fresh boot log is captured to a +file (serial-mcp-server capture), then this script greps it and prints a +structured verdict. + +Usage: + powershell -ExecutionPolicy Bypass -File verify_log.ps1 ` + -LogPath ` + [-Required 'marker1;marker2'] ` + [-Forbidden 'FAIL;ERROR;HardFault'] ` + [-ShowTailLines 40] + +Exit code: + 0 = PASS (every required marker found, no forbidden pattern matched) + 1 = FAIL (missing required marker or forbidden pattern present) + 2 = ERROR (log file not found / unreadable) + +Default required markers match the STM32F4-Base boot sequence +(see App/app_main.c + App/task/net_task.c). Override per test case, e.g.: + -Required 'App init OK;net init: OK;PHY: 100M FULL' + -Forbidden 'FAIL;ERROR;HardFault;assert' + +NOTE: ASCII-only on purpose - project source files contain Chinese and must +never be rewritten by encoding-sensitive PowerShell. Keep this script ASCII. +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [string]$LogPath, + + [string]$Required = 'App init OK;Network Init;net init: OK', + [string]$Forbidden = 'FAIL;ERROR;HardFault;assert', + [int]$ShowTailLines = 40 +) + +$ErrorActionPreference = 'Stop' + +if (-not (Test-Path -LiteralPath $LogPath)) { + Write-Output "VERIFY_ERROR: log file not found: $LogPath" + exit 2 +} + +try { + $content = Get-Content -LiteralPath $LogPath -Encoding UTF8 -ErrorAction Stop +} catch { + # Fall back to default encoding if UTF-8 read fails + try { + $content = Get-Content -LiteralPath $LogPath -ErrorAction Stop + } catch { + Write-Output "VERIFY_ERROR: cannot read log file: $LogPath" + exit 2 + } +} + +$all = $content -join "`n" + +$requiredList = @($Required -split ';' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' }) +$forbiddenList = @($Forbidden -split ';' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' }) + +Write-Output "=== Log verdict: $LogPath ===" +Write-Output ("Lines: {0}, bytes: {1}" -f $content.Count, (Get-Item -LiteralPath $LogPath).Length) + +$missing = @() +foreach ($m in $requiredList) { + if ($all.Contains($m)) { + Write-Output ("[OK] required marker found: {0}" -f $m) + } else { + Write-Output ("[MISS] required marker NOT found: {0}" -f $m) + $missing += $m + } +} + +$hits = @() +foreach ($f in $forbiddenList) { + $matches = @($content | Where-Object { $_ -like "*$f*" }) + if ($matches.Count -gt 0) { + Write-Output ("[HIT] forbidden pattern: {0} ({1} line(s))" -f $f, $matches.Count) + $hits += $f + } else { + Write-Output ("[OK] forbidden pattern absent: {0}" -f $f) + } +} + +if ($ShowTailLines -gt 0 -and $content.Count -gt 0) { + Write-Output "" + Write-Output "=== Last $ShowTailLines line(s) of log ===" + $start = [Math]::Max(0, $content.Count - $ShowTailLines) + $content[$start..($content.Count - 1)] | ForEach-Object { Write-Output $_ } +} + +if ($missing.Count -gt 0 -or $hits.Count -gt 0) { + Write-Output "" + Write-Output "VERDICT: FAIL" + exit 1 +} + +Write-Output "" +Write-Output "VERDICT: PASS" +exit 0