lift_indicator_suite/firmware/tft_app/ARCH.md

26 KiB
Raw Permalink Blame History

tft-app — архитектура

Прошивка лифтового индикатора для семейства MIMXRT1052 TFT (4 / 7 / 8 / 10"). Документ — живой источник истины по слоистой архитектуре. Статус фаз разработки — в 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). Макеты 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. Слои

┌──────────────────────────────────────────────────────────────────────┐
│ 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):

транспорт (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 (надмножество; простой протокол не заполняет лишнее):

#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 на этапе рендера.

Драйвер = чистый декодер + транспорт-адаптер:

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 — эталон доменного дизайна: UnifiedProtocolDatasul_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 / УЭЛ, матрица «протокол × дисплей».