- настроен первый HIL-тест (пока без m5stamp; pytest + pyserial + pyocd), правки во всей документации
- начало настройки окружения для отладки проектов firmware/* в devcontainer
This commit is contained in:
Dmitry Akimov 2026-03-19 18:28:45 +03:00
parent 8a96e66e38
commit 8c9e62b3cc
23 changed files with 2997 additions and 730 deletions

View file

@ -22,6 +22,7 @@
"mcu-debug.debug-tracker-vscode",
"mcu-debug.memory-view",
"mcu-debug.peripheral-viewer",
"marus25.cortex-debug",
"brobeson.ctest-lab",
"mcu-debug.rtos-views",
"ms-vscode.hexeditor",
@ -67,6 +68,7 @@
}
},
"runArgs": [
"--add-host=host.docker.internal:host-gateway",
"--name",
"tft-devcontainer"
]

View file

@ -26,4 +26,12 @@ CMAKE_GENERATOR=Ninja
OPENOCD_INTERFACE=cmsis-dap.cfg
TARGET_CFG=target/imxrt.cfg
GDB_PORT=3333
GDB_EXECUTABLE=gdb-multiarch
GDB_EXECUTABLE=gdb-multiarch
# --- 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

96
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,96 @@
{
"version": "0.2.0",
"configurations": [
// =============================================================
// firmware/test без перепрошивки (прошивка уже в flash)
// =============================================================
{
"name": "🐛 Debug: firmware_test",
"type": "cortex-debug",
"request": "launch",
"servertype": "external",
"gdbTarget": "host.docker.internal:3333",
"gdbPath": "arm-none-eabi-gdb",
"executable": "${workspaceFolder}/build/Debug/firmware/test/firmware_test.elf",
"device": "MIMXRT1052CVJ5B",
"svdFile": "${workspaceFolder}/bsp/generated/startup/MIMXRT1052.xml",
"interface": "swd",
"loadFiles": [],
"runToEntryPoint": "main",
"preLaunchTask": "build:firmware-test-debug",
"rttConfig": {
"enabled": true,
"address": "auto",
"clearSearch": false,
"decoders": [
{
"port": 0,
"type": "console",
"label": "RTT"
}
]
}
},
// =============================================================
// firmware/bootloader без перепрошивки
// =============================================================
{
"name": "🐛 Debug: bootloader",
"type": "cortex-debug",
"request": "launch",
"servertype": "external",
"gdbTarget": "host.docker.internal:3333",
"gdbPath": "arm-none-eabi-gdb",
"executable": "${workspaceFolder}/build/Debug/firmware/bootloader/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"
}
]
}
},
// =============================================================
// firmware/tft_app FreeRTOS task view
// =============================================================
{
"name": "🐛 Debug: tft_app (FreeRTOS)",
"type": "cortex-debug",
"request": "launch",
"servertype": "external",
"gdbTarget": "host.docker.internal:3333",
"gdbPath": "arm-none-eabi-gdb",
"executable": "${workspaceFolder}/build/Debug/firmware/tft_app/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"
}
]
}
}
]
}

83
.vscode/tasks.json vendored
View file

@ -65,6 +65,29 @@
"problemMatcher": []
},
// =============================================================
// HIL TARGET-ТЕСТЫ
// =============================================================
{
"label": "🎯 Build HIL Target Tests",
"group": "build",
"type": "shell",
"command": "just",
"args": [
"build::build-hil"
],
"options": {
"cwd": "${workspaceFolder}",
"statusbar": {
"color": "#00e5ff",
"label": "$(circuit-board) HIL Build",
"detail": "Build HIL target firmware"
}
},
"problemMatcher": [
"$gcc"
]
},
// =============================================================
// HAB-ОБРАЗЫ
// =============================================================
{
@ -131,6 +154,66 @@
}
},
"problemMatcher": []
},
// =============================================================
// СБОРКА ДЛЯ ОТЛАДКИ (preLaunchTask без интерактивных inputs)
// =============================================================
{
"label": "build:firmware-test-debug",
"type": "shell",
"hide": true,
"command": "just",
"args": [
"build::build-firmware-test-debug"
],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": [
"$gcc"
],
"presentation": {
"reveal": "silent",
"panel": "shared"
}
},
{
"label": "build:bootloader-debug",
"hide": true,
"type": "shell",
"command": "just",
"args": [
"build::build-bootloader-debug"
],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": [
"$gcc"
],
"presentation": {
"reveal": "silent",
"panel": "shared"
}
},
{
"label": "build:app-debug",
"hide": true,
"type": "shell",
"command": "just",
"args": [
"build::build-app-debug"
],
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": [
"$gcc"
],
"presentation": {
"reveal": "silent",
"panel": "shared"
}
}
],
"inputs": [

View file

@ -13,9 +13,19 @@ option(UNITY_TESTING_ENABLED "Enable Unity framework" OFF)
# Общие настройки для ARM сборки
# -----------------------------------------------------------------------------
if(NOT BUILD_TESTS_HOST)
set(CMAKE_EXECUTABLE_SUFFIX ".elf")
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
set(BSP_SYSCALLS_FILE
"${CMAKE_SOURCE_DIR}/bsp/generated/syscalls.c"
CACHE FILEPATH "Заглушки системных вызовов newlib")
set(BSP_GENERATED
"${CMAKE_SOURCE_DIR}/bsp/generated"
CACHE PATH "Путь до сгенерированных ConfigTools файлов")
set(BSP_STARTUP_FILE
"${BSP_GENERATED}/startup/startup_MIMXRT1052.S"
CACHE FILEPATH "Путь до стартап файла")
endif()
# -----------------------------------------------------------------------------

View file

@ -45,22 +45,18 @@ default:
# === Популярные алиасы (для удобства команды) ===
# Сокращают длинные вызовы модулей
# Инициализация окружения (эквивалент host::bootstrap)
[doc('Первичная настройка окружения после git clone (на хосте)')]
init:
@just host::bootstrap
# Быстрая прошивка тестового образа в Debug
[doc('Прошить firmware_test Debug во Flash через USB ROM')]
flash:
@just host::flash-test-debug
# Полная сборка всех проектов в Release (в контейнере)
[doc('Собрать все HAB-образы в Release')]
build-all:
@just build::hab-all-release
# CI пайплайн
[doc('Запустить CI pipeline')]
run-ci:
@just ci::pipeline

195
README.md
View file

@ -17,60 +17,78 @@
│ ├── launch.json
│ └── tasks.json # UI для just build::* (внутри devcontainer)
├── bsp/ # Board Support Package
│ └── generated/ # Сгенерировано NXP Config Tools (Pins + Clocks Tool)
│ ├── TFT_Board.mex # Источник истины конфигурации пинов и тактирования
│ ├── pin_mux.c/h # Сгенерировано из .mex (Pins Tool)
│ ├── clock_config.c/h # Сгенерировано из .mex (Clocks Tool)
│ ├── board.c/h # Ручная инициализация специфики платы
│ └── BOOT_FLAGS.md # Описание флагов загрузчика
├── cmake/ # Общие CMake модули и toolchain files
│ ├── linker/ # Линкер-скрипты под разные схемы размещения
│ ├── CMakeLists.txt
│ ├── common/ # Общие типы (bsp_status_t и др.)
│ ├── generated/ # Сгенерировано NXP Config Tools (Pins + Clocks Tool)
│ │ ├── TFT_Board.mex # Источник истины конфигурации пинов и тактирования
│ │ ├── pin_mux.c/h # Сгенерировано из .mex (Pins Tool)
│ │ ├── clock_config.c/h # Сгенерировано из .mex (Clocks Tool)
│ │ ├── board.c/h # Ручная инициализация специфики платы
│ │ ├── syscalls.c # Заглушки системных вызовов newlib
│ │ └── startup/ # Стартап-файл для ARM
│ ├── led/ # bsp_led — два UserLed (GPIO3_IO03, GPIO3_IO04)
│ ├── tick/ # bsp_tick — SysTick / FreeRTOS-совместимый таймер
│ ├── uart_host/ # bsp_uart_host — LPUART1 (MCU-Link VCOM, J2)
│ │ └── mocks/ # Мок-реализация для host-тестов
│ └── usb_cdc/ # bsp_usb_cdc — USB CDC ACM
├── cmake/ # Общие CMake модули
│ ├── linker/ # Линкер-скрипты (ram, flexspi_nor, sdram и др.)
│ ├── toolchain_arm.cmake # ARM cross-compilation toolchain
│ └── toolchain_host.cmake # Host GCC для unit-тестов
├── sdk/ # NXP MCUXpresso SDK — vendored
│ ├── CMakeLists.txt
│ ├── CMSIS/
│ ├── devices/MIMXRT1052/ # Драйверы, startup, утилиты
│ ├── components/ # fsl_button, fsl_led, serial_manager и др.
│ ├── middleware/ # FatFS, FreeRTOS, LittleFS, USB, mcuboot и др.
│ └── rtos/freertos/ # FreeRTOS (vendored через SDK)
├── lib/ # Внешние библиотеки
│ ├── Unity/ # Фреймворк для unit-тестов (vendored)
│ ├── fff/ # Fake Function Framework для моков (vendored)
├── sdk/ # NXP MCUXpresso SDK — vendored
├── lib/ # Внешние библиотеки — vendored
│ ├── Unity/ # Фреймворк для unit-тестов
│ ├── fff/ # Fake Function Framework для моков
│ └── SEGGER/ # SEGGER RTT — вывод логов через отладчик
├── firmware/
│ ├── test/ # [Проект 1] Тестовая прошивка — входной контроль платы
│ ├── bootloader/ # [Проект 2] Загрузчик с поддержкой A/B обновления
│ └── tft_app/ # [Проект 3] Основная боевая прошивка (FreeRTOS)
├── tests/ # Тесты (host + target)
│ ├── host/ # Unit/интеграционные тесты, запускаемые на хосте
│ ├── target/ # Тесты периферии, запускаемые на таргете
│ ├── HostTestingGuide.md
│ └── README.md
├── tests/
│ ├── CMakeLists.txt
│ ├── host/ # Unit-тесты на хостовом компиляторе (Unity + fff)
│ │ ├── mocks/ # Stub-хедеры NXP SDK для компиляции на хосте
│ │ ├── led/ # Тесты bsp_led
│ │ ├── ring_buffer/ # Тесты ring_buffer
│ │ ├── timeout/ # Тесты таймаут-паттерна
│ │ └── uart_host/ # Тесты bsp_uart_host (через мок)
│ ├── target/ # HIL target-прошивки (загружаются в RAM через pyOCD)
│ │ └── host_uart/ # CLI-прошивка для тестирования bsp_uart_host
│ ├── HOST_CREATE_TEST.md # Гайд: добавление host-теста
│ └── HIL_CREATE_TEST.md # Гайд: добавление HIL-теста
├── tools/
│ └── host/ # Инструменты для работы с таргетом
│ ├── flash_usb.py # Прошивка через USB ROM (nxp-spsdk / blhost)
│ ├── hab/ # Утилиты и гайд по HAB (Secure Boot)
│ ├── dcd/ # Device Configuration Data
│ ├── pyproject.toml
│ └── uv.lock
├── just/ # Just-модули (автоматизация)
│ ├── build.just # Сборка, тесты, HAB-образы (devcontainer)
│ ├── host.just # Прошивка, bootstrap, HIL (хост)
│ ├── host/ # Инструменты прошивки (spsdk)
│ │ ├── flash_usb.py # Прошивка через USB ROM (sdphost + blhost)
│ │ ├── hab/ # HAB yaml-конфиги для nxpimage
│ │ ├── dcd/ # ivt_flashloader.bin, dcd.bin
│ │ ├── pyproject.toml
│ │ └── uv.lock
│ └── hil/ # HIL-тесты (pytest + pyOCD + pyserial)
│ ├── pyproject.toml
│ ├── conftest.py # Фикстуры: загрузка ELF + UART
│ ├── pyocd_utils.py # FLEXRAM init, ELF loader, run_from_vectors
│ ├── env_config.py # Конфигурация из os.environ / .env
│ ├── load_and_run.py # CLI-утилита для ручной загрузки ELF в RAM микроконтроллера
│ └── test_uart.py # Тесты bsp_uart_host (PING/ECHO/BUF_SIZE)
├── utils/
│ └── ring_buffer/ # Платформонезависимый кольцевой буфер
├── just/
│ ├── build.just # devcontainer: сборка, тесты, HAB, HIL-прошивки
│ ├── host.just # хост: прошивка, bootstrap, HIL-запуск
│ └── ci.just # CI/CD пайплайны
├── docs/ # Документация проекта
│ ├── DEV_ARCH.md # Архитектура окружения разработки
│ ├── CMAKE_HINTS.md # Шпаргалка по CMake в проекте
│ ├── schematic.pdf # Схема платы
│ ├── mcu_rm.pdf # Reference Manual IMXRT1052
│ └── manufacturing_user's_guide.pdf
├── .env # Конфигурация проекта (VID:PID, пути, GDB и др.)
├── docs/
│ ├── DEV_ARCH.md
│ ├── CMAKE_HINTS.md
│ ├── HOW_TO_FLASH.md
│ ├── BOOT_FLAGS.md # Флаги загрузчика
│ └── schematic.pdf # Схема платы
├── pyocd.yaml # Конфигурация pyOCD (target: cortex_m, RAM-режим)
├── .env # Конфигурация проекта (VID:PID, HIL-порты и др.)
├── .env.example # Шаблон .env для новых разработчиков
├── bootstrap.sh # Первичная настройка окружения (уровень 0)
├── CMakeLists.txt # Корневой CMake
├── CMakePresets.json # Пресеты сборки (Release/Debug/Host)
├── justfile # Точка входа для команд (модули: build, host, ci)
└── README.md
├── CMakePresets.json # Пресеты сборки (Debug/Release/Host/Target)
└── justfile # Точка входа для команд (модули: build, host, ci)
```
---
@ -81,13 +99,11 @@
Bare-metal прошивка для **входного контроля** платы. Проверяет базовую работоспособность всех интерфейсов: CAN, UART, SDRAM, QSPI Flash, uSD (SDIO), RGB-интерфейс, гальванически развязанные входы, светодиоды, кнопки, IR-приёмник, MQS.
Загружается через USB ROM (Serial Download Mode) — подробнее в [HOW_TO_FLASH.md](HOW_TO_FLASH.md).
> **Рекомендуется начать разработку с этого проекта** — он наиболее прост и позволяет полностью отладить окружение сборки и прошивки.
Загружается через USB ROM (SDP) — подробнее в [docs/HOW_TO_FLASH.md](docs/HOW_TO_FLASH.md).
### 2. Загрузчик (`firmware/bootloader/`)
Отвечает за обновление боевой прошивки в полевых условиях. Поддерживает схему **A/B** с обновлением через uSD. Обновление самого загрузчика — только через внешний инструмент (USB ROM + blhost), не через себя. На производстве загружается единым blob-ом вместе с первой версией боевой прошивки.
Отвечает за обновление боевой прошивки в полевых условиях. Поддерживает схему **A/B** с обновлением через uSD. Обновление самого загрузчика — только через USB ROM + blhost, не через себя.
### 3. Боевая прошивка (`firmware/tft_app/`)
@ -97,50 +113,60 @@ Bare-metal прошивка для **входного контроля** пла
## Тестирование
Стратегия тестирования двухуровневая:
Стратегия тестирования трёхуровневая:
| Уровень | Расположение | Инструменты | Запуск |
| -------------------------------------- | --------------- | --------------- | -------------------------------------- |
| **Host-тесты** (unit + интеграционные) | `tests/host/` | Unity + fff | `just build::test-host` в devcontainer |
| **Target-тесты** (аппаратные) | `tests/target/` | Unity на железе | Удалённый ПК-сервер через SSH |
| Уровень | Расположение | Инструменты | Запуск |
|---------|-------------|-------------|--------|
| **Host-тесты** (unit) | `tests/host/` | Unity + fff | `just build::test-host` в devcontainer |
| **HIL target-тесты** (аппаратные) | `tests/target/` + `tools/hil/` | pyOCD + pyserial + pytest | `just host::hil-run` на хосте |
Подробнее — [tests/HostTestingGuide.md](tests/HostTestingGuide.md) и [tests/README.md](tests/README.md).
### Host-тесты
Компилируются и выполняются в devcontainer на хостовом компиляторе. Железо не нужно. BSP-модули тестируются через fff-фейки и stub-хедеры из `tests/host/mocks/`.
Гайд по добавлению нового теста — [tests/HOST_CREATE_TEST.md](tests/HOST_CREATE_TEST.md).
### HIL target-тесты
Каждый HIL-тест — это пара: **C-прошивка** (`tests/target/<n>/`) с текстовым CLI через UART и **pytest-тесты** (`tools/hil/test_<n>.py`). pyOCD загружает `.elf` в RAM через MCU-Link (CMSIS-DAP), pytest общается с прошивкой через MCU-Link VCOM.
```bash
pytest → uart_cmd("PING\r\n") → MCU-Link VCOM → RT1052 → "PONG\r\n" → pytest
```
Гайд по добавлению нового теста — [tests/HIL_CREATE_TEST.md](tests/HIL_CREATE_TEST.md).
---
## Управление зависимостями
| Зависимость | Подход | Причина |
| ------------------------------- | -------------------- | -------------------------------------------- |
| NXP MCUXpresso SDK | vendored | Стабильная версия, обновлений не планируется |
| FreeRTOS, FatFS, LittleFS и др. | vendored (через SDK) | Стабильные версии |
| Unity + fff | vendored | Маленькие, стабильные |
| SEGGER RTT | vendored | Стабильный |
| Зависимость | Подход | Причина |
|-------------|--------|---------|
| NXP MCUXpresso SDK | vendored | Стабильная версия, обновлений не планируется |
| FreeRTOS, FatFS, LittleFS и др. | vendored (через SDK) | Стабильные версии |
| Unity + fff | vendored | Маленькие, стабильные |
| SEGGER RTT | vendored | Стабильный |
| pyOCD, pyserial, pytest | `tools/hil/uv.lock` | Фиксированные версии |
| spsdk (nxpimage, blhost) | `tools/host/uv.lock` | Фиксированные версии |
**Принцип:** всё что не меняется — vendored (закоммичено в репозиторий). Это обеспечивает полностью автономную сборку после `git clone` без доступа к интернету.
**Принцип:** всё что не меняется — vendored. Полностью автономная сборка после `git clone` без доступа к интернету (кроме Python-зависимостей).
---
## Devcontainer — состав окружения
| Инструмент | Назначение |
| ---------------------- | -------------------------------------- |
| `arm-none-eabi-gcc` | Сборка firmware для таргета |
| `arm-none-eabi-gdb` | Отладка через GDB server (удалённая) |
| `gcc` (host) | Сборка и запуск host-тестов |
| `CMake + Ninja` | Система сборки |
| `CTest` | Запуск тестов (Unity + fff) |
| `clangd` | Language server для VSCode |
| `clang-format` | Форматирование кода |
| `clang-tidy` | Статический анализ |
| `Python 3 + nxp-spsdk` | HAB-образы (nxpimage) |
| `just` | Запуск рецептов через модули `build::` |
---
## Конфигурация платы (NXP Config Tools)
Файл `bsp/generated/TFT_Board.mex`**источник истины** для конфигурации пинов и тактирования. Открывается в NXP Config Tools (Pins Tool + Clocks Tool) для регенерации `pin_mux.c/h` и `clock_config.c/h`. Используется при старте проекта или при изменении аппаратной схемы. Коммитится вместе со сгенерированным кодом.
| Инструмент | Назначение |
|------------|------------|
| `arm-none-eabi-gcc` | Сборка firmware и HIL target-прошивок для ARM |
| `arm-none-eabi-gdb` | Отладка через GDB server |
| `gcc` / `clang` (host) | Сборка и запуск host-тестов |
| `CMake + Ninja` | Система сборки |
| `CTest` | Запуск host-тестов |
| `clangd` | Language server для VSCode |
| `clang-format` | Форматирование кода |
| `clang-tidy` | Статический анализ |
| `Python 3 + nxp-spsdk` | HAB-образы (nxpimage) |
| `just` | Запуск рецептов через модули `build::` |
---
@ -151,19 +177,16 @@ git clone <repo-url>
cd tft_manufacture_test
# Инициализация хоста (один раз)
# Устанавливает just и uv, затем настраивает окружение
./bootstrap.sh
# Открыть в VSCode → Reopen in Container
# Затем внутри devcontainer:
just build::test-host # сборка и запуск host-тестов
just build::build-firmware-test-debug # сборка firmware для таргета
just build::hab-firmware-test-debug # подготовка HAB-образа
just build::test-host # host unit-тесты
just build::build-firmware-test-debug # сборка firmware
just build::hab-firmware-test-debug # подготовка HAB-образа
just build::build-hil # сборка HIL target-прошивок
# На хосте (вне контейнера):
just flash ... # прошивка через USB ROM
just flash # прошить firmware_test debug во Flash
just host::hil-run # загрузить HIL ELF + запустить pytest
```
> Подробнее о прошивке — [HOW_TO_FLASH.md](docs/HOW_TO_FLASH.md)
> Подробнее об окружении разработки — [docs/DEV_ARCH.md](docs/DEV_ARCH.md)

View file

@ -18,6 +18,7 @@
/* BSP */
#include "bsp/tick.h"
#include "clock_config.h"
/* Utils */
#include "ring_buffer/ring_buffer.h"
@ -35,12 +36,9 @@
#define BSP_UART_HOST_RX_BUFFER_SIZE (256U)
#endif
/* Частота источника тактирования LPUART1.
* BOARD_BootClockRUN() -> 80000000UL настраивает OSC 24 MHz на LPUART.
* Скорректируй если у вас другой clock source. */
/* Частота источника тактирования LPUART1 (NXP Config Tools) */
#ifndef BSP_UART_HOST_SRC_CLOCK_HZ
//TODO: брать из generated/clock_config.h!
#define BSP_UART_HOST_SRC_CLOCK_HZ (80000000UL)
#define BSP_UART_HOST_SRC_CLOCK_HZ BOARD_BOOTCLOCKRUN_UART_CLK_ROOT
#endif
/* Приоритет прерывания LPUART1 (0 = наивысший на CM7). */

View file

@ -9,9 +9,6 @@ else()
set(TOOLCHAIN_EXT "")
endif()
# EXECUTABLE EXTENSION
set(CMAKE_EXECUTABLE_SUFFIX ".elf")
# -----------------------------------------------------------------------------
# Путь к тулчейну В devcontainer: /opt/arm-toolchain (symlink на конкретную
# версию) Переопределяется через переменную окружения ARMGCC_DIR если нужно

View file

@ -1,9 +1,8 @@
# Архитектура рабочего окружения разработчика
> Проект: TFT Firmware (MIMXRT1052CVJ5B)
> Документ описывает итоговый рабочий процесс разработчиков: от разворачивания
> окружения до сборки, тестирования, отладки и прошивки платы.
> Производственный сервер описан кратко — подробно в отдельном документе.
> Документ описывает рабочий процесс разработчиков: разворачивание окружения,
> сборка, тестирование (host + HIL), отладка и прошивка платы.
---
@ -12,15 +11,12 @@
Рабочее окружение разделено на два контекста с чёткой границей:
**Devcontainer** — всё что касается кода: сборка, статический анализ,
форматирование, host-тесты, подготовка HAB-образов. Разработчик проводит
здесь большую часть времени. Управляется через VSCode tasks и модуль `just build::`.
форматирование, host-тесты, сборка HIL target-прошивок, подготовка HAB-образов.
Управляется через VSCode tasks и модуль `just build::`.
**Хост** — всё что касается железа: прошивка платы через USB, отладка
через JLink/probe-rs. Управляется через модуль `just host::`.
Такое разделение решает несколько проблем: USB-устройства не требуют
проброса в контейнер; оба разработчика работают в идентичных условиях;
CI использует те же команды что и локальная разработка.
**Хост** — всё что касается железа: прошивка платы через USB, HIL-тесты
через pyOCD + pytest, отладка через GDB-сервер.
Управляется через модуль `just host::`.
---
@ -32,57 +28,56 @@ CI использует те же команды что и локальная р
├── Хост (Linux / macOS / Windows + Git Bash)
│ ├── just ← запуск задач хостового уровня (just host::*)
│ ├── docker ← управление devcontainer
│ ├── git ← работа с репозиторием
│ ├── uv + spsdk ← прошивка платы (flash_usb.py, sdphost, blhost)
│ │ venv: tools/host/.venv. (Linux/macOS)
│ │ venv: tools/host/.venv-host (Linux/macOS)
│ │ tools/host/.venv-host-win (Windows)
│ ├── uv + pyocd ← HIL-тесты (tools/hil/.venv)
│ │ + pyserial
│ │ + pytest
│ ├── JLinkGDBServer / probe-rs ← сервер отладки (USB → TCP :2331)
│ └── VSCode ← IDE (Dev Containers extension)
├── Devcontainer (Docker)
│ ├── ARM GCC 13.3 ← кросс-компилятор
│ ├── ARM GCC 13.3 ← кросс-компилятор (firmware + HIL target-прошивки)
│ ├── cmake + ninja ← система сборки
│ ├── clang-17 ← компилятор для host-тестов
│ ├── clangd-17 ← LSP (автодополнение, диагностика)
│ ├── clang-tidy-17 ← статический анализ
│ ├── clang-format-17 ← форматирование кода
│ ├── cmake-format ← форматирование CMakeLists
│ ├── just ← запуск задач внутри контейнера (just build::*)
│ ├── uv + spsdk ← сборка HAB-образов (только nxpimage)
│ │ venv: tools/host/.venv
│ └── Unity + fff ← фреймворки host-тестов
└── Плата TFT (IMXRT1052) — на столе у разработчика
├── USB ──────────────▶ хост (SDP-режим, прошивка)
├── SWD ──────────────▶ JLink/probe-rs на хосте (отладка)
└── CAN / UART / IO ──▶ локальный стенд (target-тесты)
└── Плата TFT (IMXRT1052)
├── USB ──────────────────▶ хост (SDP-режим, прошивка)
├── NXP MCU-Link (USB) ───▶ хост (CMSIS-DAP SWD + VCOM)
│ ├── SWD ← pyOCD загружает HIL ELF в RAM
│ └── VCOM ← pytest общается с прошивкой через UART
├── SWD ──────────────────▶ JLink/probe-rs на хосте (отладка)
└── CAN / UART / IO ──────▶ локальный стенд (будущие HIL-тесты)
```
---
## 3. Что устанавливается и где
| Инструмент | Хост | Devcontainer | Сервер |
| ------------------ | --------- | ------------ | --------- |
| `just` | ✅ | ✅ Dockerfile | ✅ |
| `docker` | ✅ | — | — |
| `git` | ✅ | ✅ | ✅ |
| `uv` | ✅ | ✅ Dockerfile | ✅ |
| `spsdk` | ✅ uv sync | ✅ uv sync | ✅ uv sync |
| ARM GCC toolchain | — | ✅ | — |
| `cmake` / `ninja` | — | ✅ | — |
| `clang` / `clangd` | — | ✅ | — |
| Unity / fff | — | ✅ | — |
| JLink / probe-rs | ✅ | — | — |
| Инструмент | Хост | Devcontainer | Сервер |
|------------|------|--------------|--------|
| `just` | ✅ | ✅ Dockerfile | ✅ |
| `docker` | ✅ | — | — |
| `uv` | ✅ | ✅ Dockerfile | ✅ |
| `spsdk` | ✅ uv sync | ✅ uv sync | ✅ uv sync |
| `pyocd` + `pyserial` + `pytest` | ✅ uv sync | — | ✅ uv sync |
| ARM GCC toolchain | — | ✅ | — |
| `cmake` / `ninja` | — | ✅ | — |
| `clang` / `clangd` | — | ✅ | — |
| Unity / fff | — | ✅ | — |
| JLink / probe-rs | ✅ | — | — |
`spsdk` присутствует везде, но с разными ролями:
`spsdk` и `pyocd` — разные venv с разными ролями:
- **devcontainer** — только `nxpimage` для сборки HAB-образов
- **хост** — полный стек: `nxpimage` + `sdphost` + `blhost` для прошивки
- **сервер** — то же что на хосте, но для производственного сценария
Версия `spsdk` зафиксирована в `tools/host/uv.lock` — все три места
используют одну и ту же версию.
- `tools/host/``spsdk`: `nxpimage` + `sdphost` + `blhost` — прошивка через USB ROM
- `tools/hil/``pyocd` + `pyserial` + `pytest` — HIL-тесты через SWD + VCOM
---
@ -92,7 +87,7 @@ CI использует те же команды что и локальная р
```ini
# USB VID:PID (NXP)
BOOTROM_VID=1fc9 # используется: host.just (udev-правила) + flash_usb.py
BOOTROM_VID=1fc9
BOOTROM_PID=0130
FLASHLOADER_VID=15a2
FLASHLOADER_PID=0073
@ -102,6 +97,12 @@ GDB_PORT=3333
GDB_EXECUTABLE=gdb-multiarch
OPENOCD_INTERFACE=cmsis-dap.cfg
TARGET_CFG=target/imxrt.cfg
# HIL — аппаратный стенд
HIL_VCOM_PORT=/dev/tty.usbmodemXXXX # VCOM-порт MCU-Link (macOS/Linux)
# HIL_VCOM_BAUD=115200 # default: 115200
# HIL_READY_TIMEOUT=5.0 # default: 5.0 сек
# HIL_PYOCD_FREQUENCY=1000000 # default: 1 МГц
```
**Как значения попадают в инструменты:**
@ -110,64 +111,54 @@ TARGET_CFG=target/imxrt.cfg
.env
├─▶ just (set dotenv-load + set export)
│ └─▶ just-рецепты видят переменные напрямую: {{BOOTROM_VID}}
│ └─▶ uv run flash_usb.py ← наследует окружение автоматически
│ └─▶ os.environ.get("BOOTROM_VID") ← без python-dotenv
│ ├─▶ just-рецепты: {{BOOTROM_VID}}, {{HIL_VCOM_PORT}}
│ └─▶ uv run python ← наследует os.environ автоматически
│ ├─▶ flash_usb.py: os.environ.get("BOOTROM_VID")
│ └─▶ env_config.py: os.environ.get("HIL_VCOM_PORT")
└─▶ .vscode/launch.json ← через ${env:GDB_PORT} если нужно
└─▶ .vscode/launch.json ← через ${env:GDB_PORT}
```
Аппаратные константы (`FLASH_BASE`, `HAB_OFFSET`, `FLEXSPI_OPTION_VALUE` и т.д.)
намеренно оставлены в коде `flash_usb.py` — это не конфигурация, а часть
протокола прошивки IMXRT1052, менять их незачем.
`.env.example` — шаблон без значений, коммитится в репозиторий.
`.env` — реальные значения, в `.gitignore` (если содержит секреты), иначе тоже коммитится.
---
## 4. Структура репозитория (automation-часть)
## 4. Структура automation-части репозитория
```bash
/
├── justfile ← корневой оркестратор; модули: build, host, ci
├── bootstrap.sh ← уровень 0: устанавливает just+uv → just host::bootstrap
├── .env ← конфигурация проекта (VID:PID, пути, GDB)
├── .env.example ← шаблон для новых разработчиков
├── pyocd.yaml ← конфигурация pyOCD (target: cortex_m, RAM-режим)
├── .env / .env.example
├── just/
│ ├── build.just ← devcontainer: сборка, тесты, HAB
│ ├── host.just ← хост: прошивка, bootstrap, HIL
│ └── ci.just ← CI/CD пайплайны
│ ├── build.just ← devcontainer: сборка firmware, host-тесты,
│ │ HAB-образы, HIL target-прошивки
│ ├── host.just ← хост: прошивка, bootstrap, HIL-тесты
│ └── ci.just
├── .devcontainer/
│ ├── Dockerfile
│ └── devcontainer.json
├── .vscode/
│ └── tasks.json ← UI для just build::* (внутри devcontainer)
├── tools/
│ └── host/
│ ├── hab/
│ │ ├── 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
│ ├── dcd/
│ │ ├── dcd.bin
│ │ └── ivt_flashloader.bin
│ ├── flash_usb.py ← читает конфиг из env (VID:PID, BUILD_DIR)
│ ├── pyproject.toml
│ ├── host/ ← spsdk-окружение (прошивка через USB ROM)
│ │ ├── flash_usb.py
│ │ ├── hab/ ← HAB yaml-конфиги
│ │ ├── dcd/ ← ivt_flashloader.bin, dcd.bin
│ │ └── uv.lock
│ └── hil/ ← HIL pytest-окружение
│ ├── conftest.py ← фикстуры: loaded_<n>, uart
│ ├── pyocd_utils.py ← FLEXRAM, ELF loader, run_from_vectors
│ ├── env_config.py ← конфигурация из os.environ
│ ├── load_and_run.py ← CLI-утилита загрузки ELF
│ ├── test_uart.py ← HIL тесты bsp_uart_host
│ └── uv.lock
├── CMakePresets.json
├── cmake/
├── sdk/
├── bsp/
├── lib/
├── firmware/
│ ├── test/
│ ├── bootloader/
│ └── tft_app/
└── tests/ ← host-тесты (Unity + fff)
├── CMakePresets.json ← Debug · Release · host-debug · target-debug
└── tests/
├── host/ ← host unit-тесты (Unity + fff)
└── target/ ← HIL target-прошивки (RAM, pyOCD)
└── host_uart/ ← CLI для тестирования UART
```
---
@ -176,13 +167,13 @@ TARGET_CFG=target/imxrt.cfg
### 5.1 Предварительные требования
| Платформа | Что нужно до bootstrap |
| --------- | ----------------------------------------------------------- |
| Linux | `docker`, `git`, `curl` |
| macOS | Docker Desktop, `git` (Xcode CLT) |
| Windows | Docker Desktop, Git for Windows → **использовать Git Bash** |
| Платформа | Что нужно до bootstrap |
|-----------|------------------------|
| Linux | `docker`, `git`, `curl` |
| macOS | Docker Desktop, `git` (Xcode CLT) |
| Windows | Docker Desktop, Git for Windows → **Git Bash** |
### 5.2 Единственная команда для нового разработчика
### 5.2 Единственная команда
```bash
git clone <repo-url> && cd <repo>
@ -195,107 +186,81 @@ git clone <repo-url> && cd <repo>
bootstrap.sh (уровень 0)
├── определить платформу (Linux / macOS / Windows Git Bash)
│ uname: MINGW64_NT-... → windows, Linux → linux, Darwin → macos
├── проверить/установить just (semver без sort -V — работает в Git Bash)
< 1.36.0 или отсутствует:
│ Linux/macOS → curl | bash → ~/.local/bin/just
│ macOS → brew install just (если есть Homebrew)
│ Windows → winget install --id Casey.Just
│ (перезапустить Git Bash после установки)
├── проверить/установить uv
< 0.4.0 или отсутствует:
│ Linux/macOS → curl -LsSf https://astral.sh/uv/install.sh | sh → ~/.local/bin/uv
│ macOS → brew install uv (если есть Homebrew)
│ Windows → powershell.exe irm https://astral.sh/uv/install.ps1 | iex
├── проверить/установить just >= 1.36.0
├── проверить/установить uv >= 0.4.0
└── exec just host::bootstrap
├── [1/3] check-deps
│ just >= 1.36.0 · uv >= 0.4.0 · docker >= 24.0.0
│ платформо-зависимые подсказки при ошибках
├── [2/3] setup-udev (только Linux)
│ /etc/udev/rules.d/99-nxp-mimxrt.rules:
│ 1FC9:0130 ← NXP BootROM (SDP-режим)
│ 15A2:0073 ← NXP Flashloader
│ VID:PID берутся из .env (BOOTROM_VID/PID, FLASHLOADER_VID/PID)
│ usermod -a -G plugdev $USER
│ требует re-login · на macOS/Windows пропускается
└── [3/3] setup-tools
uv sync в tools/host/
venv: .venv-host (Linux/macOS) · .venv-host-win (Windows)
разделение нужно: devcontainer создаёт .venv с Linux-симлинками,
Windows не может их удалить без прав администратора
SHA-256 uv.lock кэшируется в .cache/
повторный вызов мгновенный если lockfile не изменился
├── [1/3] check-deps — just · uv · docker
├── [2/3] setup-udev — udev-правила NXP USB (только Linux)
│ 1FC9:0130 ← BootROM SDP
│ 15A2:0073 ← Flashloader
└── [3/3] setup-tools — uv sync в tools/host/
SHA-256 uv.lock кешируется → повторный вызов мгновенный
```
### 5.4 После bootstrap
### 5.4 Настройка HIL-окружения (один раз, на хосте)
```bash
Открыть VSCode → "Reopen in Container"
cd tools/hil
uv sync # установить pyocd, pyserial, pytest
# Прописать VCOM-порт в .env
# macOS: ls /dev/tty.usbmodem*
# Linux: ls /dev/ttyACM*
# Добавить в .env: HIL_VCOM_PORT=/dev/tty.usbmodemXXXX
```
`postCreateCommand` выполняется автоматически при поднятии контейнера:
### 5.5 После bootstrap
```bash
cd tools/host && uv sync && # venv: tools/host/.venv (Linux, внутри контейнера)
cd ../.. &&
cmake --preset host-debug &&
cmake --preset Debug
# Открыть VSCode → "Reopen in Container"
# postCreateCommand выполняется автоматически:
# uv sync (tools/host) + cmake --preset host-debug + cmake --preset Debug
```
Прогрев CMake-кэша нужен чтобы clangd и IntelliSense заработали сразу,
без первой ручной сборки.
---
## 6. Прошивки, boot-стратегии и матрица сборки
### 6.1 Три подпроекта
### 6.1 Четыре типа сборки
| Прошивка | Boot-стратегия | DCD | Назначение |
| --------------- | -------------------- | ---- | ---------------------------------------- |
| `firmware_test` | XIP из Flash | ✅ | Входной контроль, тестирование периферии |
| `bootloader` | Копирование в ITCM | ❌ | Загрузчик, не использует SDRAM |
| `tft_app` | XIP + буферы в SDRAM | ✅ | Основное приложение (FreeRTOS, LCDIF) |
| Пресет | Toolchain | Назначение | Линкер-скрипт |
|--------|-----------|------------|---------------|
| `Debug` / `Release` | ARM GCC | firmware_test, bootloader, tft_app | `flexspi_nor.ld` |
| `host-debug` / `host-release` | clang (host) | Unity + fff тесты | — |
| `target-debug` | ARM GCC | HIL target-прошивки | `ram.ld` |
### 6.2 Матрица сборки
Каждый проект собирается в двух режимах:
| | Debug | Release |
| --------------- | ------------------- | ------------------ |
| `firmware_test` | разработка, отладка | HAB для сервера |
| `bootloader` | отладка загрузчика | финальная прошивка |
| `tft_app` | отладка приложения | финальная прошивка |
### 6.3 CMake пресеты
### 6.2 CMake пресеты
```bash
configurePresets: Debug · Release · host-debug · host-release
configurePresets:
Debug · Release ← firmware (XIP из Flash)
host-debug · host-release ← host unit-тесты
target-debug ← HIL target-прошивки (RAM)
buildPresets (ARM):
all-debug / all-release ← все проекты (для CI)
buildPresets (ARM firmware):
firmware-test-debug / release
bootloader-debug / release
app-debug / release
all-debug / all-release
buildPresets (host):
buildPresets (host-тесты):
host-debug-build / host-release-build
buildPresets (HIL):
target-debug-build ← test_host_uart (и будущие HIL-прошивки)
```
### 6.4 Карта Flash (W25Q128, 16 MB)
### 6.3 Boot-стратегии
```bash
0x60000000 FCB — Flash Config Block 512 байт (пишет Flashloader)
0x60001000 IVT + BDT ← начало HAB-образа
0x60001040 DCD — инициализация SDRAM ~1088 байт (firmware_test, tft_app)
0x60003000 Код прошивки (.text, .data…)
```
| Прошивка | Стратегия | DCD | Инструмент загрузки |
|----------|-----------|-----|---------------------|
| `firmware_test` | XIP из Flash | ✅ | SPSDK → Flash |
| `bootloader` | Копирование в ITCM | ❌ | SPSDK → Flash |
| `tft_app` | XIP + буферы в SDRAM | ✅ | SPSDK → Flash |
| HIL target (`tests/target/`) | Исполнение из ITCM/DTCM | ❌ | pyOCD → RAM |
**HIL boot-стратегия (`bsp_boot_ram`):** pyOCD настраивает FLEXRAM (128KB ITCM + 128KB DTCM), записывает сегменты ELF по физическим адресам, устанавливает SP/PC из таблицы векторов и запускает выполнение. Flash не используется.
---
@ -303,141 +268,171 @@ buildPresets (host):
### 7.1 Карта задач по контекстам
| Задача | Где |
| -------------------------------------- | -------------------------- |
| Написание кода, clangd, форматирование | devcontainer |
| Статический анализ (clang-tidy) | devcontainer |
| Host-тесты (Unity + fff) | devcontainer |
| Сборка ARM firmware (ELF) | devcontainer |
| Подготовка HAB-образов (nxpimage) | devcontainer |
| Прошивка платы через USB | **хост** |
| Отладка — GDB-сервер (JLink/probe-rs) | **хост** |
| Отладка — GDB-клиент | devcontainer → хост по TCP |
| Target-тесты (управление стендом) | **хост** |
| Задача | Где |
|--------|-----|
| Написание кода, clangd, форматирование | devcontainer |
| Статический анализ (clang-tidy) | devcontainer |
| Host unit-тесты (Unity + fff) | devcontainer |
| Сборка ARM firmware (ELF) | devcontainer |
| Сборка HIL target-прошивок | devcontainer |
| Подготовка HAB-образов (nxpimage) | devcontainer |
| Прошивка платы через USB ROM | **хост** |
| HIL-тесты (pyOCD + pytest) | **хост** |
| Отладка — GDB-сервер (JLink) | **хост** |
| Отладка — GDB-клиент | devcontainer → хост по TCP |
### 7.2 Типичная сессия разработки
```bash
Открыть VSCode → работать в devcontainer весь день
├── писать код
├── Ctrl+Shift+P → "Run Task" → 🧪 Host Tests (Debug)
│ или в терминале: just build::test-host
├── Ctrl+Shift+P → "Run Task" → 🔨 Build → firmware-test · debug
│ → build/Debug/firmware/test/firmware_test.elf
├── Ctrl+Shift+P → "Run Task" → 📦 HAB Image → firmware-test · debug
│ → build/Debug/firmware_test_hab.bin
│ Переключиться в хостовый терминал
├── just flash firmware_test debug ← прошить отладочный образ
│ или
├── just host::flash-ram firmware_test debug ← загрузить в RAM (быстро, без износа Flash)
└── F5 в VSCode → отладка через JLink
# Devcontainer (терминал VSCode)
just build::test-host # host unit-тесты — зелёные?
just build::build-firmware-test-debug # ELF собирается?
just build::hab-firmware-test-debug # HAB-образ готов
# Хостовый терминал — прошивка
just flash # прошить firmware_test debug во Flash
# Хостовый терминал — HIL
just build::build-hil # собрать HIL target-прошивку
# (можно делать в devcontainer)
just host::hil-run # загрузить ELF → запустить pytest
# Отладка
# F5 в VSCode → запустить JLinkGDBServer на хосте → attach через cortex-debug
```
### 7.3 VSCode Tasks (внутри devcontainer)
| Таск | Input 1 | Input 2 | Команда |
| ---------------------- | ------- | ------------- | ------------------------------------ |
| 🔨 Build | project | debug/release | `just build::build-<project>-<type>` |
| 🧪 Host Tests (Debug) | — | — | `just build::test-host` |
| 🧪 Host Tests (Release) | — | — | `just build::test-host-release` |
| 📦 HAB Image | project | debug/release | `just build::hab-<project>-<type>` |
| 📦 HAB All (Debug) | — | — | `just build::hab-all-debug` |
| 📦 HAB All (Release) | — | — | `just build::hab-all-release` |
| 🗑️ Clean | — | — | `just build::clean` |
Таски «Build» и «HAB Image» запрашивают два input последовательно:
сначала проект (`firmware-test / bootloader / app / all`),
затем тип (`debug / release`).
| Таск | Команда |
|------|---------|
| 🔨 Build | `just build::build-<project>-<type>` |
| 🧪 Host Tests (Debug) | `just build::test-host` |
| 🧪 Host Tests (Release) | `just build::test-host-release` |
| 🎯 Build HIL Target Tests | `just build::build-hil` |
| 📦 HAB Image | `just build::hab-<project>-<type>` |
| 📦 HAB All (Debug/Release) | `just build::hab-all-debug/release` |
| 🗑️ Clean | `just build::clean` |
---
## 8. Прошивка платы (хост)
### 8.1 Перевод платы в SDP-режим
### Перевод в SDP-режим
```bash
1. BOOT_MOD_1 → 3V3
2. Reset
3. Подключить USB к хосту
→ плата определяется как VID:PID = 1FC9:0130
4. Выполнить нужный just host::flash-* рецепт
5. После прошивки: BOOT_MOD_1 → GND, Reset
→ плата стартует из Flash
3. Подключить USB → плата как VID:PID 1FC9:0130
4. just host::flash <project> <type>
5. После прошивки: BOOT_MOD_1 → GND → Reset
```
### 8.2 Команды прошивки
### Команды прошивки
```bash
# Основной рецепт (project × type)
just host::flash firmware_test debug # разработка — итерации с отладчиком
just host::flash firmware_test release # проверить как будет на сервере
just host::flash bootloader debug
# project × type
just host::flash firmware_test debug
just host::flash firmware_test release
just host::flash bootloader release
just host::flash tft_app debug
just host::flash tft_app release
# Загрузка в RAM — без записи во Flash, мгновенный старт
# Удобно для частых итераций: не изнашивает Flash
just host::flash-ram firmware_test # default: debug
just host::flash-ram firmware_test debug
just host::flash-ram tft_app release
# В RAM — быстро, без износа Flash
just host::flash-ram firmware_test
# Быстрые псевдонимы (из корневого Justfile)
just flash # = just host::flash-test-debug
just host::flash-test-debug
just host::flash-test-release
just host::flash-production # bootloader release + tft_app release
```
### 8.3 Что происходит внутри flash_usb.py
`flash_usb.py` читает конфигурацию из окружения (just экспортирует `.env`):
- `BOOTROM_VID/PID`, `FLASHLOADER_VID/PID` → USB-адреса устройств
- `BUILD_DIR` → абсолютный путь к артефактам сборки
```bash
Плата в SDP-режиме (1FC9:0130)
├── sdphost: загрузить ivt_flashloader.bin в RAM (0x20001C00)
└── sdphost: jump-address 0x20001C00
│ ожидание до 10с — Flashloader поднимается как 15A2:0073
Flashloader (15A2:0073)
├── fill-memory + configure-memory (0xC0000007) — инициализация FlexSPI
├── flash-erase-region 0x60000000
├── fill-memory + configure-memory (0xF000000F) — запись FCB
├── write-memory 0x60001000 ← HAB-образ
└── reset
# Псевдонимы
just flash # = firmware_test debug → Flash
just host::flash-production # bootloader + tft_app release
```
---
## 9. Отладка
## 9. HIL-тесты (хост)
JLink/probe-rs работает на хосте напрямую через USB/SWD.
GDB-клиент в devcontainer подключается к серверу по TCP.
HIL-тесты проверяют периферию на реальном железе. MCU-Link обеспечивает два канала по одному USB: SWD (прошивка через pyOCD) и VCOM (UART CLI).
```bash
pytest → uart_cmd("PING\r\n")
↓ pyserial / VCOM
MCU-Link
↓ LPUART1
RT1052 (HIL прошивка)
→ "PONG\r\n"
```
### Команды
```bash
# Сборка HIL target-прошивок (devcontainer)
just build::build-hil
# Запуск тестов (хост)
just host::hil-run # загрузить ELF + все тесты
just host::hil-smoke # только smoke-тесты (-m smoke)
just host::hil-run-fast # тесты без перезагрузки ELF (--no-load)
just host::hil-load # только загрузить ELF (без pytest)
```
### Архитектура HIL-теста
Каждый HIL-тест — пара: C-прошивка в `tests/target/<n>/` и pytest-файл в `tools/hil/test_<n>.py`. Прошивка реализует текстовый CLI через `bsp_uart_host`. pytest управляет через `uart_cmd()`.
Протокол готовности: прошивка шлёт `READY\r\n` в цикле пока хост не откроет порт — исключает race condition между загрузкой ELF и открытием COM-порта.
Гайд по добавлению нового HIL-теста — [tests/HIL_CREATE_TEST.md](../tests/HIL_CREATE_TEST.md).
---
## 10. Тестирование
### 10.1 Host unit-тесты
```bash
Фреймворк: Unity + fff
Пресеты: host-debug / host-release
Компилятор: clang-17 (не ARM GCC)
Запуск: just build::test-host
```
BSP-модули тестируются через fff-фейки и stub-хедеры в `tests/host/mocks/`. `BUILD_TESTS_HOST=ON` отключает ARM-специфику.
Гайд — [tests/HOST_CREATE_TEST.md](../tests/HOST_CREATE_TEST.md).
### 10.2 HIL target-тесты
```bash
Инструменты: pyOCD (SWD) + pyserial (UART) + pytest
Пресет: target-debug → ram.ld → ITCM/DTCM
Запуск: just host::hil-run
```
Текущие тесты: `test_uart.py` — PING/ECHO/BUF_SIZE через `bsp_uart_host`.
### 10.3 Будущие HIL-тесты (с M5StampPLC)
Для тестов с внешними интерфейсами (CAN, GPIO, RS485) понадобится промежуточное звено:
```bash
pytest → M5StampPLC (Arduino CLI) → CAN/GPIO → RT1052
```
M5StampPLC реализует тот же текстовый CLI — pytest работает одинаково с обоими каналами.
---
## 11. Отладка
```bash
Хост
├── JLinkGDBServer -device MIMXRT1052 -if SWD -port 2331
│ USB/SWD → плата
└── host.docker.internal:2331 ← доступен из devcontainer
└── host.docker.internal:2331 ← доступен из devcontainer
Devcontainer
└── arm-none-eabi-gdb / probe-rs
└── arm-none-eabi-gdb
target remote host.docker.internal:2331
```
`.vscode/launch.json`:
`.vscode/launch.json` (cortex-debug, тип `external`):
```json
{
@ -448,78 +443,29 @@ Devcontainer
}
```
`host.docker.internal` — стандартный DNS-алиас Docker для хоста.
Работает на macOS, Windows и Linux (Docker Desktop).
> USB-passthrough программатора в Docker на macOS невозможен, на других
> платформах нестабилен. TCP-мост — единственное надёжное решение для
> всех платформ.
---
## 10. Тесты
### 10.1 Host-тесты
Выполняются в devcontainer на хостовом компиляторе (x86/arm64). Железо не нужно.
```bash
Фреймворк: Unity + fff
Пресеты: host-debug / host-release
Компилятор: системный clang-17 (не ARM GCC)
Запуск: just build::test-host
Результат: JUnit XML → VSCode CTest Lab + GitLab CI
```
`BUILD_TESTS_HOST=ON` отключает ARM-специфику (BSP, SDK) — компилируются
только тестируемые модули и fff-заглушки.
### 10.2 Target-тесты
Выполняются на физической плате. Прошивается `firmware_test`,
стенд подаёт сигналы и проверяет ответы.
```bash
Стенд (M5StampPLC или аналог) ←→ Плата TFT
Тестируемые подсистемы:
SDRAM 32 MB — чтение/запись паттернов
QSPI Flash — erase / write / verify
CAN — loopback + внешний фрейм от стенда
UART TTL — echo-тест
UART +24V изол. — echo-тест
Гальв. входы +24V — состояния при подаче напряжения от стенда
RTC BM8563 — установка / чтение времени
SD-карта (SDIO) — монтирование, R/W файл
MQS (аудио) — воспроизведение тестового сигнала
IR-приёмник — приём тестового кода от стенда
```
---
## 11. Жизненный цикл изменений
## 12. Жизненный цикл изменений
```bash
feature-ветка
├── код в devcontainer
│ just build::test-host ← зелёные?
│ just build::build-firmware-test-debug ← компилируется?
├── devcontainer
│ just build::test-host ← зелёные host-тесты?
│ just build::build-firmware-test-debug
│ just build::build-hil ← HIL-прошивки собираются?
├── проверка на железе
│ just build::hab-firmware-test-debug
│ just flash ← прошить (алиас flash-test-debug)
│ target-тесты со стендом ← периферия работает?
├── хост
│ just flash ← прошить, проверить на железе
│ just host::hil-run ← HIL зелёные?
├── подготовка к MR
│ just build::hab-all-release ← финальные образы
│ just host::flash firmware_test release ← убедиться что release работает
│ just build::hab-all-release
│ just host::flash firmware_test release
└── Merge Request → GitLab
[CI pipeline — отдельная тема]
host-тесты, сборка, публикация артефактов
└── Merge Request → GitLab CI
host-тесты · сборка · HIL · публикация артефактов
Производственный сервер
just host::incoming → firmware_test release → HIL
just host::production → bootloader + tft_app release
@ -527,105 +473,72 @@ feature-ветка
---
## 12. Обновление зависимостей
## 13. Производственный сервер
### spsdk
Сервер работает только с готовыми проверенными артефактами. Никакой сборки.
```bash
just host::upgrade-tools 3.8.0
git add tools/host/uv.lock tools/host/pyproject.toml
git commit -m "chore: upgrade spsdk to 3.8.0"
```
Сценарий входного контроля:
just host::incoming
└── flash firmware_test release → HIL-тесты
После этого у всех разработчиков и в контейнере обновится автоматически
при следующем `just host::setup-tools` / `uv sync`.
Финальная прошивка:
just host::production
├── flash bootloader release
└── flash tft_app release
### just в Dockerfile
```dockerfile
ARG JUST_VERSION=1.36.0 # .devcontainer/Dockerfile — единственное место
```
### ARM toolchain
```dockerfile
ARG TOOLCHAIN_VERSION=14.2.rel1 # .devcontainer/Dockerfile
```
Пересборка инвалидирует только Stage 1 (toolchain). Stage 2
(clang, cmake и др.) берётся из кэша — пересборка быстрая.
### После git pull если изменился uv.lock
```bash
just host::setup-tools # автоматически обнаружит изменение и выполнит uv sync
```
---
## 13. Производственный сервер (к сведению)
Сервер работает **только с готовыми проверенными артефактами**.
Никакой сборки, никакого компилятора.
```bash
Источник: GitLab Releases или FTP
— только теггированные релизы, прошедшие CI
Сценарий входного контроля новой платы:
1. just host::incoming
└── flash firmware_test release → HIL-тесты периферии
2. Тесты пройдены:
just host::production
├── flash bootloader release
└── flash tft_app release
Установлено: just · uv + spsdk · git
Установлено: just · uv + spsdk · uv + pyocd/pytest · git
НЕ установлено: docker · cmake · компилятор · ARM toolchain
```
Детальный `server.just` и пайплайн сервера — отдельная задача.
---
## Приложение А: минимальные версии
| Инструмент | Версия | Причина |
| ------------ | --------- | ------------------------------------------------------------ |
| `just` | 1.36.0 | поддержка `mod` с кастомным путём (`mod host 'just/host.just'`) |
| `uv` | 0.4.0 | стабильный lockfile формат |
| `docker` | 24.0.0 | Compose v2, `--build-arg` |
| `spsdk` | 3.7.x | совместимость с HAB yaml-форматом |
| ARM GCC | 13.3.rel1 | C11, LTO, текущий SDK |
| clang/clangd | 17 | поддержка `If:` в `.clangd` |
| Инструмент | Версия | Причина |
|------------|--------|---------|
| `just` | 1.36.0 | поддержка `mod` с кастомным путём |
| `uv` | 0.4.0 | стабильный lockfile формат |
| `docker` | 24.0.0 | Compose v2 |
| `spsdk` | 3.7.x | совместимость с HAB yaml-форматом |
| `pyocd` | 0.36+ | cortex_m target, write_core_register API |
| ARM GCC | 13.3.rel1 | C11, текущий SDK |
| clang/clangd | 17 | поддержка `If:` в `.clangd` |
## Приложение Б: быстрая шпаргалка
---
## Приложение Б: шпаргалка
```bash
# ── Первый запуск ──────────────────────────────────────────────
./bootstrap.sh # инициализация хоста
# ── Первый запуск ───────────────────────────────────────────
./bootstrap.sh
cd tools/hil && uv sync
# VSCode → Reopen in Container
# ── Внутри devcontainer (терминал VSCode) ──────────────────────
just build::test-host
just build::build-firmware-test-debug
just build::build-tft-app-release
just build::hab-firmware-test-debug
just build::hab-all-release
just build::clean
# ── devcontainer ────────────────────────────────────────────
just build::test-host # host unit-тесты
just build::build-firmware-test-debug # сборка firmware
just build::build-hil # сборка HIL target-прошивок
just build::hab-firmware-test-debug # HAB-образ Debug
just build::hab-all-release # все HAB Release
just build::clean # очистить build/
# ── Хостовый терминал — прошивка ───────────────────────────────
just flash # алиас: firmware_test debug
just host::flash firmware_test debug # во Flash
# ── хост — прошивка ─────────────────────────────────────────
just flash # firmware_test debug → Flash
just host::flash firmware_test release
just host::flash bootloader release
just host::flash tft_app release
just host::flash-ram firmware_test # в RAM (быстро, без износа Flash)
just host::flash-production # bootloader + tft_app release
just host::flash-production # bootloader + tft_app release
just host::flash-ram firmware_test # в RAM (без износа Flash)
# ── Хостовый терминал — обслуживание ───────────────────────────
just host::scan # найти NXP USB-устройства
just host::sdp-status # проверить BootROM
just host::setup-tools # обновить spsdk после git pull
just host::check-deps # проверить версии инструментов
# ── хост — HIL-тесты ────────────────────────────────────────
just host::hil-run # загрузить ELF + все тесты
just host::hil-smoke # только smoke
just host::hil-run-fast # без перезагрузки ELF
just host::hil-load # только загрузить ELF
# ── хост — обслуживание ─────────────────────────────────────
just host::check-deps # проверить версии
just host::setup-tools # uv sync после git pull
just host::scan # найти NXP USB-устройства
just host::upgrade-tools 3.8.0 # обновить spsdk
```

View file

@ -1,15 +1,12 @@
# firmware/test/CMakeLists.txt Тестовая прошивка — входной контроль платы на
# производстве
add_executable(firmware_test)
set(BSP_GENERATED ${CMAKE_SOURCE_DIR}/bsp/generated)
target_sources(
firmware_test
PRIVATE main.c ${PROJECT_SOURCE_DIR}/bsp/generated/syscalls.c
${BSP_GENERATED}/clock_config.c
${BSP_GENERATED}/startup/startup_MIMXRT1052.S)
set(TARGET_NAME firmware_test)
target_compile_definitions(firmware_test
add_executable(${TARGET_NAME} main.c ${BSP_GENERATED}/clock_config.c
${BSP_STARTUP_FILE} ${BSP_SYSCALLS_FILE})
target_compile_definitions(${TARGET_NAME}
PRIVATE BSP_UART_HOST_RX_BUFFER_SIZE=512)
#
# -----------------------------------------------------------------------------
@ -17,8 +14,8 @@ target_compile_definitions(firmware_test
# даёт: sdk_device, sdk_clock, sdk_common, CPU_MIMXRT1052CVJ5B, XIP_* дефайны
# -----------------------------------------------------------------------------
target_link_libraries(
firmware_test
PRIVATE bsp_board bsp_led bsp_tick bsp_uart_host bsp_boot_ram
${TARGET_NAME}
PRIVATE bsp_board bsp_led bsp_tick bsp_uart_host bsp_boot_xip
lib_external # SEGGER RTT если включён через SEGGER_RTT_ENABLED
)
@ -27,14 +24,16 @@ target_link_libraries(
# -----------------------------------------------------------------------------
# --gc-sections — удалять неиспользуемые секции (работает с
# -ffunction/data-sections) --print-memory-usage — выводить таблицу
# использования Flash/RAM после линковки -Map — генерировать map-файл для
# анализа размещения символов -T — линкерный скрипт с описанием карты памяти
# IMXRT1052
# использования Flash/RAM после линковки -Map — генерировать
# map-файл для анализа размещения символов -T — линкерный
# скрипт с описанием карты памяти IMXRT1052
target_link_options(
firmware_test PRIVATE -Wl,--gc-sections -Wl,--print-memory-usage
${TARGET_NAME} PRIVATE -Wl,--gc-sections -Wl,--print-memory-usage
-Wl,-Map=${CMAKE_BINARY_DIR}/firmware_test.map
-T${PROJECT_SOURCE_DIR}/cmake/linker/MIMXRT1052xxxxx_ram.ld)
-T${PROJECT_SOURCE_DIR}/cmake/linker/MIMXRT1052xxxxx_flexspi_nor.ld)
set_target_properties(${TARGET_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY
${CMAKE_BINARY_DIR})
# -----------------------------------------------------------------------------
# Post-build: генерация .bin для прошивки через blhost
# -----------------------------------------------------------------------------
@ -43,7 +42,5 @@ add_custom_command(
POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -O binary $<TARGET_FILE:firmware_test>
${CMAKE_BINARY_DIR}/firmware_test.bin
COMMAND ${CMAKE_OBJCOPY} -O ihex $<TARGET_FILE:firmware_test>
${CMAKE_BINARY_DIR}/firmware_test.hex
COMMAND ${CMAKE_SIZE} $<TARGET_FILE:firmware_test>
COMMENT "Generating firmware_test.bin")

View file

@ -4,8 +4,8 @@
#
# Рабочая директория — корень репозитория (не scripts/).
# =============================================================================
# === Импорт общих переменных ===
TOOLS_DIR := env('TOOLS_DIR', justfile_directory() / 'tools/host')
BUILD_DIR := env('BUILD_DIR', justfile_directory() / 'build')
@ -16,14 +16,8 @@ _default:
# =============================================================================
# ГРУППА: build
#
# Матрица сборки:
# firmware-test × debug / release
# bootloader × debug / release
# app × debug / release
# all × debug / release (все проекты сразу, для CI)
# =============================================================================
# Приватные рецепты для configure (не показываются в --list)
[private]
_configure-debug:
cmake --preset Debug
@ -32,49 +26,68 @@ _configure-debug:
_configure-release:
cmake --preset Release
[private]
_configure-target-debug:
cmake --preset target-debug
# ── firmware_test ─────────────────────────────────────────────────────────────
[doc('Собрать firmware_test (Debug)')]
[group('build')]
build-firmware-test-debug: _configure-debug
cmake --build --preset firmware-test-debug
[doc('Собрать firmware_test (Release)')]
[group('build')]
build-firmware-test-release: _configure-release
cmake --build --preset firmware-test-release
# ── bootloader ───────────────────────────────────────────────────────────────
[doc('Собрать bootloader (Debug)')]
[group('build')]
build-bootloader-debug: _configure-debug
cmake --build --preset bootloader-debug
[doc('Собрать bootloader (Release)')]
[group('build')]
build-bootloader-release: _configure-release
cmake --build --preset bootloader-release
# ── app ───────────────────────────────────────────────────────────────────────
[doc('Собрать tft_app (Debug)')]
[group('build')]
build-app-debug: _configure-debug
cmake --build --preset app-debug
[doc('Собрать tft_app (Release)')]
[group('build')]
build-app-release: _configure-release
cmake --build --preset app-release
# ── все проекты сразу ─────────────────────────────────────────────────────────
[doc('Собрать все проекты (Debug)')]
[group('build')]
build-all-debug: _configure-debug
cmake --build --preset all-debug
[doc('Собрать все проекты (Release)')]
[group('build')]
build-all-release: _configure-release
cmake --build --preset all-release
# ── утилиты ───────────────────────────────────────────────────────────────────
[group('build'), confirm("Delete build/ directory?")]
[confirm("Delete build/ directory?")]
[doc('Удалить директорию build/')]
[group('build')]
clean:
rm -rf build
# =============================================================================
# ГРУППА: test
# ГРУППА: test — host-тесты (Unity + fff, в devcontainer)
# =============================================================================
[private]
@ -85,145 +98,154 @@ _configure-host-debug:
_configure-host-release:
cmake --preset host-release
[doc('Собрать и запустить host unit-тесты (Debug)')]
[group('test')]
test-host: _configure-host-debug
cmake --build --preset host-debug-build
ctest --preset host-debug-test
[doc('Собрать и запустить host unit-тесты (Release)')]
[group('test')]
test-host-release: _configure-host-release
cmake --build --preset host-release-build
ctest --preset host-release-test
# =============================================================================
# ГРУППА: flash — подготовка HAB-образов
#
# Матрица HAB:
# firmware-test × debug / release
# bootloader × debug / release
# app × debug / release
# all × debug / release
#
# Debug HAB — для прошивки своей платы и отладки.
# Release HAB — для производственного сервера.
#
# HAB yaml конфиги:
# tools/host/hab/hab_<project>_debug.yaml — путь к Debug ELF
# tools/host/hab/hab_<project>_release.yaml — путь к Release ELF
# ГРУППА: hil — сборка target-прошивок для HIL-тестов
# Запуск самих тестов — на хосте через just host::hil-*
# =============================================================================
[doc('Собрать все HIL target-прошивки (Debug)')]
[group('hil')]
build-hil: _configure-target-debug
cmake --build --preset target-debug-build
# =============================================================================
# ГРУППА: hab_image_gen — подготовка HAB-образов
# =============================================================================
# ── firmware_test ─────────────────────────────────────────────────────────────
[doc('Собрать HAB-образ firmware_test (Debug)')]
[group('hab_image_gen')]
hab-firmware-test-debug: build-firmware-test-debug
#!/usr/bin/env bash
set -euo pipefail
mkdir -p "{{BUILD_DIR}}/Debug"
cd "{{TOOLS_DIR}}/hab"
mkdir -p "{{ BUILD_DIR }}/Debug"
cd "{{ TOOLS_DIR }}/hab"
uv run nxpimage hab export --force \
-c hab_firmware_test_debug.yaml \
-o "{{BUILD_DIR}}/Debug/firmware_test_hab.bin"
-o "{{ BUILD_DIR }}/Debug/firmware_test_hab.bin"
echo " ✅ firmware_test_hab.bin (Debug)"
[doc('Собрать HAB-образ firmware_test (Release)')]
[group('hab_image_gen')]
hab-firmware-test-release: build-firmware-test-release
#!/usr/bin/env bash
set -euo pipefail
mkdir -p "{{BUILD_DIR}}/Release"
cd "{{TOOLS_DIR}}/hab"
mkdir -p "{{ BUILD_DIR }}/Release"
cd "{{ TOOLS_DIR }}/hab"
uv run nxpimage hab export --force \
-c hab_firmware_test_release.yaml \
-o "{{BUILD_DIR}}/Release/firmware_test_hab.bin"
-o "{{ BUILD_DIR }}/Release/firmware_test_hab.bin"
echo " ✅ firmware_test_hab.bin (Release)"
# ── bootloader ───────────────────────────────────────────────────────────────
[doc('Собрать HAB-образ bootloader (Debug)')]
[group('hab_image_gen')]
hab-bootloader-debug: build-bootloader-debug
#!/usr/bin/env bash
set -euo pipefail
mkdir -p "{{BUILD_DIR}}/Debug"
cd "{{TOOLS_DIR}}/hab"
mkdir -p "{{ BUILD_DIR }}/Debug"
cd "{{ TOOLS_DIR }}/hab"
uv run nxpimage hab export --force \
-c hab_bootloader_debug.yaml \
-o "{{BUILD_DIR}}/Debug/bootloader_hab.bin"
-o "{{ BUILD_DIR }}/Debug/bootloader_hab.bin"
echo " ✅ bootloader_hab.bin (Debug)"
[doc('Собрать HAB-образ bootloader (Release)')]
[group('hab_image_gen')]
hab-bootloader-release: build-bootloader-release
#!/usr/bin/env bash
set -euo pipefail
mkdir -p "{{BUILD_DIR}}/Release"
cd "{{TOOLS_DIR}}/hab"
mkdir -p "{{ BUILD_DIR }}/Release"
cd "{{ TOOLS_DIR }}/hab"
uv run nxpimage hab export --force \
-c hab_bootloader_release.yaml \
-o "{{BUILD_DIR}}/Release/bootloader_hab.bin"
-o "{{ BUILD_DIR }}/Release/bootloader_hab.bin"
echo " ✅ bootloader_hab.bin (Release)"
# ── app ───────────────────────────────────────────────────────────────────────
[doc('Собрать HAB-образ tft_app (Debug)')]
[group('hab_image_gen')]
hab-app-debug: build-app-debug
#!/usr/bin/env bash
set -euo pipefail
mkdir -p "{{BUILD_DIR}}/Debug"
cd "{{TOOLS_DIR}}/hab"
mkdir -p "{{ BUILD_DIR }}/Debug"
cd "{{ TOOLS_DIR }}/hab"
uv run nxpimage hab export --force \
-c hab_app_debug.yaml \
-o "{{BUILD_DIR}}/Debug/app_hab.bin"
-o "{{ BUILD_DIR }}/Debug/app_hab.bin"
echo " ✅ app_hab.bin (Debug)"
[doc('Собрать HAB-образ tft_app (Release)')]
[group('hab_image_gen')]
hab-app-release: build-app-release
#!/usr/bin/env bash
set -euo pipefail
mkdir -p "{{BUILD_DIR}}/Release"
cd "{{TOOLS_DIR}}/hab"
mkdir -p "{{ BUILD_DIR }}/Release"
cd "{{ TOOLS_DIR }}/hab"
uv run nxpimage hab export --force \
-c hab_app_release.yaml \
-o "{{BUILD_DIR}}/Release/app_hab.bin"
-o "{{ BUILD_DIR }}/Release/app_hab.bin"
echo " ✅ app_hab.bin (Release)"
# ── все образы сразу ─────────────────────────────────────────────────────────
[doc('Собрать HAB-образы всех проектов (Debug)')]
[group('hab_image_gen')]
hab-all-debug: hab-firmware-test-debug hab-bootloader-debug hab-app-debug
@echo " ✅ All HAB images (Debug) ready"
[doc('Собрать HAB-образы всех проектов (Release)')]
[group('hab_image_gen')]
hab-all-release: hab-firmware-test-release hab-bootloader-release hab-app-release
@echo " ✅ All HAB images (Release) ready"
# ── верификация образа (для отладки) ─────────────────────────────────────────
# Использование: just --justfile just/build.just hab-verify firmware_test debug
# ── верификация образа ────────────────────────────────────────────────────────
[doc('Верифицировать HAB-образ: hab-verify <project> <debug|release>')]
[group('hab_image_gen')]
hab-verify project="firmware_test" type="release":
#!/usr/bin/env bash
set -euo pipefail
case "{{project}}-{{type}}" in
firmware_test-debug) BIN="{{BUILD_DIR}}/Debug/firmware_test_hab.bin" ;;
firmware_test-release) BIN="{{BUILD_DIR}}/Release/firmware_test_hab.bin" ;;
bootloader-debug) BIN="{{BUILD_DIR}}/Debug/bootloader_hab.bin" ;;
bootloader-release) BIN="{{BUILD_DIR}}/Release/bootloader_hab.bin" ;;
app-debug) BIN="{{BUILD_DIR}}/Debug/app_hab.bin" ;;
app-release) BIN="{{BUILD_DIR}}/Release/app_hab.bin" ;;
case "{{ project }}-{{ type }}" in
firmware_test-debug) BIN="{{ BUILD_DIR }}/Debug/firmware_test_hab.bin" ;;
firmware_test-release) BIN="{{ BUILD_DIR }}/Release/firmware_test_hab.bin" ;;
bootloader-debug) BIN="{{ BUILD_DIR }}/Debug/bootloader_hab.bin" ;;
bootloader-release) BIN="{{ BUILD_DIR }}/Release/bootloader_hab.bin" ;;
app-debug) BIN="{{ BUILD_DIR }}/Debug/app_hab.bin" ;;
app-release) BIN="{{ BUILD_DIR }}/Release/app_hab.bin" ;;
*)
echo " ❌ Unknown: {{project}}-{{type}}"
echo " Projects: firmware_test, bootloader, app"
echo " Types: debug, release"
echo " ❌ Unknown: {{ project }}-{{ type }}"
exit 1 ;;
esac
OUT="/tmp/hab_parse_{{project}}_{{type}}.yaml"
cd "{{TOOLS_DIR}}" && uv run nxpimage hab parse -b "${BIN}" -o "${OUT}"
OUT="/tmp/hab_parse_{{ project }}_{{ type }}.yaml"
cd "{{ TOOLS_DIR }}" && uv run nxpimage hab parse -b "${BIN}" -o "${OUT}"
echo " ✅ ${OUT}"
grep -E "(entry|csf|tag)" "${OUT}" || true
# =============================================================================
# ГРУППА: quality — линтеры, форматтеры
# ГРУППА: quality
# =============================================================================
# TODO: проверять bsp,firmware, modules
[doc('Запустить линтеры (clang-tidy)')]
[group('quality')]
lint:
echo "TODO!"
# clang-tidy -p {{BUILD_DIR}}/Debug src/**/*.c
[doc('Форматировать код (clang-format)')]
[group('quality')]
format:
echo "TODO!"
# clang-format -i src/**/*.{c,h}

View file

@ -4,50 +4,53 @@
#
# Предполагается что CI работает внутри devcontainer.
# =============================================================================
# === Импорт общих переменных ===
BUILD_DIR := env('BUILD_DIR', justfile_directory() / 'build')
set working-directory := '..'
# =============================================================================
# ГРУППА: ci — полные пайплайны
# =============================================================================
# Полный CI pipeline: сборка + тесты + линтеры
[doc('Полный CI pipeline: сборка + тесты + линтеры')]
[group('ci')]
pipeline: build test lint
@echo " ✅ CI pipeline complete"
# Сборка всех проектов в Release
[doc('Собрать все проекты в Release')]
[group('ci')]
build:
just build::build-all-release
# Unit-тесты
[doc('Запустить host unit-тесты (Release)')]
[group('ci')]
test:
just build::test-host-release
# TODO: Линтеры + статический анализ
[doc('Запустить линтеры и статический анализ')]
[group('ci')]
lint:
echo "TODO"
#just build::lint
#just build::check
# Генерация HAB-образов для релиза
[doc('Собрать HAB Release-артефакты для выкладки')]
[group('ci')]
release: build
just build::hab-all-release
@echo " ✅ Release artifacts ready in {{BUILD_DIR}}/Release/"
@echo " ✅ Release artifacts ready in {{ BUILD_DIR }}/Release/"
# Генерация coverage отчёта (если gcov настроен)
[group('ci'), private]
[group('ci')]
[private]
_coverage:
#!/usr/bin/env bash
set -euo pipefail
if command -v gcovr &>/dev/null; then
echo " 📊 Generating coverage report..."
gcovr --xml -o {{BUILD_DIR}}/coverage.xml
gcovr --xml -o {{ BUILD_DIR }}/coverage.xml
else
echo " ⚠️ gcovr not found, skipping coverage"
fi

View file

@ -1,33 +1,24 @@
# =============================================================================
# host.just — операции на хост-машине (вне devcontainer)
#
# Ответственность:
# - bootstrap окружения (just, uv, docker, udev)
# - USB-прошивка через spsdk
# - HIL-тесты на реальном железе
# - производственные сценарии (incoming, production)
#
# Все рецепты должны работать БЕЗ devcontainer.
# =============================================================================
# === Импорт общих переменных из корневого justfile ===
# Благодаря `set export` в корне, эти переменные доступны
TOOLS_DIR := env('TOOLS_DIR', justfile_directory() / 'tools/host')
HIL_DIR := justfile_directory() / 'tools/hil'
CACHE_DIR := env('CACHE_DIR', justfile_directory() / '.cache')
BUILD_DIR := env('BUILD_DIR', justfile_directory() / 'build')
_uname := `uname -s`
_venv := if _uname =~ "MINGW|MSYS|CYGWIN" { ".venv-host-win" } else { ".venv-host" }
# === Минимальные версии зависимостей ===
JUST_MIN := "1.36.0"
UV_MIN := "0.4.0"
JUST_MIN := "1.36.0"
UV_MIN := "0.4.0"
DOCKER_MIN := "24.0.0"
set working-directory := '..'
# =============================================================================
# ГРУППА: setup — инициализация рабочего окружения
# ГРУППА: setup
# =============================================================================
# [1/3] Проверить наличие и версии инструментов на хост-машине
[doc('Проверить версии и наличие зависимостей хоста (just, uv, docker)')]
[group('setup')]
check-deps:
#!/usr/bin/env bash
@ -48,7 +39,6 @@ check-deps:
*) PLATFORM="unknown" ;;
esac
# Сравнение semver без sort -V (недоступен в Git Bash)
semver_ge() {
local a="$1" b="$2"
local a1 a2 a3 b1 b2 b3
@ -69,21 +59,21 @@ check-deps:
case "${PLATFORM}" in
linux)
case "$tool" in
just) echo "uv tool install 'rust-just=={{JUST_MIN}}'" ;;
just) echo "uv tool install 'rust-just=={{ JUST_MIN }}'" ;;
uv) echo "curl -LsSf https://astral.sh/uv/install.sh | sh" ;;
docker) echo "https://docs.docker.com/engine/install/ubuntu/" ;;
esac ;;
macos)
case "$tool" in
just) echo "uv tool install 'rust-just=={{JUST_MIN}}'" ;;
just) echo "uv tool install 'rust-just=={{ JUST_MIN }}'" ;;
uv) echo "brew install uv" ;;
docker) echo "brew install --cask docker" ;;
esac ;;
windows)
case "$tool" in
just) echo "uv tool install 'rust-just=={{JUST_MIN}}'" ;;
just) echo "uv tool install 'rust-just=={{ JUST_MIN }}'" ;;
uv) echo 'powershell -c "irm https://astral.sh/uv/install.ps1 | iex"' ;;
docker) echo "winget install --id Docker.DockerDesktop (в PowerShell)" ;;
docker) echo "winget install --id Docker.DockerDesktop" ;;
esac ;;
esac
}
@ -115,9 +105,9 @@ check-deps:
echo -e "${BOLD} Checking host dependencies...${RESET}"
echo ""
check_tool "just" "just" "{{JUST_MIN}}"
check_tool "uv" "uv" "{{UV_MIN}}"
check_tool "docker" "docker" "{{DOCKER_MIN}}"
check_tool "just" "just" "{{ JUST_MIN }}"
check_tool "uv" "uv" "{{ UV_MIN }}"
check_tool "docker" "docker" "{{ DOCKER_MIN }}"
if [[ "${PLATFORM}" == "linux" ]]; then
echo ""
@ -125,14 +115,14 @@ check-deps:
echo ""
rules_file="/etc/udev/rules.d/99-nxp-mimxrt.rules"
udev_ok=true
grep -q "1fc9" "$rules_file" 2>/dev/null || { warn "udev: BootROM rule missing (VID 1fc9)"; udev_ok=false; }
grep -q "15a2" "$rules_file" 2>/dev/null || { warn "udev: Flashloader rule missing (VID 15a2)"; udev_ok=false; }
grep -q "1fc9" "$rules_file" 2>/dev/null || { warn "udev: BootROM rule missing"; udev_ok=false; }
grep -q "15a2" "$rules_file" 2>/dev/null || { warn "udev: Flashloader rule missing"; udev_ok=false; }
if ! groups 2>/dev/null | grep -q plugdev; then
warn "User '$(whoami)' not in 'plugdev' group"
udev_ok=false
fi
if [[ "$udev_ok" == "false" ]]; then
warn "USB flashing requires root without udev rules -- run: just setup-udev"
warn "Run: just host::setup-udev"
else
ok "udev rules (NXP USB) + plugdev group"
fi
@ -140,62 +130,54 @@ check-deps:
echo ""
if [[ $ERRORS -gt 0 ]]; then
echo -e " ${RED}${BOLD}$ERRORS error(s) found. Fix them before continuing.${RESET}"
echo ""
echo -e " ${RED}${BOLD}$ERRORS error(s) found.${RESET}"
exit 1
elif [[ $WARNINGS -gt 0 ]]; then
echo -e " ${YELLOW}${BOLD}$WARNINGS warning(s). Some features may not work.${RESET}"
echo -e " ${YELLOW}${BOLD}$WARNINGS warning(s).${RESET}"
else
echo -e " ${GREEN}${BOLD}All dependencies satisfied.${RESET}"
fi
echo ""
# [2/3] Установить udev правила для USB-прошивки (только Linux, требует sudo)
[doc('Установить udev rules для NXP USB (только Linux)')]
[group('setup')]
setup-udev:
#!/usr/bin/env bash
set -euo pipefail
case "$(uname -s)" in
Linux*) ;;
*)
echo " udev rules are Linux-only, skipping on $(uname -s)."
exit 0
;;
*) echo " udev rules are Linux-only, skipping."; exit 0 ;;
esac
echo " Installing udev rules for NXP USB devices..."
printf '%s\n%s\n%s\n%s\n' \
'# NXP BootROM -- SDP mode (BOOT_MOD_1 = 3V3)' \
'# NXP BootROM -- SDP mode' \
'SUBSYSTEM=="usb", ATTR{idVendor}=="1fc9", ATTR{idProduct}=="0130", MODE="0666", GROUP="plugdev"' \
'# NXP Flashloader -- after jump-address' \
'# NXP Flashloader' \
'SUBSYSTEM=="usb", ATTR{idVendor}=="15a2", ATTR{idProduct}=="0073", MODE="0666", GROUP="plugdev"' \
| sudo tee /etc/udev/rules.d/99-nxp-mimxrt.rules > /dev/null
sudo udevadm control --reload-rules
sudo udevadm trigger
sudo udevadm control --reload-rules && sudo udevadm trigger
sudo usermod -a -G plugdev "$USER"
echo " ✅ udev rules installed"
echo " ⚠️ Re-login required for 'plugdev' group to take effect"
echo " ✅ udev rules installed. Re-login required."
# [3/3] Синхронизировать зависимости tools/host (spsdk и др.)
# Идемпотентен: пропускает sync если uv.lock не изменился
[doc('Установить/обновить Python-зависимости tools/host (spsdk и др.)')]
[group('setup')]
setup-tools:
#!/usr/bin/env bash
set -euo pipefail
LOCK="{{TOOLS_DIR}}/uv.lock"
STAMP="{{CACHE_DIR}}/uv_lock.sha256"
mkdir -p "{{CACHE_DIR}}"
LOCK="{{ TOOLS_DIR }}/uv.lock"
STAMP="{{ CACHE_DIR }}/uv_lock.sha256"
mkdir -p "{{ CACHE_DIR }}"
current=$(sha256sum "$LOCK" 2>/dev/null | cut -d' ' -f1 || echo "missing")
previous=$(cat "$STAMP" 2>/dev/null || echo "none")
if [[ "$current" == "$previous" ]]; then
echo " ✅ tools/host deps up to date (lockfile unchanged)"
echo " ✅ tools/host deps up to date"
else
echo " 📦 Syncing tools/host deps..."
cd "{{TOOLS_DIR}}" && uv sync
cd "{{ TOOLS_DIR }}" && uv sync
echo "$current" > "$STAMP"
echo " ✅ tools/host deps installed"
fi
# Полная инициализация — запустить один раз после git clone
[doc('Полная первичная настройка хоста: check-deps + setup-udev + setup-tools')]
[group('setup')]
bootstrap:
#!/usr/bin/env bash
@ -224,118 +206,143 @@ bootstrap:
echo ""
# =============================================================================
# ГРУППА: flash — прошивка платы через USB
# Требует: uv + spsdk (just setup-tools), плата в SDP-режиме
# HAB-образы должны быть собраны заранее (just hab-* внутри devcontainer)
#
# Матрица прошивки:
# just flash firmware_test debug
# just flash firmware_test release
# just flash bootloader debug
# just flash bootloader release
# just flash app debug
# just flash app release
# ГРУППА: flash
# =============================================================================
# Прошить выбранный HAB-образ.
# Использование: just flash <project> <type>
# project: firmware_test | bootloader | app
# type: debug | release (default: release)
#
# Образ должен быть собран заранее в devcontainer:
# just --justfile just/build.just hab-<project>-<type>
[doc('Прошить образ во Flash через USB ROM: flash <firmware_test|bootloader|app> <debug|release>')]
[group('flash')]
flash project type="release":
#!/usr/bin/env bash
set -euo pipefail
case "{{type}}" in
case "{{ type }}" in
debug) BUILD_TYPE="Debug" ;;
release) BUILD_TYPE="Release" ;;
*)
echo " ❌ Unknown type: {{type}}"
echo " Valid: debug, release"
exit 1 ;;
*) echo " ❌ Unknown type: {{ type }}"; exit 1 ;;
esac
cd "{{TOOLS_DIR}}" && UV_PROJECT_ENVIRONMENT={{_venv}} uv run python flash_usb.py \
--firmware "{{project}}" --build-type "${BUILD_TYPE}"
cd "{{ TOOLS_DIR }}" && UV_PROJECT_ENVIRONMENT={{ _venv }} uv run python flash_usb.py \
--firmware "{{ project }}" --build-type "${BUILD_TYPE}"
# Загрузить образ в RAM без записи во Flash.
# Полезно для быстрой проверки — не изнашивает Flash, плата стартует сразу.
# Использование: just flash-ram <project> <type>
[doc('Прошить образ в RAM через USB ROM: flash-ram <firmware_test|bootloader|app> <debug|release>')]
[group('flash')]
flash-ram project type="debug":
#!/usr/bin/env bash
set -euo pipefail
case "{{type}}" in
case "{{ type }}" in
debug) BUILD_TYPE="Debug" ;;
release) BUILD_TYPE="Release" ;;
*)
echo " ❌ Unknown type: {{type}}"
echo " Valid: debug, release"
exit 1 ;;
*) echo " ❌ Unknown type: {{ type }}"; exit 1 ;;
esac
cd "{{TOOLS_DIR}}" && UV_PROJECT_ENVIRONMENT={{_venv}} uv run python flash_usb.py \
--firmware "{{project}}" --build-type "${BUILD_TYPE}" --ram-only
cd "{{ TOOLS_DIR }}" && UV_PROJECT_ENVIRONMENT={{ _venv }} uv run python flash_usb.py \
--firmware "{{ project }}" --build-type "${BUILD_TYPE}" --ram-only
# Псевдонимы для частых сценариев ────────────────────────────────────────────
# firmware_test Debug — самый частый при разработке
[doc('Прошить firmware_test Debug во Flash')]
[group('flash')]
flash-test-debug:
@just host::flash firmware_test debug
# firmware_test Release — как будет на производстве
[doc('Прошить firmware_test Release во Flash')]
[group('flash')]
flash-test-release:
@just host::flash firmware_test release
# TODO: bootloader + app Release — финальная прошивка
[group('flash'), confirm("Flash bootloader + app (Release)?")]
[confirm("Flash bootloader + app (Release)?")]
[doc('Прошить bootloader + tft_app Release во Flash (производство)')]
[group('flash')]
flash-production:
@just host::flash bootloader release
@just host::flash app release
# =============================================================================
# ГРУППА: pipeline — производственные сценарии (для сервера)
# ГРУППА: hil — запуск HIL-тестов на реальном железе
# Сборка target-прошивок — в devcontainer: just build::build-hil
# =============================================================================
# Входной контроль платы: тестовая прошивка (Release) -> HIL-тесты
_hil_build := BUILD_DIR / "target-debug"
[doc('Загрузить test_host_uart.elf на таргет без запуска тестов (для ручной отладки)')]
[group('hil')]
hil-load:
HIL_BUILD_DIR={{ _hil_build }} \
uv run --directory {{ HIL_DIR }} python load_and_run.py \
{{ _hil_build }}/tests/target/host_uart/test_host_uart.elf
[doc('Запустить все HIL-тесты')]
[group('hil')]
hil-run:
HIL_BUILD_DIR={{ _hil_build }} \
uv run --directory {{ HIL_DIR }} pytest -v
[doc('Запустить только smoke HIL-тесты (быстро, для CI)')]
[group('hil')]
hil-smoke:
HIL_BUILD_DIR={{ _hil_build }} \
uv run --directory {{ HIL_DIR }} pytest -v -m smoke
[doc('Запустить HIL-тесты без перезагрузки ELF (прошивка уже запущена)')]
[group('hil')]
hil-run-fast:
HIL_BUILD_DIR={{ _hil_build }} \
uv run --directory {{ HIL_DIR }} pytest -v --no-load
# =============================================================================
# ГРУППА: pipeline
# =============================================================================
[doc('Входной контроль: прошить firmware_test Release + запустить HIL-тесты')]
[group('pipeline')]
incoming: flash-test-release
#!/usr/bin/env bash
set -euo pipefail
echo " ▶ Running HIL tests (CAN, UART, SDRAM, SPI Flash)..."
# cd tools/hil && UV_PROJECT_ENVIRONMENT={{_venv}} uv run python run_hil.py <- раскомментить когда готово
echo " ⚠️ HIL tests not yet implemented"
echo " ▶ Running HIL tests..."
just host::hil-run
# Финальная прошивка: загрузчик + основная прошивка (Release)
[doc('Производственная прошивка: bootloader + tft_app Release')]
[group('pipeline')]
production: flash-production
@echo " ✅ Production firmware flashed (bootloader + app)"
@echo " ✅ Production firmware flashed"
# =============================================================================
# ГРУППА: util — вспомогательные инструменты
# ГРУППА: util
# =============================================================================
# Найти подключённые NXP USB-устройства
[doc('Найти подключённые NXP устройства (BootROM, flashloader, обычный режим)')]
[group('util')]
scan:
cd "{{TOOLS_DIR}}" && UV_PROJECT_ENVIRONMENT={{_venv}} uv run nxpdevscan
cd "{{ TOOLS_DIR }}" && UV_PROJECT_ENVIRONMENT={{ _venv }} uv run nxpdevscan
# Проверить связь с BootROM через SDP (плата в SDP-режиме)
[group('util'), no-cd]
[doc('Получить статус SDP (BootROM режим, VID:PID 1FC9:0130)')]
[group('util')]
[no-cd]
sdp-status:
cd "{{TOOLS_DIR}}" && UV_PROJECT_ENVIRONMENT={{_venv}} uv run sdphost -u 0x1FC9,0x0130 -- error-status
cd "{{ TOOLS_DIR }}" && UV_PROJECT_ENVIRONMENT={{ _venv }} uv run sdphost -u 0x1FC9,0x0130 -- error-status
# Проверить что Flashloader отвечает (после jump-address)
[doc('Получить статус flashloader (VID:PID 15A2:0073)')]
[group('util')]
flashloader-status:
cd "{{TOOLS_DIR}}" && UV_PROJECT_ENVIRONMENT={{_venv}} uv run blhost -u 0x15A2,0x0073 -- get-property 1 0
cd "{{ TOOLS_DIR }}" && UV_PROJECT_ENVIRONMENT={{ _venv }} uv run blhost -u 0x15A2,0x0073 -- get-property 1 0
# Обновить spsdk до новой версии
# Использование: just upgrade-tools 3.8.0
[doc('Обновить spsdk до указанной версии: upgrade-tools <version>')]
[group('util')]
upgrade-tools version:
cd "{{TOOLS_DIR}}" && uv add "spsdk=={{version}}" && uv sync
@echo " ✅ spsdk upgraded to {{version}}"
@echo " Run: git add tools/host/uv.lock tools/host/pyproject.toml"
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

10
pyocd_debug.yaml Normal file
View file

@ -0,0 +1,10 @@
# pyocd_debug.yaml — конфигурация для отладки через GDB
#
# Используется: just host::debug-server
# Не использовать для HIL-тестов — для них pyocd.yaml (target: cortex_m, RAM-режим)
#
# Первый запуск — установить CMSIS pack:
# just host::debug-pack-install
target_override: mimxrt1050_quadspi
frequency: 4000000

372
tests/HIL_CREATE_TEST.md Normal file
View file

@ -0,0 +1,372 @@
# Добавление нового HIL-теста
## Обзор стека
```bash
devcontainer хост
──────────────────────────────── ──────────────────────────────────────
tests/target/<name>/ tools/hil/
main.c ← C-прошивка test_<name>.py ← pytest-тесты
CMakeLists.txt conftest.py ← фикстуры (общие)
CMakePresets.json just/host.just
target-debug-build hil-run, hil-smoke ...
└── targets: [test_<name>]
just/build.just
build-hil
```
---
## Шаг 1 — C-прошивка: `tests/target/<name>/`
### `main.c`
Минимальный шаблон для нового теста:
```c
#include "board.h"
#include "bsp/led.h"
#include "bsp/tick.h"
#include "bsp/uart_host.h"
#include <string.h>
#define CLI_BAUD_RATE 115200U
#define CLI_LINE_MAX 128U
#define CLI_RX_TIMEOUT 100U
static size_t cli_read_line(uint8_t *p_buf, size_t max_len)
{
size_t pos = 0U;
while (pos < (max_len - 1U)) {
int32_t byte = bsp_uart_host_read_byte(CLI_RX_TIMEOUT);
if (byte < 0) break;
if ((char)byte == '\r') continue;
if ((char)byte == '\n') break;
p_buf[pos++] = (uint8_t)byte;
}
p_buf[pos] = '\0';
return pos;
}
static void cli_process_line(const char *p_line)
{
if (strncmp(p_line, "PING", 4U) == 0) {
bsp_uart_host_write_str("PONG\r\n");
}
/* TODO: добавить команды для нового теста */
else if (p_line[0] != '\0') {
bsp_uart_host_write_str("ERR_UNKNOWN\r\n");
}
}
int main(void)
{
board_hw_init();
bsp_tick_init();
bsp_led_init();
bsp_uart_host_init(CLI_BAUD_RATE);
bsp_led_on(LED_HEARTBEAT);
/* Шлём READY пока хост не открыл порт */
while (bsp_uart_host_rx_available() == 0U) {
bsp_uart_host_write_str("READY\r\n");
bsp_delay(200U);
}
static uint8_t s_line_buf[CLI_LINE_MAX];
for (;;) {
size_t len = cli_read_line(s_line_buf, sizeof(s_line_buf));
if (len > 0U) cli_process_line((const char *)s_line_buf);
}
}
```
### `CMakeLists.txt`
```cmake
set(TARGET_NAME test_<name>)
add_executable(${TARGET_NAME}
main.c
${BSP_GENERATED}/clock_config.c
${BSP_STARTUP_FILE}
${BSP_SYSCALLS_FILE}
)
target_link_options(${TARGET_NAME} PRIVATE
-T${CMAKE_SOURCE_DIR}/cmake/linker/MIMXRT1052xxxxx_ram.ld
-Wl,--gc-sections
-Wl,--print-memory-usage
-Wl,-Map=${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}.map
)
target_link_libraries(${TARGET_NAME} PRIVATE
bsp_boot_ram
bsp_board
bsp_led
bsp_tick
bsp_uart_host
# + bsp_can / bsp_sdio / ... если нужно
)
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
COMMAND ${CMAKE_SIZE} $<TARGET_FILE:${TARGET_NAME}>
COMMENT "Size: ${TARGET_NAME}"
)
```
---
## Шаг 2 — Подключить в `tests/target/CMakeLists.txt`
```cmake
add_subdirectory(host_uart)
add_subdirectory(<name>) # ← добавить строку
```
---
## Шаг 3 — `CMakePresets.json`: добавить таргет в `target-debug-build`
```json
{
"name": "target-debug-build",
"configurePreset": "target-debug",
"targets": [
"test_host_uart",
"test_<name>"
]
}
```
---
## Шаг 4 — `just/build.just`: `build-hil` пересобирает всё автоматически
Ничего менять не нужно — `build-hil` вызывает `cmake --build --preset target-debug-build`,
а пресет уже знает про новый таргет после Шага 3.
```bash
# Проверить что новый таргет собирается:
just build::build-hil
```
---
## Шаг 5 — Python-тест: `tools/hil/test_<name>.py`
```python
"""test_<name>.py — HIL тест <что тестируем>."""
import pytest
from conftest import uart_cmd
class Test<Name>:
@pytest.fixture(autouse=True)
def _setup(self, loaded_<name>, uart):
self.ser = uart
@pytest.mark.smoke
def test_ping(self):
"""Базовая проверка канала."""
assert uart_cmd(self.ser, "PING") == "PONG"
def test_something(self):
"""Описание теста."""
resp = uart_cmd(self.ser, "MY_CMD arg")
assert resp == "EXPECTED"
```
---
## Шаг 6 — `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",
)
```
Фикстура `uart` уже зависит от `loaded_host_uart`. Для нового теста нужна
своя пара: `loaded_<name>` + при необходимости своя `uart_<name>` если нужен
отдельный порт или скорость. Обычно достаточно переиспользовать `uart`.
---
## Шаг 7 — `just/host.just`: добавить рецепты (опционально)
Для часто используемых тестов удобно добавить алиасы:
```just
[group('hil')]
hil-run-<name>:
HIL_BUILD_DIR={{_hil_build}} \
uv run --directory {{HIL_DIR}} pytest test_<name>.py -v
```
Если алиас не нужен — `just host::hil-run` запускает **все** тесты из `tools/hil/`
автоматически (pytest обходит все `test_*.py`).
---
## Полный цикл
```bash
# 1. devcontainer — собрать прошивку
just build::build-hil
# 2. хост — запустить все HIL тесты (включая новый)
just host::hil-run
# 3. хост — только новый тест
just host::hil-run-<name>
# 4. хост — загрузить ELF без тестов (для ручной отладки)
uv run --directory tools/hil python load_and_run.py \
build/target-debug/tests/target/<name>/test_<name>.elf
```
---
## Как работают фикстуры
### Общая схема зависимостей
Для каждого тест-модуля цепочка фикстур одна и та же:
```bash
test_foo()
└── _setup (scope=function, autouse)
├── loaded_<n> (scope=module) ← грузит ELF на MCU
└── uart (scope=module) ← открывает порт, ждёт READY
└── depends_on: loaded_<n> ← гарантирует порядок
```
`scope=module` означает: фикстура создаётся один раз на весь файл с тестами
и уничтожается после последнего теста в нём. Все тесты внутри одного файла
разделяют один и тот же экземпляр — ELF загружается один раз, порт открывается
один раз.
### Порядок вызовов внутри одного модуля
```bash
──────────────────────────────────── module scope (один раз на файл)
1. loaded_<n>()
└── _load_elf()
├── open_target() → pyOCD: подключиться к MCU
├── flexram_init() → настроить ITCM/DTCM
├── load_elf() → записать PT_LOAD сегменты по адресам
└── run_from_vectors()→ SP/PC из 0x00000000/0x00000004 → resume
2. uart() (зависит от loaded_<n>, создаётся после)
├── serial.Serial.open()
├── while readline() != "READY": ← ждём сигнал от прошивки
└── yield ser → порт готов к работе
──────────────────────────────────── function scope (каждый тест)
3. _setup()
└── self.ser = uart → просто сохранить ссылку
4. test_ping() → uart_cmd(self.ser, "PING") == "PONG"
5. test_echo_simple()
6. ... остальные тесты
──────────────────────────────────── teardown (в обратном порядке)
7. uart teardown → ser.close()
8. loaded_<n> teardown → (нет, возвращает None)
```
### Почему `uart` явно зависит от `loaded_<n>`
```python
def uart(request, loaded_<n>): # ← зависимость объявлена в сигнатуре
...
```
Без этой зависимости pytest мог бы создать `uart` раньше чем ELF загружен.
Порт бы открылся, но `READY` не пришёл бы — таймаут и падение. Явная
зависимость в сигнатуре — единственный надёжный способ гарантировать порядок.
### Что происходит при запуске нескольких тест-файлов
```bash
pytest test_uart.py test_can.py
```
```bash
test_uart.py test_can.py
────────────────────────────── ──────────────────────────────
loaded_host_uart ← создаётся loaded_can ← создаётся
uart ← создаётся uart ← создаётся заново
test_ping test_can_send
test_echo test_can_receive
uart.close() ← teardown uart.close() ← teardown
```
Каждый файл получает **свою** загрузку ELF и свой сеанс UART. MCU
перезагружается между файлами — это правильно, у каждого теста своя прошивка.
### Что происходит при запуске одного теста из модуля
```bash
uv run pytest test_uart.py::TestUartBasic::test_echo_simple -v
```
Несмотря на то что запущен один тест, `scope=module`-фикстуры всё равно
создаются: ELF загружается, порт открывается, READY ожидается. Это цена за
изоляцию — зато тест полностью самодостаточен.
### Шаблон `conftest.py` для нового теста
Для каждого нового тест-модуля нужно добавить только одну фикстуру — `loaded_<n>`.
Всё остальное (`uart`, `uart_cmd`, `open_target`, `flexram_init`) переиспользуется:
```python
# conftest.py — добавить:
@pytest.fixture(scope="module")
def loaded_<n>(request: pytest.FixtureRequest) -> None:
_load_elf(
request,
Path(cfg.BUILD_DIR) / "tests/target/<n>/test_<n>.elf",
)
# Если нужна отдельная uart-фикстура (другой порт, другой бод):
@pytest.fixture(scope="module")
def uart_<n>(
request: pytest.FixtureRequest,
loaded_<n>, # ← порядок гарантирован
) -> Generator[serial.Serial, None, None]:
port = request.config.getoption("--vcom")
ser = serial.Serial(port=port, baudrate=115200, timeout=2.0)
# ждём READY ...
yield ser
ser.close()
```
В большинстве случаев отдельная `uart_<n>` не нужна — стандартная `uart`
работает для любого теста, потому что протокол (`READY` + текстовые команды)
одинаковый для всех прошивок.
---
## Чеклист
```bash
[ ] tests/target/<name>/main.c — C-прошивка с CLI
[ ] tests/target/<name>/CMakeLists.txt — сборка с bsp_boot_ram
[ ] tests/target/CMakeLists.txt — add_subdirectory(<name>)
[ ] CMakePresets.json — добавить test_<name> в targets
[ ] tools/hil/test_<name>.py — pytest-тесты
[ ] tools/hil/conftest.py — добавить loaded_<name> фикстуру
[ ] just/host.just — алиас hil-run-<name> (опционально)
```

376
tests/HOST_CREATE_TEST.md Normal file
View file

@ -0,0 +1,376 @@
# Добавление нового host-теста
## Обзор стека
```
devcontainer
─────────────────────────────────────────────────────────────────
tests/host/<n>/test_<n>.c ← тест (Unity + опционально fff)
tests/host/CMakeLists.txt ← регистрация через add_host_test()
tests/host/mocks/ ← stub-хедеры NXP SDK (если нужны)
CMakePresets.json just/build.just
host-debug-build test-host
└── targets: [test_<n>] cmake --build + ctest
```
---
## Шаг 0 — Определить категорию теста
Перед написанием кода определи к какой категории относится модуль:
| Категория | Описание | Инструментарий |
|-----------|----------|----------------|
| **A** | Нет вызовов NXP SDK: алгоритмы, парсеры, FSM, структуры данных | Unity |
| **B** | BSP-модуль вызывает `fsl_*.h`, USB-стек и т.д. | Unity + fff + stub-хедеры |
**Признак категории A:** в `.c` файле модуля нет ни одного `#include "fsl_*.h"`.
**Признак категории B:** есть хотя бы один такой include.
---
## Шаг 1 — Создать тестовый файл
### Категория A — платформонезависимый модуль
```c
/* tests/host/<n>/test_<n>.c */
#include "unity.h"
#include "<n>.h" /* тестируемый модуль */
void setUp(void) { /* сброс состояния перед каждым тестом */ }
void tearDown(void) { /* очистка после каждого теста */ }
void test_something(void)
{
/* Arrange */
int input = 42;
/* Act */
int result = module_process(input);
/* Assert */
TEST_ASSERT_EQUAL(expected, result);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_something);
return UNITY_END();
}
```
### Категория B — BSP-модуль с fff-фейками
Порядок `#include` принципиален — нарушение порядка вызовет ошибки компиляции:
```c
/* tests/host/<n>/test_<n>.c */
#include "unity.h"
#include "fff.h"
DEFINE_FFF_GLOBALS; /* 1. ровно один раз на весь .c файл */
#include "fsl_<driver>.h" /* 2. stub-хедер с типами (из mocks/) */
/* 3. объявить фейки для всех SDK-функций, которые вызывает тестируемый модуль */
FAKE_VOID_FUNC(SDK_Function_A, ArgType1, ArgType2);
FAKE_VALUE_FUNC(status_t, SDK_Function_B, ArgType1);
#include "bsp/<module>.h" /* 4. тестируемый модуль — всегда последним */
void setUp(void)
{
RESET_FAKE(SDK_Function_A);
RESET_FAKE(SDK_Function_B);
FFF_RESET_HISTORY();
/* при необходимости задать дефолтные return_val */
}
void tearDown(void) { }
void test_something(void)
{
/* Arrange: настроить поведение фейков */
SDK_Function_B_fake.return_val = kStatus_Success;
/* Act */
bsp_status_t status = bsp_module_do_something();
/* Assert: проверить результат и вызовы */
TEST_ASSERT_EQUAL(BSP_OK, status);
TEST_ASSERT_EQUAL(1, SDK_Function_A_fake.call_count);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_something);
return UNITY_END();
}
```
---
## Шаг 2 — Stub-хедеры (только категория B)
Если тестируемый модуль использует NXP SDK хедеры которых ещё нет в `tests/host/mocks/` — нужно создать stub.
### Что такое stub-хедер и зачем он нужен
NXP SDK хедеры (`fsl_gpio.h` и др.) тянут платформенные регистровые определения для Cortex-M7 — они не компилируются на хосте. Stub-хедер в `tests/host/mocks/` содержит только минимально необходимые типы и сигнатуры функций. CMake подключает `mocks/` **до** SDK, поэтому компилятор находит stub раньше оригинала.
### Шаблон stub-хедера
Добавляй в stub только то, что реально используется в тестируемом `.c` файле:
```c
/* tests/host/mocks/fsl_<driver>.h */
#pragma once
#include <stdint.h>
/* Минимально необходимые типы */
typedef struct { uint32_t reserved[64]; } DRIVER_Type;
typedef enum {
kStatus_Success = 0,
kStatus_Fail = 1,
} status_t;
/* Сигнатуры функций — реализации предоставляет fff */
void SDK_Function_A(DRIVER_Type *base, uint32_t arg);
status_t SDK_Function_B(DRIVER_Type *base, const uint8_t *data, size_t len);
```
### Уже существующие stubs в `tests/host/mocks/`
| Файл | Что заменяет | Используется в |
|------|-------------|----------------|
| `fsl_gpio.h` | GPIO драйвер | `test_bsp_led` |
| `pin_mux.h` | Макросы пинов из `generated/` | `test_bsp_led` |
| `board.h` | `board_hw_init()` | `test_bsp_led` |
Если нужный stub уже есть — ничего создавать не нужно, просто укажи `mocks/` в `MOCKS` аргументе `add_host_test()`.
---
## Шаг 3 — Зарегистрировать тест в `tests/host/CMakeLists.txt`
Используй функцию `add_host_test()`. Она создаёт исполняемый файл и регистрирует его в CTest:
```cmake
# Категория A — без mocks
add_host_test(
NAME test_<n>
SOURCES <n>/test_<n>.c
${PROJECT_SOURCE_DIR}/<path_to_module>/<module>.c
INCLUDES
${PROJECT_SOURCE_DIR}/<path_to_module>/include
)
# Категория B — с mocks
add_host_test(
NAME test_<n>
SOURCES <n>/test_<n>.c
${PROJECT_SOURCE_DIR}/bsp/<module>/src/<module>.c
INCLUDES
${PROJECT_SOURCE_DIR}/bsp/<module>/include
MOCKS
${BSP_MOCKS_DIR} # = tests/host/mocks/
)
```
### Аргументы `add_host_test()`
| Аргумент | Обязателен | Описание |
|----------|-----------|----------|
| `NAME` | ✓ | Имя исполняемого файла и теста в CTest |
| `SOURCES` | ✓ | Тестовый `.c` + исходники тестируемых модулей |
| `INCLUDES` | — | Дополнительные include-пути (для `#include "bsp/led.h"` и т.д.) |
| `MOCKS` | — | Директории со stub-хедерами (подключаются с высшим приоритетом) |
`lib_external` (Unity + fff) подключается автоматически — добавлять не нужно.
---
## Шаг 4 — Добавить таргет в `CMakePresets.json`
```json
{
"name": "host-debug-build",
"configurePreset": "host-debug",
"targets": [
"test_bsp_led",
"test_ring_buffer",
"test_timeout_pattern",
"uart_host_mock_example",
"test_<n>"
]
}
```
То же самое для `host-release-build` если нужен Release-прогон.
---
## Шаг 5 — Запустить
```bash
# Сборка + все тесты одной командой (devcontainer)
just build::test-host
# Только новый тест
ctest --preset host-debug-test -R test_<n> -V
# Напрямую — виден полный вывод Unity без CTest-обёртки
./build/host-debug/tests/host/test_<n>
```
---
## Справочник: Unity assertions
```c
/* Целые числа */
TEST_ASSERT_EQUAL(expected, actual)
TEST_ASSERT_EQUAL_INT8 / INT16 / INT32 / UINT8 / UINT32(expected, actual)
TEST_ASSERT_NOT_EQUAL(expected, actual)
TEST_ASSERT_INT_WITHIN(delta, expected, actual)
/* Булевые / указатели */
TEST_ASSERT_TRUE(condition)
TEST_ASSERT_FALSE(condition)
TEST_ASSERT_NULL(pointer)
TEST_ASSERT_NOT_NULL(pointer)
/* Строки / память */
TEST_ASSERT_EQUAL_STRING(expected, actual)
TEST_ASSERT_EQUAL_MEMORY(expected, actual, len)
TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, len)
/* Явный провал / пропуск */
TEST_FAIL_MESSAGE("причина")
TEST_IGNORE_MESSAGE("в процессе")
```
---
## Справочник: fff-фейки
### Объявление
```c
FAKE_VOID_FUNC(func, ArgType1, ArgType2); /* void-функция */
FAKE_VALUE_FUNC(RetType, func, ArgType1, ArgType2);/* с возвращаемым значением */
FAKE_VOID_FUNC_VARARG(func, const char *, ...); /* variadic */
```
### Управление поведением
```c
/* Фиксированное возвращаемое значение */
func_fake.return_val = kStatus_Fail;
/* Последовательность значений */
status_t seq[] = {kStatus_Success, kStatus_Success, kStatus_Fail};
SET_RETURN_SEQ(func, seq, 3);
/* Кастомная реализация — высший приоритет, перекрывает return_val */
func_fake.custom_fake = my_impl;
```
### Проверка вызовов
```c
TEST_ASSERT_EQUAL(2, func_fake.call_count); /* сколько раз вызвана */
TEST_ASSERT_EQUAL(expected, func_fake.arg0_val); /* аргумент последнего вызова */
TEST_ASSERT_EQUAL_PTR(func, fff.call_history[0]); /* порядок вызовов */
TEST_ASSERT_EQUAL(0, func_fake.call_count); /* не была вызвана */
```
### Сброс в setUp
```c
void setUp(void)
{
RESET_FAKE(func_a); /* сбрасывает счётчик, историю, return_val, custom_fake */
RESET_FAKE(func_b);
FFF_RESET_HISTORY(); /* сбрасывает глобальную историю порядка вызовов */
}
```
---
## Ловушки
**Dangling pointer из `arg_history[]`.**
`arg_history[]` хранит указатели, не копии. Если функция получала указатель на стековую переменную — после возврата это UB. Использовать `custom_fake` с копированием по значению:
```c
static gpio_pin_config_t s_captured;
static void capture(GPIO_Type *base, uint32_t pin, const gpio_pin_config_t *cfg)
{
s_captured = *cfg; /* копия по значению пока стек ещё жив */
}
void setUp(void) {
RESET_FAKE(GPIO_PinInit);
GPIO_PinInit_fake.custom_fake = capture;
bsp_led_init();
}
void test_init_output(void) {
TEST_ASSERT_EQUAL(kGPIO_DigitalOutput, s_captured.direction);
}
```
**`static` функции не мокаются.**
FFF не видит `static` функции снаружи translation unit. Решение — compile-time seam:
```c
#ifdef UNIT_TEST
void internal_fn(void); /* тест подставит свою реализацию */
#else
static void internal_fn(void) { ... }
#endif
```
**Отсутствие стандартных хедеров в BSP.**
BSP-модули должны явно включать `<stdint.h>`, `<stdbool.h>`, `<stddef.h>` — не полагаться на транзитивное подтягивание через NXP SDK. На хосте этот транзит отсутствует, компиляция упадёт с `undeclared identifier 'size_t'`.
---
## Полный цикл
```bash
# 1. Создать тестовый файл
tests/host/<n>/test_<n>.c
# 2. Создать stub-хедер (если категория B и stub не существует)
tests/host/mocks/fsl_<driver>.h
# 3. Добавить вызов add_host_test() в
tests/host/CMakeLists.txt
# 4. Добавить "test_<n>" в targets в
CMakePresets.json ← host-debug-build и host-release-build
# 5. Запустить в devcontainer
just build::test-host
```
---
## Чеклист
```
[ ] Определена категория (A или B)
[ ] tests/host/<n>/test_<n>.c — тест с main(), setUp(), tearDown()
[ ] tests/host/mocks/fsl_<driver>.h — stub (только категория B, если нет)
[ ] tests/host/CMakeLists.txt — add_host_test(NAME test_<n> ...)
[ ] CMakePresets.json — добавить test_<n> в host-debug-build
[ ] just build::test-host — зелёный прогон
```

View file

@ -3,10 +3,9 @@
# =============================================================================
set(TARGET_NAME test_host_uart)
set(BSP_GENERATED ${CMAKE_SOURCE_DIR}/bsp/generated)
add_executable(${TARGET_NAME} main.c ${BSP_GENERATED}/clock_config.c
${BSP_GENERATED}/startup/startup_MIMXRT1052.S)
${BSP_STARTUP_FILE} ${BSP_SYSCALLS_FILE})
target_compile_definitions(${TARGET_NAME}
PRIVATE BSP_UART_HOST_RX_BUFFER_SIZE=512)

View file

@ -100,11 +100,19 @@ int main(void)
bsp_tick_init();
bsp_led_init();
bsp_uart_host_init(CLI_BAUD_RATE);
/* pytest ждёт эту строку как сигнал готовности */
bsp_uart_host_write_str("READY\r\n");
bsp_led_on(LED_HEARTBEAT);
/* Шлём READY каждые 200 мс пока хост не откроет порт и не пришлёт байт.
* Как только в RX-буфере что-то появится переходим в основной цикл.
* Это убирает race condition между загрузкой ELF и открытием UART. */
while (bsp_uart_host_rx_available() == 0U)
{
bsp_uart_host_write_str("READY\r\n");
bsp_led_toggle(LED_APP);
bsp_delay(200U);
}
bsp_led_off(LED_APP);
static uint8_t s_line_buf[CLI_LINE_MAX];
for (;;)

View file

@ -1,12 +1,12 @@
"""
conftest.py pytest-фикстуры для HIL-тестов MIMXRT1052.
Вся работа с железом делегирована pyocd_utils.
Конфигурация читается из env_config (приоритет: env > .env > default).
"""
from __future__ import annotations
import logging
import os
import time
from pathlib import Path
from typing import Generator
@ -14,28 +14,19 @@ from typing import Generator
import pytest
import serial
import env_config as cfg
from pyocd_utils import flexram_init, load_elf, open_target, run_from_vectors
log = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
# Конфигурация — переопределяется через переменные окружения
# ---------------------------------------------------------------------------
_REPO_ROOT = Path(__file__).resolve().parents[3]
_BUILD_DIR = Path(os.environ.get("HIL_BUILD_DIR",
_REPO_ROOT / "build" / "target-debug"))
_VCOM_PORT = os.environ.get("HIL_VCOM_PORT", "/dev/tty.usbmodemGUXFBWDJBWTGQ3")
_VCOM_BAUD = int(os.environ.get("HIL_VCOM_BAUD", "115200"))
_READY_TIMEOUT = float(os.environ.get("HIL_READY_TIMEOUT", "5.0"))
# ---------------------------------------------------------------------------
# CLI-опции pytest
# CLI-опции pytest (перекрывают .env и os.environ)
# ---------------------------------------------------------------------------
def pytest_addoption(parser: pytest.Parser) -> None:
parser.addoption("--elf", default=None, help="Путь к .elf файлу")
parser.addoption("--vcom", default=_VCOM_PORT, help="VCOM-порт MCU-Link")
parser.addoption("--no-load", action="store_true", help="ELF уже запущен")
parser.addoption("--elf", default=None, help="Путь к .elf файлу")
parser.addoption("--vcom", default=cfg.VCOM_PORT, help="VCOM-порт MCU-Link")
parser.addoption("--no-load", action="store_true", help="ELF уже запущен")
# ---------------------------------------------------------------------------
@ -49,7 +40,7 @@ def _load_elf(request: pytest.FixtureRequest, default_elf: Path) -> None:
elf = Path(request.config.getoption("--elf") or default_elf)
assert elf.exists(), f"ELF не найден: {elf}"
with open_target() as target:
with open_target(frequency=cfg.PYOCD_FREQUENCY) as target:
flexram_init(target)
load_elf(target, str(elf))
run_from_vectors(target)
@ -60,16 +51,12 @@ def _load_elf(request: pytest.FixtureRequest, default_elf: Path) -> None:
# ---------------------------------------------------------------------------
# Фикстуры загрузки (scope=module — один раз на файл с тестами)
# ---------------------------------------------------------------------------
@pytest.fixture(scope="module")
def loaded_firmware_test(request: pytest.FixtureRequest) -> None:
_load_elf(request, _BUILD_DIR / "firmware/test/firmware_test.elf")
@pytest.fixture(scope="module")
def loaded_host_uart(request: pytest.FixtureRequest) -> None:
_load_elf(
request,
_BUILD_DIR / "tests/target/host_uart/test_host_uart.elf",
Path(cfg.BUILD_DIR) / "tests/target/host_uart/test_host_uart.elf",
)
@ -79,21 +66,21 @@ def loaded_host_uart(request: pytest.FixtureRequest) -> None:
@pytest.fixture(scope="module")
def uart(
request: pytest.FixtureRequest,
loaded_host_uart, # гарантирует порядок: сначала загрузка
loaded_host_uart,
) -> Generator[serial.Serial, None, None]:
port = request.config.getoption("--vcom")
log.info("UART %s @ %d baud", port, _VCOM_BAUD)
log.info("UART %s @ %d baud", port, cfg.VCOM_BAUD)
ser = serial.Serial(
port=port,
baudrate=_VCOM_BAUD,
baudrate=cfg.VCOM_BAUD,
timeout=2.0,
write_timeout=1.0,
)
# Ждём "READY" — прошивка отправляет его один раз при старте
deadline = time.monotonic() + _READY_TIMEOUT
# Ждём READY
deadline = time.monotonic() + cfg.READY_TIMEOUT
ready = False
while time.monotonic() < deadline:
line = ser.readline().decode("ascii", errors="replace").strip()
@ -105,8 +92,8 @@ def uart(
if not ready:
ser.close()
pytest.fail(
f"Прошивка не отправила READY за {_READY_TIMEOUT} с. "
"Проверьте VCOM-порт и bsp_uart_host_init()."
f"Прошивка не отправила READY за {cfg.READY_TIMEOUT} с "
"проверьте VCOM-порт и bsp_uart_host_init()."
)
ser.reset_input_buffer()

26
tools/hil/env_config.py Normal file
View file

@ -0,0 +1,26 @@
"""
env_config.py конфигурация HIL из переменных окружения.
При запуске через just (set dotenv-load + set export) все переменные
из корневого .env автоматически попадают в os.environ до запуска pytest.
При прямом запуске pytest (без just) выставить переменные вручную
или через `export HIL_VCOM_PORT=...` перед запуском.
"""
from __future__ import annotations
import os
from pathlib import Path
_REPO_ROOT = Path(__file__).resolve().parents[2]
def _get(key: str, default: str) -> str:
return os.environ.get(key, default)
BUILD_DIR: str = _get("HIL_BUILD_DIR",
str(_REPO_ROOT / "build" / "target-debug"))
VCOM_PORT: str = _get("HIL_VCOM_PORT", "/dev/ttyACM0")
VCOM_BAUD: int = int(_get("HIL_VCOM_BAUD", "115200"))
READY_TIMEOUT: float = float(_get("HIL_READY_TIMEOUT", "5.0"))
PYOCD_FREQUENCY: int = int(_get("HIL_PYOCD_FREQUENCY", "1000000"))

View file

@ -1,7 +1,7 @@
[project]
name = "hil"
version = "0.1.0"
description = "Add your description here"
description = "HIL-тесты для MIMXRT1052 — pytest + pyOCD + pyserial"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
@ -23,4 +23,4 @@ markers = [
log_cli = true
log_cli_level = "INFO"
log_format = "%(asctime)s [%(levelname)-8s] %(name)s: %(message)s"
log_date_format = "%H:%M:%S"
log_date_format = "%H:%M:%S"

1334
tools/host/uv.lock Normal file

File diff suppressed because it is too large Load diff