From 310a8d6bc9f060b149cc1b08540d9dc769cd72bf Mon Sep 17 00:00:00 2001 From: Dmitry Akimov Date: Thu, 12 Mar 2026 13:39:28 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=20=D0=9D=D0=B0=D1=87=D0=B0=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D1=8B=D0=B9=20=D1=8D=D1=82=D0=B0=D0=BF=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B7=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B5=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=87=D0=B5=D0=B3=D0=BE=20=D0=BE=D0=BA?= =?UTF-8?q?=D1=80=D1=83=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B7=D0=B0=D0=BA?= =?UTF-8?q?=D0=BE=D0=BD=D1=87=D0=B5=D0=BD=20-=20=D0=BF=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=BD=D0=B0=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D1=81=D0=BF=D0=BE=D1=81=D0=BE=D0=B1=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BE=D0=BA=D1=80=D1=83=D0=B6=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20(host=20+=20=D0=B2=20=D0=BA=D0=BE=D0=BD=D1=82=D0=B5?= =?UTF-8?q?=D0=B9=D0=BD=D0=B5=D1=80=D0=B5)=20=D0=BD=D0=B0=20=D0=BD=D0=B5?= =?UTF-8?q?=D1=81=D0=BA=D0=BE=D0=BB=D1=8C=D0=BA=D0=B8=D1=85=20=D1=85=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D1=85=20(Win,=20Mac)=20-=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BD=D0=B5=20=D0=B7=D0=B0=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=BD=D1=83=D1=82=D1=8B=20=D0=BC=D0=BE=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D1=8B=20=D1=81=20ci,=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 29 +++++++++++ .gitattributes | 2 + .gitignore | 3 +- HOW_TO_FLASH.md | 104 ++++++++++++++++++++++++++++++++++++++ Justfile | 6 +-- README.md | 23 ++++++--- docs/DEV_ARCH.md | 84 +++++++++++++++++++++++++----- firmware/test/main.c | 2 +- just/build.just | 31 +++++++----- just/host.just | 17 ++++--- tools/host/flash_usb.py | 76 ++++++++++++++++------------ tools/host/pyproject.toml | 1 + 12 files changed, 302 insertions(+), 76 deletions(-) create mode 100644 HOW_TO_FLASH.md diff --git a/.env.example b/.env.example index e69de29..42f5b33 100644 --- a/.env.example +++ b/.env.example @@ -0,0 +1,29 @@ +# ============================================================================= +# .env — единый источник конфигурации проекта +# Читается: just (set dotenv-load) → экспортируется в окружение (set export) +# → наследуется uv run python3 автоматически +# ============================================================================= + +# --- Hardware --- +BOARD=MIMXRT1052 + +# --- USB VID:PID (NXP) --- +# Используются: just host.just (check-deps, udev) + flash_usb.py +BOOTROM_VID=1fc9 +BOOTROM_PID=0130 +FLASHLOADER_VID=15a2 +FLASHLOADER_PID=0073 + +# --- Paths --- +# BUILD_DIR и TOOLS_DIR задаются абсолютно в корневом justfile +# через justfile_directory(), здесь можно переопределить если нужно +# BUILD_DIR=/custom/path/build + +# --- Build --- +CMAKE_GENERATOR=Ninja + +# --- Debug / SWD --- +OPENOCD_INTERFACE=cmsis-dap.cfg +TARGET_CFG=target/imxrt.cfg +GDB_PORT=3333 +GDB_EXECUTABLE=gdb-multiarch \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index dfe0770..903c009 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ # Auto detect text files and perform LF normalization * text=auto +.env text eol=lf +.env.example text eol=lf diff --git a/.gitignore b/.gitignore index 3f6a872..2679cdf 100644 --- a/.gitignore +++ b/.gitignore @@ -68,5 +68,6 @@ compile_commands.json __pycache__/ *.pyc .env - +tools/host/.venv-host/ +tools/host/.venv-host-win/ diff --git a/HOW_TO_FLASH.md b/HOW_TO_FLASH.md new file mode 100644 index 0000000..f73f209 --- /dev/null +++ b/HOW_TO_FLASH.md @@ -0,0 +1,104 @@ +# HOW TO FLASH + +Прошивка выполняется **на хосте** (вне devcontainer) через USB ROM (Serial Download Protocol). + +--- + +## 1. Перевод платы в SDP-режим + +```bash +1. BOOT_MOD_1 → 3V3 +2. Reset +3. Подключить USB к хосту + → плата определяется как VID:PID 1FC9:0130 +``` + +После прошивки — вернуть в нормальный режим: + +```bash +BOOT_MOD_1 → GND → Reset +``` + +--- + +## 2. Подготовить HAB-образ (внутри devcontainer) + +HAB-образ собирается из ELF-файла командой `nxpimage`. Выполнять в терминале VSCode: + +```bash +just build::hab-firmware-test-debug # → build/Debug/firmware_test_hab.bin +just build::hab-firmware-test-release # → build/Release/firmware_test_hab.bin +just build::hab-bootloader-release # → build/Release/bootloader_hab.bin +just build::hab-app-release # → build/Release/app_hab.bin +just build::hab-all-release # все три Release за один раз +``` + +--- + +## 3. Прошивка (хостовый терминал) + +### Запись во Flash + +```bash +# Основной рецепт: just host::flash +just host::flash firmware_test debug # разработка, итерации с отладчиком +just host::flash firmware_test release # проверить как будет на сервере +just host::flash bootloader release +just host::flash app release +``` + +### Загрузка в RAM (без записи во Flash) + +Быстро, не изнашивает Flash. Плата стартует сразу после загрузки. + +```bash +just host::flash-ram firmware_test # default: debug +just host::flash-ram firmware_test debug +just host::flash-ram firmware_test release +``` + +### Быстрые алиасы + +```bash +just flash # = just host::flash firmware_test debug +just host::flash-test-debug # то же +just host::flash-test-release +just host::flash-production # bootloader release + app release (с подтверждением) +``` + +--- + +## 4. Диагностика + +```bash +just host::scan # найти подключённые NXP USB-устройства +just host::sdp-status # проверить связь с BootROM (плата в SDP-режиме) +just host::flashloader-status # проверить Flashloader (после jump-address) +just host::check-deps # проверить версии just / uv / docker +``` + +--- + +## 5. Что происходит при прошивке + +```bash +Плата в SDP-режиме (1FC9:0130) + │ + ├── sdphost: загрузить ivt_flashloader.bin в RAM (0x20001C00) + └── sdphost: jump-address → Flashloader поднимается как 15A2:0073 + │ + ├── configure-memory (0xC0000007) — инициализация FlexSPI NOR + ├── flash-erase-region 0x60000000 + ├── configure-memory (0xF000000F) — запись FCB в 0x60000000 + ├── write-memory 0x60001000 ← HAB-образ + └── reset +``` + +--- + +## 6. Производственный сценарий (сервер) + +```bash +just host::incoming # firmware_test release → HIL-тесты периферии +just host::production # bootloader release + app release +``` diff --git a/Justfile b/Justfile index 98af3f0..b69de58 100644 --- a/Justfile +++ b/Justfile @@ -20,9 +20,9 @@ set dotenv-load # === Общие переменные (доступны во всех модулях через export) === BOARD := env('BOARD', 'MIMXRT1052') -BUILD_DIR := env('BUILD_DIR', 'build') -TOOLS_DIR := env('TOOLS_DIR', 'tools/host') -CACHE_DIR := ".cache" +BUILD_DIR := env('BUILD_DIR', justfile_directory() / 'build') +TOOLS_DIR := env('TOOLS_DIR', justfile_directory() / 'tools/host') +CACHE_DIR := justfile_directory() / '.cache' # === Модули === # Каждый модуль — это namespace с изолированными рецептами diff --git a/README.md b/README.md index 397b3e4..9b4bd44 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,11 @@ ```bash / ├── .devcontainer/ # VSCode Devcontainer — единое окружение для всех разработчиков +│ ├── Dockerfile +│ └── devcontainer.json +├── .vscode/ +│ ├── launch.json +│ └── tasks.json # UI для just build::* (внутри devcontainer) ├── bsp/ # Board Support Package │ └── generated/ # Сгенерировано NXP Config Tools (Pins + Clocks Tool) │ ├── TFT_Board.mex # Источник истины конфигурации пинов и тактирования @@ -40,27 +45,31 @@ ├── tests/ # Тесты (host + target) │ ├── host/ # Unit/интеграционные тесты, запускаемые на хосте │ ├── target/ # Тесты периферии, запускаемые на таргете -│ └── HostTestingGuide.md +│ ├── HostTestingGuide.md +│ └── README.md ├── tools/ │ └── host/ # Инструменты для работы с таргетом │ ├── flash_usb.py # Прошивка через USB ROM (nxp-spsdk / blhost) │ ├── hab/ # Утилиты и гайд по HAB (Secure Boot) -│ └── dcd/ # Device Configuration Data +│ ├── dcd/ # Device Configuration Data +│ ├── pyproject.toml +│ └── uv.lock ├── just/ # Just-модули (автоматизация) │ ├── build.just # Сборка, тесты, HAB-образы (devcontainer) │ ├── host.just # Прошивка, bootstrap, HIL (хост) │ └── ci.just # CI/CD пайплайны -├── scripts/ -│ └── bootstrap.sh # Первичная настройка окружения (уровень 0) ├── 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 и др.) +├── .env.example # Шаблон .env для новых разработчиков +├── bootstrap.sh # Первичная настройка окружения (уровень 0) ├── CMakeLists.txt # Корневой CMake ├── CMakePresets.json # Пресеты сборки (Release/Debug/Host) -├── Justfile # Точка входа для команд (модули: build, host, ci) +├── justfile # Точка входа для команд (модули: build, host, ci) └── README.md ``` @@ -142,7 +151,7 @@ git clone cd tft_manufacture_test # Инициализация хоста (один раз) -sudo chmod +x bootstrap.sh +# Устанавливает just и uv, затем настраивает окружение ./bootstrap.sh # Открыть в VSCode → Reopen in Container @@ -158,3 +167,5 @@ just flash # прошивка через USB ROM > Подробнее о прошивке — [HOW_TO_FLASH.md](HOW_TO_FLASH.md) > Подробнее об окружении разработки — [docs/DEV_ARCH.md](docs/DEV_ARCH.md) +> +> \ No newline at end of file diff --git a/docs/DEV_ARCH.md b/docs/DEV_ARCH.md index 8cddf52..4836e41 100644 --- a/docs/DEV_ARCH.md +++ b/docs/DEV_ARCH.md @@ -34,7 +34,8 @@ CI использует те же команды что и локальная р │ ├── docker ← управление devcontainer │ ├── git ← работа с репозиторием │ ├── uv + spsdk ← прошивка платы (flash_usb.py, sdphost, blhost) -│ │ venv: tools/host/.venv-host +│ │ venv: tools/host/.venv-host (Linux/macOS) +│ │ tools/host/.venv-host-win (Windows) │ ├── JLinkGDBServer / probe-rs ← сервер отладки (USB → TCP :2331) │ └── VSCode ← IDE (Dev Containers extension) │ @@ -48,7 +49,7 @@ CI использует те же команды что и локальная р │ ├── cmake-format ← форматирование CMakeLists │ ├── just ← запуск задач внутри контейнера (just build::*) │ ├── uv + spsdk ← сборка HAB-образов (только nxpimage) -│ │ venv: tools/host/.venv-container +│ │ venv: tools/host/.venv │ └── Unity + fff ← фреймворки host-тестов │ └── Плата TFT (IMXRT1052) — на столе у разработчика @@ -83,7 +84,45 @@ CI использует те же команды что и локальная р Версия `spsdk` зафиксирована в `tools/host/uv.lock` — все три места используют одну и ту же версию. +--- +## 3.1 Конфигурация проекта — `.env` + +`.env` в корне репозитория — единый источник конфигурации для всего стека. + +```ini +# USB VID:PID (NXP) +BOOTROM_VID=1fc9 # используется: host.just (udev-правила) + flash_usb.py +BOOTROM_PID=0130 +FLASHLOADER_VID=15a2 +FLASHLOADER_PID=0073 + +# GDB / отладка +GDB_PORT=3333 +GDB_EXECUTABLE=gdb-multiarch +OPENOCD_INTERFACE=cmsis-dap.cfg +TARGET_CFG=target/imxrt.cfg +``` + +**Как значения попадают в инструменты:** + +```bash +.env + │ + ├─▶ just (set dotenv-load + set export) + │ └─▶ just-рецепты видят переменные напрямую: {{BOOTROM_VID}} + │ └─▶ uv run flash_usb.py ← наследует окружение автоматически + │ └─▶ os.environ.get("BOOTROM_VID") ← без python-dotenv + │ + └─▶ .vscode/launch.json ← через ${env:GDB_PORT} если нужно +``` + +Аппаратные константы (`FLASH_BASE`, `HAB_OFFSET`, `FLEXSPI_OPTION_VALUE` и т.д.) +намеренно оставлены в коде `flash_usb.py` — это не конфигурация, а часть +протокола прошивки IMXRT1052, менять их незачем. + +`.env.example` — шаблон без значений, коммитится в репозиторий. +`.env` — реальные значения, в `.gitignore` (если содержит секреты), иначе тоже коммитится. --- @@ -91,16 +130,19 @@ CI использует те же команды что и локальная р ```bash / -├── Justfile ← корневой оркестратор; модули: build, host, ci +├── justfile ← корневой оркестратор; модули: build, host, ci +├── bootstrap.sh ← уровень 0: устанавливает just+uv → just host::bootstrap +├── .env ← конфигурация проекта (VID:PID, пути, GDB) +├── .env.example ← шаблон для новых разработчиков ├── just/ │ ├── build.just ← devcontainer: сборка, тесты, HAB │ ├── host.just ← хост: прошивка, bootstrap, HIL │ └── ci.just ← CI/CD пайплайны -├── scripts/ -│ └── bootstrap.sh ← уровень 0: just → just host::bootstrap ├── .devcontainer/ │ ├── Dockerfile │ └── devcontainer.json +├── .vscode/ +│ └── tasks.json ← UI для just build::* (внутри devcontainer) ├── tools/ │ └── host/ │ ├── hab/ @@ -113,11 +155,9 @@ CI использует те же команды что и локальная р │ ├── dcd/ │ │ ├── dcd.bin │ │ └── ivt_flashloader.bin -│ ├── flash_usb.py +│ ├── flash_usb.py ← читает конфиг из env (VID:PID, BUILD_DIR) │ ├── pyproject.toml │ └── uv.lock -├── .vscode/ -│ └── tasks.json ← UI для just build::* (внутри devcontainer) ├── CMakePresets.json ├── cmake/ ├── sdk/ @@ -157,12 +197,19 @@ bootstrap.sh (уровень 0) ├── определить платформу (Linux / macOS / Windows Git Bash) │ uname: MINGW64_NT-... → windows, Linux → linux, Darwin → macos │ -├── проверить just (semver без sort -V — работает в Git Bash) +├── проверить/установить 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 +│ └── exec just host::bootstrap │ ├── [1/3] check-deps @@ -173,11 +220,15 @@ bootstrap.sh (уровень 0) │ /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 на хосте) + uv sync в tools/host/ + venv: .venv-host (Linux/macOS) · .venv-host-win (Windows) + разделение нужно: devcontainer создаёт .venv с Linux-симлинками, + Windows не может их удалить без прав администратора SHA-256 uv.lock кэшируется в .cache/ повторный вызов мгновенный если lockfile не изменился ``` @@ -191,7 +242,7 @@ bootstrap.sh (уровень 0) `postCreateCommand` выполняется автоматически при поднятии контейнера: ```bash -cd tools/host && uv sync && # venv: .venv-container +cd tools/host && uv sync && # venv: tools/host/.venv (Linux, внутри контейнера) cd ../.. && cmake --preset host-debug && cmake --preset Debug @@ -266,7 +317,7 @@ buildPresets (host): ### 7.2 Типичная сессия разработки -``` +```bash Открыть VSCode → работать в devcontainer весь день │ ├── писать код @@ -311,7 +362,7 @@ buildPresets (host): ### 8.1 Перевод платы в SDP-режим -``` +```bash 1. BOOT_MOD_1 → 3V3 2. Reset 3. Подключить USB к хосту @@ -347,7 +398,12 @@ 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) diff --git a/firmware/test/main.c b/firmware/test/main.c index c1b852c..5366beb 100644 --- a/firmware/test/main.c +++ b/firmware/test/main.c @@ -30,7 +30,7 @@ int main(void) { BOARD_Init(); GPIO_PinWrite(BOARD_INITPINS_UserLed1_PORT, BOARD_INITPINS_UserLed1_PIN, 0); - GPIO_PinWrite(BOARD_INITPINS_UserLed2_PORT, BOARD_INITPINS_UserLed2_PIN, 0); + // GPIO_PinWrite(BOARD_INITPINS_UserLed2_PORT, BOARD_INITPINS_UserLed2_PIN, 0); while (1) { } diff --git a/just/build.just b/just/build.just index 65bf7e7..bd7b4af 100644 --- a/just/build.just +++ b/just/build.just @@ -117,20 +117,22 @@ test-host-release: _configure-host-release hab-firmware-test-debug: build-firmware-test-debug #!/usr/bin/env bash set -euo pipefail + 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)" [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" 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 ─────────────────────────────────────────────────────────────── @@ -138,20 +140,22 @@ hab-firmware-test-release: build-firmware-test-release hab-bootloader-debug: build-bootloader-debug #!/usr/bin/env bash set -euo pipefail + 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)" [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" 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 ─────────────────────────────────────────────────────────────────────── @@ -159,20 +163,22 @@ hab-bootloader-release: build-bootloader-release hab-app-debug: build-app-debug #!/usr/bin/env bash set -euo pipefail + 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)" [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" 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)" # ── все образы сразу ───────────────────────────────────────────────────────── @@ -190,14 +196,13 @@ hab-all-release: hab-firmware-test-release hab-bootloader-release hab-app-releas hab-verify project="firmware_test" type="release": #!/usr/bin/env bash set -euo pipefail - ROOT="$(pwd)" case "{{project}}-{{type}}" in - firmware_test-debug) BIN="${ROOT}/{{BUILD_DIR}}/Debug/firmware_test_hab.bin" ;; - firmware_test-release) BIN="${ROOT}/{{BUILD_DIR}}/Release/firmware_test_hab.bin" ;; - bootloader-debug) BIN="${ROOT}/{{BUILD_DIR}}/Debug/bootloader_hab.bin" ;; - bootloader-release) BIN="${ROOT}/{{BUILD_DIR}}/Release/bootloader_hab.bin" ;; - app-debug) BIN="${ROOT}/{{BUILD_DIR}}/Debug/app_hab.bin" ;; - app-release) BIN="${ROOT}/{{BUILD_DIR}}/Release/app_hab.bin" ;; + 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" diff --git a/just/host.just b/just/host.just index 82ddef3..bb8f994 100644 --- a/just/host.just +++ b/just/host.just @@ -15,6 +15,9 @@ TOOLS_DIR := env('TOOLS_DIR', justfile_directory() / 'tools/host') CACHE_DIR := env('CACHE_DIR', justfile_directory() / '.cache') +_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" @@ -114,7 +117,7 @@ check-deps: check_tool "just" "just" "{{JUST_MIN}}" check_tool "uv" "uv" "{{UV_MIN}}" - check_tool "docker" "docker" "{{DOCKER_MIN}}" version --format '{{{{.Client.Version}}}}' + check_tool "docker" "docker" "{{DOCKER_MIN}}" if [[ "${PLATFORM}" == "linux" ]]; then echo "" @@ -253,7 +256,7 @@ flash project type="release": echo " Valid: debug, release" exit 1 ;; esac - cd "{{TOOLS_DIR}}" && uv run python3 flash_usb.py \ + cd "{{TOOLS_DIR}}" && UV_PROJECT_ENVIRONMENT={{_venv}} uv run python flash_usb.py \ --firmware "{{project}}" --build-type "${BUILD_TYPE}" # Загрузить образ в RAM без записи во Flash. @@ -271,7 +274,7 @@ flash-ram project type="debug": echo " Valid: debug, release" exit 1 ;; esac - cd "{{TOOLS_DIR}}" && uv run python3 flash_usb.py \ + cd "{{TOOLS_DIR}}" && UV_PROJECT_ENVIRONMENT={{_venv}} uv run python flash_usb.py \ --firmware "{{project}}" --build-type "${BUILD_TYPE}" --ram-only # Псевдонимы для частых сценариев ──────────────────────────────────────────── @@ -302,7 +305,7 @@ incoming: flash-test-release #!/usr/bin/env bash set -euo pipefail echo " ▶ Running HIL tests (CAN, UART, SDRAM, SPI Flash)..." - # cd tools/hil && uv run python3 run_hil.py <- раскомментить когда готово + # cd tools/hil && UV_PROJECT_ENVIRONMENT={{_venv}} uv run python run_hil.py <- раскомментить когда готово echo " ⚠️ HIL tests not yet implemented" # Финальная прошивка: загрузчик + основная прошивка (Release) @@ -317,17 +320,17 @@ production: flash-production # Найти подключённые NXP USB-устройства [group('util')] scan: - cd "{{TOOLS_DIR}}" && uv run nxpdevscan + cd "{{TOOLS_DIR}}" && UV_PROJECT_ENVIRONMENT={{_venv}} uv run nxpdevscan # Проверить связь с BootROM через SDP (плата в SDP-режиме) [group('util'), no-cd] sdp-status: - cd "{{TOOLS_DIR}}" && 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) [group('util')] flashloader-status: - cd "{{TOOLS_DIR}}" && 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 diff --git a/tools/host/flash_usb.py b/tools/host/flash_usb.py index 8564e95..c0c4fc5 100644 --- a/tools/host/flash_usb.py +++ b/tools/host/flash_usb.py @@ -17,35 +17,56 @@ flash_usb.py — прошивка MIMXRT1052 через USB (BootROM SDP → Fla → blhost стирает нужный регион Flash → blhost пишет HAB образ начиная с 0x60002000 → blhost reset + +Конфигурация: + Переменные окружения (задаются в .env, экспортируются через just): + BOOTROM_VID — VID BootROM SDP (default: 1fc9) + BOOTROM_PID — PID BootROM SDP (default: 0130) + FLASHLOADER_VID — VID Flashloader (default: 15a2) + FLASHLOADER_PID — PID Flashloader (default: 0073) + BUILD_DIR — путь к директории сборки (default: /build) """ import argparse +import os import subprocess import sys import time from pathlib import Path -# ─── Константы ──────────────────────────────────────────────────────────────── -SCRIPT_DIR = Path(__file__).parent.resolve() -REPO_ROOT = SCRIPT_DIR.parent.parent -BUILD_DIR = REPO_ROOT / "build" -FLASHLOADER = SCRIPT_DIR / "dcd" / "ivt_flashloader.bin" +# ─── Пути ───────────────────────────────────────────────────────────────────── +SCRIPT_DIR = Path(__file__).parent.resolve() +REPO_ROOT = SCRIPT_DIR.parent.parent +FLASHLOADER = SCRIPT_DIR / "dcd" / "ivt_flashloader.bin" -# USB VID:PID -SDP_USB = "0x1FC9,0x0130" # BootROM Serial Download Protocol -BLHOST_USB = "0x15A2,0x0073" # Flashloader запущен +# BUILD_DIR: берём из окружения (just экспортирует из .env как абсолютный путь), +# fallback — рассчитываем от расположения скрипта +BUILD_DIR = Path(os.environ.get("BUILD_DIR", str(REPO_ROOT / "build"))) +# ─── USB VID:PID — из окружения (.env → just set export → uv run) ───────────── +def _usb(vid_key: str, vid_default: str, pid_key: str, pid_default: str) -> str: + + vid = os.environ.get(vid_key, vid_default).strip().upper().lstrip("0X") + pid = os.environ.get(pid_key, pid_default).strip().upper().lstrip("0X") + return f"0x{vid},0x{pid}" + +SDP_USB = _usb("BOOTROM_VID", "1fc9", "BOOTROM_PID", "0130") # BootROM SDP +BLHOST_USB = _usb("FLASHLOADER_VID","15a2", "FLASHLOADER_PID","0073") # Flashloader + +# ─── Аппаратные константы (часть логики прошивки, не конфигурация) ──────────── # FlexSPI NOR config option word: 0xC0000007 # bits[31:28]=0xC — tag (QuadSPI NOR) # bits[3:0]=0x7 — option size FLEXSPI_OPTION_ADDR = "0x2000" FLEXSPI_OPTION_VALUE = "0xC0000007" -FLEXSPI_MEMORY_ID = "9" # FlexSPI NOR memory interface ID +FLEXSPI_MEMORY_ID = "9" # FlexSPI NOR memory interface ID + +# Option word для записи FCB: tag=0xF → Write FCB command +FLEXSPI_FCB_VALUE = "0xF000000F" # Flash layout -FLASH_BASE = 0x60000000 -HAB_OFFSET = 0x1000 # IVT offset: HAB binary starts with IVT at byte 0 - # write address = FLASH_BASE + ivtOffset = 0x60001000 +FLASH_BASE = 0x60000000 +HAB_OFFSET = 0x1000 # IVT offset: write address = FLASH_BASE + HAB_OFFSET def run(cmd: list[str], check: bool = True) -> subprocess.CompletedProcess: @@ -64,7 +85,7 @@ def step(msg: str) -> None: def wait_for_flashloader(timeout: int = 10) -> bool: - """Ждём пока Flashloader поднимется (VID:PID 15A2:0073)""" + """Ждём пока Flashloader поднимется""" print(f"\n Ожидание Flashloader (до {timeout}с)...", end="", flush=True) for i in range(timeout): time.sleep(1) @@ -82,7 +103,6 @@ def wait_for_flashloader(timeout: int = 10) -> bool: def load_flashloader() -> None: """Загружает Flashloader через SDP если ещё не запущен""" - # Проверяем — вдруг уже запущен result = subprocess.run( ["blhost", "-u", BLHOST_USB, "-j", "--", "get-property", "1", "0"], capture_output=True @@ -108,11 +128,6 @@ def load_flashloader() -> None: sys.exit(1) -# Option word для записи FCB в Flash[0x60000000] -# 0xF000000F: tag=0xF → Write FCB command (Flashloader пишет готовый FCB в начало Flash) -FLEXSPI_FCB_VALUE = "0xF000000F" - - def configure_flexspi() -> None: """Инициализирует FlexSPI NOR контроллер через Flashloader""" step("Конфигурация FlexSPI NOR (инициализация контроллера)") @@ -124,7 +139,7 @@ def configure_flexspi() -> None: def write_fcb() -> None: """Записывает Flash Configuration Block в 0x60000000 - + Отдельный шаг после erase! Flashloader генерирует FCB из параметров FlexSPI и пишет его по адресу 0x60000000. Без FCB BootROM не знает как читать Flash. Option word 0xF000000F: tag=0xF → Write FCB command. @@ -144,7 +159,6 @@ def flash(hab_bin: Path, ram_only: bool = False) -> None: if ram_only: step(f"Загрузка в RAM (без записи во Flash): {hab_bin.name}") - # RAM-only: грузим в RAM прямо через SDP без flashloader run(["sdphost", "-u", SDP_USB, "-j", "--", "write-file", f"0x{FLASH_BASE + HAB_OFFSET:08X}", str(hab_bin)]) run(["sdphost", "-u", SDP_USB, "-j", "--", @@ -152,10 +166,9 @@ def flash(hab_bin: Path, ram_only: bool = False) -> None: return load_flashloader() - configure_flexspi() # 0xC0000007 — инициализация FlexSPI контроллера + configure_flexspi() write_addr = f"0x{FLASH_BASE + HAB_OFFSET:08X}" - # Размер стирания: HAB_OFFSET (0x1000) + размер образа, выровнено вверх до 4KB erase_size = ((HAB_OFFSET + hab_bin.stat().st_size + 0xFFF) // 0x1000) * 0x1000 step(f"Прошивка Flash: {hab_bin.name}") @@ -164,14 +177,12 @@ def flash(hab_bin: Path, ram_only: bool = False) -> None: print(f" Адрес: {write_addr}") print(f" Стирание: 0x{FLASH_BASE:08X} .. +{erase_size} байт") - # memoryId=0 — mapped (XIP) адресное пространство run(["blhost", "-u", BLHOST_USB, "--", "flash-erase-region", f"0x{FLASH_BASE:08X}", str(erase_size), "0"]) - write_fcb() # 0xF000000F — запись FCB в 0x60000000 (после erase!) + write_fcb() - # memoryId=0 — mapped (XIP) адресное пространство run(["blhost", "-u", BLHOST_USB, "--", "write-memory", write_addr, str(hab_bin), "0"]) @@ -181,6 +192,9 @@ def flash(hab_bin: Path, ram_only: bool = False) -> None: def main() -> None: + print(f"DEBUG SDP_USB = {repr(SDP_USB)}") + print(f"DEBUG BLHOST_USB = {repr(BLHOST_USB)}") + print(f"DEBUG BUILD_DIR = {repr(BUILD_DIR)}") parser = argparse.ArgumentParser(description="Прошивка MIMXRT1052 через USB") parser.add_argument("--firmware", required=True, choices=["firmware_test", "bootloader", "app"], @@ -192,18 +206,18 @@ def main() -> None: help="Загрузить в RAM без записи во Flash") args = parser.parse_args() - # HAB образ собирается командой: nxpimage hab export -c hab_.yaml - # Выходной файл: build//_hab.bin hab_bin = BUILD_DIR / args.build_type / f"{args.firmware}_hab.bin" print(f"\n{'═'*60}") print(f" MIMXRT1052 Flash Tool") - print(f" Прошивка: {args.firmware} [{args.build_type}]") - print(f" Образ: {hab_bin}") + print(f" Прошивка: {args.firmware} [{args.build_type}]") + print(f" Образ: {hab_bin}") + print(f" SDP USB: {SDP_USB}") + print(f" BL USB: {BLHOST_USB}") print(f"{'═'*60}") flash(hab_bin, ram_only=args.ram_only) if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/tools/host/pyproject.toml b/tools/host/pyproject.toml index 59e2831..6d74ff2 100644 --- a/tools/host/pyproject.toml +++ b/tools/host/pyproject.toml @@ -7,3 +7,4 @@ requires-python = ">=3.10" dependencies = [ "spsdk==3.7.0", ] + From 806b41e0a57a9c1dc5b08274302e0c924d883485 Mon Sep 17 00:00:00 2001 From: Ezra Maccabee Date: Fri, 13 Mar 2026 19:12:35 +0300 Subject: [PATCH 2/3] =?UTF-8?q?#=201.=20=D0=9D=D0=B0=D0=B1=D1=80=D0=BE?= =?UTF-8?q?=D1=81=D0=BE=D0=BA=20=D0=B0=D1=80=D1=85=D0=B8=D1=82=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D1=83=D1=80=D1=8B=20firmware=5Ftest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 - docs/DEV_ARCH.md | 2 +- firmware/test/README.md | 244 ++++++++++++++++++++++++++++++++++++ firmware/test/arch.svg | 268 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 513 insertions(+), 3 deletions(-) create mode 100644 firmware/test/README.md create mode 100644 firmware/test/arch.svg diff --git a/README.md b/README.md index 9b4bd44..2dde742 100644 --- a/README.md +++ b/README.md @@ -167,5 +167,3 @@ just flash # прошивка через USB ROM > Подробнее о прошивке — [HOW_TO_FLASH.md](HOW_TO_FLASH.md) > Подробнее об окружении разработки — [docs/DEV_ARCH.md](docs/DEV_ARCH.md) -> -> \ No newline at end of file diff --git a/docs/DEV_ARCH.md b/docs/DEV_ARCH.md index 4836e41..66324c8 100644 --- a/docs/DEV_ARCH.md +++ b/docs/DEV_ARCH.md @@ -34,7 +34,7 @@ CI использует те же команды что и локальная р │ ├── docker ← управление devcontainer │ ├── git ← работа с репозиторием │ ├── uv + spsdk ← прошивка платы (flash_usb.py, sdphost, blhost) -│ │ venv: tools/host/.venv-host (Linux/macOS) +│ │ venv: tools/host/.venv. (Linux/macOS) │ │ tools/host/.venv-host-win (Windows) │ ├── JLinkGDBServer / probe-rs ← сервер отладки (USB → TCP :2331) │ └── VSCode ← IDE (Dev Containers extension) diff --git a/firmware/test/README.md b/firmware/test/README.md new file mode 100644 index 0000000..b63957b --- /dev/null +++ b/firmware/test/README.md @@ -0,0 +1,244 @@ +# firmware_test — Architecture + +> Target: NXP IMXRT1052CVJ5B +> Версия документа: 0.1 +> Статус: draft + +--- + +## 1. Назначение + +`firmware_test` — входная тестовая прошивка для проверки работоспособности платы при производстве и во время разработки. Запускается напрямую через BootROM (USB Serial Download), без предварительной прошивки загрузчика. После успешного прохождения всех тестов инициирует фазу провижининга (привязка Chip UID к версиям ПО). + +--- + +## 2. Workflow прошивки платы + +```bash +BootROM (USB Serial Download, встроен в IMXRT1052) + ↓ +firmware_test (залит напрямую) + ↓ [тесты прошли, provisioning выполнен] +Флашим: Bootloader + App + ↓ +Ждём heartbeat Bootloader → App + ↓ +Плата принята +``` + +Вариант с предварительной заливкой загрузчика не используется — BootROM является надёжным и всегда доступным recovery-path, не зависящим от состояния Flash. + +--- + +## 3. Высокоуровневая архитектура + +```bash +┌─────────────────────────────────────────────────────┐ +│ firmware_test │ +│ │ +│ USB CDC ──► Protocol ──► Test runner ──► Local UI │ +│ (JSON-lines) (sequencer) (LED+disp) │ +│ │ +│ ┌─────────────────────┐ ┌──────────────────────┐ │ +│ │ self-tests │ │ HIL tests │ │ +│ │ SDRAM QSPI uSD │ │ CAN UART Opto-in │ │ +│ │ RTC Display IR │ │ UART ISO IR burst │ │ +│ └─────────────────────┘ └──────────────────────┘ │ +│ │ +│ HAL / BSP │ +└─────────────────────────────────────────────────────┘ +``` + +--- + +## 4. Компоненты + +### 4.1 USB CDC + +Единственный канал связи с внешним миром. Представляется хосту как виртуальный COM-порт. Инициализируется первым, до запуска тестов. При старте ожидает подключения хоста с таймаутом — если хост не подключился, тесты запускаются автономно. + +### 4.2 Protocol + +Протокол — **JSON-lines**: каждое сообщение является отдельным JSON-объектом, завершённым символом `\n`. Библиотека: cJSON из NXP SDK. + +Направление **хост → плата** (команды): + +```json +{"type":"cmd","cmd":"run_all"} +{"type":"cmd","cmd":"run","id":"sdram"} +{"type":"confirm","id":"display","confirmed":true} +``` + +Направление **плата → хост** (события): + +```json +{"type":"session_start","fw":"0.1.0","target":"IMXRT1052","uptime_ms":0} +{"type":"test_begin","id":"sdram","name":"SDRAM 32MB","critical":true} +{"type":"test_result","id":"sdram","status":"pass","ms":312,"detail":"32MB R/W OK"} +{"type":"confirm_request","id":"display","timeout_ms":15000} +{"type":"abort","reason":"critical_fail","id":"usd"} +{"type":"summary","passed":7,"failed":0,"skipped":2,"aborted":false,"overall":"pass"} +{"type":"provision_ready","chip_uid":"A3F2C1B400E70012"} +{"type":"provision_ack","fw":"1.0.0","bootloader":"1.0.0","recorded":true} +``` + +> **Примечание:** `uptime_ms` вместо Unix timestamp — RTC может быть не инициализирован на новой плате. Хост приклеивает реальное время самостоятельно. + +### 4.3 Test runner + +Центральный компонент. Хранит реестр тест-модулей, управляет порядком запуска, обрабатывает критические сбои, формирует `summary`. + +**Порядок выполнения:** + +1. Self-tests в порядке реестра +2. Проверка критических сбоев — если есть, HIL не запускается +3. HIL tests (только если Firefly подключён и self-tests прошли) +4. Summary report +5. Provisioning (только при `overall == pass`) + +**Интерфейс тест-модуля (`test_module.h`):** + +```cpp +typedef enum { + TEST_STATUS_PASS = 0, + TEST_STATUS_FAIL, + TEST_STATUS_SKIP, +} test_status_t; + +typedef struct { + test_status_t status; + uint32_t duration_ms; + char detail[96]; /* диагностическая строка, опционально */ +} test_result_t; + +typedef struct { + const char *id; /* "sdram", "qspi", "can" — ключ в JSON */ + const char *name; /* "SDRAM 32MB" — для display/лога */ + bool critical; /* abort HIL если FAIL */ + bool requires_hil; /* пропустить если Firefly не готов */ + void (*init)(void); + test_result_t (*run)(void); + void (*deinit)(void); +} test_module_t; +``` + +### 4.4 Local UI + +Отображает текущее состояние тестирования на светодиодах и дисплее. Получает события от Test runner. Дисплей при этом является частью тест-процесса (display_test). + +**LED-паттерны:** + +| Состояние | LED1 | LED2 | +|------------------------|-------------|-------------| +| Тест выполняется | мигает | выкл | +| Все тесты PASS | вкл | выкл | +| Есть FAIL | выкл | вкл | +| Ожидание подтверждения | оба мигают | | + +--- + +## 5. Тест-модули + +### 5.1 Self-tests + +| ID | Название | Critical | Описание | +|------------|------------------|----------|--------------------------------------------------| +| `sdram` | SDRAM 32MB | ✅ | Write/read паттерны по всему объёму | +| `qspi` | QSPI Flash | ✅ | JEDEC ID + запись/чтение тестового сектора | +| `usd` | uSD (SDIO) | ✅ | Mount + R/W тестового файла (SKIP если нет карты)| +| `rtc` | RTC BM8563 | — | I2C presence, set/get time | +| `display` | Display RGB888 | — | R/G/B/W заливки, подтверждение оператором | +| `ir` | IR receiver | — | GPIO idle state HIGH, peripheral init | + +**Display test — логика подтверждения:** + +- Плата посылает `confirm_request` с `timeout_ms: 15000` +- Оператор нажимает **одну** кнопку: PASS или FAIL +- Если кнопка не нажата за 15 секунд — статус `SKIP` (ответственность на операторе) +- Одновременно проверяются обе кнопки — это полноценный тест кнопок + +### 5.2 HIL tests (требуют Firefly AIO-3588Q) + +| ID | Название | Стенд | Описание | +|--------------|------------------|------------------------------|----------------------------------------| +| `can` | CAN | Firefly CAN | Обмен фреймами, full-duplex | +| `uart_ttl` | UART TTL | Firefly UART | Echo паттерн | +| `uart_iso` | UART ISO +24V | Firefly UART + интерф. плата | Только RX, Firefly посылает | +| `opto` | Opto-in +24V | Firefly GPIO + интерф. плата | Все каналы, Firefly дёргает GPIO | +| `ir_hil` | IR burst | Firefly GPIO + IR LED | Приём burst 38 кГц, факт прерывания | + +--- + +## 6. Provisioning + +Выполняется после `summary: overall == pass`. Не является тестом — это отдельный этап жизненного цикла платы. + +**Источник UID:** регистры OCOTP (One-Time Programmable fuses), 64-bit Chip UID. Читается через HAL. + +**Интерфейс (`provisioning.h`):** + +```c +typedef struct { + char chip_uid[17]; /* 64-bit UID как hex-строка, null-terminated */ + char fw_version[16]; + char bootloader_version[16]; + bool provisioned; +} provision_info_t; + +/* вызывается только при overall == PASS */ +void provisioning_run(provision_info_t *out); +``` + +**Поток:** + +```bash +плата посылает provision_ready + chip_uid + ↓ +хост записывает в БД: uid ↔ fw_version ↔ bootloader_version + ↓ +хост посылает provision_ack + ↓ +плата устанавливает provisioned = true +``` + +Вся логика на стороне хоста (запись в БД, генерация сертификата, привязка партии). Прошивка только читает UID и ждёт подтверждения. + +--- + +## 7. Реестр тестов + +```c +/* test_registry.c */ +static const test_module_t *tests[] = { + &test_sdram, /* critical */ + &test_qspi, /* critical */ + &test_usd, /* critical, SKIP если нет карты */ + &test_rtc, + &test_display, /* operator confirm */ + &test_ir, /* self-test уровень */ + &test_can, /* requires_hil */ + &test_uart_ttl, /* requires_hil */ + &test_uart_iso, /* requires_hil */ + &test_opto, /* requires_hil */ + &test_ir_hil, /* requires_hil, SKIP если нет IR на стенде */ +}; +``` + +--- + +## 8. Статусы тестов + +| Статус | Значение | +|--------|-------------------------------------------------------| +| `PASS` | Тест прошёл успешно | +| `FAIL` | Тест провален, в `detail` диагностическая информация | +| `SKIP` | Тест пропущен (нет карты, нет Firefly, таймаут оператора) | + +--- + +## 9. Открытые вопросы + +- [ ] Формат `detail` при FAIL для каждого теста (договориться между разработчиками) +- [ ] Handshake-протокол между firmware_test и Firefly (как плата узнаёт о готовности стенда) +- [ ] Полная схема интерфейсной платы для Firefly (оптовходы, IR LED, уровни +24V) +- [ ] GUI на сервере: формат отображения `summary` и хранение истории плат \ No newline at end of file diff --git a/firmware/test/arch.svg b/firmware/test/arch.svg new file mode 100644 index 0000000..da527f6 --- /dev/null +++ b/firmware/test/arch.svg @@ -0,0 +1,268 @@ + + + + + + + + + + +firmware_test · high-level architecture +NXP IMXRT1052CVJ5B + + +firmware_test + + + + + USB CDC + virtual serial port + +↔ Host PC +(терминал / GUI) + + + + + + + Protocol + JSON-lines · cJSON + + + + + + Test runner + sequencer + registry + + + + + + Local UI + LEDs + display + + + + + + + + + + +self-tests + + + SDRAM + 32 MB + + QSPI + W25Q128 + + uSD + SDIO + + RTC + BM8563 + + Display + operator ✓ + + IR + idle GPIO + + + +HIL tests · Firefly AIO-3588Q + + + CAN + frame exchange + + UART TTL + echo pattern + + UART ISO + +24V RX only + + Opto-in + +24V all ch. + + + + + + HAL / BSP + + + + +firmware_test · test runner flow + + + + + Boot + clock, USB CDC, HAL init + + + + + + + Wait for host + USB connect or timeout + + + + + +self-tests + + + + SDRAM + critical + write/read pattern + + + + QSPI Flash + critical + JEDEC ID + R/W + + + + uSD + critical + mount + R/W + + + + RTC + I2C presence + set / get + + + + Display + R/G/B/W fill + btn confirm + + + + + + + + + + + + Check critical failures + SDRAM || QSPI || uSD FAIL → abort HIL, report immediately + + + + + + +HIL tests · requires Firefly + + + + CAN + full-duplex + + + + UART TTL + echo + + + + UART ISO + RX only + + + + Opto-in + all channels + + + + IR burst + 38 kHz RX + + + + + + + + Summary report + JSON · pass/fail/skip per test · overall + + + + + + + + Provisioning + OCOTP chip_uid → host DB · only on pass + + +only if overall == pass + + + + +critical test + +HIL test + +operator + + From fdc7ce58708b8c3081f29828d6d4f60189becb25 Mon Sep 17 00:00:00 2001 From: Ezra Maccabee Date: Fri, 13 Mar 2026 19:38:29 +0300 Subject: [PATCH 3/3] # 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Набросок структуру будущего bsp --- bsp/README.md | 203 +++++++++++++++++++++ bsp/usb_cdc/README.md | 342 ++++++++++++++++++++++++++++++++++++ {bsp => docs}/BOOT_FLAGS.md | 0 3 files changed, 545 insertions(+) create mode 100644 bsp/README.md create mode 100644 bsp/usb_cdc/README.md rename {bsp => docs}/BOOT_FLAGS.md (100%) diff --git a/bsp/README.md b/bsp/README.md new file mode 100644 index 0000000..e8569d7 --- /dev/null +++ b/bsp/README.md @@ -0,0 +1,203 @@ +# BSP — Board Support Package + +> Целевая платформа: NXP IMXRT1052CVJ5B +> Используется в: `firmware/bootloader`, `firmware/test`, `firmware/tft_app` + +--- + +## Концепция + +BSP — единственное место в монорепо где есть знание о конкретном железе. Все три прошивки работают с периферией только через BSP. Прямых вызовов NXP SDK (`fsl_*.h`) за пределами `bsp/` быть не должно. + +```bash +firmware/test firmware/bootloader firmware/tft_app + ↓ ↓ ↓ + ┌─────────────────────────────────────────────────────┐ + │ BSP │ + │ bsp_usb_cdc bsp_uart bsp_can bsp_sdram ... │ + └─────────────────────────────────────────────────────┘ + ↓ ↓ ↓ + ┌─────────────────────────────────────────────────────┐ + │ NXP SDK / middleware │ + │ fsl_lpuart fsl_flexcan usb stack ... │ + └─────────────────────────────────────────────────────┘ +``` + +--- + +## Структура + +```bash +bsp/ +├── CMakeLists.txt # корневой: add_subdirectory для всех компонентов +├── README.md # этот файл +│ +├── generated/ # ← MCUXpresso Config Tools, не редактировать руками +│ ├── board.c / board.h +│ ├── clock_config.c / clock_config.h +│ ├── pin_mux.c / pin_mux.h +│ ├── peripherals.c / peripherals.h +│ └── startup/ +│ └── startup_MIMXRT1052.S +│ +├── usb_cdc/ # USB CDC ACM (Virtual COM Port) +├── uart/ # LPUART: TTL + изолированный RX +24V +├── can/ # FlexCAN +├── sdram/ # SEMC → SDRAM 32 MB (MT48LC16M16) +├── qspi/ # FlexSPI → W25Q128 (QSPI Flash) +├── sdio/ # uSDHC → uSD слот +├── display/ # eLCDIF → RGB888 +├── gpio/ # кнопки, LED, гальванически развязанные входы +└── mqs/ # MQS → аналоговый аудио выход +``` + +--- + +## Компоненты CMake + +Каждый компонент — отдельная статическая библиотека `bsp_`. + +### bsp_board — фундамент, от которого зависят все остальные + +```cmake +target_link_libraries(bsp_<любой_компонент> PUBLIC bsp_board) +``` + +`bsp_board` содержит: стартап, clock config, pin mux, board init. Формируется из `generated/` и не должен меняться руками — только через MCUXpresso Config Tools с последующей перегенерацией. + +### Boot-сценарии — INTERFACE-библиотеки + +Каждая прошивка выбирает один сценарий исполнения кода: + +| Таргет CMake | Сценарий | Кто использует | +|---|---|---| +| `bsp_boot_xip` | XIP — код исполняется из Flash | `firmware/test`, `firmware/tft_app` | +| `bsp_boot_itcm` | копирование в ITCM | `firmware/bootloader` | +| `bsp_boot_sdram` | копирование в SDRAM | зарезервировано | + +Подключается явно в каждом проекте: + +```cmake +target_link_libraries(firmware_test PRIVATE bsp_board bsp_boot_xip ...) +``` + +### Компоненты периферии + +Каждый компонент подключается независимо — прошивка линкует только то что использует: + +```cmake +# firmware/test — использует всё +target_link_libraries(firmware_test PRIVATE + bsp_board bsp_boot_xip + bsp_usb_cdc bsp_uart bsp_can + bsp_sdram bsp_qspi bsp_sdio + bsp_rtc bsp_display bsp_gpio bsp_ir +) + +# firmware/bootloader — минимальный набор +target_link_libraries(bootloader PRIVATE + bsp_board bsp_boot_itcm + bsp_usb_cdc bsp_qspi bsp_uart +) +``` + +--- + +## Правила написания компонентов + +### Граница изоляции + +Публичные заголовки компонента (`include/bsp/*.h`) не должны содержать ни одного `#include` из NXP SDK. Снаружи BSP — только стандартные типы C (`stdint.h`, `stdbool.h`, `stddef.h`) и собственные типы проекта. + +```c +/* ПРАВИЛЬНО — bsp/usb_cdc/include/bsp/usb_cdc.h */ +#include +#include +typedef enum { USB_CDC_OK, USB_CDC_ERR_NOT_READY } usb_cdc_status_t; +usb_cdc_status_t usb_cdc_init(void); + +/* НЕПРАВИЛЬНО */ +#include "fsl_common.h" /* ← утечка NXP SDK наружу */ +``` + +Платформенные хедеры (`fsl_*.h`, `usb_device_*.h`) живут только в `src/` — как PRIVATE зависимости. + +### Структура одного компонента + +```bash +bsp// +├── CMakeLists.txt +├── include/ +│ └── bsp/ +│ └── .h # публичный API — без NXP хедеров +└── src/ + ├── .c # реализация + └── <конфиг>.h # приватные конфиги стека (напр. usb_device_config.h) +``` + +```cmake +# bsp//CMakeLists.txt — шаблон +add_library(bsp_ STATIC src/.c) + +target_include_directories(bsp_ + PUBLIC include/ # bsp/.h доступен снаружи + PRIVATE src/ # конфиги и NXP хедеры — только внутри +) + +target_link_libraries(bsp_ + PUBLIC bsp_board # транзитивно во все потребители + PRIVATE sdk_ # NXP SDK — не торчит наружу +) +``` + +### Защита от host-сборки + +Каждый компонент должен быть безопасен при `BUILD_TESTS_HOST=ON`. Вариантов два: + +**А — guard в CMakeLists (рекомендуется для большинства компонентов):** + +```cmake +if(BUILD_TESTS_HOST) + return() +endif() +``` + +**Б — stub-реализация для компонентов которые тестируются на хосте:** + +```c +/* src/usb_cdc.c */ +#ifdef BSP_USB_CDC_VIRTUAL +/* заглушка — пишет в stdout, используется в host-тестах */ +usb_cdc_status_t usb_cdc_write(const uint8_t *data, size_t len) { + fwrite(data, 1, len, stdout); + return USB_CDC_OK; +} +#else +/* реальная реализация через NXP USB stack */ +#endif +``` + +--- + +## Связь с generated/ + +`generated/` — выхлоп MCUXpresso Config Tools. Содержит конфигурацию тактирования, пинов и периферии для конкретной платы. + +**Что трогать можно:** файлы в `generated/` можно и нужно перегенерировать через Config Tools при изменении схемы. + +**Что трогать нельзя:** редактировать `generated/` руками — изменения потеряются при следующей перегенерации. + +**Как добавить новый пин или периферию:** открыть проект в MCUXpresso Config Tools → внести изменения → Update Code → закоммитить изменённые файлы из `generated/`. + +--- + +## Добавление нового компонента — чеклист + +```bash +[ ] Создать bsp// со структурой include/src/CMakeLists.txt +[ ] Публичный хедер include/bsp/.h — без NXP хедеров +[ ] target_link_libraries: PUBLIC bsp_board, PRIVATE sdk_* +[ ] Guard BUILD_TESTS_HOST в CMakeLists или stub-реализация в .c +[ ] add_subdirectory(bsp/) в bsp/CMakeLists.txt +[ ] Добавить target в нужные прошивки (firmware/*/CMakeLists.txt) +``` diff --git a/bsp/usb_cdc/README.md b/bsp/usb_cdc/README.md new file mode 100644 index 0000000..874eb89 --- /dev/null +++ b/bsp/usb_cdc/README.md @@ -0,0 +1,342 @@ +# USB — теория для embedded-разработчика + +> Памятка: фокус на CDC ACM (Virtual COM Port) для NXP IMXRT1052 + +--- + +## 1. Основы архитектуры USB + +USB — это **master-slave** шина. Хост всегда инициирует обмен, устройство только отвечает. Никакой "самодеятельности" от устройства быть не может — только реакция на запросы хоста. + +```bash +HOST (PC) DEVICE (MCU) +────────── ──────────── +OS USB stack USB device stack + ↕ ↕ +Host controller (xHCI/EHCI) ←→ Device controller (EHCI на IMXRT) + D+ D− VBUS GND +``` + +### Физический уровень + +| Параметр | USB Full Speed | USB High Speed | +|----------|---------------|----------------| +| Скорость | 12 Мбит/с | 480 Мбит/с | +| IMXRT1052 | ✅ | ✅ | +| Практическая пропускная способность BULK | ~1 МБ/с | ~40 МБ/с | +| Применение | CDC ACM, HID | MSD, Video | + +IMXRT1052 имеет два USB контроллера: `USB1` (OTG, EHCI) и `USB2` (Host only). Для CDC ACM используем `USB1`. + +--- + +## 2. Ключевые понятия + +### Дескрипторы + +Дескрипторы — это набор структур, которые устройство возвращает хосту при подключении (в ответ на `GET_DESCRIPTOR`). Хост читает их и решает, какой драйвер загрузить. + +```bash +Device Descriptor +└── Configuration Descriptor + ├── Interface Descriptor #0 (CDC Control) + │ ├── CDC Header Functional Descriptor + │ ├── CDC Call Management Descriptor + │ ├── CDC ACM Functional Descriptor + │ ├── CDC Union Functional Descriptor + │ └── Endpoint Descriptor (INT IN) + └── Interface Descriptor #1 (CDC Data) + ├── Endpoint Descriptor (BULK IN) + └── Endpoint Descriptor (BULK OUT) +``` + +Важные поля `Device Descriptor`: + +| Поле | Значение | Смысл | +|------|----------|-------| +| `bDeviceClass` | 0xEF | Composite (классы на уровне интерфейсов) | +| `idVendor` | 0x1FC9 | VID NXP (или свой) | +| `idProduct` | произвольный | PID — идентификатор продукта | +| `bcdUSB` | 0x0200 | USB 2.0 | + +### Endpoints (эндпоинты) + +Эндпоинт — это буфер в устройстве с определённым направлением и типом передачи. EP0 — всегда управляющий (Control), остальные — настраиваются. + +| Тип | Гарантия доставки | Применение | +|-----|------------------|------------| +| Control | да | конфигурация устройства, EP0 | +| Bulk | да (retry) | большие данные, CDC ACM данные | +| Interrupt | да (периодически) | HID, CDC ACM нотификации | +| Isochronous | нет | аудио, видео | + +**Для CDC ACM нужны три эндпоинта:** + +```bash +EP0 Control IN/OUT — управление (всегда есть, не конфигурируется) +EP1 INT IN — нотификации CDC (DTR, RTS — наследие модемов) +EP2 BULK IN — данные device → host (твои JSON-строки → PC) +EP3 BULK OUT — данные host → device (команды PC → плата) +``` + +### Enumeration — что происходит при подключении кабеля + +```bash +1. Хост видит устройство (pull-up на D+) +2. USB Reset (SE0, 10 мс) +3. GET_DESCRIPTOR(Device) → хост узнаёт VID/PID, версию USB +4. SET_ADDRESS → устройство получает адрес на шине (1–127) +5. GET_DESCRIPTOR(Configuration) → хост видит интерфейсы +6. GET_DESCRIPTOR(String) × N → имена для Device Manager +7. SET_CONFIGURATION(1) → USB stack поднимает эндпоинты + → на стороне устройства срабатывает callback kUSB_DeviceEventSetConfiguration +8. Хост загружает драйвер по (bDeviceClass, idVendor, idProduct) + → CDC ACM: cdc_acm.ko (Linux) / usbser.sys (Windows) +9. Появляется /dev/ttyACM0 или COM3 +10. Пользователь открывает порт → хост посылает SET_CONTROL_LINE_STATE с DTR=1 + → устройство видит "хост подключён" +``` + +Шаг 10 критически важен: **пока терминал не открыт — DTR = 0**. Слать данные до появления DTR бессмысленно — хост их не читает. + +--- + +## 3. CDC ACM — детали класса + +CDC (Communications Device Class) — класс для коммуникационных устройств. ACM (Abstract Control Model) — подкласс, изначально для модемов, сейчас стандарт де-факто для Virtual COM Port. + +### Почему CDC ACM а не другие классы + +| Класс | Что видит OS | Проблема | +|-------|-------------|----------| +| **CDC ACM** | `/dev/ttyACM0`, `COM3` | — нет, это и нужно | +| Vendor | ничего | нужен свой драйвер под каждую ОС | +| HID | `/dev/hidraw0` | пакет максимум 64 байта, неудобно | +| MSC | блочное устройство | совсем не то | + +Главное преимущество CDC ACM: **стандартный драйвер есть везде** — Linux, Windows 10+, macOS — без установки чего-либо. + +### Ограничения которые надо знать + +**USB CDC не гарантирует границы сообщений.** Данные идут потоком через BULK-эндпоинты. Если ты послал `{"type":"result"}\n{"type":"summary"}\n` — хост может получить это как один кусок, два куска, или три куска произвольного размера. + +Поэтому **всегда нужен frame delimiter**. В нашем проекте — символ `\n` (JSON-lines). Приёмная сторона буферизирует до `\n` и только тогда парсит JSON. + +**Скорость** не ограничена физическими 115200 бод как у UART. USB Full Speed BULK даёт практически ~1 МБ/с. Baudrate в настройках терминала для CDC ACM — декоративный, реально на скорость не влияет. + +--- + +## 4. NXP USB Stack на IMXRT1052 + +### Архитектура стека + +```bash +твой код (bsp_usb_cdc) + ↕ callbacks + API +usb_device_cdc_acm.c ← CDC ACM класс (middleware/usb/device/class/) + ↕ +usb_device_dci.c ← Device Controller Interface (middleware/usb/device/) + ↕ +usb_device_ehci.c ← EHCI контроллер (middleware/usb/device/) + ↕ +USB PHY (usb_phy.c) ← физический уровень (middleware/usb/phy/) + ↕ +EHCI hardware registers +``` + +### Callback-архитектура + +NXP USB stack работает через callbacks — ты не вызываешь функции стека для приёма данных, стек сам вызывает твои функции когда что-то происходит. + +Два уровня callbacks: + +```c +/* 1. Callback уровня устройства — системные события */ +usb_status_t USB_DeviceCallback(usb_device_handle handle, + uint32_t event, + void *param) +{ + switch (event) { + case kUSB_DeviceEventBusReset: + /* сброс шины — переинициализировать эндпоинты */ + break; + case kUSB_DeviceEventSetConfiguration: + /* хост завершил enumeration — можно начинать работать */ + break; + } +} + +/* 2. Callback уровня CDC ACM класса — данные и управление */ +usb_status_t USB_DeviceCdcAcmCallback(class_handle_t handle, + uint32_t event, + void *param) +{ + switch (event) { + case kUSB_DeviceCdcEventSendResponse: + /* BULK IN передача завершена — буфер можно переиспользовать */ + break; + case kUSB_DeviceCdcEventRecvResponse: + /* BULK OUT данные получены — param указывает на буфер */ + break; + case kUSB_DeviceCdcEventSetControlLineState: + /* DTR/RTS изменились — проверяем подключение хоста */ + break; + } +} +``` + +### usb_device_config.h — конфигурационный файл + +NXP USB stack требует конфигурационный хедер. Он **не входит в SDK** — его пишешь ты и кладёшь в `bsp/usb_cdc/src/`. Ключевые параметры: + +```c +/* bsp/usb_cdc/src/usb_device_config.h */ + +/* Тип контроллера: EHCI для IMXRT1052 */ +#define USB_DEVICE_CONFIG_EHCI 1 + +/* Включаем CDC ACM класс */ +#define USB_DEVICE_CONFIG_CDC_ACM 1 + +/* Количество одновременных CDC инстансов */ +#define USB_DEVICE_CONFIG_CDC_ACM_INSTANCE_COUNT 1 + +/* Количество эндпоинтов (EP0 + INT + BULK IN + BULK OUT = 4) */ +#define USB_DEVICE_CONFIG_ENDPOINTS 4 + +/* Размер BULK буферов (степень двойки, FS max = 64 байта на транзакцию, + но можно использовать большие буферы для нескольких транзакций) */ +#define USB_DEVICE_CONFIG_CDC_ACM_MAX_DATAPIPE_SIZE 512 + +/* Bare-metal (без RTOS) */ +#define USB_DEVICE_CONFIG_USE_TASK 0 +``` + +### IRQ и polling + +На IMXRT1052 USB работает через прерывания. Стек нужно "тикать" из ISR: + +```c +/* в startup или IRQ handler регистрации */ +void USB_OTG1_IRQHandler(void) { + USB_DeviceEhciIsrFunction(g_usb_device_handle); +} +``` + +В bare-metal также нужен периодический вызов `USB_DeviceTaskFunction()` из main loop — он обрабатывает отложенные события которые нельзя делать прямо в ISR. + +--- + +## 5. Практические моменты для firmware_test + +### Инициализация — правильный порядок + +```c +/* 1. Clock init — USB PLL должен быть поднят ДО USB init */ +CLOCK_InitUsb1Pll(...); /* 480 MHz USB PLL */ +CLOCK_InitUsb1Pfd(...); + +/* 2. PHY init */ +USB_EhciPhyInit(CONTROLLER_ID, CLK_USRPH_24MHZ, NULL); + +/* 3. Device stack init */ +USB_DeviceInit(CONTROLLER_ID, USB_DeviceCallback, &handle); + +/* 4. Регистрация CDC ACM класса */ +USB_DeviceCdcAcmInit(...); + +/* 5. Старт */ +USB_DeviceRun(handle); +``` + +Если clock не инициализирован до USB — enumeration не пройдёт, хост увидит "USB device not recognized". + +### Определение факта подключения хоста + +Не надо проверять "есть ли питание на VBUS". Правильный способ — смотреть на **DTR флаг** из `SET_CONTROL_LINE_STATE`: + +```c +static volatile bool s_host_connected = false; + +/* внутри USB_DeviceCdcAcmCallback */ +case kUSB_DeviceCdcEventSetControlLineState: { + usb_device_cdc_acm_request_param_struct_t *p = param; + /* бит 0 = DTR, бит 1 = RTS */ + s_host_connected = (p->setupValue & 0x01) != 0; + break; +} + +bool usb_cdc_is_connected(void) { + return s_host_connected; +} +``` + +### Буферизация + +NXP USB stack не буферизует — это твоя ответственность. Минимальная схема: + +``` +TX: кольцевой буфер → usb_cdc_write() кладёт туда данные + → USB task вычитывает и передаёт через USB_DeviceCdcAcmSend() + → по kUSB_DeviceCdcEventSendResponse — можно слать следующий чанк + +RX: USB_DeviceCdcAcmRecv() регистрирует буфер для приёма + → по kUSB_DeviceCdcEventRecvResponse — данные в буфере + → приложение вычитывает до '\n' и парсит JSON +``` + +### Важно: двойная буферизация TX + +`USB_DeviceCdcAcmSend()` принимает указатель на буфер и **не копирует данные**. Буфер должен жить до получения `kUSB_DeviceCdcEventSendResponse`. Типичная ошибка — передать указатель на локальную переменную. + +```c +/* НЕПРАВИЛЬНО */ +void send_something(void) { + char buf[64]; + snprintf(buf, sizeof(buf), "{\"type\":\"result\"}\n"); + USB_DeviceCdcAcmSend(handle, EP_BULK_IN, (uint8_t*)buf, strlen(buf)); + /* buf уходит из стека — UB! */ +} + +/* ПРАВИЛЬНО — статический или глобальный буфер */ +static uint8_t s_tx_buf[512]; +``` + +--- + +## 6. Схема эндпоинтов для дескрипторов + +``` +EP номер Направление Тип Размер пакета Назначение +──────── ─────────── ───────── ───────────── ────────── +EP0 IN + OUT Control 64 байта enumeration (автоматически) +EP1 IN Interrupt 16 байт CDC нотификации (DTR/RTS events) +EP2 IN Bulk 64 байта (FS) данные device → host +EP3 OUT Bulk 64 байта (FS) данные host → device +``` + +Номера EP назначаются в дескрипторах. NXP примеры используют именно эту схему для Full Speed CDC ACM. + +--- + +## 7. Отладочные признаки проблем + +| Симптом | Вероятная причина | +|---------|------------------| +| "USB device not recognized" на хосте | не инициализирован USB PLL / PHY | +| Устройство определяется, порт не появляется | ошибка в дескрипторах (класс, подкласс, протокол) | +| Порт появился, данные не идут | DTR не поднят (терминал не открыт) или ошибка TX буферизации | +| Данные обрываются / мусор | буфер TX освобождается до SendResponse | +| Работает раз через раз | нет re-submit RX буфера после RecvResponse | +| Зависает при переподключении | нет обработки kUSB_DeviceEventBusReset → не сбрасываются эндпоинты | + +--- + +## 8. Ссылки + +- `sdk/middleware/usb/` — исходники NXP USB stack +- `sdk/boards/evkbimxrt1050/usb_examples/usb_device_cdc_vcom/` — референсный пример +- `sdk/middleware/usb/device/class/usb_device_cdc_acm.c` — реализация класса +- `sdk/middleware/usb/include/usb_device_cdc_acm.h` — API класса +- USB 2.0 Specification — [usb.org](https://www.usb.org/document-library/usb-20-specification) +- USB CDC Specification (PSTN) — [usb.org](https://www.usb.org/document-library/class-definitions-communication-devices-12) \ No newline at end of file diff --git a/bsp/BOOT_FLAGS.md b/docs/BOOT_FLAGS.md similarity index 100% rename from bsp/BOOT_FLAGS.md rename to docs/BOOT_FLAGS.md