From 0a45805350c7e1e8fde77b078263d73a3a5b723b Mon Sep 17 00:00:00 2001 From: Ezra Maccabee Date: Mon, 20 Apr 2026 20:07:25 +0300 Subject: [PATCH] # Added: sdram service test --- bsp/CMakeLists.txt | 1 + bsp/sdram/CMakeLists.txt | 25 ++ bsp/sdram/include/bsp/sdram.h | 85 ++++++ bsp/sdram/src/sdram.c | 122 ++++++++ bsp/sdram/src/sdram_test.c | 0 bsp/sdram/СMakeLists.txt | 0 firmware/test/CMakeLists.txt | 10 +- firmware/test/src/test_runner.c | 3 +- firmware/test/src/tests/README.md | 418 +++++++++++++++++++++++++++ firmware/test/src/tests/test_sdram.c | 377 ++++++++++++++++++++++++ 10 files changed, 1035 insertions(+), 6 deletions(-) create mode 100644 bsp/sdram/CMakeLists.txt delete mode 100644 bsp/sdram/src/sdram_test.c delete mode 100644 bsp/sdram/СMakeLists.txt create mode 100644 firmware/test/src/tests/README.md create mode 100644 firmware/test/src/tests/test_sdram.c diff --git a/bsp/CMakeLists.txt b/bsp/CMakeLists.txt index 9de92ce..f1aff58 100644 --- a/bsp/CMakeLists.txt +++ b/bsp/CMakeLists.txt @@ -16,6 +16,7 @@ add_subdirectory(opto) add_subdirectory(can) add_subdirectory(button) add_subdirectory(usb_cdc) +add_subdirectory(sdram) # Подавляем предупреждения при компиляции собственных .c файлов библиотеки target_compile_options(bsp_board PRIVATE -w) diff --git a/bsp/sdram/CMakeLists.txt b/bsp/sdram/CMakeLists.txt new file mode 100644 index 0000000..1453913 --- /dev/null +++ b/bsp/sdram/CMakeLists.txt @@ -0,0 +1,25 @@ +# bsp/sdram/CMakeLists.txt bsp_sdram — верификация и тест внешней SDRAM +# (MT48LC16M16A2, 32 MB) +# +# Архитектурное решение [DECISION]: SEMC инициализируется DCD до main(). +# bsp_sdram_init() только верифицирует доступность памяти — не трогает SEMC +# регистры. +# +# Зависимости: sdk_semc — fsl_semc.h нужен для SEMC_GetStatusFlags() в +# верификации bsp_tick — bsp_tick_get_ms() для замера времени тест-паттернов + +if(BUILD_TESTS_HOST) + return() +endif() + +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) diff --git a/bsp/sdram/include/bsp/sdram.h b/bsp/sdram/include/bsp/sdram.h index e69de29..bb5a7c7 100644 --- a/bsp/sdram/include/bsp/sdram.h +++ b/bsp/sdram/include/bsp/sdram.h @@ -0,0 +1,85 @@ +/** + * @file sdram.h + * @brief BSP для внешней SDRAM MT48LC16M16A2 (32 МБ, шина 16 бит). + * + * Архитектурное ограничение [DECISION]: + * SEMC инициализируется DCD до вызова main(). Этот модуль не трогает + * регистры SEMC — только верифицирует работоспособность памяти. + * + * Карта памяти: + * 0x80000000 — начало SDRAM (SEMC BR0) + * 0x81FFFFFF — конец SDRAM (32 MB) + * 0x81E00000 — начало non-cacheable региона (USB DMA, 2 MB) + * + * Тестовый регион (не пересекается с .data/.bss и non-cacheable): + * 0x80200000 — начало (2 MB offset от базы) + * + * Использование: + * @code + * bsp_sdram_result_t result; + * + * if (bsp_sdram_init() != BSP_OK) { // DCD не отработал + * handle_critical_error(); + * } + * + * bsp_sdram_test_fast(&result); // ~50 мс, 64 KB + * bsp_sdram_test_full(&result); // ~2–5 с, 1 MB + * @endcode + */ + +#ifndef BSP_SDRAM_H_ +#define BSP_SDRAM_H_ + +#include "bsp/status.h" + +#include + +/** + * @brief Размер расширенного тестового региона, байт (27 MB). + * + * От BSP_SDRAM_TEST_BASE_ADDR (0x80200000) до 0x81D00000. + * Оставляет 1 MB защитную зону перед heap и стеком firmware_test + * (stack top = 0x81E00000 − 4KB heap − 4KB stack ≈ 0x81DFE000). + */ +#define BSP_SDRAM_TEST_EXTENDED_SIZE 0x01B00000UL + +/* ── Константы ─────────────────────────────────────────────────────────── */ + +/** @brief Базовый адрес SDRAM (SEMC BR0). */ +#define BSP_SDRAM_BASE_ADDR 0x80000000UL + +/** @brief Полный размер SDRAM, байт (32 MB). */ +#define BSP_SDRAM_SIZE_BYTES 0x02000000UL + +/** + * @brief Базовый адрес тестового региона. + * + * Смещение 2 MB от базы — гарантированно выше .data/.bss firmware_test + * и ниже non-cacheable региона (0x81E00000). + */ +#define BSP_SDRAM_TEST_BASE_ADDR 0x80200000UL + +/** @brief Размер быстрого теста, байт (64 KB). */ +#define BSP_SDRAM_TEST_FAST_SIZE 0x00010000UL + +/** @brief Размер полного теста, байт (1 MB). */ +#define BSP_SDRAM_TEST_FULL_SIZE 0x00100000UL + +/* ── Public API ────────────────────────────────────────────────────────── */ + +/** + * @brief Верифицировать доступность SDRAM. + * + * Проверяет что SEMC контроллер инициализирован DCD и SDRAM отвечает — + * выполняет минимальный write/read/verify на первых 4 байтах тестового + * региона. Не затрагивает .data/.bss прошивки. + * + * @note Не реинициализирует SEMC — DCD уже сделал это до main(). + * + * @retval BSP_OK SDRAM доступна и отвечает корректно. + * @retval BSP_ERR_INIT SEMC не готов (DCD не отработал). + * @retval BSP_ERR_TIMEOUT SEMC занят дольше ожидаемого. + */ +bsp_status_t bsp_sdram_init(void); + +#endif /* BSP_SDRAM_H_ */ \ No newline at end of file diff --git a/bsp/sdram/src/sdram.c b/bsp/sdram/src/sdram.c index e69de29..90ebb5e 100644 --- a/bsp/sdram/src/sdram.c +++ b/bsp/sdram/src/sdram.c @@ -0,0 +1,122 @@ +/** + * @file sdram.c + * @brief Верификация внешней SDRAM MT48LC16M16A2 (32 МБ, шина 16 бит). + * + * SEMC инициализируется DCD до main() — этот модуль только проверяет + * доступность памяти. Регистры SEMC не модифицируются. + * + * Кэш: SDRAM настроена как Normal Write-Back cacheable (MPU Region 8). + * Верификация требует явного cache maintenance перед readback — иначе + * чтение попадает в кэш и физическая SDRAM не тестируется. + */ + +#include "bsp/sdram.h" + +#include "bsp/tick.h" +#include "fsl_semc.h" + +#include +#include + +/* ── Константы ─────────────────────────────────────────────────────────── */ + +/** @brief Таймаут ожидания готовности SEMC контроллера, мс. */ +#define SDRAM_SEMC_IDLE_TIMEOUT_MS 10U + +/** + * @brief Размер кэш-линии Cortex-M7, байт. + * + * SCB_CleanDCache_by_Addr / SCB_InvalidateDCache_by_Addr требуют + * выравнивания адреса и размера по кэш-линии. + * BSP_SDRAM_TEST_BASE_ADDR = 0x80200000 — кратен 32 байтам. ✓ + */ +#define SDRAM_CACHE_LINE_BYTES 32U + +/** @brief Эталонный паттерн верификации (чередующиеся биты). */ +#define SDRAM_VERIFY_PATTERN_A 0xA5A5A5A5UL + +/** @brief Инверсия эталонного паттерна. */ +#define SDRAM_VERIFY_PATTERN_B 0x5A5A5A5AUL + +/* ── Состояние модуля ──────────────────────────────────────────────────── */ + +static bool s_initialised = false; + +/* ── Внутренние функции ────────────────────────────────────────────────── */ + +/** + * @brief Дождаться перехода SEMC в состояние IDLE. + * + * @return BSP_OK при успехе, BSP_ERR_TIMEOUT если SEMC не ответил. + */ +static bsp_status_t wait_semc_idle(void) +{ + const uint32_t START_MS = bsp_tick_get_ms(); + + while ((SEMC->STS0 & SEMC_STS0_IDLE_MASK) == 0U) + { + if ((bsp_tick_get_ms() - START_MS) >= SDRAM_SEMC_IDLE_TIMEOUT_MS) + { + return BSP_ERR_TIMEOUT; + } + } + + return BSP_OK; +} + +/** + * @brief Сбросить кэш-линию по адресу тестового региона. + * + * Clean (запись dirty линии в SDRAM) + Invalidate (следующее чтение + * пойдёт в SDRAM, не в кэш) + DSB (барьер завершения операции). + */ +static void flush_cache_at_test_base(void) +{ + uint32_t *const p_addr = (uint32_t *) BSP_SDRAM_TEST_BASE_ADDR; + + SCB_CleanDCache_by_Addr(p_addr, (int32_t) SDRAM_CACHE_LINE_BYTES); + SCB_InvalidateDCache_by_Addr(p_addr, (int32_t) SDRAM_CACHE_LINE_BYTES); + __DSB(); +} + +/** + * @brief Записать слово в тестовый адрес, сбросить кэш, прочитать обратно. + * + * @param[in] pattern Значение для записи и верификации. + * @return BSP_OK если readback совпал, BSP_ERR_INIT при расхождении. + */ +static bsp_status_t verify_word(uint32_t pattern) +{ + volatile uint32_t *const p_test = (volatile uint32_t *) BSP_SDRAM_TEST_BASE_ADDR; + + *p_test = pattern; + flush_cache_at_test_base(); + + return (*p_test == pattern) ? BSP_OK : BSP_ERR_INIT; +} + +/* ── Public API ────────────────────────────────────────────────────────── */ + +bsp_status_t bsp_sdram_init(void) +{ + bsp_status_t status = wait_semc_idle(); + if (status != BSP_OK) + { + return status; + } + + status = verify_word(SDRAM_VERIFY_PATTERN_A); + if (status != BSP_OK) + { + return BSP_ERR_INIT; + } + + status = verify_word(SDRAM_VERIFY_PATTERN_B); + if (status != BSP_OK) + { + return BSP_ERR_INIT; + } + + s_initialised = true; + return BSP_OK; +} diff --git a/bsp/sdram/src/sdram_test.c b/bsp/sdram/src/sdram_test.c deleted file mode 100644 index e69de29..0000000 diff --git a/bsp/sdram/СMakeLists.txt b/bsp/sdram/СMakeLists.txt deleted file mode 100644 index e69de29..0000000 diff --git a/firmware/test/CMakeLists.txt b/firmware/test/CMakeLists.txt index e698fbb..e0d9754 100644 --- a/firmware/test/CMakeLists.txt +++ b/firmware/test/CMakeLists.txt @@ -9,22 +9,22 @@ add_executable( src/cli.c src/protocol.c src/test_runner.c + src/tests/test_sdram.c ${BSP_GENERATED}/clock_config.c ${BSP_STARTUP_FILE} ${BSP_SYSCALLS_FILE}) target_include_directories(firmware_test PRIVATE src/) -target_compile_definitions(${TARGET_NAME} - PRIVATE BSP_UART_HOST_RX_BUFFER_SIZE=512) +target_compile_definitions( + ${TARGET_NAME} PRIVATE BSP_UART_HOST_RX_BUFFER_SIZE=512 BOARD_MPU_SDRAM=1) # # ----------------------------------------------------------------------------- # Зависимости — только то что нужно для входного контроля bsp_board транзитивно # даёт: sdk_device, sdk_clock, sdk_common, CPU_MIMXRT1052CVJ5B, XIP_* дефайны # ----------------------------------------------------------------------------- -target_link_libraries( - ${TARGET_NAME} PRIVATE bsp_board bsp_led bsp_tick bsp_boot_xip bsp_status - bsp_usb_cdc) +target_link_libraries(${TARGET_NAME} PRIVATE bsp_board bsp_led bsp_tick + bsp_boot_xip bsp_usb_cdc bsp_sdram) # ----------------------------------------------------------------------------- # Linker script diff --git a/firmware/test/src/test_runner.c b/firmware/test/src/test_runner.c index 5d54703..d3f3e8e 100644 --- a/firmware/test/src/test_runner.c +++ b/firmware/test/src/test_runner.c @@ -41,9 +41,10 @@ * ─────────────────────────────────────────────────────────────────────────*/ #ifndef UNIT_TEST - +extern const test_module_t K_TEST_SDRAM; static const test_module_t *const k_registry[] = { /* populated starting from Этап 2 */ + &K_TEST_SDRAM, }; #define REGISTRY_SIZE (sizeof(k_registry) / sizeof(k_registry[0])) diff --git a/firmware/test/src/tests/README.md b/firmware/test/src/tests/README.md new file mode 100644 index 0000000..3f34c42 --- /dev/null +++ b/firmware/test/src/tests/README.md @@ -0,0 +1,418 @@ +# firmware_test — Тест-модули + +> Расположение: `firmware/test/src/tests/README.md` +> +> Каждый тест-модуль реализует интерфейс `test_module_t` и регистрируется +> в реестре `test_runner.c`. Этот документ описывает что именно проверяет +> каждый тест, какие гарантии даёт, какие аппаратные инварианты должны +> соблюдаться, и каковы ограничения. + +--- + +## Содержание + +- [Как читать эту таблицу](#как-читать-эту-таблицу) +- [test_sdram — SDRAM 32 MB](#test_sdram--sdram-32-mb) +- [test_qspi — QSPI Flash 8 MB](#test_qspi--qspi-flash-8-mb) *(запланирован)* +- [test_usd — uSD SDIO](#test_usd--usd-sdio) *(запланирован)* +- [test_display — Display RGB888](#test_display--display-rgb888) *(запланирован)* +- [test_buttons — Test_But_1 / Test_But_2](#test_buttons--test_but_1--test_but_2) *(запланирован)* +- [test_can — CAN loopback](#test_can--can-loopback) *(запланирован)* +- [test_uart_ttl — UART TTL](#test_uart_ttl--uart-ttl) *(запланирован)* +- [test_uart_iso — UART ISO / RS_RX](#test_uart_iso--uart-iso--rs_rx) *(запланирован)* +- [test_opto — Opto-in EXT_IN1/IN2](#test_opto--opto-in-ext_in1in2) *(запланирован)* + +--- + +## Как читать эту таблицу + +**Critical** — при провале в `run_all` все последующие тесты получают `SKIP`. +Некритические тесты могут упасть без остановки прогона. + +**HIL** — тест требует внешних сигналов от M5StampPLC. +Без стенда тест вернёт `SKIP` или `FAIL`. + +**Тип confirm:** + +- `pre_confirm` — test_runner ждёт JSON-ответа оператора до запуска `run()`. +- `в run()` — тест сам вызывает `test_runner_wait_confirm()` внутри. +- `prompt only` — отправляет `confirm_request` как UI-подсказку, + ответ не ожидается (тест детектирует физическое событие сам). +- `—` — неинтерактивный тест. + +--- + +## test_sdram — SDRAM 32 MB + +**Файл:** `test_sdram.c` +**ID:** `sdram` +**Critical:** ✅ | **HIL:** ❌ | **Confirm:** — + +### Аппаратный контекст + +| Параметр | Значение | +|---|---| +| Чип | MT48LC16M16A2 | +| Объём | 32 MB | +| Шина данных | 16 бит | +| Интерфейс MCU | SEMC (0x402F0000) | +| Базовый адрес | 0x80000000 | +| Тестовый регион | 0x80200000 — 0x80A00000 | +| MPU | Region 8: Normal WB cacheable (BOARD_MPU_SDRAM=1) | + +### Инварианты выполнения + +- SEMC инициализирован DCD **до** `main()` — `bsp_sdram_init()` только верифицирует. +- MPU Region 8 (`Normal WB`) активен — кэш включён для SDRAM. +- MPU Region 9 (`Non-cacheable`, 0x81E00000, 2MB) активен — USB DMA изолирован. +- Тестовый регион не пересекается с `.data`/`.bss` прошивки (смещение +2 MB от базы). +- Тестовый регион не пересекается с non-cacheable регионом (граница 0x81E00000). +- Сброс кэша (`SCB_CleanDCache_by_Addr`) выполняется **после каждого** write-прохода — + readback всегда из физической SDRAM, не из кэша. + +### Что тестирует — четыре фазы + +#### Фаза 1: Address bus (~1 мс) + +Записывает уникальный байт в 24 позиции на степенях двойки +(2^0..2^23 от TEST_BASE), каждую с индивидуальным cache line flush. + +**Покрытие:** все 24 адресных бита MT48LC16M16A2 (13 row + 9 col + 2 bank). + +**Ловит:** + +- Address aliasing — замыкание адресных линий SEMC. +- Неправильное подключение адресных линий к чипу. + +**Не ловит:** + +- Деградацию отдельных ячеек вне точек степеней двойки. + +--- + +#### Фаза 2: Data bus (~1 с) + +Walking ones (0x01, 0x02, ..., 0x80, 0x01, ...) и его инверсия +на регионе 64 KB. + +**Покрытие:** все 8 бит шины данных. + +**Ловит:** + +- Stuck-at-0 и stuck-at-1 фолты на битах шины данных. +- Обрыв линии DATA между MCU и чипом. + +**Не ловит:** + +- Coupling между несмежными битами (для этого — фаза 3). + +--- + +#### Фаза 3: Sequential integrity (~25 с) + +Address pattern (`offset & 0xFF`) и его инверсия на регионе 2 MB, +два паттерна × два прохода (write → flush → verify). + +**Покрытие:** 2 MB непрерывного адресного пространства. + +**Ловит:** + +- Coupling faults между соседними ячейками. +- Деградированные ячейки в тестируемом регионе. +- Частичный address aliasing внутри 2 MB. + +**Не ловит:** + +- Деградацию ячеек вне тестируемых 2 MB (всего 32 MB в чипе). + +--- + +#### Фаза 4: Retention (~2 с) + +Address pattern на 256 KB: запись → `flush_dcache` → ожидание 200 мс → верификация. + +200 мс ≈ 3 полных refresh-периода MT48LC16M16A2 (период = 64 мс). + +**Покрытие:** 256 KB с задержкой на несколько refresh-циклов. + +**Ловит:** + +- Refresh timing failures — ячейки теряют данные между refresh-циклами. +- Деградацию конденсаторов ячеек памяти (capacitor leakage). + +**Не ловит:** + +- Retention failures при температурных крайностях. + +--- + +### Гарантии теста при PASS + +- Все 24 адресных бита работают независимо без aliasing. +- Все 8 бит шины данных переключаются корректно. +- 2 MB последовательных ячеек не имеют coupling faults. +- 256 KB удерживают данные минимум через 3 refresh-цикла. +- SEMC контроллер инициализирован и отвечает. + +### Интерпретация FAIL + +`detail` содержит: `addr=0xXXXXXXXX exp=0xXX got=0xXX` + +| Диапазон addr | Вероятная фаза | Диагноз | +|---|---|---| +| `0x80200000` — `0x80A00000` (степени двойки) | Фаза 1 | Address aliasing | +| `0x80200000` — `0x80210000` | Фаза 2 | Stuck-at на шине данных | +| `0x80200000` — `0x80400000` | Фаза 3 | Coupling или деградация ячейки | +| `0x80200000` — `0x80240000` | Фаза 4 | Refresh timing failure | + +**Анализ `exp` XOR `got`:** биты где `(exp ^ got) != 0` — сбойные линии шины данных. + +### Ограничения + +- Не покрывает все 32 MB (только 2 MB для sequential). +- Не проверяет retention при нагреве или низком напряжении питания. +- Не является заменой полного March C− алгоритма. +- Во время теста (~28 с) USB CDC занят, `ping` не отвечает. + +### Типичное время выполнения + +| Фаза | ~Время | +|---|---| +| Address bus | < 1 мс | +| Data bus | ~1 с | +| Sequential | ~25 с | +| Retention | ~2 с | +| **Итого** | **~28 с** | + +--- + +## test_qspi — QSPI Flash 8 MB + +**Файл:** `test_qspi.c` *(не реализован — Этап 3)* +**ID:** `qspi` +**Critical:** ✅ | **HIL:** ❌ | **Confirm:** — + +### Аппаратный контекст + +| Параметр | Значение | +|---|---| +| Чип | W25Q64FVSSIG | +| Объём | 8 MB | +| Интерфейс | FlexSPI | +| BSP | `bsp_qspi` | + +### Что будет тестировать + +- JEDEC ID верификация: manufacturer=`0xEF`, device=`0x4017`. +- Erase последнего сектора (4 KB, не затрагивает прошивку). +- Write 256 байт → Read → Compare. + +### Гарантии при PASS + +- FlexSPI контроллер инициализирован. +- Чип отвечает с ожидаемым JEDEC ID. +- Базовые операции чтения/записи/стирания работают. + +--- + +## test_usd — uSD SDIO + +**Файл:** `test_usd.c` *(не реализован — Этап 4)* +**ID:** `usd` +**Critical:** ❌ | **HIL:** ❌ | **Confirm:** pre_confirm + +### Аппаратный контекст + +| Параметр | Значение | +|---|---| +| Интерфейс | USDHC | +| Файловая система | FatFS (SDK middleware) | +| BSP | `bsp_usd` | +| Pre-confirm prompt | "Вставьте карту microSD и нажмите OK" | + +### Что будет тестировать + +- Mount FAT — карта вставлена и файловая система читаема. +- Write тестового файла → Read → Compare (деструктивно для одного файла). +- Unmount. + +### Поведение без карты + +Оператор отказался от pre_confirm или таймаут 30 с → `TEST_STATUS_SKIP`. + +--- + +## test_display — Display RGB888 + +**Файл:** `test_display.c` *(не реализован — Этап 5)* +**ID:** `display` +**Critical:** ❌ | **HIL:** ❌ | **Confirm:** в run() + +### Что будет тестировать + +Четыре шага с визуальным подтверждением оператора: + +| Шаг | ID confirm | Промпт | Таймаут | +|---|---|---|---| +| Красный | `display_red` | "Экран залит красным?" | 15 с | +| Зелёный | `display_green` | "Экран залит зелёным?" | 15 с | +| Синий | `display_blue` | "Экран залит синим?" | 15 с | +| Белый | `display_white` | "Экран залит белым?" | 15 с | + +Итог = AND всех четырёх подтверждений. + +### Гарантии при PASS + +- RGB888 интерфейс выводит каждый цвет независимо. +- Подсветка PWM активна (оператор видит экран). + +### Интерпретация FAIL + +`detail` содержит ID шага, который не был подтверждён: `"display_blue not confirmed"`. +Это указывает на конкретный цветовой канал с дефектом. + +--- + +## test_buttons — Test_But_1 / Test_But_2 + +**Файл:** `test_buttons.c` *(не реализован — Этап 5)* +**ID:** `buttons` +**Critical:** ❌ | **HIL:** ❌ | **Confirm:** prompt only + +### Аппаратный контекст + +| Кнопка | Пин MCU | GPIO | +|---|---|---| +| Test_But_1 | GPIO_B1_14 | GPIO2[30] | +| Test_But_2 | GPIO_B1_15 | GPIO2[31] | + +### Что будет тестировать + +Два шага. Таргет отправляет `confirm_request` как инструкцию оператору +и детектирует нажатие через `bsp_button` — JSON confirm не нужен. + +| Шаг | ID | Промпт | Таймаут | +|---|---|---|---| +| Кнопка 1 | `btn1_press` | "Нажмите кнопку Test_But_1" | 10 с | +| Кнопка 2 | `btn2_press` | "Нажмите кнопку Test_But_2" | 10 с | + +### Гарантии при PASS + +- Оба GPIO входа корректно регистрируют нажатие. +- `bsp_button` debounce логика работает. + +--- + +## test_can — CAN loopback + +**Файл:** `test_can.c` *(не реализован — Этап 6)* +**ID:** `can` +**Critical:** ❌ | **HIL:** ✅ | **Confirm:** — + +### Аппаратный контекст + +| Параметр | Значение | +|---|---| +| BSP | `bsp_can` ✅ | +| M5 | CAN интерфейсная плата → шина платы | + +### Что будет тестировать + +M5StampPLC отправляет CAN фрейм → плата принимает → сравниваем ID и данные. + +### Гарантии при PASS + +- CAN контроллер и трансивер работают. +- Принятый фрейм совпадает с отправленным по ID и payload. + +--- + +## test_uart_ttl — UART TTL + +**Файл:** `test_uart_ttl.c` *(не реализован — Этап 6)* +**ID:** `uart_ttl` +**Critical:** ❌ | **HIL:** ✅ | **Confirm:** — + +### Аппаратный контекст + +| Параметр | Значение | +|---|---| +| BSP | `bsp_uart_host` ✅ | +| M5 | UART ↔ UART TTL платы | + +### Что будет тестировать + +M5 отправляет пакет → плата получает → echo обратно → M5 верифицирует. + +--- + +## test_uart_iso — UART ISO / RS_RX + +**Файл:** `test_uart_iso.c` *(не реализован — Этап 6)* +**ID:** `uart_iso` +**Critical:** ❌ | **HIL:** ✅ | **Confirm:** — + +### Аппаратный контекст + +| Параметр | Значение | +|---|---| +| BSP | `bsp_opto` (rs_as_gpio=true) ✅ | +| Пин MCU | GPIO_AD_B1_07 / GPIO1[23] | +| Оптопара | PS2801-4 (неинвертирующая, active-HIGH) | +| M5 | RLY2 → RS_RX | + +### Что будет тестировать + +M5 RLY2 активирует оптовход RS_RX → плата детектирует через `bsp_opto`. +M5 RLY2 деактивирует → плата детектирует inactive. + +### Инварианты + +- `bsp_opto` инициализирован с `rs_as_gpio=true`. +- Пин GPIO_AD_B1_07 переведён в GPIO INPUT (не LPUART3_RX). + +--- + +## test_opto — Opto-in EXT_IN1/IN2 + +**Файл:** `test_opto.c` *(не реализован — Этап 6)* +**ID:** `opto` +**Critical:** ❌ | **HIL:** ✅ | **Confirm:** — + +### Аппаратный контекст + +| Канал | Пин MCU | GPIO | Оптопара | M5 | +|---|---|---|---|---| +| EXT_IN1 | GPIO_AD_B1_06 | GPIO1[22] | PS2801-4 | RLY3 | +| EXT_IN2 | GPIO_AD_B1_05 | GPIO1[21] | PS2801-4 | RLY4 | + +### Что будет тестировать + +Каждый канал независимо: M5 активирует реле → плата детектирует ACTIVE → +M5 деактивирует → плата детектирует INACTIVE. + +### Гарантии при PASS + +- Оба оптоизолированных входа корректно детектируют HIGH/LOW. +- Debounce логика `bsp_opto` (MODE_LEVEL) работает корректно. + +--- + +## Порядок в реестре `test_runner.c` + +```c +static const test_module_t *const k_registry[] = { + &K_TEST_SDRAM, /* critical — первым */ + &K_TEST_QSPI, /* critical */ + &K_TEST_USD, /* non-critical, interactive, pre_confirm */ + &K_TEST_DISPLAY, /* non-critical, interactive, confirm в run() */ + &K_TEST_BUTTONS, /* non-critical, interactive, prompt only */ + &K_TEST_CAN, /* non-critical, HIL */ + &K_TEST_UART_TTL, /* non-critical, HIL */ + &K_TEST_UART_ISO, /* non-critical, HIL */ + &K_TEST_OPTO, /* non-critical, HIL */ +}; +``` + +Critical тесты идут первыми — при их провале HIL и интерактивные тесты +пропускаются автоматически, экономя время диагностики. diff --git a/firmware/test/src/tests/test_sdram.c b/firmware/test/src/tests/test_sdram.c new file mode 100644 index 0000000..76ce69d --- /dev/null +++ b/firmware/test/src/tests/test_sdram.c @@ -0,0 +1,377 @@ +/** + * @file test_sdram.c + * @brief Тест-модуль firmware_test: SDRAM MT48LC16M16A2 (32 МБ, 16-bit bus). + * + * Четыре фазы (~30 с): + * + * 1. Address bus — 24 степени двойки (2^0..2^23 от TEST_BASE), + * каждая с individual cache line flush. + * Покрывает все 24 адресных бита MT48LC16M16A2 + * (13 row + 9 col + 2 bank). Время: < 1 мс. + * + * 2. Data bus — walking ones + инверсия, 64 KB. + * Ловит stuck-at faults на всех 8 битах шины данных. + * Время: ~0.8 с. + * + * 3. Sequential — address pattern + инверсия, 2 MB. + * Ловит coupling faults между соседними ячейками. + * Время: ~24 с. + * + * 4. Retention — address pattern, 256 KB: запись → flush → 200 мс → verify. + * Ловит refresh timing failures. + * Время: ~2 с. + * + * Кэш: тот же подход что в рабочей версии — SCB_CleanDCache_by_Addr + DSB. + * USB: bsp_usb_cdc_poll() каждые SDRAM_USB_POLL_INTERVAL_BYTES. + */ + +#include "bsp/sdram.h" +#include "bsp/tick.h" +#include "bsp/usb_cdc.h" +#include "fsl_common.h" +#include "test_module.h" + +#include +#include +#include +#include +#include + +/* ── Константы ─────────────────────────────────────────────────────────── */ + +/** @brief Интервал вызова bsp_usb_cdc_poll() при записи/чтении (4 KB). */ +#define SDRAM_USB_POLL_INTERVAL_BYTES 0x1000U + +/** @brief Ширина walking ones паттерна, бит. */ +#define SDRAM_WALKING_ONES_BITS 8U + +/** + * @brief Количество адресных бит для address bus теста. + * + * MT48LC16M16A2: 13 row + 9 col + 2 bank = 24 бита. + * Тестируем 2^0..2^23 байтовых смещений от TEST_BASE. + * Максимальное смещение: 2^23 = 8 MB → 0x80200000 + 0x800000 = 0x80A00000 + * Граница SDRAM: 0x82000000. ✓ + */ +#define SDRAM_ADDR_BUS_BITS 24U + +/** @brief Размер кэш-линии Cortex-M7, байт. */ +#define SDRAM_CACHE_LINE_BYTES 32U + +/** + * @brief Размер sequential теста, байт (2 MB). + * + * Больший регион (vs 1 MB в оригинале) — лучше ловит coupling faults. + */ +#define SDRAM_SEQUENTIAL_SIZE 0x00200000UL + +/** + * @brief Размер retention теста, байт (256 KB). + * + * Достаточно для проверки refresh timing при 200 мс задержке. + */ +#define SDRAM_RETENTION_SIZE 0x00040000UL + +/** + * @brief Время удержания данных в retention тесте, мс. + * + * MT48LC16M16A2: период авто-refresh 64 мс. + * 200 мс ≈ 3 refresh-периода. + */ +#define SDRAM_RETENTION_DELAY_MS 200U + +/** @brief Шаг ожидания в retention тесте (для USB poll), мс. */ +#define SDRAM_RETENTION_POLL_STEP_MS 10U + +/* ── Локальные типы ────────────────────────────────────────────────────── */ + +/** + * @brief Информация о первой ошибке паттерн-прохода. + * + * fail_addr == 0 означает отсутствие ошибок. + */ +typedef struct +{ + uint32_t fail_addr; /**< Адрес первой ошибки. */ + uint8_t expected; /**< Ожидаемое значение. */ + uint8_t got; /**< Прочитанное значение. */ +} sdram_fail_info_t; + +/** @brief Функция-генератор одного байта тестового паттерна. */ +typedef uint8_t (*pattern_fn_t)(uint32_t offset); + +/* ── Состояние модуля ──────────────────────────────────────────────────── */ + +static bool g_s_ready = false; + +/* ── Паттерн-функции ───────────────────────────────────────────────────── */ + +static uint8_t pattern_walking_ones(uint32_t offset) +{ + return (uint8_t) (1U << (offset % SDRAM_WALKING_ONES_BITS)); +} + +static uint8_t pattern_walking_ones_inv(uint32_t offset) +{ + return (uint8_t) (~(1U << (offset % SDRAM_WALKING_ONES_BITS)) & 0xFFU); +} + +static uint8_t pattern_addr(uint32_t offset) +{ + return (uint8_t) (offset & 0xFFU); +} + +static uint8_t pattern_addr_inv(uint32_t offset) +{ + return (uint8_t) (~offset & 0xFFU); +} + +/* ── Вспомогательные функции ───────────────────────────────────────────── */ + +/** + * @brief Clean + Invalidate кэш для региона от TEST_BASE. + * + * Тот же подход что в рабочей версии — SCB_CleanDCache_by_Addr. + */ +static void flush_dcache(uint32_t size) +{ + uint32_t *const P_BASE = (uint32_t *) BSP_SDRAM_TEST_BASE_ADDR; + + SCB_CleanDCache_by_Addr(P_BASE, (int32_t) size); + SCB_InvalidateDCache_by_Addr(P_BASE, (int32_t) size); + __DSB(); +} + +/** + * @brief Clean + Invalidate одну кэш-линию по произвольному байтовому адресу. + * + * Используется в address bus тесте для точечного сброса после каждой записи. + * Адрес выравнивается на SDRAM_CACHE_LINE_BYTES автоматически. + * + * @param[in] byte_addr Любой байтовый адрес внутри нужной кэш-линии. + */ +static void flush_dcache_line_at(uint32_t byte_addr) +{ + uint32_t *const P_LINE = (uint32_t *) (byte_addr & ~(SDRAM_CACHE_LINE_BYTES - 1U)); + + SCB_CleanDCache_by_Addr(P_LINE, (int32_t) SDRAM_CACHE_LINE_BYTES); + SCB_InvalidateDCache_by_Addr(P_LINE, (int32_t) SDRAM_CACHE_LINE_BYTES); + __DSB(); +} + +/** + * @brief Ожидание с периодическим USB poll. + */ +static void delay_with_poll(uint32_t ms) +{ + const uint32_t START_MS = bsp_tick_get_ms(); + + while ((bsp_tick_get_ms() - START_MS) < ms) + { + bsp_delay(SDRAM_RETENTION_POLL_STEP_MS); + bsp_usb_cdc_poll(); + } +} + +/** + * @brief Записать паттерн в тестовый регион. + */ +static void write_pattern(uint32_t size, pattern_fn_t p_fn) +{ + volatile uint8_t *const P_BASE = (volatile uint8_t *) BSP_SDRAM_TEST_BASE_ADDR; + + for (uint32_t i = 0U; i < size; i++) + { + if ((i & (SDRAM_USB_POLL_INTERVAL_BYTES - 1U)) == 0U) + { + bsp_usb_cdc_poll(); + } + P_BASE[i] = p_fn(i); + } +} + +/** + * @brief Верифицировать паттерн. Останавливается на первом несовпадении. + */ +static bool verify_pattern(uint32_t size, pattern_fn_t p_fn, sdram_fail_info_t *p_fail) +{ + volatile const uint8_t *const P_BASE = (volatile const uint8_t *) BSP_SDRAM_TEST_BASE_ADDR; + + for (uint32_t i = 0U; i < size; i++) + { + if ((i & (SDRAM_USB_POLL_INTERVAL_BYTES - 1U)) == 0U) + { + bsp_usb_cdc_poll(); + } + + const uint8_t EXPECTED = p_fn(i); + const uint8_t GOT = P_BASE[i]; + + if (GOT != EXPECTED) + { + p_fail->fail_addr = BSP_SDRAM_TEST_BASE_ADDR + i; + p_fail->expected = EXPECTED; + p_fail->got = GOT; + return false; + } + } + return true; +} + +/** + * @brief Один паттерн-проход: запись → flush → верификация. + */ +static bool run_pass(uint32_t size, pattern_fn_t p_fn, sdram_fail_info_t *p_fail) +{ + write_pattern(size, p_fn); + flush_dcache(size); + return verify_pattern(size, p_fn, p_fail); +} + +/** + * @brief Сформировать FAIL-результат. + */ +static test_result_t make_fail_result(const sdram_fail_info_t *p_fail, uint32_t duration_ms) +{ + test_result_t result = { + .status = TEST_STATUS_FAIL, + .duration_ms = duration_ms, + }; + (void) snprintf(result.detail, TEST_DETAIL_SIZE, "addr=0x%08" PRIX32 " exp=0x%02X got=0x%02X", + p_fail->fail_addr, (unsigned int) p_fail->expected, (unsigned int) p_fail->got); + return result; +} + +/* ── Фазы теста ────────────────────────────────────────────────────────── */ + +/** + * @brief Фаза 1: шина адреса — 24 бита. + * + * Записывает уникальный байт (bit+1) в каждую из 24 позиций степеней двойки. + * После каждой записи сбрасывает только одну кэш-линию → мгновенно. + * Максимальное смещение: 2^23 = 8 MB → 0x80A00000 — внутри SDRAM. ✓ + */ +static bool run_phase_address_bus(sdram_fail_info_t *p_fail) +{ + volatile uint8_t *const P_BASE = (volatile uint8_t *) BSP_SDRAM_TEST_BASE_ADDR; + + for (uint32_t bit = 0U; bit < SDRAM_ADDR_BUS_BITS; bit++) + { + const uint32_t OFFSET = (1UL << bit); + const uint8_t PATTERN = (uint8_t) (bit + 1U); + + P_BASE[OFFSET] = PATTERN; + flush_dcache_line_at(BSP_SDRAM_TEST_BASE_ADDR + OFFSET); + + const uint8_t GOT = P_BASE[OFFSET]; + if (GOT != PATTERN) + { + p_fail->fail_addr = BSP_SDRAM_TEST_BASE_ADDR + OFFSET; + p_fail->expected = PATTERN; + p_fail->got = GOT; + return false; + } + } + return true; +} + +/** + * @brief Фаза 2: шина данных — 64 KB. + * + * Walking ones + инверсия. Ловит stuck-at faults на всех битах. + */ +static bool run_phase_data_bus(sdram_fail_info_t *p_fail) +{ + return run_pass(BSP_SDRAM_TEST_FAST_SIZE, pattern_walking_ones, p_fail) && + run_pass(BSP_SDRAM_TEST_FAST_SIZE, pattern_walking_ones_inv, p_fail); +} + +/** + * @brief Фаза 3: sequential integrity — 2 MB. + * + * Address pattern + инверсия. Ловит coupling faults между соседними ячейками. + */ +static bool run_phase_sequential(sdram_fail_info_t *p_fail) +{ + return run_pass(SDRAM_SEQUENTIAL_SIZE, pattern_addr, p_fail) && + run_pass(SDRAM_SEQUENTIAL_SIZE, pattern_addr_inv, p_fail); +} + +/** + * @brief Фаза 4: retention — 256 KB, 200 мс. + * + * Запись → flush → ожидание → верификация. + * Ловит refresh timing failures. + */ +static bool run_phase_retention(sdram_fail_info_t *p_fail) +{ + write_pattern(SDRAM_RETENTION_SIZE, pattern_addr); + flush_dcache(SDRAM_RETENTION_SIZE); + delay_with_poll(SDRAM_RETENTION_DELAY_MS); + return verify_pattern(SDRAM_RETENTION_SIZE, pattern_addr, p_fail); +} + +/* ── Реализация тест-модуля ────────────────────────────────────────────── */ + +static void sdram_test_init(void) +{ + g_s_ready = (bsp_sdram_init() == BSP_OK); +} + +static test_result_t sdram_test_run(void) +{ + if (!g_s_ready) + { + test_result_t result = { .status = TEST_STATUS_FAIL, .duration_ms = 0U }; + (void) snprintf(result.detail, TEST_DETAIL_SIZE, "%s", "SEMC not ready — DCD failed?"); + return result; + } + + const uint32_t START_MS = bsp_tick_get_ms(); + sdram_fail_info_t fail = { 0U, 0U, 0U }; + + bool is_ok = run_phase_address_bus(&fail); + if (is_ok) + { + is_ok = run_phase_data_bus(&fail); + } + if (is_ok) + { + is_ok = run_phase_sequential(&fail); + } + if (is_ok) + { + is_ok = run_phase_retention(&fail); + } + + const uint32_t DURATION_MS = bsp_tick_get_ms() - START_MS; + + if (!is_ok) + { + return make_fail_result(&fail, DURATION_MS); + } + + return (test_result_t){ + .status = TEST_STATUS_PASS, + .duration_ms = DURATION_MS, + .detail = { 0 }, + }; +} + +static void sdram_test_deinit(void) +{ + g_s_ready = false; +} + +/* ── Дескриптор модуля ─────────────────────────────────────────────────── */ + +const test_module_t K_TEST_SDRAM = { + .id = "sdram", + .name = "SDRAM 32 MB", + .critical = true, + .requires_hil = false, + .pre_confirm_prompt = NULL, + .init = sdram_test_init, + .run = sdram_test_run, + .deinit = sdram_test_deinit, +}; \ No newline at end of file