diff --git a/.env.example b/.env.example index 73a5dbc..d86f1ce 100644 --- a/.env.example +++ b/.env.example @@ -26,12 +26,19 @@ CMAKE_GENERATOR=Ninja OPENOCD_INTERFACE=cmsis-dap.cfg TARGET_CFG=target/imxrt.cfg GDB_PORT=3333 -GDB_EXECUTABLE=gdb-multiarch +GDB_EXECUTABLE=arm-none-eabi-gdb + + +# pyOCD — таргет и частота для gdbserver и flash_swd.py +PYOCD_TARGET=mimxrt1050_quadspi +PYOCD_FREQUENCY=4000000 + +# FCB-бинарник для flash_swd.py (Flash Configuration Block, W25Q128 Quad SPI) +FCB_PATH=tools/host/dcd/w25q128_fdcb.bin # --- HIL (аппаратный стенд) --- -# VCOM-порт MCU-Link (Linux: /dev/ttyACM0, macOS: /dev/cu.usbmodem*) HIL_VCOM_PORT=/dev/tty.usbmodemGUXFBWDJBWTGQ3 HIL_VCOM_BAUD=115200 # default: 115200 HIL_READY_TIMEOUT=5.0 # default: 5.0 сек HIL_PYOCD_FREQUENCY=1000000 # default: 1 МГц -№HIL_BUILD_DIR= # default: build/target-debug +HIL_BUILD_DIR=build/target-debug # default: diff --git a/.vscode/launch.json b/.vscode/launch.json index db441bf..6f98b20 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,21 +15,11 @@ "device": "MIMXRT1052CVJ5B", "svdFile": "${workspaceFolder}/bsp/generated/startup/MIMXRT1052.xml", "interface": "swd", - "showDevDebugOutput": "raw", "loadFiles": [], "runToEntryPoint": "main", "preLaunchTask": "build:firmware-test-debug", "rttConfig": { - "enabled": true, - "address": "auto", - "clearSearch": false, - "decoders": [ - { - "port": 0, - "type": "console", - "label": "RTT" - } - ] + "enabled": false } }, // ============================================================= @@ -42,25 +32,13 @@ "servertype": "external", "gdbTarget": "host.docker.internal:3333", "gdbPath": "arm-none-eabi-gdb", - "executable": "${workspaceFolder}/build/Debug/firmware/bootloader/bootloader.elf", + "executable": "${workspaceFolder}/build/Debug/bootloader.elf", "device": "MIMXRT1052CVJ5B", "svdFile": "${workspaceFolder}/bsp/generated/startup/MIMXRT1052.xml", "interface": "swd", "loadFiles": [], "runToEntryPoint": "main", - "preLaunchTask": "build:bootloader-debug", - "rttConfig": { - "enabled": true, - "address": "auto", - "clearSearch": false, - "decoders": [ - { - "port": 0, - "type": "console", - "label": "RTT" - } - ] - } + "preLaunchTask": "build-and-rtt:firmware-test-debug", }, // ============================================================= // firmware/tft_app — FreeRTOS task view @@ -72,26 +50,14 @@ "servertype": "external", "gdbTarget": "host.docker.internal:3333", "gdbPath": "arm-none-eabi-gdb", - "executable": "${workspaceFolder}/build/Debug/firmware/tft_app/app.elf", + "executable": "${workspaceFolder}/build/Debug/app.elf", "device": "MIMXRT1052CVJ5B", "svdFile": "${workspaceFolder}/bsp/generated/startup/MIMXRT1052.xml", "interface": "swd", "loadFiles": [], "runToEntryPoint": "main", "rtos": "FreeRTOS", - "preLaunchTask": "build:app-debug", - "rttConfig": { - "enabled": true, - "address": "auto", - "clearSearch": false, - "decoders": [ - { - "port": 0, - "type": "console", - "label": "RTT" - } - ] - } + "preLaunchTask": "build:firmware-test-debug", } ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 49522e7..88db8aa 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -169,9 +169,6 @@ "options": { "cwd": "${workspaceFolder}" }, - "problemMatcher": [ - "$gcc" - ], "presentation": { "reveal": "silent", "panel": "shared" @@ -188,9 +185,6 @@ "options": { "cwd": "${workspaceFolder}" }, - "problemMatcher": [ - "$gcc" - ], "presentation": { "reveal": "silent", "panel": "shared" @@ -207,14 +201,11 @@ "options": { "cwd": "${workspaceFolder}" }, - "problemMatcher": [ - "$gcc" - ], "presentation": { "reveal": "silent", "panel": "shared" } - }, + } ], "inputs": [ { diff --git a/FLASH_LOG.log b/FLASH_LOG.log deleted file mode 100644 index b8fad10..0000000 --- a/FLASH_LOG.log +++ /dev/null @@ -1,375 +0,0 @@ -tft_manufacture_test git:(dev) ✗ uv run --directory tools/hil pyocd flash \ - --target mimxrt1050_quadspi \ - --base-address 0x60000000 -vvv \ - --erase chip \ - ../../build/Debug/firmware_test_hab.bin -0000149 D Project directory: /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/hil [session] -0000150 D Project directory: /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/tools/hil [session] -0000152 D CMSIS-DAP v2 probe GUXFBWDJBWTGQ: firmware version 3.160, protocol version 2.1.1 [dap_access_cmsis_dap] -0000154 I Target type is mimxrt1050_quadspi [board] -0000157 D Running task load_svd [sequencer] -0000157 D Running task pre_connect [sequencer] -0000157 D Running task dp_init [sequencer] -0000157 D Running task lock_probe [sequencer] -0000157 D Running task get_probe_capabilities [sequencer] -0000157 D Running task connect [sequencer] -0000161 D Default wire protocol selected; using SWD [dap] -0000162 D Sending deprecated SWJ sequence to select SWD [swj] -0000164 I DP IDR = 0x0bd11477 (v1 MINDP rev0) [dap] -0000164 D Running task clear_sticky_err [sequencer] -0000164 D Running task power_up_debug [sequencer] -0000165 D Running task check_version [sequencer] -0000165 D Running task unlock_probe [sequencer] -0000165 D Running task unlock_device [sequencer] -0000165 D Running task create_discoverer [sequencer] -0000165 D Running task discovery [sequencer] -0000165 D Running task find_aps [sequencer] -0000169 D Running task create_aps [sequencer] -0000169 D Running task create_ap.0 [sequencer] -0000179 D AHB-AP#0 default HPROT=3 HNONSEC=0 [ap] -0000180 D AHB-AP#0 implemented HPROT=f HNONSEC=0 [ap] -0000181 I AHB-AP#0 IDR = 0x04770041 (AHB-AP var4 rev0) [discovery] -0000181 D Running task find_components [sequencer] -0000181 D Running task init_ap.0 [sequencer] -0000185 I AHB-AP#0 Class 0x1 ROM table #0 @ 0xe00fd000 (designer=00e:NXP part=88c) [rom_table] -0000187 I [0] [rom_table] -0000187 I AHB-AP#0 Class 0x1 ROM table #1 @ 0xe00fe000 (designer=43b:Arm part=4c8) [rom_table] -0000191 I [0] [rom_table] -0000191 I AHB-AP#0 Class 0x1 ROM table #2 @ 0xe00ff000 (designer=43b:Arm part=4c7) [rom_table] -0000194 I [0] [rom_table] -0000207 I [1] [rom_table] -0000209 I [2] [rom_table] -0000210 I [3] [rom_table] -0000210 D [4] [rom_table] -0000210 D [5] [rom_table] -0000214 I [1] [rom_table] -0000219 I [2] [rom_table] -0000219 D [3]<1ff02002 not present> [rom_table] -0000221 I [1] [rom_table] -0000222 I [2] [rom_table] -0000222 D [3]<1ff03002 not present> [rom_table] -0000222 D Running task create_cores [sequencer] -0000239 I IMXRT Boot Mode: Internal Boot [target_imxrt] -0000242 I CPU core #0: Cortex-M7 r1p1, v7.0-M architecture [cortex_m] -0000243 I Extensions: [DSP, FPU, FPU_DP, FPU_V5, MPU] [cortex_m] -0000243 I FPU present: FPv5-D16-M [cortex_m] -0000244 D selected core #0 [soc_target] -0000244 D Running task create_components [sequencer] -0000244 D Creating DWT component [discovery] -0000246 I 4 hardware watchpoints [dwt] -0000246 D Creating FPB component [discovery] -0000247 I 8 hardware breakpoints, 1 literal comparators [fpb] -0000247 D fpb has been disabled [fpb] -0000247 D Creating ITM component [discovery] -0000249 D Creating TPIU component [discovery] -0000250 D Running task check_for_cores [sequencer] -0000250 D Running task halt_on_connect [sequencer] -0000250 D halting core 0 [cortex_m] -0000250 D Running task post_connect [sequencer] -0000250 D Running task post_connect_hook [sequencer] -0000250 D Running task create_flash [sequencer] -0000251 D Running task notify [sequencer] -0000251 I IMXRT Boot Mode: Internal Boot [target_imxrt] -0000252 I IMXRT Boot Device: 0 [target_imxrt] -0000252 D normal_set_reset_catch [target_imxrt] -0000252 D set reset catch, core 0 [cortex_m] -0000252 D removing all breakpoints before reset [manager] -0000252 D halting core 0 [cortex_m] -0000254 D reset, core 0, type=VECTRESET [cortex_m] -0000254 D no delegate or debug sequence handled reset; performing VECTRESET reset, core 0 [cortex_m] -0000364 D clear reset catch, core 0 [cortex_m] -0000365 D disabling hardware breakpoints after reset [manager] -0000366 I Loading /Users/von_akimow/Desktop/TFT_ENV/tft_manufacture_test/build/Debug/firmware_test_hab.bin at 0x60000000 [load_cmd] -0000367 D Using double buffer chip erase program [builder] -0000367 D 0 of 89 pages have erased data [builder] -0000367 D halting core 0 [cortex_m] -0000586 D resuming core 0 [cortex_m] -0000586 D added=[] removed=[] [manager] -0000586 D bps after flush={} [manager] -0000592 D resuming core 0 [cortex_m] -0000592 D added=[] removed=[] [manager] -0000593 D bps after flush={} [manager] -0031381 D resuming core 0 [cortex_m] -0031381 D added=[] removed=[] [manager] -0031381 D bps after flush={} [manager] -[ ] 1%0031383 D halting core 0 [cortex_m] -0031392 D resuming core 0 [cortex_m] -0031392 D added=[] removed=[] [manager] -0031392 D bps after flush={} [manager] -0031396 D resuming core 0 [cortex_m] -0031396 D added=[] removed=[] [manager] -0031396 D bps after flush={} [manager] -[= ] 3%0031404 D resuming core 0 [cortex_m] -0031404 D added=[] removed=[] [manager] -0031404 D bps after flush={} [manager] -[= ] 4%0031412 D resuming core 0 [cortex_m] -0031412 D added=[] removed=[] [manager] -0031412 D bps after flush={} [manager] -[== ] 5%0031419 D resuming core 0 [cortex_m] -0031419 D added=[] removed=[] [manager] -0031419 D bps after flush={} [manager] -[== ] 6%0031427 D resuming core 0 [cortex_m] -0031427 D added=[] removed=[] [manager] -0031427 D bps after flush={} [manager] -[=== ] 7%0031435 D resuming core 0 [cortex_m] -0031435 D added=[] removed=[] [manager] -0031435 D bps after flush={} [manager] -[==== ] 8%0031442 D resuming core 0 [cortex_m] -0031442 D added=[] removed=[] [manager] -0031442 D bps after flush={} [manager] -[==== ] 9%0031450 D resuming core 0 [cortex_m] -0031450 D added=[] removed=[] [manager] -0031450 D bps after flush={} [manager] -[===== ] 10%0031458 D resuming core 0 [cortex_m] -0031458 D added=[] removed=[] [manager] -0031458 D bps after flush={} [manager] -[===== ] 11%0031465 D resuming core 0 [cortex_m] -0031465 D added=[] removed=[] [manager] -0031465 D bps after flush={} [manager] -[====== ] 12%0031473 D resuming core 0 [cortex_m] -0031473 D added=[] removed=[] [manager] -0031473 D bps after flush={} [manager] -[====== ] 14%0031481 D resuming core 0 [cortex_m] -0031481 D added=[] removed=[] [manager] -0031481 D bps after flush={} [manager] -[======= ] 15%0031489 D resuming core 0 [cortex_m] -0031489 D added=[] removed=[] [manager] -0031489 D bps after flush={} [manager] -[======= ] 16%0031496 D resuming core 0 [cortex_m] -0031496 D added=[] removed=[] [manager] -0031497 D bps after flush={} [manager] -[======== ] 17%0031504 D resuming core 0 [cortex_m] -0031504 D added=[] removed=[] [manager] -0031504 D bps after flush={} [manager] -[========= ] 18%0031512 D resuming core 0 [cortex_m] -0031512 D added=[] removed=[] [manager] -0031512 D bps after flush={} [manager] -[========= ] 19%0031520 D resuming core 0 [cortex_m] -0031520 D added=[] removed=[] [manager] -0031520 D bps after flush={} [manager] -[========== ] 20%0031528 D resuming core 0 [cortex_m] -0031528 D added=[] removed=[] [manager] -0031528 D bps after flush={} [manager] -[========== ] 21%0031536 D resuming core 0 [cortex_m] -0031536 D added=[] removed=[] [manager] -0031536 D bps after flush={} [manager] -[=========== ] 22%0031544 D resuming core 0 [cortex_m] -0031544 D added=[] removed=[] [manager] -0031544 D bps after flush={} [manager] -[=========== ] 24%0031552 D resuming core 0 [cortex_m] -0031552 D added=[] removed=[] [manager] -0031552 D bps after flush={} [manager] -[============ ] 25%0031560 D resuming core 0 [cortex_m] -0031560 D added=[] removed=[] [manager] -0031560 D bps after flush={} [manager] -[============ ] 26%0031568 D resuming core 0 [cortex_m] -0031568 D added=[] removed=[] [manager] -0031568 D bps after flush={} [manager] -[============= ] 27%0031576 D resuming core 0 [cortex_m] -0031576 D added=[] removed=[] [manager] -0031577 D bps after flush={} [manager] -[============= ] 28%0031584 D resuming core 0 [cortex_m] -0031585 D added=[] removed=[] [manager] -0031585 D bps after flush={} [manager] -[============== ] 29%0031593 D resuming core 0 [cortex_m] -0031593 D added=[] removed=[] [manager] -0031593 D bps after flush={} [manager] -[=============== ] 30%0031601 D resuming core 0 [cortex_m] -0031601 D added=[] removed=[] [manager] -0031601 D bps after flush={} [manager] -[=============== ] 31%0031609 D resuming core 0 [cortex_m] -0031609 D added=[] removed=[] [manager] -0031609 D bps after flush={} [manager] -[================ ] 32%0031617 D resuming core 0 [cortex_m] -0031617 D added=[] removed=[] [manager] -0031617 D bps after flush={} [manager] -[================ ] 34%0031626 D resuming core 0 [cortex_m] -0031626 D added=[] removed=[] [manager] -0031626 D bps after flush={} [manager] -[================= ] 35%0031634 D resuming core 0 [cortex_m] -0031634 D added=[] removed=[] [manager] -0031634 D bps after flush={} [manager] -[================= ] 36%0031643 D resuming core 0 [cortex_m] -0031643 D added=[] removed=[] [manager] -0031643 D bps after flush={} [manager] -[================== ] 37%0031651 D resuming core 0 [cortex_m] -0031651 D added=[] removed=[] [manager] -0031651 D bps after flush={} [manager] -[================== ] 38%0031660 D resuming core 0 [cortex_m] -0031660 D added=[] removed=[] [manager] -0031660 D bps after flush={} [manager] -[=================== ] 39%0031668 D resuming core 0 [cortex_m] -0031668 D added=[] removed=[] [manager] -0031668 D bps after flush={} [manager] -[==================== ] 40%0031677 D resuming core 0 [cortex_m] -0031677 D added=[] removed=[] [manager] -0031677 D bps after flush={} [manager] -[==================== ] 41%0031686 D resuming core 0 [cortex_m] -0031686 D added=[] removed=[] [manager] -0031686 D bps after flush={} [manager] -[===================== ] 42%0031694 D resuming core 0 [cortex_m] -0031694 D added=[] removed=[] [manager] -0031694 D bps after flush={} [manager] -[===================== ] 44%0031703 D resuming core 0 [cortex_m] -0031703 D added=[] removed=[] [manager] -0031703 D bps after flush={} [manager] -[====================== ] 45%0031712 D resuming core 0 [cortex_m] -0031712 D added=[] removed=[] [manager] -0031712 D bps after flush={} [manager] -[====================== ] 46%0031721 D resuming core 0 [cortex_m] -0031721 D added=[] removed=[] [manager] -0031721 D bps after flush={} [manager] -[======================= ] 47%0031729 D resuming core 0 [cortex_m] -0031729 D added=[] removed=[] [manager] -0031729 D bps after flush={} [manager] -[======================= ] 48%0031738 D resuming core 0 [cortex_m] -0031738 D added=[] removed=[] [manager] -0031738 D bps after flush={} [manager] -[======================== ] 49%0031747 D resuming core 0 [cortex_m] -0031747 D added=[] removed=[] [manager] -0031747 D bps after flush={} [manager] -[========================= ] 50%0031756 D resuming core 0 [cortex_m] -0031756 D added=[] removed=[] [manager] -0031756 D bps after flush={} [manager] -[========================= ] 51%0031765 D resuming core 0 [cortex_m] -0031765 D added=[] removed=[] [manager] -0031765 D bps after flush={} [manager] -[========================== ] 52%0031773 D resuming core 0 [cortex_m] -0031773 D added=[] removed=[] [manager] -0031773 D bps after flush={} [manager] -[========================== ] 53%0031782 D resuming core 0 [cortex_m] -0031782 D added=[] removed=[] [manager] -0031782 D bps after flush={} [manager] -[=========================== ] 55%0031791 D resuming core 0 [cortex_m] -0031791 D added=[] removed=[] [manager] -0031791 D bps after flush={} [manager] -[=========================== ] 56%0031800 D resuming core 0 [cortex_m] -0031800 D added=[] removed=[] [manager] -0031800 D bps after flush={} [manager] -[============================ ] 57%0031809 D resuming core 0 [cortex_m] -0031809 D added=[] removed=[] [manager] -0031809 D bps after flush={} [manager] -[============================ ] 58%0031818 D resuming core 0 [cortex_m] -0031818 D added=[] removed=[] [manager] -0031818 D bps after flush={} [manager] -[============================= ] 59%0031827 D resuming core 0 [cortex_m] -0031827 D added=[] removed=[] [manager] -0031827 D bps after flush={} [manager] -[============================== ] 60%0031836 D resuming core 0 [cortex_m] -0031836 D added=[] removed=[] [manager] -0031836 D bps after flush={} [manager] -[============================== ] 61%0031845 D resuming core 0 [cortex_m] -0031845 D added=[] removed=[] [manager] -0031845 D bps after flush={} [manager] -[=============================== ] 62%0031854 D resuming core 0 [cortex_m] -0031854 D added=[] removed=[] [manager] -0031854 D bps after flush={} [manager] -[=============================== ] 63%0031863 D resuming core 0 [cortex_m] -0031863 D added=[] removed=[] [manager] -0031863 D bps after flush={} [manager] -[================================ ] 65%0031872 D resuming core 0 [cortex_m] -0031872 D added=[] removed=[] [manager] -0031872 D bps after flush={} [manager] -[================================ ] 66%0031881 D resuming core 0 [cortex_m] -0031881 D added=[] removed=[] [manager] -0031881 D bps after flush={} [manager] -[================================= ] 67%0031890 D resuming core 0 [cortex_m] -0031890 D added=[] removed=[] [manager] -0031890 D bps after flush={} [manager] -[================================= ] 68%0031899 D resuming core 0 [cortex_m] -0031899 D added=[] removed=[] [manager] -0031899 D bps after flush={} [manager] -[================================== ] 69%0031908 D resuming core 0 [cortex_m] -0031908 D added=[] removed=[] [manager] -0031908 D bps after flush={} [manager] -[=================================== ] 70%0031918 D resuming core 0 [cortex_m] -0031918 D added=[] removed=[] [manager] -0031918 D bps after flush={} [manager] -[=================================== ] 71%0031927 D resuming core 0 [cortex_m] -0031927 D added=[] removed=[] [manager] -0031927 D bps after flush={} [manager] -[==================================== ] 72%0031936 D resuming core 0 [cortex_m] -0031936 D added=[] removed=[] [manager] -0031936 D bps after flush={} [manager] -[==================================== ] 73%0031945 D resuming core 0 [cortex_m] -0031945 D added=[] removed=[] [manager] -0031945 D bps after flush={} [manager] -[===================================== ] 75%0031954 D resuming core 0 [cortex_m] -0031954 D added=[] removed=[] [manager] -0031954 D bps after flush={} [manager] -[===================================== ] 76%0031963 D resuming core 0 [cortex_m] -0031963 D added=[] removed=[] [manager] -0031963 D bps after flush={} [manager] -[====================================== ] 77%0031972 D resuming core 0 [cortex_m] -0031972 D added=[] removed=[] [manager] -0031972 D bps after flush={} [manager] -[====================================== ] 78%0031981 D resuming core 0 [cortex_m] -0031981 D added=[] removed=[] [manager] -0031981 D bps after flush={} [manager] -[======================================= ] 79%0031990 D resuming core 0 [cortex_m] -0031990 D added=[] removed=[] [manager] -0031990 D bps after flush={} [manager] -[======================================== ] 80%0032000 D resuming core 0 [cortex_m] -0032000 D added=[] removed=[] [manager] -0032000 D bps after flush={} [manager] -[======================================== ] 81%0032009 D resuming core 0 [cortex_m] -0032009 D added=[] removed=[] [manager] -0032009 D bps after flush={} [manager] -[========================================= ] 82%0032018 D resuming core 0 [cortex_m] -0032018 D added=[] removed=[] [manager] -0032018 D bps after flush={} [manager] -[========================================= ] 83%0032027 D resuming core 0 [cortex_m] -0032027 D added=[] removed=[] [manager] -0032027 D bps after flush={} [manager] -[========================================== ] 84%0032036 D resuming core 0 [cortex_m] -0032036 D added=[] removed=[] [manager] -0032036 D bps after flush={} [manager] -[========================================== ] 86%0032045 D resuming core 0 [cortex_m] -0032045 D added=[] removed=[] [manager] -0032045 D bps after flush={} [manager] -[=========================================== ] 87%0032054 D resuming core 0 [cortex_m] -0032054 D added=[] removed=[] [manager] -0032054 D bps after flush={} [manager] -[=========================================== ] 88%0032063 D resuming core 0 [cortex_m] -0032063 D added=[] removed=[] [manager] -0032063 D bps after flush={} [manager] -[============================================ ] 89%0032072 D resuming core 0 [cortex_m] -0032072 D added=[] removed=[] [manager] -0032072 D bps after flush={} [manager] -[============================================= ] 90%0032082 D resuming core 0 [cortex_m] -0032082 D added=[] removed=[] [manager] -0032082 D bps after flush={} [manager] -[============================================= ] 91%0032091 D resuming core 0 [cortex_m] -0032091 D added=[] removed=[] [manager] -0032091 D bps after flush={} [manager] -[============================================== ] 92%0032100 D resuming core 0 [cortex_m] -0032100 D added=[] removed=[] [manager] -0032100 D bps after flush={} [manager] -[============================================== ] 93%0032109 D resuming core 0 [cortex_m] -0032110 D added=[] removed=[] [manager] -0032110 D bps after flush={} [manager] -[=============================================== ] 94%0032118 D resuming core 0 [cortex_m] -0032119 D added=[] removed=[] [manager] -0032119 D bps after flush={} [manager] -[=============================================== ] 96%0032127 D resuming core 0 [cortex_m] -0032127 D added=[] removed=[] [manager] -0032127 D bps after flush={} [manager] -[================================================ ] 97%0032136 D resuming core 0 [cortex_m] -0032136 D added=[] removed=[] [manager] -0032136 D bps after flush={} [manager] -[================================================ ] 98%0032146 D resuming core 0 [cortex_m] -0032146 D added=[] removed=[] [manager] -0032146 D bps after flush={} [manager] -[================================================= ] 99%0032155 D resuming core 0 [cortex_m] -0032155 D added=[] removed=[] [manager] -0032155 D bps after flush={} [manager] -[==================================================] 100% -0032159 D resuming core 0 [cortex_m] -0032159 D added=[] removed=[] [manager] -0032159 D bps after flush={} [manager] -0032161 I Erased chip, programmed 22784 bytes (89 pages), skipped 0 bytes (0 pages) at 0.70 kB/s [loader] -0032162 D reset, core 0, type=VECTRESET [cortex_m] -0032162 D no delegate or debug sequence handled reset; performing VECTRESET reset, core 0 [cortex_m] -0032269 D uninit session [session] -0032270 D uninit board [board] -0032271 D cannot resume core 0: core is RUNNING [cortex_m] \ No newline at end of file diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..a72332e --- /dev/null +++ b/TODO.md @@ -0,0 +1,152 @@ +# Контекст проекта — состояние на текущий момент + +## Проект + +Монорепозиторий `tft_manufacture_test` для **MIMXRT1052CVJ5B** (Cortex-M7, NXP i.MX RT1052). Три firmware-проекта: `firmware/test` (входной контроль, bare-metal), `firmware/bootloader` (A/B обновление, bare-metal), `firmware/tft_app` (боевая прошивка, FreeRTOS). Сборка в devcontainer, операции с железом на хосте. + +Стек: CMake + Ninja + CMakePresets, `just` как task runner (модули `build.just`, `host.just`, `ci.just`), `uv` для Python-зависимостей, devcontainer (VSCode + arm-none-eabi-gcc + clangd). + +--- + +## Что сделано в этой сессии + +### 1. Отладка через SWD — настроена и работает + +**Архитектура:** +``` +Хост: pyocd gdbserver :3333 ← MCU-Link (CMSIS-DAP) ← SWD ← MIMXRT1052 +DevContainer: arm-none-eabi-gdb → host.docker.internal:3333 +VSCode: cortex-debug (servertype: external) +``` + +**Файлы:** +- `pyocd_debug.yaml` — конфиг pyOCD (`target_override: mimxrt1050_quadspi`, `frequency: 4000000`, `rtt.enabled: true`, `rtt.port: 4445`) +- `.vscode/launch.json` — три конфигурации cortex-debug +- `.vscode/tasks.json` — `preLaunchTask` для каждой конфигурации + `rtt:connect` +- `just/host.just` — рецепты `debug-server`, `flash-swd-*` + +**Параметры из `.env`:** +``` +GDB_PORT=3333 +PYOCD_TARGET=mimxrt1050_quadspi +PYOCD_FREQUENCY=4000000 +FCB_PATH=tools/host/dcd/w25q128_fdcb.bin +``` + +**Конфигурации launch.json:** +- `🐛 Debug: firmware_test` — `build/Debug/firmware_test.elf` +- `🐛 Debug: bootloader` — `build/Debug/bootloader.elf` +- `🐛 Debug: tft_app (FreeRTOS)` — `build/Debug/app.elf`, `"rtos": "FreeRTOS"` + +Все три: `loadFiles: []` (не перепрошивают), `runToEntryPoint: main`, SVD из `bsp/generated/startup/MIMXRT1052.xml`. + + + +--- + +### 2. Прошивка через SWD — настроена и работает + +**Проблема:** GDB `load` не работает для XIP-прошивок на IMXRT — Boot ROM при cold-start читает FCB по `0x60000000`, а ELF-секции кладутся без FCB. + +**Решение:** `tools/host/flash_swd.py` собирает итоговый образ: +``` +0x60000000 w25q128_fdcb.bin (512 байт) — FCB для W25Q128 Quad SPI +0x60000200 0xFF × 3584 байт — padding +0x60001000 *_hab.bin — IVT + DCD + код +``` + +Всё в одном 64KB-секторе — стирается и записывается за одну транзакцию. + + +**Рецепты в host.just:** +``` +just host::flash-swd-test-debug +just host::flash-swd-test-release +just host::flash-swd-bootloader-debug +just host::flash-swd-bootloader-release +just host::flash-swd-app-debug +just host::flash-swd-app-release +``` + +Скрипт читает конфигурацию из окружения (`PYOCD_TARGET`, `PYOCD_FREQUENCY`, `BUILD_DIR`, `FCB_PATH`). Относительные пути из `.env` автоматически разрешаются от `REPO_ROOT`. + +**Рабочий цикл А (прошивка уже в Flash):** +``` +just host::debug-server → VSCode: 🐛 Debug → F5 +``` + +**Рабочий цикл Б (прошить через SWD + отладить):** +``` +just build::hab-firmware-test-debug (в контейнере) +just host::flash-swd-test-debug (на хосте) +⚡ power cycle +just host::debug-server +VSCode: 🐛 Debug → F5 +``` + +--- + +### 3. RTT — решено отказаться, заменить на UART + +**Почему отказались от RTT:** +- `servertype: external` в cortex-debug официально не поддерживает RTT нативно +- Workaround через `postLaunchCommands` + `rtt_client.py` работает, но требует ручного запуска RTT-клиента после F5 +- Автозапуск через `preLaunchTask` зависает (клиент стартует до того как pyOCD открыл порт) +- Итоговое UX: два ручных действия вместо одного + +**Вывод:** RTT остаётся в проекте как возможность (код в `lib/SEGGER`, `SEGGER_RTT_ENABLED=ON` в Debug-пресете), но для логов использовать не будем. + +--- + +### 4. UART как канал логов — решение принято + +**BSP:** `bsp/uart_host` — `bsp_uart_host_write_str()` / `bsp_uart_host_write()`. + +TX — `LPUART_WriteBlocking` (blocking polling). Для логов это нормально: +- Bare-metal: нет проблем +- FreeRTOS: задача вытесняется по таймеру, но мьютекс нужен если несколько задач пишут лог + +**Ограничения bsp_uart_host для логгера:** +- Нельзя вызывать из ISR +- В FreeRTOS-контексте нужен мьютекс на уровне логгера (не в bsp_uart_host) + +**Мониторинг логов на хосте:** +``` +just host::uart-monitor +``` +Использует `python3 -m serial.tools.miniterm` (pyserial, кроссплатформенно — macOS/Linux/Windows). Порт и бод из `.env` (`HIL_VCOM_PORT`, `HIL_VCOM_BAUD`). + +--- + +## Следующий шаг — слой логгирования + +Планируется добавить логгер поверх `bsp_uart_host`. Кандидат — **log.c** (rxi, однофайловый, MIT). Нужно: + +1. Вендоринг `log.h` / `log.c` в `lib/log/` или `utils/log/` +2. CMake-таргет +3. Callback-адаптер → `bsp_uart_host_write_str()` +4. Мьютекс для FreeRTOS (в callback, не в bsp) +5. Запрет вызова из ISR (задокументировать) +6. Красивый вывод: уровень, файл, строка, ANSI-цвета + +--- + +## Структура файлов отладки (итог) + +``` +. +├── .env # GDB_PORT, PYOCD_*, RTT_PORT, FCB_PATH +├── .vscode/ +│ ├── launch.json # 3 конфигурации cortex-debug +│ └── tasks.json # build:*, rtt:connect, uart-monitor +├── pyocd_debug.yaml # target, frequency, rtt config +├── bsp/generated/startup/MIMXRT1052.xml # SVD +├── just/host.just # debug-server, flash-swd-*, uart-monitor +└── tools/ + ├── hil/ # uv-проект: pyocd, pyserial, pytest + └── host/ + ├── flash_swd.py # FCB+HAB → pyocd flash + ├── rtt_client.py # RTT TCP-клиент (retry-loop) + └── dcd/ + └── w25q128_fdcb.bin # FCB для W25Q128 Quad SPI +``` \ No newline at end of file diff --git a/docs/HOW_TO_DEBUG.md b/docs/HOW_TO_DEBUG.md new file mode 100644 index 0000000..dc96e56 --- /dev/null +++ b/docs/HOW_TO_DEBUG.md @@ -0,0 +1,228 @@ +# Отладка прошивок через SWD + GDB + +## Обзор архитектуры + +Отладка построена на проброске GDB-сервера с хоста в devcontainer по TCP. Это позволяет держать весь инструментарий сборки и языковой сервер внутри контейнера, не проводя USB-пробник внутрь Docker. + +``` +┌─────────────────────────────────────┐ ┌──────────────────────────────────┐ +│ Хост (macOS/Linux) │ │ DevContainer │ +│ │ │ │ +│ just host::debug-server │ │ VSCode + cortex-debug │ +│ └─ pyocd gdbserver :3333 ──────────┼─────┼──► arm-none-eabi-gdb │ +│ │TCP │ └─ символы из .elf │ +│ MCU-Link (CMSIS-DAP) │3333 │ │ +│ └─ SWD ──► MIMXRT1052 │ │ RTT Console (SEGGER RTT логи) │ +│ Flash / SDRAM │ │ Peripherals (SVD регистры) │ +│ SEGGER RTT буфер │ │ RTOS view (FreeRTOS задачи) │ +└─────────────────────────────────────┘ └──────────────────────────────────┘ +``` + +**Ключевой принцип:** `pyocd gdbserver` запускается на хосте и слушает на `0.0.0.0:3333`. Из контейнера GDB подключается через `host.docker.internal:3333` — специальный DNS-алиас Docker, который резолвится в IP хост-машины. + +--- + +## Компоненты + +### На хосте + +| Компонент | Роль | Источник | +|---|---|---| +| `pyocd` | GDB-сервер + flash-программатор | `tools/hil/uv.lock` | +| `MCU-Link` | CMSIS-DAP v2 пробник | USB к плате | +| `just host::debug-server` | Запуск GDB-сервера | `just/host.just` | +| `just host::flash-swd-*` | Прошивка через SWD | `just/host.just` | +| `tools/host/flash_swd.py` | Сборка FCB+HAB образа и запись | `tools/host/` | +| `tools/host/dcd/w25q128_fdcb.bin` | FCB для W25Q128 (Quad SPI) | NXP SecureProvisioningTool | + +### В devcontainer + +| Компонент | Роль | +|---|---| +| `arm-none-eabi-gdb` | GDB клиент, подключается к серверу на хосте | +| `cortex-debug` (VSCode extension) | UI для GDB: брейкпоинты, стек, регистры | +| `.vscode/launch.json` | Конфигурации запуска отладки | +| `.vscode/tasks.json` | `preLaunchTask` — пересборка ELF перед стартом | +| `build/Debug/*.elf` | Символы для GDB (DWARF debug info) | +| `bsp/generated/startup/MIMXRT1052.xml` | SVD — описание регистров периферии | + +### Конфигурация + +Параметры отладки задаются в `.env` и автоматически экспортируются через `just` (`set export`), откуда наследуются скриптами: + +```bash +# .env — секция Debug / SWD +GDB_PORT=3333 +PYOCD_TARGET=mimxrt1050_quadspi +PYOCD_FREQUENCY=4000000 +FCB_PATH=tools/host/dcd/w25q128_fdcb.bin +``` + +--- + +## Прошивки, поддерживаемые отладкой + +| Конфигурация VSCode | ELF | Особенности | +|---|---|---| +| `🐛 Debug: firmware_test` | `build/Debug/firmware_test.elf` | Bare-metal, входной контроль | +| `🐛 Debug: bootloader` | `build/Debug/bootloader.elf` | Bare-metal, A/B обновление | +| `🐛 Debug: tft_app (FreeRTOS)` | `build/Debug/app.elf` | FreeRTOS, task view | + +Все три — XIP-прошивки, исполняются напрямую из QuadSPI NOR Flash (`0x60000000`). + +--- + +## Режимы запуска отладки + +### Режим А — прошивка уже в Flash + +Стандартный ежедневный сценарий. Прошивка была залита ранее любым способом и исполняется на плате. + +```bash +# 1. Хост — запустить GDB-сервер (оставить работать в отдельном терминале) +just host::debug-server + +# 2. DevContainer — VSCode +# Run & Debug (Ctrl+Shift+D) → выбрать конфигурацию → F5 +``` + +GDB сбрасывает MCU, загружает символы из ELF и останавливается на входе в `main`. Flash не перезаписывается. + +### Режим Б — прошить через SWD, затем отладить + +Когда нужно обновить прошивку без перевода платы в режим Serial Downloader. Удобно при итеративной разработке когда плата закреплена в стенде. + +```bash +# 1. DevContainer — собрать HAB-образ +just build::hab-firmware-test-debug + +# 2. Хост — прошить через SWD (MCU-Link, без смены BOOT_MODE) +just host::flash-swd-test-debug + +# 3. ⚡ Power cycle платы (обязательно — VECTRESET не реинициализирует FlexSPI) + +# 4. Хост — запустить GDB-сервер +just host::debug-server + +# 5. DevContainer — VSCode → 🐛 Debug: firmware_test → F5 +``` + +### Режим В — прошить через USB SDP, затем отладить + +Классический способ. Требует перевода платы в режим Serial Downloader (BOOT_MODE = 01). + +```bash +# 1. DevContainer — собрать +just build::build-firmware-test-debug + +# 2. Хост — перевести плату в Serial Downloader mode, затем: +just host::flash-test-debug + +# 3. Хост — запустить GDB-сервер +just host::debug-server + +# 4. DevContainer — VSCode → 🐛 Debug: firmware_test → F5 +``` + +--- + +## Почему flash через SWD требует FCB + +При прошивке через USB SDP (режимы А и В) ROM-загрузчик сам инициализирует FlexSPI контроллер по DCD из HAB-образа — Flash Configuration Block ему не нужен. + +При прошивке через SWD flash-алгоритм pyOCD записывает данные напрямую в NOR Flash. При cold-start Boot ROM первым делом читает FCB по адресу `0x60000000`, конфигурирует по нему FlexSPI, и только потом ищет IVT. Без FCB бутлоадер не может обратиться к Flash. + +`flash_swd.py` решает это, собирая итоговый образ перед записью: + +```bash +0x60000000 w25q128_fdcb.bin (512 байт) — FCB: параметры W25Q128, Quad SPI +0x60000200 0xFF × 3584 байт — padding (значение стёртой ячейки) +0x60001000 firmware_test_hab.bin — IVT + DCD + код (ivtOffset = 0x1000) +``` + +Весь диапазон `0x60000000–0x6000FFFF` умещается в один 64KB-сектор Flash, поэтому стирается и записывается за одну транзакцию — FCB и HAB не перезаписывают друг друга. + +--- + +## RTT-логи + +SEGGER RTT включён только в Debug-сборках (`SEGGER_RTT_ENABLED=ON` в `CMakePresets.json`). В Release-сборках RTT отключён и символ `_SEGGER_RTT` в ELF отсутствует. + +После старта отладки вкладка `TERMINAL → RTT` в VSCode принимает вывод из RTT-буфера канала 0. `cortex-debug` находит адрес буфера автоматически по символу `_SEGGER_RTT` из ELF (`address: auto` в `launch.json`). + +Использование в коде: + +```c +#include "SEGGER_RTT.h" + +SEGGER_RTT_printf(0, "value = %d\n", value); +``` + +--- + +## FreeRTOS task view + +Конфигурация `🐛 Debug: tft_app (FreeRTOS)` включает `"rtos": "FreeRTOS"` — cortex-debug разбирает внутренние структуры планировщика и показывает вкладку `RTOS` с таблицей задач: имя, состояние (`Running` / `Ready` / `Blocked` / `Suspended`), использование стека, приоритет. При паузе можно переключиться в контекст любой задачи и просмотреть её стек вызовов. + +--- + +## Просмотр регистров периферии + +Вкладка `Peripherals` в панели отладки показывает все периферийные блоки MIMXRT1052 по SVD-файлу `bsp/generated/startup/MIMXRT1052.xml`. Значения регистров обновляются при каждой паузе. Можно раскрыть любой блок (GPIO, LPUART, USB, FlexSPI и т.д.) и просматривать поля побитово. + +--- + +## Ограничения и важные замечания + +**MCU-Link монопольный ресурс.** `debug-server` и `flash-swd` не могут работать одновременно — оба занимают пробник. Перед `flash-swd` остановите сервер (Ctrl+C), и наоборот. + +**HIL-тесты vs отладка.** pyOCD также используется для HIL (загрузка ELF в RAM через `pyocd.yaml`). Перед запуском HIL-тестов (`just host::hil-run`) остановите GDB-сервер. + +**Power cycle после flash-swd обязателен.** pyOCD завершает запись командой VECTRESET, которая не реинициализирует FlexSPI контроллер. Boot ROM при таком сбросе не может прочитать FCB и не стартует из Flash. Только полное отключение питания гарантирует корректный cold-start. + +**Только Debug-сборки.** Отладка с символами возможна только для `Debug` CMake-пресета. Release-сборки компилируются с `-O2` без DWARF-символов. + +--- + +## Быстрый старт (первый запуск) + +```bash +# 1. Убедиться что cortex-debug установлен в devcontainer +# .devcontainer/devcontainer.json → extensions: ["marus25.cortex-debug"] + +# 2. Убедиться что в .devcontainer/devcontainer.json есть (для Linux-хостов): +# "runArgs": ["--add-host=host.docker.internal:host-gateway"] + +# 3. Залить прошивку любым способом (один раз) +just host::flash-test-debug # USB SDP +# или +just host::flash-swd-test-debug # SWD (после just build::hab-firmware-test-debug) + +# 4. Запустить GDB-сервер на хосте +just host::debug-server + +# 5. В VSCode (devcontainer) +# Ctrl+Shift+D → 🐛 Debug: firmware_test → F5 +``` + +--- + +## Дерево файлов отладки + +```bash +. +├── .env # GDB_PORT, PYOCD_TARGET, PYOCD_FREQUENCY, FCB_PATH +├── .vscode/ +│ ├── launch.json # Конфигурации cortex-debug (3 проекта) +│ └── tasks.json # preLaunchTask: build:*-debug +├── bsp/generated/startup/ +│ └── MIMXRT1052.xml # SVD — регистры периферии +├── just/ +│ └── host.just # debug-server, flash-swd-* +└── tools/ + ├── hil/ # uv-проект с pyocd + └── host/ + ├── flash_swd.py # FCB + HAB → Flash через pyOCD + └── dcd/ + └── w25q128_fdcb.bin # FCB для W25Q128 Quad SPI +``` \ No newline at end of file diff --git a/docs/HOW_TO_FLASH.md b/docs/HOW_TO_FLASH.md index f73f209..cda670d 100644 --- a/docs/HOW_TO_FLASH.md +++ b/docs/HOW_TO_FLASH.md @@ -1,10 +1,19 @@ # HOW TO FLASH -Прошивка выполняется **на хосте** (вне devcontainer) через USB ROM (Serial Download Protocol). +Поддерживаются два независимых способа прошивки: + +| Способ | Интерфейс | Требование | Инструмент | +|---|---|---|---| +| **USB SDP** | USB ↔ ROM-загрузчик | BOOT_MODE = 01 (Serial Downloader) | `spsdk` (sdphost + blhost) | +| **SWD** | MCU-Link ↔ CMSIS-DAP | Плата в любом режиме загрузки | `pyocd` | --- -## 1. Перевод платы в SDP-режим +## Способ 1 — USB SDP (Serial Download Protocol) + +Стандартный производственный способ. ROM-загрузчик принимает образ по USB и записывает его во Flash через Flashloader. Требует физического переключения пина `BOOT_MOD_1`. + +### 1.1 Перевести плату в SDP-режим ```bash 1. BOOT_MOD_1 → 3V3 @@ -19,11 +28,7 @@ BOOT_MOD_1 → GND → Reset ``` ---- - -## 2. Подготовить HAB-образ (внутри devcontainer) - -HAB-образ собирается из ELF-файла командой `nxpimage`. Выполнять в терминале VSCode: +### 1.2 Подготовить HAB-образ (внутри devcontainer) ```bash just build::hab-firmware-test-debug # → build/Debug/firmware_test_hab.bin @@ -33,53 +38,25 @@ just build::hab-app-release # → build/Release/app_hab.bin just build::hab-all-release # все три Release за один раз ``` ---- - -## 3. Прошивка (хостовый терминал) - -### Запись во Flash +### 1.3 Прошить (хостовый терминал) ```bash -# Основной рецепт: just host::flash -just host::flash firmware_test debug # разработка, итерации с отладчиком -just host::flash firmware_test release # проверить как будет на сервере +# Запись во Flash +just host::flash firmware_test debug +just host::flash firmware_test release just host::flash bootloader release just host::flash app release -``` -### Загрузка в RAM (без записи во Flash) - -Быстро, не изнашивает Flash. Плата стартует сразу после загрузки. - -```bash -just host::flash-ram firmware_test # default: debug +# Загрузка в RAM (без записи во Flash — быстро, не изнашивает Flash) just host::flash-ram firmware_test debug -just host::flash-ram firmware_test release -``` -### Быстрые алиасы - -```bash -just flash # = just host::flash firmware_test debug -just host::flash-test-debug # то же -just host::flash-test-release +# Быстрые алиасы +just host::flash-test-debug # firmware_test debug → Flash +just host::flash-test-release # firmware_test release → Flash just host::flash-production # bootloader release + app release (с подтверждением) ``` ---- - -## 4. Диагностика - -```bash -just host::scan # найти подключённые NXP USB-устройства -just host::sdp-status # проверить связь с BootROM (плата в SDP-режиме) -just host::flashloader-status # проверить Flashloader (после jump-address) -just host::check-deps # проверить версии just / uv / docker -``` - ---- - -## 5. Что происходит при прошивке +### 1.4 Что происходит при прошивке через USB SDP ```bash Плата в SDP-режиме (1FC9:0130) @@ -94,11 +71,102 @@ just host::check-deps # проверить версии just / uv / docke └── reset ``` +ROM-загрузчик сам конфигурирует FlexSPI через DCD из HAB-образа, поэтому FCB +в образе не нужен — его пишет Flashloader отдельно. + --- -## 6. Производственный сценарий (сервер) +## Способ 2 — SWD через MCU-Link + +Прошивка через отладочный пробник (MCU-Link, CMSIS-DAP). Плата остаётся +в нормальном режиме загрузки — переключать `BOOT_MOD_1` не нужно. Удобно +при итеративной разработке когда плата закреплена в стенде, а также как +часть отладочного цикла. + +**Ограничения:** + +- После записи обязателен **power cycle** (не reset) — VECTRESET не реинициализирует FlexSPI, Boot ROM не стартует +- MCU-Link используется монопольно: нельзя запускать одновременно с `debug-server` или HIL-тестами + +### 2.1 Подготовить HAB-образ (внутри devcontainer) ```bash -just host::incoming # firmware_test release → HIL-тесты периферии +just build::hab-firmware-test-debug +just build::hab-bootloader-debug +just build::hab-app-debug +``` + +### 2.2 Прошить (хостовый терминал) + +```bash +just host::flash-swd-test-debug # firmware_test Debug +just host::flash-swd-test-release # firmware_test Release +just host::flash-swd-bootloader-debug +just host::flash-swd-bootloader-release +just host::flash-swd-app-debug +just host::flash-swd-app-release + +# После любого flash-swd — обязательно: +# ⚡ Отключить и подключить питание платы +``` + +### 2.3 Что происходит при прошивке через SWD + +`flash_swd.py` собирает итоговый образ из двух частей перед записью: + +```bash +0x60000000 w25q128_fdcb.bin (512 байт) — FCB: параметры W25Q128, Quad SPI +0x60000200 0xFF × 3584 байт — padding (значение стёртой ячейки) +0x60001000 *_hab.bin — IVT + DCD + код (ivtOffset = 0x1000) +``` + +Весь диапазон умещается в один 64 KB сектор Flash — стирается и записывается +за одну транзакцию. FCB нужен потому что при cold-start Boot ROM читает его +первым, конфигурирует по нему FlexSPI, и только потом ищет IVT. При USB SDP +этим занимается ROM-загрузчик по DCD, FCB ему не нужен. + +### 2.4 Зависимости + +| Файл | Назначение | +|---|---| +| `tools/host/flash_swd.py` | Скрипт сборки образа и вызова pyOCD | +| `tools/host/dcd/w25q128_fdcb.bin` | FCB для W25Q128 в режиме Quad SPI | +| `tools/hil/` (uv-проект) | pyocd, вызывается через `uv run` | + +FCB-бинарник (`w25q128_fdcb.bin`) генерируется в NXP SecureProvisioningTool +для W25Q128 в режиме Quad SPI и хранится в репозитории — пересоздавать не нужно. + +--- + +## Сравнение способов + +| | USB SDP | SWD | +|---|---|---| +| Переключение BOOT_MODE | Нужно | Не нужно | +| Power cycle после записи | Не нужен | **Обязателен** | +| FCB в образе | Не нужен (Flashloader пишет сам) | **Обязателен** | +| Скорость записи | ~50–100 kB/s | ~8–10 kB/s | +| Совместимость с отладкой | Раздельно | MCU-Link монопольный | +| Производственный сценарий | ✓ | — | +| Итеративная разработка | Неудобно (смена режима) | ✓ | + +--- + +## Диагностика + +```bash +just host::scan # найти подключённые NXP USB-устройства +just host::sdp-status # проверить связь с BootROM (плата в SDP-режиме) +just host::flashloader-status # проверить Flashloader (после jump-address) +just host::debug-list-targets # проверить что pyOCD видит mimxrt1050_quadspi +just host::check-deps # проверить версии just / uv / docker +``` + +--- + +## Производственный сценарий + +```bash +just host::incoming # firmware_test release → Flash → HIL-тесты периферии just host::production # bootloader release + app release ``` diff --git a/firmware/test/main.c b/firmware/test/main.c index be5bb75..a6c801a 100644 --- a/firmware/test/main.c +++ b/firmware/test/main.c @@ -1,28 +1,31 @@ + #include "board.h" #include "bsp/led.h" #include "bsp/tick.h" #include "bsp/uart_host.h" - int main(void) { - const uint16_t DELAY_MS = 1000; + const uint16_t DELAY_MS = 100; const uint32_t UART_BAUDRATE = 115200; board_hw_init(); bsp_led_init(); bsp_tick_init(); + if (bsp_uart_host_init(UART_BAUDRATE) != BSP_OK) { + while (1) { bsp_led_toggle(LED_HEARTBEAT); - bsp_delay(50); + bsp_delay(DELAY_MS); } } - + uint32_t cycle = 0; while (1) { + bsp_led_on(LED_HEARTBEAT); - bsp_uart_host_write_str("We are here!"); + bsp_uart_host_write_str("We are here!\n"); bsp_delay(DELAY_MS); bsp_led_off(LED_HEARTBEAT); bsp_delay(DELAY_MS); diff --git a/just/host.just b/just/host.just index b9cf354..1803fa8 100644 --- a/just/host.just +++ b/just/host.just @@ -206,10 +206,11 @@ bootstrap: echo "" # ============================================================================= -# ГРУППА: flash +# ГРУППА: flash — USB Serial Downloader (SDP через ROM-загрузчик) +# Требует: плата в режиме Serial Downloader (BOOT_MODE = 01) # ============================================================================= -[doc('Прошить образ во Flash через USB ROM: flash ')] +[doc('Прошить образ через USB SDP: flash ')] [group('flash')] flash project type="release": #!/usr/bin/env bash @@ -222,7 +223,7 @@ flash project type="release": cd "{{ TOOLS_DIR }}" && UV_PROJECT_ENVIRONMENT={{ _venv }} uv run python flash_usb.py \ --firmware "{{ project }}" --build-type "${BUILD_TYPE}" -[doc('Прошить образ в RAM через USB ROM: flash-ram ')] +[doc('Прошить образ в RAM через USB SDP: flash-ram ')] [group('flash')] flash-ram project type="debug": #!/usr/bin/env bash @@ -235,23 +236,67 @@ flash-ram project type="debug": cd "{{ TOOLS_DIR }}" && UV_PROJECT_ENVIRONMENT={{ _venv }} uv run python flash_usb.py \ --firmware "{{ project }}" --build-type "${BUILD_TYPE}" --ram-only -[doc('Прошить firmware_test Debug во Flash')] +[doc('Прошить firmware_test Debug через USB SDP')] [group('flash')] flash-test-debug: @just host::flash firmware_test debug -[doc('Прошить firmware_test Release во Flash')] +[doc('Прошить firmware_test Release через USB SDP')] [group('flash')] flash-test-release: @just host::flash firmware_test release [confirm("Flash bootloader + app (Release)?")] -[doc('Прошить bootloader + tft_app Release во Flash (производство)')] +[doc('Прошить bootloader + tft_app Release через USB SDP (производство)')] [group('flash')] flash-production: @just host::flash bootloader release @just host::flash app release +# ============================================================================= +# ГРУППА: flash-swd — прошивка через SWD (MCU-Link, без смены BOOT_MODE) +# Требует: GDB-сервер НЕ запущен (pyOCD занимает пробник монопольно) +# После записи — обязательный power cycle платы +# ============================================================================= + +_flash_swd := TOOLS_DIR / "flash_swd.py" + +[doc('Прошить firmware_test Debug через SWD. Нужен power cycle после')] +[group('flash-swd')] +flash-swd-test-debug: + uv run --directory {{ HIL_DIR }} python {{ _flash_swd }} \ + --firmware firmware_test --build-type Debug + +[doc('Прошить firmware_test Release через SWD. Нужен power cycle после')] +[group('flash-swd')] +flash-swd-test-release: + uv run --directory {{ HIL_DIR }} python {{ _flash_swd }} \ + --firmware firmware_test --build-type Release + +[doc('Прошить bootloader Debug через SWD. Нужен power cycle после')] +[group('flash-swd')] +flash-swd-bootloader-debug: + uv run --directory {{ HIL_DIR }} python {{ _flash_swd }} \ + --firmware bootloader --build-type Debug + +[doc('Прошить bootloader Release через SWD. Нужен power cycle после')] +[group('flash-swd')] +flash-swd-bootloader-release: + uv run --directory {{ HIL_DIR }} python {{ _flash_swd }} \ + --firmware bootloader --build-type Release + +[doc('Прошить tft_app Debug через SWD. Нужен power cycle после')] +[group('flash-swd')] +flash-swd-app-debug: + uv run --directory {{ HIL_DIR }} python {{ _flash_swd }} \ + --firmware app --build-type Debug + +[doc('Прошить tft_app Release через SWD. Нужен power cycle после')] +[group('flash-swd')] +flash-swd-app-release: + uv run --directory {{ HIL_DIR }} python {{ _flash_swd }} \ + --firmware app --build-type Release + # ============================================================================= # ГРУППА: hil — запуск HIL-тестов на реальном железе # Сборка target-прошивок — в devcontainer: just build::build-hil @@ -284,6 +329,36 @@ hil-run-fast: HIL_BUILD_DIR={{ _hil_build }} \ uv run --directory {{ HIL_DIR }} pytest -v --no-load +# ============================================================================= +# ГРУППА: debug — GDB-сервер для отладки из VSCode (devcontainer) +# +# Рабочий цикл А — прошивка уже в Flash: +# just host::debug-server → VSCode: 🐛 Debug: → F5 +# +# Рабочий цикл Б — прошить и отладить без USB SDP: +# just host::flash-swd-test-debug → power cycle → +# just host::debug-server → VSCode: 🐛 Debug: firmware_test → F5 +# +# Важно: debug-server и flash-swd используют MCU-Link монопольно. +# Остановите сервер (Ctrl+C) перед flash-swd и наоборот. +# ============================================================================= + +_hil_dir := "tools/hil" + +[doc('Запустить GDB-сервер. Оставить запущенным, подключаться из VSCode')] +[group('debug')] +debug-server: + uv run --directory {{ _hil_dir }} pyocd gdbserver \ + --config {{ justfile_directory() }}/pyocd_debug.yaml \ + --port {{ env('GDB_PORT', '3333') }} \ + --persist \ + --semihosting + +[doc('Показать доступные builtin-таргеты pyOCD для MIMXRT')] +[group('debug')] +debug-list-targets: + uv run --directory {{ _hil_dir }} pyocd list --targets | grep -i mimx + # ============================================================================= # ГРУППА: pipeline # ============================================================================= @@ -327,30 +402,9 @@ upgrade-tools version: cd "{{ TOOLS_DIR }}" && uv add "spsdk=={{ version }}" && uv sync @echo " ✅ spsdk upgraded to {{ version }}" -# ============================================================================= -# ГРУППА: debug -# ============================================================================= - -_hil_dir := "tools/hil" - -[doc('Запустить GDB-сервер на порту 3333. Оставить запущенным, подключаться из VSCode')] -[group('debug')] -debug-server: - uv run --directory {{ _hil_dir }} pyocd gdbserver \ - --config {{ justfile_directory() }}/pyocd_debug.yaml \ - --port 3333 \ - --persist \ - --semihosting - -[doc('Показать доступные builtin-таргеты pyOCD для MIMXRT')] -[group('debug')] -debug-list-targets: - uv run --directory {{ _hil_dir }} pyocd list --targets | grep -i mimx - -[doc('Собрать HAB Debug и прошить через MCU-Link')] -[group('flash')] -flash-test-debug-swd: - uv run --directory {{ _hil_dir }} pyocd flash \ - --config {{ justfile_directory() }}/pyocd_debug.yaml \ - --base-address 0x60000000 \ - {{ BUILD_DIR }}/Debug/firmware_test_hab.bin +[doc('Открыть UART монитор на MCU-Link VCOM (кроссплатформенный)')] +[group('util')] +uart-monitor: + uv run --directory {{ HIL_DIR }} python3 -m serial.tools.miniterm \ + {{ env('HIL_VCOM_PORT') }} {{ env('HIL_VCOM_BAUD', '115200') }} \ + --raw diff --git a/pyocd_debug.yaml b/pyocd_debug.yaml index 21b8b12..bf3be37 100644 --- a/pyocd_debug.yaml +++ b/pyocd_debug.yaml @@ -3,8 +3,6 @@ # Используется: just host::debug-server # Не использовать для HIL-тестов — для них pyocd.yaml (target: cortex_m, RAM-режим) # -# Первый запуск — установить CMSIS pack: -# just host::debug-pack-install target_override: mimxrt1050_quadspi -frequency: 4000000 \ No newline at end of file +frequency: 4000000 diff --git a/tools/hil/README.md b/tools/hil/README.md index e69de29..faa0a48 100644 --- a/tools/hil/README.md +++ b/tools/hil/README.md @@ -0,0 +1,313 @@ +# tools/hil — HIL-тесты и утилиты отладки MIMXRT1052 + +Изолированное Python-окружение на базе [uv](https://docs.astral.sh/uv/) для: +- **HIL-тестов** (Hardware-in-the-Loop) — загрузка ELF в RAM и pytest через UART +- **GDB-сервера** для отладки из VSCode (pyocd gdbserver) +- **SWD-прошивки** через `flash_swd.py` (вызывается из `tools/host/`) + +Запускается на **хост-машине** — не внутри devcontainer. + +--- + +## Структура + +``` +tools/hil/ +├── conftest.py — pytest-фикстуры: загрузка ELF, открытие UART, ожидание READY +├── env_config.py — конфигурация из переменных окружения (.env → just → pytest) +├── pyocd_utils.py — утилиты pyOCD: FLEXRAM, ELF-загрузка, запуск из векторов +├── load_and_run.py — CLI-обёртка: загрузить ELF в RAM и запустить вручную +├── test_uart.py — HIL-тесты bsp_uart_host (PING/ECHO/BUF_SIZE) +├── pyproject.toml — зависимости (pyocd, pyserial, pytest) +└── uv.lock — lockfile (коммитить) +``` + +--- + +## Концепция HIL-тестов + +Каждый HIL-тест — это пара: + +``` +tests/target//main.c ← C-прошивка с текстовым CLI через UART +tools/hil/test_.py ← pytest-тесты, общаются с прошивкой по UART +``` + +pyOCD загружает `.elf` в RAM через MCU-Link (CMSIS-DAP). pytest общается с +прошивкой через MCU-Link VCOM (pyserial): + +``` +pytest → uart_cmd("PING\r\n") → MCU-Link VCOM → RT1052 → "PONG\r\n" → pytest +``` + +ELF загружается в **ITCM/DTCM** (не Flash) — быстро, не изнашивает Flash, +не требует HAB-образа. + +--- + +## Предварительные требования + +### 1. uv — один раз на машину + +```bash +# macOS / Linux +curl -LsSf https://astral.sh/uv/install.sh | sh + +# Windows +powershell -c "irm https://astral.sh/uv/install.ps1 | iex" +``` + +### 2. Зависимости проекта — один раз + +```bash +cd tools/hil +uv sync +``` + +### 3. Настроить `.env` в корне репозитория + +```bash +HIL_VCOM_PORT=/dev/tty.usbmodemGUXFBWDJBWTGQ3 # macOS +# HIL_VCOM_PORT=/dev/ttyACM0 # Linux +# HIL_VCOM_PORT=COM3 # Windows +HIL_VCOM_BAUD=115200 +HIL_READY_TIMEOUT=5.0 +HIL_PYOCD_FREQUENCY=1000000 +HIL_BUILD_DIR=build/target-debug +``` + +Найти порт MCU-Link VCOM: + +```bash +just host::scan # nxpdevscan — все NXP устройства +ls /dev/tty.usbmodem* # macOS +ls /dev/ttyACM* # Linux +``` + +--- + +## Запуск HIL-тестов + +Сборка target-прошивок выполняется в devcontainer: + +```bash +# devcontainer: +just build::build-hil # → build/target-debug/tests/target/*/test_*.elf +``` + +Запуск тестов — на хосте: + +```bash +just host::hil-run # все HIL-тесты +just host::hil-smoke # только smoke-тесты (быстро) +just host::hil-run-fast # без перезагрузки ELF (прошивка уже запущена) +``` + +Или напрямую через pytest: + +```bash +cd tools/hil + +# Все тесты +uv run pytest -v + +# Только smoke +uv run pytest -v -m smoke + +# Конкретный файл +uv run pytest test_uart.py -v + +# Без перезагрузки ELF (прошивка уже запущена) +uv run pytest -v --no-load + +# Другой ELF +uv run pytest test_uart.py -v --elf /path/to/custom.elf + +# Другой VCOM-порт +uv run pytest -v --vcom /dev/ttyACM1 +``` + +--- + +## Конфигурация + +Конфигурация читается в `env_config.py`. Приоритет: CLI-опции pytest > `os.environ` > defaults. + +| Переменная окружения | CLI pytest | Default | Описание | +|---|---|---|---| +| `HIL_BUILD_DIR` | — | `build/target-debug` | Директория с target ELF-файлами | +| `HIL_VCOM_PORT` | `--vcom` | `/dev/ttyACM0` | UART-порт MCU-Link VCOM | +| `HIL_VCOM_BAUD` | — | `115200` | Скорость UART | +| `HIL_READY_TIMEOUT` | — | `5.0` | Таймаут ожидания `READY` от прошивки (сек) | +| `HIL_PYOCD_FREQUENCY` | — | `1000000` | Частота SWD для загрузки ELF в RAM | + +При запуске через `just` все переменные из корневого `.env` автоматически +экспортируются в окружение (`set dotenv-load` + `set export`). + +--- + +## Как работают фикстуры + +### Цепочка зависимостей + +```bash +test_ping() + └── _setup (autouse, scope=function) + ├── loaded_host_uart (scope=module) ← грузит ELF в RAM + └── uart (scope=module) ← открывает VCOM, ждёт READY + └── depends_on: loaded_host_uart +``` + +`scope=module` — ELF загружается один раз на весь файл с тестами, порт +открывается один раз. Все тесты внутри файла разделяют одно соединение. + +### Порядок выполнения + +```bash +1. loaded_() + ├── open_target() → pyOCD: подключиться к MCU через SWD + ├── flexram_init() → настроить ITCM/DTCM/OCRAM + ├── load_elf() → записать PT_LOAD сегменты по адресам + └── run_from_vectors() → SP/PC из 0x00000000/0x00000004 → resume + +2. uart() + ├── serial.Serial.open() + ├── while readline() != "READY": ... ← ждём сигнал от прошивки + └── yield ser + +3. test_ping(), test_echo(), ... ← тесты + +4. uart teardown → ser.close() +``` + +### Почему `uart` зависит от `loaded_` + +```python +def uart(request, loaded_host_uart): # ← явная зависимость в сигнатуре +``` + +Без этого pytest мог бы создать `uart` раньше чем ELF загружен — порт открылся +бы, но `READY` не пришёл. Явная зависимость гарантирует порядок. + +--- + +## pyocd_utils — справочник + +### `open_target(frequency)` + +Контекстный менеджер, открывает pyOCD-сессию с первым найденным пробником. +Таргет — `cortex_m` (generic, без flash-алгоритма — для RAM-операций достаточно). + +```python +with open_target(frequency=1_000_000) as target: + flexram_init(target) + load_elf(target, "test.elf") + run_from_vectors(target) +``` + +### `flexram_init(target)` + +Настраивает FLEXRAM через `IOMUXC_GPR16/GPR17`: + +- 128 KB ITCM (0x00000000) — код +- 128 KB DTCM (0x20000000) — данные, стек +- 256 KB OCRAM (0x20200000) — буферы + +### `load_elf(target, elf_path)` + +Записывает все `PT_LOAD` сегменты ELF по физическим адресам (`p_paddr`). +Использует `pyelftools`. + +### `run_from_vectors(target)` + +Читает SP и PC из таблицы векторов (ITCM `0x00000000`/`0x00000004`), +выставляет регистры, вызывает `target.resume()`. Проверяет: + +- SP в диапазоне DTCM `[0x20000000, 0x20040000]` +- PC в диапазоне ITCM `[0x00000400, 0x00020000]` + +### `load_and_run(elf_path, frequency)` + +Комбо-функция: open_target + flexram_init + load_elf + run_from_vectors. +Для CLI и одиночных скриптов. + +--- + +## load_and_run.py — ручная загрузка ELF + +Для ручной отладки без запуска тестов: + +```bash +# Загрузить ELF в RAM и запустить +uv run python load_and_run.py build/target-debug/tests/target/host_uart/test_host_uart.elf + +# Через just: +just host::hil-load +``` + +После этого можно подключиться к VCOM вручную: + +```bash +just host::uart-monitor +``` + +--- + +## Протокол CLI в target-прошивках + +Все HIL target-прошивки (`tests/target//main.c`) реализуют единый +текстовый CLI через `bsp_uart_host`: + +- Прошивка отправляет `READY\r\n` пока хост не открыл порт +- Хост посылает команду строкой с `\r\n` +- Прошивка отвечает одной строкой с `\r\n` + +Минимальные команды в каждой прошивке: + +| Команда | Ответ | Назначение | +|---|---|---| +| `PING` | `PONG` | Проверка канала | +| `<неизвестная>` | `ERR_UNKNOWN` | Прошивка не зависает | + +--- + +## Добавление нового HIL-теста + +Подробный гайд — в `tests/HIL_CREATE_TEST.md`. Краткая схема: + +```bash +1. tests/target//main.c — C-прошивка с CLI +2. tests/target//CMakeLists.txt — сборка с bsp_boot_ram +3. tests/target/CMakeLists.txt — add_subdirectory() +4. CMakePresets.json — добавить test_ в target-debug-build +5. tools/hil/test_.py — pytest-тесты +6. tools/hil/conftest.py — добавить loaded_ фикстуру +``` + +Фикстура для нового теста в `conftest.py`: + +```python +@pytest.fixture(scope="module") +def loaded_(request: pytest.FixtureRequest) -> None: + _load_elf( + request, + Path(cfg.BUILD_DIR) / "tests/target//test_.elf", + ) +``` + +--- + +## Совместимость с GDB-сервером + +pyOCD используется для двух независимых задач: + +| Задача | Команда | Таргет | Порт | +|---|---|---|---| +| HIL-тесты (RAM) | `pyocd` через `pyocd_utils` | `cortex_m` | — | +| GDB-сервер (отладка) | `pyocd gdbserver` | `mimxrt1050_quadspi` | 3333 | + +**MCU-Link монопольный** — нельзя запускать HIL и GDB-сервер одновременно. +Перед `just host::hil-run` остановите GDB-сервер (`Ctrl+C`), и наоборот. + +Разные таргеты намеренны: HIL не нужен flash-алгоритм (`cortex_m` достаточно), +GDB-сервер нужен полноценный таргет для корректного reset и SVD. diff --git a/tools/hil/targets.txt b/tools/hil/targets.txt deleted file mode 100644 index eb6ec0b..0000000 --- a/tools/hil/targets.txt +++ /dev/null @@ -1,207 +0,0 @@ - Name Vendor Part Number Families Source ------------------------------------------------------------------------------------------------------- - air001 AirM2M Air001 builtin - air32f103xb AirM2M Air32F103xB builtin - air32f103xc AirM2M Air32F103xC builtin - air32f103xe AirM2M Air32F103xE builtin - air32f103xg AirM2M Air32F103xG builtin - air32f103xp AirM2M Air32F103xP builtin - ama3b1kk_kbr Ambiq Micro AMA3B1KK_KBR builtin - cc3220sf Texas Instruments CC3220SF builtin - cortex_m Generic CoreSightTarget builtin - cy8c64_sysap Cypress cy8c64_sysap builtin - cy8c64x5_cm0 Cypress cy8c64x5_cm0 builtin - cy8c64x5_cm0_full_flash Cypress cy8c64x5_cm0_full_flash builtin - cy8c64x5_cm4 Cypress cy8c64x5_cm4 builtin - cy8c64x5_cm4_full_flash Cypress cy8c64x5_cm4_full_flash builtin - cy8c64xa_cm0 Cypress cy8c64xA_cm0 builtin - cy8c64xa_cm0_full_flash Cypress cy8c64xA_cm0_full_flash builtin - cy8c64xa_cm4 Cypress cy8c64xA_cm4 builtin - cy8c64xa_cm4_full_flash Cypress cy8c64xA_cm4_full_flash builtin - cy8c64xx_cm0 Cypress cy8c64xx_cm0 builtin - cy8c64xx_cm0_full_flash Cypress cy8c64xx_cm0_full_flash builtin - cy8c64xx_cm0_nosmif Cypress cy8c64xx_cm0_nosmif builtin - cy8c64xx_cm0_s25hx512t Cypress cy8c64xx_cm0_s25hx512t builtin - cy8c64xx_cm4 Cypress cy8c64xx_cm4 builtin - cy8c64xx_cm4_full_flash Cypress cy8c64xx_cm4_full_flash builtin - cy8c64xx_cm4_nosmif Cypress cy8c64xx_cm4_nosmif builtin - cy8c64xx_cm4_s25hx512t Cypress cy8c64xx_cm4_s25hx512t builtin - cy8c6xx5 Cypress CY8C6xx5 builtin - cy8c6xx7 Cypress CY8C6xx7 builtin - cy8c6xx7_nosmif Cypress CY8C6xx7_nosmif builtin - cy8c6xx7_s25fs512s Cypress CY8C6xx7_S25FS512S builtin - cy8c6xxa Cypress CY8C6xxA builtin - hc32a448 HDSC HC32F448xC builtin - hc32a448xa HDSC HC32F448xA builtin - hc32a448xc HDSC HC32F448xC builtin - hc32a460 HDSC HC32F460xE builtin - hc32a460xe HDSC HC32F460xE builtin - hc32a4a0 HDSC HC32F4A0xI builtin - hc32a4a0xi HDSC HC32F4A0xI builtin - hc32f003 HDSC HC32F003 builtin - hc32f005 HDSC HC32F005 builtin - hc32f030 HDSC HC32F030 builtin - hc32f072 HDSC HC32F072 builtin - hc32f115 HDSC HC32F115x8 builtin - hc32f115x8 HDSC HC32F115x8 builtin - hc32f120 HDSC HC32F120x8TA builtin - hc32f120x6 HDSC HC32F120x6TA builtin - hc32f120x8 HDSC HC32F120x8TA builtin - hc32f155 HDSC HC32F155xC builtin - hc32f155xa HDSC HC32F155xA builtin - hc32f155xc HDSC HC32F155xC builtin - hc32f160 HDSC HC32F160xC builtin - hc32f160xa HDSC HC32F160xA builtin - hc32f160xc HDSC HC32F160xC builtin - hc32f190 HDSC HC32F190 builtin - hc32f196 HDSC HC32F196 builtin - hc32f334 HDSC HC32F334xA builtin - hc32f334x8 HDSC HC32F334x8 builtin - hc32f334xa HDSC HC32F334xA builtin - hc32f448 HDSC HC32F448xC builtin - hc32f448xa HDSC HC32F448xA builtin - hc32f448xc HDSC HC32F448xC builtin - hc32f451 HDSC HC32F451xE builtin - hc32f451xc HDSC HC32F451xC builtin - hc32f451xe HDSC HC32F451xE builtin - hc32f452 HDSC HC32F452xE builtin - hc32f452xc HDSC HC32F452xC builtin - hc32f452xe HDSC HC32F452xE builtin - hc32f460 HDSC HC32F460xE builtin - hc32f460xc HDSC HC32F460xC builtin - hc32f460xe HDSC HC32F460xE builtin - hc32f467 HDSC HC32F467xG builtin - hc32f467xg HDSC HC32F467xG builtin - hc32f472 HDSC HC32F472xE builtin - hc32f472xc HDSC HC32F472xC builtin - hc32f472xe HDSC HC32F472xE builtin - hc32f4a0 HDSC HC32F4A0xI builtin - hc32f4a0xg HDSC HC32F4A0xG builtin - hc32f4a0xi HDSC HC32F4A0xI builtin - hc32f4a2 HDSC HC32F4A0xI builtin - hc32f4a2xi HDSC HC32F4A0xI builtin - hc32l072 HDSC HC32L072 builtin - hc32l073 HDSC HC32L073 builtin - hc32l110 HDSC HC32L110 builtin - hc32l130 HDSC HC32L130 builtin - hc32l136 HDSC HC32L136 builtin - hc32l190 HDSC HC32L190 builtin - hc32l196 HDSC HC32L196 builtin - hc32m120 HDSC HC32M120 builtin - hc32m120x6 HDSC HC32M120 builtin - hc32m423xa HDSC HC32M423xA builtin - k20d50m NXP K20D50M builtin - k22f NXP K22F builtin - k22fa12 NXP K22FA12 builtin - k28f15 NXP K28F15 builtin - k32l2b3 NXP K32L2B3 builtin - k32w042s NXP K32W042S builtin - k64f NXP K64F builtin - k66f18 NXP K66F18 builtin - k82f25615 NXP K82F25615 builtin - ke15z7 NXP KE15Z7 builtin - ke17z7 NXP KE17Z7 builtin - ke18f16 NXP KE18F16 builtin - kinetis NXP Kinetis builtin - kl02z NXP KL02Z builtin - kl05z NXP KL05Z builtin - kl25z NXP KL25Z builtin - kl26z NXP KL26Z builtin - kl27z4 NXP KL27Z4 builtin - kl28z NXP KL28x builtin - kl43z4 NXP KL43Z4 builtin - kl46z NXP KL46Z builtin - kl82z7 NXP KL82Z7 builtin - kv10z7 NXP KV10Z7 builtin - kv11z7 NXP KV11Z7 builtin - kw01z4 NXP KW01Z4 builtin - kw24d5 NXP KW24D5 builtin - kw36z4 NXP KW36Z4 builtin - kw40z4 NXP KW40Z4 builtin - kw41z4 NXP KW41Z4 builtin - lpc11u24 NXP LPC11U24 builtin - lpc11xx_32 NXP LPC11XX_32 builtin - lpc1768 NXP LPC1768 builtin - lpc4088 NXP LPC4088 builtin - lpc4088dm NXP LPC4088dm builtin - lpc4088qsb NXP LPC4088qsb builtin - lpc4330 NXP LPC4330 builtin - lpc54114 NXP LPC54114 builtin - lpc54608 NXP LPC54608 builtin - lpc5526 NXP LPC5526 builtin - lpc55s16 NXP LPC55S16 builtin - lpc55s28 NXP LPC55S28 builtin - lpc55s36 NXP LPC55S36 builtin - lpc55s69 NXP LPC55S69 builtin - lpc800 NXP LPC800 builtin - lpc824 NXP LPC824 builtin - lpc845 NXP LPC845 builtin - m2354kjfae Nuvoton M2354KJFAE builtin - m252kg6ae Nuvoton M252KG6AE builtin - m263kiaae Nuvoton M263KIAAE builtin - m467hjhae Nuvoton M467HJHAE builtin - m487jidae Nuvoton M487JIDAE builtin - max32600 Maxim MAX32600 builtin - max32620 Maxim MAX32620 builtin - max32625 Maxim MAX32625 builtin - max32630 Maxim MAX32630 builtin - max32660 Maxim MAX32660 builtin - max32666 Maxim MAX32666 builtin - max32670 Maxim MAX32670 builtin - mimxrt1010 NXP MIMXRT1011xxxxx builtin - mimxrt1015 NXP MIMXRT1015xxxxx builtin - mimxrt1020 NXP MIMXRT1021xxxxx builtin - mimxrt1024 NXP MIMXRT1024xxxxx builtin - mimxrt1050 NXP MIMXRT1052xxxxB_hyperflash builtin - mimxrt1050_hyperflash NXP MIMXRT1052xxxxB_hyperflash builtin - mimxrt1050_quadspi NXP MIMXRT1052xxxxB_quadspi builtin - mimxrt1060 NXP MIMXRT1062xxxxA builtin - mimxrt1064 NXP MIMXRT1064xxxxA builtin - mimxrt1170_cm4 NXP MIMXRT1176xxxxx_CM4 builtin - mimxrt1170_cm7 NXP MIMXRT1176xxxxx_CM7 builtin - mps2_an521 Arm AN521 builtin - mps3_an522 Arm AN522 builtin - mps3_an540 Arm AN540 builtin - musca_a1 Arm MuscaA1 builtin - musca_b1 Arm MuscaB1 builtin - musca_s1 Arm MuscaS1 builtin - ncs36510 ONSemiconductor NCS36510 builtin - nrf51 Nordic Semiconductor NRF51 builtin - nrf51822 Nordic Semiconductor NRF51 builtin - nrf52 Nordic Semiconductor NRF52832 builtin - nrf52832 Nordic Semiconductor NRF52832 builtin - nrf52833 Nordic Semiconductor NRF52833 builtin - nrf52840 Nordic Semiconductor NRF52840 builtin - nrf54l Nordic Semiconductor NRF54L15 builtin - nrf91 Nordic Semiconductor NRF91XX builtin - rp2040 Raspberry Pi RP2040Core0 builtin - rp2040_core0 Raspberry Pi RP2040Core0 builtin - rp2040_core1 Raspberry Pi RP2040Core1 builtin - rp2350 Raspberry Pi RP2350 builtin - rtl8195am Realtek Semiconductor RTL8195AM builtin - rtl8762c Realtek Semiconductor RTL8762C builtin - s32k344 NXP S32K344 builtin - s5js100 Samsung S5JS100 builtin - stm32f051 STMicroelectronics STM32F051 builtin - stm32f103rc STMicroelectronics STM32F103RC builtin - stm32f412xe STMicroelectronics STM32F412xE builtin - stm32f412xg STMicroelectronics STM32F412xG builtin - stm32f429xg STMicroelectronics STM32F429xG builtin - stm32f429xi STMicroelectronics STM32F429xI builtin - stm32f439xg STMicroelectronics STM32F439xG builtin - stm32f439xi STMicroelectronics STM32F439xI builtin - stm32f767zi STMicroelectronics STM32F767xx builtin - stm32h723xx STMicroelectronics STM32H723xx builtin - stm32h743xx STMicroelectronics STM32H743xx builtin - stm32h750xx STMicroelectronics STM32H750xx builtin - stm32h7b0xx STMicroelectronics STM32H7B0xx builtin - stm32l031x6 STMicroelectronics STM32L031x6 builtin - stm32l432kc STMicroelectronics STM32L432xC builtin - stm32l475xc STMicroelectronics STM32L475xC builtin - stm32l475xe STMicroelectronics STM32L475xE builtin - stm32l475xg STMicroelectronics STM32L475xG builtin - w7500 WIZnet W7500 builtin - ytm32b1ld0 Yuntu Microelectronics YTM32B1LD0 builtin - ytm32b1le0 Yuntu Microelectronics YTM32B1LE0 builtin - ytm32b1md1 Yuntu Microelectronics YTM32B1MD1 builtin - ytm32b1me0 YTMicro YTM32B1ME0 builtin diff --git a/tools/host/README.md b/tools/host/README.md index 49a260b..92b4e4c 100644 --- a/tools/host/README.md +++ b/tools/host/README.md @@ -1,9 +1,16 @@ # tools/host — Окружение прошивки MIMXRT1052CVJ5B Изолированное Python-окружение на базе [uv](https://docs.astral.sh/uv/) для сборки -HAB-образов и прошивки платы через USB. Запускается на хост-машине разработчика — +HAB-образов и прошивки платы. Запускается на хост-машине разработчика — **не внутри devcontainer**. +Поддерживаются два независимых способа прошивки: + +| Способ | Скрипт | Интерфейс | Требование | +|---|---|---|---| +| USB SDP | `flash_usb.py` | USB ↔ ROM-загрузчик | BOOT_MODE = 01 | +| SWD | `flash_swd.py` | MCU-Link ↔ CMSIS-DAP | Плата в любом режиме | + --- ## Структура @@ -11,18 +18,28 @@ HAB-образов и прошивки платы через USB. Запуска ```bash tools/host/ ├── dcd/ -│ ├── dcd.bin ← DCD бинарник (инициализация SDRAM, коммитить как есть) -│ └── ivt_flashloader.bin ← NXP Flashloader (коммитить) +│ ├── dcd.bin ← DCD бинарник (инициализация SDRAM) +│ ├── ivt_flashloader.bin ← NXP Flashloader (USB SDP) +│ ├── w25q64_fdcb.bin ← FCB для W25Q64 (SWD flash) +│ ├── w25q128_fdcb.bin ← FCB для W25Q128 (SWD flash) ← используется +│ └── w25q512_fdcb.bin ← FCB для W25Q512 (SWD flash) ├── hab/ -│ ├── hab_firmware_test.yaml ← HAB: Debug + DCD (входной контроль) -│ ├── hab_bootloader.yaml ← HAB: Release, без DCD -│ └── hab_app.yaml ← HAB: Release + DCD (боевая прошивка) -├── flash_usb.py ← скрипт прошивки через USB -├── pyproject.toml ← зависимости (spsdk==3.7.x) -├── uv.lock ← lockfile (коммитить) +│ ├── hab_firmware_test_debug.yaml +│ ├── hab_firmware_test_release.yaml +│ ├── hab_bootloader_debug.yaml +│ ├── hab_bootloader_release.yaml +│ ├── hab_app_debug.yaml +│ └── hab_app_release.yaml +├── flash_usb.py ← прошивка через USB ROM (SDP → blhost) +├── flash_swd.py ← прошивка через SWD (pyocd, FCB+HAB) +├── HAB_GUIDE.md +├── pyproject.toml +├── uv.lock └── README.md ``` +--- + ## Предварительные требования ### 1. uv — один раз на машину @@ -44,13 +61,11 @@ uv sync ### 3. udev правила — только Linux, один раз на машину -Без этого для работы с USB нужен `sudo`: - ```bash sudo tee /etc/udev/rules.d/99-nxp-mimxrt.rules << 'EOF' -# NXP BootROM — SDP режим (BOOT_MOD_1 = 3V3) +# NXP BootROM — SDP режим SUBSYSTEM=="usb", ATTR{idVendor}=="1fc9", ATTR{idProduct}=="0130", MODE="0666", GROUP="plugdev" -# NXP Flashloader — после jump-address +# NXP Flashloader SUBSYSTEM=="usb", ATTR{idVendor}=="15a2", ATTR{idProduct}=="0073", MODE="0666", GROUP="plugdev" EOF @@ -61,18 +76,20 @@ sudo usermod -a -G plugdev $USER --- -## Карта Flash (W25Q64FVSSIG, 8 MB) +## Карта Flash ```bash 0x60000000 ┌─────────────────────────────┐ │ FCB — Flash Config Block │ 512 байт - │ (пишет Flashloader, │ - │ не входит в HAB образ) │ + │ При USB SDP: пишет │ + │ Flashloader автоматически. │ + │ При SWD: flash_swd.py │ + │ берёт из dcd/*_fdcb.bin. │ 0x60001000 ├─────────────────────────────┤ - │ IVT — Image Vector Table │ ← начало HAB образа + │ IVT — Image Vector Table │ ← начало HAB-образа │ BDT — Boot Data Table │ 0x60001040 ├─────────────────────────────┤ - │ DCD — SDRAM init │ ~1088 байт (для fw_test и app) + │ DCD — SDRAM init │ ~1088 байт (fw_test, app) 0x60003000 ├─────────────────────────────┤ │ Код прошивки │ │ (.text, .data, ...) │ @@ -81,68 +98,39 @@ sudo usermod -a -G plugdev $USER --- -## Пайплайн: сборка HAB образа +## Способ 1 — USB SDP (`flash_usb.py`) -```bash -cd tools/host/hab - -# firmware_test (Debug) — входной контроль платы -uv run nxpimage hab export --force \ - -c hab_firmware_test.yaml \ - -o ../../../../build/Debug/firmware_test_hab.bin - -# bootloader (Release) -uv run nxpimage hab export --force \ - -c hab_bootloader.yaml \ - -o ../../../../build/Release/bootloader_hab.bin - -# app (Release) — боевая прошивка -uv run nxpimage hab export --force \ - -c hab_app.yaml \ - -o ../../../../build/Release/app_hab.bin -``` - -### Проверка образа после сборки - -```bash -uv run nxpimage hab parse -b ../../../../build/Debug/firmware_test_hab.bin -``` - -Ожидаемый результат: IVT с корректным `entry`, DCD с тегом `0xD2`, -`csf = 0x00000000` (unsigned). - ---- - -## Пайплайн: прошивка через USB +Прошивка через ROM-загрузчик. Требует перевода платы в режим Serial Downloader. ### Подготовка платы -Перевести плату в режим SDP (Serial Download Protocol): - -1. Подтянуть `BOOT_MOD_1` к `3V3` -2. Reset -3. Подключить USB к ПК - -В этом режиме плата определяется как `VID:PID = 1FC9:0130`. - -### Запуск скрипта - ```bash -cd tools/host - -# firmware_test -uv run python3 flash_usb.py --firmware firmware_test --build-type Debug - -# app -uv run python3 flash_usb.py --firmware app --build-type Release - -# bootloader -uv run python3 flash_usb.py --firmware bootloader --build-type Release +1. BOOT_MOD_1 → 3V3 +2. Reset +3. Подключить USB → плата определяется как VID:PID 1FC9:0130 ``` -После прошивки: вернуть `BOOT_MOD_1` к `GND`, Reset — плата стартует из Flash. +После прошивки: `BOOT_MOD_1 → GND → Reset`. -### Последовательность команд внутри скрипта +### Запуск + +```bash +# Предпочтительно через just (на хосте): +just host::flash firmware_test debug +just host::flash firmware_test release +just host::flash bootloader release +just host::flash app release + +# Или напрямую: +cd tools/host +uv run python3 flash_usb.py --firmware firmware_test --build-type Debug +uv run python3 flash_usb.py --firmware app --build-type Release + +# Загрузка в RAM без записи во Flash (быстро, не изнашивает Flash): +uv run python3 flash_usb.py --firmware firmware_test --build-type Debug --ram-only +``` + +### Последовательность команд ```bash Плата в SDP режиме (1FC9:0130) @@ -153,29 +141,117 @@ uv run python3 flash_usb.py --firmware bootloader --build-type Release │ (ожидание до 10с пока Flashloader поднимется на 15A2:0073) │ Flashloader (15A2:0073) - │ - │ Шаг 1 — инициализация FlexSPI NOR контроллера - ├─ blhost fill-memory 0x2000 4 0xC0000007 word - ├─ blhost configure-memory 9 0x2000 - │ - │ Шаг 2 — стирание Flash - ├─ blhost flash-erase-region 0x60000000 0 (memoryId=0, XIP) - │ - │ Шаг 3 — запись FCB в 0x60000000 - │ (ПОСЛЕ стирания! Flashloader генерирует FCB из параметров шага 1) - ├─ blhost fill-memory 0x2000 4 0xF000000F word - ├─ blhost configure-memory 9 0x2000 - │ - │ Шаг 4 — запись HAB образа начиная с 0x60001000 - ├─ blhost write-memory 0x60001000 *_hab.bin 0 (memoryId=0, XIP) - └─ blhost reset + ├─ configure-memory 0xC0000007 — инициализация FlexSPI NOR + ├─ flash-erase-region 0x60000000 + ├─ configure-memory 0xF000000F — запись FCB в 0x60000000 + ├─ write-memory 0x60001000 ← HAB-образ + └─ reset ``` -> **Почему FCB не в образе?** -> Flashloader генерирует FCB автоматически из параметров FlexSPI (option word -> `0xC0000007`). Запись FCB через `0xF000000F` — это отдельная команда, -> выполняемая строго после `flash-erase-region`, иначе FCB будет затёрт следующим -> стиранием. +FCB генерируется Flashloader'ом автоматически из параметров FlexSPI — отдельный +`*_fdcb.bin` не нужен. + +--- + +## Способ 2 — SWD (`flash_swd.py`) + +Прошивка через отладочный пробник (MCU-Link, CMSIS-DAP). Плата остаётся в +нормальном режиме загрузки — переключать `BOOT_MOD_1` не нужно. + +**После записи обязателен power cycle** — VECTRESET не реинициализирует FlexSPI, +Boot ROM не стартует без холодного старта. + +### Почему нужен FCB при SWD + +При USB SDP FlexSPI конфигурируется ROM-загрузчиком через DCD. При SWD +flash-алгоритм pyOCD пишет данные напрямую — Boot ROM при cold-start читает FCB +первым и по нему конфигурирует FlexSPI. Без FCB плата не стартует. + +`flash_swd.py` собирает итоговый образ перед записью: + +``` +0x60000000 *_fdcb.bin (512 байт) — FCB +0x60000200 0xFF × 3584 байт — padding (erased flash value) +0x60001000 *_hab.bin — HAB-образ (ivtOffset = 0x1000) +``` + +Всё умещается в один 64KB-сектор — стирается и записывается за одну транзакцию. + +### FCB-файлы + +| Файл | Микросхема | Режим | +|---|---|---| +| `w25q64_fdcb.bin` | Winbond W25Q64 | Quad SPI | +| `w25q128_fdcb.bin` | Winbond W25Q128 | Quad SPI | +| `w25q512_fdcb.bin` | Winbond W25Q512 | Quad SPI | + +Активный FCB задаётся в `.env`: `FCB_PATH=tools/host/dcd/w25q128_fdcb.bin`. + +FCB-файлы получены из NXP SecureProvisioningTool и хранятся в репозитории — +пересоздавать не нужно. + +### Запуск + +```bash +# Предпочтительно через just (на хосте): +just host::flash-swd-test-debug +just host::flash-swd-test-release +just host::flash-swd-bootloader-debug +just host::flash-swd-bootloader-release +just host::flash-swd-app-debug +just host::flash-swd-app-release + +# Или напрямую: +cd tools/host +uv run --directory ../hil python3 flash_swd.py --firmware firmware_test --build-type Debug +uv run --directory ../hil python3 flash_swd.py --firmware app --build-type Release + +# Собрать образ без записи (для проверки): +uv run --directory ../hil python3 flash_swd.py --firmware firmware_test --build-type Debug --dry-run +``` + +### Конфигурация flash_swd.py + +Приоритет: аргументы CLI > переменные окружения > defaults. + +| Переменная | CLI-аргумент | Default | +|---|---|---| +| `PYOCD_TARGET` | `--target` | `mimxrt1050_quadspi` | +| `PYOCD_FREQUENCY` | `--frequency` | `4000000` | +| `BUILD_DIR` | — | `/build` | +| `FCB_PATH` | `--fcb` | `tools/host/dcd/w25q128_fdcb.bin` | + +Переменные задаются в `.env` и экспортируются через `just` (`set export`). + +--- + +## Сравнение способов + +| | USB SDP | SWD | +|---|---|---| +| Переключение BOOT_MODE | Нужно | Не нужно | +| Power cycle после записи | Не нужен | **Обязателен** | +| FCB в образе | Не нужен (Flashloader пишет сам) | **Нужен** (`*_fdcb.bin`) | +| Скорость записи | ~50–100 kB/s | ~8–10 kB/s | +| Совместимость с отладкой | Раздельно | MCU-Link монопольный | +| Производственный сценарий | ✓ | — | +| Итеративная разработка | Неудобно (смена режима) | ✓ | + +--- + +## Пайплайн: сборка HAB-образов + +Выполняется **внутри devcontainer**: + +```bash +just build::hab-firmware-test-debug # → build/Debug/firmware_test_hab.bin +just build::hab-firmware-test-release # → build/Release/firmware_test_hab.bin +just build::hab-bootloader-debug +just build::hab-bootloader-release +just build::hab-app-debug +just build::hab-app-release +just build::hab-all-release # все три Release за один раз +``` --- @@ -185,34 +261,30 @@ uv run python3 flash_usb.py --firmware bootloader --build-type Release управления прошивке. Инициализирует PLL, CCM clock gates, SEMC контроллер и микросхему SDRAM (MT48LCxxM4). -Файл хранится в репозитории в бинарном виде и **не требует пересборки** — он -меняется только при изменении схемотехники. +Файл хранится в репозитории в бинарном виде и **не требует пересборки**. ```bash Цель: MT48LC16M16A2P-6A, 32 MB, шина 16 бит, CS0 SEMC BR0: base=0x80000000, size=32MB, VLD=1 - SEMC BR1–BR3: VLD=0 (один чип, один CS) + SEMC BR1–BR3: VLD=0 ``` -Использование DCD по прошивкам: - | Прошивка | DCD | Причина | |---|---|---| | `firmware_test` | ✓ | тесты работают с SDRAM | -| `app` | ✓ | FreeRTOS heap и буферы LCDIF размещены в SDRAM | -| `bootloader` | ✗ | загрузчик не использует SDRAM, инициализация в app | +| `app` | ✓ | FreeRTOS heap и буферы LCDIF в SDRAM | +| `bootloader` | ✗ | загрузчик не использует SDRAM | --- ## Flashloader `dcd/ivt_flashloader.bin` — NXP-программа, загружаемая в RAM через SDP. -Предоставляет `blhost` доступ к Flash, которого нет через BootROM напрямую. ```bash Entry point: 0x20002401 Загрузка по: 0x20001C00 -VID:PID после старта: 15A2:0073 +VID:PID после: 15A2:0073 Источник: MCUXpresso Secure Provisioning Tool 25.12 ``` @@ -224,15 +296,14 @@ VID:PID после старта: 15A2:0073 # Найти подключённые NXP устройства uv run nxpdevscan -# Проверить связь с BootROM через SDP +# Проверить связь с BootROM (плата в SDP-режиме) sdphost -u 0x1FC9,0x0130 -- error-status -# Проверить что Flashloader отвечает (после jump-address) +# Проверить что Flashloader отвечает blhost -u 0x15A2,0x0073 -- get-property 1 0 -# Версии инструментов -uv run nxpimage --version -uv run blhost --version +# Проверить что pyOCD видит таргет (для SWD) +just host::debug-list-targets ``` ### Типичные ошибки @@ -240,10 +311,11 @@ uv run blhost --version | Симптом | Причина | Решение | |---|---|---| | `USB HID device not found: 1FC9:0130` | Плата не в SDP режиме | Проверить `BOOT_MOD_1` → 3V3 и Reset | -| Flashloader timeout после jump | `ivt_flashloader.bin` повреждён или не тот | Взять из SPT 25.12 | -| Плата не стартует после прошивки | `BOOT_MOD_1` не переключён обратно | Вернуть `BOOT_MOD_1` → GND, Reset | -| Плата зависает сразу после старта | DCD завис (неверный `dcd.bin`) | Использовать только верифицированный `dcd.bin` | -| Плата стартует, периферия не работает | CCM clock gates в DCD отключают нужные клоки | Проверить `dcd.bin` — только верифицированный вариант | +| Flashloader timeout после jump | `ivt_flashloader.bin` повреждён | Взять из SPT 25.12 | +| Плата не стартует после USB SDP | `BOOT_MOD_1` не переключён обратно | `BOOT_MOD_1` → GND, Reset | +| Плата не стартует после SWD flash | Power cycle не был выполнен | Отключить и подключить питание | +| Плата не стартует после SWD flash | Неверный FCB (другая Flash-микросхема) | Проверить `FCB_PATH` в `.env` | +| `skipped N bytes` при SWD flash | pyOCD считает содержимое актуальным | Добавить `--erase chip` или `--erase sector` | --- diff --git a/tools/host/dcd/w25q128_fdcb.bin b/tools/host/dcd/w25q128_fdcb.bin new file mode 100644 index 0000000..cf32c52 Binary files /dev/null and b/tools/host/dcd/w25q128_fdcb.bin differ diff --git a/tools/host/dcd/w25q512_fdcb.bin b/tools/host/dcd/w25q512_fdcb.bin new file mode 100644 index 0000000..f62c843 Binary files /dev/null and b/tools/host/dcd/w25q512_fdcb.bin differ diff --git a/tools/host/dcd/w25q64_fdcb.bin b/tools/host/dcd/w25q64_fdcb.bin new file mode 100644 index 0000000..61b605c Binary files /dev/null and b/tools/host/dcd/w25q64_fdcb.bin differ diff --git a/tools/host/flash_swd.py b/tools/host/flash_swd.py new file mode 100644 index 0000000..fdf1b82 --- /dev/null +++ b/tools/host/flash_swd.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +""" +flash_swd.py — прошивка MIMXRT1052 через SWD (MCU-Link / CMSIS-DAP) + +Отличие от flash_usb.py (spsdk): + flash_usb.py — USB Serial Downloader Protocol (SDP). Плата должна быть в + режиме Serial Downloader (BOOT_MODE = 01). Использует ROM- + загрузчик для разбора IVT/DCD/HAB — сам конфигурирует FlexSPI + по DCD, поэтому FCB в образе не нужен. + + flash_swd.py — SWD через отладочный пробник (MCU-Link). Плата в любом + режиме загрузки; отладчик загружает flash-алгоритм в RAM + и пишет NOR Flash напрямую. Boot ROM при cold-start читает + FCB первым — поэтому этот скрипт объединяет FCB + HAB + перед записью в единый образ. + +Структура образа в Flash (XIP NOR): + 0x60000000 FCB (512 байт) — Flash Configuration Block (W25Q128, Quad SPI) + 0x60000200 0xFF (padding) — до адреса IVT + 0x60001000 IVT — начало HAB-образа (ivtOffset = 0x1000) + 0x60001020 DCD — инициализация SDRAM (если есть) + 0x60002000 .text / .data — код прошивки + +Конфигурация (приоритет: аргументы CLI > переменные окружения > defaults): + PYOCD_TARGET — таргет pyOCD (default: mimxrt1050_quadspi) + PYOCD_FREQUENCY — частота SWD в Гц (default: 4000000) + BUILD_DIR — директория сборки (default: /build) + FCB_PATH — путь к FCB-бинарнику (default: tools/host/dcd/w25q128_fdcb.bin) + +Переменные задаются в .env и автоматически экспортируются через just (set export). + +Использование: + python flash_swd.py --firmware firmware_test --build-type Debug + python flash_swd.py --firmware bootloader --build-type Release + python flash_swd.py --firmware app --build-type Debug --dry-run +""" + +import argparse +import os +import subprocess +import sys +import tempfile +from pathlib import Path + +# ── Корень репозитория ──────────────────────────────────────────────────────── + +REPO_ROOT = Path(__file__).resolve().parents[2] + +# ── Конфигурация из окружения (just экспортирует .env через set export) ─────── + +def _env(key: str, default: str) -> str: + return os.environ.get(key, default) + +PYOCD_TARGET = _env("PYOCD_TARGET", "mimxrt1050_quadspi") +PYOCD_FREQUENCY = _env("PYOCD_FREQUENCY", "4000000") +BUILD_DIR = Path(_env("BUILD_DIR", str(REPO_ROOT / "build"))) +FCB_PATH = Path(_env("FCB_PATH", str(REPO_ROOT / "tools/host/dcd/w25q128_fdcb.bin"))) +if not FCB_PATH.is_absolute(): + FCB_PATH = REPO_ROOT / FCB_PATH +HIL_DIR = REPO_ROOT / "tools" / "hil" + +# IVT располагается по смещению 0x1000 от начала Flash (ivtOffset в HAB yaml) +IVT_OFFSET = 0x1000 + +# Маппинг имён прошивок → имена HAB-файлов (генерируются build::hab-*) +HAB_NAMES = { + "firmware_test": "firmware_test_hab.bin", + "bootloader": "bootloader_hab.bin", + "app": "app_hab.bin", +} + +# ── Helpers ─────────────────────────────────────────────────────────────────── + +def build_full_image(fcb_path: Path, hab_path: Path) -> bytes: + """Объединить FCB + padding + HAB в единый образ для записи с 0x60000000.""" + fcb = fcb_path.read_bytes() + hab = hab_path.read_bytes() + + if len(fcb) > IVT_OFFSET: + raise ValueError( + f"FCB size {len(fcb)} bytes exceeds IVT_OFFSET {IVT_OFFSET:#x}" + ) + + padding = b"\xff" * (IVT_OFFSET - len(fcb)) # 0xFF = erased flash value + image = fcb + padding + hab + + print(f" FCB: {len(fcb):>6} bytes @ 0x60000000") + print(f" Padding: {len(padding):>6} bytes @ 0x{0x60000000 + len(fcb):08X}") + print(f" HAB: {len(hab):>6} bytes @ 0x60001000") + print(f" Total: {len(image):>6} bytes") + + return image + + +def run_pyocd_flash(image_path: Path, target: str, frequency: str) -> int: + """Запустить pyocd flash через uv run из tools/hil.""" + cmd = [ + "uv", "run", + "--directory", str(HIL_DIR), + "pyocd", "flash", + "--target", target, + "--frequency", frequency, + "--base-address", "0x60000000", + "--erase", "sector", + str(image_path), + ] + print(f"\n Running: {' '.join(cmd)}\n") + return subprocess.run(cmd, cwd=REPO_ROOT).returncode + + +# ── Main ────────────────────────────────────────────────────────────────────── + +def main() -> int: + parser = argparse.ArgumentParser( + description="Flash MIMXRT1052 via SWD (MCU-Link). " + "Prepends FCB to HAB image before programming.", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__, + ) + parser.add_argument( + "--firmware", + choices=list(HAB_NAMES.keys()), + required=True, + help="Firmware project to flash", + ) + parser.add_argument( + "--build-type", + choices=["Debug", "Release"], + default="Debug", + help="CMake build type (default: Debug)", + ) + parser.add_argument( + "--fcb", + type=Path, + default=FCB_PATH, + help=f"Path to FCB binary (default from FCB_PATH env or {FCB_PATH})", + ) + parser.add_argument( + "--target", + default=PYOCD_TARGET, + help=f"pyOCD target (default from PYOCD_TARGET env or {PYOCD_TARGET})", + ) + parser.add_argument( + "--frequency", + default=PYOCD_FREQUENCY, + help=f"SWD frequency in Hz (default from PYOCD_FREQUENCY env or {PYOCD_FREQUENCY})", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Build the combined image and save it, but do not flash", + ) + args = parser.parse_args() + + # ── Проверить FCB ───────────────────────────────────────────────────────── + if not args.fcb.exists(): + print(f" ❌ FCB not found: {args.fcb}", file=sys.stderr) + print( + " Hint: obtain w25q128_fdcb.bin from NXP SecureProvisioningTool\n" + " for W25Q128 in Quad SPI mode and place it in tools/host/dcd/\n" + " or set FCB_PATH in .env", + file=sys.stderr, + ) + return 1 + + # ── Найти HAB-образ ─────────────────────────────────────────────────────── + hab_name = HAB_NAMES[args.firmware] + hab_path = BUILD_DIR / args.build_type / hab_name + + if not hab_path.exists(): + print(f" ❌ HAB image not found: {hab_path}", file=sys.stderr) + fw_slug = args.firmware.replace("_", "-") + bt_slug = args.build_type.lower() + print( + f" Hint: run just build::hab-{fw_slug}-{bt_slug} " + "inside devcontainer first.", + file=sys.stderr, + ) + return 1 + + print(f"\n Firmware : {args.firmware} ({args.build_type})") + print(f" HAB : {hab_path}") + print(f" FCB : {args.fcb}") + print(f" Target : {args.target}") + print(f" Frequency : {args.frequency} Hz\n") + + # ── Собрать объединённый образ ──────────────────────────────────────────── + image = build_full_image(args.fcb, hab_path) + + if args.dry_run: + out = BUILD_DIR / args.build_type / f"{args.firmware}_full.bin" + out.parent.mkdir(parents=True, exist_ok=True) + out.write_bytes(image) + print(f"\n Dry run — image saved to {out}") + return 0 + + # ── Записать во Flash через pyOCD ───────────────────────────────────────── + with tempfile.NamedTemporaryFile( + suffix=f"_{args.firmware}_full.bin", delete=False + ) as tmp: + tmp_path = Path(tmp.name) + tmp_path.write_bytes(image) + + try: + rc = run_pyocd_flash(tmp_path, args.target, args.frequency) + finally: + tmp_path.unlink(missing_ok=True) + + if rc == 0: + print(f"\n ✅ {args.firmware} ({args.build_type}) flashed via SWD") + print(" ⚡ Power cycle the board to boot from Flash") + else: + print(f"\n ❌ pyocd flash failed (exit code {rc})", file=sys.stderr) + + return rc + + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file