Merge branch 'dev' of https://github.com/OSabuser/tft_manufacture_test into dev
This commit is contained in:
commit
d459459524
17 changed files with 1356 additions and 75 deletions
29
.env.example
29
.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
|
||||||
2
.gitattributes
vendored
2
.gitattributes
vendored
|
|
@ -1,2 +1,4 @@
|
||||||
# Auto detect text files and perform LF normalization
|
# Auto detect text files and perform LF normalization
|
||||||
* text=auto
|
* text=auto
|
||||||
|
.env text eol=lf
|
||||||
|
.env.example text eol=lf
|
||||||
|
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -68,5 +68,6 @@ compile_commands.json
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
.env
|
.env
|
||||||
|
tools/host/.venv-host/
|
||||||
|
tools/host/.venv-host-win/
|
||||||
|
|
||||||
|
|
|
||||||
104
HOW_TO_FLASH.md
Normal file
104
HOW_TO_FLASH.md
Normal file
|
|
@ -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 <project> <type>
|
||||||
|
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
|
||||||
|
```
|
||||||
6
Justfile
6
Justfile
|
|
@ -20,9 +20,9 @@ set dotenv-load
|
||||||
|
|
||||||
# === Общие переменные (доступны во всех модулях через export) ===
|
# === Общие переменные (доступны во всех модулях через export) ===
|
||||||
BOARD := env('BOARD', 'MIMXRT1052')
|
BOARD := env('BOARD', 'MIMXRT1052')
|
||||||
BUILD_DIR := env('BUILD_DIR', 'build')
|
BUILD_DIR := env('BUILD_DIR', justfile_directory() / 'build')
|
||||||
TOOLS_DIR := env('TOOLS_DIR', 'tools/host')
|
TOOLS_DIR := env('TOOLS_DIR', justfile_directory() / 'tools/host')
|
||||||
CACHE_DIR := ".cache"
|
CACHE_DIR := justfile_directory() / '.cache'
|
||||||
|
|
||||||
# === Модули ===
|
# === Модули ===
|
||||||
# Каждый модуль — это namespace с изолированными рецептами
|
# Каждый модуль — это namespace с изолированными рецептами
|
||||||
|
|
|
||||||
21
README.md
21
README.md
|
|
@ -11,6 +11,11 @@
|
||||||
```bash
|
```bash
|
||||||
/
|
/
|
||||||
├── .devcontainer/ # VSCode Devcontainer — единое окружение для всех разработчиков
|
├── .devcontainer/ # VSCode Devcontainer — единое окружение для всех разработчиков
|
||||||
|
│ ├── Dockerfile
|
||||||
|
│ └── devcontainer.json
|
||||||
|
├── .vscode/
|
||||||
|
│ ├── launch.json
|
||||||
|
│ └── tasks.json # UI для just build::* (внутри devcontainer)
|
||||||
├── bsp/ # Board Support Package
|
├── bsp/ # Board Support Package
|
||||||
│ └── generated/ # Сгенерировано NXP Config Tools (Pins + Clocks Tool)
|
│ └── generated/ # Сгенерировано NXP Config Tools (Pins + Clocks Tool)
|
||||||
│ ├── TFT_Board.mex # Источник истины конфигурации пинов и тактирования
|
│ ├── TFT_Board.mex # Источник истины конфигурации пинов и тактирования
|
||||||
|
|
@ -40,27 +45,31 @@
|
||||||
├── tests/ # Тесты (host + target)
|
├── tests/ # Тесты (host + target)
|
||||||
│ ├── host/ # Unit/интеграционные тесты, запускаемые на хосте
|
│ ├── host/ # Unit/интеграционные тесты, запускаемые на хосте
|
||||||
│ ├── target/ # Тесты периферии, запускаемые на таргете
|
│ ├── target/ # Тесты периферии, запускаемые на таргете
|
||||||
│ └── HostTestingGuide.md
|
│ ├── HostTestingGuide.md
|
||||||
|
│ └── README.md
|
||||||
├── tools/
|
├── tools/
|
||||||
│ └── host/ # Инструменты для работы с таргетом
|
│ └── host/ # Инструменты для работы с таргетом
|
||||||
│ ├── flash_usb.py # Прошивка через USB ROM (nxp-spsdk / blhost)
|
│ ├── flash_usb.py # Прошивка через USB ROM (nxp-spsdk / blhost)
|
||||||
│ ├── hab/ # Утилиты и гайд по HAB (Secure Boot)
|
│ ├── hab/ # Утилиты и гайд по HAB (Secure Boot)
|
||||||
│ └── dcd/ # Device Configuration Data
|
│ ├── dcd/ # Device Configuration Data
|
||||||
|
│ ├── pyproject.toml
|
||||||
|
│ └── uv.lock
|
||||||
├── just/ # Just-модули (автоматизация)
|
├── just/ # Just-модули (автоматизация)
|
||||||
│ ├── build.just # Сборка, тесты, HAB-образы (devcontainer)
|
│ ├── build.just # Сборка, тесты, HAB-образы (devcontainer)
|
||||||
│ ├── host.just # Прошивка, bootstrap, HIL (хост)
|
│ ├── host.just # Прошивка, bootstrap, HIL (хост)
|
||||||
│ └── ci.just # CI/CD пайплайны
|
│ └── ci.just # CI/CD пайплайны
|
||||||
├── scripts/
|
|
||||||
│ └── bootstrap.sh # Первичная настройка окружения (уровень 0)
|
|
||||||
├── docs/ # Документация проекта
|
├── docs/ # Документация проекта
|
||||||
│ ├── DEV_ARCH.md # Архитектура окружения разработки
|
│ ├── DEV_ARCH.md # Архитектура окружения разработки
|
||||||
│ ├── CMAKE_HINTS.md # Шпаргалка по CMake в проекте
|
│ ├── CMAKE_HINTS.md # Шпаргалка по CMake в проекте
|
||||||
│ ├── schematic.pdf # Схема платы
|
│ ├── schematic.pdf # Схема платы
|
||||||
│ ├── mcu_rm.pdf # Reference Manual IMXRT1052
|
│ ├── mcu_rm.pdf # Reference Manual IMXRT1052
|
||||||
│ └── manufacturing_user's_guide.pdf
|
│ └── manufacturing_user's_guide.pdf
|
||||||
|
├── .env # Конфигурация проекта (VID:PID, пути, GDB и др.)
|
||||||
|
├── .env.example # Шаблон .env для новых разработчиков
|
||||||
|
├── bootstrap.sh # Первичная настройка окружения (уровень 0)
|
||||||
├── CMakeLists.txt # Корневой CMake
|
├── CMakeLists.txt # Корневой CMake
|
||||||
├── CMakePresets.json # Пресеты сборки (Release/Debug/Host)
|
├── CMakePresets.json # Пресеты сборки (Release/Debug/Host)
|
||||||
├── Justfile # Точка входа для команд (модули: build, host, ci)
|
├── justfile # Точка входа для команд (модули: build, host, ci)
|
||||||
└── README.md
|
└── README.md
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -142,7 +151,7 @@ git clone <repo-url>
|
||||||
cd tft_manufacture_test
|
cd tft_manufacture_test
|
||||||
|
|
||||||
# Инициализация хоста (один раз)
|
# Инициализация хоста (один раз)
|
||||||
sudo chmod +x bootstrap.sh
|
# Устанавливает just и uv, затем настраивает окружение
|
||||||
./bootstrap.sh
|
./bootstrap.sh
|
||||||
|
|
||||||
# Открыть в VSCode → Reopen in Container
|
# Открыть в VSCode → Reopen in Container
|
||||||
|
|
|
||||||
203
bsp/README.md
Normal file
203
bsp/README.md
Normal file
|
|
@ -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_<name>`.
|
||||||
|
|
||||||
|
### 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 <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
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/<name>/
|
||||||
|
├── CMakeLists.txt
|
||||||
|
├── include/
|
||||||
|
│ └── bsp/
|
||||||
|
│ └── <name>.h # публичный API — без NXP хедеров
|
||||||
|
└── src/
|
||||||
|
├── <name>.c # реализация
|
||||||
|
└── <конфиг>.h # приватные конфиги стека (напр. usb_device_config.h)
|
||||||
|
```
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
# bsp/<name>/CMakeLists.txt — шаблон
|
||||||
|
add_library(bsp_<name> STATIC src/<name>.c)
|
||||||
|
|
||||||
|
target_include_directories(bsp_<name>
|
||||||
|
PUBLIC include/ # bsp/<name>.h доступен снаружи
|
||||||
|
PRIVATE src/ # конфиги и NXP хедеры — только внутри
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(bsp_<name>
|
||||||
|
PUBLIC bsp_board # транзитивно во все потребители
|
||||||
|
PRIVATE sdk_<driver> # 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/<name>/ со структурой include/src/CMakeLists.txt
|
||||||
|
[ ] Публичный хедер include/bsp/<name>.h — без NXP хедеров
|
||||||
|
[ ] target_link_libraries: PUBLIC bsp_board, PRIVATE sdk_*
|
||||||
|
[ ] Guard BUILD_TESTS_HOST в CMakeLists или stub-реализация в .c
|
||||||
|
[ ] add_subdirectory(bsp/<name>) в bsp/CMakeLists.txt
|
||||||
|
[ ] Добавить target в нужные прошивки (firmware/*/CMakeLists.txt)
|
||||||
|
```
|
||||||
342
bsp/usb_cdc/README.md
Normal file
342
bsp/usb_cdc/README.md
Normal file
|
|
@ -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)
|
||||||
|
|
@ -34,7 +34,8 @@ CI использует те же команды что и локальная р
|
||||||
│ ├── docker ← управление devcontainer
|
│ ├── docker ← управление devcontainer
|
||||||
│ ├── git ← работа с репозиторием
|
│ ├── git ← работа с репозиторием
|
||||||
│ ├── uv + spsdk ← прошивка платы (flash_usb.py, sdphost, blhost)
|
│ ├── uv + spsdk ← прошивка платы (flash_usb.py, sdphost, blhost)
|
||||||
│ │ venv: tools/host/.venv-host
|
│ │ venv: tools/host/.venv. (Linux/macOS)
|
||||||
|
│ │ tools/host/.venv-host-win (Windows)
|
||||||
│ ├── JLinkGDBServer / probe-rs ← сервер отладки (USB → TCP :2331)
|
│ ├── JLinkGDBServer / probe-rs ← сервер отладки (USB → TCP :2331)
|
||||||
│ └── VSCode ← IDE (Dev Containers extension)
|
│ └── VSCode ← IDE (Dev Containers extension)
|
||||||
│
|
│
|
||||||
|
|
@ -48,7 +49,7 @@ CI использует те же команды что и локальная р
|
||||||
│ ├── cmake-format ← форматирование CMakeLists
|
│ ├── cmake-format ← форматирование CMakeLists
|
||||||
│ ├── just ← запуск задач внутри контейнера (just build::*)
|
│ ├── just ← запуск задач внутри контейнера (just build::*)
|
||||||
│ ├── uv + spsdk ← сборка HAB-образов (только nxpimage)
|
│ ├── uv + spsdk ← сборка HAB-образов (только nxpimage)
|
||||||
│ │ venv: tools/host/.venv-container
|
│ │ venv: tools/host/.venv
|
||||||
│ └── Unity + fff ← фреймворки host-тестов
|
│ └── Unity + fff ← фреймворки host-тестов
|
||||||
│
|
│
|
||||||
└── Плата TFT (IMXRT1052) — на столе у разработчика
|
└── Плата TFT (IMXRT1052) — на столе у разработчика
|
||||||
|
|
@ -83,7 +84,45 @@ CI использует те же команды что и локальная р
|
||||||
Версия `spsdk` зафиксирована в `tools/host/uv.lock` — все три места
|
Версия `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
|
```bash
|
||||||
/
|
/
|
||||||
├── Justfile ← корневой оркестратор; модули: build, host, ci
|
├── justfile ← корневой оркестратор; модули: build, host, ci
|
||||||
|
├── bootstrap.sh ← уровень 0: устанавливает just+uv → just host::bootstrap
|
||||||
|
├── .env ← конфигурация проекта (VID:PID, пути, GDB)
|
||||||
|
├── .env.example ← шаблон для новых разработчиков
|
||||||
├── just/
|
├── just/
|
||||||
│ ├── build.just ← devcontainer: сборка, тесты, HAB
|
│ ├── build.just ← devcontainer: сборка, тесты, HAB
|
||||||
│ ├── host.just ← хост: прошивка, bootstrap, HIL
|
│ ├── host.just ← хост: прошивка, bootstrap, HIL
|
||||||
│ └── ci.just ← CI/CD пайплайны
|
│ └── ci.just ← CI/CD пайплайны
|
||||||
├── scripts/
|
|
||||||
│ └── bootstrap.sh ← уровень 0: just → just host::bootstrap
|
|
||||||
├── .devcontainer/
|
├── .devcontainer/
|
||||||
│ ├── Dockerfile
|
│ ├── Dockerfile
|
||||||
│ └── devcontainer.json
|
│ └── devcontainer.json
|
||||||
|
├── .vscode/
|
||||||
|
│ └── tasks.json ← UI для just build::* (внутри devcontainer)
|
||||||
├── tools/
|
├── tools/
|
||||||
│ └── host/
|
│ └── host/
|
||||||
│ ├── hab/
|
│ ├── hab/
|
||||||
|
|
@ -113,11 +155,9 @@ CI использует те же команды что и локальная р
|
||||||
│ ├── dcd/
|
│ ├── dcd/
|
||||||
│ │ ├── dcd.bin
|
│ │ ├── dcd.bin
|
||||||
│ │ └── ivt_flashloader.bin
|
│ │ └── ivt_flashloader.bin
|
||||||
│ ├── flash_usb.py
|
│ ├── flash_usb.py ← читает конфиг из env (VID:PID, BUILD_DIR)
|
||||||
│ ├── pyproject.toml
|
│ ├── pyproject.toml
|
||||||
│ └── uv.lock
|
│ └── uv.lock
|
||||||
├── .vscode/
|
|
||||||
│ └── tasks.json ← UI для just build::* (внутри devcontainer)
|
|
||||||
├── CMakePresets.json
|
├── CMakePresets.json
|
||||||
├── cmake/
|
├── cmake/
|
||||||
├── sdk/
|
├── sdk/
|
||||||
|
|
@ -157,12 +197,19 @@ bootstrap.sh (уровень 0)
|
||||||
├── определить платформу (Linux / macOS / Windows Git Bash)
|
├── определить платформу (Linux / macOS / Windows Git Bash)
|
||||||
│ uname: MINGW64_NT-... → windows, Linux → linux, Darwin → macos
|
│ uname: MINGW64_NT-... → windows, Linux → linux, Darwin → macos
|
||||||
│
|
│
|
||||||
├── проверить just (semver без sort -V — работает в Git Bash)
|
├── проверить/установить just (semver без sort -V — работает в Git Bash)
|
||||||
│ < 1.36.0 или отсутствует:
|
│ < 1.36.0 или отсутствует:
|
||||||
│ Linux/macOS → curl | bash → ~/.local/bin/just
|
│ Linux/macOS → curl | bash → ~/.local/bin/just
|
||||||
|
│ macOS → brew install just (если есть Homebrew)
|
||||||
│ Windows → winget install --id Casey.Just
|
│ Windows → winget install --id Casey.Just
|
||||||
│ (перезапустить Git Bash после установки)
|
│ (перезапустить 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
|
└── exec just host::bootstrap
|
||||||
│
|
│
|
||||||
├── [1/3] check-deps
|
├── [1/3] check-deps
|
||||||
|
|
@ -173,11 +220,15 @@ bootstrap.sh (уровень 0)
|
||||||
│ /etc/udev/rules.d/99-nxp-mimxrt.rules:
|
│ /etc/udev/rules.d/99-nxp-mimxrt.rules:
|
||||||
│ 1FC9:0130 ← NXP BootROM (SDP-режим)
|
│ 1FC9:0130 ← NXP BootROM (SDP-режим)
|
||||||
│ 15A2:0073 ← NXP Flashloader
|
│ 15A2:0073 ← NXP Flashloader
|
||||||
|
│ VID:PID берутся из .env (BOOTROM_VID/PID, FLASHLOADER_VID/PID)
|
||||||
│ usermod -a -G plugdev $USER
|
│ usermod -a -G plugdev $USER
|
||||||
│ требует re-login · на macOS/Windows пропускается
|
│ требует re-login · на macOS/Windows пропускается
|
||||||
│
|
│
|
||||||
└── [3/3] setup-tools
|
└── [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/
|
SHA-256 uv.lock кэшируется в .cache/
|
||||||
повторный вызов мгновенный если lockfile не изменился
|
повторный вызов мгновенный если lockfile не изменился
|
||||||
```
|
```
|
||||||
|
|
@ -191,7 +242,7 @@ bootstrap.sh (уровень 0)
|
||||||
`postCreateCommand` выполняется автоматически при поднятии контейнера:
|
`postCreateCommand` выполняется автоматически при поднятии контейнера:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd tools/host && uv sync && # venv: .venv-container
|
cd tools/host && uv sync && # venv: tools/host/.venv (Linux, внутри контейнера)
|
||||||
cd ../.. &&
|
cd ../.. &&
|
||||||
cmake --preset host-debug &&
|
cmake --preset host-debug &&
|
||||||
cmake --preset Debug
|
cmake --preset Debug
|
||||||
|
|
@ -266,7 +317,7 @@ buildPresets (host):
|
||||||
|
|
||||||
### 7.2 Типичная сессия разработки
|
### 7.2 Типичная сессия разработки
|
||||||
|
|
||||||
```
|
```bash
|
||||||
Открыть VSCode → работать в devcontainer весь день
|
Открыть VSCode → работать в devcontainer весь день
|
||||||
│
|
│
|
||||||
├── писать код
|
├── писать код
|
||||||
|
|
@ -311,7 +362,7 @@ buildPresets (host):
|
||||||
|
|
||||||
### 8.1 Перевод платы в SDP-режим
|
### 8.1 Перевод платы в SDP-режим
|
||||||
|
|
||||||
```
|
```bash
|
||||||
1. BOOT_MOD_1 → 3V3
|
1. BOOT_MOD_1 → 3V3
|
||||||
2. Reset
|
2. Reset
|
||||||
3. Подключить USB к хосту
|
3. Подключить USB к хосту
|
||||||
|
|
@ -347,7 +398,12 @@ just host::flash-production # bootloader release + tft_app release
|
||||||
|
|
||||||
### 8.3 Что происходит внутри flash_usb.py
|
### 8.3 Что происходит внутри flash_usb.py
|
||||||
|
|
||||||
```
|
`flash_usb.py` читает конфигурацию из окружения (just экспортирует `.env`):
|
||||||
|
|
||||||
|
- `BOOTROM_VID/PID`, `FLASHLOADER_VID/PID` → USB-адреса устройств
|
||||||
|
- `BUILD_DIR` → абсолютный путь к артефактам сборки
|
||||||
|
|
||||||
|
```bash
|
||||||
Плата в SDP-режиме (1FC9:0130)
|
Плата в SDP-режиме (1FC9:0130)
|
||||||
│
|
│
|
||||||
├── sdphost: загрузить ivt_flashloader.bin в RAM (0x20001C00)
|
├── sdphost: загрузить ivt_flashloader.bin в RAM (0x20001C00)
|
||||||
|
|
|
||||||
244
firmware/test/README.md
Normal file
244
firmware/test/README.md
Normal file
|
|
@ -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` и хранение истории плат
|
||||||
268
firmware/test/arch.svg
Normal file
268
firmware/test/arch.svg
Normal file
|
|
@ -0,0 +1,268 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1360" viewBox="0 0 680 1180">
|
||||||
|
<defs>
|
||||||
|
<marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||||
|
<path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</marker>
|
||||||
|
<style>
|
||||||
|
text { font-family: system-ui, sans-serif; fill: #1a1a1a; }
|
||||||
|
.th { font-size: 14px; font-weight: 500; }
|
||||||
|
.ts { font-size: 12px; font-weight: 400; fill: #555; }
|
||||||
|
.label-section { font-size: 11px; font-weight: 400; fill: #888; }
|
||||||
|
|
||||||
|
/* teal */
|
||||||
|
.c-teal rect, .c-teal circle { fill: #E1F5EE; stroke: #0F6E56; }
|
||||||
|
.c-teal .th { fill: #085041; }
|
||||||
|
.c-teal .ts { fill: #0F6E56; }
|
||||||
|
|
||||||
|
/* purple */
|
||||||
|
.c-purple rect { fill: #EEEDFE; stroke: #534AB7; }
|
||||||
|
.c-purple .th { fill: #3C3489; }
|
||||||
|
.c-purple .ts { fill: #534AB7; }
|
||||||
|
|
||||||
|
/* amber */
|
||||||
|
.c-amber rect { fill: #FAEEDA; stroke: #854F0B; }
|
||||||
|
.c-amber .th { fill: #633806; }
|
||||||
|
.c-amber .ts { fill: #854F0B; }
|
||||||
|
|
||||||
|
/* gray */
|
||||||
|
.c-gray rect { fill: #F1EFE8; stroke: #5F5E5A; }
|
||||||
|
.c-gray .th { fill: #2C2C2A; }
|
||||||
|
.c-gray .ts { fill: #5F5E5A; }
|
||||||
|
|
||||||
|
/* red */
|
||||||
|
.c-red rect { fill: #FCEBEB; stroke: #A32D2D; }
|
||||||
|
.c-red .th { fill: #501313; }
|
||||||
|
.c-red .ts { fill: #A32D2D; }
|
||||||
|
|
||||||
|
.arr { stroke: #888; stroke-width: 1; fill: none; }
|
||||||
|
.box-dashed { fill: none; stroke: #bbb; stroke-width: 0.8; stroke-dasharray: 5 3; }
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- ═══════════════════════════════════════════════════════
|
||||||
|
DIAGRAM 1: High-level architecture
|
||||||
|
═══════════════════════════════════════════════════════ -->
|
||||||
|
|
||||||
|
<text x="340" y="30" text-anchor="middle" class="th" style="font-size:16px;fill:#1a1a1a;">firmware_test · high-level architecture</text>
|
||||||
|
<text x="340" y="48" text-anchor="middle" class="ts">NXP IMXRT1052CVJ5B</text>
|
||||||
|
|
||||||
|
<rect x="20" y="60" width="640" height="440" rx="16" class="box-dashed"/>
|
||||||
|
<text x="340" y="78" text-anchor="middle" class="label-section">firmware_test</text>
|
||||||
|
|
||||||
|
<!-- USB CDC -->
|
||||||
|
<g class="c-teal">
|
||||||
|
<rect x="40" y="88" width="160" height="50" rx="8" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="120" y="108" text-anchor="middle" dominant-baseline="central">USB CDC</text>
|
||||||
|
<text class="ts" x="120" y="126" text-anchor="middle" dominant-baseline="central">virtual serial port</text>
|
||||||
|
</g>
|
||||||
|
<text class="ts" x="212" y="107" fill="#888">↔ Host PC</text>
|
||||||
|
<text class="ts" x="212" y="123" fill="#888">(терминал / GUI)</text>
|
||||||
|
|
||||||
|
<line x1="120" y1="138" x2="120" y2="180" class="arr" marker-end="url(#arrow)"/>
|
||||||
|
|
||||||
|
<!-- Protocol -->
|
||||||
|
<g class="c-purple">
|
||||||
|
<rect x="40" y="180" width="160" height="50" rx="8" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="120" y="200" text-anchor="middle" dominant-baseline="central">Protocol</text>
|
||||||
|
<text class="ts" x="120" y="218" text-anchor="middle" dominant-baseline="central">JSON-lines · cJSON</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Test runner -->
|
||||||
|
<g class="c-purple">
|
||||||
|
<rect x="250" y="180" width="190" height="50" rx="8" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="345" y="200" text-anchor="middle" dominant-baseline="central">Test runner</text>
|
||||||
|
<text class="ts" x="345" y="218" text-anchor="middle" dominant-baseline="central">sequencer + registry</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Local UI -->
|
||||||
|
<g class="c-amber">
|
||||||
|
<rect x="490" y="180" width="150" height="50" rx="8" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="565" y="200" text-anchor="middle" dominant-baseline="central">Local UI</text>
|
||||||
|
<text class="ts" x="565" y="218" text-anchor="middle" dominant-baseline="central">LEDs + display</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<line x1="200" y1="205" x2="250" y2="205" class="arr" marker-start="url(#arrow)" marker-end="url(#arrow)" stroke="#666"/>
|
||||||
|
<line x1="440" y1="205" x2="490" y2="205" class="arr" marker-end="url(#arrow)" stroke="#666"/>
|
||||||
|
|
||||||
|
<path d="M345,230 L345,260 L180,260 L180,270" fill="none" stroke="#bbb" stroke-width="0.8" marker-end="url(#arrow)"/>
|
||||||
|
<path d="M345,230 L345,260 L505,260 L505,270" fill="none" stroke="#bbb" stroke-width="0.8" marker-end="url(#arrow)"/>
|
||||||
|
|
||||||
|
<!-- Self-tests region -->
|
||||||
|
<rect x="30" y="270" width="300" height="150" rx="8" class="box-dashed"/>
|
||||||
|
<text class="label-section" x="56" y="287">self-tests</text>
|
||||||
|
|
||||||
|
<g class="c-gray"><rect x="46" y="295" width="76" height="44" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="84" y="312" text-anchor="middle" dominant-baseline="central">SDRAM</text>
|
||||||
|
<text class="ts" x="84" y="328" text-anchor="middle" dominant-baseline="central">32 MB</text></g>
|
||||||
|
<g class="c-gray"><rect x="132" y="295" width="76" height="44" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="170" y="312" text-anchor="middle" dominant-baseline="central">QSPI</text>
|
||||||
|
<text class="ts" x="170" y="328" text-anchor="middle" dominant-baseline="central">W25Q128</text></g>
|
||||||
|
<g class="c-gray"><rect x="218" y="295" width="76" height="44" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="256" y="312" text-anchor="middle" dominant-baseline="central">uSD</text>
|
||||||
|
<text class="ts" x="256" y="328" text-anchor="middle" dominant-baseline="central">SDIO</text></g>
|
||||||
|
<g class="c-gray"><rect x="46" y="349" width="76" height="44" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="84" y="366" text-anchor="middle" dominant-baseline="central">RTC</text>
|
||||||
|
<text class="ts" x="84" y="382" text-anchor="middle" dominant-baseline="central">BM8563</text></g>
|
||||||
|
<g class="c-gray"><rect x="132" y="349" width="76" height="44" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="170" y="366" text-anchor="middle" dominant-baseline="central">Display</text>
|
||||||
|
<text class="ts" x="170" y="382" text-anchor="middle" dominant-baseline="central">operator ✓</text></g>
|
||||||
|
<g class="c-gray"><rect x="218" y="349" width="76" height="44" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="256" y="366" text-anchor="middle" dominant-baseline="central">IR</text>
|
||||||
|
<text class="ts" x="256" y="382" text-anchor="middle" dominant-baseline="central">idle GPIO</text></g>
|
||||||
|
|
||||||
|
<!-- HIL tests region -->
|
||||||
|
<rect x="350" y="270" width="290" height="150" rx="8" class="box-dashed"/>
|
||||||
|
<text class="label-section" x="376" y="287">HIL tests · Firefly AIO-3588Q</text>
|
||||||
|
|
||||||
|
<g class="c-teal"><rect x="366" y="295" width="120" height="44" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="426" y="312" text-anchor="middle" dominant-baseline="central">CAN</text>
|
||||||
|
<text class="ts" x="426" y="328" text-anchor="middle" dominant-baseline="central">frame exchange</text></g>
|
||||||
|
<g class="c-teal"><rect x="496" y="295" width="120" height="44" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="556" y="312" text-anchor="middle" dominant-baseline="central">UART TTL</text>
|
||||||
|
<text class="ts" x="556" y="328" text-anchor="middle" dominant-baseline="central">echo pattern</text></g>
|
||||||
|
<g class="c-teal"><rect x="366" y="349" width="120" height="44" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="426" y="366" text-anchor="middle" dominant-baseline="central">UART ISO</text>
|
||||||
|
<text class="ts" x="426" y="382" text-anchor="middle" dominant-baseline="central">+24V RX only</text></g>
|
||||||
|
<g class="c-teal"><rect x="496" y="349" width="120" height="44" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="556" y="366" text-anchor="middle" dominant-baseline="central">Opto-in</text>
|
||||||
|
<text class="ts" x="556" y="382" text-anchor="middle" dominant-baseline="central">+24V all ch.</text></g>
|
||||||
|
|
||||||
|
<!-- HAL / BSP -->
|
||||||
|
<line x1="340" y1="420" x2="340" y2="448" class="arr" marker-end="url(#arrow)" stroke="#aaa"/>
|
||||||
|
<g class="c-gray">
|
||||||
|
<rect x="30" y="448" width="620" height="40" rx="8" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="340" y="468" text-anchor="middle" dominant-baseline="central">HAL / BSP</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- ═══════════════════════════════════════════════════════
|
||||||
|
DIAGRAM 2: Test runner flow
|
||||||
|
═══════════════════════════════════════════════════════ -->
|
||||||
|
|
||||||
|
<text x="340" y="540" text-anchor="middle" class="th" style="font-size:16px;fill:#1a1a1a;">firmware_test · test runner flow</text>
|
||||||
|
|
||||||
|
<!-- Boot -->
|
||||||
|
<g class="c-gray">
|
||||||
|
<rect x="240" y="558" width="200" height="44" rx="8" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="340" y="576" text-anchor="middle" dominant-baseline="central">Boot</text>
|
||||||
|
<text class="ts" x="340" y="592" text-anchor="middle" dominant-baseline="central">clock, USB CDC, HAL init</text>
|
||||||
|
</g>
|
||||||
|
<line x1="340" y1="602" x2="340" y2="630" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
||||||
|
|
||||||
|
<!-- Wait for host -->
|
||||||
|
<g class="c-purple">
|
||||||
|
<rect x="240" y="630" width="200" height="44" rx="8" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="340" y="648" text-anchor="middle" dominant-baseline="central">Wait for host</text>
|
||||||
|
<text class="ts" x="340" y="664" text-anchor="middle" dominant-baseline="central">USB connect or timeout</text>
|
||||||
|
</g>
|
||||||
|
<line x1="340" y1="674" x2="340" y2="698" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
||||||
|
|
||||||
|
<!-- Self-tests block -->
|
||||||
|
<rect x="30" y="698" width="620" height="168" rx="10" class="box-dashed"/>
|
||||||
|
<text class="label-section" x="56" y="714">self-tests</text>
|
||||||
|
|
||||||
|
<g class="c-red">
|
||||||
|
<rect x="46" y="720" width="110" height="54" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="101" y="740" text-anchor="middle" dominant-baseline="central">SDRAM</text>
|
||||||
|
<text class="ts" x="101" y="756" text-anchor="middle" dominant-baseline="central">critical</text>
|
||||||
|
<text class="ts" x="101" y="768" text-anchor="middle" dominant-baseline="central">write/read pattern</text>
|
||||||
|
</g>
|
||||||
|
<g class="c-red">
|
||||||
|
<rect x="166" y="720" width="110" height="54" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="221" y="740" text-anchor="middle" dominant-baseline="central">QSPI Flash</text>
|
||||||
|
<text class="ts" x="221" y="756" text-anchor="middle" dominant-baseline="central">critical</text>
|
||||||
|
<text class="ts" x="221" y="768" text-anchor="middle" dominant-baseline="central">JEDEC ID + R/W</text>
|
||||||
|
</g>
|
||||||
|
<g class="c-red">
|
||||||
|
<rect x="286" y="720" width="110" height="54" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="341" y="740" text-anchor="middle" dominant-baseline="central">uSD</text>
|
||||||
|
<text class="ts" x="341" y="756" text-anchor="middle" dominant-baseline="central">critical</text>
|
||||||
|
<text class="ts" x="341" y="768" text-anchor="middle" dominant-baseline="central">mount + R/W</text>
|
||||||
|
</g>
|
||||||
|
<g class="c-gray">
|
||||||
|
<rect x="406" y="720" width="110" height="54" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="461" y="740" text-anchor="middle" dominant-baseline="central">RTC</text>
|
||||||
|
<text class="ts" x="461" y="756" text-anchor="middle" dominant-baseline="central">I2C presence</text>
|
||||||
|
<text class="ts" x="461" y="768" text-anchor="middle" dominant-baseline="central">set / get</text>
|
||||||
|
</g>
|
||||||
|
<g class="c-amber">
|
||||||
|
<rect x="526" y="720" width="110" height="54" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="581" y="740" text-anchor="middle" dominant-baseline="central">Display</text>
|
||||||
|
<text class="ts" x="581" y="756" text-anchor="middle" dominant-baseline="central">R/G/B/W fill</text>
|
||||||
|
<text class="ts" x="581" y="768" text-anchor="middle" dominant-baseline="central">btn confirm</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<line x1="101" y1="774" x2="101" y2="794" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
||||||
|
<line x1="221" y1="774" x2="221" y2="794" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
||||||
|
<line x1="341" y1="774" x2="341" y2="794" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
||||||
|
<line x1="461" y1="774" x2="461" y2="794" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
||||||
|
<line x1="581" y1="774" x2="581" y2="794" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
||||||
|
|
||||||
|
<!-- Check critical failures -->
|
||||||
|
<g class="c-gray">
|
||||||
|
<rect x="46" y="794" width="590" height="44" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="341" y="812" text-anchor="middle" dominant-baseline="central">Check critical failures</text>
|
||||||
|
<text class="ts" x="341" y="828" text-anchor="middle" dominant-baseline="central">SDRAM || QSPI || uSD FAIL → abort HIL, report immediately</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<line x1="340" y1="866" x2="340" y2="890" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
||||||
|
|
||||||
|
<!-- HIL block -->
|
||||||
|
<rect x="30" y="890" width="620" height="78" rx="10" class="box-dashed"/>
|
||||||
|
<text class="label-section" x="56" y="906">HIL tests · requires Firefly</text>
|
||||||
|
|
||||||
|
<g class="c-teal">
|
||||||
|
<rect x="46" y="912" width="100" height="44" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="96" y="930" text-anchor="middle" dominant-baseline="central">CAN</text>
|
||||||
|
<text class="ts" x="96" y="946" text-anchor="middle" dominant-baseline="central">full-duplex</text>
|
||||||
|
</g>
|
||||||
|
<g class="c-teal">
|
||||||
|
<rect x="156" y="912" width="100" height="44" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="206" y="930" text-anchor="middle" dominant-baseline="central">UART TTL</text>
|
||||||
|
<text class="ts" x="206" y="946" text-anchor="middle" dominant-baseline="central">echo</text>
|
||||||
|
</g>
|
||||||
|
<g class="c-teal">
|
||||||
|
<rect x="266" y="912" width="100" height="44" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="316" y="930" text-anchor="middle" dominant-baseline="central">UART ISO</text>
|
||||||
|
<text class="ts" x="316" y="946" text-anchor="middle" dominant-baseline="central">RX only</text>
|
||||||
|
</g>
|
||||||
|
<g class="c-teal">
|
||||||
|
<rect x="376" y="912" width="100" height="44" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="426" y="930" text-anchor="middle" dominant-baseline="central">Opto-in</text>
|
||||||
|
<text class="ts" x="426" y="946" text-anchor="middle" dominant-baseline="central">all channels</text>
|
||||||
|
</g>
|
||||||
|
<g class="c-gray">
|
||||||
|
<rect x="486" y="912" width="100" height="44" rx="6" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="536" y="930" text-anchor="middle" dominant-baseline="central">IR burst</text>
|
||||||
|
<text class="ts" x="536" y="946" text-anchor="middle" dominant-baseline="central">38 kHz RX</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<line x1="340" y1="968" x2="340" y2="996" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
||||||
|
|
||||||
|
<!-- Summary -->
|
||||||
|
<g class="c-teal">
|
||||||
|
<rect x="160" y="996" width="360" height="50" rx="8" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="340" y="1016" text-anchor="middle" dominant-baseline="central">Summary report</text>
|
||||||
|
<text class="ts" x="340" y="1034" text-anchor="middle" dominant-baseline="central">JSON · pass/fail/skip per test · overall</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<line x1="340" y1="1046" x2="340" y2="1074" class="arr" marker-end="url(#arrow)" stroke="#aaa" stroke-dasharray="4 3"/>
|
||||||
|
|
||||||
|
<!-- Provisioning -->
|
||||||
|
<g class="c-purple">
|
||||||
|
<rect x="160" y="1074" width="360" height="50" rx="8" stroke-width="0.5"/>
|
||||||
|
<text class="th" x="340" y="1094" text-anchor="middle" dominant-baseline="central">Provisioning</text>
|
||||||
|
<text class="ts" x="340" y="1112" text-anchor="middle" dominant-baseline="central">OCOTP chip_uid → host DB · only on pass</text>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<text class="ts" x="536" y="1050" fill="#aaa">only if overall == pass</text>
|
||||||
|
|
||||||
|
<!-- Legend -->
|
||||||
|
<rect x="30" y="1140" width="260" height="30" rx="6" fill="none" stroke="#eee" stroke-width="0.5"/>
|
||||||
|
<rect x="40" y="1151" width="12" height="8" rx="2" fill="#FCEBEB" stroke="#A32D2D" stroke-width="0.5"/>
|
||||||
|
<text class="ts" x="58" y="1159">critical test</text>
|
||||||
|
<rect x="130" y="1151" width="12" height="8" rx="2" fill="#E1F5EE" stroke="#0F6E56" stroke-width="0.5"/>
|
||||||
|
<text class="ts" x="148" y="1159">HIL test</text>
|
||||||
|
<rect x="200" y="1151" width="12" height="8" rx="2" fill="#FAEEDA" stroke="#854F0B" stroke-width="0.5"/>
|
||||||
|
<text class="ts" x="218" y="1159">operator</text>
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -30,7 +30,7 @@ int main(void)
|
||||||
{
|
{
|
||||||
BOARD_Init();
|
BOARD_Init();
|
||||||
GPIO_PinWrite(BOARD_INITPINS_UserLed1_PORT, BOARD_INITPINS_UserLed1_PIN, 0);
|
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)
|
while (1)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,20 +117,22 @@ test-host-release: _configure-host-release
|
||||||
hab-firmware-test-debug: build-firmware-test-debug
|
hab-firmware-test-debug: build-firmware-test-debug
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
mkdir -p "{{BUILD_DIR}}/Debug"
|
||||||
cd "{{TOOLS_DIR}}/hab"
|
cd "{{TOOLS_DIR}}/hab"
|
||||||
uv run nxpimage hab export --force \
|
uv run nxpimage hab export --force \
|
||||||
-c hab_firmware_test_debug.yaml \
|
-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)"
|
echo " ✅ firmware_test_hab.bin (Debug)"
|
||||||
|
|
||||||
[group('hab_image_gen')]
|
[group('hab_image_gen')]
|
||||||
hab-firmware-test-release: build-firmware-test-release
|
hab-firmware-test-release: build-firmware-test-release
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
mkdir -p "{{BUILD_DIR}}/Release"
|
||||||
cd "{{TOOLS_DIR}}/hab"
|
cd "{{TOOLS_DIR}}/hab"
|
||||||
uv run nxpimage hab export --force \
|
uv run nxpimage hab export --force \
|
||||||
-c hab_firmware_test_release.yaml \
|
-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)"
|
echo " ✅ firmware_test_hab.bin (Release)"
|
||||||
|
|
||||||
# ── bootloader ───────────────────────────────────────────────────────────────
|
# ── bootloader ───────────────────────────────────────────────────────────────
|
||||||
|
|
@ -138,20 +140,22 @@ hab-firmware-test-release: build-firmware-test-release
|
||||||
hab-bootloader-debug: build-bootloader-debug
|
hab-bootloader-debug: build-bootloader-debug
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
mkdir -p "{{BUILD_DIR}}/Debug"
|
||||||
cd "{{TOOLS_DIR}}/hab"
|
cd "{{TOOLS_DIR}}/hab"
|
||||||
uv run nxpimage hab export --force \
|
uv run nxpimage hab export --force \
|
||||||
-c hab_bootloader_debug.yaml \
|
-c hab_bootloader_debug.yaml \
|
||||||
-o "../../../{{BUILD_DIR}}/Debug/bootloader_hab.bin"
|
-o "{{BUILD_DIR}}/Debug/bootloader_hab.bin"
|
||||||
echo " ✅ bootloader_hab.bin (Debug)"
|
echo " ✅ bootloader_hab.bin (Debug)"
|
||||||
|
|
||||||
[group('hab_image_gen')]
|
[group('hab_image_gen')]
|
||||||
hab-bootloader-release: build-bootloader-release
|
hab-bootloader-release: build-bootloader-release
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
mkdir -p "{{BUILD_DIR}}/Release"
|
||||||
cd "{{TOOLS_DIR}}/hab"
|
cd "{{TOOLS_DIR}}/hab"
|
||||||
uv run nxpimage hab export --force \
|
uv run nxpimage hab export --force \
|
||||||
-c hab_bootloader_release.yaml \
|
-c hab_bootloader_release.yaml \
|
||||||
-o "../../../{{BUILD_DIR}}/Release/bootloader_hab.bin"
|
-o "{{BUILD_DIR}}/Release/bootloader_hab.bin"
|
||||||
echo " ✅ bootloader_hab.bin (Release)"
|
echo " ✅ bootloader_hab.bin (Release)"
|
||||||
|
|
||||||
# ── app ───────────────────────────────────────────────────────────────────────
|
# ── app ───────────────────────────────────────────────────────────────────────
|
||||||
|
|
@ -159,20 +163,22 @@ hab-bootloader-release: build-bootloader-release
|
||||||
hab-app-debug: build-app-debug
|
hab-app-debug: build-app-debug
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
mkdir -p "{{BUILD_DIR}}/Debug"
|
||||||
cd "{{TOOLS_DIR}}/hab"
|
cd "{{TOOLS_DIR}}/hab"
|
||||||
uv run nxpimage hab export --force \
|
uv run nxpimage hab export --force \
|
||||||
-c hab_app_debug.yaml \
|
-c hab_app_debug.yaml \
|
||||||
-o "../../../{{BUILD_DIR}}/Debug/app_hab.bin"
|
-o "{{BUILD_DIR}}/Debug/app_hab.bin"
|
||||||
echo " ✅ app_hab.bin (Debug)"
|
echo " ✅ app_hab.bin (Debug)"
|
||||||
|
|
||||||
[group('hab_image_gen')]
|
[group('hab_image_gen')]
|
||||||
hab-app-release: build-app-release
|
hab-app-release: build-app-release
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
mkdir -p "{{BUILD_DIR}}/Release"
|
||||||
cd "{{TOOLS_DIR}}/hab"
|
cd "{{TOOLS_DIR}}/hab"
|
||||||
uv run nxpimage hab export --force \
|
uv run nxpimage hab export --force \
|
||||||
-c hab_app_release.yaml \
|
-c hab_app_release.yaml \
|
||||||
-o "../../../{{BUILD_DIR}}/Release/app_hab.bin"
|
-o "{{BUILD_DIR}}/Release/app_hab.bin"
|
||||||
echo " ✅ app_hab.bin (Release)"
|
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":
|
hab-verify project="firmware_test" type="release":
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ROOT="$(pwd)"
|
|
||||||
case "{{project}}-{{type}}" in
|
case "{{project}}-{{type}}" in
|
||||||
firmware_test-debug) BIN="${ROOT}/{{BUILD_DIR}}/Debug/firmware_test_hab.bin" ;;
|
firmware_test-debug) BIN="{{BUILD_DIR}}/Debug/firmware_test_hab.bin" ;;
|
||||||
firmware_test-release) BIN="${ROOT}/{{BUILD_DIR}}/Release/firmware_test_hab.bin" ;;
|
firmware_test-release) BIN="{{BUILD_DIR}}/Release/firmware_test_hab.bin" ;;
|
||||||
bootloader-debug) BIN="${ROOT}/{{BUILD_DIR}}/Debug/bootloader_hab.bin" ;;
|
bootloader-debug) BIN="{{BUILD_DIR}}/Debug/bootloader_hab.bin" ;;
|
||||||
bootloader-release) BIN="${ROOT}/{{BUILD_DIR}}/Release/bootloader_hab.bin" ;;
|
bootloader-release) BIN="{{BUILD_DIR}}/Release/bootloader_hab.bin" ;;
|
||||||
app-debug) BIN="${ROOT}/{{BUILD_DIR}}/Debug/app_hab.bin" ;;
|
app-debug) BIN="{{BUILD_DIR}}/Debug/app_hab.bin" ;;
|
||||||
app-release) BIN="${ROOT}/{{BUILD_DIR}}/Release/app_hab.bin" ;;
|
app-release) BIN="{{BUILD_DIR}}/Release/app_hab.bin" ;;
|
||||||
*)
|
*)
|
||||||
echo " ❌ Unknown: {{project}}-{{type}}"
|
echo " ❌ Unknown: {{project}}-{{type}}"
|
||||||
echo " Projects: firmware_test, bootloader, app"
|
echo " Projects: firmware_test, bootloader, app"
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@
|
||||||
TOOLS_DIR := env('TOOLS_DIR', justfile_directory() / 'tools/host')
|
TOOLS_DIR := env('TOOLS_DIR', justfile_directory() / 'tools/host')
|
||||||
CACHE_DIR := env('CACHE_DIR', justfile_directory() / '.cache')
|
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"
|
JUST_MIN := "1.36.0"
|
||||||
UV_MIN := "0.4.0"
|
UV_MIN := "0.4.0"
|
||||||
|
|
@ -253,7 +256,7 @@ flash project type="release":
|
||||||
echo " Valid: debug, release"
|
echo " Valid: debug, release"
|
||||||
exit 1 ;;
|
exit 1 ;;
|
||||||
esac
|
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}"
|
--firmware "{{project}}" --build-type "${BUILD_TYPE}"
|
||||||
|
|
||||||
# Загрузить образ в RAM без записи во Flash.
|
# Загрузить образ в RAM без записи во Flash.
|
||||||
|
|
@ -271,7 +274,7 @@ flash-ram project type="debug":
|
||||||
echo " Valid: debug, release"
|
echo " Valid: debug, release"
|
||||||
exit 1 ;;
|
exit 1 ;;
|
||||||
esac
|
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
|
--firmware "{{project}}" --build-type "${BUILD_TYPE}" --ram-only
|
||||||
|
|
||||||
# Псевдонимы для частых сценариев ────────────────────────────────────────────
|
# Псевдонимы для частых сценариев ────────────────────────────────────────────
|
||||||
|
|
@ -302,7 +305,7 @@ incoming: flash-test-release
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
echo " ▶ Running HIL tests (CAN, UART, SDRAM, SPI Flash)..."
|
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"
|
echo " ⚠️ HIL tests not yet implemented"
|
||||||
|
|
||||||
# Финальная прошивка: загрузчик + основная прошивка (Release)
|
# Финальная прошивка: загрузчик + основная прошивка (Release)
|
||||||
|
|
@ -317,17 +320,17 @@ production: flash-production
|
||||||
# Найти подключённые NXP USB-устройства
|
# Найти подключённые NXP USB-устройства
|
||||||
[group('util')]
|
[group('util')]
|
||||||
scan:
|
scan:
|
||||||
cd "{{TOOLS_DIR}}" && uv run nxpdevscan
|
cd "{{TOOLS_DIR}}" && UV_PROJECT_ENVIRONMENT={{_venv}} uv run nxpdevscan
|
||||||
|
|
||||||
# Проверить связь с BootROM через SDP (плата в SDP-режиме)
|
# Проверить связь с BootROM через SDP (плата в SDP-режиме)
|
||||||
[group('util'), no-cd]
|
[group('util'), no-cd]
|
||||||
sdp-status:
|
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)
|
# Проверить что Flashloader отвечает (после jump-address)
|
||||||
[group('util')]
|
[group('util')]
|
||||||
flashloader-status:
|
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 до новой версии
|
# Обновить spsdk до новой версии
|
||||||
# Использование: just upgrade-tools 3.8.0
|
# Использование: just upgrade-tools 3.8.0
|
||||||
|
|
|
||||||
|
|
@ -17,24 +17,43 @@ flash_usb.py — прошивка MIMXRT1052 через USB (BootROM SDP → Fla
|
||||||
→ blhost стирает нужный регион Flash
|
→ blhost стирает нужный регион Flash
|
||||||
→ blhost пишет HAB образ начиная с 0x60002000
|
→ blhost пишет HAB образ начиная с 0x60002000
|
||||||
→ blhost reset
|
→ 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: <repo_root>/build)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# ─── Константы ────────────────────────────────────────────────────────────────
|
# ─── Пути ─────────────────────────────────────────────────────────────────────
|
||||||
SCRIPT_DIR = Path(__file__).parent.resolve()
|
SCRIPT_DIR = Path(__file__).parent.resolve()
|
||||||
REPO_ROOT = SCRIPT_DIR.parent.parent
|
REPO_ROOT = SCRIPT_DIR.parent.parent
|
||||||
BUILD_DIR = REPO_ROOT / "build"
|
|
||||||
FLASHLOADER = SCRIPT_DIR / "dcd" / "ivt_flashloader.bin"
|
FLASHLOADER = SCRIPT_DIR / "dcd" / "ivt_flashloader.bin"
|
||||||
|
|
||||||
# USB VID:PID
|
# BUILD_DIR: берём из окружения (just экспортирует из .env как абсолютный путь),
|
||||||
SDP_USB = "0x1FC9,0x0130" # BootROM Serial Download Protocol
|
# fallback — рассчитываем от расположения скрипта
|
||||||
BLHOST_USB = "0x15A2,0x0073" # Flashloader запущен
|
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
|
# FlexSPI NOR config option word: 0xC0000007
|
||||||
# bits[31:28]=0xC — tag (QuadSPI NOR)
|
# bits[31:28]=0xC — tag (QuadSPI NOR)
|
||||||
# bits[3:0]=0x7 — option size
|
# bits[3:0]=0x7 — option size
|
||||||
|
|
@ -42,10 +61,12 @@ FLEXSPI_OPTION_ADDR = "0x2000"
|
||||||
FLEXSPI_OPTION_VALUE = "0xC0000007"
|
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 layout
|
||||||
FLASH_BASE = 0x60000000
|
FLASH_BASE = 0x60000000
|
||||||
HAB_OFFSET = 0x1000 # IVT offset: HAB binary starts with IVT at byte 0
|
HAB_OFFSET = 0x1000 # IVT offset: write address = FLASH_BASE + HAB_OFFSET
|
||||||
# write address = FLASH_BASE + ivtOffset = 0x60001000
|
|
||||||
|
|
||||||
|
|
||||||
def run(cmd: list[str], check: bool = True) -> subprocess.CompletedProcess:
|
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:
|
def wait_for_flashloader(timeout: int = 10) -> bool:
|
||||||
"""Ждём пока Flashloader поднимется (VID:PID 15A2:0073)"""
|
"""Ждём пока Flashloader поднимется"""
|
||||||
print(f"\n Ожидание Flashloader (до {timeout}с)...", end="", flush=True)
|
print(f"\n Ожидание Flashloader (до {timeout}с)...", end="", flush=True)
|
||||||
for i in range(timeout):
|
for i in range(timeout):
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
@ -82,7 +103,6 @@ def wait_for_flashloader(timeout: int = 10) -> bool:
|
||||||
|
|
||||||
def load_flashloader() -> None:
|
def load_flashloader() -> None:
|
||||||
"""Загружает Flashloader через SDP если ещё не запущен"""
|
"""Загружает Flashloader через SDP если ещё не запущен"""
|
||||||
# Проверяем — вдруг уже запущен
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["blhost", "-u", BLHOST_USB, "-j", "--", "get-property", "1", "0"],
|
["blhost", "-u", BLHOST_USB, "-j", "--", "get-property", "1", "0"],
|
||||||
capture_output=True
|
capture_output=True
|
||||||
|
|
@ -108,11 +128,6 @@ def load_flashloader() -> None:
|
||||||
sys.exit(1)
|
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:
|
def configure_flexspi() -> None:
|
||||||
"""Инициализирует FlexSPI NOR контроллер через Flashloader"""
|
"""Инициализирует FlexSPI NOR контроллер через Flashloader"""
|
||||||
step("Конфигурация FlexSPI NOR (инициализация контроллера)")
|
step("Конфигурация FlexSPI NOR (инициализация контроллера)")
|
||||||
|
|
@ -144,7 +159,6 @@ def flash(hab_bin: Path, ram_only: bool = False) -> None:
|
||||||
|
|
||||||
if ram_only:
|
if ram_only:
|
||||||
step(f"Загрузка в RAM (без записи во Flash): {hab_bin.name}")
|
step(f"Загрузка в RAM (без записи во Flash): {hab_bin.name}")
|
||||||
# RAM-only: грузим в RAM прямо через SDP без flashloader
|
|
||||||
run(["sdphost", "-u", SDP_USB, "-j", "--",
|
run(["sdphost", "-u", SDP_USB, "-j", "--",
|
||||||
"write-file", f"0x{FLASH_BASE + HAB_OFFSET:08X}", str(hab_bin)])
|
"write-file", f"0x{FLASH_BASE + HAB_OFFSET:08X}", str(hab_bin)])
|
||||||
run(["sdphost", "-u", SDP_USB, "-j", "--",
|
run(["sdphost", "-u", SDP_USB, "-j", "--",
|
||||||
|
|
@ -152,10 +166,9 @@ def flash(hab_bin: Path, ram_only: bool = False) -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
load_flashloader()
|
load_flashloader()
|
||||||
configure_flexspi() # 0xC0000007 — инициализация FlexSPI контроллера
|
configure_flexspi()
|
||||||
|
|
||||||
write_addr = f"0x{FLASH_BASE + HAB_OFFSET:08X}"
|
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
|
erase_size = ((HAB_OFFSET + hab_bin.stat().st_size + 0xFFF) // 0x1000) * 0x1000
|
||||||
|
|
||||||
step(f"Прошивка Flash: {hab_bin.name}")
|
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" Адрес: {write_addr}")
|
||||||
print(f" Стирание: 0x{FLASH_BASE:08X} .. +{erase_size} байт")
|
print(f" Стирание: 0x{FLASH_BASE:08X} .. +{erase_size} байт")
|
||||||
|
|
||||||
# memoryId=0 — mapped (XIP) адресное пространство
|
|
||||||
run(["blhost", "-u", BLHOST_USB, "--",
|
run(["blhost", "-u", BLHOST_USB, "--",
|
||||||
"flash-erase-region", f"0x{FLASH_BASE:08X}",
|
"flash-erase-region", f"0x{FLASH_BASE:08X}",
|
||||||
str(erase_size), "0"])
|
str(erase_size), "0"])
|
||||||
|
|
||||||
write_fcb() # 0xF000000F — запись FCB в 0x60000000 (после erase!)
|
write_fcb()
|
||||||
|
|
||||||
# memoryId=0 — mapped (XIP) адресное пространство
|
|
||||||
run(["blhost", "-u", BLHOST_USB, "--",
|
run(["blhost", "-u", BLHOST_USB, "--",
|
||||||
"write-memory", write_addr, str(hab_bin), "0"])
|
"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:
|
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 = argparse.ArgumentParser(description="Прошивка MIMXRT1052 через USB")
|
||||||
parser.add_argument("--firmware", required=True,
|
parser.add_argument("--firmware", required=True,
|
||||||
choices=["firmware_test", "bootloader", "app"],
|
choices=["firmware_test", "bootloader", "app"],
|
||||||
|
|
@ -192,14 +206,14 @@ def main() -> None:
|
||||||
help="Загрузить в RAM без записи во Flash")
|
help="Загрузить в RAM без записи во Flash")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# HAB образ собирается командой: nxpimage hab export -c hab_<firmware>.yaml
|
|
||||||
# Выходной файл: build/<BuildType>/<firmware>_hab.bin
|
|
||||||
hab_bin = BUILD_DIR / args.build_type / f"{args.firmware}_hab.bin"
|
hab_bin = BUILD_DIR / args.build_type / f"{args.firmware}_hab.bin"
|
||||||
|
|
||||||
print(f"\n{'═'*60}")
|
print(f"\n{'═'*60}")
|
||||||
print(f" MIMXRT1052 Flash Tool")
|
print(f" MIMXRT1052 Flash Tool")
|
||||||
print(f" Прошивка: {args.firmware} [{args.build_type}]")
|
print(f" Прошивка: {args.firmware} [{args.build_type}]")
|
||||||
print(f" Образ: {hab_bin}")
|
print(f" Образ: {hab_bin}")
|
||||||
|
print(f" SDP USB: {SDP_USB}")
|
||||||
|
print(f" BL USB: {BLHOST_USB}")
|
||||||
print(f"{'═'*60}")
|
print(f"{'═'*60}")
|
||||||
|
|
||||||
flash(hab_bin, ram_only=args.ram_only)
|
flash(hab_bin, ram_only=args.ram_only)
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,4 @@ requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"spsdk==3.7.0",
|
"spsdk==3.7.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue