From be36d56170c76c2881dd022ed9ea6a1bf90c4d54 Mon Sep 17 00:00:00 2001 From: Dmitry Akimov Date: Thu, 14 May 2026 00:04:36 +0300 Subject: [PATCH] # Update documentation --- README.md | 16 +- bsp/README.md | 14 +- bsp/display/README.md | 328 ++++++++++++++++++++++++++++ bsp/generated/README.md | 18 +- bsp/sdram/README.md | 228 +++++++++++++++++++ docs/DEV_ARCH.md | 55 +++-- docs/testing/hil/HIL_CREATE_TEST.md | 4 +- docs/testing/hil/HIL_FIXTURES.md | 12 +- docs/testing/hil/HIL_HOW_TO.md | 10 +- tools/hil/README.md | 6 +- tools/host/README.md | 2 +- 11 files changed, 647 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 99634e4..b2ab416 100644 --- a/README.md +++ b/README.md @@ -38,16 +38,26 @@ **Host-тесты** запускаются в devcontainer без железа. BSP-модули тестируются через fff-фейки и stub-хедеры. -**HIL-тесты** — каждый тест это пара: C-прошивка с UART CLI (`tests/target//`) и pytest-файл (`tools/hil/test_.py`). pyOCD загружает ELF в RAM через MCU-Link. Тесты с внешними сигналами управляются через M5StampPLC (реле → оптовходы таргета). +**HIL-тесты** — каждый тест это пара: C-прошивка с UART CLI (`tests/target//`) и pytest-файл (`tools/hil/NN_test_.py`). pyOCD загружает ELF в RAM через MCU-Link. Тесты с внешними сигналами управляются через M5StampPLC (реле → оптовходы таргета). + +Фактический набор HIL-тестов (`tools/hil/`): + +| Файл | Назначение | +| -------------------- | ----------------------------------------- | +| `01_test_uart.py` | UART CLI / MCU-Link VCOM | +| `02_test_opto.py` | Оптовходы EXT_IN1, EXT_IN2, RS_RX | +| `03_test_can.py` | CAN-интерфейс | +| `04_test_button.py` | Пользовательские кнопки | +| `05_test_usb_cdc.py` | USB CDC ACM | ```bash pytest → uart_cmd() → MCU-Link VCOM → RT1052 pytest → m5.opto_set() → M5StampPLC RLY → EXT_IN1/IN2/RS_RX → RT1052 ``` -- Как добавить host-тест — [tests/HOST_CREATE_TEST.md](tests/HOST_CREATE_TEST.md) +- Как добавить host-тест — [docs/testing/host/HOST_CREATE_TEST.md](docs/testing/host/HOST_CREATE_TEST.md) - Как добавить HIL-тест — [docs/testing/hil/HIL_CREATE_TEST.md](docs/testing/hil/HIL_CREATE_TEST.md) -- Как запустить HIL-тесты — [docs/testing/hil/HIL_HOWTO.md](docs/testing/hil/HIL_HOWTO.md) +- Как запустить HIL-тесты — [docs/testing/hil/HIL_HOW_TO.md](docs/testing/hil/HIL_HOW_TO.md) - HIL стенд и подключение — [docs/testing/hil/HIL_BENCH.md](docs/testing/hil/HIL_BENCH.md) --- diff --git a/bsp/README.md b/bsp/README.md index 323961d..7635711 100644 --- a/bsp/README.md +++ b/bsp/README.md @@ -47,7 +47,13 @@ bsp/ ├── uart_host/ # bsp_uart_host — LPUART1 (MCU-Link VCOM, J2) │ └── mocks/ # fff-заглушки для host-тестов ├── opto/ # bsp_opto — оптоизолированные входы PS2801-4 -└── usb_cdc/ # bsp_usb_cdc — USB CDC ACM +├── can/ # bsp_can — FlexCAN2 (трансивер SN65HVD230D) +├── button/ # bsp_button — тактовые кнопки SWT6x6 с debounce +├── display/ # bsp_display — TFT-дисплей +├── usb_cdc/ # bsp_usb_cdc — USB CDC ACM +├── sdram/ # bsp_sdram — внешний SDRAM через SEMC +├── qspi_flash/ # bsp_qspi_flash — QSPI Flash W25Q64/128/256/512 +└── sd/ # bsp_sd — SD host-контроллер (USDHC1) ``` --- @@ -87,7 +93,13 @@ target_link_libraries(test_hil_opto PRIVATE bsp_board bsp_boot_ram ...) | `bsp_tick` | `tick/` | [tick/README.md](tick/README.md) | | `bsp_uart_host` | `uart_host/` | [uart_host/README.md](uart_host/README.md) | | `bsp_opto` | `opto/` | [opto/README.md](opto/README.md) | +| `bsp_can` | `can/` | [can/README.md](can/README.md) | +| `bsp_button` | `button/` | [button/README.md](button/README.md) | +| `bsp_display` | `display/` | [display/README.md](display/README.md) | | `bsp_usb_cdc` | `usb_cdc/` | [usb_cdc/README.md](usb_cdc/README.md) | +| `bsp_sdram` | `sdram/` | [sdram/README.md](sdram/README.md) | +| `bsp_qspi_flash` | `qspi_flash/` | [qspi_flash/README.md](qspi_flash/README.md) | +| `bsp_sd` | `sd/` | [sd/README.md](sd/README.md) | --- diff --git a/bsp/display/README.md b/bsp/display/README.md index e69de29..969c752 100644 --- a/bsp/display/README.md +++ b/bsp/display/README.md @@ -0,0 +1,328 @@ +# bsp_display — ELCDIF RGB-дисплей (TFT4 / TFT7 / TFT8 / TFT10) + +> Расположение: `bsp/display/` +> Публичный заголовок: `bsp/display/include/bsp/display.h` +> Реализация: `bsp/display/src/display.c` + +Модуль инициализирует ELCDIF в RGB-режиме, настраивает пиксельный клок, +управляет GPIO подсветки и пинами ориентации/режима LR, UD, MODE, DITHB. +Предоставляет минимальное API для смены фреймбуфера, поворота и оповещения +о завершении кадра через callback. Bare-metal совместим (без FreeRTOS). + +--- + +## Аппаратный контекст + +| Сигнал / параметр | Аппаратное назначение | +| ----------------- | ---------------------------------------------------------------- | +| ELCDIF | NXP ELCDIF, RGB-режим, формат пикселя `kELCDIF_PixelFormatXRGB8888`, шина `kELCDIF_DataBus24Bit` | +| Подсветка | `GPIO1[20]` — active-high | +| LR (горизонт.) | `GPIO1[28]` (`Lcdlr_value`) | +| MODE | `GPIO1[29]` — HIGH = DE mode (обязательно для ELCDIF) | +| UD (вертикаль) | `GPIO1[30]` | +| DITHB | `GPIO1[31]` — HIGH = dithering disable (IC default) | +| Пиксельный клок | PLL2 (`mux=0`) для TFT7/TFT8; Video PLL (`mux=2`) для TFT4 | +| IRQ | `LCDIF_IRQHandler` в ITCM, приоритет `DISPLAY_IRQ_PRIORITY = 2` | + +`IOMUXC` конфигурируется в `BOARD_InitPins()` за пределами модуля — здесь +выполняется только `GPIO_PinWrite`. + +Делители пиксельного клока (исходный код, PLL2 = 528 МГц): + +| Дисплей | clk_mux | pre_div | div | Эффективная частота | +| ------- | ------------- | ------- | --- | ------------------- | +| TFT7 | PLL2 | 2 | 4 | `528/3/5 = 35.2 МГц`| +| TFT8 | PLL2 | 2 | 3 | `528/3/4 = 44.0 МГц`| +| TFT4 | Video PLL | — (TODO)| — | требует `CLOCK_InitVideoPll` | +| TFT10 | — | — | — | таблица не заполнена (`{ 0 }`) | + +Тайминги HSW/HFP/HBP/VSW/VFP/VBP заданы константами в `display.c` +(`DISPLAY_TFT7_*`, `DISPLAY_TFT8_*`, `DISPLAY_TFT4_*`). + +--- + +## Состав модуля + +``` +bsp/display/ +├── include/bsp/display.h # публичный заголовок +├── src/display.c # реализация API + LCDIF_IRQHandler +└── CMakeLists.txt # цель bsp_display +``` + +`LCDIF_IRQHandler` размещён в ITCM (`AT_QUICKACCESS_SECTION_CODE`) и только +вызывает зарегистрированный callback; состояние модуля он не модифицирует. + +--- + +## Публичные типы + +```c +typedef enum bsp_display_type_e { + BSP_DISPLAY_TFT4 = 0U, /* 480 × 272, нет ножек ориентации/MODE/DITHB */ + BSP_DISPLAY_TFT7, /* 1024 × 600, LR + UD + MODE + DITHB */ + BSP_DISPLAY_TFT8, /* 800 × 600, LR + UD + MODE + DITHB */ + BSP_DISPLAY_TFT10, /* зарезервировано, спецификации уточняются */ + BSP_DISPLAY_COUNT, +} bsp_display_type_t; + +typedef enum bsp_display_rotation_e { + BSP_DISPLAY_ROTATE_0 = 0U, /* LR=1 UD=0 */ + BSP_DISPLAY_ROTATE_90, /* LR=1 UD=1 */ + BSP_DISPLAY_ROTATE_180, /* LR=0 UD=1 */ + BSP_DISPLAY_ROTATE_270, /* LR=0 UD=0 */ +} bsp_display_rotation_t; + +typedef struct bsp_display_size_s { + uint16_t width; + uint16_t height; +} bsp_display_size_t; + +typedef void (*bsp_display_frame_cb_t)(void); /* ISR-safe */ +``` + +Размеры максимального дисплея (для статического выделения буферов): + +```c +#define BSP_DISPLAY_MAX_WIDTH 1024U +#define BSP_DISPLAY_MAX_HEIGHT 600U +``` + +--- + +## Публичный API + +```c +bsp_status_t bsp_display_init(bsp_display_type_t type, + uint32_t framebuffer_addr, + bsp_display_frame_cb_t p_on_frame_done); + +bsp_status_t bsp_display_deinit(void); + +bsp_status_t bsp_display_set_rotation(bsp_display_rotation_t rotation); + +void bsp_display_set_next_buffer(uint32_t framebuffer_addr); + +const bsp_display_size_t *bsp_display_get_size(void); + +bsp_display_type_t bsp_display_get_type(void); +``` + +### `bsp_display_init` + +Настраивает пиксельный клок (через `CLOCK_SetMux` / `CLOCK_SetDiv`), включает +`kCLOCK_LcdPixel`, поднимает подсветку, для TFT7/TFT8 инициализирует ножки +ориентации (`ROTATE_0`: LR=1, UD=0) и режима (MODE=1: DE-mode, DITHB=1), +конфигурирует ELCDIF через `ELCDIF_RgbModeInit` и запускает его через +`ELCDIF_RgbModeStart`. Включает прерывание `kELCDIF_CurFrameDoneInterruptEnable` +с приоритетом `DISPLAY_IRQ_PRIORITY = 2`. + +Требования к аргументам и поведение: + +- `framebuffer_addr` — физический адрес первого фреймбуфера. Согласно + заголовку, ожидается выравнивание по 64 байтам и размещение в NonCacheable + SDRAM. (Само значение модуль не проверяет — это контракт потребителя.) +- `p_on_frame_done` — ISR-safe callback; `NULL` означает «без callback». + Сохраняется до включения IRQ, чтобы избежать гонки. +- Повторный вызов без `bsp_display_deinit()` — идемпотентен, возвращает + `BSP_OK` без побочных эффектов. + +Коды возврата: + +| Код | Когда | +| ---------------------- | ----------------------------------------------------------- | +| `BSP_OK` | Дисплей инициализирован (или уже был инициализирован). | +| `BSP_ERR_PARAM` | `type >= BSP_DISPLAY_COUNT`. | +| `BSP_ERR_NOT_SUPPORTED`| `type` требует Video PLL (TFT4) — `init_pixelclock` возвращает ошибку до реализации `CLOCK_InitVideoPll`. | + +> Поведение для `BSP_DISPLAY_TFT10` целостно не описано в коде: запись в +> `K_HW_CFG[BSP_DISPLAY_TFT10]` сделана как `{ 0 }`. Использовать TFT10 как +> рабочий параметр сейчас не гарантируется — типу зарезервировано место в +> enum. + +### `bsp_display_deinit` + +Останавливает ELCDIF (`ELCDIF_RgbModeStop`), выключает IRQ, вызывает +`ELCDIF_Deinit`, отключает `kCLOCK_LcdPixel` и гасит подсветку. Безопасен +при вызове до `init` и повторно. Всегда возвращает `BSP_OK`. + +### `bsp_display_set_rotation` + +Переключает ориентацию через ножки LR/UD для дисплеев с +`has_orientation_pins = true` (TFT7/TFT8). Для TFT4 поддерживается только +`BSP_DISPLAY_ROTATE_0`; другие значения возвращают `BSP_ERR_NOT_SUPPORTED`. +До `bsp_display_init()` возвращает `BSP_ERR_INIT`. Неизвестная rotation — +`BSP_ERR_PARAM`. + +Соответствие rotation → LR/UD (из `display.c`): + +| Rotation | LR | UD | +| ---------------------- | -- | -- | +| `BSP_DISPLAY_ROTATE_0` | 1 | 0 | +| `BSP_DISPLAY_ROTATE_90`| 1 | 1 | +| `BSP_DISPLAY_ROTATE_180`| 0 | 1 | +| `BSP_DISPLAY_ROTATE_270`| 0 | 0 | + +### `bsp_display_set_next_buffer` + +Тонкая обёртка над `ELCDIF_SetNextBufferAddr`. Согласно заголовку, безопасна +из ISR и из задачи; переключение произойдёт аппаратно по окончании текущего +кадра. Возврата нет. + +### `bsp_display_get_size` / `bsp_display_get_type` + +Возвращают зафиксированные при `init` параметры активного дисплея. +`bsp_display_get_size()` возвращает `NULL` до `init`; `bsp_display_get_type()` +возвращает `BSP_DISPLAY_COUNT`, если дисплей не инициализирован. + +### Callback `bsp_display_frame_cb_t` + +Вызывается из `LCDIF_IRQHandler` при флаге `kELCDIF_CurFrameDone`. Должен +быть ISR-safe: запись в `volatile`, `xSemaphoreGiveFromISR()` и т.п.; любые +блокирующие операции запрещены (требование из заголовка). + +--- + +## Порядок использования + +```c +#include "bsp/display.h" + +/* Фреймбуфер — статический, в NonCacheable SDRAM, выровнен по 64 байтам. */ +static AT_NONCACHEABLE_SECTION_ALIGN( + uint32_t fb[BSP_DISPLAY_MAX_HEIGHT * BSP_DISPLAY_MAX_WIDTH], 64U); + +static volatile bool g_frame_done; +static void on_frame_done(void) { g_frame_done = true; } /* ISR-safe */ + +void app_init(void) +{ + /* board_hw_init() / CLOCK_*/ + + bsp_status_t s = bsp_display_init(BSP_DISPLAY_TFT8, + (uint32_t) fb, + on_frame_done); + if (s != BSP_OK) { /* обработать */ } + + (void) bsp_display_set_rotation(BSP_DISPLAY_ROTATE_0); + + /* Залить буфер и показать кадр */ + g_frame_done = false; + bsp_display_set_next_buffer((uint32_t) fb); + while (!g_frame_done) { /* ждать */ } +} +``` + +--- + +## Зависимости и CMake + +```cmake +# bsp/display/CMakeLists.txt +add_library(bsp_display STATIC src/display.c) + +target_include_directories(bsp_display + PUBLIC include/ + PRIVATE src/) + +target_link_libraries(bsp_display + PUBLIC bsp_status + PRIVATE bsp_board sdk_elcdif) +``` + +- `bsp_status` (PUBLIC) — `bsp_status_t` в публичном API. +- `bsp_board` (PRIVATE) — общие board-уровневые символы (`BOARD_*`, IOMUXC). +- `sdk_elcdif` (PRIVATE) — `fsl_elcdif.h`, тип `elcdif_rgb_mode_config_t`, + функции `ELCDIF_*`, флаги полярности. + +Дополнительно `display.c` подключает `fsl_clock.h` (`CLOCK_SetMux`, +`CLOCK_SetDiv`, `CLOCK_EnableClock`, `kCLOCK_LcdifPreMux/PreDiv/Div`, +`kCLOCK_LcdPixel`) и `fsl_gpio.h` (`GPIO_PinWrite`) — символы предоставляются +SDK через транзитивные зависимости. + +Цель не собирается при `BUILD_TESTS_HOST=ON` (host-сборка) — ранний +`return()` в `CMakeLists.txt`. + +Потребитель (`firmware/test/CMakeLists.txt`): + +```cmake +target_link_libraries(firmware_test PRIVATE + ... + bsp_display + ... +) + +target_compile_definitions(firmware_test PRIVATE + DISPLAY_TEST_TYPE=BSP_DISPLAY_TFT8 + ... +) +``` + +`DISPLAY_TEST_TYPE` — параметр **тест-модуля** (`test_display.c`), а не самого +`bsp_display`; см. ниже. + +--- + +## Связь с firmware_test (`test_display.c`) + +Тест-модуль `firmware/test/src/tests/test_display.c` использует это BSP так: + +- Тип дисплея определяется макросом `DISPLAY_TEST_TYPE` (по умолчанию + `BSP_DISPLAY_TFT8`), задаётся через `target_compile_definitions` в + `firmware/test/CMakeLists.txt`. +- В `init` тест-модуль вызывает + `bsp_display_init((bsp_display_type_t) DISPLAY_TEST_TYPE, (uint32_t) g_s_framebuf, display_frame_cb)`, + затем `bsp_display_set_rotation(BSP_DISPLAY_ROTATE_0)`. +- Фреймбуфер — `g_s_framebuf[BSP_DISPLAY_MAX_HEIGHT * BSP_DISPLAY_MAX_WIDTH]` + типа `uint32_t`, выровнен по 64 байтам через `AT_NONCACHEABLE_SECTION_ALIGN`. +- Frame sync — `volatile bool g_s_frame_done`, выставляется в callback + `display_frame_cb` (записывает `true`); сбрасывается перед каждым + `bsp_display_set_next_buffer((uint32_t) g_s_framebuf)`. +- Два этапа теста (фактическое поведение из кода): + - **Этап 1 — Цвет**: четыре шага `step_color(RED, GREEN, BLUE, WHITE)` + с XRGB8888 цветами (`0x00FF0000`, `0x0000FF00`, `0x000000FF`, `0x00FFFFFF`). + Каждый шаг — заливка → ожидание кадра → confirm оператора + с таймаутом `DISPLAY_CONFIRM_TIMEOUT_MS = 15000 мс`. + - **Этап 2 — Ротация**: выполняется только если + `bsp_display_get_type() != BSP_DISPLAY_TFT4`. Заливка «левая половина RED, + правая BLUE», затем confirm для `ROTATE_0`, затем `ROTATE_90` через + `bsp_display_set_rotation()`, после чего восстанавливается `ROTATE_0`. +- В `deinit` вызывается `bsp_display_deinit()`. + +Дескриптор тест-модуля: + +```c +const test_module_t K_TEST_DISPLAY = { + .id = "display", + .name = "TFT Display RGB888", + .critical = false, + .requires_hil = false, + ... +}; +``` + +--- + +## Ограничения и замечания + +- **TFT4 не поддерживается** до реализации Video PLL: `bsp_display_init` + возвращает `BSP_ERR_NOT_SUPPORTED`, в исходнике это явно отмечено как TODO + (`CLOCK_InitVideoPll`). Соответственно, и тест-модуль `display` пропускает + этап ротации для TFT4 и поддерживает только `ROTATE_0` через API. +- **TFT10** присутствует только как зарезервированное значение `enum` + (`K_HW_CFG[BSP_DISPLAY_TFT10] = { 0 }`). Реальные тайминги/делители не + заполнены; конкретное поведение `bsp_display_init(BSP_DISPLAY_TFT10, ...)` + не описано документацией модуля и не гарантируется. +- `bsp_display_init` идемпотентен: повторный вызов без `deinit` возвращает + `BSP_OK` и не перенастраивает аппаратуру. Чтобы переинициализировать с + другим типом или новым адресом фреймбуфера, нужно сначала вызвать + `bsp_display_deinit()`. +- Адрес фреймбуфера должен указывать на NonCacheable память (согласно + заголовку); модуль не делает cache maintenance над буфером. +- `bsp_display_set_next_buffer` возвращает `void` — отсутствие ошибки от + ELCDIF предполагается; верификация переключения буфера — задача + потребителя (например, через FRAME_DONE callback). +- IOMUXC ножек LR/UD/MODE/DITHB и пина подсветки конфигурируется в + `BOARD_InitPins()` за пределами модуля; неправильная конфигурация + IOMUXC проявится отсутствием реакции дисплея, а не возвратом ошибки из + API. diff --git a/bsp/generated/README.md b/bsp/generated/README.md index bfa2c3d..c387aa9 100644 --- a/bsp/generated/README.md +++ b/bsp/generated/README.md @@ -7,7 +7,7 @@ ## Что такое Config Tools -NXP Config Tools — набор утилит для визуальной настройки микроконтроллера. Включает несколько инструментов: Pins Tool, Clocks Tool, Peripherals Tool. Генерирует инициализационный код на основе конфигурации сохранённой в файле `TFT_BOARD.mex`. +NXP Config Tools — набор утилит для визуальной настройки микроконтроллера. Включает несколько инструментов: Pins Tool, Clocks Tool, Peripherals Tool. Генерирует инициализационный код на основе конфигурации сохранённой в файле `TFT_Board.mex`. --- @@ -27,15 +27,15 @@ NXP Config Tools — набор утилит для визуальной нас ```bash bsp/ -└── board/ - ├── TFT_BOARD.mex # Исходник конфигурации — главный файл Config Tools - ├── pin_mux.c # Сгенерировано из board.mex (Pins Tool) +└── generated/ + ├── TFT_Board.mex # Исходник конфигурации — главный файл Config Tools + ├── pin_mux.c # Сгенерировано из TFT_Board.mex (Pins Tool) ├── pin_mux.h - ├── clock_config.c # Сгенерировано из board.mex (Clocks Tool) + ├── clock_config.c # Сгенерировано из TFT_Board.mex (Clocks Tool) └── clock_config.h ``` -`board.mex` — источник истины. Все изменения вносятся только через него. +`TFT_Board.mex` — источник истины. Все изменения вносятся только через него. --- @@ -133,12 +133,12 @@ Config Tools открывается **только при изменении а **Порядок внесения изменений:** -1. Открыть `bsp/board/TFT_BOARD.mex` в Config Tools +1. Открыть `bsp/generated/TFT_Board.mex` в Config Tools 2. Внести изменения 3. Сгенерировать код (Update Code) -4. Закоммитить `TFT_BOARD.mex` и сгенерированные файлы **в одном коммите** +4. Закоммитить `TFT_Board.mex` и сгенерированные файлы **в одном коммите** -**Главное правило:** `pin_mux.c`, `pin_mux.h`, `clock_config.c`, `clock_config.h` — **не редактировать вручную**. Только через Config Tools. Ручная правка приведёт к рассинхронизации с `board.mex` и потере воспроизводимости конфигурации. +**Главное правило:** `pin_mux.c`, `pin_mux.h`, `clock_config.c`, `clock_config.h` — **не редактировать вручную**. Только через Config Tools. Ручная правка приведёт к рассинхронизации с `TFT_Board.mex` и потере воспроизводимости конфигурации. --- diff --git a/bsp/sdram/README.md b/bsp/sdram/README.md index e69de29..6ff436d 100644 --- a/bsp/sdram/README.md +++ b/bsp/sdram/README.md @@ -0,0 +1,228 @@ +# bsp_sdram — внешняя SDRAM MT48LC16M16A2 (32 МБ) + +> Расположение: `bsp/sdram/` +> Публичный заголовок: `bsp/sdram/include/bsp/sdram.h` +> Реализация: `bsp/sdram/src/sdram.c` + +Модуль обеспечивает минимальную верификацию доступности внешней SDRAM, +подключённой к SEMC. Подробное тестирование (паттерны, шина адреса/данных, +retention) выполняется не здесь, а в тест-модуле `firmware_test/test_sdram.c`, +который опирается на константы и API этого модуля. + +--- + +## Аппаратный контекст + +| Параметр | Значение | +| --------------------- | ------------------------------------------------ | +| Чип | MT48LC16M16A2 | +| Объём | 32 МБ | +| Ширина шины данных | 16 бит | +| Интерфейс MCU | SEMC, регион BR0 | +| Базовый адрес | `0x80000000` (`BSP_SDRAM_BASE_ADDR`) | +| Конец региона | `0x81FFFFFF` (`+ BSP_SDRAM_SIZE_BYTES = 32 МБ`) | + +Карта тестового региона (из `sdram.h`): + +| Адрес | Назначение | +| ------------ | ---------------------------------------------------------- | +| `0x80000000` | Начало SDRAM (SEMC BR0) | +| `0x80200000` | `BSP_SDRAM_TEST_BASE_ADDR` — база тестового региона | +| `0x81E00000` | Начало non-cacheable региона (USB DMA, 2 MB) | +| `0x81FFFFFF` | Конец SDRAM | + +Тестовая база смещена на 2 МБ от начала SDRAM, что согласно комментариям в +заголовке гарантированно выше `.data`/`.bss` прошивки и ниже non-cacheable +региона. + +--- + +## Архитектурное ограничение: SEMC инициализируется DCD до `main()` + +Модуль **не настраивает** контроллер SEMC и не модифицирует его регистры. +Согласно комментарию в `bsp/sdram/CMakeLists.txt` и `sdram.h`, инициализация +SEMC выполнена через **DCD до вызова `main()`**. Регион SDRAM также описан в +MPU как Normal Write-Back cacheable — соответствующая настройка делается за +пределами этого модуля (исходники модуля её не выполняют). + +Следствие для верификации: чтобы проверить именно физическую SDRAM, а не +кэш, при readback в реализации используется явный +`SCB_CleanDCache_by_Addr` + `SCB_InvalidateDCache_by_Addr` + `__DSB()`. + +--- + +## Состав модуля + +``` +bsp/sdram/ +├── include/bsp/sdram.h # публичный заголовок +├── src/sdram.c # реализация bsp_sdram_init() +└── CMakeLists.txt # цель bsp_sdram +``` + +В `sdram.c` определены только статические вспомогательные функции +(`wait_semc_idle`, `flush_cache_at_test_base`, `verify_word`) и одна публичная +функция `bsp_sdram_init()`. Других публичных операций (например, расширенных +тестов памяти) модуль не предоставляет. + +--- + +## Публичные константы + +```c +#define BSP_SDRAM_BASE_ADDR 0x80000000UL /* SEMC BR0 */ +#define BSP_SDRAM_SIZE_BYTES 0x02000000UL /* 32 MB */ +#define BSP_SDRAM_TEST_BASE_ADDR 0x80200000UL /* +2 MB от базы */ +#define BSP_SDRAM_TEST_FAST_SIZE 0x00010000UL /* 64 KB */ +#define BSP_SDRAM_TEST_FULL_SIZE 0x00100000UL /* 1 MB */ +#define BSP_SDRAM_TEST_EXTENDED_SIZE 0x01B00000UL /* 27 MB */ +``` + +Размеры тестов — это **константы для потребителей**; сам `bsp_sdram` не +запускает по ним внутренние проходы. Например, `BSP_SDRAM_TEST_FAST_SIZE` +используется в `firmware_test/test_sdram.c` (фаза «data bus»). Константы +`BSP_SDRAM_TEST_FULL_SIZE` и `BSP_SDRAM_TEST_EXTENDED_SIZE` определены в +заголовке, но их использование текущими потребителями в дереве не описано — +рассматривайте их как ориентиры из описания карты памяти. + +--- + +## Публичный API + +```c +bsp_status_t bsp_sdram_init(void); +``` + +Назначение: верифицировать, что SEMC завершил инициализацию (выполненную DCD) +и что SDRAM отвечает по тестовому адресу. + +Поведение (из `sdram.c`): + +1. Ждёт перехода SEMC в состояние IDLE по флагу `SEMC->STS0 & SEMC_STS0_IDLE_MASK`, + таймаут — `SDRAM_SEMC_IDLE_TIMEOUT_MS = 10 мс` (через `bsp_tick_get_ms()`). +2. Записывает по `BSP_SDRAM_TEST_BASE_ADDR` паттерн `0xA5A5A5A5`, + делает `SCB_CleanDCache_by_Addr` + `SCB_InvalidateDCache_by_Addr` + `__DSB()`, + читает обратно и сверяет. +3. Повторяет то же для инверсного паттерна `0x5A5A5A5A`. +4. При успехе устанавливает внутренний флаг готовности и возвращает `BSP_OK`. + +Коды возврата: + +| Код | Когда | +| ----------------- | --------------------------------------------------------------------- | +| `BSP_OK` | SDRAM доступна, оба паттерна успешно прочитаны обратно. | +| `BSP_ERR_TIMEOUT` | SEMC не перешёл в IDLE за `SDRAM_SEMC_IDLE_TIMEOUT_MS` (10 мс). | +| `BSP_ERR_INIT` | Хотя бы один паттерн не совпал при readback (DCD/SDRAM не готовы). | + +`bsp_sdram_init()` затрагивает только 4 байта по адресу +`BSP_SDRAM_TEST_BASE_ADDR` (две записи 32-битных слов) и не пересекается с +`.data`/`.bss` прошивки благодаря смещению на 2 МБ от базы. + +--- + +## Порядок использования + +```c +#include "bsp/sdram.h" + +if (bsp_sdram_init() != BSP_OK) { + /* SEMC/SDRAM недоступны — это критическая ошибка для прошивки, + которая использует SDRAM под фреймбуферы и тестовые регионы. */ + handle_critical_error(); +} + +/* Дальше — обычная работа с памятью по адресам внутри + [BSP_SDRAM_BASE_ADDR, BSP_SDRAM_BASE_ADDR + BSP_SDRAM_SIZE_BYTES). */ +``` + +Полноценные тесты памяти (шина адреса, шина данных, sequential, retention) +запускаются отдельным тест-модулем — см. раздел «Связь с firmware_test». + +--- + +## Зависимости и CMake + +```cmake +# bsp/sdram/CMakeLists.txt +add_library(bsp_sdram STATIC src/sdram.c) + +target_include_directories(bsp_sdram + PUBLIC include/ + PRIVATE src/) + +target_link_libraries(bsp_sdram + PUBLIC bsp_status + PRIVATE bsp_board bsp_tick sdk_semc) +``` + +- `bsp_status` (PUBLIC) — `bsp_status_t` в публичном API. +- `bsp_tick` (PRIVATE) — `bsp_tick_get_ms()` для таймаута SEMC IDLE. +- `sdk_semc` (PRIVATE) — `fsl_semc.h`, нужен для `SEMC->STS0` и + `SEMC_STS0_IDLE_MASK` при проверке готовности контроллера. +- `bsp_board` (PRIVATE) — общие board-уровневые символы. + +Цель не собирается при `BUILD_TESTS_HOST=ON` (host-сборка), `CMakeLists.txt` +содержит ранний `return()`. + +Потребитель (пример из `firmware/test/CMakeLists.txt`): + +```cmake +target_link_libraries(firmware_test PRIVATE + ... + bsp_sdram + ... +) +``` + +--- + +## Связь с firmware_test (`test_sdram.c`) + +Тест-модуль `firmware/test/src/tests/test_sdram.c` использует этот BSP как +основу: + +- В `init`-фазе модуль вызывает `bsp_sdram_init()` и сохраняет результат + в `g_s_ready`. При неуспехе `run` сразу возвращает FAIL с + `detail = "SEMC not ready — DCD failed?"`. +- Базовый адрес тестового региона берётся из `BSP_SDRAM_TEST_BASE_ADDR`. +- Размер фазы «data bus» — `BSP_SDRAM_TEST_FAST_SIZE` (64 KB). +- Остальные фазы (`address bus`, `sequential`, `retention`) используют + свои локальные константы (`SDRAM_ADDR_BUS_BITS`, `SDRAM_SEQUENTIAL_SIZE`, + `SDRAM_RETENTION_SIZE`), определённые в `test_sdram.c`, не в BSP. +- Cache maintenance в тестовом модуле повторяет ту же схему, что в BSP: + `SCB_CleanDCache_by_Addr` + `SCB_InvalidateDCache_by_Addr` + `__DSB()`. + +Дескриптор тест-модуля: + +```c +const test_module_t K_TEST_SDRAM = { + .id = "sdram", + .name = "SDRAM 32 MB", + .critical = true, + .requires_hil = false, + ... +}; +``` + +--- + +## Ограничения и замечания + +- Модуль **не выполняет** инициализацию SEMC, DCD или MPU. Если соответствующие + механизмы не отработали до `main()`, `bsp_sdram_init()` вернёт ошибку, но + починить ситуацию из этого модуля нельзя — корень проблемы в DCD / startup + / clock-config. +- Реентрантность `bsp_sdram_init()` не описана и не гарантируется: в прошивке + вызов выполняется однократно на этапе инициализации. +- Тайм-аут IDLE (`10 мс`) рассчитан на здоровый контроллер; в случае реального + отказа SEMC именно эта величина определяет, через сколько `BSP_ERR_TIMEOUT` + будет возвращён. +- Cache maintenance в `verify_word()` работает только по одной кэш-линии + (32 байта), и `BSP_SDRAM_TEST_BASE_ADDR = 0x80200000` подобран кратным + размеру кэш-линии Cortex-M7 — иначе вызовы `SCB_*_by_Addr` потребовали бы + выравнивания. +- Связь между константами размеров (`BSP_SDRAM_TEST_FULL_SIZE`, + `BSP_SDRAM_TEST_EXTENDED_SIZE`) и конкретными сценариями тестирования не + гарантируется этим BSP — это значения из карты памяти, которые потребитель + может использовать или игнорировать. Точная стратегия тестов SDRAM + определена в `firmware_test/test_sdram.c`. diff --git a/docs/DEV_ARCH.md b/docs/DEV_ARCH.md index 6740193..6deb33a 100644 --- a/docs/DEV_ARCH.md +++ b/docs/DEV_ARCH.md @@ -183,8 +183,11 @@ HIL_USB_CDC_TIMEOUT=5.0 │ ├── pyocd_utils.py ← FLEXRAM init, ELF loader, run_from_vectors │ ├── env_config.py ← конфигурация из os.environ / .env │ ├── load_and_run.py ← CLI-утилита загрузки ELF в RAM вручную -│ ├── test_uart.py ← HIL тест bsp_uart_host (без M5) -│ ├── test_opto.py ← HIL тест bsp_opto (через M5StampPLC) +│ ├── 01_test_uart.py ← HIL тест bsp_uart_host (без M5) +│ ├── 02_test_opto.py ← HIL тест bsp_opto (через M5StampPLC) +│ ├── 03_test_can.py ← HIL тест bsp_can +│ ├── 04_test_button.py ← HIL тест bsp_button +│ ├── 05_test_usb_cdc.py ← HIL тест USB CDC │ ├── m5/ │ │ ├── agent.py ← MicroPython агент на M5StampPLC │ │ ├── cli.py ← интерактивный CLI для ручного тестирования стенда @@ -196,14 +199,24 @@ HIL_USB_CDC_TIMEOUT=5.0 ├── tests/ │ ├── host/ ← host unit-тесты (Unity + fff) │ │ ├── mocks/ ← stub-хедеры NXP SDK для компиляции на хосте +│ │ ├── button/ +│ │ ├── can/ +│ │ ├── cli/ │ │ ├── led/ +│ │ ├── log/ │ │ ├── opto/ +│ │ ├── prio_queue/ +│ │ ├── protocol/ │ │ ├── ring_buffer/ +│ │ ├── runner/ │ │ ├── timeout/ │ │ └── uart_host/ │ └── target/ ← HIL target-прошивки (RAM, pyOCD) │ ├── host_uart/ ← CLI для тестирования bsp_uart_host -│ └── hil_opto/ ← CLI для тестирования bsp_opto +│ ├── hil_button/ ← CLI для тестирования bsp_button +│ ├── hil_can/ ← CLI для тестирования bsp_can +│ ├── hil_opto/ ← CLI для тестирования bsp_opto +│ └── hil_usb_cdc/ ← CLI для тестирования USB CDC │ └── docs/ ├── DEV_ARCH.md ← этот документ @@ -213,7 +226,7 @@ HIL_USB_CDC_TIMEOUT=5.0 ├── mimxrt1052/ ← MCU: BOOT_FLAGS, reference manual └── testing/ ├── hil/ - │ ├── HIL_HOWTO.md ← как проводить HIL-тесты + │ ├── HIL_HOW_TO.md ← как проводить HIL-тесты │ ├── HIL_BENCH.md ← стенд: оборудование, подключение │ └── HIL_CREATE_TEST.md ← как добавить новый HIL-тест └── host/ @@ -300,12 +313,17 @@ buildPresets (ARM firmware): all-debug / all-release buildPresets (host-тесты): - host-debug-build: test_bsp_led, test_log, test_bsp_opto, - test_ring_buffer, test_timeout_pattern, uart_host_mock_example + host-debug-build: test_bsp_led, test_log, test_bsp_opto, test_bsp_button, + test_bsp_can, test_cli, test_protocol, test_firmware_runner, + test_prio_queue, uart_host_mock_example, test_ring_buffer, + test_timeout_pattern host-release-build: то же buildPresets (HIL): - target-debug-build: test_host_uart, test_hil_opto + target-debug-build: test_host_uart, test_hil_button, test_hil_can, + test_hil_usb_cdc, test_hil_opto + +Источник истины по списку целей — [CMakePresets.json](../CMakePresets.json). ``` ### 6.3 Boot-стратегии @@ -409,7 +427,7 @@ just host::flash-swd-test-release HIL-тесты проверяют периферию на реальном железе. Два типа: -### Базовые (без стенда) — `test_uart.py` +### Базовые (без стенда) — `01_test_uart.py` Только MCU-Link: SWD загружает ELF в RAM, VCOM обеспечивает UART CLI. @@ -421,7 +439,7 @@ MCU-Link RT1052 → "PONG" ``` -### С M5StampPLC — `test_opto.py` и другие +### С M5StampPLC — `02_test_opto.py` и другие `M5StampPLC` управляет входными сигналами таргета через реле. pytest оркестрирует оба канала одновременно. @@ -438,11 +456,11 @@ pytest ```bash just build::build-hil # (devcontainer) собрать HIL ELF just host::hil-run # прогнать все HIL тесты -just host::hil-uart # только test_uart.py -just host::hil-opto # только test_opto.py +just host::hil-uart # только 01_test_uart.py +just host::hil-opto # только 02_test_opto.py ``` -Подробно — [docs/testing/hil/HIL_HOWTO.md](testing/hil/HIL_HOWTO.md). +Подробно — [docs/testing/hil/HIL_HOW_TO.md](testing/hil/HIL_HOW_TO.md). Добавление нового теста — [docs/testing/hil/HIL_CREATE_TEST.md](testing/hil/HIL_CREATE_TEST.md). Стенд и подключение — [docs/testing/hil/HIL_BENCH.md](testing/hil/HIL_BENCH.md). @@ -462,8 +480,10 @@ just host::hil-opto # только test_opto.py BSP-модули тестируются через fff-фейки и stub-хедеры в `tests/host/mocks/`. `BUILD_TESTS_HOST=ON` отключает ARM-специфику и SDK-заголовки. -Покрытие: `bsp_led`, `bsp_opto`, `bsp_uart_host`, `ring_buffer`, timeout-паттерн. -Гайд — [tests/HOST_CREATE_TEST.md](../tests/HOST_CREATE_TEST.md). +Покрытие: `bsp_led`, `bsp_opto`, `bsp_button`, `bsp_can`, `bsp_uart_host`, +`cli`, `protocol`, `firmware_runner`, `prio_queue`, `ring_buffer`, `log`, +timeout-паттерн. +Гайд — [testing/host/HOST_CREATE_TEST.md](testing/host/HOST_CREATE_TEST.md). ### 10.2 HIL target-тесты @@ -473,7 +493,9 @@ BSP-модули тестируются через fff-фейки и stub-хед Запуск: just host::hil-run (на хосте) ``` -Текущие тесты: `test_uart.py` (PING/ECHO/BUF_SIZE), `test_opto.py` (оптовходы IN1/IN2/RS через M5). +Текущие тесты: `01_test_uart.py` (PING/ECHO/BUF_SIZE), `02_test_opto.py` +(оптовходы IN1/IN2/RS через M5), `03_test_can.py` (CAN-шина), `04_test_button.py` +(кнопочные входы), `05_test_usb_cdc.py` (USB CDC). --- @@ -500,7 +522,8 @@ Devcontainer - `🐛 Debug: bootloader` — bare-metal, A/B обновление - `🐛 Debug: tft_app (FreeRTOS)` — FreeRTOS task view -RTT-логи (`SEGGER_RTT_ENABLED=ON`) пока что не используются вместо этого можно использовать `port/log`. +RTT-логи доступны в Debug-сборках (`SEGGER_RTT_ENABLED=ON`); подробности — в +[docs/HOW_TO_DEBUG.md](HOW_TO_DEBUG.md). --- diff --git a/docs/testing/hil/HIL_CREATE_TEST.md b/docs/testing/hil/HIL_CREATE_TEST.md index dfed53c..3c15b4f 100644 --- a/docs/testing/hil/HIL_CREATE_TEST.md +++ b/docs/testing/hil/HIL_CREATE_TEST.md @@ -407,9 +407,9 @@ test_foo() ### Порядок при запуске нескольких файлов ```bash -pytest test_uart.py test_.py +pytest 01_test_uart.py test_.py -test_uart.py test_.py +01_test_uart.py test_.py ───────────────────── ───────────────────── loaded_host_uart m5 ← создаётся uart ← создаётся loaded_ diff --git a/docs/testing/hil/HIL_FIXTURES.md b/docs/testing/hil/HIL_FIXTURES.md index 69cb60a..600708e 100644 --- a/docs/testing/hil/HIL_FIXTURES.md +++ b/docs/testing/hil/HIL_FIXTURES.md @@ -65,7 +65,7 @@ Scope определяет **как долго живёт** экземпляр ### Почему для HIL основной scope — `module` -Каждый тест-файл (`test_uart.py`, `test_opto.py`) работает со **своей прошивкой**. +Каждый тест-файл (`01_test_uart.py`, `02_test_opto.py`) работает со **своей прошивкой**. Загружать ELF перед каждой `test_*()` — слишком дорого (~2 с на загрузку через pyOCD). `scope="module"` означает: загрузил один раз, прогнал все тесты файла, закрыл. @@ -194,8 +194,8 @@ test_opto_ch1() ```bash tools/hil/ ├── conftest.py ← фикстуры: _load_elf, uart, m5, loaded_* -├── test_uart.py ← видит всё из conftest.py -├── test_opto.py ← видит всё из conftest.py +├── 01_test_uart.py ← видит всё из conftest.py +├── 02_test_opto.py ← видит всё из conftest.py └── m5/ └── conftest.py ← (если бы был) виден только в m5/ ``` @@ -244,11 +244,11 @@ Teardown выполняется в **обратном** порядке созд При запуске нескольких файлов каждый получает **свой** набор module-фикстур: ```bash -pytest test_uart.py test_opto.py +pytest 01_test_uart.py 02_test_opto.py ``` ```bash -test_uart.py test_opto.py +01_test_uart.py 02_test_opto.py ───────────────────────── ───────────────────────── loaded_host_uart ← создаётся m5 ← создаётся uart ← создаётся loaded_hil_opto @@ -538,7 +538,7 @@ class Test: ## 12. Визуальная схема жизненного цикла ```bash -pytest test_opto.py +pytest 02_test_opto.py ────────────── module scope (один раз на файл) ────────────── diff --git a/docs/testing/hil/HIL_HOW_TO.md b/docs/testing/hil/HIL_HOW_TO.md index 1549414..9e78361 100644 --- a/docs/testing/hil/HIL_HOW_TO.md +++ b/docs/testing/hil/HIL_HOW_TO.md @@ -136,21 +136,21 @@ just host::hil-button # конкретный интерактивны ### Конкретный автоматический тест ```bash -just host::hil-uart # только test_uart.py -just host::hil-opto # только test_opto.py -just host::hil-can # только test_can.py +just host::hil-uart # только 01_test_uart.py +just host::hil-opto # только 02_test_opto.py +just host::hil-can # только 03_test_can.py ``` ### Один тест-кейс (для отладки) ```bash -uv run --directory tools/hil pytest test_opto.py::TestOptoConnectivity::test_target_ping -v +uv run --directory tools/hil pytest 02_test_opto.py::TestOptoConnectivity::test_target_ping -v ``` ### Без перезагрузки ELF (если прошивка уже запущена) ```bash -uv run --directory tools/hil pytest test_opto.py -v --no-load +uv run --directory tools/hil pytest 02_test_opto.py -v --no-load ``` Удобно при отладке тестов когда прошивка уже в RAM и не нужно каждый раз diff --git a/tools/hil/README.md b/tools/hil/README.md index 36b358d..c6ca913 100644 --- a/tools/hil/README.md +++ b/tools/hil/README.md @@ -15,8 +15,8 @@ tools/hil/ ├── env_config.py — конфигурация из os.environ / .env ├── pyocd_utils.py — FLEXRAM init, ELF loader, run_from_vectors ├── load_and_run.py — CLI-утилита: загрузить ELF в RAM вручную -├── test_uart.py — HIL тест bsp_uart_host (без стенда) -├── test_opto.py — HIL тест bsp_opto (через M5StampPLC) +├── 01_test_uart.py — HIL тест bsp_uart_host (без стенда) +├── 02_test_opto.py — HIL тест bsp_opto (через M5StampPLC) ├── m5/ │ ├── agent.py — MicroPython агент на M5StampPLC (реле, входы, CAN) │ ├── cli.py — интерактивный CLI для ручного тестирования стенда и таргета @@ -40,7 +40,7 @@ tools/hil/ | Документ | Содержимое | | --------------------------------------------------------------- | ---------------------------------------------- | -| [HIL_HOWTO.md](../../docs/testing/hil/HIL_HOWTO.md) | Как запускать HIL-тесты (пошагово) | +| [HIL_HOW_TO.md](../../docs/testing/hil/HIL_HOW_TO.md) | Как запускать HIL-тесты (пошагово) | | [HIL_BENCH.md](../../docs/testing/hil/HIL_BENCH.md) | Стенд: оборудование, подключение, маппинг реле | | [HIL_CREATE_TEST.md](../../docs/testing/hil/HIL_CREATE_TEST.md) | Как добавить новый HIL-тест | diff --git a/tools/host/README.md b/tools/host/README.md index f73cde0..32fc649 100644 --- a/tools/host/README.md +++ b/tools/host/README.md @@ -20,7 +20,7 @@ tools/host/ │ ├── w25q128_fdcb.bin — FCB для W25Q128 Quad SPI ← используется │ ├── w25q64_fdcb.bin — FCB для W25Q64 Quad SPI │ └── w25q512_fdcb.bin — FCB для W25Q512 Quad SPI -├── HAB_GUIDE.md — подробно про HAB-образы и процесс подписи +├── ../../docs/mimxrt1052/HAB_GUIDE.md — подробно про HAB-образы и процесс подписи ├── pyproject.toml └── uv.lock ```