# test_usd: done
This commit is contained in:
parent
10015af93a
commit
da48fbf75d
5 changed files with 396 additions and 349 deletions
|
|
@ -44,7 +44,7 @@
|
||||||
## Транспорт
|
## Транспорт
|
||||||
|
|
||||||
| Параметр | Значение |
|
| Параметр | Значение |
|
||||||
|---|---|
|
| ------------------------- | ------------------------------------------------------------ |
|
||||||
| Интерфейс | USB CDC ACM |
|
| Интерфейс | USB CDC ACM |
|
||||||
| Разъём | J2 |
|
| Разъём | J2 |
|
||||||
| Кодировка | UTF-8 |
|
| Кодировка | UTF-8 |
|
||||||
|
|
@ -181,7 +181,7 @@
|
||||||
```
|
```
|
||||||
|
|
||||||
| Поле | Тип | Описание |
|
| Поле | Тип | Описание |
|
||||||
|---|---|---|
|
| ----------- | ------ | -------------------------- |
|
||||||
| `fw` | string | Версия firmware_test |
|
| `fw` | string | Версия firmware_test |
|
||||||
| `target` | string | Идентификатор платформы |
|
| `target` | string | Идентификатор платформы |
|
||||||
| `uptime_ms` | number | Время с момента старта, мс |
|
| `uptime_ms` | number | Время с момента старта, мс |
|
||||||
|
|
@ -218,7 +218,7 @@
|
||||||
```
|
```
|
||||||
|
|
||||||
| `status` | Смысл |
|
| `status` | Смысл |
|
||||||
|---|---|
|
| -------- | ---------------------------------------------------------------------------- |
|
||||||
| `"pass"` | Тест пройден |
|
| `"pass"` | Тест пройден |
|
||||||
| `"fail"` | Тест провален; поле `detail` содержит описание |
|
| `"fail"` | Тест провален; поле `detail` содержит описание |
|
||||||
| `"skip"` | Тест пропущен (нет оборудования, таймаут оператора, прерван `critical` fail) |
|
| `"skip"` | Тест пропущен (нет оборудования, таймаут оператора, прерван `critical` fail) |
|
||||||
|
|
@ -295,7 +295,7 @@ display (подтвердить цвет), кнопки (нажать кнопк
|
||||||
```
|
```
|
||||||
|
|
||||||
| Код | Причина |
|
| Код | Причина |
|
||||||
|---|---|
|
| --------------- | ----------------------------------------------- |
|
||||||
| `PARSE_ERR` | Строка не является валидным JSON-lines запросом |
|
| `PARSE_ERR` | Строка не является валидным JSON-lines запросом |
|
||||||
| `UNKNOWN_CMD` | Поле `"cmd"` содержит неизвестное значение |
|
| `UNKNOWN_CMD` | Поле `"cmd"` содержит неизвестное значение |
|
||||||
| `UNKNOWN_TEST` | Поле `"id"` в `run` не найдено в реестре |
|
| `UNKNOWN_TEST` | Поле `"id"` в `run` не найдено в реестре |
|
||||||
|
|
@ -307,7 +307,7 @@ display (подтвердить цвет), кнопки (нажать кнопк
|
||||||
## Матрица тестов
|
## Матрица тестов
|
||||||
|
|
||||||
| ID | Название | Тип | Critical | HIL (M5) | Интерактивный |
|
| ID | Название | Тип | Critical | HIL (M5) | Интерактивный |
|
||||||
|---|---|---|---|---|---|
|
| ---------- | --------------------- | ------------------ | -------- | -------- | ------------------ |
|
||||||
| `sdram` | SDRAM 32 MB | self | ✅ | ❌ | ❌ |
|
| `sdram` | SDRAM 32 MB | self | ✅ | ❌ | ❌ |
|
||||||
| `qspi` | QSPI Flash 8 MB | self | ✅ | ❌ | ❌ |
|
| `qspi` | QSPI Flash 8 MB | self | ✅ | ❌ | ❌ |
|
||||||
| `usd` | uSD (SDIO) | self + interactive | ❌ | ❌ | ✅ (вставить карту) |
|
| `usd` | uSD (SDIO) | self + interactive | ❌ | ❌ | ✅ (вставить карту) |
|
||||||
|
|
@ -332,19 +332,31 @@ display (подтвердить цвет), кнопки (нажать кнопк
|
||||||
### uSD
|
### uSD
|
||||||
|
|
||||||
Карта вставляется оператором по запросу. Тест не входит в критический путь.
|
Карта вставляется оператором по запросу. Тест не входит в критический путь.
|
||||||
|
Pre-confirm обрабатывается `test_runner` до вызова `run()`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
← {"type":"confirm_request","id":"usd_insert","prompt":"Вставьте карту microSD и нажмите OK","timeout_ms":30000}
|
← {"type":"confirm_request","id":"usd","prompt":"Insert microSD card and press OK","timeout_ms":30000}
|
||||||
→ {"type":"confirm","id":"usd_insert","confirmed":true}
|
→ {"type":"confirm","id":"usd","confirmed":true}
|
||||||
← {"type":"test_begin","id":"usd",...}
|
← {"type":"test_begin","id":"usd","name":"microSD (SDIO)","critical":false}
|
||||||
[запись + верификация блока данных]
|
← {"type":"progress","test":"usd","step":"card_detect","status":"ok"}
|
||||||
← {"type":"test_result","id":"usd","status":"pass","ms":541,"detail":""}
|
← {"type":"progress","test":"usd","step":"mount","status":"ok"}
|
||||||
|
← {"type":"progress","test":"usd","step":"write","status":"ok"}
|
||||||
|
← {"type":"progress","test":"usd","step":"read_compare","status":"ok"}
|
||||||
|
← {"type":"test_result","id":"usd","status":"pass","ms":741,"detail":""}
|
||||||
```
|
```
|
||||||
|
|
||||||
Если оператор отказался (`"confirmed":false`) или истёк таймаут:
|
Если оператор отказался (`"confirmed":false`):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
← {"type":"test_result","id":"usd","status":"skip","ms":0,"detail":"operator skipped"}
|
← {"type":"test_begin","id":"usd","name":"microSD (SDIO)","critical":false}
|
||||||
|
← {"type":"test_result","id":"usd","status":"skip","ms":0,"detail":"operator declined"}
|
||||||
|
```
|
||||||
|
|
||||||
|
Если истёк таймаут (30 с без ответа):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
← {"type":"test_begin","id":"usd","name":"microSD (SDIO)","critical":false}
|
||||||
|
← {"type":"test_result","id":"usd","status":"skip","ms":0,"detail":"confirm timeout"}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -19,15 +19,15 @@
|
||||||
## Текущий статус
|
## Текущий статус
|
||||||
|
|
||||||
| Компонент | Статус | Примечание |
|
| Компонент | Статус | Примечание |
|
||||||
|----------------------------|--------|------------|
|
| -------------------------- | ------ | -------------------------------------- |
|
||||||
| `bsp_usb_cdc` | ✅ | HIL тест пройден |
|
| `bsp_usb_cdc` | ✅ | HIL тест пройден |
|
||||||
| firmware_test скелет | ✅ | `main.c` + `cli.c` |
|
| firmware_test скелет | ✅ | `main.c` + `cli.c` |
|
||||||
| Протокол v2 + test_runner | ✅ | JSON-lines event-driven |
|
| Протокол v2 + test_runner | ✅ | JSON-lines event-driven |
|
||||||
| `bsp_sdram` + `test_sdram` | ✅ | 4 фазы: addr/data/seq/retention |
|
| `bsp_sdram` + `test_sdram` | ✅ | 4 фазы: addr/data/seq/retention |
|
||||||
| `bsp_qspi_flash` | ✅ | W25Q64/128/256/512, ITCM, IRQ lock |
|
| `bsp_qspi_flash` | ✅ | W25Q64/128/256/512, ITCM, IRQ lock |
|
||||||
| `test_qspi` | ✅ | JEDEC + erase + rw + addr range |
|
| `test_qspi` | ✅ | JEDEC + erase + rw + addr range |
|
||||||
| `bsp_usd` | ⬜ | Этап 4 |
|
| `bsp_usd` | ✅ | bsp_sd + FatFS (firmware_test_fatfs) |
|
||||||
| `test_usd` | ⬜ | Этап 4 |
|
| `test_usd` | ✅ | pre_confirm + 4 шага + progress events |
|
||||||
| Display test | ⬜ | Этап 5 |
|
| Display test | ⬜ | Этап 5 |
|
||||||
| Button test | ⬜ | Этап 5 |
|
| Button test | ⬜ | Этап 5 |
|
||||||
| CAN test | ⬜ | Этап 6 (bsp_can ✅) |
|
| CAN test | ⬜ | Этап 6 (bsp_can ✅) |
|
||||||
|
|
@ -60,7 +60,7 @@
|
||||||
### Аппаратный контекст
|
### Аппаратный контекст
|
||||||
|
|
||||||
| Параметр | Значение |
|
| Параметр | Значение |
|
||||||
|---|---|
|
| ---------------- | -------------------------------------------- |
|
||||||
| Интерфейс | USDHC (SDIO) |
|
| Интерфейс | USDHC (SDIO) |
|
||||||
| Карта | microSD, вставляется оператором перед тестом |
|
| Карта | microSD, вставляется оператором перед тестом |
|
||||||
| Файловая система | FatFS (SDK middleware) |
|
| Файловая система | FatFS (SDK middleware) |
|
||||||
|
|
@ -88,7 +88,7 @@ void bsp_usd_deinit(void);
|
||||||
### test_usd — шаги
|
### test_usd — шаги
|
||||||
|
|
||||||
| Шаг | Действие | Время |
|
| Шаг | Действие | Время |
|
||||||
|---|---|---|
|
| ----------------- | ------------------------------ | -------- |
|
||||||
| 1: Card detect | `bsp_usd_is_card_present()` | < 1 мс |
|
| 1: Card detect | `bsp_usd_is_card_present()` | < 1 мс |
|
||||||
| 2: Mount | `f_mount()` — FAT/exFAT | < 200 мс |
|
| 2: Mount | `f_mount()` — FAT/exFAT | < 200 мс |
|
||||||
| 3: Write | Записать 4 KB тестовый файл | < 500 мс |
|
| 3: Write | Записать 4 KB тестовый файл | < 500 мс |
|
||||||
|
|
@ -123,12 +123,17 @@ target_link_libraries(bsp_usd
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Открытые вопросы перед реализацией
|
### Закрытые решения (Этап 4)
|
||||||
|
|
||||||
- [ ] Есть ли `sdk_usdhc` таргет в `sdk/CMakeLists.txt`?
|
- BSP-слой: `bsp_sd` (host init/deinit/card detect) + `firmware_test_fatfs` (FatFS).
|
||||||
- [ ] Карта вставлена постоянно или оператор вставляет каждый раз?
|
`bsp_usd` как отдельный модуль не создавался — тест работает напрямую через `bsp_sd` + `ff.h`.
|
||||||
- [ ] Нужен ли Card Detect GPIO или только USDHC status?
|
- Card detect: `bsp_sd_is_inserted()` через `USDHC_GetPresentStatusFlags`. GPIO-прерывание не используется.
|
||||||
- [ ] Файловая система: FAT32 или exFAT (размер карт)?
|
- Карта вставляется оператором по запросу (pre_confirm). Между тестами может извлекаться.
|
||||||
|
- Drive `2:/`. Тестовый файл `2:/FWTEST.TMP`, удаляется в любом исходе (через `deinit()`).
|
||||||
|
- Паттерн: `byte[i] = i & 0xFF`, 4096 байт.
|
||||||
|
- `critical = false`: тест не блокирует HIL-тесты при отсутствии карты.
|
||||||
|
- Confirm timeout: 30 000 мс (`PROTOCOL_CONFIRM_TIMEOUT_MS`).
|
||||||
|
- Отдельный `test_usd.h` не создавался — `extern K_TEST_USD` объявлен в `test_runner.c`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -137,7 +142,7 @@ target_link_libraries(bsp_usd
|
||||||
### test_display
|
### test_display
|
||||||
|
|
||||||
| Параметр | Значение |
|
| Параметр | Значение |
|
||||||
|---|---|
|
| -------- | ------------------------------------ |
|
||||||
| Critical | ❌ |
|
| Critical | ❌ |
|
||||||
| HIL | ❌ |
|
| HIL | ❌ |
|
||||||
| Confirm | Внутри `run()` — 4 отдельных confirm |
|
| Confirm | Внутри `run()` — 4 отдельных confirm |
|
||||||
|
|
@ -151,7 +156,7 @@ target_link_libraries(bsp_usd
|
||||||
### test_buttons
|
### test_buttons
|
||||||
|
|
||||||
| Параметр | Значение |
|
| Параметр | Значение |
|
||||||
|---|---|
|
| -------- | ------------------------------------- |
|
||||||
| Critical | ❌ |
|
| Critical | ❌ |
|
||||||
| HIL | ❌ |
|
| HIL | ❌ |
|
||||||
| Confirm | prompt only (детект через bsp_button) |
|
| Confirm | prompt only (детект через bsp_button) |
|
||||||
|
|
@ -163,7 +168,7 @@ target_link_libraries(bsp_usd
|
||||||
### Аппаратный контекст кнопок
|
### Аппаратный контекст кнопок
|
||||||
|
|
||||||
| Кнопка | Пин MCU | GPIO |
|
| Кнопка | Пин MCU | GPIO |
|
||||||
|---|---|---|
|
| ---------- | ---------- | --------- |
|
||||||
| Test_But_1 | GPIO_B1_14 | GPIO2[30] |
|
| Test_But_1 | GPIO_B1_14 | GPIO2[30] |
|
||||||
| Test_But_2 | GPIO_B1_15 | GPIO2[31] |
|
| Test_But_2 | GPIO_B1_15 | GPIO2[31] |
|
||||||
|
|
||||||
|
|
@ -246,15 +251,15 @@ bsp_status_t bsp_prov_read_uid(uint8_t *p_uid, size_t len); /* 8 байт из
|
||||||
## Матрица тестов — итоговая
|
## Матрица тестов — итоговая
|
||||||
|
|
||||||
| ID | Название | Тип | Critical | HIL (M5) | BSP | Статус |
|
| ID | Название | Тип | Critical | HIL (M5) | BSP | Статус |
|
||||||
|-------------|----------------|-------------|----------|----------|------------------|--------|
|
| ---------- | -------------- | ----------- | -------- | -------- | ----------------- | ------ |
|
||||||
| — | PING | cmd | — | ❌ | — | ✅ |
|
| — | PING | cmd | — | ❌ | — | ✅ |
|
||||||
| `sdram` | SDRAM 32MB | self | ✅ | ❌ | `bsp_sdram` ✅ | ✅ |
|
| `sdram` | SDRAM 32MB | self | ✅ | ❌ | `bsp_sdram` ✅ | ✅ |
|
||||||
| `qspi` | QSPI Flash | self | ✅ | ❌ | `bsp_qspi_flash` ✅ | ✅ |
|
| `qspi` | QSPI Flash | self | ✅ | ❌ | `bsp_qspi_flash`✅ | ✅ |
|
||||||
| `usd` | uSD (SDIO) | interactive | ✅ | ❌ | `bsp_usd` ⬜ | ⬜ |
|
| `usd` | uSD (SDIO) | interactive | ❌ | ❌ | `bsp_sd` ✅ | ✅ |
|
||||||
| `display` | Display RGB888 | interactive | ❌ | ❌ | существующий BSP | ⬜ |
|
| `display` | Display RGB888 | interactive | ❌ | ❌ | существующий BSP | ⬜ |
|
||||||
| `buttons` | Test_But_1/2 | interactive | ❌ | ❌ | `bsp_button` ✅ | ⬜ |
|
| `buttons` | Test_But_1/2 | interactive | ❌ | ❌ | `bsp_button` ✅ | ⬜ |
|
||||||
| `can` | CAN loopback | HIL | ❌ | ✅ | `bsp_can` ✅ | ⬜ |
|
| `can` | CAN loopback | HIL | ❌ | ✅ | `bsp_can` ✅ | ⬜ |
|
||||||
| `uart_ttl` | UART TTL | HIL | ❌ | ✅ | `bsp_uart_host`✅| ⬜ |
|
| `uart_ttl` | UART TTL | HIL | ❌ | ✅ | `bsp_uart_host`✅ | ⬜ |
|
||||||
| `uart_iso` | UART ISO +24V | HIL | ❌ | ✅ | `bsp_opto` ✅ | ⬜ |
|
| `uart_iso` | UART ISO +24V | HIL | ❌ | ✅ | `bsp_opto` ✅ | ⬜ |
|
||||||
| `opto` | Opto-in EXT | HIL | ❌ | ✅ | `bsp_opto` ✅ | ⬜ |
|
| `opto` | Opto-in EXT | HIL | ❌ | ✅ | `bsp_opto` ✅ | ⬜ |
|
||||||
|
|
||||||
|
|
@ -266,8 +271,8 @@ bsp_status_t bsp_prov_read_uid(uint8_t *p_uid, size_t len); /* 8 байт из
|
||||||
✅ Этап 1 (протокол v2 + runner)
|
✅ Этап 1 (протокол v2 + runner)
|
||||||
✅ Этап 2 (bsp_sdram + test_sdram)
|
✅ Этап 2 (bsp_sdram + test_sdram)
|
||||||
✅ Этап 3 (bsp_qspi_flash + test_qspi)
|
✅ Этап 3 (bsp_qspi_flash + test_qspi)
|
||||||
⬜ Этап 4 (bsp_usd + test_usd) ← ТЕКУЩИЙ
|
✅ Этап 4 (bsp_sd + test_usd)
|
||||||
⬜ Этап 5 (display + buttons)
|
⬜ Этап 5 (display + buttons) ← ТЕКУЩИЙ
|
||||||
⬜ Этап 6 (CAN + UART + Opto, HIL)
|
⬜ Этап 6 (CAN + UART + Opto, HIL)
|
||||||
⬜ Этап 7 (provisioning)
|
⬜ Этап 7 (provisioning)
|
||||||
⬜ Этап 8 (tools/production/ TUI runner) ← параллельно с 6-7
|
⬜ Этап 8 (tools/production/ TUI runner) ← параллельно с 6-7
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
#define FIRMWARE_TEST_VERSION "0.1.0"
|
#define FIRMWARE_TEST_VERSION "0.1.0"
|
||||||
|
|
||||||
/** @brief Таймаут подтверждения по умолчанию, мс. */
|
/** @brief Таймаут подтверждения по умолчанию, мс. */
|
||||||
#define PROTOCOL_CONFIRM_TIMEOUT_MS 15000U
|
#define PROTOCOL_CONFIRM_TIMEOUT_MS 30000U
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Отправить событие session_start.
|
* @brief Отправить событие session_start.
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
- [Как читать эту таблицу](#как-читать-эту-таблицу)
|
- [Как читать эту таблицу](#как-читать-эту-таблицу)
|
||||||
- [test_sdram — SDRAM 32 MB](#test_sdram--sdram-32-mb)
|
- [test_sdram — SDRAM 32 MB](#test_sdram--sdram-32-mb)
|
||||||
- [test_qspi — QSPI Flash W25Qxx](#test_qspi--qspi-flash-w25qxx)
|
- [test_qspi — QSPI Flash W25Qxx](#test_qspi--qspi-flash-w25qxx)
|
||||||
- [test_usd — uSD SDIO](#test_usd--usd-sdio) *(запланирован)*
|
- [test_usd — uSD SDIO](#test_usd--usd-sdio)
|
||||||
- [test_display — Display RGB888](#test_display--display-rgb888) *(запланирован)*
|
- [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_buttons — Test_But_1 / Test_But_2](#test_buttons--test_but_1--test_but_2) *(запланирован)*
|
||||||
- [test_can — CAN loopback](#test_can--can-loopback) *(запланирован)*
|
- [test_can — CAN loopback](#test_can--can-loopback) *(запланирован)*
|
||||||
|
|
@ -51,7 +51,7 @@
|
||||||
### Аппаратный контекст
|
### Аппаратный контекст
|
||||||
|
|
||||||
| Параметр | Значение |
|
| Параметр | Значение |
|
||||||
|---|---|
|
| --------------- | ------------------------------------------------- |
|
||||||
| Чип | MT48LC16M16A2 |
|
| Чип | MT48LC16M16A2 |
|
||||||
| Объём | 32 MB |
|
| Объём | 32 MB |
|
||||||
| Шина данных | 16 бит |
|
| Шина данных | 16 бит |
|
||||||
|
|
@ -159,7 +159,7 @@ Address pattern на 256 KB: запись → `flush_dcache` → ожидани
|
||||||
`detail` содержит: `addr=0xXXXXXXXX exp=0xXX got=0xXX`
|
`detail` содержит: `addr=0xXXXXXXXX exp=0xXX got=0xXX`
|
||||||
|
|
||||||
| Диапазон addr | Вероятная фаза | Диагноз |
|
| Диапазон addr | Вероятная фаза | Диагноз |
|
||||||
|---|---|---|
|
| -------------------------------------------- | -------------- | ------------------------------ |
|
||||||
| `0x80200000` — `0x80A00000` (степени двойки) | Фаза 1 | Address aliasing |
|
| `0x80200000` — `0x80A00000` (степени двойки) | Фаза 1 | Address aliasing |
|
||||||
| `0x80200000` — `0x80210000` | Фаза 2 | Stuck-at на шине данных |
|
| `0x80200000` — `0x80210000` | Фаза 2 | Stuck-at на шине данных |
|
||||||
| `0x80200000` — `0x80400000` | Фаза 3 | Coupling или деградация ячейки |
|
| `0x80200000` — `0x80400000` | Фаза 3 | Coupling или деградация ячейки |
|
||||||
|
|
@ -177,7 +177,7 @@ Address pattern на 256 KB: запись → `flush_dcache` → ожидани
|
||||||
### Типичное время выполнения
|
### Типичное время выполнения
|
||||||
|
|
||||||
| Фаза | ~Время |
|
| Фаза | ~Время |
|
||||||
|---|---|
|
| ----------- | --------- |
|
||||||
| Address bus | < 1 мс |
|
| Address bus | < 1 мс |
|
||||||
| Data bus | ~1 с |
|
| Data bus | ~1 с |
|
||||||
| Sequential | ~25 с |
|
| Sequential | ~25 с |
|
||||||
|
|
@ -195,7 +195,7 @@ Address pattern на 256 KB: запись → `flush_dcache` → ожидани
|
||||||
### Аппаратный контекст
|
### Аппаратный контекст
|
||||||
|
|
||||||
| Параметр | Значение |
|
| Параметр | Значение |
|
||||||
|---|---|
|
| --------------- | ------------------------------------------- |
|
||||||
| Чипы | W25Q64 / W25Q128 / W25Q256 / W25Q512 |
|
| Чипы | W25Q64 / W25Q128 / W25Q256 / W25Q512 |
|
||||||
| Объём | 8 / 16 / 32 / 64 MB |
|
| Объём | 8 / 16 / 32 / 64 MB |
|
||||||
| Интерфейс MCU | FlexSPI1, порт A1 |
|
| Интерфейс MCU | FlexSPI1, порт A1 |
|
||||||
|
|
@ -335,7 +335,7 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000
|
||||||
`detail` содержит текстовое описание шага и причины:
|
`detail` содержит текстовое описание шага и причины:
|
||||||
|
|
||||||
| Паттерн `detail` | Шаг | Вероятный диагноз |
|
| Паттерн `detail` | Шаг | Вероятный диагноз |
|
||||||
|---|---|---|
|
| -------------------------------- | --- | --------------------------------------------- |
|
||||||
| `JEDEC: read failed` | 1 | FlexSPI не инициализирован / нет связи |
|
| `JEDEC: read failed` | 1 | FlexSPI не инициализирован / нет связи |
|
||||||
| `JEDEC: mfr=0xXX exp=0xEF` | 1 | Неизвестный производитель чипа |
|
| `JEDEC: mfr=0xXX exp=0xEF` | 1 | Неизвестный производитель чипа |
|
||||||
| `JEDEC: unknown cap=0xXX` | 1 | Незнакомая ёмкость, чип не в списке поддержки |
|
| `JEDEC: unknown cap=0xXX` | 1 | Незнакомая ёмкость, чип не в списке поддержки |
|
||||||
|
|
@ -359,7 +359,7 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000
|
||||||
### Типичное время выполнения
|
### Типичное время выполнения
|
||||||
|
|
||||||
| Шаг | Чип | ~Время |
|
| Шаг | Чип | ~Время |
|
||||||
|---|---|---|
|
| ----------------- | ----------- | ----------- |
|
||||||
| 1: JEDEC ID | все | < 1 мс |
|
| 1: JEDEC ID | все | < 1 мс |
|
||||||
| 2: Erase + Verify | все | ~500 мс |
|
| 2: Erase + Verify | все | ~500 мс |
|
||||||
| 3: Write + Read | все | ~200 мс |
|
| 3: Write + Read | все | ~200 мс |
|
||||||
|
|
@ -372,28 +372,74 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000
|
||||||
|
|
||||||
## test_usd — uSD SDIO
|
## test_usd — uSD SDIO
|
||||||
|
|
||||||
**Файл:** `test_usd.c` *(не реализован — Этап 4)*
|
**Файл:** `test_usd.c`
|
||||||
**ID:** `usd`
|
**ID:** `usd`
|
||||||
**Critical:** ❌ | **HIL:** ❌ | **Confirm:** pre_confirm
|
**Critical:** ❌ | **HIL:** ❌ | **Confirm:** pre_confirm
|
||||||
|
|
||||||
### Аппаратный контекст
|
### Аппаратный контекст
|
||||||
|
|
||||||
| Параметр | Значение |
|
| Параметр | Значение |
|
||||||
|---|---|
|
| ----------------- | --------------------------------------------------------------- |
|
||||||
| Интерфейс | USDHC |
|
| Интерфейс MCU | USDHC1 |
|
||||||
| Файловая система | FatFS (SDK middleware) |
|
| CLK / CMD / D0–D3 | GPIO_SD_B0_00–05 |
|
||||||
| BSP | `bsp_usd` |
|
| Card Detect | GPIO_B1_12 → USDHC1_CD_B → `USDHC_GetPresentStatusFlags` |
|
||||||
| Pre-confirm prompt | "Вставьте карту microSD и нажмите OK" |
|
| Питание карты | GPIO_AD_B1_03 (SdPwr, active-low, управляется SDK) |
|
||||||
|
| Drive FatFS | `2:/` |
|
||||||
|
| BSP | `bsp_sd` (host init/deinit/card detect) + `firmware_test_fatfs` |
|
||||||
|
|
||||||
### Что будет тестировать
|
### Инварианты выполнения
|
||||||
|
|
||||||
- Mount FAT — карта вставлена и файловая система читаема.
|
- `bsp_sd_init()` вызывается в `init()`. При провале `run()` возвращает FAIL немедленно.
|
||||||
- Write тестового файла → Read → Compare (деструктивно для одного файла).
|
- Все ресурсы (файл, mount, host) освобождаются в `deinit()` — вызывается test_runner
|
||||||
- Unmount.
|
всегда, включая FAIL и SKIP.
|
||||||
|
- Тестовый файл `2:/FWTEST.TMP` удаляется при любом исходе.
|
||||||
|
- `bsp_usb_cdc_poll()` вызывается вокруг каждой блокирующей операции.
|
||||||
|
|
||||||
### Поведение без карты
|
### Что тестирует — четыре шага
|
||||||
|
|
||||||
Оператор отказался от pre_confirm или таймаут 30 с → `TEST_STATUS_SKIP`.
|
| Шаг | Действие | Progress event | ~Время |
|
||||||
|
| ------------ | ------------------------------------------ | ------------------- | -------- |
|
||||||
|
| card_detect | `bsp_sd_is_inserted()` | `step:card_detect` | < 1 мс |
|
||||||
|
| mount | `f_mount(&fs, "2:/", 1)` | `step:mount` | < 200 мс |
|
||||||
|
| write | `f_open` + `f_write` 4 KB + `f_close` | `step:write` | < 500 мс |
|
||||||
|
| read_compare | `f_open` + `f_read` + `memcmp` + `f_close` | `step:read_compare` | < 200 мс |
|
||||||
|
|
||||||
|
**Паттерн:** `byte[i] = i & 0xFF`, 4096 байт. Детектирует stuck-at-0/1 и partial write.
|
||||||
|
|
||||||
|
### Гарантии теста при PASS
|
||||||
|
|
||||||
|
- USDHC host инициализирован и карта подключена.
|
||||||
|
- FatFS корректно монтирует раздел (FAT32 / exFAT).
|
||||||
|
- Запись и чтение 4 KB совпадают побайтово.
|
||||||
|
- Тестовый файл удалён с карты.
|
||||||
|
|
||||||
|
### Интерпретация FAIL
|
||||||
|
|
||||||
|
| Паттерн `detail` | Причина |
|
||||||
|
| ---------------------------------------------- | ----------------------------------------------------- |
|
||||||
|
| `no card detected` | Карта не вставлена или CD не работает |
|
||||||
|
| `sd init failed` | USDHC host не инициализировался |
|
||||||
|
| `mount failed: N` | FatFS не может прочитать файловую систему (FR code N) |
|
||||||
|
| `open failed: N` | Нет места или файловая система только для чтения |
|
||||||
|
| `write failed: N` / `write incomplete: X/4096` | Ошибка записи на карту |
|
||||||
|
| `read failed: N` | Ошибка чтения |
|
||||||
|
| `compare failed at offset N` | Данные не совпадают — битый сектор карты |
|
||||||
|
|
||||||
|
### Поведение без карты / отказ оператора
|
||||||
|
|
||||||
|
- Оператор не ответил за 30 с → `TEST_STATUS_SKIP`, `detail: "confirm timeout"`.
|
||||||
|
- Оператор нажал "отказ" → `TEST_STATUS_SKIP`, `detail: "operator declined"`.
|
||||||
|
- Тест не critical → `run_all` продолжает выполнение остальных тестов.
|
||||||
|
|
||||||
|
### Типичное время выполнения
|
||||||
|
|
||||||
|
| Этап | ~Время |
|
||||||
|
| ------------------- | ----------- |
|
||||||
|
| SD init + card init | ~200 мс |
|
||||||
|
| Mount | ~100 мс |
|
||||||
|
| Write 4 KB | ~400 мс |
|
||||||
|
| Read + Compare 4 KB | ~150 мс |
|
||||||
|
| **Итого** | **~850 мс** |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -408,7 +454,7 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000
|
||||||
Четыре шага с визуальным подтверждением оператора:
|
Четыре шага с визуальным подтверждением оператора:
|
||||||
|
|
||||||
| Шаг | ID confirm | Промпт | Таймаут |
|
| Шаг | ID confirm | Промпт | Таймаут |
|
||||||
|---|---|---|---|
|
| ------- | --------------- | ---------------------- | ------- |
|
||||||
| Красный | `display_red` | "Экран залит красным?" | 15 с |
|
| Красный | `display_red` | "Экран залит красным?" | 15 с |
|
||||||
| Зелёный | `display_green` | "Экран залит зелёным?" | 15 с |
|
| Зелёный | `display_green` | "Экран залит зелёным?" | 15 с |
|
||||||
| Синий | `display_blue` | "Экран залит синим?" | 15 с |
|
| Синий | `display_blue` | "Экран залит синим?" | 15 с |
|
||||||
|
|
@ -437,7 +483,7 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000
|
||||||
### Аппаратный контекст
|
### Аппаратный контекст
|
||||||
|
|
||||||
| Кнопка | Пин MCU | GPIO |
|
| Кнопка | Пин MCU | GPIO |
|
||||||
|---|---|---|
|
| ---------- | ---------- | --------- |
|
||||||
| Test_But_1 | GPIO_B1_14 | GPIO2[30] |
|
| Test_But_1 | GPIO_B1_14 | GPIO2[30] |
|
||||||
| Test_But_2 | GPIO_B1_15 | GPIO2[31] |
|
| Test_But_2 | GPIO_B1_15 | GPIO2[31] |
|
||||||
|
|
||||||
|
|
@ -447,7 +493,7 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000
|
||||||
и детектирует нажатие через `bsp_button` — JSON confirm не нужен.
|
и детектирует нажатие через `bsp_button` — JSON confirm не нужен.
|
||||||
|
|
||||||
| Шаг | ID | Промпт | Таймаут |
|
| Шаг | ID | Промпт | Таймаут |
|
||||||
|---|---|---|---|
|
| -------- | ------------ | --------------------------- | ------- |
|
||||||
| Кнопка 1 | `btn1_press` | "Нажмите кнопку Test_But_1" | 10 с |
|
| Кнопка 1 | `btn1_press` | "Нажмите кнопку Test_But_1" | 10 с |
|
||||||
| Кнопка 2 | `btn2_press` | "Нажмите кнопку Test_But_2" | 10 с |
|
| Кнопка 2 | `btn2_press` | "Нажмите кнопку Test_But_2" | 10 с |
|
||||||
|
|
||||||
|
|
@ -467,7 +513,7 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000
|
||||||
### Аппаратный контекст
|
### Аппаратный контекст
|
||||||
|
|
||||||
| Параметр | Значение |
|
| Параметр | Значение |
|
||||||
|---|---|
|
| -------- | ----------------------------------- |
|
||||||
| BSP | `bsp_can` ✅ |
|
| BSP | `bsp_can` ✅ |
|
||||||
| M5 | CAN интерфейсная плата → шина платы |
|
| M5 | CAN интерфейсная плата → шина платы |
|
||||||
|
|
||||||
|
|
@ -491,7 +537,7 @@ M5StampPLC отправляет CAN фрейм → плата принимает
|
||||||
### Аппаратный контекст
|
### Аппаратный контекст
|
||||||
|
|
||||||
| Параметр | Значение |
|
| Параметр | Значение |
|
||||||
|---|---|
|
| -------- | --------------------- |
|
||||||
| BSP | `bsp_uart_host` ✅ |
|
| BSP | `bsp_uart_host` ✅ |
|
||||||
| M5 | UART ↔ UART TTL платы |
|
| M5 | UART ↔ UART TTL платы |
|
||||||
|
|
||||||
|
|
@ -510,7 +556,7 @@ M5 отправляет пакет → плата получает → echo об
|
||||||
### Аппаратный контекст
|
### Аппаратный контекст
|
||||||
|
|
||||||
| Параметр | Значение |
|
| Параметр | Значение |
|
||||||
|---|---|
|
| -------- | --------------------------------------- |
|
||||||
| BSP | `bsp_opto` (rs_as_gpio=true) ✅ |
|
| BSP | `bsp_opto` (rs_as_gpio=true) ✅ |
|
||||||
| Пин MCU | GPIO_AD_B1_07 / GPIO1[23] |
|
| Пин MCU | GPIO_AD_B1_07 / GPIO1[23] |
|
||||||
| Оптопара | PS2801-4 (неинвертирующая, active-HIGH) |
|
| Оптопара | PS2801-4 (неинвертирующая, active-HIGH) |
|
||||||
|
|
@ -537,7 +583,7 @@ M5 RLY2 деактивирует → плата детектирует inactive.
|
||||||
### Аппаратный контекст
|
### Аппаратный контекст
|
||||||
|
|
||||||
| Канал | Пин MCU | GPIO | Оптопара | M5 |
|
| Канал | Пин MCU | GPIO | Оптопара | M5 |
|
||||||
|---|---|---|---|---|
|
| ------- | ------------- | --------- | -------- | ---- |
|
||||||
| EXT_IN1 | GPIO_AD_B1_06 | GPIO1[22] | PS2801-4 | RLY3 |
|
| EXT_IN1 | GPIO_AD_B1_06 | GPIO1[22] | PS2801-4 | RLY3 |
|
||||||
| EXT_IN2 | GPIO_AD_B1_05 | GPIO1[21] | PS2801-4 | RLY4 |
|
| EXT_IN2 | GPIO_AD_B1_05 | GPIO1[21] | PS2801-4 | RLY4 |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,30 +2,21 @@
|
||||||
* @file test_usd.c
|
* @file test_usd.c
|
||||||
* @brief Тест-модуль firmware_test: microSD (USDHC / FatFS).
|
* @brief Тест-модуль firmware_test: microSD (USDHC / FatFS).
|
||||||
*
|
*
|
||||||
* Пять шагов:
|
* Жизненный цикл управляется test_runner:
|
||||||
|
* init() — bsp_sd_init(): инициализация USDHC host.
|
||||||
|
* run() — card detect → mount → write → read/compare → unmount.
|
||||||
|
* deinit() — safety-net: закрыть файл, отмонтировать, bsp_sd_deinit().
|
||||||
|
* Вызывается test_runner даже при FAIL run().
|
||||||
*
|
*
|
||||||
* Шаг 1 — Card detect (~1 мс)
|
* Паттерн: byte[i] = i & 0xFF, 4096 байт.
|
||||||
* bsp_sd_is_inserted(). Карта должна быть вставлена оператором —
|
* Покрывает: stuck-at-0, stuck-at-1, partial write, address aliasing.
|
||||||
* pre_confirm_prompt отрабатывает до вызова run() на уровне test_runner.
|
|
||||||
*
|
*
|
||||||
* Шаг 2 — SD init (~200 мс)
|
* Буферы g_s_write_buf / g_s_read_buf — статические: стек firmware_test
|
||||||
* bsp_sd_init(): инициализация USDHC host и подключение к карте.
|
* составляет 4 KB (linker script), 4 KB-буферы на него не влезают.
|
||||||
*
|
*
|
||||||
* Шаг 3 — Mount (~200 мс)
|
* Состояние монтирования (g_s_mounted) и открытого файла (g_s_file_open)
|
||||||
* f_mount(&g_s_fs, "2:/", 1): монтирование FatFS раздела.
|
* отслеживается на уровне модуля — deinit() корректно освобождает ресурсы
|
||||||
*
|
* в любом сценарии провала.
|
||||||
* Шаг 4 — Write (~500 мс)
|
|
||||||
* Паттерн byte[i] = i & 0xFF, 4096 байт.
|
|
||||||
* Создаём FWTEST.TMP, записываем, закрываем.
|
|
||||||
*
|
|
||||||
* Шаг 5 — Read + Compare (~200 мс)
|
|
||||||
* Открываем FWTEST.TMP на чтение, читаем 4096 байт, сравниваем побайтово.
|
|
||||||
*
|
|
||||||
* Cleanup (unlink + unmount + deinit) выполняется во всех исходах через
|
|
||||||
* usd_cleanup(). Тестовый файл FWTEST.TMP удаляется независимо от результата.
|
|
||||||
*
|
|
||||||
* @note Буферы 4 KB + FatFS-объекты — статические. На стек не ложатся:
|
|
||||||
* стек firmware_test = 4 KB (linker script ram.ld).
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "bsp/sd.h"
|
#include "bsp/sd.h"
|
||||||
|
|
@ -35,36 +26,45 @@
|
||||||
#include "test_module.h"
|
#include "test_module.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
/* ── Константы ─────────────────────────────────────────────────────────── */
|
/* ── Константы ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
/** @brief Размер тестового файла, байт (покрывает stuck-at и partial write). */
|
/** @brief Размер тестового файла, байт. */
|
||||||
#define USD_TEST_SIZE 4096U
|
#define USD_TEST_SIZE 4096U
|
||||||
|
|
||||||
/** @brief Точка монтирования FatFS (drive 2, задан в firmware_test_fatfs). */
|
/** @brief Точка монтирования FatFS (drive 2). */
|
||||||
#define USD_MOUNT_POINT "2:/"
|
#define USD_MOUNT_POINT "2:/"
|
||||||
|
|
||||||
/** @brief Путь временного тестового файла. */
|
/** @brief Путь тестового файла. Удаляется после теста в любом исходе. */
|
||||||
#define USD_TEST_FILE "2:/FWTEST.TMP"
|
#define USD_TEST_FILE "2:/FWTEST.TMP"
|
||||||
|
|
||||||
/* ── Статические объекты FatFS и буферы ────────────────────────────────── */
|
/* ── Статические объекты FatFS ──────────────────────────────────────────── */
|
||||||
|
|
||||||
/** @brief Рабочая область FatFS. Не на стеке — sizeof(FATFS) ≈ 516 байт. */
|
/** @brief Рабочая область FatFS. sizeof(FATFS) ≈ 516 байт — не на стеке. */
|
||||||
static FATFS g_s_fs;
|
static FATFS g_s_fs;
|
||||||
|
|
||||||
/** @brief Дескриптор открытого файла. */
|
/** @brief Дескриптор открытого файла. */
|
||||||
static FIL g_s_file;
|
static FIL g_s_file;
|
||||||
|
|
||||||
/** @brief Буфер тестового паттерна (запись). */
|
/* ── Буферы ─────────────────────────────────────────────────────────────── */
|
||||||
static uint8_t g_s_write_buf[USD_TEST_SIZE];
|
|
||||||
|
|
||||||
/** @brief Буфер прочитанных данных (верификация). */
|
static uint8_t g_s_write_buf[USD_TEST_SIZE];
|
||||||
static uint8_t g_s_read_buf[USD_TEST_SIZE];
|
static uint8_t g_s_read_buf[USD_TEST_SIZE];
|
||||||
|
|
||||||
|
/* ── Состояние модуля ───────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/** @brief true если bsp_sd_init() прошёл успешно. */
|
||||||
|
static bool g_s_ready;
|
||||||
|
|
||||||
|
/** @brief true если FatFS смонтирована (f_mount выполнен). */
|
||||||
|
static bool g_s_mounted;
|
||||||
|
|
||||||
|
/** @brief true если g_s_file открыт и требует f_close. */
|
||||||
|
static bool g_s_file_open;
|
||||||
|
|
||||||
/* ── Вспомогательные функции ────────────────────────────────────────────── */
|
/* ── Вспомогательные функции ────────────────────────────────────────────── */
|
||||||
|
|
||||||
static test_result_t make_fail(const char *p_detail)
|
static test_result_t make_fail(const char *p_detail)
|
||||||
|
|
@ -74,34 +74,13 @@ static test_result_t make_fail(const char *p_detail)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Освободить ресурсы: закрыть файл, удалить тестовый файл,
|
|
||||||
* отмонтировать, deинициализировать SD host.
|
|
||||||
*
|
|
||||||
* Best-effort: коды ошибок FatFS игнорируются.
|
|
||||||
* Безопасно вызывать в любом сочетании флагов.
|
|
||||||
*
|
|
||||||
* @param file_open true → дескриптор g_s_file открыт и требует f_close.
|
|
||||||
* @param mounted true → FatFS смонтирован, выполнить f_unlink + f_unmount.
|
|
||||||
*/
|
|
||||||
static void usd_cleanup(bool file_open, bool mounted)
|
|
||||||
{
|
|
||||||
if (file_open)
|
|
||||||
{
|
|
||||||
(void) f_close(&g_s_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mounted)
|
|
||||||
{
|
|
||||||
(void) f_unlink(USD_TEST_FILE);
|
|
||||||
(void) f_unmount(USD_MOUNT_POINT);
|
|
||||||
}
|
|
||||||
|
|
||||||
(void) bsp_sd_deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Шаг 1: Card detect ─────────────────────────────────────────────────── */
|
/* ── Шаг 1: Card detect ─────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Проверить наличие карты через аппаратный регистр USDHC.
|
||||||
|
*
|
||||||
|
* Вызывается после bsp_sd_init() — host уже инициализирован.
|
||||||
|
*/
|
||||||
static bool step_card_detect(test_result_t *p_out)
|
static bool step_card_detect(test_result_t *p_out)
|
||||||
{
|
{
|
||||||
bsp_usb_cdc_poll();
|
bsp_usb_cdc_poll();
|
||||||
|
|
@ -117,62 +96,42 @@ static bool step_card_detect(test_result_t *p_out)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Шаг 2: SD host init ────────────────────────────────────────────────── */
|
/* ── Шаг 2: Mount ───────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
static bool step_sd_init(test_result_t *p_out)
|
|
||||||
{
|
|
||||||
bsp_usb_cdc_poll();
|
|
||||||
|
|
||||||
if (bsp_sd_init() != BSP_OK)
|
|
||||||
{
|
|
||||||
*p_out = make_fail("sd init failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cli_send("{\"type\":\"progress\",\"test\":\"usd\","
|
|
||||||
"\"step\":\"init\",\"status\":\"ok\"}\n");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Шаг 3: Mount ───────────────────────────────────────────────────────── */
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Смонтировать FatFS раздел. Устанавливает g_s_mounted = true при успехе.
|
||||||
|
*/
|
||||||
static bool step_mount(test_result_t *p_out)
|
static bool step_mount(test_result_t *p_out)
|
||||||
{
|
{
|
||||||
bsp_usb_cdc_poll();
|
bsp_usb_cdc_poll();
|
||||||
|
|
||||||
FRESULT result = f_mount(&g_s_fs, USD_MOUNT_POINT, 1);
|
FRESULT fr = f_mount(&g_s_fs, USD_MOUNT_POINT, 1);
|
||||||
|
|
||||||
if (result != FR_OK)
|
if (fr != FR_OK)
|
||||||
{
|
{
|
||||||
char detail[TEST_DETAIL_SIZE];
|
char detail[TEST_DETAIL_SIZE];
|
||||||
(void) snprintf(detail, sizeof(detail), "mount failed: %d", (int) result);
|
(void) snprintf(detail, sizeof(detail), "mount failed: %d", (int) fr);
|
||||||
*p_out = make_fail(detail);
|
*p_out = make_fail(detail);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_s_mounted = true;
|
||||||
|
|
||||||
cli_send("{\"type\":\"progress\",\"test\":\"usd\","
|
cli_send("{\"type\":\"progress\",\"test\":\"usd\","
|
||||||
"\"step\":\"mount\",\"status\":\"ok\"}\n");
|
"\"step\":\"mount\",\"status\":\"ok\"}\n");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Шаг 4: Write ───────────────────────────────────────────────────────── */
|
/* ── Шаг 3: Write ───────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Заполнить паттерн, создать файл FWTEST.TMP, записать USD_TEST_SIZE байт.
|
* @brief Записать тестовый паттерн в FWTEST.TMP.
|
||||||
*
|
*
|
||||||
* При ошибке записи файл остаётся открытым — вызывающая сторона обязана
|
* Отслеживает g_s_file_open: при провале файл может оставаться открытым —
|
||||||
* вызвать usd_cleanup(true, true). Состояние сообщается через p_file_left_open.
|
* deinit() закроет его через f_close.
|
||||||
*
|
|
||||||
* При успехе файл закрыт до возврата.
|
|
||||||
*
|
|
||||||
* @param[out] p_file_left_open true если g_s_file открыт при возврате false.
|
|
||||||
* @param[out] p_out Описание ошибки при возврате false.
|
|
||||||
* @return true при успехе.
|
|
||||||
*/
|
*/
|
||||||
static bool step_write(bool *p_file_left_open, test_result_t *p_out)
|
static bool step_write(test_result_t *p_out)
|
||||||
{
|
{
|
||||||
*p_file_left_open = false;
|
|
||||||
|
|
||||||
for (uint32_t i = 0U; i < USD_TEST_SIZE; i++)
|
for (uint32_t i = 0U; i < USD_TEST_SIZE; i++)
|
||||||
{
|
{
|
||||||
g_s_write_buf[i] = (uint8_t) (i & 0xFFU);
|
g_s_write_buf[i] = (uint8_t) (i & 0xFFU);
|
||||||
|
|
@ -180,48 +139,46 @@ static bool step_write(bool *p_file_left_open, test_result_t *p_out)
|
||||||
|
|
||||||
bsp_usb_cdc_poll();
|
bsp_usb_cdc_poll();
|
||||||
|
|
||||||
FRESULT result = f_open(&g_s_file, USD_TEST_FILE, FA_WRITE | FA_CREATE_ALWAYS);
|
FRESULT fr = f_open(&g_s_file, USD_TEST_FILE, FA_WRITE | FA_CREATE_ALWAYS);
|
||||||
|
|
||||||
if (result != FR_OK)
|
if (fr != FR_OK)
|
||||||
{
|
{
|
||||||
char detail[TEST_DETAIL_SIZE];
|
char detail[TEST_DETAIL_SIZE];
|
||||||
(void) snprintf(detail, sizeof(detail), "open failed: %d", (int) result);
|
(void) snprintf(detail, sizeof(detail), "open failed: %d", (int) fr);
|
||||||
*p_out = make_fail(detail);
|
*p_out = make_fail(detail);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*p_file_left_open = true;
|
g_s_file_open = true;
|
||||||
|
|
||||||
UINT bytes_written = 0U;
|
|
||||||
|
|
||||||
result = f_write(&g_s_file, g_s_write_buf, USD_TEST_SIZE, &bytes_written);
|
|
||||||
|
|
||||||
|
UINT bw = 0U;
|
||||||
|
fr = f_write(&g_s_file, g_s_write_buf, USD_TEST_SIZE, &bw);
|
||||||
bsp_usb_cdc_poll();
|
bsp_usb_cdc_poll();
|
||||||
|
|
||||||
if (result != FR_OK || bytes_written != USD_TEST_SIZE)
|
if (fr != FR_OK || bw != USD_TEST_SIZE)
|
||||||
{
|
{
|
||||||
char detail[TEST_DETAIL_SIZE];
|
char detail[TEST_DETAIL_SIZE];
|
||||||
if (result != FR_OK)
|
if (fr != FR_OK)
|
||||||
{
|
{
|
||||||
(void) snprintf(detail, sizeof(detail), "write failed: %d", (int) result);
|
(void) snprintf(detail, sizeof(detail), "write failed: %d", (int) fr);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
(void) snprintf(detail, sizeof(detail), "write incomplete: %u/%u",
|
(void) snprintf(detail, sizeof(detail), "write incomplete: %u/%u", (unsigned int) bw,
|
||||||
(unsigned int) bytes_written, (unsigned int) USD_TEST_SIZE);
|
(unsigned int) USD_TEST_SIZE);
|
||||||
}
|
}
|
||||||
*p_out = make_fail(detail);
|
*p_out = make_fail(detail);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = f_close(&g_s_file);
|
fr = f_close(&g_s_file);
|
||||||
*p_file_left_open = false;
|
g_s_file_open = false; /* сброс независимо от результата f_close */
|
||||||
bsp_usb_cdc_poll();
|
bsp_usb_cdc_poll();
|
||||||
|
|
||||||
if (result != FR_OK)
|
if (fr != FR_OK)
|
||||||
{
|
{
|
||||||
char detail[TEST_DETAIL_SIZE];
|
char detail[TEST_DETAIL_SIZE];
|
||||||
(void) snprintf(detail, sizeof(detail), "close after write failed: %d", (int) result);
|
(void) snprintf(detail, sizeof(detail), "close after write failed: %d", (int) fr);
|
||||||
*p_out = make_fail(detail);
|
*p_out = make_fail(detail);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -231,59 +188,51 @@ static bool step_write(bool *p_file_left_open, test_result_t *p_out)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Шаг 5: Read + Compare ──────────────────────────────────────────────── */
|
/* ── Шаг 4: Read + Compare ──────────────────────────────────────────────── */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Открыть FWTEST.TMP на чтение, прочитать USD_TEST_SIZE байт,
|
* @brief Прочитать FWTEST.TMP и сравнить с g_s_write_buf побайтово.
|
||||||
* сравнить с g_s_write_buf побайтово.
|
|
||||||
*
|
*
|
||||||
* При ошибке до f_close файл может оставаться открытым — состояние
|
* Отслеживает g_s_file_open аналогично step_write.
|
||||||
* передаётся через p_file_left_open. При успехе файл закрыт до возврата.
|
|
||||||
*
|
|
||||||
* @param[out] p_file_left_open true если g_s_file открыт при возврате false.
|
|
||||||
* @param[out] p_out Описание ошибки при возврате false.
|
|
||||||
* @return true при успехе.
|
|
||||||
*/
|
*/
|
||||||
static bool step_read_compare(bool *p_file_left_open, test_result_t *p_out)
|
static bool step_read_compare(test_result_t *p_out)
|
||||||
{
|
{
|
||||||
*p_file_left_open = false;
|
|
||||||
|
|
||||||
bsp_usb_cdc_poll();
|
bsp_usb_cdc_poll();
|
||||||
|
|
||||||
FRESULT result = f_open(&g_s_file, USD_TEST_FILE, FA_READ);
|
FRESULT fr = f_open(&g_s_file, USD_TEST_FILE, FA_READ);
|
||||||
|
|
||||||
if (result != FR_OK)
|
if (fr != FR_OK)
|
||||||
{
|
{
|
||||||
char detail[TEST_DETAIL_SIZE];
|
char detail[TEST_DETAIL_SIZE];
|
||||||
(void) snprintf(detail, sizeof(detail), "open for read failed: %d", (int) result);
|
(void) snprintf(detail, sizeof(detail), "open for read failed: %d", (int) fr);
|
||||||
*p_out = make_fail(detail);
|
*p_out = make_fail(detail);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*p_file_left_open = true;
|
g_s_file_open = true;
|
||||||
|
|
||||||
(void) memset(g_s_read_buf, 0, USD_TEST_SIZE);
|
(void) memset(g_s_read_buf, 0, sizeof(g_s_read_buf));
|
||||||
|
|
||||||
UINT bytes_read = 0U;
|
UINT br = 0U;
|
||||||
result = f_read(&g_s_file, g_s_read_buf, USD_TEST_SIZE, &bytes_read);
|
fr = f_read(&g_s_file, g_s_read_buf, USD_TEST_SIZE, &br);
|
||||||
bsp_usb_cdc_poll();
|
bsp_usb_cdc_poll();
|
||||||
|
|
||||||
if (result != FR_OK || bytes_read != USD_TEST_SIZE)
|
if (fr != FR_OK || br != USD_TEST_SIZE)
|
||||||
{
|
{
|
||||||
char detail[TEST_DETAIL_SIZE];
|
char detail[TEST_DETAIL_SIZE];
|
||||||
(void) snprintf(detail, sizeof(detail), "read failed: %d", (int) result);
|
(void) snprintf(detail, sizeof(detail), "read failed: %d", (int) fr);
|
||||||
*p_out = make_fail(detail);
|
*p_out = make_fail(detail);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = f_close(&g_s_file);
|
fr = f_close(&g_s_file);
|
||||||
*p_file_left_open = false;
|
g_s_file_open = false; /* сброс независимо от результата f_close */
|
||||||
bsp_usb_cdc_poll();
|
bsp_usb_cdc_poll();
|
||||||
|
|
||||||
if (result != FR_OK)
|
if (fr != FR_OK)
|
||||||
{
|
{
|
||||||
char detail[TEST_DETAIL_SIZE];
|
char detail[TEST_DETAIL_SIZE];
|
||||||
(void) snprintf(detail, sizeof(detail), "close after read failed: %d", (int) result);
|
(void) snprintf(detail, sizeof(detail), "close after read failed: %d", (int) fr);
|
||||||
*p_out = make_fail(detail);
|
*p_out = make_fail(detail);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -307,46 +256,52 @@ static bool step_read_compare(bool *p_file_left_open, test_result_t *p_out)
|
||||||
|
|
||||||
/* ── Реализация тест-модуля ─────────────────────────────────────────────── */
|
/* ── Реализация тест-модуля ─────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Инициализация: запустить USDHC host.
|
||||||
|
*
|
||||||
|
* Вызывается test_runner после pre_confirm (карта уже вставлена оператором).
|
||||||
|
* g_s_ready = false при провале → run() вернёт FAIL немедленно.
|
||||||
|
*/
|
||||||
|
static void usd_init(void)
|
||||||
|
{
|
||||||
|
g_s_ready = false;
|
||||||
|
g_s_mounted = false;
|
||||||
|
g_s_file_open = false;
|
||||||
|
g_s_ready = (bsp_sd_init() == BSP_OK);
|
||||||
|
}
|
||||||
|
|
||||||
static test_result_t usd_run(void)
|
static test_result_t usd_run(void)
|
||||||
{
|
{
|
||||||
test_result_t fail_result = { .status = TEST_STATUS_FAIL, .duration_ms = 0U, .detail = { 0 } };
|
if (!g_s_ready)
|
||||||
bool file_open = false;
|
{
|
||||||
|
return make_fail("sd init failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
test_result_t fail_result = { .status = TEST_STATUS_FAIL, .duration_ms = 0U, .detail = { 0 } };
|
||||||
|
|
||||||
/* Шаг 1: проверка карты (bsp_sd_init ещё не вызван → без cleanup) */
|
|
||||||
if (!step_card_detect(&fail_result))
|
if (!step_card_detect(&fail_result))
|
||||||
{
|
{
|
||||||
return fail_result;
|
return fail_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Шаг 2: init (mount ещё не вызван → только deinit при провале) */
|
|
||||||
if (!step_sd_init(&fail_result))
|
|
||||||
{
|
|
||||||
(void) bsp_sd_deinit();
|
|
||||||
return fail_result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Шаг 3: mount */
|
|
||||||
if (!step_mount(&fail_result))
|
if (!step_mount(&fail_result))
|
||||||
{
|
{
|
||||||
(void) bsp_sd_deinit();
|
|
||||||
return fail_result;
|
return fail_result;
|
||||||
}
|
}
|
||||||
|
if (!step_write(&fail_result))
|
||||||
/* Шаги 4-5: write + read/compare (файловая система смонтирована) */
|
|
||||||
if (!step_write(&file_open, &fail_result))
|
|
||||||
{
|
{
|
||||||
usd_cleanup(file_open, true);
|
|
||||||
return fail_result;
|
return fail_result;
|
||||||
}
|
}
|
||||||
|
if (!step_read_compare(&fail_result))
|
||||||
if (!step_read_compare(&file_open, &fail_result))
|
|
||||||
{
|
{
|
||||||
usd_cleanup(file_open, true);
|
|
||||||
return fail_result;
|
return fail_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Успех: файл закрыт внутри step_read_compare, раздел ещё смонтирован */
|
/* Успех: файл закрыт внутри step_read_compare.
|
||||||
usd_cleanup(false, true);
|
* Удаляем тестовый файл и отмонтируем до возврата —
|
||||||
|
* deinit() проверит g_s_mounted и пропустит повторное unmount. */
|
||||||
|
(void) f_unlink(USD_TEST_FILE);
|
||||||
|
(void) f_unmount(USD_MOUNT_POINT);
|
||||||
|
g_s_mounted = false;
|
||||||
|
|
||||||
return (test_result_t){
|
return (test_result_t){
|
||||||
.status = TEST_STATUS_PASS,
|
.status = TEST_STATUS_PASS,
|
||||||
|
|
@ -355,6 +310,35 @@ static test_result_t usd_run(void)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Safety-net cleanup: закрыть файл, отмонтировать, остановить host.
|
||||||
|
*
|
||||||
|
* Вызывается test_runner всегда — после PASS и после FAIL.
|
||||||
|
* При PASS все флаги уже сброшены в run() — функция завершается быстро.
|
||||||
|
* При FAIL освобождает ресурсы в любом состоянии провала.
|
||||||
|
*/
|
||||||
|
static void usd_deinit(void)
|
||||||
|
{
|
||||||
|
if (g_s_file_open)
|
||||||
|
{
|
||||||
|
(void) f_close(&g_s_file);
|
||||||
|
g_s_file_open = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_s_mounted)
|
||||||
|
{
|
||||||
|
(void) f_unlink(USD_TEST_FILE);
|
||||||
|
(void) f_unmount(USD_MOUNT_POINT);
|
||||||
|
g_s_mounted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_s_ready)
|
||||||
|
{
|
||||||
|
(void) bsp_sd_deinit();
|
||||||
|
g_s_ready = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Дескриптор модуля ──────────────────────────────────────────────────── */
|
/* ── Дескриптор модуля ──────────────────────────────────────────────────── */
|
||||||
|
|
||||||
const test_module_t K_TEST_USD = {
|
const test_module_t K_TEST_USD = {
|
||||||
|
|
@ -362,8 +346,8 @@ const test_module_t K_TEST_USD = {
|
||||||
.name = "microSD (SDIO)",
|
.name = "microSD (SDIO)",
|
||||||
.critical = false,
|
.critical = false,
|
||||||
.requires_hil = false,
|
.requires_hil = false,
|
||||||
.pre_confirm_prompt = "Вставьте карту microSD и нажмите OK",
|
.pre_confirm_prompt = "Insert microSD card and press OK",
|
||||||
.init = NULL,
|
.init = usd_init,
|
||||||
.run = usd_run,
|
.run = usd_run,
|
||||||
.deinit = NULL,
|
.deinit = usd_deinit,
|
||||||
};
|
};
|
||||||
Loading…
Reference in a new issue