# tft_app: Phase 0
This commit is contained in:
parent
e2e3f8aff8
commit
a9e668196a
24 changed files with 1084 additions and 28 deletions
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
|
|
@ -57,7 +57,7 @@
|
||||||
"loadFiles": [],
|
"loadFiles": [],
|
||||||
"runToEntryPoint": "main",
|
"runToEntryPoint": "main",
|
||||||
"rtos": "FreeRTOS",
|
"rtos": "FreeRTOS",
|
||||||
"preLaunchTask": "build:firmware-test-debug",
|
"preLaunchTask": "build:app-debug",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -47,8 +47,11 @@ add_subdirectory(lib)
|
||||||
if(NOT BUILD_TESTS_HOST)
|
if(NOT BUILD_TESTS_HOST)
|
||||||
add_subdirectory(firmware/test)
|
add_subdirectory(firmware/test)
|
||||||
add_subdirectory(firmware/bootloader)
|
add_subdirectory(firmware/bootloader)
|
||||||
# Заглушка tft_app для аппаратной верификации bootutil (Фаза 2) — удалить,
|
add_subdirectory(firmware/tft_app)
|
||||||
# когда появится реальный firmware/tft_app. См. firmware/bootloader/PLAN.md.
|
# Заглушка tft_app для аппаратной верификации bootutil (Фаза 2). Пока
|
||||||
|
# оставляем рядом с реальным firmware/tft_app как независимую A/B-регрессию
|
||||||
|
# загрузчика — удалить, когда app сам сможет служить стендом. См.
|
||||||
|
# firmware/bootloader/PLAN.md и firmware/tft_app/PLAN.md.
|
||||||
add_subdirectory(firmware/bootloader/test_stub)
|
add_subdirectory(firmware/bootloader/test_stub)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,8 @@
|
||||||
"test_mcuboot_boot_select",
|
"test_mcuboot_boot_select",
|
||||||
"test_slot_version",
|
"test_slot_version",
|
||||||
"test_update_policy",
|
"test_update_policy",
|
||||||
"test_recovery"
|
"test_recovery",
|
||||||
|
"test_tft_app_smoke"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -190,7 +191,8 @@
|
||||||
"test_mcuboot_boot_select",
|
"test_mcuboot_boot_select",
|
||||||
"test_slot_version",
|
"test_slot_version",
|
||||||
"test_update_policy",
|
"test_update_policy",
|
||||||
"test_recovery"
|
"test_recovery",
|
||||||
|
"test_tft_app_smoke"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
237
cmake/linker/MIMXRT1052xxxxx_app_slot.ld
Normal file
237
cmake/linker/MIMXRT1052xxxxx_app_slot.ld
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
/*
|
||||||
|
** ###################################################################
|
||||||
|
** Linker file — tft_app как MCUboot-слот-образ (Direct-XIP).
|
||||||
|
**
|
||||||
|
** Полный набор секций как в MIMXRT1052xxxxx_bootloader_flexspi_nor.ld
|
||||||
|
** (включая .ram_function → ITCM для AT_QUICKACCESS_SECTION_CODE, .ncache,
|
||||||
|
** DataQuickAccess), но адаптирован под слот:
|
||||||
|
** - НЕТ .flash_config / .ivt (образ грузит не BootROM, а bootloader
|
||||||
|
** software-jump'ом — FCB/IVT/DCD не нужны).
|
||||||
|
** - Перед vector table зарезервирован imgtool-заголовок (0x200):
|
||||||
|
** boot_select.c прыгает по flash_base + fa_off + ih_hdr_size.
|
||||||
|
** - m_text — на весь слот (2 МБ − header − vectors).
|
||||||
|
**
|
||||||
|
** КРИТИЧНО: .ram_function размещает код bsp_qspi_flash
|
||||||
|
** (AT_QUICKACCESS_SECTION_CODE, входная секция CodeQuickAccess) в ITCM и
|
||||||
|
** копируется startup'ом. Без неё этот код исполняется из flash, а любая
|
||||||
|
** IP-команда FlexSPI (bsp_qspi_init/erase/write) блокирует AHB-путь →
|
||||||
|
** HardFault на fetch следующей инструкции из той же flash (XIP). См.
|
||||||
|
** bsp/qspi_flash/README.md "XIP-безопасность" и
|
||||||
|
** docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md §5.
|
||||||
|
**
|
||||||
|
** Базовый адрес слота — через -Wl,--defsym=__slot_base__=0x...
|
||||||
|
** Slot A: 0x60040000 Slot Б: 0x60240000
|
||||||
|
**
|
||||||
|
** Copyright 2016 Freescale Semiconductor, Inc.
|
||||||
|
** Copyright 2016-2024 NXP
|
||||||
|
** SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
** ###################################################################
|
||||||
|
*/
|
||||||
|
|
||||||
|
ENTRY(Reset_Handler)
|
||||||
|
|
||||||
|
HEAP_SIZE = DEFINED(__heap_size__) ? __heap_size__ : 0x0400; /* newlib sbrk-heap; FreeRTOS heap_4 — свой массив в .bss */
|
||||||
|
STACK_SIZE = DEFINED(__stack_size__) ? __stack_size__ : 0x1000;
|
||||||
|
VECTOR_RAM_SIZE = DEFINED(__ram_vector_table__) ? 0x00000400 : 0;
|
||||||
|
|
||||||
|
SLOT_BASE = DEFINED(__slot_base__) ? __slot_base__ : 0x60040000;
|
||||||
|
SLOT_SIZE = 0x00200000; /* 2 МБ — = imgtool -S 0x200000 и flash_map */
|
||||||
|
IMGTOOL_HDR_SZ = 0x200; /* imgtool sign -H 0x200 */
|
||||||
|
VECTOR_SIZE = 0x400;
|
||||||
|
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
m_interrupts (RX) : ORIGIN = SLOT_BASE + IMGTOOL_HDR_SZ, LENGTH = VECTOR_SIZE
|
||||||
|
m_text (RX) : ORIGIN = SLOT_BASE + IMGTOOL_HDR_SZ + VECTOR_SIZE, LENGTH = SLOT_SIZE - IMGTOOL_HDR_SZ - VECTOR_SIZE
|
||||||
|
m_qacode (RX) : ORIGIN = 0x00000000, LENGTH = 0x00020000 /* SRAM_ITC 128 КБ — ramfunc */
|
||||||
|
m_data (RW) : ORIGIN = 0x20000000, LENGTH = 0x00020000 /* SRAM_DTC 128 КБ — .data/.bss/heap/stack */
|
||||||
|
m_data2 (RW) : ORIGIN = 0x20200000, LENGTH = 0x00040000 /* SRAM_OC 256 КБ — ncache */
|
||||||
|
m_sdram (RW) : ORIGIN = 0x80000000, LENGTH = 0x02000000 /* SDRAM 32 МБ — Фаза 0 не используется */
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
__NCACHE_REGION_START = ORIGIN(m_data2);
|
||||||
|
__NCACHE_REGION_SIZE = 0x2000; /* 8 КБ non-cacheable */
|
||||||
|
|
||||||
|
/* Vector table (imgtool header зарезервирован до неё в MEMORY). */
|
||||||
|
.interrupts :
|
||||||
|
{
|
||||||
|
__VECTOR_TABLE = .;
|
||||||
|
__Vectors = .;
|
||||||
|
. = ALIGN(4);
|
||||||
|
KEEP(*(.isr_vector))
|
||||||
|
. = ALIGN(4);
|
||||||
|
} > m_interrupts
|
||||||
|
|
||||||
|
.text :
|
||||||
|
{
|
||||||
|
. = ALIGN(4);
|
||||||
|
*(.text)
|
||||||
|
*(.text*)
|
||||||
|
*(.rodata)
|
||||||
|
*(.rodata*)
|
||||||
|
*(.glue_7)
|
||||||
|
*(.glue_7t)
|
||||||
|
*(.eh_frame)
|
||||||
|
KEEP (*(.init))
|
||||||
|
KEEP (*(.fini))
|
||||||
|
. = ALIGN(4);
|
||||||
|
} > m_text
|
||||||
|
|
||||||
|
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } > m_text
|
||||||
|
|
||||||
|
.ARM :
|
||||||
|
{
|
||||||
|
__exidx_start = .;
|
||||||
|
*(.ARM.exidx*)
|
||||||
|
__exidx_end = .;
|
||||||
|
} > m_text
|
||||||
|
|
||||||
|
.ctors :
|
||||||
|
{
|
||||||
|
__CTOR_LIST__ = .;
|
||||||
|
KEEP (*crtbegin.o(.ctors))
|
||||||
|
KEEP (*crtbegin?.o(.ctors))
|
||||||
|
KEEP (*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors))
|
||||||
|
KEEP (*(SORT(.ctors.*)))
|
||||||
|
KEEP (*(.ctors))
|
||||||
|
__CTOR_END__ = .;
|
||||||
|
} > m_text
|
||||||
|
|
||||||
|
.dtors :
|
||||||
|
{
|
||||||
|
__DTOR_LIST__ = .;
|
||||||
|
KEEP (*crtbegin.o(.dtors))
|
||||||
|
KEEP (*crtbegin?.o(.dtors))
|
||||||
|
KEEP (*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors))
|
||||||
|
KEEP (*(SORT(.dtors.*)))
|
||||||
|
KEEP (*(.dtors))
|
||||||
|
__DTOR_END__ = .;
|
||||||
|
} > m_text
|
||||||
|
|
||||||
|
.preinit_array :
|
||||||
|
{
|
||||||
|
PROVIDE_HIDDEN (__preinit_array_start = .);
|
||||||
|
KEEP (*(.preinit_array*))
|
||||||
|
PROVIDE_HIDDEN (__preinit_array_end = .);
|
||||||
|
} > m_text
|
||||||
|
|
||||||
|
.init_array :
|
||||||
|
{
|
||||||
|
PROVIDE_HIDDEN (__init_array_start = .);
|
||||||
|
KEEP (*(SORT(.init_array.*)))
|
||||||
|
KEEP (*(.init_array*))
|
||||||
|
PROVIDE_HIDDEN (__init_array_end = .);
|
||||||
|
} > m_text
|
||||||
|
|
||||||
|
.fini_array :
|
||||||
|
{
|
||||||
|
PROVIDE_HIDDEN (__fini_array_start = .);
|
||||||
|
KEEP (*(SORT(.fini_array.*)))
|
||||||
|
KEEP (*(.fini_array*))
|
||||||
|
PROVIDE_HIDDEN (__fini_array_end = .);
|
||||||
|
} > m_text
|
||||||
|
|
||||||
|
__etext = .;
|
||||||
|
__DATA_ROM = .;
|
||||||
|
|
||||||
|
.interrupts_ram :
|
||||||
|
{
|
||||||
|
. = ALIGN(4);
|
||||||
|
__VECTOR_RAM__ = .;
|
||||||
|
__interrupts_ram_start__ = .;
|
||||||
|
*(.m_interrupts_ram)
|
||||||
|
. += VECTOR_RAM_SIZE;
|
||||||
|
. = ALIGN(4);
|
||||||
|
__interrupts_ram_end__ = .;
|
||||||
|
} > m_data
|
||||||
|
|
||||||
|
__VECTOR_RAM = DEFINED(__ram_vector_table__) ? __VECTOR_RAM__ : ORIGIN(m_interrupts);
|
||||||
|
__RAM_VECTOR_TABLE_SIZE_BYTES = DEFINED(__ram_vector_table__) ? (__interrupts_ram_end__ - __interrupts_ram_start__) : 0x0;
|
||||||
|
|
||||||
|
.data : AT(__DATA_ROM)
|
||||||
|
{
|
||||||
|
. = ALIGN(4);
|
||||||
|
__DATA_RAM = .;
|
||||||
|
__data_start__ = .;
|
||||||
|
*(.data)
|
||||||
|
*(.data*)
|
||||||
|
*(DataQuickAccess) /* quick access data section */
|
||||||
|
KEEP(*(.jcr*))
|
||||||
|
. = ALIGN(4);
|
||||||
|
__data_end__ = .;
|
||||||
|
} > m_data
|
||||||
|
|
||||||
|
__ram_function_flash_start = __DATA_ROM + (__data_end__ - __data_start__); /* startup копирует ramfunc из flash в ITCM */
|
||||||
|
|
||||||
|
.ram_function : AT(__ram_function_flash_start)
|
||||||
|
{
|
||||||
|
. = ALIGN(32);
|
||||||
|
__ram_function_start__ = .;
|
||||||
|
*(CodeQuickAccess) /* AT_QUICKACCESS_SECTION_CODE — bsp_qspi FlexSPI IP-код */
|
||||||
|
. = ALIGN(128);
|
||||||
|
__ram_function_end__ = .;
|
||||||
|
} > m_qacode
|
||||||
|
|
||||||
|
__NDATA_ROM = __ram_function_flash_start + (__ram_function_end__ - __ram_function_start__);
|
||||||
|
.ncache.init :
|
||||||
|
{
|
||||||
|
. = ALIGN(32);
|
||||||
|
__noncachedata_start__ = .;
|
||||||
|
*(NonCacheable.init)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__noncachedata_init_end__ = .;
|
||||||
|
} > m_data2
|
||||||
|
. = __noncachedata_init_end__;
|
||||||
|
.ncache :
|
||||||
|
{
|
||||||
|
*(NonCacheable)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__noncachedata_end__ = .;
|
||||||
|
} > m_data2
|
||||||
|
|
||||||
|
__DATA_END = __NDATA_ROM;
|
||||||
|
text_end = ORIGIN(m_text) + LENGTH(m_text);
|
||||||
|
ASSERT(__DATA_END <= text_end, "region m_text overflowed with text and data")
|
||||||
|
ASSERT(text_end <= SLOT_BASE + SLOT_SIZE, "app вышел за пределы слота (2 МБ)")
|
||||||
|
ASSERT((__noncachedata_end__ - ORIGIN(m_data2)) <= LENGTH(m_data2), "m_data2 ncache overflow")
|
||||||
|
|
||||||
|
.bss :
|
||||||
|
{
|
||||||
|
. = ALIGN(4);
|
||||||
|
__START_BSS = .;
|
||||||
|
__bss_start__ = .;
|
||||||
|
*(.bss)
|
||||||
|
*(.bss*)
|
||||||
|
*(COMMON)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__bss_end__ = .;
|
||||||
|
__END_BSS = .;
|
||||||
|
} > m_data
|
||||||
|
|
||||||
|
.heap :
|
||||||
|
{
|
||||||
|
. = ALIGN(8);
|
||||||
|
__end__ = .;
|
||||||
|
PROVIDE(end = .);
|
||||||
|
__HeapBase = .;
|
||||||
|
. += HEAP_SIZE;
|
||||||
|
__HeapLimit = .;
|
||||||
|
__heap_limit = .;
|
||||||
|
} > m_data
|
||||||
|
|
||||||
|
.stack :
|
||||||
|
{
|
||||||
|
. = ALIGN(8);
|
||||||
|
. += STACK_SIZE;
|
||||||
|
} > m_data
|
||||||
|
|
||||||
|
__StackTop = ORIGIN(m_data) + LENGTH(m_data);
|
||||||
|
__StackLimit = __StackTop - STACK_SIZE;
|
||||||
|
PROVIDE(__stack = __StackTop);
|
||||||
|
|
||||||
|
.ARM.attributes 0 : { *(.ARM.attributes) }
|
||||||
|
|
||||||
|
ASSERT(__StackLimit >= __HeapLimit, "region m_data overflowed with stack and heap")
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
**1.1 Индикация в реальном времени.** Местоположение кабины, спецрежимы, музыкальное
|
**1.1 Индикация в реальном времени.** Местоположение кабины, спецрежимы, музыкальное
|
||||||
сопровождение при движении, озвучка сигналов.
|
сопровождение при движении, озвучка сигналов.
|
||||||
|
|
||||||
- Сигналы от СУЛ приходят по последовательному интерфейсу (+24 В UART, кастомный
|
- Сигналы от СУЛ приходят по последовательному интерфейсу (+24 В UART, кастомный
|
||||||
бинарный протокол) либо по CAN.
|
бинарный протокол) либо по CAN.
|
||||||
- Отображение состояния двух диспетчерских оптовходов: «Вызов подан» / «Вызов принят» / пусто.
|
- Отображение состояния двух диспетчерских оптовходов: «Вызов подан» / «Вызов принят» / пусто.
|
||||||
|
|
@ -56,7 +57,7 @@ A/B Direct-XIP; ассеты/layout — задача tft-app, см. §12).
|
||||||
|
|
||||||
### 4. Слои
|
### 4. Слои
|
||||||
|
|
||||||
```
|
```bash
|
||||||
┌──────────────────────────────────────────────────────────────────────┐
|
┌──────────────────────────────────────────────────────────────────────┐
|
||||||
│ app/ FreeRTOS-задачи, wiring, main (firmware/tft_app)│ L4
|
│ app/ FreeRTOS-задачи, wiring, main (firmware/tft_app)│ L4
|
||||||
├──────────────────────────────────────────────────────────────────────┤
|
├──────────────────────────────────────────────────────────────────────┤
|
||||||
|
|
@ -77,7 +78,7 @@ A/B Direct-XIP; ассеты/layout — задача tft-app, см. §12).
|
||||||
|
|
||||||
**Поток данных** (адаптирован из проекта `special`, поверх FreeRTOS):
|
**Поток данных** (адаптирован из проекта `special`, поверх FreeRTOS):
|
||||||
|
|
||||||
```
|
```bash
|
||||||
транспорт (CAN/UART) ─► sul_driver.transport ─► sul_driver.decode(frame) ─► sul_result_t
|
транспорт (CAN/UART) ─► sul_driver.transport ─► sul_driver.decode(frame) ─► sul_result_t
|
||||||
│ poll + timeout→default
|
│ poll + timeout→default
|
||||||
▼
|
▼
|
||||||
|
|
@ -92,12 +93,12 @@ A/B Direct-XIP; ассеты/layout — задача tft-app, см. §12).
|
||||||
|
|
||||||
### 5. Размещение и тестируемость
|
### 5. Размещение и тестируемость
|
||||||
|
|
||||||
| Слой | Где живёт | Тип | Host-тест |
|
| Слой | Где живёт | Тип | Host-тест |
|
||||||
|---|---|---|---|
|
| ------------------------------------------------------------------------------- | -------------------------------------------------- | ----------- | --------------------------- |
|
||||||
| `elevator_model`, `sul` (декодеры), `controller`, `audio_policy`, layout-солвер | `firmware/tft_app/src/domain/*`, `.../ui/layout/*` | чистый C | **да** (golden-векторы) |
|
| `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 |
|
| `sul` транспорт-адаптеры | `firmware/tft_app/src/domain/sul/transport/*` | HW | HIL |
|
||||||
| `gfx`, `audio_engine`, `assets`, `settings_store`, `fs` | `firmware/tft_app/src/services/*` | HW/адаптеры | частично (парсеры/TLV — да) |
|
| `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 |
|
| `ui`, `menu`, `app` | `firmware/tft_app/src/{ui,menu,app}/*` | app | вид/меню — HIL |
|
||||||
|
|
||||||
Host-тесты — `tests/host/tft_app_*` (Unity + fff, компилируем `.c` домена против моков bsp,
|
Host-тесты — `tests/host/tft_app_*` (Unity + fff, компилируем `.c` домена против моков bsp,
|
||||||
по образцу `tests/host/mcuboot_port`, `tests/host/protocol`).
|
по образцу `tests/host/mcuboot_port`, `tests/host/protocol`).
|
||||||
|
|
@ -190,10 +191,10 @@ typedef struct {
|
||||||
— 50pin с U/D·L/R). SDRAM, SEMC, периферия — одинаковы. Панели TFT7/8/10 уже разведены рантаймом
|
— 50pin с U/D·L/R). SDRAM, SEMC, периферия — одинаковы. Панели TFT7/8/10 уже разведены рантаймом
|
||||||
в `bsp_display` (таблица `panel_config[]`: тайминги, клок, `has_orientation_pins`).
|
в `bsp_display` (таблица `panel_config[]`: тайминги, клок, `has_orientation_pins`).
|
||||||
|
|
||||||
| Профиль сборки | LCDIF | Панель | Клок |
|
| Профиль сборки | LCDIF | Панель | Клок |
|
||||||
|---|---|---|---|
|
| -------------- | --------------------- | ---------------------------------------- | --------- |
|
||||||
| `app-tft4` | 40pin, без ориентации | TFT4 (фикс) | Video PLL |
|
| `app-tft4` | 40pin, без ориентации | TFT4 (фикс) | Video PLL |
|
||||||
| `app-big` | 50pin, с U/D·L/R | 7 / 8 / 10 — **рантайм** из provisioning | PLL2 |
|
| `app-big` | 50pin, с U/D·L/R | 7 / 8 / 10 — **рантайм** из provisioning | PLL2 |
|
||||||
|
|
||||||
Различие изолировано в одном board-файле пин-мукса LCDIF. **Тип панели — provisioning-параметр**
|
Различие изолировано в одном board-файле пин-мукса LCDIF. **Тип панели — provisioning-параметр**
|
||||||
(пишется service_tui), не пользовательская настройка. Матрица сборки — два buildPreset
|
(пишется service_tui), не пользовательская настройка. Матрица сборки — два buildPreset
|
||||||
|
|
@ -201,14 +202,14 @@ typedef struct {
|
||||||
|
|
||||||
### 10. Карта QSPI (W25Q128, 16 МБ)
|
### 10. Карта QSPI (W25Q128, 16 МБ)
|
||||||
|
|
||||||
| Регион | Смещение | Размер | Владелец |
|
| Регион | Смещение | Размер | Владелец |
|
||||||
|---|---|---|---|
|
| ----------------- | ---------- | ------- | ---------- |
|
||||||
| bootloader | `0x000000` | 256 КБ | bootloader |
|
| bootloader | `0x000000` | 256 КБ | bootloader |
|
||||||
| slot A (tft_app) | `0x040000` | 2 МБ | MCUboot |
|
| slot A (tft_app) | `0x040000` | 2 МБ | MCUboot |
|
||||||
| slot Б (tft_app) | `0x240000` | 2 МБ | MCUboot |
|
| slot Б (tft_app) | `0x240000` | 2 МБ | MCUboot |
|
||||||
| **layout-регион** | `0x440000` | 64 КБ | tft_app |
|
| **layout-регион** | `0x440000` | 64 КБ | tft_app |
|
||||||
| **assets-регион** | `0x450000` | ≈ 11 МБ | tft_app |
|
| **assets-регион** | `0x450000` | ≈ 11 МБ | tft_app |
|
||||||
| settings | `0xFFE000` | 4 КБ | tft_app |
|
| settings | `0xFFE000` | 4 КБ | tft_app |
|
||||||
|
|
||||||
layout- и assets-регионы — **вне** flash-area загрузчика (bootutil про них не знает). Разметка
|
layout- и assets-регионы — **вне** flash-area загрузчика (bootutil про них не знает). Разметка
|
||||||
фиксируется в **едином partition-заголовке**, из которого читают app и генератор для service_tui.
|
фиксируется в **едином partition-заголовке**, из которого читают app и генератор для service_tui.
|
||||||
|
|
|
||||||
122
firmware/tft_app/CMakeLists.txt
Normal file
122
firmware/tft_app/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
# firmware/tft_app/CMakeLists.txt
|
||||||
|
#
|
||||||
|
# tft_app — прошивка лифтового индикатора. Собирается как MCUboot-слот-образ
|
||||||
|
# (Direct-XIP), подписывается imgtool (см. just build::sign-app). Архитектура —
|
||||||
|
# ARCH.md, план фаз — PLAN.md.
|
||||||
|
#
|
||||||
|
# Фаза 0 (каркас): FreeRTOS-мигалка + кормление WDOG + рантайм self-confirm
|
||||||
|
# слота + UART диагностика (LPUART1/MCU-Link VCOM — см. src/app/main.c про
|
||||||
|
# отказ от USB CDC под FreeRTOS для этой цели).
|
||||||
|
|
||||||
|
if(BUILD_TESTS_HOST)
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# FreeRTOS ядро — первый RTOS-потребитель в репозитории. Собираем свой STATIC
|
||||||
|
# таргет из вендоренных исходников SDK (порт GCC/ARM_CM4F пригоден для cm7f,
|
||||||
|
# heap_4). Конфиг — src/app/FreeRTOSConfig.h.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
set(FREERTOS_DIR ${CMAKE_SOURCE_DIR}/sdk/rtos/freertos/freertos-kernel)
|
||||||
|
|
||||||
|
add_library(
|
||||||
|
freertos_kernel STATIC
|
||||||
|
${FREERTOS_DIR}/tasks.c
|
||||||
|
${FREERTOS_DIR}/list.c
|
||||||
|
${FREERTOS_DIR}/queue.c
|
||||||
|
${FREERTOS_DIR}/timers.c
|
||||||
|
${FREERTOS_DIR}/event_groups.c
|
||||||
|
${FREERTOS_DIR}/stream_buffer.c
|
||||||
|
${FREERTOS_DIR}/portable/GCC/ARM_CM4F/port.c
|
||||||
|
${FREERTOS_DIR}/portable/MemMang/heap_4.c)
|
||||||
|
|
||||||
|
target_include_directories(
|
||||||
|
freertos_kernel
|
||||||
|
PUBLIC ${FREERTOS_DIR}/include
|
||||||
|
${FREERTOS_DIR}/portable/GCC/ARM_CM4F
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/app) # FreeRTOSConfig.h
|
||||||
|
|
||||||
|
# core_cm7.h, MIMXRT1052.h, SystemCoreClock
|
||||||
|
target_link_libraries(freertos_kernel PUBLIC sdk_device)
|
||||||
|
|
||||||
|
# Вендоренное ядро — не наш стиль/warnings.
|
||||||
|
target_compile_options(freertos_kernel PRIVATE -w)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# app — исполняемый образ слота
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
include(${CMAKE_SOURCE_DIR}/firmware/bootloader/mcuboot_port/bootutil_sources.cmake)
|
||||||
|
|
||||||
|
add_executable(
|
||||||
|
app
|
||||||
|
src/app/main.c
|
||||||
|
${BSP_GENERATED}/clock_config.c # BOARD_BootClockRUN (зовётся board_hw_init)
|
||||||
|
${BSP_STARTUP_FILE}
|
||||||
|
${BSP_SYSCALLS_FILE}
|
||||||
|
# confirm_self(): flash_area_* + boot_set_next. Переиспользуем backend и
|
||||||
|
# bootutil_public (общий с bootloader/test_stub). flash_map_backend.c зовёт
|
||||||
|
# led_status_tick_install() — даём локальную no-op заглушку (boot_shims.c).
|
||||||
|
src/app/boot_shims.c
|
||||||
|
# bsp_tick, скомпилирован ПРЯМО в app (не через libbsp_tick.a) — режим задан
|
||||||
|
# ниже (BSP_TICK_FREERTOS_MODE), чтобы не тянуть SysTick_Handler,
|
||||||
|
# конфликтующий с FreeRTOS-портом. Приходит транзитивно через bsp_uart_host.
|
||||||
|
${CMAKE_SOURCE_DIR}/bsp/tick/src/tick.c
|
||||||
|
${CMAKE_SOURCE_DIR}/firmware/bootloader/mcuboot_port/flash_map_backend.c
|
||||||
|
${MCUBOOT_BOOTUTIL_DIR}/src/bootutil_public.c)
|
||||||
|
|
||||||
|
target_include_directories(
|
||||||
|
app PRIVATE src/app ${MCUBOOT_BOOTUTIL_INCLUDES}
|
||||||
|
${CMAKE_SOURCE_DIR}/firmware/bootloader/src) # led_status.h
|
||||||
|
|
||||||
|
# __STARTUP_INITIALIZE_RAMFUNCTION — startup копирует .ram_function из flash в
|
||||||
|
# ITCM. БЕЗ него ramfunc-код bsp_qspi (AT_QUICKACCESS_SECTION_CODE) в ITCM не
|
||||||
|
# попадает → запись QSPI виснет (см. bsp/qspi_flash/README.md). Без USB CDC
|
||||||
|
# __STARTUP_INITIALIZE_NONCACHEDATA не нужен — ни один линкуемый модуль не
|
||||||
|
# кладёт данные в NonCacheable/NonCacheable.init (проверено).
|
||||||
|
target_compile_definitions(
|
||||||
|
app PRIVATE APP_OWN_SLOT_ID=0 __STARTUP_CLEAR_BSS __STARTUP_INITIALIZE_RAMFUNCTION)
|
||||||
|
|
||||||
|
# Вендоренный bootutil_public.c — под своими warnings (как в bootloader/stub).
|
||||||
|
set_source_files_properties(
|
||||||
|
${MCUBOOT_BOOTUTIL_DIR}/src/bootutil_public.c
|
||||||
|
PROPERTIES COMPILE_OPTIONS "${MCUBOOT_VENDORED_COMPILE_OPTIONS}")
|
||||||
|
|
||||||
|
# FreeRTOS-режим bsp_tick: без него tick.c определяет SysTick_Handler
|
||||||
|
# (bare-metal), конфликтующий с обработчиком FreeRTOS-порта. Компилируется прямо
|
||||||
|
# в app (link-line раньше архивов) → bare-metal member из libbsp_tick.a не
|
||||||
|
# извлекается.
|
||||||
|
set_source_files_properties(
|
||||||
|
${CMAKE_SOURCE_DIR}/bsp/tick/src/tick.c PROPERTIES COMPILE_DEFINITIONS
|
||||||
|
BSP_TICK_FREERTOS_MODE)
|
||||||
|
|
||||||
|
target_link_libraries(
|
||||||
|
app
|
||||||
|
PRIVATE bsp_board
|
||||||
|
bsp_led
|
||||||
|
bsp_wdog
|
||||||
|
bsp_boot_state
|
||||||
|
bsp_qspi_flash
|
||||||
|
bsp_uart_host
|
||||||
|
bsp_boot_xip_no_dcd
|
||||||
|
freertos_kernel
|
||||||
|
port_log_uart) # UART диагностика (utils_log + bsp_uart_host, LPUART1/MCU-Link VCOM)
|
||||||
|
|
||||||
|
target_link_options(
|
||||||
|
app
|
||||||
|
PRIVATE
|
||||||
|
-Wl,--gc-sections
|
||||||
|
-Wl,--print-memory-usage
|
||||||
|
-Wl,-Map=${CMAKE_BINARY_DIR}/app.map
|
||||||
|
-Wl,--defsym=__slot_base__=0x60040000 # Slot A; Slot Б — на фазе обновления
|
||||||
|
-Wl,--defsym=__stack_size__=0x1000
|
||||||
|
-Wl,--defsym=__heap_size__=0x400
|
||||||
|
-T${CMAKE_SOURCE_DIR}/cmake/linker/MIMXRT1052xxxxx_app_slot.ld)
|
||||||
|
|
||||||
|
set_target_properties(app PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
TARGET app
|
||||||
|
POST_BUILD
|
||||||
|
COMMAND ${CMAKE_OBJCOPY} -O binary $<TARGET_FILE:app> ${CMAKE_BINARY_DIR}/app.bin
|
||||||
|
COMMAND ${CMAKE_SIZE} $<TARGET_FILE:app>
|
||||||
|
COMMENT "Generating app.bin")
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
| # | Фаза | Статус |
|
| # | Фаза | Статус |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 0 | Каркас проекта и сборка | ⬜ |
|
| 0 | Каркас проекта и сборка | ✅ |
|
||||||
| 1 | Walking skeleton (CAN → модель → этаж + стрелки) | ⬜ |
|
| 1 | Walking skeleton (CAN → модель → этаж + стрелки) | ⬜ |
|
||||||
| 2 | Домен: полный `sul_result_t`, приоритеты, таймаут | ⬜ |
|
| 2 | Домен: полный `sul_result_t`, приоритеты, таймаут | ⬜ |
|
||||||
| 3 | Настройки и меню, локальные входы (opto) | ⬜ |
|
| 3 | Настройки и меню, локальные входы (opto) | ⬜ |
|
||||||
|
|
@ -28,7 +28,62 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Фаза 0 — Каркас проекта и сборка ⬜
|
## Фаза 0 — Каркас проекта и сборка ✅
|
||||||
|
|
||||||
|
**Итог.** Подтверждено на железе дважды (первый запуск + power cycle через
|
||||||
|
переустановку с SD): `image_ok` персистит через рестарт (`before-confirm` на
|
||||||
|
втором запуске читает `1`, записанный на первом), загрузчик не откатывает
|
||||||
|
образ. Закрывает оба исходных симптома отчёта: (1) SWD-прошивка работала один
|
||||||
|
раз, но не переживала перезапуск; (2) обновление через SD зацикливалось
|
||||||
|
(запись в слот A + рестарт). Причина обоих — один и тот же баг (ниже).
|
||||||
|
|
||||||
|
**Root-cause (главная находка Фазы 0) — забытый дефайн `__STARTUP_
|
||||||
|
INITIALIZE_RAMFUNCTION`.** Запись QSPI из `app` виснет в `qspi_write_fifo`
|
||||||
|
(FlexSPI не дренажит IP TX FIFO), хотя весь write-путь `bsp_qspi` помечен
|
||||||
|
`AT_QUICKACCESS_SECTION_CODE` (ramfunc). Причина: startup копирует секцию
|
||||||
|
`.ram_function` из flash в ITCM только под `#ifdef __STARTUP_
|
||||||
|
INITIALIZE_RAMFUNCTION` — `app` его не задавал (только `__STARTUP_CLEAR_BSS`).
|
||||||
|
Оба рабочих потребителя `bsp_qspi` (firmware_test, bootloader) его задают.
|
||||||
|
Без него в ITCM оставался leftover ramfunc **загрузчика** (исполнялся первым)
|
||||||
|
→ чтения «случайно работали» на совместимом коде, запись — падала.
|
||||||
|
`bsp/qspi_flash/README.md` прямо предупреждает («без него ITCM содержит нули
|
||||||
|
→ HardFault»). Подтверждено изолированным QSPI self-test (erase+write+readback)
|
||||||
|
до возврата полной диагностики.
|
||||||
|
|
||||||
|
**Прочие находки bring-up (для истории и будущих фаз — Фаза 3/settings и
|
||||||
|
Фаза 7/assets тоже пишут QSPI, наступят на смежное):**
|
||||||
|
- `confirm_self()`/любая `flash_area_*` требует `bsp_qspi_init()` заранее —
|
||||||
|
иначе запись молча падает (независимо от ramfunc-бага).
|
||||||
|
- Слот-линкер обязан иметь секцию `.ram_function` (ITCM) — `app_slot.ld`
|
||||||
|
переписан по образцу боотлоадерного (все RAM-секции).
|
||||||
|
- `bsp_usb_cdc_init()`/аналогичные bsp-вызовы, дёргающие `bsp_delay()`, нельзя
|
||||||
|
звать до `vTaskStartScheduler()` — в FreeRTOS-режиме `bsp_tick` это
|
||||||
|
`xTaskGetTickCount()`, который не идёт до старта планировщика → зависает
|
||||||
|
навсегда. Любая tick-зависимая bsp-инициализация — только из задачи.
|
||||||
|
- Первый потребитель `BSP_TICK_FREERTOS_MODE` в репозитории вскрыл 2
|
||||||
|
латентных бага: (1) общий `bsp_tick` — bare-metal STATIC-либа,
|
||||||
|
`SysTick_Handler` конфликтует с FreeRTOS-портом — решение: компилировать
|
||||||
|
`tick.c` прямо в таргет с `BSP_TICK_FREERTOS_MODE`, не через `libbsp_tick.a`;
|
||||||
|
(2) `configTICK_RATE_HZ` нельзя как `((TickType_t)1000)` — `#if` в `tick.c`
|
||||||
|
не разбирает cast, только `1000`.
|
||||||
|
- **Диагностика — UART (LPUART1/MCU-Link VCOM), не USB CDC.** CDC под FreeRTOS
|
||||||
|
(первый потребитель target-side USB device stack в RTOS-контексте) не
|
||||||
|
заработал стабильно за несколько заходов на этом пути. UART — самый
|
||||||
|
проверенный канал в репозитории (все `tests/target/*` HIL-образы), доступен
|
||||||
|
сразу через уже подключённый для SWD кабель, без enumeration/wait. Если
|
||||||
|
понадобится CDC под FreeRTOS позже (Фаза 7: provisioning-канал) — отдельная
|
||||||
|
сфокусированная задача. `port_log_cdc` (переиспользуемый адаптер) оставлен
|
||||||
|
в дереве, из `app` сейчас не линкуется.
|
||||||
|
- `.vscode/launch.json`: у `🐛 Debug: tft_app` был `preLaunchTask` на
|
||||||
|
`firmware_test` (copy-paste) — исправлено на `build:app-debug`.
|
||||||
|
|
||||||
|
**Осознанное отклонение от исходного критерия выхода:** `firmware/bootloader/
|
||||||
|
test_stub` НЕ удалён (план ниже это предполагал) — оставлен как независимая
|
||||||
|
A/B-регрессия загрузчика, раз `app` пока не покрывает те же сценарии
|
||||||
|
(hang-классы, revert без self-confirm и т.д.). Переоценить, когда/если `app`
|
||||||
|
сможет их replicate.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
**Цель.** `firmware/tft_app` собирается как ARM-таргет, грузится через bootloader, мигает
|
**Цель.** `firmware/tft_app` собирается как ARM-таргет, грузится через bootloader, мигает
|
||||||
heartbeat; host-тест-харнесс на месте. Пустой, но живой каркас всех слоёв.
|
heartbeat; host-тест-харнесс на месте. Пустой, но живой каркас всех слоёв.
|
||||||
|
|
@ -45,7 +100,7 @@ heartbeat; host-тест-харнесс на месте. Пустой, но жи
|
||||||
**Тесты.** `just build::build-app-big-debug` собирает ELF; host smoke-тест зелёный.
|
**Тесты.** `just build::build-app-big-debug` собирает ELF; host smoke-тест зелёный.
|
||||||
|
|
||||||
**Критерий выхода.** ELF грузится bootloader'ом из слота, heartbeat мигает (проверка — пользователь
|
**Критерий выхода.** ELF грузится bootloader'ом из слота, heartbeat мигает (проверка — пользователь
|
||||||
на железе). Удалён `test_stub` из корневого CMake.
|
на железе). ✅ Выполнено; `test_stub` сохранён (см. отклонение выше).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
65
firmware/tft_app/README.md
Normal file
65
firmware/tft_app/README.md
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
# tft_app
|
||||||
|
|
||||||
|
Прошивка лифтового индикатора (MIMXRT1052, TFT 4/7/8/10). Собирается как
|
||||||
|
MCUboot-слот-образ (Direct-XIP), которым управляет [bootloader](../bootloader/).
|
||||||
|
|
||||||
|
- **Архитектура** — [ARCH.md](ARCH.md).
|
||||||
|
- **План разработки (статус фаз)** — [PLAN.md](PLAN.md).
|
||||||
|
|
||||||
|
> `OLD_PROJECT*` и `special/` — референсные дампы старых серийных проектов
|
||||||
|
> (в `.gitignore`), источник бизнес-логики и доменного дизайна. Не собираются.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Статус: Фаза 0 (каркас)
|
||||||
|
|
||||||
|
Минимальный живой каркас: FreeRTOS + heartbeat + кормление унаследованного от
|
||||||
|
загрузчика WDOG + само-подтверждение слота MCUboot. Доменные/презентационные
|
||||||
|
слои — со следующих фаз.
|
||||||
|
|
||||||
|
## Сборка и подпись
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# devcontainer:
|
||||||
|
just build::build-app-debug # app.elf/.bin (Slot A, XIP из слота)
|
||||||
|
just build::sign-app-debug # + imgtool EC256 → build/Debug/signed/app_slot_a.bin
|
||||||
|
just build::test-host # host-тесты (вкл. test_tft_app_smoke) зелёные
|
||||||
|
```
|
||||||
|
|
||||||
|
Подпись — imgtool ECDSA P-256 (как stub загрузчика), **без** `--confirm`: образ
|
||||||
|
подтверждает свой слот в рантайме (`boot_set_next`). HAB для app не нужен — его
|
||||||
|
грузит не BootROM, а загрузчик после проверки imgtool-подписи.
|
||||||
|
|
||||||
|
## Прошивка (хост)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# итеративно — SWD подписанного слот-образа в Slot A (0x60040000), power cycle после:
|
||||||
|
just host::flash-swd-app-slot-debug
|
||||||
|
```
|
||||||
|
|
||||||
|
## Диагностика (Фаза 0)
|
||||||
|
|
||||||
|
USB CDC ACM (VCOM), 115200. Логирует boot-последовательность и **периодически
|
||||||
|
(раз в 2 с) состояние трейлера слота** (magic/copy_done/image_ok) — видно
|
||||||
|
независимо от момента подключения терминала:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
screen /dev/cu.usbmodemXXXX 115200 # macOS; порт свой на каждое подключение
|
||||||
|
```
|
||||||
|
|
||||||
|
Транспорт — `port_log_cdc` (переиспользуемый адаптер `utils/log` над
|
||||||
|
`bsp_usb_cdc`, симметричный `port_log_uart`), best-effort (строки без хоста
|
||||||
|
молча теряются).
|
||||||
|
|
||||||
|
## Раскладка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
src/
|
||||||
|
├── app/ точка входа, FreeRTOS-задачи, wiring, FreeRTOSConfig.h
|
||||||
|
├── domain/ чистый C, host-тесты: elevator_model, sul (декодеры), controller
|
||||||
|
├── services/ адаптеры к железу: gfx, audio_engine, assets, settings, fs
|
||||||
|
├── ui/ layout-движок, темы, fallback-рендер
|
||||||
|
└── menu/ экран настроек, навигация двумя кнопками
|
||||||
|
```
|
||||||
|
|
||||||
|
Слот-линкер — [cmake/linker/MIMXRT1052xxxxx_app_slot.ld](../../cmake/linker/MIMXRT1052xxxxx_app_slot.ld).
|
||||||
122
firmware/tft_app/src/app/FreeRTOSConfig.h
Normal file
122
firmware/tft_app/src/app/FreeRTOSConfig.h
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
* FreeRTOSConfig.h — конфигурация ядра для tft_app (MIMXRT1052, Cortex-M7F).
|
||||||
|
*
|
||||||
|
* Порт: GCC/ARM_CM4F (используется и для cm7f, см.
|
||||||
|
* sdk/rtos/freertos/freertos-kernel/CMakeLists.txt). Схема кучи — heap_4.
|
||||||
|
*
|
||||||
|
* Фаза 0: configTOTAL_HEAP_SIZE держим скромным во внутренней RAM (heap_4
|
||||||
|
* размещает свой массив в .bss → DTCM). На фазе дисплея куча (и фреймбуферы)
|
||||||
|
* переедут в SDRAM — тогда пересмотреть размер и размещение.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FREERTOS_CONFIG_H
|
||||||
|
#define FREERTOS_CONFIG_H
|
||||||
|
|
||||||
|
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
|
||||||
|
#include <stdint.h>
|
||||||
|
extern uint32_t SystemCoreClock; /* задаётся менеджером тактирования */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* ── Планировщик ─────────────────────────────────────────────────────────── */
|
||||||
|
#define configUSE_PREEMPTION 1
|
||||||
|
#define configUSE_TICKLESS_IDLE 0
|
||||||
|
#define configCPU_CLOCK_HZ (SystemCoreClock)
|
||||||
|
/* Без cast — bsp/tick.c (BSP_TICK_FREERTOS_MODE) сравнивает это значение в
|
||||||
|
* `#if configTICK_RATE_HZ != 1000`; препроцессор не умеет разбирать cast
|
||||||
|
* внутри #if ("missing binary operator"). Для C-кода бит-в-бит то же самое —
|
||||||
|
* TickType_t получается неявным преобразованием на месте использования. */
|
||||||
|
#define configTICK_RATE_HZ 1000
|
||||||
|
#define configMAX_PRIORITIES 8
|
||||||
|
#define configMINIMAL_STACK_SIZE ((unsigned short)128)
|
||||||
|
#define configMAX_TASK_NAME_LEN 20
|
||||||
|
#define configUSE_16_BIT_TICKS 0
|
||||||
|
#define configIDLE_SHOULD_YIELD 1
|
||||||
|
#define configUSE_TASK_NOTIFICATIONS 1
|
||||||
|
#define configUSE_MUTEXES 1
|
||||||
|
#define configUSE_RECURSIVE_MUTEXES 1
|
||||||
|
#define configUSE_COUNTING_SEMAPHORES 1
|
||||||
|
#define configQUEUE_REGISTRY_SIZE 8
|
||||||
|
#define configUSE_QUEUE_SETS 0
|
||||||
|
#define configUSE_TIME_SLICING 1
|
||||||
|
#define configUSE_NEWLIB_REENTRANT 0
|
||||||
|
#define configENABLE_BACKWARD_COMPATIBILITY 0
|
||||||
|
#define configSTACK_DEPTH_TYPE uint32_t
|
||||||
|
|
||||||
|
/* ── Память ──────────────────────────────────────────────────────────────── */
|
||||||
|
#define configFRTOS_MEMORY_SCHEME 4 /* heap_4.c */
|
||||||
|
#define configSUPPORT_STATIC_ALLOCATION 0
|
||||||
|
#define configSUPPORT_DYNAMIC_ALLOCATION 1
|
||||||
|
#define configTOTAL_HEAP_SIZE ((size_t)(0x8000)) /* 32 КБ (Фаза 0, DTCM) */
|
||||||
|
#define configAPPLICATION_ALLOCATED_HEAP 0
|
||||||
|
|
||||||
|
/* ── Hooks (строгая диагностика на этапе разработки) ─────────────────────── */
|
||||||
|
#define configUSE_IDLE_HOOK 0
|
||||||
|
#define configUSE_TICK_HOOK 0
|
||||||
|
#define configCHECK_FOR_STACK_OVERFLOW 2 /* → vApplicationStackOverflowHook */
|
||||||
|
#define configUSE_MALLOC_FAILED_HOOK 1 /* → vApplicationMallocFailedHook */
|
||||||
|
#define configUSE_DAEMON_TASK_STARTUP_HOOK 0
|
||||||
|
|
||||||
|
/* ── Статистика/трассировка ──────────────────────────────────────────────── */
|
||||||
|
#define configGENERATE_RUN_TIME_STATS 0
|
||||||
|
#define configUSE_TRACE_FACILITY 1
|
||||||
|
#define configUSE_STATS_FORMATTING_FUNCTIONS 0
|
||||||
|
|
||||||
|
/* ── Со-рутины (не используем) ───────────────────────────────────────────── */
|
||||||
|
#define configUSE_CO_ROUTINES 0
|
||||||
|
#define configMAX_CO_ROUTINE_PRIORITIES 2
|
||||||
|
|
||||||
|
/* ── Программные таймеры ─────────────────────────────────────────────────── */
|
||||||
|
#define configUSE_TIMERS 1
|
||||||
|
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1)
|
||||||
|
#define configTIMER_QUEUE_LENGTH 10
|
||||||
|
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 2)
|
||||||
|
|
||||||
|
/* ── Ловим ошибки на разработке ──────────────────────────────────────────── */
|
||||||
|
#define configASSERT(x) \
|
||||||
|
if ((x) == 0) \
|
||||||
|
{ \
|
||||||
|
taskDISABLE_INTERRUPTS(); \
|
||||||
|
for (;;) \
|
||||||
|
{ \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Опциональные API ────────────────────────────────────────────────────── */
|
||||||
|
#define INCLUDE_vTaskPrioritySet 1
|
||||||
|
#define INCLUDE_uxTaskPriorityGet 1
|
||||||
|
#define INCLUDE_vTaskDelete 1
|
||||||
|
#define INCLUDE_vTaskSuspend 1
|
||||||
|
#define INCLUDE_xResumeFromISR 1
|
||||||
|
#define INCLUDE_vTaskDelayUntil 1
|
||||||
|
#define INCLUDE_vTaskDelay 1
|
||||||
|
#define INCLUDE_xTaskGetSchedulerState 1
|
||||||
|
#define INCLUDE_xTaskGetCurrentTaskHandle 1
|
||||||
|
#define INCLUDE_uxTaskGetStackHighWaterMark 1
|
||||||
|
#define INCLUDE_xTaskGetIdleTaskHandle 0
|
||||||
|
#define INCLUDE_eTaskGetState 0
|
||||||
|
#define INCLUDE_xTimerPendFunctionCall 1
|
||||||
|
#define INCLUDE_xTaskAbortDelay 0
|
||||||
|
#define INCLUDE_xTaskGetHandle 0
|
||||||
|
#define INCLUDE_xTaskResumeFromISR 1
|
||||||
|
|
||||||
|
/* ── Прерывания Cortex-M (RT1052 NVIC = 4 бита приоритета) ───────────────── */
|
||||||
|
#ifdef __NVIC_PRIO_BITS
|
||||||
|
#define configPRIO_BITS __NVIC_PRIO_BITS
|
||||||
|
#else
|
||||||
|
#define configPRIO_BITS 4
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY ((1U << configPRIO_BITS) - 1)
|
||||||
|
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 2
|
||||||
|
|
||||||
|
#define configKERNEL_INTERRUPT_PRIORITY \
|
||||||
|
(configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
|
||||||
|
#define configMAX_SYSCALL_INTERRUPT_PRIORITY \
|
||||||
|
(configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
|
||||||
|
|
||||||
|
/* ── Маппинг обработчиков порта на CMSIS-имена (vector table) ────────────── */
|
||||||
|
#define vPortSVCHandler SVC_Handler
|
||||||
|
#define xPortPendSVHandler PendSV_Handler
|
||||||
|
#define xPortSysTickHandler SysTick_Handler
|
||||||
|
|
||||||
|
#endif /* FREERTOS_CONFIG_H */
|
||||||
15
firmware/tft_app/src/app/boot_shims.c
Normal file
15
firmware/tft_app/src/app/boot_shims.c
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* boot_shims.c — заглушки символов, которые дёргает общий с bootloader
|
||||||
|
* flash_map_backend.c, но которые не имеют смысла в контексте app.
|
||||||
|
*
|
||||||
|
* flash_map_backend.c зовёт led_status_tick_install() из цикла поблочного
|
||||||
|
* erase, чтобы двигать LED-анимацию прогресса в bootloader. В app окно
|
||||||
|
* установки не открыто (LED-индикацией управляет прикладная логика), поэтому
|
||||||
|
* здесь это no-op — ровно как у test_stub, но без утаскивания led_status.c
|
||||||
|
* (и его зависимости от bare-metal bsp_tick, конфликтующего с FreeRTOS-SysTick).
|
||||||
|
*/
|
||||||
|
|
||||||
|
void led_status_tick_install(void)
|
||||||
|
{
|
||||||
|
/* no-op: у app нет окна LED-установки */
|
||||||
|
}
|
||||||
206
firmware/tft_app/src/app/main.c
Normal file
206
firmware/tft_app/src/app/main.c
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
/**
|
||||||
|
* @file main.c
|
||||||
|
* @brief tft_app — точка входа. Фаза 0 (каркас): FreeRTOS + heartbeat + WDOG +
|
||||||
|
* рантайм self-confirm слота MCUboot + UART диагностика.
|
||||||
|
*
|
||||||
|
* Каркас walking-skeleton: образ линкуется как валидный MCUboot-слот
|
||||||
|
* (Direct-XIP), bootloader в него прыгает, FreeRTOS стартует, LED мигает,
|
||||||
|
* WDOG кормится, образ ПОДТВЕРЖДАЕТ СЕБЯ в рантайме (нет revert на повторной
|
||||||
|
* загрузке — anti-brick сохранён). Бизнес-логика — со следующих фаз (PLAN.md).
|
||||||
|
*
|
||||||
|
* Контракт со стороны bootloader (см. firmware/bootloader/test_stub/main.c):
|
||||||
|
* - WDOG взведён загрузчиком, WDE write-once — образ ОБЯЗАН кормить его.
|
||||||
|
* - Образ подписан imgtool БЕЗ --confirm → обязан подтвердить себя рантаймом
|
||||||
|
* (boot_set_next на своём слоте), иначе MCUboot откатит его.
|
||||||
|
*
|
||||||
|
* Записью QSPI из app под Direct-XIP можно пользоваться только потому, что
|
||||||
|
* ramfunc-код bsp_qspi реально копируется в ITCM — для этого потребитель
|
||||||
|
* ОБЯЗАН задать __STARTUP_INITIALIZE_RAMFUNCTION (см. CMakeLists и
|
||||||
|
* bsp/qspi_flash/README.md). Без него запись виснет в qspi_write_fifo.
|
||||||
|
*
|
||||||
|
* ── Диагностика: UART (LPUART1 / MCU-Link VCOM), НЕ USB CDC ──────────────
|
||||||
|
* USB CDC (target-side EHCI/PHY) под FreeRTOS не заработал стабильно —
|
||||||
|
* несколько заходов, последний раз плата падала в recovery загрузчика сразу
|
||||||
|
* после добавления wait-for-host цикла (вероятная причина — не сама USB, а
|
||||||
|
* инкрементально узкий стек boot_task, но целый класс неопределённости
|
||||||
|
* "первый в репозитории FreeRTOS-потребитель USB device stack" остаётся).
|
||||||
|
* LPUART1 — самый проверенный канал в репозитории (используется во ВСЕХ
|
||||||
|
* tests/target/* HIL-образах), идёт через уже подключённый для SWD кабель
|
||||||
|
* MCU-Link, не требует enumeration/wait, пины уже смуксены в BOARD_InitPins().
|
||||||
|
* Если понадобится USB CDC под FreeRTOS (например, Фаза 7 — provisioning) —
|
||||||
|
* отдельная сфокусированная задача, не диагностика Фазы 0.
|
||||||
|
*
|
||||||
|
* main() — минимальный bare-metal (board bring-up + запуск планировщика). Всё
|
||||||
|
* остальное — в boot_task, после старта планировщика (унификация с QSPI/
|
||||||
|
* confirm-логикой; UART сам по себе не требует тика для init/write).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "FreeRTOS.h"
|
||||||
|
#include "board.h"
|
||||||
|
#include "bootutil/bootutil_public.h"
|
||||||
|
#include "bsp/boot_state.h"
|
||||||
|
#include "bsp/led.h"
|
||||||
|
#include "bsp/qspi_flash.h"
|
||||||
|
#include "bsp/uart_host.h"
|
||||||
|
#include "bsp/wdog.h"
|
||||||
|
#include "flash_map.h"
|
||||||
|
#include "log/log.h"
|
||||||
|
#include "port/log_uart.h"
|
||||||
|
#include "task.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/* Фаза 0: диагностика self-confirm по UART (LPUART1 / MCU-Link VCOM,
|
||||||
|
* 115200). Сократить/убрать, когда подтверждено на железе многократно. */
|
||||||
|
#define LOG_TAG "app"
|
||||||
|
|
||||||
|
/* Собственный слот образа. Slot A = 0 (primary), Slot Б = 1 (secondary). */
|
||||||
|
#ifndef APP_OWN_SLOT_ID
|
||||||
|
#define APP_OWN_SLOT_ID 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define HEARTBEAT_PERIOD_MS 500U
|
||||||
|
#define WDOG_FEED_PERIOD_MS 100U /* кормим чаще периода мигания — таймаут WDOG >= 1 c */
|
||||||
|
#define STATUS_LOG_PERIOD_MS 2000U /* периодический re-log трейлера — виден независимо
|
||||||
|
* от момента подключения терминала (UART без
|
||||||
|
* handshake — писать можно сразу, слушают или нет) */
|
||||||
|
|
||||||
|
/* ── Диагностика трейлера слота (read-only, безопасно звать многократно) ── */
|
||||||
|
|
||||||
|
static void log_slot_status(const char *p_when)
|
||||||
|
{
|
||||||
|
const struct flash_area *p_fap;
|
||||||
|
const int RC_OPEN = flash_area_open((uint8_t) APP_OWN_SLOT_ID, &p_fap);
|
||||||
|
if (RC_OPEN != 0)
|
||||||
|
{
|
||||||
|
LOG_E(LOG_TAG, "%s: flash_area_open(slot%d) rc=%d", p_when, APP_OWN_SLOT_ID, RC_OPEN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct boot_swap_state st = {0};
|
||||||
|
const int RC_RD = boot_read_swap_state(p_fap, &st);
|
||||||
|
LOG_I(LOG_TAG, "%s: slot%d magic=%d copy_done=%d image_ok=%d (rd=%d)", p_when, APP_OWN_SLOT_ID,
|
||||||
|
st.magic, st.copy_done, st.image_ok, RC_RD);
|
||||||
|
|
||||||
|
flash_area_close(p_fap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Подтвердить СОБСТВЕННЫЙ слот (APP_OWN_SLOT_ID).
|
||||||
|
*
|
||||||
|
* boot_set_next(fap, active=true, confirm=true), НЕ boot_set_confirmed(): та
|
||||||
|
* жёстко пишет в FLASH_AREA_IMAGE_PRIMARY (Slot A) независимо от исполняемого
|
||||||
|
* слота — для Direct-XIP с двумя слотами это подтвердило бы не тот при
|
||||||
|
* исполнении из Slot Б.
|
||||||
|
*/
|
||||||
|
static void confirm_self(void)
|
||||||
|
{
|
||||||
|
log_slot_status("before-confirm"); /* ожидаем magic=1(GOOD) image_ok=3(UNSET) */
|
||||||
|
|
||||||
|
const struct flash_area *p_fap;
|
||||||
|
const int RC_OPEN = flash_area_open((uint8_t) APP_OWN_SLOT_ID, &p_fap);
|
||||||
|
if (RC_OPEN != 0)
|
||||||
|
{
|
||||||
|
LOG_E(LOG_TAG, "confirm: flash_area_open(slot%d) rc=%d", APP_OWN_SLOT_ID, RC_OPEN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int RC_SET = boot_set_next(p_fap, true, true);
|
||||||
|
LOG_I(LOG_TAG, "confirm: boot_set_next rc=%d", RC_SET);
|
||||||
|
|
||||||
|
flash_area_close(p_fap);
|
||||||
|
|
||||||
|
log_slot_status("after-confirm"); /* ожидаем image_ok=1(SET) */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void boot_task(void *p_arg)
|
||||||
|
{
|
||||||
|
(void) p_arg;
|
||||||
|
|
||||||
|
/* LPUART1/MCU-Link VCOM — доступен сразу, без enumeration/wait (в отличие
|
||||||
|
* от target-side USB CDC). log_mutex не нужен — единственная задача. */
|
||||||
|
(void) bsp_uart_host_init(115200U);
|
||||||
|
log_uart_init();
|
||||||
|
|
||||||
|
/* flash_map_backend требует bsp_qspi_init() ДО любой flash_area_*. */
|
||||||
|
const bool QSPI_OK = (bsp_qspi_init() == BSP_OK);
|
||||||
|
LOG_I(LOG_TAG, "tft_app phase0 boot: qspi=%s", QSPI_OK ? "OK" : "FAIL");
|
||||||
|
|
||||||
|
/* «Дошёл до устойчивого состояния» — сбрасывает счётчик попыток загрузки
|
||||||
|
* (recovery загрузчика). SRC GPR, без flash. */
|
||||||
|
bsp_boot_health_mark();
|
||||||
|
|
||||||
|
if (QSPI_OK)
|
||||||
|
{
|
||||||
|
confirm_self();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG_E(LOG_TAG, "qspi_init FAILED — self-confirm skipped, slot will revert");
|
||||||
|
}
|
||||||
|
|
||||||
|
const TickType_t FEED_PERIOD = pdMS_TO_TICKS(WDOG_FEED_PERIOD_MS);
|
||||||
|
uint32_t elapsed_ms = 0U;
|
||||||
|
uint32_t since_status_ms = 0U;
|
||||||
|
TickType_t last_wake = xTaskGetTickCount();
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
bsp_wdog_refresh();
|
||||||
|
|
||||||
|
elapsed_ms += WDOG_FEED_PERIOD_MS;
|
||||||
|
if (elapsed_ms >= HEARTBEAT_PERIOD_MS)
|
||||||
|
{
|
||||||
|
elapsed_ms = 0U;
|
||||||
|
bsp_led_toggle(LED_APP);
|
||||||
|
}
|
||||||
|
|
||||||
|
since_status_ms += WDOG_FEED_PERIOD_MS;
|
||||||
|
if (since_status_ms >= STATUS_LOG_PERIOD_MS)
|
||||||
|
{
|
||||||
|
since_status_ms = 0U;
|
||||||
|
log_slot_status("periodic");
|
||||||
|
}
|
||||||
|
|
||||||
|
vTaskDelayUntil(&last_wake, FEED_PERIOD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
board_hw_init(); /* BOARD_ConfigMPU + BOARD_InitPins + BOARD_BootClockRUN */
|
||||||
|
bsp_led_init();
|
||||||
|
|
||||||
|
/* x4 (2 КБ) с запасом: логирование (vsnprintf) + чтение трейлера. */
|
||||||
|
(void) xTaskCreate(boot_task, "boot", configMINIMAL_STACK_SIZE * 4U, NULL,
|
||||||
|
tskIDLE_PRIORITY + 1U, NULL);
|
||||||
|
|
||||||
|
vTaskStartScheduler();
|
||||||
|
|
||||||
|
/* Сюда планировщик не возвращается. Если вернулся — не хватило кучи под
|
||||||
|
* idle/timer задачу. WDOG сбросит плату. */
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── FreeRTOS hooks (строгая диагностика) ────────────────────────────────── */
|
||||||
|
|
||||||
|
void vApplicationStackOverflowHook(TaskHandle_t task, char *name)
|
||||||
|
{
|
||||||
|
(void) task;
|
||||||
|
(void) name;
|
||||||
|
taskDISABLE_INTERRUPTS();
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
/* WDOG сбросит плату — детерминированный отказ вместо тихой порчи. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void vApplicationMallocFailedHook(void)
|
||||||
|
{
|
||||||
|
taskDISABLE_INTERRUPTS();
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
3
firmware/tft_app/src/domain/README.md
Normal file
3
firmware/tft_app/src/domain/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# domain
|
||||||
|
|
||||||
|
Слой tft_app. Наполняется по фазам — см. ../../PLAN.md и ../../ARCH.md.
|
||||||
3
firmware/tft_app/src/menu/README.md
Normal file
3
firmware/tft_app/src/menu/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# menu
|
||||||
|
|
||||||
|
Слой tft_app. Наполняется по фазам — см. ../../PLAN.md и ../../ARCH.md.
|
||||||
3
firmware/tft_app/src/services/README.md
Normal file
3
firmware/tft_app/src/services/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# services
|
||||||
|
|
||||||
|
Слой tft_app. Наполняется по фазам — см. ../../PLAN.md и ../../ARCH.md.
|
||||||
3
firmware/tft_app/src/ui/README.md
Normal file
3
firmware/tft_app/src/ui/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# ui
|
||||||
|
|
||||||
|
Слой tft_app. Наполняется по фазам — см. ../../PLAN.md и ../../ARCH.md.
|
||||||
|
|
@ -66,6 +66,39 @@ build-app-debug: _configure-debug
|
||||||
build-app-release: _configure-release
|
build-app-release: _configure-release
|
||||||
cmake --build --preset app-release
|
cmake --build --preset app-release
|
||||||
|
|
||||||
|
# ── подпись app (imgtool EC256, как stub; БЕЗ --confirm — self-confirm в рантайме) ──
|
||||||
|
# Загрузчик валидирует подпись слота (MCUBOOT_VALIDATE_PRIMARY_SLOT +
|
||||||
|
# MCUBOOT_SIGN_EC256), поэтому образ ОБЯЗАН быть подписан тестовым ключом
|
||||||
|
# root-ec-p256.pem. Прошивка результата — через SWD в слот A (0x60040000).
|
||||||
|
# HAB-контейнер (hab-app-*/SDP-путь) на Фазе 0 не используется, см. PLAN.md.
|
||||||
|
#
|
||||||
|
# БЕЗ --confirm: image_ok в трейлере НЕ выставлен на этапе подписи — образ
|
||||||
|
# подтверждает себя рантаймом (boot_set_next в confirm_self, src/app/main.c).
|
||||||
|
# Так сохраняется anti-brick revert: образ, который загрузился, но не смог
|
||||||
|
# подтвердиться (завис до confirm), MCUboot откатит на следующей загрузке.
|
||||||
|
|
||||||
|
[doc('Собрать и подписать tft_app для Slot A (Debug)')]
|
||||||
|
[group('build')]
|
||||||
|
sign-app-debug: build-app-debug
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p "{{ BUILD_DIR }}/Debug/signed"
|
||||||
|
{{ MCUBOOT_UV }} "{{ MCUBOOT_IMGTOOL }}" sign \
|
||||||
|
-k "{{ MCUBOOT_KEY }}" -H 0x200 -S 0x200000 -v 0.1.0 --align 1 --pad-header --pad \
|
||||||
|
"{{ BUILD_DIR }}/Debug/app.bin" "{{ BUILD_DIR }}/Debug/signed/app_slot_a.bin"
|
||||||
|
echo " ✅ build/Debug/signed/app_slot_a.bin (Slot A 0x60040000) — прошивка через just host::flash-swd-app-slot-debug"
|
||||||
|
|
||||||
|
[doc('Собрать и подписать tft_app для Slot A (Release)')]
|
||||||
|
[group('build')]
|
||||||
|
sign-app-release: build-app-release
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p "{{ BUILD_DIR }}/Release/signed"
|
||||||
|
{{ MCUBOOT_UV }} "{{ MCUBOOT_IMGTOOL }}" sign \
|
||||||
|
-k "{{ MCUBOOT_KEY }}" -H 0x200 -S 0x200000 -v 0.1.0 --align 1 --pad-header --pad \
|
||||||
|
"{{ BUILD_DIR }}/Release/app.bin" "{{ BUILD_DIR }}/Release/signed/app_slot_a.bin"
|
||||||
|
echo " ✅ build/Release/signed/app_slot_a.bin (Slot A 0x60040000)"
|
||||||
|
|
||||||
# ── все проекты сразу ─────────────────────────────────────────────────────────
|
# ── все проекты сразу ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
[doc('Собрать все проекты (Debug)')]
|
[doc('Собрать все проекты (Debug)')]
|
||||||
|
|
|
||||||
|
|
@ -302,6 +302,30 @@ flash-swd-app-release:
|
||||||
uv run --directory "{{ HIL_DIR }}" python "{{ _flash_swd }}" \
|
uv run --directory "{{ HIL_DIR }}" python "{{ _flash_swd }}" \
|
||||||
--firmware app --build-type Release
|
--firmware app --build-type Release
|
||||||
|
|
||||||
|
# ── слот-образ (подписанный imgtool, без HAB) — пишем сырой .bin по адресу ──
|
||||||
|
# слота A напрямую через pyOCD (не через flash_swd.py: тот собирает FCB+IVT+HAB
|
||||||
|
# под 0x60000000; слот — это данные, которые читает boot_go(), не boot-образ
|
||||||
|
# BootROM). Тот же приём, что для stub'ов загрузчика. Требует собранного и
|
||||||
|
# подписанного образа: just build::sign-app-<debug|release>.
|
||||||
|
|
||||||
|
[doc('Прошить подписанный tft_app в Slot A (0x60040000) через SWD (Debug). Power cycle после')]
|
||||||
|
[group('flash-swd')]
|
||||||
|
flash-swd-app-slot-debug:
|
||||||
|
uv run --directory "{{ HIL_DIR }}" pyocd flash \
|
||||||
|
--target {{ env('PYOCD_TARGET', 'mimxrt1050_quadspi') }} \
|
||||||
|
--frequency {{ env('PYOCD_FREQUENCY', '4000000') }} \
|
||||||
|
--base-address 0x60040000 --erase sector \
|
||||||
|
"{{ BUILD_DIR }}/Debug/signed/app_slot_a.bin"
|
||||||
|
|
||||||
|
[doc('Прошить подписанный tft_app в Slot A (0x60040000) через SWD (Release). Power cycle после')]
|
||||||
|
[group('flash-swd')]
|
||||||
|
flash-swd-app-slot-release:
|
||||||
|
uv run --directory "{{ HIL_DIR }}" pyocd flash \
|
||||||
|
--target {{ env('PYOCD_TARGET', 'mimxrt1050_quadspi') }} \
|
||||||
|
--frequency {{ env('PYOCD_FREQUENCY', '4000000') }} \
|
||||||
|
--base-address 0x60040000 --erase sector \
|
||||||
|
"{{ BUILD_DIR }}/Release/signed/app_slot_a.bin"
|
||||||
|
|
||||||
[confirm("Стереть всю Flash (W25Q128)? Все прошивки будут удалены.")]
|
[confirm("Стереть всю Flash (W25Q128)? Все прошивки будут удалены.")]
|
||||||
[doc('Стереть всю Flash (W25Q128) через SWD (chip erase). Нужен power cycle после')]
|
[doc('Стереть всю Flash (W25Q128) через SWD (chip erase). Нужен power cycle после')]
|
||||||
[group('flash-swd')]
|
[group('flash-swd')]
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,25 @@ target_link_libraries(
|
||||||
# Явный include на случай если utils обрабатывается после port в корневом CMake.
|
# Явный include на случай если utils обрабатывается после port в корневом CMake.
|
||||||
# Дублирует транзитивный путь из utils — безвредно.
|
# Дублирует транзитивный путь из utils — безвредно.
|
||||||
target_include_directories(port_log_uart PUBLIC ${CMAKE_SOURCE_DIR}/utils)
|
target_include_directories(port_log_uart PUBLIC ${CMAKE_SOURCE_DIR}/utils)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# port_log_cdc — USB CDC ACM адаптер для utils_log.
|
||||||
|
#
|
||||||
|
# Связывает utils_log с bsp_usb_cdc: - регистрирует bsp_usb_cdc_write() как
|
||||||
|
# write callback (best-effort — см. port/log_cdc.h) - предоставляет
|
||||||
|
# strong-реализацию log_get_timestamp_ms() → bsp_tick_get_ms()
|
||||||
|
#
|
||||||
|
# ВАЖНО: bsp_usb_cdc_init() вызывает bsp_delay() внутри себя — под FreeRTOS
|
||||||
|
# звать ТОЛЬКО после vTaskStartScheduler(), из задачи (см. port/log_cdc.h).
|
||||||
|
#
|
||||||
|
# Не компилируется для host-тестов (bsp_usb_cdc там недоступен).
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
add_library(port_log_cdc STATIC src/log_cdc.c)
|
||||||
|
|
||||||
|
target_include_directories(port_log_cdc PUBLIC include/)
|
||||||
|
|
||||||
|
target_link_libraries(
|
||||||
|
port_log_cdc PUBLIC utils # несёт log.h и LOG_LEVEL транзитивно
|
||||||
|
bsp_usb_cdc bsp_tick)
|
||||||
|
target_include_directories(port_log_cdc PUBLIC ${CMAKE_SOURCE_DIR}/utils)
|
||||||
|
|
|
||||||
46
port/log/include/port/log_cdc.h
Normal file
46
port/log/include/port/log_cdc.h
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
/**
|
||||||
|
* @file port/log_cdc.h
|
||||||
|
* @brief USB CDC ACM адаптер логгера: регистрирует bsp_usb_cdc как транспорт.
|
||||||
|
*
|
||||||
|
* @warning Инициализация — ТОЛЬКО после vTaskStartScheduler() (из задачи
|
||||||
|
* FreeRTOS). bsp_usb_cdc_init() внутри себя вызывает bsp_delay(), которая в
|
||||||
|
* FreeRTOS-режиме bsp_tick реализована через xTaskGetTickCount() — до старта
|
||||||
|
* планировщика тик не идёт (счётчик стоит на 0), вызов до этого момента
|
||||||
|
* зависает навсегда.
|
||||||
|
*
|
||||||
|
* @code
|
||||||
|
* // внутри задачи, ПОСЛЕ vTaskStartScheduler():
|
||||||
|
* bsp_usb_cdc_init();
|
||||||
|
* log_cdc_init(); // регистрирует транспорт + timestamp
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* Транспорт best-effort: пока хост не открыл VCOM (или предыдущая передача
|
||||||
|
* ещё не завершена), записи молча отбрасываются — см. bsp_usb_cdc_write().
|
||||||
|
* Не подходит для сообщений, которые обязаны быть доставлены — для этого
|
||||||
|
* нужен ack-протокол поверх (как JSON-протокол bootloader'а).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef PORT_LOG_CDC_H
|
||||||
|
#define PORT_LOG_CDC_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Зарегистрировать USB CDC как транспорт логгера.
|
||||||
|
*
|
||||||
|
* Вызывает log_init() с cdc-callback и регистрирует
|
||||||
|
* log_get_timestamp_ms() → bsp_tick_get_ms().
|
||||||
|
*
|
||||||
|
* Предусловие: bsp_usb_cdc_init() уже вызван — после старта планировщика,
|
||||||
|
* см. предупреждение в докстроке файла.
|
||||||
|
*/
|
||||||
|
void log_cdc_init(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* PORT_LOG_CDC_H */
|
||||||
48
port/log/src/log_cdc.c
Normal file
48
port/log/src/log_cdc.c
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
/**
|
||||||
|
* @file log_cdc.c
|
||||||
|
* @brief USB CDC ACM адаптер логгера.
|
||||||
|
*
|
||||||
|
* Подключает utils_log к bsp_usb_cdc:
|
||||||
|
* - write callback → bsp_usb_cdc_write() (best-effort, см. port/log_cdc.h)
|
||||||
|
* - timestamp hook → bsp_tick_get_ms()
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "port/log_cdc.h"
|
||||||
|
|
||||||
|
#include "bsp/tick.h"
|
||||||
|
#include "bsp/usb_cdc.h"
|
||||||
|
#include "log/log.h"
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Strong-реализация weak-хука timestamp */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
uint32_t log_get_timestamp_ms(void)
|
||||||
|
{
|
||||||
|
return bsp_tick_get_ms();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Write callback */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static void cdc_write(const char *p_buf, size_t len, void *p_ctx)
|
||||||
|
{
|
||||||
|
(void) p_ctx;
|
||||||
|
if (!bsp_usb_cdc_is_ready())
|
||||||
|
{
|
||||||
|
return; /* хост не подключён — молча отбрасываем (best-effort) */
|
||||||
|
}
|
||||||
|
/* Не блокируемся на BSP_ERR_BUSY (предыдущая передача не завершена) —
|
||||||
|
* это диагностический канал, потерянная строка не критична. */
|
||||||
|
(void) bsp_usb_cdc_write((const uint8_t *) p_buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Публичный API */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
void log_cdc_init(void)
|
||||||
|
{
|
||||||
|
log_init(cdc_write, NULL);
|
||||||
|
}
|
||||||
|
|
@ -314,3 +314,12 @@ if(CMAKE_C_COMPILER_ID MATCHES "Clang")
|
||||||
${PROJECT_SOURCE_DIR}/firmware/bootloader/src/slot_version.c
|
${PROJECT_SOURCE_DIR}/firmware/bootloader/src/slot_version.c
|
||||||
PROPERTIES COMPILE_OPTIONS "-fno-sanitize=address,undefined")
|
PROPERTIES COMPILE_OPTIONS "-fno-sanitize=address,undefined")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# tft_app — smoke (Фаза 0: проверка харнесса; доменные тесты — Фаза 1)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
add_host_test(
|
||||||
|
NAME
|
||||||
|
test_tft_app_smoke
|
||||||
|
SOURCES
|
||||||
|
tft_app_smoke/test_tft_app_smoke.c)
|
||||||
|
|
|
||||||
8
tests/host/tft_app_smoke/README.md
Normal file
8
tests/host/tft_app_smoke/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# test_tft_app_smoke
|
||||||
|
|
||||||
|
Фаза 0: проверка работоспособности host-харнесса tft_app.
|
||||||
|
|
||||||
|
На Фазе 0 в `firmware/tft_app` ещё нет чистой доменной логики — тест лишь
|
||||||
|
подтверждает, что таргет собирается и запускается. На Фазе 1 заменяется
|
||||||
|
реальными доменными тестами (декодер НКУ-CAN → `sul_result_t`, `controller`
|
||||||
|
diff). См. [firmware/tft_app/PLAN.md](../../../firmware/tft_app/PLAN.md).
|
||||||
26
tests/host/tft_app_smoke/test_tft_app_smoke.c
Normal file
26
tests/host/tft_app_smoke/test_tft_app_smoke.c
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* test_tft_app_smoke.c — Фаза 0: проверка живости host-харнесса tft_app.
|
||||||
|
*
|
||||||
|
* На Фазе 0 в tft_app ещё нет чистой доменной логики. Этот тест лишь
|
||||||
|
* подтверждает, что таргет собирается clang'ом, линкуется с Unity и
|
||||||
|
* запускается под CTest в пресетах host-*. На Фазе 1 заменяется реальными
|
||||||
|
* доменными тестами (sul-декодер НКУ → sul_result_t, controller diff) —
|
||||||
|
* см. firmware/tft_app/PLAN.md.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "unity.h"
|
||||||
|
|
||||||
|
void setUp(void) {}
|
||||||
|
void tearDown(void) {}
|
||||||
|
|
||||||
|
static void test_harness_alive(void)
|
||||||
|
{
|
||||||
|
TEST_ASSERT_EQUAL_INT(2, 1 + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
UNITY_BEGIN();
|
||||||
|
RUN_TEST(test_harness_alive);
|
||||||
|
return UNITY_END();
|
||||||
|
}
|
||||||
BIN
tools/service_tui/assets/macos_icon.png
Normal file
BIN
tools/service_tui/assets/macos_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
Loading…
Reference in a new issue