# tft-app — архитектура > Прошивка лифтового индикатора для семейства **MIMXRT1052 TFT (4 / 7 / 8 / 10")**. > Документ — живой источник истины по слоистой архитектуре. Статус фаз разработки — > в [PLAN.md](PLAN.md). --- ## Часть I. Требования (бизнес-логика) ### 1. Основные функции **1.1 Индикация в реальном времени.** Местоположение кабины, спецрежимы, музыкальное сопровождение при движении, озвучка сигналов. - Сигналы от СУЛ приходят по последовательному интерфейсу (+24 В UART, кастомный бинарный протокол) либо по CAN. - Отображение состояния двух диспетчерских оптовходов: «Вызов подан» / «Вызов принят» / пусто. **1.2 Обновление ПО в поле** через microSD (сам образ прошивки — задача bootloader, A/B Direct-XIP; ассеты/layout — задача tft-app, см. §12). **1.3 Графический интерфейс настроек** в рантайме (меню, навигация двумя кнопками). ### 2. Частности **2.1 Отображение.** 4 типа дисплея (TFT4/7/8/10); одна кодовая база, две сборки (см. §9). Ассеты (иконки, картинки, звук) — на встроенной флеш, обновляются с microSD. Шрифты — C-массивы ([LCD Image Converter](https://lcd-image-converter.riuson.com/)). Макеты UI унифицированы и упрощены; по запросу клиента — кастом. **2.2 Связь с СУЛ.** Общий программный интерфейс ко всем СУЛ; одна прошивка — множество протоколов; выбор протокола через меню. Протоколы: УЭЛ, УКЛ, НКУ-CAN, НКУ-SD7, УИМ (список открыт). **2.3 Настройки.** Общий набор параметров + кастом под заказ. Настройки влияют на UI и на работу протокола (напр. адрес индикатора: 0..15 у НКУ-CAN, 1..50 у УИМ) — все связи предусмотрены (см. §6, §8). --- ## Часть II. Архитектура ### 3. Принципы 1. **Домен не знает железа.** Декодеры протоколов и доменная модель — чистый C без единого HAL-вызова, тестируются на хосте (Unity + fff) тем же способом, что модули bootloader. 2. **Одна прошивка — много протоколов.** Протокол выбирается в рантайме из настроек через реестр драйверов (§6). Новый протокол = реализовать декодер + зарегистрировать, без правок в остальных слоях. 3. **Деградация, а не отказ.** Любой сбой ассетов/layout/связи ведёт к безопасному упрощению индикации (§7, §11), устройство не «кирпичится» и не гаснет. 4. **Данные, а не код.** Макет, каталог ассетов, таблица приоритетов режимов, дескрипторы настроек протоколов — данные (компилируемые или загружаемые), не разветвления в коде. 5. **Границы модулей = конвенции репозитория.** Каждый слой — статическая библиотека CMake, публичные заголовки в `include/`, приватное в `src/`. Аппаратные модули — через `bsp/*`. ### 4. Слои ```bash ┌──────────────────────────────────────────────────────────────────────┐ │ app/ FreeRTOS-задачи, wiring, main (firmware/tft_app)│ L4 ├──────────────────────────────────────────────────────────────────────┤ │ ui/ view-model → экран (layout-движок, темы, fallback-рендер) │ │ menu/ экран настроек, навигация 2 кнопки │ L3 презентация │ audio_policy доменное событие → команды звука │ ├──────────────────────────────────────────────────────────────────────┤ │ controller редьюсер: sul_result_t (+кэш) → indication_task (diff) │ │ sul/ реестр драйверов; драйвер = decoder(pure) + transport(hw) │ L2 ДОМЕН │ elevator_model канонический sul_result_t, приоритеты режимов │ (чистый C, host-тесты) ├──────────────────────────────────────────────────────────────────────┤ │ gfx (compositor+PXP+fonts) audio_engine (WAV/playlist над bsp_mqs) │ │ assets (TLV-ридер, image cache) settings_store fs (FatFS SD/QSPI) │ L1 сервисы (адаптеры к железу) ├──────────────────────────────────────────────────────────────────────┤ │ bsp/* can uart_host opto button mqs sd qspi_flash sdram display led … │ L0 БОЕВОЙ, не трогаем └──────────────────────────────────────────────────────────────────────┘ ``` **Поток данных** (адаптирован из проекта `special`, поверх FreeRTOS): ```bash транспорт (CAN/UART) ─► sul_driver.transport ─► sul_driver.decode(frame) ─► sul_result_t │ poll + timeout→default ▼ controller.process(sul_result_t) ─► indication_task (dirty-флаги) │ ┌────────────────────────────────────────────────┬─────────┘ ▼ ▼ ui.render(task, model, settings) audio_policy(task, model, settings) (+ локальные входы: opto диспетчер, меню) │ audio_engine (bsp_mqs) ``` ### 5. Размещение и тестируемость | Слой | Где живёт | Тип | Host-тест | | ------------------------------------------------------------------------------- | -------------------------------------------------- | ----------- | --------------------------- | | `elevator_model`, `sul` (декодеры), `controller`, `audio_policy`, layout-солвер | `firmware/tft_app/src/domain/*`, `.../ui/layout/*` | чистый C | **да** (golden-векторы) | | `sul` транспорт-адаптеры | `firmware/tft_app/src/domain/sul/transport/*` | HW | HIL | | `gfx`, `audio_engine`, `assets`, `settings_store`, `fs` | `firmware/tft_app/src/services/*` | HW/адаптеры | частично (парсеры/TLV — да) | | `ui`, `menu`, `app` | `firmware/tft_app/src/{ui,menu,app}/*` | app | вид/меню — HIL | Host-тесты — `tests/host/tft_app_*` (Unity + fff, компилируем `.c` домена против моков bsp, по образцу `tests/host/mcuboot_port`, `tests/host/protocol`). ### 6. Связь с СУЛ (`sul`) **Канонический выход любого протокола** — `sul_result_t` (надмножество; простой протокол не заполняет лишнее): ```c #define SUL_POS_MAX 4 /* сейчас значимы 2; запас до 4 */ typedef struct { char pos[SUL_POS_MAX * 4 + 1]; /* UTF-8, позиция кабины (напр. "12","-1","П") */ char next[SUL_POS_MAX * 4 + 1];/* UTF-8, следующий этаж (или пусто) */ uint8_t direction; /* none / up / down / double */ /* Ортогональные сигналы (могут сосуществовать; приоритет разрешает controller): */ bool arrival; /* гонг */ bool movement; /* начало движения */ bool overload; /* перегруз */ bool fire_alarm; /* пожарная тревога */ bool lading; /* погрузка */ bool maintenance; /* сервисный режим */ bool fireman; /* режим пожарного */ bool seismic; /* сейсмоопасность */ bool error; /* авария */ uint16_t lading_secs; /* обратный отсчёт погрузки, 0 = нет */ uint8_t floor_num; /* производный числовой этаж для озвучки (0 = н/д) */ } sul_result_t; ``` - **Позиция — UTF-8 строка** (у нас реальные шрифты ASCII + кириллица), а не число: универсально для всех протоколов, host-тест сравнивает строки. Декодер не знает про шрифт. - **Escape-hatch под кастом не закладываем** (YAGNI); совместимость обеспечивает версия схемы. - **Валидация рендеримости** (§11): декодер может выдать кодпойнт вне покрытия активного шрифта (кастомные коды протокола) → по-символьный fallback на этапе рендера. **Драйвер = чистый декодер + транспорт-адаптер:** ```c typedef struct { const uint8_t *data; uint16_t len; uint32_t id; uint8_t bus; } sul_frame_t; /* Чистая функция — без железа, host-тестируется golden-векторами. */ typedef sul_status_t (*sul_decode_fn)(void *ctx, const sul_frame_t *frame, sul_result_t *out); typedef struct { uint8_t id; /* идентификатор протокола (стабильный) */ const char *name; /* для меню */ sul_decode_fn decode; /* pure */ const sul_settings_desc_t *settings; /* per-protocol параметры (§8) */ /* транспорт (CAN/UART) — отдельный тонкий адаптер, привязан к драйверу */ } sul_driver_t; ``` - **Реестр** `sul_registry[]` — таблица драйверов по `id`. Активный выбирается из настроек. Добавление протокола = запись в таблицу. - **Poll + timeout.** `sul` опрашивается периодически; при отсутствии кадров дольше таймаута выдаётся `default` (потеря связи → сброс режимного состояния), как в `special`/OLD_PROJECT. ### 7. Контроллер и приоритеты режимов `controller.process(sul_result_t*) → indication_task_t` (dirty-флаги: `pos_pending`, `direction_pending`, `mode_pending`, `arrival_pending`, …) — презентация перерисовывает и озвучивает только изменившееся (кэш прошлого состояния внутри контроллера). - Ортогональные булевы сигналы `sul_result_t` сходятся в **один экранный режим** через **таблицу приоритетов** (пожар > перегруз > сейсмо > сервис > … > норма). - **Таблица приоритетов — данные, потенциально клиентские**, живёт рядом с клиентским конфигом/layout, а не хардкодом в контроллере. Контроллер применяет активную таблицу. ### 8. Настройки Три раздельных источника (не смешивать): 1. **`sul_result_t`** — только данные от СУЛ (§6). 2. **`settings`** — конфиг устройства/пользователя: громкости, лого, серийник, ёмкость, год, выбранный протокол, панель (provisioning). Хранится на QSPI (сектор настроек), формат с магиком/версией/CRC (развитие `settings_manager` из OLD_PROJECT). 3. **Локальные входы** — оптовходы (диспетчерский вызов/ответ) и кнопки/меню; вливаются на уровне контроллера/презентации, не часть протокольных данных. **Per-protocol настройки.** Каждый протокол регистрирует `sul_settings_desc_t` — какие у него параметры, диапазоны, подписи для меню (напр. адрес 0..15 у НКУ-CAN, 1..50 у УИМ). `settings` держит слайс под активный протокол; `driver` получает свой конфиг; меню строится из дескриптора (развитие `settings_mgr_bind_menu`). Общие настройки — отдельно от протокольных. ### 9. Дисплеи и платы Платы отличаются **только RGB-интерфейсом LCDIF** (TFT4 — 40pin без пинов ориентации; TFT7/8/10 — 50pin с U/D·L/R). SDRAM, SEMC, периферия — одинаковы. Панели TFT7/8/10 уже разведены рантаймом в `bsp_display` (таблица `panel_config[]`: тайминги, клок, `has_orientation_pins`). | Профиль сборки | LCDIF | Панель | Клок | | -------------- | --------------------- | ---------------------------------------- | --------- | | `app-tft4` | 40pin, без ориентации | TFT4 (фикс) | Video PLL | | `app-big` | 50pin, с U/D·L/R | 7 / 8 / 10 — **рантайм** из provisioning | PLL2 | Различие изолировано в одном board-файле пин-мукса LCDIF. **Тип панели — provisioning-параметр** (пишется service_tui), не пользовательская настройка. Матрица сборки — два buildPreset (`app-tft4`, `app-big`), как `bootloader`/`firmware-test`. ### 10. Карта QSPI (размер-независимая) Прошивка работает на разных QSPI NOR (W25Q128 16 МБ / 256 / 512 …). Поэтому **все критичные регионы — по фиксированным смещениям** (компайл-тайм, без рантайм-детекта), а **размер-зависим только регион ассетов**: он стартует с фиксированного адреса и тянется до конца чипа (`длина = ёмкость − assets_start`; ёмкость — по JEDEC-ID). | Регион | Смещение | Размер | Владелец | | ----------------- | ---------- | ----------------- | ---------- | | bootloader | `0x000000` | 256 КБ | bootloader | | slot A (tft_app) | `0x040000` | 2 МБ | MCUboot | | slot Б (tft_app) | `0x240000` | 2 МБ | MCUboot | | **layout-регион** | `0x440000` | 64 КБ | tft_app | | **settings** | `0x450000` | 8 КБ (2 сектора) | tft_app | | **assets-регион** | `0x452000` | остаток до конца | tft_app | - **settings — фиксированный `0x450000`, размер-независимо** (общий адрес для app и service_tui, без вычислений из ёмкости). Отведено 2 сектора: первый рабочий, второй — задел под power-safe ping-pong (erase+write настроек не атомарен; ping-pong защищает от обрыва питания). Клиентский UX-зоопарк (ярус C настроек, §8) хранится в TLV в **layout-регионе**, не здесь. - **assets — остаток до конца чипа**: единственный размер-зависимый регион; больше флеш = больше места под ассеты, без правок карты. layout-, settings- и assets-регионы — **вне** flash-area загрузчика (bootutil про них не знает). Разметка фиксируется в **едином partition-заголовке**, из которого читают app и генератор для service_tui. ### 11. Ассеты, layout и fallback **Разделяем два артефакта** вместо монолитного `style.img`: - **layout** — декларативная таблица виджетов (что рисуем: позиция/стрелка/режим/лого/…, якорь, размер в %/единицах, шрифт, привязка к полю модели). Якорное позиционирование → один макет раскладывается под 480×272 и под 1024×600. Формат — **TLV**, схема версионируется. Layout-солвер (якорь→пиксели) — чистый C, host-тест (golden-render). Виджеты бывают **примитивные** (text/rect/line/arrow) и **спрайтовые** (sprite-by-id) — единый движок. - **assets** — упакованный индексированный **TLV-бандл** (host-утилита `tools/`): PNG → сырой ARGB8888 на этапе пака (в рантайме нет lodepng и boot-time декодирования; XIP-mmap с FlexSPI, блит без копии). Звук — WAV в том же бандле. **Загрузка layout при старте:** сначала layout-регион QSPI (магик+версия схемы+CRC+совместимость); если валиден — берём его; иначе — **встроенный default**. - **Модель 1 (baked):** нужный layout вкомпилен как default в app-слот → цельный подписанный образ, тестируется целиком, деплой через bootloader A/B. layout-регион пуст. - **Модель 2 (injected):** базовый бинарь; service_tui пишет клиентский layout-блоб в регион (вне слота → app-обновление его не трогает). Пересборка не нужна. Валидация блоба — на host (схема + golden-render), поэтому проверка клиентского layout не требует сборки клиентского бинаря. **Fallback (safe-mode).** `default`-layout — **asset-free и FS-free**: не монтирует QSPI-FAT/SD, не трогает TLV-ридер. Зависимости — только `bsp_display` + `bsp_can`/`sul` + домен + один вкомпилированный шрифт + примитивы `gfx`. Рисует **только этаж + стрелки (вверх/вниз)**, чёрный фон, без звука. Это же — первый экран walking-skeleton (§PLAN, Фаза 1). **Деградация — по-виджетно.** layout битый → полный fallback. layout валиден, но ассет отсутствует/битый → рисуем **примитивную форму этого виджета**, а не полный откат. Символ вне покрытия шрифта → по-символьный fallback. Решение защёлкивается на старте (при XIP-mmap рантайм не ревалидирует — иначе битое чтение = hardfault); валидация ассетов — CRC в индексе TLV. ### 12. Обновление в поле - **Ассеты/layout — app-side updater** (доменные данные; app знает схему и валидацию; переиспользуем `bsp_qspi` + FatFS-на-SD, как `update_style_task` в OLD_PROJECT). Запись — потоковая, erase-before-write по 4 КБ, **заголовок-валидатор пишется последним** (торн-запись → регион невалиден → безопасный fallback, не кирпич). - **Provisioning-заливка** (service_tui, заводской/сервисный контекст): канал — USB-CDC загрузчика или USB-SDP ROM, **при условии приемлемой скорости**. Если заливка ассетов этим путём окажется слишком медленной — отказываемся и делаем **только через microSD**. Решение — по замеру на Фазе обновлений (§PLAN). ### 13. Тестирование - **Host (Unity + fff):** декодеры всех протоколов (golden-векторы кадров → `sul_result_t`), контроллер (diff + приоритеты), layout-солвер (golden-render), TLV-ридер/паковщик, парсеры настроек, валидация рендеримости. - **HIL:** gfx/PXP/ELCDIF, audio/MQS, SD/QSPI, реальные транспорты CAN/UART (по образцу `tests/target/*` и `06_test_firmware_*`). ### 14. Errata-вотчлист (IMXRT1050 Chip Errata Rev. 2.1) > Риски кремния, привязанные к фазам. Многие пункты — **только для ревизии A0** > (исправлены в A1) → на A1-кремнии неактуальны. **Открытый вопрос: уточнить ревизию > кремния стендовых плат (A0/A1)** — определяет применимость половины списка. Источник — > `DOCS/MANUALS/ERRATA.pdf` (IMXRT1050CE, Rev. 2.1, 08/2019). | Errata | Суть | Фаза | Ревизия | | --- | --- | --- | --- | | **ERR011138** | LCDIF: последовательное программирование LUT может сбоить, если две записи идут близко. | 4/9 — актуально **только если** используем LUT ELCDIF для Index8BPP-фреймбуфера (экономия RAM ~4× vs ARGB8888). | A0, исправлено в A1 | | **ERR011207** | FlexSPI: при `FLEXSPI_AHBCR[PREFETCHEN]=1` в редком случае возвращаются неверные данные. | 4 — XIP-mmap блит ассетов из QSPI. Проверить конфиг prefetch в `bsp` FlexSPI. | A0, исправлено в A1 | | **ERR011377** | FlexSPI: бит статуса блокировки DLL неточен из-за тайминга. | 4/7 — запись/чтение QSPI-ассетов. | Без фикса | | **ERR011572** | Cortex-M7: write-through чтения/записи могут вернуть неверные данные. | Все — наш фреймбуфер `AT_NONCACHEABLE` обходит проблему; но любой write-through-регион под подозрением. | Без фикса | | **ERR006032 / ERR009527 / ERR009595** | FlexCAN: баги TX-пути (кадр с неверным ID при abort/deactivate в bus-idle; сбой abort; порча кадра при freeze/low-power в bus-off). | 2 — **если** «удалённая установка адреса» требует CAN **TX**; иначе 8. Пока только RX — низкий риск. | Без фикса | --- ## Приложение. Заимствования из проектов - `special` — эталон доменного дизайна: `UnifiedProtocolData`→`sul_result_t`, `proto_handler` (poll+timeout)→реестр `sul`, `controller`+`IndicationTask`. Адаптирован под FreeRTOS и снейк-кейс. - `OLD_PROJECT` (НКУ-CAN, TFT7/10) — боевые `bsp`, compositor (PXP), audio-движок, `settings_manager`, декодер НКУ-CAN (PACKET1..5, адресация, удалённая установка адреса). - `OLD_PROJECT_TFT8_UKL` — наш `bsp` (button/opto/sd/w25q/settings/file_loader), UART+CAN. - `TFT10_UIM`, `TFT4_UIM`, `TFT4_SD7`, `TFT4_UEL` — декодеры УИМ / НКУ-SD7 / УЭЛ, матрица «протокол × дисплей».