Кастомный скрипт в tools/host для прошивки bare-metal платы
This commit is contained in:
Dmitry Akimov 2026-03-10 18:16:57 +03:00
parent 853eda2d08
commit 3940947963
19 changed files with 640 additions and 952 deletions

View file

@ -16,8 +16,9 @@ set_source_files_properties(board/board.c board/pin_mux.c
PROPERTIES COMPILE_OPTIONS "-w") PROPERTIES COMPILE_OPTIONS "-w")
# Дефайны PUBLIC — транзитивно подтянутся во все firmware таргеты TODO: для # Дефайны PUBLIC — транзитивно подтянутся во все firmware таргеты TODO: для
# каждого приложения свои дефайны # каждого приложения свои дефайны
target_compile_definitions(bsp_board PUBLIC CPU_MIMXRT1052CVJ5B target_compile_definitions(
SKIP_SYSCLK_INIT) bsp_board PUBLIC CPU_MIMXRT1052CVJ5B SKIP_SYSCLK_INIT XIP_EXTERNAL_FLASH=1
XIP_BOOT_HEADER_ENABLE=1 XIP_BOOT_HEADER_DCD_ENABLE=1)
# Сценарий A: XIP — код исполняется прямо из Flash # Сценарий A: XIP — код исполняется прямо из Flash
add_library(bsp_boot_xip INTERFACE) add_library(bsp_boot_xip INTERFACE)

223
docs/HAB_GUIDE.md Normal file
View file

@ -0,0 +1,223 @@
# High Assurance Boot (HAB) — Принцип работы и применение на MIMXRT1052
## 1. Что такое HAB
**High Assurance Boot (HAB)** — механизм безопасной загрузки, встроенный в BootROM микроконтроллеров семейства NXP i.MX/RT. Его цель — криптографически гарантировать, что на чипе запустится только авторизованный код.
HAB работает на уровне кремния: BootROM прошит на заводе и не может быть изменён. Вся цепочка доверия строится от него.
---
## 2. Угрозы без HAB
Без механизма защищённой загрузки любой, имеющий физический доступ к устройству, может:
- подключить программатор и залить произвольную прошивку
- загрузить вредоносный код через USB в режиме Serial Download Protocol (SDP)
- модифицировать прошивку во Flash между производством и эксплуатацией
HAB устраняет эти угрозы на аппаратном уровне.
---
## 3. Структура HAB-образа
HAB-образ — это не просто бинарник прошивки, а обёртка с метаданными вокруг него. BootрOM ищет обязательные структуры по фиксированным смещениям.
### Карта памяти Flash (FlexSPI NOR, XIP)
```bash
0x60000000 ┌──────────────────────────────────┐
│ FCB — Flash Configuration Block │ 512 байт
│ (конфигурация FlexSPI контроллера│
│ записывается Flashloader'ом) │
0x60001000 ├──────────────────────────────────┤
│ IVT — Image Vector Table │ 32 байта
0x60001020 ├──────────────────────────────────┤
│ BDT — Boot Data Table │ 12 байт
0x60001030 ├──────────────────────────────────┤
│ DCD — Device Config Data │ опционально
│ (инициализация SDRAM и др.) │
├──────────────────────────────────┤
0x60003000 │ Код прошивки (.text, .data, ...) │
│ │
├──────────────────────────────────┤
│ CSF — Command Sequence File │ только в signed-образе
│ (подпись, ключи, хэши) │
└──────────────────────────────────┘
```
### 3.1 IVT — Image Vector Table
Главная точка входа для BootROM. Структура фиксирована, 32 байта, тег `0xD1`:
```c
typedef struct {
uint32_t header; // тег 0xD1, длина, версия
uint32_t entry; // адрес Reset_Handler прошивки
uint32_t reserved1;
uint32_t dcd; // адрес DCD (0 если не нужен)
uint32_t boot_data; // адрес BDT
uint32_t self; // адрес самого IVT
uint32_t csf; // адрес CSF (0 если unsigned)
uint32_t reserved2;
} ivt_t;
```
BootROM находит IVT по фиксированному смещению от начала Flash (`ivtOffset = 0x1000` для FlexSPI NOR), читает его и получает адреса всех остальных структур.
### 3.2 BDT — Boot Data Table
Сообщает BootROM, где начинается образ и каков его полный размер:
```c
typedef struct {
uint32_t start; // 0x60000000 — начало образа во Flash
uint32_t length; // полный размер в байтах
uint32_t plugin; // 0 для обычного образа
} boot_data_t;
```
### 3.3 DCD — Device Configuration Data
Опциональная секция с командами инициализации периферии, которые BootROM выполняет **до** передачи управления прошивке. Типичное применение — инициализация SDRAM (SEMC), чтобы к моменту старта первой инструкции прошивки память уже была доступна.
DCD содержит последовательность команд двух типов:
| Команда | Тег | Назначение |
|---|---|---|
| `Write Data` | `0xCC` | Записать значение по адресу |
| `Check Bits Set` | `0xCF` | Ждать пока бит станет `1` (poll) |
Формат бинарника: `Tag(1) | Length(2 BE) | Parameter(1) | данные`.
### 3.4 CSF — Command Sequence File
Криптографическая секция. Присутствует только в подписанных образах. Содержит:
- таблицу публичных ключей (SRK table)
- хэши защищённых регионов образа (IVT, BDT, код)
- подпись RSA 2048/4096 или ECDSA
---
## 4. Режимы HAB
### 4.1 По типу образа
| Режим | `flags` | CSF | Применение |
|---|---|---|---|
| Unsigned | `0x00` | отсутствует | разработка, отладка |
| Signed | `0x08` | RSA/ECDSA подпись | производство |
| Signed + Encrypted | `0x0C` | подпись + шифрование кода | максимальная защита |
### 4.2 По состоянию чипа (OTP fuse)
Независимо от типа образа, чип может находиться в одном из двух состояний:
**HAB Open** (fuse `SEC_CONFIG = 0`, заводское состояние):
BootROM проверяет подпись, но **игнорирует ошибки** — запуск происходит в любом случае. Это позволяет работать с unsigned образами на этапе разработки.
**HAB Closed** (fuse `SEC_CONFIG = 1`):
При ошибке проверки подписи BootROM **отказывает в запуске** безвозвратно. Запись fuse необратима — чип нельзя вернуть в Open состояние.
---
## 5. Цепочка доверия (Trust Chain)
```bash
Производитель устройства
├── Генерирует пару ключей RSA 4096
│ private.pem → хранится в защищённом месте (HSM / vault)
│ public.pem → включается в образ (SRK table)
├── SHA256(public.pem) записывается в OTP fuses (SRK_HASH)
│ ↑ необратимо, выполняется один раз при производстве
└── При сборке каждого релиза:
nxpimage hab export (flags=0x08)
→ хэшируются IVT + BDT + код
→ подписывается private.pem
→ CSF встраивается в образ
При загрузке на чипе:
BootROM
→ читает IVT → находит CSF
→ извлекает public key из CSF
→ SHA256(public key) == SRK_HASH из fuses? → ДА
→ проверяет подпись хэшей регионов → ОК
→ прыгает на entry point
```
---
## 6. Почему Unsigned-образ требует HAB-контейнер
Распространённое заблуждение: "раз образ без подписи, зачем вообще HAB?". Ответ прост:
**BootROM всегда ищет IVT по фиксированному смещению.** Это не опциональная безопасность — это формат загрузки. Без IVT и BDT BootROM не знает:
- куда прыгать (entry point)
- где находится DCD для инициализации SDRAM
- каков размер образа
`nxpimage hab export` для unsigned образа выполняет именно эту работу — добавляет IVT, BDT, встраивает DCD — без какой-либо криптографии. CSF при этом равен нулю.
---
## 7. Процесс прошивки MIMXRT1052 (USB, без JTAG)
BootROM в режиме SDP умеет только писать в RAM и прыгать. Для записи во Flash необходим **Flashloader** — специальная программа от NXP.
```
Плата в SDP режиме (BOOT_MOD_1 = 3V3)
│ sdphost -u 0x1FC9,0x0130
├─ write-file 0x20001C00 ivt_flashloader.bin
└─ jump-address 0x20001C00
│ (ожидание ~3с инициализации)
Flashloader запущен
│ blhost -u 0x15A2,0x0073
├─ fill-memory 0x2000 4 0xC0000007 ← option word для FlexSPI NOR
├─ configure-memory 9 0x2000 ← Flashloader пишет FCB в Flash
├─ flash-erase-region 0x60000000 …
├─ write-memory 0x60002000 firmware_hab.bin ← HAB образ
└─ reset
```
---
## 8. Жизненный цикл для проекта TFT
| Стадия | Режим HAB | Подпись | Fuse |
|---|---|---|---|
| Разработка | Open | Unsigned (`0x00`) | не трогаем |
| Входной контроль | Open | Unsigned (`0x00`) | не трогаем |
| Предсерийные образцы | Open | Signed (`0x08`) | не трогаем |
| Серийное производство | **Closed** | Signed (`0x08`) | `SEC_CONFIG = 1` |
> ⚠️ Запись `SEC_CONFIG = 1` необратима. Перед закрытием HAB необходимо убедиться, что подписанный образ успешно проходит верификацию на реальном железе.
---
## 9. Инструменты
| Инструмент | Назначение |
|---|---|
| `nxpimage hab export` | Сборка HAB-образа (IVT + BDT + DCD + опционально CSF) |
| `nxpimage hab parse` | Разбор готового образа для проверки |
| `sdphost` | Связь с BootROM в SDP режиме (запись в RAM, прыжок) |
| `blhost` | Связь с Flashloader (запись во Flash, конфигурация памяти) |
| `nxpdevscan` | Обнаружение подключённых NXP устройств |
---
## 10. Литература
- **NXP AN12263** — "HAB Code Signing for i.MX RT" — пошаговая инструкция по подписи образов
- **MIMXRT1052 Reference Manual** — Chapter 9: System Boot (структуры IVT, BDT, DCD, CSF)
- [**SPSDK Documentation**](https://spsdk.readthedocs.io) — документация по nxpimage, blhost, sdphost
- **NXP Application Note AN4581** — "Secure Boot on i.MX50, i.MX53, and i.MX 6 Series"

View file

@ -13,7 +13,6 @@ target_sources(firmware_test PRIVATE main.c
target_link_libraries( target_link_libraries(
firmware_test firmware_test
PRIVATE bsp_board PRIVATE bsp_board
bsp_boot_xip
# SDK драйверы периферии — добавляй по мере написания тестов # SDK драйверы периферии — добавляй по мере написания тестов
sdk_lpuart # UART тест sdk_lpuart # UART тест
sdk_flexcan # CAN тест sdk_flexcan # CAN тест

View file

@ -11,7 +11,6 @@
#include "fsl_gpio.h" #include "fsl_gpio.h"
#include "pin_mux.h" #include "pin_mux.h"
/******************************************************************************* /*******************************************************************************
* Definitions * Definitions
******************************************************************************/ ******************************************************************************/

View file

@ -1,164 +1,257 @@
# Host Tools — окружение для прошивки # tools/host — Окружение прошивки MIMXRT1052CVJ5B
Изолированное Python-окружение на базе [uv](https://docs.astral.sh/uv/). Изолированное Python-окружение на базе [uv](https://docs.astral.sh/uv/) для сборки
Устанавливается на хост-машине разработчика — **не внутри devcontainer**. HAB-образов и прошивки платы через USB. Запускается на хост-машине разработчика —
**не внутри devcontainer**.
---
## Структура ## Структура
```bash ```bash
tools/host/ tools/host/
├── dcd/ ├── dcd/
│ ├── dcd_mt48lc16m16a2.cfg ← источник истины для DCD (коммитить) │ ├── dcd.bin ← DCD бинарник (инициализация SDRAM, коммитить как есть)
│ ├── dcd_compiler.py ← скрипт: .cfg → .bin │ └── ivt_flashloader.bin ← NXP Flashloader (коммитить)
│ └── dcd.bin ← артефакт (пересобирается из .cfg)
├── fdcb/
│ ├── fcb_w25q64_quad.yaml ← конфиг FCB: W25Q64, Quad I/O, 133 MHz
│ └── fcb.bin ← артефакт (пересобирается из .yaml)
├── hab/ ├── hab/
│ ├── hab_firmware_test.yaml ← HAB: Debug + DCD (входной контроль) │ ├── hab_firmware_test.yaml ← HAB: Debug + DCD (входной контроль)
│ ├── hab_bootloader.yaml ← HAB: Release, без DCD │ ├── hab_bootloader.yaml ← HAB: Release, без DCD
│ └── hab_app.yaml ← HAB: Release + DCD (боевая прошивка) │ └── hab_app.yaml ← HAB: Release + DCD (боевая прошивка)
├── flash_usb.py ← скрипт прошивки через USB
├── image/ ├── pyproject.toml ← зависимости (spsdk==3.7.x)
│ ├── bootimg_firmware_test.yaml
│ ├── bootimg_bootloader.yaml
│ └── bootimg_app.yaml
├── pyproject.toml ← зависимости (spsdk, версия зафиксирована)
├── uv.lock ← lockfile (коммитить) ├── uv.lock ← lockfile (коммитить)
├── .python-version
└── README.md └── README.md
``` ```
## Установка uv (один раз на машину) ## Предварительные требования
### 1. uv — один раз на машину
```bash ```bash
# Linux/macOS # macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows # Windows
powershell -c "irm https://astral.sh/uv/install.ps1 | iex" powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
``` ```
## Первый запуск (один раз на репозиторий) ### 2. Зависимости проекта — один раз на репозиторий
```bash ```bash
cd tools/host cd tools/host
uv sync uv sync
``` ```
### udev правило для USB SDP — только Linux, один раз на машину ### 3. udev правила — только Linux, один раз на машину
Без этого правила `blhost` требует `sudo`: Без этого для работы с USB нужен `sudo`:
```bash ```bash
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1fc9", ATTR{idProduct}=="0135", MODE="0666", GROUP="plugdev"' \ sudo tee /etc/udev/rules.d/99-nxp-mimxrt.rules << 'EOF'
| sudo tee /etc/udev/rules.d/99-nxp-sdp.rules # NXP BootROM — SDP режим (BOOT_MOD_1 = 3V3)
SUBSYSTEM=="usb", ATTR{idVendor}=="1fc9", ATTR{idProduct}=="0130", MODE="0666", GROUP="plugdev"
# NXP Flashloader — после jump-address
SUBSYSTEM=="usb", ATTR{idVendor}=="15a2", ATTR{idProduct}=="0073", MODE="0666", GROUP="plugdev"
EOF
sudo udevadm control --reload-rules && sudo udevadm trigger sudo udevadm control --reload-rules && sudo udevadm trigger
sudo usermod -a -G plugdev $USER sudo usermod -a -G plugdev $USER
# Перелогиниться после usermod! # После usermod — перелогиниться!
``` ```
## Пайплайн сборки загрузочного образа ---
### Шаг 1 — DCD (пересобирать при изменении dcd_mt48lc16m16a2.cfg) ## Карта Flash (W25Q64FVSSIG, 8 MB)
```bash ```bash
cd tools/host/dcd 0x60000000 ┌─────────────────────────────┐
uv run python3 dcd_compiler.py dcd_mt48lc16m16a2.cfg dcd.bin │ FCB — Flash Config Block │ 512 байт
│ (пишет Flashloader, │
│ не входит в HAB образ) │
0x60001000 ├─────────────────────────────┤
│ IVT — Image Vector Table │ ← начало HAB образа
│ BDT — Boot Data Table │
0x60001040 ├─────────────────────────────┤
│ DCD — SDRAM init │ ~1088 байт (для fw_test и app)
0x60003000 ├─────────────────────────────┤
│ Код прошивки │
│ (.text, .data, ...) │
└─────────────────────────────┘
``` ```
### Шаг 2 — FCB (пересобирать при изменении fcb_w25q64_quad.yaml) ---
## Пайплайн: сборка HAB образа
```bash ```bash
cd tools/host/fdcb cd tools/host/hab
uv run nxpimage bootable-image fcb export -c fcb_w25q64_quad.yaml -o fcb.bin
```
### Шаг 3 — Загрузочный образ # firmware_test (Debug) — входной контроль платы
uv run nxpimage hab export --force \
```bash -c hab_firmware_test.yaml \
cd tools/host/image -o ../../../../build/Debug/firmware_test_hab.bin
# firmware_test (Debug, входной контроль)
uv run nxpimage bootable-image export -c bootimg_firmware_test.yaml
# bootloader (Release) # bootloader (Release)
uv run nxpimage bootable-image export -c bootimg_bootloader.yaml uv run nxpimage hab export --force \
-c hab_bootloader.yaml \
-o ../../../../build/Release/bootloader_hab.bin
# app (Release, боевая прошивка) # app (Release) — боевая прошивка
uv run nxpimage bootable-image export -c bootimg_app.yaml uv run nxpimage hab export --force \
-c hab_app.yaml \
-o ../../../../build/Release/app_hab.bin
``` ```
Артефакты попадают в `build/Debug/` и `build/Release/` соответственно. ### Проверка образа после сборки
### Проверка образа
```bash ```bash
cd tools/host/image uv run nxpimage hab parse -b ../../../../build/Debug/firmware_test_hab.bin
uv run nxpimage bootable-image parse \
-b ../../../../build/Debug/firmware_test_bootable.bin \
-f mimxrt1050
``` ```
В выводе должны присутствовать секции `FCB`, `IVT`, `BDT`, `DCD`, `App`. Ожидаемый результат: IVT с корректным `entry`, DCD с тегом `0xD2`,
`csf = 0x00000000` (unsigned).
## Прошивка через USB (BootROM SDP режим) ---
Перевести плату в SDP режим: `BOOT_MOD_1 = 1` (подтянуть к 3V3), затем подать питание или нажать Reset. ## Пайплайн: прошивка через USB
### Подготовка платы
Перевести плату в режим SDP (Serial Download Protocol):
1. Подтянуть `BOOT_MOD_1` к `3V3`
2. Reset
3. Подключить USB к ПК
В этом режиме плата определяется как `VID:PID = 1FC9:0130`.
### Запуск скрипта
```bash ```bash
cd tools/host cd tools/host
# Проверить что устройство определилось (VID:PID 1FC9:0135) # firmware_test
export VID_PID=0x1fc9:0x0130 uv run python3 flash_usb.py --firmware firmware_test --build-type Debug
uv run nxpdevscan
# Версия BootROM на таргете # app
uv run blhost -u $VID_PID get-property 1 uv run python3 flash_usb.py --firmware app --build-type Release
# Доступные BootROM области памяти
uv run blhost -u $VID_PID list-memory
# Прошить firmware_test # bootloader
uv run blhost -u $VID_PID flash-erase-all uv run python3 flash_usb.py --firmware bootloader --build-type Release
uv run blhost -u $VID_PID write-memory 0x60000000 \
../../build/Debug/firmware_test_bootable.bin
uv run blhost -u $VID_PID reset
# Загрузить в RAM без записи во Flash (для быстрой отладки)
uv run blhost -u $VID_PID load-image ../../build/Debug/firmware_test_bootable.bin
``` ```
После прошивки вернуть `BOOT_MOD_1 = 0` (подтянуть к GND) для нормального старта из Flash. После прошивки: вернуть `BOOT_MOD_1` к `GND`, Reset — плата стартует из Flash.
## Удалённая прошивка (сервер) ### Последовательность команд внутри скрипта
```bash ```bash
# Из devcontainer или локально — копируем образ на сервер и прошиваем Плата в SDP режиме (1FC9:0130)
./tools/host/flash_remote.sh firmware_test Debug
├─ sdphost write-file 0x20001C00 ivt_flashloader.bin
└─ sdphost jump-address 0x20001C00
│ (ожидание до 10с пока Flashloader поднимется на 15A2:0073)
Flashloader (15A2:0073)
│ Шаг 1 — инициализация FlexSPI NOR контроллера
├─ blhost fill-memory 0x2000 4 0xC0000007 word
├─ blhost configure-memory 9 0x2000
│ Шаг 2 — стирание Flash
├─ blhost flash-erase-region 0x60000000 <size> 0 (memoryId=0, XIP)
│ Шаг 3 — запись FCB в 0x60000000
│ (ПОСЛЕ стирания! Flashloader генерирует FCB из параметров шага 1)
├─ blhost fill-memory 0x2000 4 0xF000000F word
├─ blhost configure-memory 9 0x2000
│ Шаг 4 — запись HAB образа начиная с 0x60001000
├─ blhost write-memory 0x60001000 *_hab.bin 0 (memoryId=0, XIP)
└─ blhost reset
``` ```
> **Почему FCB не в образе?**
> Flashloader генерирует FCB автоматически из параметров FlexSPI (option word
> `0xC0000007`). Запись FCB через `0xF000000F` — это отдельная команда,
> выполняемая строго после `flash-erase-region`, иначе FCB будет затёрт следующим
> стиранием.
---
## DCD — инициализация SDRAM
`dcd/dcd.bin` — бинарный блоб команд, который BootROM выполняет до передачи
управления прошивке. Инициализирует PLL, CCM clock gates, SEMC контроллер
и микросхему SDRAM (MT48LCxxM4).
Файл хранится в репозитории в бинарном виде и **не требует пересборки** — он
меняется только при изменении схемотехники.
```bash
Цель: MT48LC16M16A2P-6A, 32 MB, шина 16 бит, CS0
SEMC BR0: base=0x80000000, size=32MB, VLD=1
SEMC BR1BR3: VLD=0 (один чип, один CS)
```
Использование DCD по прошивкам:
| Прошивка | DCD | Причина |
|---|---|---|
| `firmware_test` | ✓ | тесты работают с SDRAM |
| `app` | ✓ | FreeRTOS heap и буферы LCDIF размещены в SDRAM |
| `bootloader` | ✗ | загрузчик не использует SDRAM, инициализация в app |
---
## Flashloader
`dcd/ivt_flashloader.bin` — NXP-программа, загружаемая в RAM через SDP.
Предоставляет `blhost` доступ к Flash, которого нет через BootROM напрямую.
```bash
Entry point: 0x20002401
Загрузка по: 0x20001C00
VID:PID после старта: 15A2:0073
Источник: MCUXpresso Secure Provisioning Tool 25.12
```
---
## Диагностика ## Диагностика
```bash ```bash
# Список поддерживаемых семейств (проверка установки) # Найти подключённые NXP устройства
uv run nxpimage bootable-image get-families uv run nxpdevscan
# Список доступных шаблонов для mimxrt1050 # Проверить связь с BootROM через SDP
uv run nxpimage bootable-image list-templates -f mimxrt1050 sdphost -u 0x1FC9,0x0130 -- error-status
# Версия SPSDK # Проверить что Flashloader отвечает (после jump-address)
blhost -u 0x15A2,0x0073 -- get-property 1 0
# Версии инструментов
uv run nxpimage --version uv run nxpimage --version
uv run blhost --version uv run blhost --version
``` ```
### Типичные ошибки
| Симптом | Причина | Решение |
|---|---|---|
| `USB HID device not found: 1FC9:0130` | Плата не в SDP режиме | Проверить `BOOT_MOD_1` → 3V3 и Reset |
| Flashloader timeout после jump | `ivt_flashloader.bin` повреждён или не тот | Взять из SPT 25.12 |
| Плата не стартует после прошивки | `BOOT_MOD_1` не переключён обратно | Вернуть `BOOT_MOD_1` → GND, Reset |
| Плата зависает сразу после старта | DCD завис (неверный `dcd.bin`) | Использовать только верифицированный `dcd.bin` |
| Плата стартует, периферия не работает | CCM clock gates в DCD отключают нужные клоки | Проверить `dcd.bin` — только верифицированный вариант |
---
## Обновление зависимостей ## Обновление зависимостей
```bash ```bash
cd tools/host cd tools/host
uv add "spsdk==X.Y.Z" uv add "spsdk==X.Y.Z"
uv sync uv sync
git add uv.lock git add uv.lock pyproject.toml
``` ```

Binary file not shown.

View file

@ -1,216 +0,0 @@
#!/usr/bin/env python3
"""
dcd_cfg_to_bin.py конвертация DCD .cfg (NXP Config Tools) в .bin (BootROM формат)
Формат DCD бинарника (IMX RT RM, Chapter 9.7):
[Header]
0xD2 DCD tag
length[1:0] общая длина в байтах (big-endian, 2 байта)
0x41 version
[Commands] последовательно:
Write Data (0xCC):
0xCC tag, length BE16, parameter (ширина: 4=word)
[addr BE32, value BE32] × N группируются подряд идущие записи
Check Bits Set (0xCF) wait_until (expr & mask) != 0:
0xCF tag, 0x000C length, parameter (4=word)
addr BE32, mask BE32, count BE32 (0 = бесконечно)
Использование:
python3 dcd_cfg_to_bin.py dcd.cfg dcd.bin
python3 dcd_cfg_to_bin.py dcd.cfg # выведет dcd.bin рядом с cfg
"""
import re
import struct
import sys
from pathlib import Path
from dataclasses import dataclass, field
from typing import Union
# ─── Константы DCD ────────────────────────────────────────────────────────────
DCD_TAG = 0xD2
DCD_VERSION = 0x41
CMD_WRITE = 0xCC
CMD_CHECK_SET = 0xCF # wait_until (reg & mask) != 0
CMD_CHECK_CLEAR = 0xD0 # wait_until (reg & mask) == 0
WIDTH_WORD = 0x04 # 32-bit
# ─── Типы команд ──────────────────────────────────────────────────────────────
@dataclass
class WriteEntry:
addr: int
value: int
@dataclass
class WriteCmd:
entries: list[WriteEntry] = field(default_factory=list)
def to_bytes(self) -> bytes:
length = 4 + len(self.entries) * 8
# NXP RM format: Tag(1) | Length(2 BE) | Parameter(1)
hdr = struct.pack(">BHB", CMD_WRITE, length, WIDTH_WORD)
data = b"".join(
struct.pack(">II", e.addr, e.value) for e in self.entries
)
return hdr + data
@dataclass
class CheckCmd:
addr: int
mask: int
check_set: bool = True # True = wait != 0, False = wait == 0
def to_bytes(self) -> bytes:
tag = CMD_CHECK_SET if self.check_set else CMD_CHECK_CLEAR
# NXP RM format: Tag(1) | Length(2 BE=12) | Parameter(1) | Addr(4) | Mask(4)
# Parameter: bits[2:0]=width(4=32bit), bit[3]=check_set → 0x1C / check_clear → 0x14
# No count field — poll indefinitely is the default (length=12, not 16)
param = 0x1C if self.check_set else 0x14
length = 12 # 4 header + 4 addr + 4 mask
return struct.pack(">BHBII", tag, length, param, self.addr, self.mask)
# ─── Парсер .cfg ─────────────────────────────────────────────────────────────
def parse_hex(s: str) -> int:
return int(s, 16) if s.startswith("0x") or s.startswith("0X") else int(s)
def parse_cfg(text: str) -> list[Union[WriteCmd, CheckCmd]]:
"""
Парсит .cfg и возвращает список команд DCD.
Соседние write-записи группируются в одну WriteCmd.
"""
# Убираем блочные комментарии /* ... */
text = re.sub(r"/\*.*?\*/", "", text, flags=re.DOTALL)
# Убираем строчные комментарии // ...
text = re.sub(r"//[^\n]*", "", text)
commands: list[Union[WriteCmd, CheckCmd]] = []
current_write: WriteCmd | None = None
# Паттерн: *(uint32_t*)0xADDR = 0xVALUE;
re_write = re.compile(
r"\*\s*\(\s*uint32_t\s*\*\s*\)\s*(0x[0-9A-Fa-f]+)\s*=\s*(0x[0-9A-Fa-f]+)\s*;"
)
# Паттерн: (*(uint32_t*)0xADDR & 0xMASK) != 0;
re_check_set = re.compile(
r"\(\s*\*\s*\(\s*uint32_t\s*\*\s*\)\s*(0x[0-9A-Fa-f]+)\s*&\s*(0x[0-9A-Fa-f]+)\s*\)\s*!=\s*0\s*;"
)
# Паттерн: (*(uint32_t*)0xADDR & 0xMASK) == 0;
re_check_clear = re.compile(
r"\(\s*\*\s*\(\s*uint32_t\s*\*\s*\)\s*(0x[0-9A-Fa-f]+)\s*&\s*(0x[0-9A-Fa-f]+)\s*\)\s*==\s*0\s*;"
)
for line in text.splitlines():
line = line.strip()
if not line:
continue
m = re_write.search(line)
if m:
addr = parse_hex(m.group(1))
value = parse_hex(m.group(2))
if current_write is None:
current_write = WriteCmd()
commands.append(current_write)
current_write.entries.append(WriteEntry(addr, value))
continue
m = re_check_set.search(line)
if m:
if current_write is not None:
current_write = None # разрываем группу write
addr = parse_hex(m.group(1))
mask = parse_hex(m.group(2))
commands.append(CheckCmd(addr, mask, check_set=True))
continue
m = re_check_clear.search(line)
if m:
if current_write is not None:
current_write = None
addr = parse_hex(m.group(1))
mask = parse_hex(m.group(2))
commands.append(CheckCmd(addr, mask, check_set=False))
continue
# Не write и не check — разрываем текущую write-группу
# (на случай если между записями есть другой синтаксис)
if current_write is not None:
current_write = None
return commands
# ─── Сборка бинарника ─────────────────────────────────────────────────────────
def build_dcd_binary(commands: list[Union[WriteCmd, CheckCmd]]) -> bytes:
body = b"".join(cmd.to_bytes() for cmd in commands)
total_length = 4 + len(body) # 4 байта header
# NXP RM format: Tag(1) | Length(2 BE) | Version(1)
header = struct.pack(">BHB", DCD_TAG, total_length, DCD_VERSION)
return header + body
# ─── Отчёт ────────────────────────────────────────────────────────────────────
def print_report(commands: list[Union[WriteCmd, CheckCmd]], out_size: int) -> None:
writes = sum(1 for c in commands if isinstance(c, WriteCmd))
checks = sum(1 for c in commands if isinstance(c, CheckCmd))
entries = sum(len(c.entries) for c in commands if isinstance(c, WriteCmd))
print(f"\n{''*52}")
print(f" DCD конвертация завершена")
print(f"{''*52}")
print(f" Write команд (групп) : {writes}")
print(f" Write записей всего : {entries}")
print(f" Check команд : {checks}")
print(f" Размер бинарника : {out_size} байт (0x{out_size:04X})")
print(f"{''*52}")
print("\n Команды:")
for i, cmd in enumerate(commands):
if isinstance(cmd, WriteCmd):
print(f" [{i:02d}] WRITE × {len(cmd.entries):3d} entries"
f" (0x{cmd.entries[0].addr:08X} … 0x{cmd.entries[-1].addr:08X})")
else:
kind = "CHECK_SET (!= 0)" if cmd.check_set else "CHECK_CLR (== 0)"
print(f" [{i:02d}] {kind} addr=0x{cmd.addr:08X} mask=0x{cmd.mask:08X}")
print()
# ─── main ─────────────────────────────────────────────────────────────────────
def main():
if len(sys.argv) < 2:
print(__doc__)
sys.exit(1)
cfg_path = Path(sys.argv[1])
if not cfg_path.exists():
print(f"[ERROR] Файл не найден: {cfg_path}")
sys.exit(1)
out_path = Path(sys.argv[2]) if len(sys.argv) >= 3 else cfg_path.with_suffix(".bin")
text = cfg_path.read_text(encoding="utf-8", errors="replace")
commands = parse_cfg(text)
if not commands:
print("[ERROR] Ни одной команды не найдено. Проверь формат .cfg файла.")
sys.exit(1)
binary = build_dcd_binary(commands)
out_path.write_bytes(binary)
print(f"[OK] Входной файл : {cfg_path}")
print(f"[OK] Выходной файл: {out_path}")
print_report(commands, len(binary))
if __name__ == "__main__":
main()

View file

@ -1,232 +0,0 @@
[DCD Descriptor]
// =============================================================================
// Clock Init
// MT48LC16M16A2 SDRAM on MIMXRT1052CVJ5B
//
// Включаем только необходимые тактовые домены (fix 6):
// CCM_CCGR0: биты [1:0]=11 (aips_tz1), биты [3:2]=11 (aips_tz2) — доступ к периферии
// CCM_CCGR2: биты [29:28]=11 (iomuxc) — конфигурация пинов
// CCM_CCGR3: биты [5:4]=11 (semc) — контроллер SDRAM
// CCM_CCGR4: биты [3:2]=11 (iomuxc_gpr) — GPR регистры
// CCM_CCGR5: биты [1:0]=11 (rom) — BootROM
//
// ВАЖНО: если после изменения плата не стартует — проверь по RM Table 18-5
// (CCM Clock Gating Register) и вернись к 0xFFFFFFFF для отладки.
// =============================================================================
*(uint32_t*)0x400FC068 = 0x0000000F; // CCM_CCGR0: aips_tz1 + aips_tz2
*(uint32_t*)0x400FC06C = 0x00000000; // CCM_CCGR1: не нужен для DCD
*(uint32_t*)0x400FC070 = 0x30000000; // CCM_CCGR2: iomuxc (CG14, bits[29:28])
*(uint32_t*)0x400FC074 = 0x00000030; // CCM_CCGR3: semc (CG2, bits[5:4])
*(uint32_t*)0x400FC078 = 0x0000000C; // CCM_CCGR4: iomuxc_gpr (CG1, bits[3:2])
*(uint32_t*)0x400FC07C = 0x00000003; // CCM_CCGR5: rom (CG0, bits[1:0])
*(uint32_t*)0x400FC080 = 0x00000000; // CCM_CCGR6: не нужен для DCD
// PLL_SYS: 528 MHz (24 MHz * 22), DIV_SELECT=1 → Fvco=528MHz
*(uint32_t*)0x400D8030 = 0x00002001;
// Ждём lock PLL_SYS (bit 31 = LOCK) перед переключением источника тактирования (fix 3)
(*(uint32_t*)0x400D8030 & 0x80000000) != 0;
// PFD2 = 528 * 18 / 0x23(35) = ~271 MHz... нет.
// PFD528[PFD2_FRAC] = 0x23 = 35 → Fout = 528 * 18 / 35 = ~271?
// Фактически 0x00230000 → PFD2_FRAC = 0x23 = 35 → F = 528*18/35 ≈ 271 МГц
// Но для SEMC @ 133 МГц нужен делитель CCM_CBCDR[SEMC_PODF].
// Проверь соответствие с clock_config.c!
*(uint32_t*)0x400D8100 = 0x00230000; // CCM_ANALOG_PFD_528: PFD2 = 133 MHz
// CCM_CBCDR: SEMC_CLK_SEL=1 (PLL2_PFD2), SEMC_ALT_CLK_SEL=0, SEMC_PODF=1 (делитель /2)
// → SEMC clock = 133 / 2 = 66.5 МГц
// Если нужно 133 МГц — SEMC_PODF=0 (делитель /1), но убедись что SDRAM выдержит
*(uint32_t*)0x400FC014 = 0x00010D40; // CCM_CBCDR
// =============================================================================
// SDRAM Init — Configure IOMUXC for SEMC pins
// MT48LC16M16A2: 16Mx16bit, 4 Banks, LVTTL
// =============================================================================
// IOMUXC — MUX mode: все пины SEMC в режиме ALT0 (SEMC функция)
*(uint32_t*)0x401F8014 = 0x00000000;
*(uint32_t*)0x401F8018 = 0x00000000;
*(uint32_t*)0x401F801C = 0x00000000;
*(uint32_t*)0x401F8020 = 0x00000000;
*(uint32_t*)0x401F8024 = 0x00000000;
*(uint32_t*)0x401F8028 = 0x00000000;
*(uint32_t*)0x401F802C = 0x00000000;
*(uint32_t*)0x401F8030 = 0x00000000;
*(uint32_t*)0x401F8034 = 0x00000000;
*(uint32_t*)0x401F8038 = 0x00000000;
*(uint32_t*)0x401F803C = 0x00000000;
*(uint32_t*)0x401F8040 = 0x00000000;
*(uint32_t*)0x401F8044 = 0x00000000;
*(uint32_t*)0x401F8048 = 0x00000000;
*(uint32_t*)0x401F804C = 0x00000000;
*(uint32_t*)0x401F8050 = 0x00000000;
*(uint32_t*)0x401F8054 = 0x00000000;
*(uint32_t*)0x401F8058 = 0x00000000;
*(uint32_t*)0x401F805C = 0x00000000;
*(uint32_t*)0x401F8060 = 0x00000000;
*(uint32_t*)0x401F8064 = 0x00000000;
*(uint32_t*)0x401F8068 = 0x00000000;
*(uint32_t*)0x401F806C = 0x00000000;
*(uint32_t*)0x401F8070 = 0x00000000;
*(uint32_t*)0x401F8074 = 0x00000000;
*(uint32_t*)0x401F8078 = 0x00000000;
*(uint32_t*)0x401F807C = 0x00000000;
*(uint32_t*)0x401F8080 = 0x00000000;
*(uint32_t*)0x401F8084 = 0x00000000;
*(uint32_t*)0x401F8088 = 0x00000000;
*(uint32_t*)0x401F808C = 0x00000000;
*(uint32_t*)0x401F8090 = 0x00000000;
*(uint32_t*)0x401F8094 = 0x00000000;
*(uint32_t*)0x401F8098 = 0x00000000;
*(uint32_t*)0x401F809C = 0x00000000;
*(uint32_t*)0x401F80A0 = 0x00000000;
*(uint32_t*)0x401F80A4 = 0x00000000;
*(uint32_t*)0x401F80A8 = 0x00000000;
*(uint32_t*)0x401F80AC = 0x00000000;
*(uint32_t*)0x401F80B0 = 0x00000010; // SEMC_DATA15: SION=1 (software input on)
*(uint32_t*)0x401F80B4 = 0x00000000;
*(uint32_t*)0x401F80B8 = 0x00000000;
// IOMUXC — PAD ctrl: 0x000110F9
// SRE=1 (Fast Slew Rate)
// DSE=7 (Drive Strength: R0/7 — максимальный)
// SPEED=3 (200 MHz)
// ODE=0 (Open Drain disabled)
// PKE=1 (Pull/Keeper enabled)
// PUE=1 (Pull — не Keeper)
// PUS=0 (100K pull-down)
// HYS=0 (Hysteresis disabled)
*(uint32_t*)0x401F8204 = 0x000110F9;
*(uint32_t*)0x401F8208 = 0x000110F9;
*(uint32_t*)0x401F820C = 0x000110F9;
*(uint32_t*)0x401F8210 = 0x000110F9;
*(uint32_t*)0x401F8214 = 0x000110F9;
*(uint32_t*)0x401F8218 = 0x000110F9;
*(uint32_t*)0x401F821C = 0x000110F9;
*(uint32_t*)0x401F8220 = 0x000110F9;
*(uint32_t*)0x401F8224 = 0x000110F9;
*(uint32_t*)0x401F8228 = 0x000110F9;
*(uint32_t*)0x401F822C = 0x000110F9;
*(uint32_t*)0x401F8230 = 0x000110F9;
*(uint32_t*)0x401F8234 = 0x000110F9;
*(uint32_t*)0x401F8238 = 0x000110F9;
*(uint32_t*)0x401F823C = 0x000110F9;
*(uint32_t*)0x401F8240 = 0x000110F9;
*(uint32_t*)0x401F8244 = 0x000110F9;
*(uint32_t*)0x401F8248 = 0x000110F9;
*(uint32_t*)0x401F824C = 0x000110F9;
*(uint32_t*)0x401F8250 = 0x000110F9;
*(uint32_t*)0x401F8254 = 0x000110F9;
*(uint32_t*)0x401F8258 = 0x000110F9;
*(uint32_t*)0x401F825C = 0x000110F9;
*(uint32_t*)0x401F8260 = 0x000110F9;
*(uint32_t*)0x401F8264 = 0x000110F9;
*(uint32_t*)0x401F8268 = 0x000110F9;
*(uint32_t*)0x401F826C = 0x000110F9;
*(uint32_t*)0x401F8270 = 0x000110F9;
*(uint32_t*)0x401F8274 = 0x000110F9;
*(uint32_t*)0x401F8278 = 0x000110F9;
*(uint32_t*)0x401F827C = 0x000110F9;
*(uint32_t*)0x401F8280 = 0x000110F9;
*(uint32_t*)0x401F8284 = 0x000110F9;
*(uint32_t*)0x401F8288 = 0x000110F9;
*(uint32_t*)0x401F828C = 0x000110F9;
*(uint32_t*)0x401F8290 = 0x000110F9;
*(uint32_t*)0x401F8294 = 0x000110F9;
*(uint32_t*)0x401F8298 = 0x000110F9;
*(uint32_t*)0x401F829C = 0x000110F9;
*(uint32_t*)0x401F82A0 = 0x000110F9;
*(uint32_t*)0x401F82A4 = 0x000110F9;
*(uint32_t*)0x401F82A8 = 0x000110F9;
// =============================================================================
// SEMC Controller — конфигурация для MT48LC16M16A2
// Базовый адрес SDRAM: 0x80000000
// Размер: 32 MB (MT48LC16M16A2: 16M × 16bit = 32 MB)
// =============================================================================
// SEMC_MCR: MDIS=0, BTO=0x1F (timeout), DQSMD=1 (DQS internal loop)
*(uint32_t*)0x402F0000 = 0x10000004;
// SEMC_BMCR0 & BMCR1: burst весовые коэффициенты
// 0x00000081: WQOS=1, RQOS=0, AGE=0, BSTLEN=0x10 (16 burst)
*(uint32_t*)0x402F0008 = 0x00000081; // SEMC_BMCR0
*(uint32_t*)0x402F000C = 0x00000081; // SEMC_BMCR1
// SEMC_BR0..BR7 — Base Register для каждого region
// BR0: SDRAM @ 0x80000000, размер 32MB (MR=0x1B → 32MB), VLD=1
*(uint32_t*)0x402F0010 = 0x8000001B; // BR0: SDRAM CS0, 0x80000000, 32MB
*(uint32_t*)0x402F0014 = 0x82000000; // BR1: SDRAM CS1 (если есть)
*(uint32_t*)0x402F0018 = 0x84000000; // BR2
*(uint32_t*)0x402F001C = 0x86000000; // BR3
*(uint32_t*)0x402F0020 = 0x90000021; // BR4: NAND
*(uint32_t*)0x402F0024 = 0xA0000019; // BR5: NOR/SRAM
*(uint32_t*)0x402F0028 = 0xA8000017; // BR6
*(uint32_t*)0x402F002C = 0xA900001B; // BR7
*(uint32_t*)0x402F0030 = 0x00000021; // BR8
// SEMC_INTEN: прерывания отключены
*(uint32_t*)0x402F0004 = 0x000079A8;
// SEMC_SDRAMCR0: тайминги MT48LC16M16A2 @ 133MHz
// PS=1 (16-bit data bus), BL=3 (Burst Length 8), COL=2 (9-bit col addr),
// CL=3 (CAS Latency 3)
*(uint32_t*)0x402F0040 = 0x00000F31;
// SEMC_SDRAMCR1
// PRE2ACT=2, ACT2RW=2, RFRC=9 (tRFC), WRC=2, CKEOFF=2, ACT2PRE=6
*(uint32_t*)0x402F0044 = 0x00652922;
// SEMC_SDRAMCR2: Refresh rate
// MT48LC16M16A2: 4096 refresh cycles / 64ms → ~15.6 мкс @ 133MHz
// SRRC=0x02 (самообновление), REF2REF=0x02, ACT2ACT=0x01
*(uint32_t*)0x402F0048 = 0x00020201;
// SEMC_SDRAMCR3
// REBL=1, PRESCALE=0x21, RT=0x3D, UT=0x0F
*(uint32_t*)0x402F004C = 0x08193D0F;
// SEMC_IOCR
*(uint32_t*)0x402F0080 = 0x00000021;
*(uint32_t*)0x402F0084 = 0x00888888;
// SEMC_IPTXDAT / IPRXDAT placeholder
*(uint32_t*)0x402F0094 = 0x00000002;
*(uint32_t*)0x402F0098 = 0x00000000;
// =============================================================================
// SDRAM Init Sequence (JEDEC: Precharge All → Auto-Refresh × 2 → Mode Register Set)
// =============================================================================
// SEMC_IPCR0: IP command target адрес = начало SDRAM
*(uint32_t*)0x402F0090 = 0x80000000;
// IPCMD: Precharge All (команда 0xF)
*(uint32_t*)0x402F009C = 0xA55A000F;
// Ждём завершения: SEMC_INTR[IPCMDDONE] (bit 0)
(*(uint32_t*)0x402F003C & 0x01) != 0;
// IPCMD: Auto-Refresh #1 (команда 0xC)
*(uint32_t*)0x402F0090 = 0x80000000;
*(uint32_t*)0x402F009C = 0xA55A000C;
(*(uint32_t*)0x402F003C & 0x01) != 0;
// IPCMD: Auto-Refresh #2
*(uint32_t*)0x402F0090 = 0x80000000;
*(uint32_t*)0x402F009C = 0xA55A000C;
(*(uint32_t*)0x402F003C & 0x01) != 0;
// Mode Register Set:
// MRS = 0x33: Burst Length=8 (BL=3), Burst Type=Sequential, CAS Latency=3
*(uint32_t*)0x402F00A0 = 0x00000033;
*(uint32_t*)0x402F0090 = 0x80000000;
// IPCMD: Mode Register Set (команда 0xA)
*(uint32_t*)0x402F009C = 0xA55A000A;
(*(uint32_t*)0x402F003C & 0x01) != 0;
// SEMC_SDRAMCR3: финальное значение Refresh Rate после инит-последовательности
// Переключаемся с медленного (init) refresh на рабочий
*(uint32_t*)0x402F004C = 0x50210A09;

Binary file not shown.

Binary file not shown.

View file

@ -1,152 +0,0 @@
# =============================================================================
# FCB — Flash Configuration Block
# Чип: Winbond W25Q128FVSSIG (64 Mbit = 8 MB), Quad SPI
# Режим: Fast Read Quad I/O (0xEB), 133 MHz, 4-line
# Цель: MIMXRT1052CVJ5B (family: mimxrt1050 в SPSDK 3.7)
#
# LUT-последовательности:
# Seq 0: Fast Read Quad I/O (0xEB) — основное XIP-чтение
# Seq 1: Read Status Reg 1 (0x05) — опрос WIP бита
# Seq 2: Write Enable (0x06) — перед любой записью/стиранием
# Seq 3: Erase Sector 4KB (0x20) — стирание сектора
# Seq 4: Write SR2 (0x31) — установка QE бита (вызов при старте)
# Seq 5: Page Program Quad (0x32) — запись страницы в Quad режиме
# Seq 11: Chip Erase (0x60) — полное стирание чипа
# Seq 12: Read Status Reg 1 (0x05) — дублирует seq1, для blhost
# =============================================================================
family: mimxrt1050
revision: latest
type: flexspi_nor
fcb_settings:
tag: '0x42464346' # 'FCFB' — magic, фиксировано
version: '0x56010400' # Version 1.4 (актуально для SPSDK 3.7)
readSampleClkSrc: '0x01' # loopback from DQS pad
csHoldTime: '0x03'
csSetupTime: '0x03'
columnAddressWidth: '0x03'
# ---------------------------------------------------------------------------
# QE bit enable — W25Q128 Status Register 2, bit 1
# BootROM выполнит Write Enable (seq 2) + Write SR2 (seq 4) при старте.
# deviceModeArg = 0x02 → SR2 = 0b00000010 (QE=1)
# waitTimeCfgCommands = 30 × 100us = 3ms (достаточно для записи SR)
# ---------------------------------------------------------------------------
deviceModeCfgEnable: '0x01'
deviceModeType: '0x01'
waitTimeCfgCommands: '0x001E'
deviceModeSeq:
seqNum: 1
seqId: 4 # → Seq 4: Write SR2 (0x31)
deviceModeArg: '0x00000002' # SR2 = 0x02 → QE=1
deviceType: '0x01' # Serial NOR
sflashPadType: '0x04' # Quad — 4 линии данных (IO0..IO3)
serialClkFreq: '0x07' # 133 MHz
sflashA1Size: '0x01000000' # 16 MB — W25Q128FVSSIG (128 Mbit / 8)
pageSize: '0x00000100' # 256 байт — стандарт для W25Q128
sectorSize: '0x00001000' # 4 KB — минимальная единица стирания
# ---------------------------------------------------------------------------
# LUT — Look-Up Table
# Каждая последовательность: 8 инструкций (instr0..instr7)
# Неиспользуемые инструкции = STOP (значение по умолчанию, можно не писать)
# ---------------------------------------------------------------------------
# ── Seq 0: Fast Read Quad I/O (0xEB) ───────────────────────────────────────
# CMD(×1) 0xEB → RADDR(×4) 24bit → DUMMY(×4) 6cycles → READ(×4)
lookupTable_seq0_instr0:
OPERAND: 0xEB
NUM_PADS: 1PAD
OPERATION_CODE: CMD_SDR
lookupTable_seq0_instr1:
OPERAND: 0x18 # 24-bit address
NUM_PADS: 4PAD
OPERATION_CODE: RADDR_SDR
lookupTable_seq0_instr2:
OPERAND: 0x06 # 6 dummy cycles @ 133MHz (4 mode + 2 dummy)
NUM_PADS: 4PAD
OPERATION_CODE: DUMMY_SDR
lookupTable_seq0_instr3:
OPERAND: 0x04
NUM_PADS: 4PAD
OPERATION_CODE: READ_SDR
# ── Seq 1: Read Status Register 1 (0x05) ───────────────────────────────────
# CMD(×1) 0x05 → READ(×1) 1byte
lookupTable_seq1_instr0:
OPERAND: 0x05
NUM_PADS: 1PAD
OPERATION_CODE: CMD_SDR
lookupTable_seq1_instr1:
OPERAND: 0x04
NUM_PADS: 1PAD
OPERATION_CODE: READ_SDR
# ── Seq 2: Write Enable (0x06) ──────────────────────────────────────────────
# CMD(×1) 0x06
lookupTable_seq2_instr0:
OPERAND: 0x06
NUM_PADS: 1PAD
OPERATION_CODE: CMD_SDR
# ── Seq 3: Erase Sector 4KB (0x20) ─────────────────────────────────────────
# CMD(×1) 0x20 → RADDR(×4) 24bit
lookupTable_seq3_instr0:
OPERAND: 0x20
NUM_PADS: 1PAD
OPERATION_CODE: CMD_SDR
lookupTable_seq3_instr1:
OPERAND: 0x18
NUM_PADS: 4PAD
OPERATION_CODE: RADDR_SDR
# ── Seq 4: Write Status Register 2 (0x31) — установка QE бита ──────────────
# CMD(×1) 0x31 → WRITE(×1) 1byte
# Вызывается через deviceModeSeq при инициализации BootROM.
# deviceModeArg (0x02) записывается как аргумент.
lookupTable_seq4_instr0:
OPERAND: 0x31
NUM_PADS: 1PAD
OPERATION_CODE: CMD_SDR
lookupTable_seq4_instr1:
OPERAND: 0x01 # записать 1 байт
NUM_PADS: 1PAD
OPERATION_CODE: WRITE_SDR
# ── Seq 5: Page Program Quad Input (0x32) ──────────────────────────────────
# CMD(×1) 0x32 → RADDR(×1) 24bit → WRITE(×4)
lookupTable_seq5_instr0:
OPERAND: 0x32
NUM_PADS: 1PAD
OPERATION_CODE: CMD_SDR
lookupTable_seq5_instr1:
OPERAND: 0x18
NUM_PADS: 1PAD
OPERATION_CODE: RADDR_SDR
lookupTable_seq5_instr2:
OPERAND: 0x04
NUM_PADS: 4PAD
OPERATION_CODE: WRITE_SDR
# Seq 6..10: не используются (STOP по умолчанию)
# ── Seq 11: Chip Erase (0x60) ───────────────────────────────────────────────
# CMD(×1) 0x60
lookupTable_seq11_instr0:
OPERAND: 0x60
NUM_PADS: 1PAD
OPERATION_CODE: CMD_SDR
# ── Seq 12: Read Status Register 1 (дубль для blhost) ──────────────────────
lookupTable_seq12_instr0:
OPERAND: 0x05
NUM_PADS: 1PAD
OPERATION_CODE: CMD_SDR
lookupTable_seq12_instr1:
OPERAND: 0x04
NUM_PADS: 1PAD
OPERATION_CODE: READ_SDR

View file

@ -1,152 +0,0 @@
# =============================================================================
# FCB — Flash Configuration Block
# Чип: Winbond W25Q64FVSSIG (64 Mbit = 8 MB), Quad SPI
# Режим: Fast Read Quad I/O (0xEB), 133 MHz, 4-line
# Цель: MIMXRT1052CVJ5B (family: mimxrt1050 в SPSDK 3.7)
#
# LUT-последовательности:
# Seq 0: Fast Read Quad I/O (0xEB) — основное XIP-чтение
# Seq 1: Read Status Reg 1 (0x05) — опрос WIP бита
# Seq 2: Write Enable (0x06) — перед любой записью/стиранием
# Seq 3: Erase Sector 4KB (0x20) — стирание сектора
# Seq 4: Write SR2 (0x31) — установка QE бита (вызов при старте)
# Seq 5: Page Program Quad (0x32) — запись страницы в Quad режиме
# Seq 11: Chip Erase (0x60) — полное стирание чипа
# Seq 12: Read Status Reg 1 (0x05) — дублирует seq1, для blhost
# =============================================================================
family: mimxrt1050
revision: latest
type: flexspi_nor
fcb_settings:
tag: '0x42464346' # 'FCFB' — magic, фиксировано
version: '0x56010400' # Version 1.4 (актуально для SPSDK 3.7)
readSampleClkSrc: '0x01' # loopback from DQS pad
csHoldTime: '0x03'
csSetupTime: '0x03'
columnAddressWidth: '0x03'
# ---------------------------------------------------------------------------
# QE bit enable — W25Q64 Status Register 2, bit 1
# BootROM выполнит Write Enable (seq 2) + Write SR2 (seq 4) при старте.
# deviceModeArg = 0x02 → SR2 = 0b00000010 (QE=1)
# waitTimeCfgCommands = 30 × 100us = 3ms (достаточно для записи SR)
# ---------------------------------------------------------------------------
deviceModeCfgEnable: '0x01'
deviceModeType: '0x01'
waitTimeCfgCommands: '0x001E'
deviceModeSeq:
seqNum: 1
seqId: 4 # → Seq 4: Write SR2 (0x31)
deviceModeArg: '0x00000002' # SR2 = 0x02 → QE=1
deviceType: '0x01' # Serial NOR
sflashPadType: '0x04' # Quad — 4 линии данных (IO0..IO3)
serialClkFreq: '0x07' # 133 MHz
sflashA1Size: '0x00800000' # 8 MB — W25Q64FVSSIG (64 Mbit / 8)
pageSize: '0x00000100' # 256 байт — стандарт для W25Q64
sectorSize: '0x00001000' # 4 KB — минимальная единица стирания
# ---------------------------------------------------------------------------
# LUT — Look-Up Table
# Каждая последовательность: 8 инструкций (instr0..instr7)
# Неиспользуемые инструкции = STOP (значение по умолчанию, можно не писать)
# ---------------------------------------------------------------------------
# ── Seq 0: Fast Read Quad I/O (0xEB) ───────────────────────────────────────
# CMD(×1) 0xEB → RADDR(×4) 24bit → DUMMY(×4) 6cycles → READ(×4)
lookupTable_seq0_instr0:
OPERAND: 0xEB
NUM_PADS: 1PAD
OPERATION_CODE: CMD_SDR
lookupTable_seq0_instr1:
OPERAND: 0x18 # 24-bit address
NUM_PADS: 4PAD
OPERATION_CODE: RADDR_SDR
lookupTable_seq0_instr2:
OPERAND: 0x06 # 6 dummy cycles @ 133MHz (4 mode + 2 dummy)
NUM_PADS: 4PAD
OPERATION_CODE: DUMMY_SDR
lookupTable_seq0_instr3:
OPERAND: 0x04
NUM_PADS: 4PAD
OPERATION_CODE: READ_SDR
# ── Seq 1: Read Status Register 1 (0x05) ───────────────────────────────────
# CMD(×1) 0x05 → READ(×1) 1byte
lookupTable_seq1_instr0:
OPERAND: 0x05
NUM_PADS: 1PAD
OPERATION_CODE: CMD_SDR
lookupTable_seq1_instr1:
OPERAND: 0x04
NUM_PADS: 1PAD
OPERATION_CODE: READ_SDR
# ── Seq 2: Write Enable (0x06) ──────────────────────────────────────────────
# CMD(×1) 0x06
lookupTable_seq2_instr0:
OPERAND: 0x06
NUM_PADS: 1PAD
OPERATION_CODE: CMD_SDR
# ── Seq 3: Erase Sector 4KB (0x20) ─────────────────────────────────────────
# CMD(×1) 0x20 → RADDR(×4) 24bit
lookupTable_seq3_instr0:
OPERAND: 0x20
NUM_PADS: 1PAD
OPERATION_CODE: CMD_SDR
lookupTable_seq3_instr1:
OPERAND: 0x18
NUM_PADS: 4PAD
OPERATION_CODE: RADDR_SDR
# ── Seq 4: Write Status Register 2 (0x31) — установка QE бита ──────────────
# CMD(×1) 0x31 → WRITE(×1) 1byte
# Вызывается через deviceModeSeq при инициализации BootROM.
# deviceModeArg (0x02) записывается как аргумент.
lookupTable_seq4_instr0:
OPERAND: 0x31
NUM_PADS: 1PAD
OPERATION_CODE: CMD_SDR
lookupTable_seq4_instr1:
OPERAND: 0x01 # записать 1 байт
NUM_PADS: 1PAD
OPERATION_CODE: WRITE_SDR
# ── Seq 5: Page Program Quad Input (0x32) ──────────────────────────────────
# CMD(×1) 0x32 → RADDR(×1) 24bit → WRITE(×4)
lookupTable_seq5_instr0:
OPERAND: 0x32
NUM_PADS: 1PAD
OPERATION_CODE: CMD_SDR
lookupTable_seq5_instr1:
OPERAND: 0x18
NUM_PADS: 1PAD
OPERATION_CODE: RADDR_SDR
lookupTable_seq5_instr2:
OPERAND: 0x04
NUM_PADS: 4PAD
OPERATION_CODE: WRITE_SDR
# Seq 6..10: не используются (STOP по умолчанию)
# ── Seq 11: Chip Erase (0x60) ───────────────────────────────────────────────
# CMD(×1) 0x60
lookupTable_seq11_instr0:
OPERAND: 0x60
NUM_PADS: 1PAD
OPERATION_CODE: CMD_SDR
# ── Seq 12: Read Status Register 1 (дубль для blhost) ──────────────────────
lookupTable_seq12_instr0:
OPERAND: 0x05
NUM_PADS: 1PAD
OPERATION_CODE: CMD_SDR
lookupTable_seq12_instr1:
OPERAND: 0x04
NUM_PADS: 1PAD
OPERATION_CODE: READ_SDR

209
tools/host/flash_usb.py Normal file
View file

@ -0,0 +1,209 @@
#!/usr/bin/env python3
"""
flash_usb.py прошивка MIMXRT1052 через USB (BootROM SDP Flashloader blhost)
Использование:
python3 flash_usb.py --firmware firmware_test --build-type Debug
python3 flash_usb.py --firmware app --build-type Release
python3 flash_usb.py --firmware bootloader --build-type Release
python3 flash_usb.py --firmware firmware_test --build-type Debug --ram-only
Шаги:
1. Устройство в SDP режиме (VID:PID 1FC9:0130)
sdphost загружает ivt_flashloader.bin в RAM
sdphost прыгает на flashloader
2. Flashloader запущен (VID:PID 15A2:0073)
blhost конфигурирует FlexSPI NOR (пишет FCB)
blhost стирает нужный регион Flash
blhost пишет HAB образ начиная с 0x60002000
blhost reset
"""
import argparse
import subprocess
import sys
import time
from pathlib import Path
# ─── Константы ────────────────────────────────────────────────────────────────
SCRIPT_DIR = Path(__file__).parent.resolve()
REPO_ROOT = SCRIPT_DIR.parent.parent
BUILD_DIR = REPO_ROOT / "build"
FLASHLOADER = SCRIPT_DIR / "dcd" / "ivt_flashloader.bin"
# USB VID:PID
SDP_USB = "0x1FC9,0x0130" # BootROM Serial Download Protocol
BLHOST_USB = "0x15A2,0x0073" # Flashloader запущен
# FlexSPI NOR config option word: 0xC0000007
# bits[31:28]=0xC — tag (QuadSPI NOR)
# bits[3:0]=0x7 — option size
FLEXSPI_OPTION_ADDR = "0x2000"
FLEXSPI_OPTION_VALUE = "0xC0000007"
FLEXSPI_MEMORY_ID = "9" # FlexSPI NOR memory interface ID
# Flash layout
FLASH_BASE = 0x60000000
HAB_OFFSET = 0x1000 # IVT offset: HAB binary starts with IVT at byte 0
# write address = FLASH_BASE + ivtOffset = 0x60001000
def run(cmd: list[str], check: bool = True) -> subprocess.CompletedProcess:
print(f" $ {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=False)
if check and result.returncode != 0:
print(f"\n[ERROR] Команда завершилась с кодом {result.returncode}")
sys.exit(1)
return result
def step(msg: str) -> None:
print(f"\n{''*60}")
print(f" {msg}")
print(f"{''*60}")
def wait_for_flashloader(timeout: int = 10) -> bool:
"""Ждём пока Flashloader поднимется (VID:PID 15A2:0073)"""
print(f"\n Ожидание Flashloader (до {timeout}с)...", end="", flush=True)
for i in range(timeout):
time.sleep(1)
result = subprocess.run(
["blhost", "-u", BLHOST_USB, "-j", "--", "get-property", "1", "0"],
capture_output=True
)
if result.returncode == 0:
print(f" OK ({i+1}с)")
return True
print(".", end="", flush=True)
print(" TIMEOUT")
return False
def load_flashloader() -> None:
"""Загружает Flashloader через SDP если ещё не запущен"""
# Проверяем — вдруг уже запущен
result = subprocess.run(
["blhost", "-u", BLHOST_USB, "-j", "--", "get-property", "1", "0"],
capture_output=True
)
if result.returncode == 0:
print(" Flashloader уже запущен — пропускаем загрузку")
return
if not FLASHLOADER.exists():
print(f"[ERROR] Не найден: {FLASHLOADER}")
print(" Скачай ivt_flashloader.bin из MCUXpresso Secure Provisioning Tool")
print(" и положи в tools/host/dcd/ivt_flashloader.bin")
sys.exit(1)
step("Загрузка Flashloader через SDP (1FC9:0130)")
run(["sdphost", "-u", SDP_USB, "-j", "--",
"write-file", "0x20001C00", str(FLASHLOADER)])
run(["sdphost", "-u", SDP_USB, "-j", "--",
"jump-address", "0x20001C00"])
if not wait_for_flashloader():
print("[ERROR] Flashloader не ответил. Проверь BOOT_MOD пины и подключение.")
sys.exit(1)
# Option word для записи FCB в Flash[0x60000000]
# 0xF000000F: tag=0xF → Write FCB command (Flashloader пишет готовый FCB в начало Flash)
FLEXSPI_FCB_VALUE = "0xF000000F"
def configure_flexspi() -> None:
"""Инициализирует FlexSPI NOR контроллер через Flashloader"""
step("Конфигурация FlexSPI NOR (инициализация контроллера)")
run(["blhost", "-u", BLHOST_USB, "--",
"fill-memory", FLEXSPI_OPTION_ADDR, "4", FLEXSPI_OPTION_VALUE, "word"])
run(["blhost", "-u", BLHOST_USB, "--",
"configure-memory", FLEXSPI_MEMORY_ID, FLEXSPI_OPTION_ADDR])
def write_fcb() -> None:
"""Записывает Flash Configuration Block в 0x60000000
Отдельный шаг после erase! Flashloader генерирует FCB из параметров FlexSPI
и пишет его по адресу 0x60000000. Без FCB BootROM не знает как читать Flash.
Option word 0xF000000F: tag=0xF Write FCB command.
"""
step("Запись FCB в Flash[0x60000000]")
run(["blhost", "-u", BLHOST_USB, "--",
"fill-memory", FLEXSPI_OPTION_ADDR, "4", FLEXSPI_FCB_VALUE, "word"])
run(["blhost", "-u", BLHOST_USB, "--",
"configure-memory", FLEXSPI_MEMORY_ID, FLEXSPI_OPTION_ADDR])
def flash(hab_bin: Path, ram_only: bool = False) -> None:
if not hab_bin.exists():
print(f"[ERROR] Файл не найден: {hab_bin}")
print(" Сначала собери образ: uv run nxpimage hab export -c ...")
sys.exit(1)
if ram_only:
step(f"Загрузка в RAM (без записи во Flash): {hab_bin.name}")
# RAM-only: грузим в RAM прямо через SDP без flashloader
run(["sdphost", "-u", SDP_USB, "-j", "--",
"write-file", f"0x{FLASH_BASE + HAB_OFFSET:08X}", str(hab_bin)])
run(["sdphost", "-u", SDP_USB, "-j", "--",
"jump-address", f"0x{FLASH_BASE + HAB_OFFSET:08X}"])
return
load_flashloader()
configure_flexspi() # 0xC0000007 — инициализация FlexSPI контроллера
write_addr = f"0x{FLASH_BASE + HAB_OFFSET:08X}"
# Размер стирания: HAB_OFFSET (0x1000) + размер образа, выровнено вверх до 4KB
erase_size = ((HAB_OFFSET + hab_bin.stat().st_size + 0xFFF) // 0x1000) * 0x1000
step(f"Прошивка Flash: {hab_bin.name}")
print(f" Образ: {hab_bin}")
print(f" Размер: {hab_bin.stat().st_size} байт")
print(f" Адрес: {write_addr}")
print(f" Стирание: 0x{FLASH_BASE:08X} .. +{erase_size} байт")
# memoryId=0 — mapped (XIP) адресное пространство
run(["blhost", "-u", BLHOST_USB, "--",
"flash-erase-region", f"0x{FLASH_BASE:08X}",
str(erase_size), "0"])
write_fcb() # 0xF000000F — запись FCB в 0x60000000 (после erase!)
# memoryId=0 — mapped (XIP) адресное пространство
run(["blhost", "-u", BLHOST_USB, "--",
"write-memory", write_addr, str(hab_bin), "0"])
step("Reset")
run(["blhost", "-u", BLHOST_USB, "--", "reset"])
print("\n ✓ Прошивка завершена успешно")
def main() -> None:
parser = argparse.ArgumentParser(description="Прошивка MIMXRT1052 через USB")
parser.add_argument("--firmware", required=True,
choices=["firmware_test", "bootloader", "app"],
help="Имя прошивки")
parser.add_argument("--build-type", required=True,
choices=["Debug", "Release"],
help="Тип сборки")
parser.add_argument("--ram-only", action="store_true",
help="Загрузить в RAM без записи во Flash")
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"
print(f"\n{''*60}")
print(f" MIMXRT1052 Flash Tool")
print(f" Прошивка: {args.firmware} [{args.build_type}]")
print(f" Образ: {hab_bin}")
print(f"{''*60}")
flash(hab_bin, ram_only=args.ram_only)
if __name__ == "__main__":
main()

View file

@ -1,18 +1,20 @@
# ============================================================================= # =============================================================================
# HAB Container — firmware/app (боевая прошивка) # HAB Container — firmware/app (боевая прошивка)
# Расположение: tools/host/hab/hab_app.yaml
# #
# flags: 0x0 — образ НЕ подписан # Сборка:
# DCD: обязателен — линкер размещает FreeRTOS heap и буферы LCDIF # cd tools/host/hab
# в SDRAM (0x80000000), которая должна быть инициализирована # uv run nxpimage hab export --force -c hab_app.yaml \
# до первой инструкции прошивки. # -o ../../../build/Release/app_hab.bin
# ============================================================================= # =============================================================================
options: options:
flags: 0x0 flags: 0x00
startAddress: 0x60000000
ivtOffset: 0x1000
initialLoadSize: 0x2000
family: mimxrt1050 family: mimxrt1050
bootDevice: flexspi_nor DCDFilePath: ../dcd/dcd.bin # обязателен — heap FreeRTOS и LCDIF в SDRAM
startAddress: 0x60000000 # FlexSPI NOR base address (XIP)
DCDFilePath: ../dcd/dcd.bin
inputImageFile: "../../../build/Release/app.bin" inputImageFile: "../../../build/Release/app.bin"

View file

@ -1,17 +1,20 @@
# ============================================================================= # =============================================================================
# HAB Container — firmware/bootloader # HAB Container — firmware/bootloader
# Расположение: tools/host/hab/hab_bootloader.yaml
# #
# flags: 0x0 — образ НЕ подписан # Сборка:
# DCD: отсутствует намеренно — загрузчик не использует SDRAM, # cd tools/host/hab
# минимальная зависимость от железа = максимальная надёжность. # uv run nxpimage hab export --force -c hab_bootloader.yaml \
# SDRAM инициализируется позже, уже внутри app через её DCD. # -o ../../../build/Release/bootloader_hab.bin
# ============================================================================= # =============================================================================
options: options:
flags: 0x0 flags: 0x00
startAddress: 0x60000000
ivtOffset: 0x1000
initialLoadSize: 0x2000
family: mimxrt1050 family: mimxrt1050
bootDevice: flexspi_nor # DCD намеренно отсутствует — загрузчик не использует SDRAM
startAddress: 0x60000000 # FlexSPI NOR base address (XIP)
inputImageFile: "../../../build/Release/bootloader.bin" inputImageFile: "../../../build/Release/bootloader.bin"

View file

@ -1,23 +1,22 @@
# ============================================================================= # =============================================================================
# HAB Container — firmware/test (входной контроль производства) # HAB Container — firmware/test (входной контроль производства)
# Расположение: tools/host/hab/hab_firmware_test.yaml
# #
# flags: 0x0 — образ НЕ подписан (HAB в open/unsigned режиме) # Сборка:
# DCD: присутствует — инициализация SDRAM обязательна для тестов # cd tools/host/hab
# # uv run nxpimage hab export --force -c hab_firmware_test.yaml \
# Важно: sections пустой — для unsigned образа CSF не нужен. # -o ../../../build/Debug/firmware_test.bin
# BootROM примет образ без проверки подписи пока HAB не залочен (fuses).
# ============================================================================= # =============================================================================
options: options:
flags: 0x0 # unsigned — без подписи flags: 0x00 # unsigned — без подписи
startAddress: 0x60000000 # FlexSPI NOR base (XIP)
ivtOffset: 0x1000 # IVT offset for FlexSPI NOR boot
initialLoadSize: 0x2000 # размер области IVT+BDT+DCD до кода
family: mimxrt1050 family: mimxrt1050
bootDevice: flexspi_nor # ivtOffset=0x1000, initialLoadSize=0x2000 — автоматически DCDFilePath: ../dcd/dcd.bin # инициализация SDRAM
startAddress: 0x60000000 # FlexSPI NOR base address (XIP)
# DCD — инициализация SDRAM до передачи управления прошивке.
# Сгенерирован из dcd.cfg скриптом tools/dcd_cfg_to_bin.py
DCDFilePath: ../dcd/dcd.bin
# .bin или .srec — оба принимаются
inputImageFile: "../../../build/Debug/firmware_test.bin" inputImageFile: "../../../build/Debug/firmware_test.bin"
sections: [] sections: []

View file

@ -1,26 +0,0 @@
# =============================================================================
# Bootable Image — firmware/app (боевая прошивка)
# Расположение: tools/host/image/bootimg_app.yaml
#
# Пайплайн сборки:
#
# 1. FCB (один раз, из tools/host/fdcb/):
# nxpimage bootable-image fcb export -c fcb_w25q64_quad.yaml -o fcb.bin
#
# 2. Образ (из tools/host/image/):
# nxpimage bootable-image export -c bootimg_app.yaml
#
# 3. Проверка:
# nxpimage bootable-image parse -b ../../../../build/Release/app_bootable.bin -f mimxrt1050
# =============================================================================
family: mimxrt1050
revision: latest
memory_type: flexspi_nor
output: "../../../build/Release/app_bootable.bin"
output_format: bin
fcb: ../fdcb/fcb.bin
hab_container: ../hab/hab_app.yaml

View file

@ -1,26 +0,0 @@
# =============================================================================
# Bootable Image — firmware/bootloader
# Расположение: tools/host/image/bootimg_bootloader.yaml
#
# Пайплайн сборки:
#
# 1. FCB (один раз, из tools/host/fdcb/):
# nxpimage bootable-image fcb export -c fcb_w25q64_quad.yaml -o fcb.bin
#
# 2. Образ (из tools/host/image/):
# nxpimage bootable-image export -c bootimg_bootloader.yaml
#
# 3. Проверка:
# nxpimage bootable-image parse -b ../../../../build/Release/bootloader_bootable.bin -f mimxrt1050
# =============================================================================
family: mimxrt1050
revision: latest
memory_type: flexspi_nor
output: "../../../build/Release/bootloader_bootable.bin"
output_format: bin
fcb: ../fdcb/fcb.bin
hab_container: ../hab/hab_bootloader.yaml

View file

@ -1,36 +0,0 @@
# =============================================================================
# Bootable Image — firmware/test (входной контроль производства)
# Расположение: tools/host/image/bootimg_firmware_test.yaml
#
# Пайплайн сборки:
#
# 1. FCB (один раз, из tools/host/fdcb/):
# nxpimage bootable-image fcb export -c fcb_w25q64_quad.yaml -o fcb.bin
#
# 2. Образ (из tools/host/image/):
# nxpimage bootable-image export -c bootimg_firmware_test.yaml
#
# 3. Проверка:
# nxpimage bootable-image parse -b ../../../../build/Debug/firmware_test_bootable.bin -f mimxrt1050
#
# Структура образа во Flash (0x60000000):
# 0x000 FCB (512 байт)
# 0x400 зарезервировано
# 0x1000 IVT ]
# 0x1020 BDT ] HAB container
# 0x1030 DCD ]
# 0x3000 код ]
# =============================================================================
family: mimxrt1050
revision: latest
memory_type: flexspi_nor
output: "../../../build/Debug/firmware_test_bootable.bin"
output_format: bin
# FCB — собран из tools/host/fdcb/fcb_w25q64_quad.yaml
fcb: ../fdcb/fcb.bin
# HAB container — YAML напрямую, export соберёт его сам
hab_container: ../hab/hab_firmware_test.yaml