- работает загрузка подготовленного HAB бинарника по SWD в QSPI
- после этого можно запускать отладку в VSCode devcontainer
- От SeggerRTT отказался, логи будут в UART
This commit is contained in:
Dmitry Akimov 2026-03-20 15:52:18 +03:00
parent ec2c02b957
commit 4570b27e71
17 changed files with 1321 additions and 832 deletions

View file

@ -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:

44
.vscode/launch.json vendored
View file

@ -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",
}
]
}

11
.vscode/tasks.json vendored
View file

@ -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": [
{

View file

@ -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]<e00fe000:ROM class=1 designer=43b:Arm part=4c8> [rom_table]
0000187 I AHB-AP#0 Class 0x1 ROM table #1 @ 0xe00fe000 (designer=43b:Arm part=4c8) [rom_table]
0000191 I [0]<e00ff000:ROM class=1 designer=43b:Arm part=4c7> [rom_table]
0000191 I AHB-AP#0 Class 0x1 ROM table #2 @ 0xe00ff000 (designer=43b:Arm part=4c7) [rom_table]
0000194 I [0]<e000e000:SCS v7-M class=14 designer=43b:Arm part=00c> [rom_table]
0000207 I [1]<e0001000:DWT v7-M class=14 designer=43b:Arm part=002> [rom_table]
0000209 I [2]<e0002000:FPB v7-M class=14 designer=43b:Arm part=00e> [rom_table]
0000210 I [3]<e0000000:ITM v7-M class=14 designer=43b:Arm part=001> [rom_table]
0000210 D [4]<fff41002 not present> [rom_table]
0000210 D [5]<fff42002 not present> [rom_table]
0000214 I [1]<e0041000:ETM M7 class=9 designer=43b:Arm part=975 devtype=13 archid=4a13 devid=0:0:0> [rom_table]
0000219 I [2]<e0042000:CTI CS-400 class=9 designer=43b:Arm part=906 devtype=14 archid=0000 devid=40800:0:0> [rom_table]
0000219 D [3]<1ff02002 not present> [rom_table]
0000221 I [1]<e0040000:TPIU M7 class=9 designer=43b:Arm part=9a9 devtype=11 archid=0000 devid=ca1:0:0> [rom_table]
0000222 I [2]<e0043000:TSGEN class=15 designer=43b:Arm part=101> [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 <pyocd.core.session.Session object at 0x102979420> [session]
0032270 D uninit board <pyocd.board.board.Board object at 0x1048020e0> [board]
0032271 D cannot resume core 0: core is RUNNING [cortex_m]

152
TODO.md Normal file
View file

@ -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
```

228
docs/HOW_TO_DEBUG.md Normal file
View file

@ -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)
```
Весь диапазон `0x600000000x6000FFFF` умещается в один 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
```

View file

@ -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 <project> <type>
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 пишет сам) | **Обязателен** |
| Скорость записи | ~50100 kB/s | ~810 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
```

View file

@ -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);

View file

@ -206,10 +206,11 @@ bootstrap:
echo ""
# =============================================================================
# ГРУППА: flash
# ГРУППА: flash — USB Serial Downloader (SDP через ROM-загрузчик)
# Требует: плата в режиме Serial Downloader (BOOT_MODE = 01)
# =============================================================================
[doc('Прошить образ во Flash через USB ROM: flash <firmware_test|bootloader|app> <debug|release>')]
[doc('Прошить образ через USB SDP: flash <firmware_test|bootloader|app> <debug|release>')]
[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 <firmware_test|bootloader|app> <debug|release>')]
[doc('Прошить образ в RAM через USB SDP: flash-ram <firmware_test|bootloader|app> <debug|release>')]
[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: <project> → 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

View file

@ -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

View file

@ -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/<name>/main.c ← C-прошивка с текстовым CLI через UART
tools/hil/test_<name>.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_<n>()
├── 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_<n>`
```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/<name>/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/<name>/main.c — C-прошивка с CLI
2. tests/target/<name>/CMakeLists.txt — сборка с bsp_boot_ram
3. tests/target/CMakeLists.txt — add_subdirectory(<name>)
4. CMakePresets.json — добавить test_<name> в target-debug-build
5. tools/hil/test_<name>.py — pytest-тесты
6. tools/hil/conftest.py — добавить loaded_<name> фикстуру
```
Фикстура для нового теста в `conftest.py`:
```python
@pytest.fixture(scope="module")
def loaded_<name>(request: pytest.FixtureRequest) -> None:
_load_elf(
request,
Path(cfg.BUILD_DIR) / "tests/target/<name>/test_<name>.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.

View file

@ -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

View file

@ -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 <size> 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` | — | `<repo>/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`) |
| Скорость записи | ~50100 kB/s | ~810 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 BR1BR3: VLD=0 (один чип, один CS)
SEMC BR1BR3: 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` |
---

Binary file not shown.

Binary file not shown.

Binary file not shown.

219
tools/host/flash_swd.py Normal file
View file

@ -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: <repo_root>/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())