# Added: sdram service test

This commit is contained in:
Dmitry Akimov 2026-04-20 20:07:25 +03:00
parent bf324b2aa2
commit 0a45805350
10 changed files with 1035 additions and 6 deletions

View file

@ -16,6 +16,7 @@ add_subdirectory(opto)
add_subdirectory(can) add_subdirectory(can)
add_subdirectory(button) add_subdirectory(button)
add_subdirectory(usb_cdc) add_subdirectory(usb_cdc)
add_subdirectory(sdram)
# Подавляем предупреждения при компиляции собственных .c файлов библиотеки # Подавляем предупреждения при компиляции собственных .c файлов библиотеки
target_compile_options(bsp_board PRIVATE -w) target_compile_options(bsp_board PRIVATE -w)

25
bsp/sdram/CMakeLists.txt Normal file
View file

@ -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)

View file

@ -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); // ~25 с, 1 MB
* @endcode
*/
#ifndef BSP_SDRAM_H_
#define BSP_SDRAM_H_
#include "bsp/status.h"
#include <stdint.h>
/**
* @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_ */

View file

@ -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 <stdbool.h>
#include <stdint.h>
/* ── Константы ─────────────────────────────────────────────────────────── */
/** @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;
}

View file

@ -9,22 +9,22 @@ add_executable(
src/cli.c src/cli.c
src/protocol.c src/protocol.c
src/test_runner.c src/test_runner.c
src/tests/test_sdram.c
${BSP_GENERATED}/clock_config.c ${BSP_GENERATED}/clock_config.c
${BSP_STARTUP_FILE} ${BSP_STARTUP_FILE}
${BSP_SYSCALLS_FILE}) ${BSP_SYSCALLS_FILE})
target_include_directories(firmware_test PRIVATE src/) target_include_directories(firmware_test PRIVATE src/)
target_compile_definitions(${TARGET_NAME} target_compile_definitions(
PRIVATE BSP_UART_HOST_RX_BUFFER_SIZE=512) ${TARGET_NAME} PRIVATE BSP_UART_HOST_RX_BUFFER_SIZE=512 BOARD_MPU_SDRAM=1)
# #
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Зависимости — только то что нужно для входного контроля bsp_board транзитивно # Зависимости — только то что нужно для входного контроля bsp_board транзитивно
# даёт: sdk_device, sdk_clock, sdk_common, CPU_MIMXRT1052CVJ5B, XIP_* дефайны # даёт: sdk_device, sdk_clock, sdk_common, CPU_MIMXRT1052CVJ5B, XIP_* дефайны
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
target_link_libraries( target_link_libraries(${TARGET_NAME} PRIVATE bsp_board bsp_led bsp_tick
${TARGET_NAME} PRIVATE bsp_board bsp_led bsp_tick bsp_boot_xip bsp_status bsp_boot_xip bsp_usb_cdc bsp_sdram)
bsp_usb_cdc)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Linker script # Linker script

View file

@ -41,9 +41,10 @@
* */ * */
#ifndef UNIT_TEST #ifndef UNIT_TEST
extern const test_module_t K_TEST_SDRAM;
static const test_module_t *const k_registry[] = { static const test_module_t *const k_registry[] = {
/* populated starting from Этап 2 */ /* populated starting from Этап 2 */
&K_TEST_SDRAM,
}; };
#define REGISTRY_SIZE (sizeof(k_registry) / sizeof(k_registry[0])) #define REGISTRY_SIZE (sizeof(k_registry) / sizeof(k_registry[0]))

View file

@ -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 и интерактивные тесты
пропускаются автоматически, экономя время диагностики.

View file

@ -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 <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
/* ── Константы ─────────────────────────────────────────────────────────── */
/** @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,
};