Готово. Полная повторная верификация: | Проверка | Результат | | ---------------------- | ------------------------------------------------------ | | Host-тесты | **19/19** зелёные | | Debug сборка+подпись | чисто, `image_ok` UNSET | | Release сборка+подпись | чисто, `image_ok` UNSET | | Оба бинарника | 2 097 152 байт (ровно размер слота) | | Аппаратно (ваш лог) | self-confirm пережил перезагрузку, CAN→рендер работает | PLAN.md обновлён: Фаза 1 → ✅, известное ограничение (single-buffer) зафиксировано с честной формулировкой механизма, явно вписано в объём Фазы 4 вместе с PXP. Также отметил, что критерий «timeout→--» проверен только логически (host-тестом), не на реальном обрыве связи — не стал завышать степень подтверждённости. Отчёт — [tft_app_phase_0_1_report.md](../../../private/tmp/claude-501/-Users-von-akimow-Desktop-TFT-ENV-tft-manufacture-test/93af6316-b6c7-4b2a-9063-169ca896d1ff/scratchpad/tft_app_phase_0_1_report.md). Полный текст ниже — можно скопировать целиком в новый тред: --- ```markdown # tft_app — отчёт по Фазам 0–1 > Канонические источники — `firmware/tft_app/ARCH.md` (архитектура) и `firmware/tft_app/PLAN.md` > (статус фаз, живой документ, обновляется каждой фазой). Этот файл — снимок на момент завершения > Фазы 1, для быстрого ввода в контекст новой сессии. При расхождении — верить PLAN.md/ARCH.md, не > этому файлу. **Статус:** Фаза 0 ✅ и Фаза 1 ✅ — обе аппаратно подтверждены. Работа Фазы 1 **не закоммичена** (в рабочем дереве поверх коммита `# tft_app: Phase 0`, `a9e6681`). --- ## Фаза 0 — Каркас (FreeRTOS + self-confirm слота MCUboot) **Итог.** `firmware/tft_app` собирается как MCUboot-слот-образ (Direct-XIP), bootloader в него прыгает, FreeRTOS стартует, образ подтверждает себя в рантайме (`boot_set_next`) и переживает power cycle без отката — anti-brick сохранён. Подтверждено дважды на реальном железе (в т.ч. через переустановку с microSD). **Путь до рабочего состояния был не прямым — три независимых бага, все найдены и закрыты:** 1. **`bsp_qspi_init()` обязателен до любого `flash_area_*`.** Без него запись `image_ok` в трейлер слота молча падала — `boot_set_next()` возвращал успех, но ничего не персистило. 2. **Корневая причина (самая коварная): забытый дефайн `__STARTUP_INITIALIZE_RAMFUNCTION`.** Запись QSPI из app виснет в `qspi_write_fifo` — FlexSPI не дренирует IP TX FIFO. Причина: startup копирует секцию `.ram_function` (код `bsp_qspi`, помеченный `AT_QUICKACCESS_SECTION_CODE`) из flash в ITCM только под этим дефайном; `app` его не задавал — в ITCM оставался leftover ramfunc **загрузчика** (тот исполнялся первым), поэтому чтения «случайно работали», а запись — нет. Оба рабочих потребителя `bsp_qspi` (firmware_test, bootloader) этот дефайн задают; `app` был первым, кто его пропустил. Диагностировано под отладчиком (зависание конкретно в `qspi_write_fifo`), подтверждено изолированным erase+write+readback self-test (частота мигания LED = индикатор результата) до восстановления полной диагностики. 3. **UART, не USB CDC, для диагностики/логов.** USB CDC (target-side EHCI/PHY) под FreeRTOS не заработал стабильно за несколько заходов — первый в репозитории FreeRTOS-потребитель target USB device stack, класс неопределённости остался непроверенным. LPUART1/MCU-Link VCOM — самый проверенный канал в репозитории (все `tests/target/*` HIL-образы), доступен сразу через уже подключённый для SWD кабель, без enumeration/wait. Решение принято явно с пользователем. **Побочные, но важные для будущих фаз находки:** - Любая bsp-инициализация, зависящая от тика (`bsp_delay()` внутри `bsp_usb_cdc_init()` и т.п.), нельзя звать **до** `vTaskStartScheduler()` — в FreeRTOS-режиме `bsp_tick` это `xTaskGetTickCount()`, который не идёт до старта планировщика → вызов зависает навсегда молча. `main()` в итоге — минимальный bare-metal (board bring-up + запуск планировщика), вся инициализация — из задачи. - Первый в репозитории потребитель `BSP_TICK_FREERTOS_MODE` вскрыл 2 латентных бага: (1) общий `bsp_tick` — bare-metal STATIC-либа → `SysTick_Handler` конфликтует с FreeRTOS-портом (решение: компилировать `tick.c` прямо в `app`, не через `libbsp_tick.a`); (2) `configTICK_RATE_HZ` нельзя как `((TickType_t)1000)` — `#if` в `tick.c` не разбирает cast, только `1000`. - `.vscode/launch.json`: у `🐛 Debug: tft_app` был `preLaunchTask` на `firmware_test` (copy-paste) — исправлено на `build:app-debug`. - Тонкий стек задачи (изначально x2=1 КБ) маскировался под тот же класс симптома, что и другие баги (зависание/reset-loop) — увеличен с запасом (x4-x6, 2-3 КБ) везде, где заводятся новые задачи. **Осознанное отклонение от исходного плана:** `firmware/bootloader/test_stub` НЕ удалён (план предполагал) — оставлен как независимая A/B-регрессия загрузчика, `app` пока не покрывает те же сценарии (hang-классы, revert без self-confirm). --- ## Фаза 1 — Walking skeleton (CAN → декодер → контроллер → fallback-рендер) **Итог.** Полный вертикальный срез: реальный CAN-кадр НКУ-CAN → чистый декодер → контроллер (diff) → fallback-рендер (позиция + стрелка) на экране. Подтверждено на TFT8 с реальным CAN-трафиком станции. ### Архитектура (по слоям, каждый — своя CMake-либа, конвенция `bsp/`) ``` domain/elevator_model sul_result_t (pos: UTF-8 строка, direction: none/up/down/double), sul_default_state() domain/sul sul_frame_t, sul_driver_t, sul_status_t, реестр драйверов (заголовок и реализация реестра — РАЗНЫЕ CMake-таргеты, иначе цикл: декодеру нужны только типы sul.h, реестру — decode() декодера) domain/sul/nku_can чистый декодер PACKET1 (направление) / PACKET3 (позиция) — порт OLD_PROJECT msg_receiver_task, БЕЗ единого HAL-вызова domain/sul/transport/can тонкий bsp_can → sul_frame_t адаптер (HW, не host-тестируется) domain/controller process(sul_result_t) → indication_task_t (diff), кэш services/gfx framebuffer (SDRAM non-cacheable) + RLE-декодер шрифтов (lcd-image-converter формат) + примитив стрелки ui/fallback рендер pos+стрелка, asset-free/FS-free (ARCH §11) app задачи sul_rx (WDOG+CAN+decode+controller) и render, связаны очередью глубины 1 (xQueueOverwrite — важно только последнее состояние, не история) ``` **Host-тесты (все зелёные, домен полностью протестирован до касания железа):** - `tests/host/tft_app_sul_nku` — 16 тестов, golden-векторы CAN-кадров (все direction, позиции, легаси-квирк `left==0`≈пробел, кириллица, IGNORED/ERR на чужой ID/DLC/вне таблицы символов, накопление состояния между PACKET1 и PACKET3). - `tests/host/tft_app_controller` — 6 тестов (diff-логика, timeout→default тем же путём, что и обычный кадр). - Полный пакет: **19/19** (включая существующие тесты bootloader/bsp). ### Ключевые находки/решения по ходу 1. **Framebuffer в SDRAM обязан идти через `AT_NONCACHEABLE_SECTION_ALIGN`** (прямая рекомендация NXP, порт паттерна из `OLD_PROJECT/source/display/image_cache.c`). MPU держит SDRAM как `Normal Write-Back Cacheable` (`board_mpu_init()`, Region 8); ELCDIF читает framebuffer напрямую по DMA — без non-cacheable региона CPU писал бы через кэш, DMA видел бы устаревшие данные (мусор/частичные кадры на экране, выглядело бы как загадочный баг железа). Механизм уже был параметризован в `board_mpu_init()` через линкер-символы `__NCACHE_REGION_START/SIZE` (Region 9, унаследовано из bootloader) — переопределены в `app_slot.ld` на новый `m_sdram_ncache` (2 МБ в начале SDRAM) вместо OCRAM (256 КБ, для framebuffer ~1.83 МБ не влезло бы). На первой попытке framebuffer был сырым указателем на константный адрес (работало случайно, линкер честно репортил `0 B` в секции) — исправлено на настоящее объявление переменной через макрос. 2. **RLE-декодер шрифтов — порт проверенного в проде алгоритма** из `OLD_PROJECT/source/fonts/ fonts.c` (UNIQUE/REPEATABLE блоки, бинарный поиск по отсортированному коду символа), не переизобретён с нуля. Формат — то, что реально экспортирует lcd-image-converter (`tImage`/ `tChar`/`tFont`, ARGB8888, "Color A8R8G8B8"). Пользователь сгенерировал `FloorFontFallback.c` (Inter 215pt, символы 0-9 и "-") и `SystemFont.c` (JetBrains Mono 24pt, ASCII+кириллица) инструментом напрямую. 3. **По-символьный fallback** (ARCH §11): символ вне таблицы шрифта → подстановка `'-'` (согласованная политика — "-" общий заменитель во `FloorFontFallback`). 4. Сгенерированные шрифтовые файлы ожидают типы из bare-name `fonts.h` по-разному (`SystemFont.c` — явный `#include`, `FloorFontFallback.c` — вообще без include, разные версии конвертера) — закрыто через `-include`/`-I` в CMake; сами сгенерированные файлы не редактировались (конвертер их перезапишет при регенерации). 5. Файлы шрифтов перенесены из `domain/` (где их положил пользователь) в `services/gfx/fonts/` — по ARCH §4 шрифты относятся к gfx, не к домену (домен — чистый C без рендеринга). ### ⚠️ Известное ограничение (зафиксировано, чинится в Фазе 4) Один framebuffer, без double buffering — `ui_fallback_render()` пишет напрямую в буфер, который ELCDIF в этот момент сканирует по DMA. Если перерисовка (`gfx_clear` + RLE-decode глифов + стрелка) не укладывается в один кадр развёртки, видно сам процесс закраски (глиф «набирается» по частям за несколько кадров) — **не tearing и не порча пикселей** (каждая отдельная запись всегда валидна), просто заметен сам процесс. Подтверждено на железе пользователем ("рвано, но без артефактов" — совпадает с механизмом). **Фикс — double buffering**, НЕ требует PXP отдельно (это вопрос буферизации, не композиции): второй framebuffer (`m_sdram_ncache` 2→4 МБ), swap через `bsp_display_set_next_buffer()` + семафор на `FRAME_DONE` (сейчас `gfx_init()` передаёт `NULL` вместо колбэка). Явно вписано в объём Фазы 4 (PLAN.md) вместе с PXP-компоновщиком (для многослойной композиции реальных ассетов — фон+спрайты+текст, та же фаза, естественно смежная работа). ### Прочее - Критерий выхода выполнен на TFT8: реальный CAN-трафик → корректный номер этажа и стрелка. Таймаут→"--" логически проверен host-тестом, аппаратно на реальном обрыве связи отдельно не гонялся (низкий риск — тот же код-путь, что у обычного кадра). - Debug и Release оба собираются и подписываются чисто (2 097 152 байта — ровно размер слота, `image_ok` UNSET в обоих — рантайм self-confirm, не sign-time `--confirm`). --- ## Состояние репозитория на конец Фазы 1 - Ветка: `tft-app-dev`. - Коммит `a9e6681` (# tft_app: Phase 0) — последний закоммиченный чекпоинт. - Вся Фаза 1 — в рабочем дереве, не закоммичена: `firmware/tft_app/src/{domain,services/gfx, ui/fallback}/`, `firmware/tft_app/src/app/main.c` (переписан), `tests/host/tft_app_sul_nku/`, `tests/host/tft_app_controller/`, изменения в `CMakePresets.json`, `cmake/linker/MIMXRT1052xxxxx_app_slot.ld`, `firmware/tft_app/CMakeLists.txt`, `tests/host/CMakeLists.txt`, `firmware/tft_app/PLAN.md`. - Стоит рассмотреть коммит перед длительным перерывом/переключением контекста — на усмотрение пользователя (не коммичу без явной просьбы). ## Дальше — Фаза 2 Полный контракт `sul_result_t` (next, сигналы, `lading_secs`, `floor_num`), полный декодер НКУ-CAN (PACKET1..5, режимы, удалённая установка адреса), таблица приоритетов режимов (данные, потенциально клиентские), валидация рендеримости позиции (renderable-множество шрифта + по-символьный fallback — механизм уже есть в `gfx`, п.3 выше). Подробности — PLAN.md, раздел «Фаза 2». ``` --- Одно практическое замечание, раз вы переключаетесь в новый тред: вся Фаза 1 сейчас **не закоммичена** — сидит в рабочем дереве поверх коммита Фазы 0. Хотите, чтобы я закоммитил перед переключением (стандартный `git add` + сообщение по Фазе 1), или оставляете как есть?