26 KiB
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. Принципы
- Домен не знает железа. Декодеры протоколов и доменная модель — чистый C без единого HAL-вызова, тестируются на хосте (Unity + fff) тем же способом, что модули bootloader.
- Одна прошивка — много протоколов. Протокол выбирается в рантайме из настроек через реестр драйверов (§6). Новый протокол = реализовать декодер + зарегистрировать, без правок в остальных слоях.
- Деградация, а не отказ. Любой сбой ассетов/layout/связи ведёт к безопасному упрощению индикации (§7, §11), устройство не «кирпичится» и не гаснет.
- Данные, а не код. Макет, каталог ассетов, таблица приоритетов режимов, дескрипторы настроек протоколов — данные (компилируемые или загружаемые), не разветвления в коде.
- Границы модулей = конвенции репозитория. Каждый слой — статическая библиотека 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. Настройки
Три раздельных источника (не смешивать):
sul_result_t— только данные от СУЛ (§6).settings— конфиг устройства/пользователя: громкости, лого, серийник, ёмкость, год, выбранный протокол, панель (provisioning). Хранится на QSPI (сектор настроек), формат с магиком/версией/CRC (развитиеsettings_managerиз OLD_PROJECT).- Локальные входы — оптовходы (диспетчерский вызов/ответ) и кнопки/меню; вливаются на уровне контроллера/презентации, не часть протокольных данных.
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 / УЭЛ, матрица «протокол × дисплей».