From 8d85ffe87787e1ff7cb83b9e8e10c5b03b0506e5 Mon Sep 17 00:00:00 2001 From: Ezra Maccabee Date: Mon, 25 May 2026 18:19:55 +0300 Subject: [PATCH] # test_display: finished --- Justfile | 10 +- bsp/display/README.md | 54 +++--- bsp/display/include/bsp/display.h | 8 +- bsp/display/src/display.c | 6 +- firmware/test/src/cli.c | 51 ++++-- firmware/test/src/tests/README.md | 223 ++++++++++++++++++------- firmware/test/src/tests/test_display.c | 19 ++- just/build.just | 2 +- just/ci.just | 2 +- just/host.just | 12 +- utils/log/README.md | 34 ++-- utils/prio_queue/README.md | 28 ++-- utils/ring_buffer/README.md | 10 +- 13 files changed, 289 insertions(+), 170 deletions(-) diff --git a/Justfile b/Justfile index 9574dda..90ff511 100644 --- a/Justfile +++ b/Justfile @@ -32,7 +32,7 @@ CACHE_DIR := justfile_directory() / '.cache' mod host 'just/host.just' # build.just — сборка в DevContainer mod build 'just/build.just' -# ci.just — CI/CD сценарии (опционально) +# ci.just — CI/CD сценарии mod ci 'just/ci.just' # === Default рецепт === @@ -42,17 +42,11 @@ mod ci 'just/ci.just' default: @just --list --list-submodules -# === Популярные алиасы (для удобства команды) === -# Сокращают длинные вызовы модулей - +# === Популярные алиасы === [doc('Первичная настройка окружения после git clone (на хосте)')] init: @just host::bootstrap -[doc('Прошить firmware_test Debug во Flash через USB ROM')] -flash: - @just host::flash-test-debug - [doc('Собрать все HAB-образы в Release')] build-all: @just build::hab-all-release diff --git a/bsp/display/README.md b/bsp/display/README.md index 969c752..104c712 100644 --- a/bsp/display/README.md +++ b/bsp/display/README.md @@ -13,28 +13,28 @@ ## Аппаратный контекст -| Сигнал / параметр | Аппаратное назначение | -| ----------------- | ---------------------------------------------------------------- | +| Сигнал / параметр | Аппаратное назначение | +| ----------------- | ------------------------------------------------------------------------------------------------ | | ELCDIF | NXP ELCDIF, RGB-режим, формат пикселя `kELCDIF_PixelFormatXRGB8888`, шина `kELCDIF_DataBus24Bit` | -| Подсветка | `GPIO1[20]` — active-high | -| LR (горизонт.) | `GPIO1[28]` (`Lcdlr_value`) | -| MODE | `GPIO1[29]` — HIGH = DE mode (обязательно для ELCDIF) | -| UD (вертикаль) | `GPIO1[30]` | -| DITHB | `GPIO1[31]` — HIGH = dithering disable (IC default) | -| Пиксельный клок | PLL2 (`mux=0`) для TFT7/TFT8; Video PLL (`mux=2`) для TFT4 | -| IRQ | `LCDIF_IRQHandler` в ITCM, приоритет `DISPLAY_IRQ_PRIORITY = 2` | +| Подсветка | `GPIO1[20]` — active-high | +| LR (горизонт.) | `GPIO1[28]` (`Lcdlr_value`) | +| MODE | `GPIO1[29]` — HIGH = DE mode (обязательно для ELCDIF) | +| UD (вертикаль) | `GPIO1[30]` | +| DITHB | `GPIO1[31]` — HIGH = dithering disable (IC default) | +| Пиксельный клок | PLL2 (`mux=0`) для TFT7/TFT8; Video PLL (`mux=2`) для TFT4 | +| IRQ | `LCDIF_IRQHandler` в ITCM, приоритет `DISPLAY_IRQ_PRIORITY = 2` | `IOMUXC` конфигурируется в `BOARD_InitPins()` за пределами модуля — здесь выполняется только `GPIO_PinWrite`. Делители пиксельного клока (исходный код, PLL2 = 528 МГц): -| Дисплей | clk_mux | pre_div | div | Эффективная частота | -| ------- | ------------- | ------- | --- | ------------------- | -| TFT7 | PLL2 | 2 | 4 | `528/3/5 = 35.2 МГц`| -| TFT8 | PLL2 | 2 | 3 | `528/3/4 = 44.0 МГц`| -| TFT4 | Video PLL | — (TODO)| — | требует `CLOCK_InitVideoPll` | -| TFT10 | — | — | — | таблица не заполнена (`{ 0 }`) | +| Дисплей | clk_mux | pre_div | div | Эффективная частота | +| ------- | --------- | -------- | --- | ------------------------------ | +| TFT7 | PLL2 | 2 | 4 | `528/3/5 = 35.2 МГц` | +| TFT8 | PLL2 | 2 | 3 | `528/3/4 = 44.0 МГц` | +| TFT4 | Video PLL | — (TODO) | — | требует `CLOCK_InitVideoPll` | +| TFT10 | — | — | — | таблица не заполнена (`{ 0 }`) | Тайминги HSW/HFP/HBP/VSW/VFP/VBP заданы константами в `display.c` (`DISPLAY_TFT7_*`, `DISPLAY_TFT8_*`, `DISPLAY_TFT4_*`). @@ -43,7 +43,7 @@ ## Состав модуля -``` +```bash bsp/display/ ├── include/bsp/display.h # публичный заголовок ├── src/display.c # реализация API + LCDIF_IRQHandler @@ -129,11 +129,11 @@ bsp_display_type_t bsp_display_get_type(void); Коды возврата: -| Код | Когда | -| ---------------------- | ----------------------------------------------------------- | -| `BSP_OK` | Дисплей инициализирован (или уже был инициализирован). | -| `BSP_ERR_PARAM` | `type >= BSP_DISPLAY_COUNT`. | -| `BSP_ERR_NOT_SUPPORTED`| `type` требует Video PLL (TFT4) — `init_pixelclock` возвращает ошибку до реализации `CLOCK_InitVideoPll`. | +| Код | Когда | +| ----------------------- | --------------------------------------------------------------------------------------------------------- | +| `BSP_OK` | Дисплей инициализирован (или уже был инициализирован). | +| `BSP_ERR_PARAM` | `type >= BSP_DISPLAY_COUNT`. | +| `BSP_ERR_NOT_SUPPORTED` | `type` требует Video PLL (TFT4) — `init_pixelclock` возвращает ошибку до реализации `CLOCK_InitVideoPll`. | > Поведение для `BSP_DISPLAY_TFT10` целостно не описано в коде: запись в > `K_HW_CFG[BSP_DISPLAY_TFT10]` сделана как `{ 0 }`. Использовать TFT10 как @@ -156,12 +156,12 @@ bsp_display_type_t bsp_display_get_type(void); Соответствие rotation → LR/UD (из `display.c`): -| Rotation | LR | UD | -| ---------------------- | -- | -- | -| `BSP_DISPLAY_ROTATE_0` | 1 | 0 | -| `BSP_DISPLAY_ROTATE_90`| 1 | 1 | -| `BSP_DISPLAY_ROTATE_180`| 0 | 1 | -| `BSP_DISPLAY_ROTATE_270`| 0 | 0 | +| Rotation | LR | UD | +| ----------------------------- | --- | --- | +| `BSP_DISPLAY_ROTATE_0` | 1 | 0 | +| `BSP_DISPLAY_FLIP_VERTICAL` | 1 | 1 | +| `BSP_DISPLAY_FLIP_BOTH` | 0 | 1 | +| `BSP_DISPLAY_FLIP_HORIZONTAL` | 0 | 0 | ### `bsp_display_set_next_buffer` diff --git a/bsp/display/include/bsp/display.h b/bsp/display/include/bsp/display.h index 6058930..03395a9 100644 --- a/bsp/display/include/bsp/display.h +++ b/bsp/display/include/bsp/display.h @@ -37,10 +37,10 @@ typedef enum bsp_display_type_e typedef enum bsp_display_rotation_e { - BSP_DISPLAY_ROTATE_0 = 0U, /**< Горизонтальная (нормальная). LR=1 UD=0. */ - BSP_DISPLAY_ROTATE_90, /**< Вертикальная CW 90°. LR=1 UD=1. */ - BSP_DISPLAY_ROTATE_180, /**< Горизонтальная перевёрнутая. LR=0 UD=1. */ - BSP_DISPLAY_ROTATE_270, /**< Вертикальная CCW 90°. LR=0 UD=0. */ + BSP_DISPLAY_ROTATE_0 = 0U, /**< LR=1 UD=0. Нормальная ориентация. */ + BSP_DISPLAY_FLIP_VERTICAL, /**< LR=1 UD=1. Вертикальный флип (UPDN). */ + BSP_DISPLAY_FLIP_BOTH, /**< LR=0 UD=1. Горизонтальный + вертикальный. */ + BSP_DISPLAY_FLIP_HORIZONTAL, /**< LR=0 UD=0. Горизонтальный флип (SHLR). */ } bsp_display_rotation_t; /* ── Размер дисплея ──────────────────────────────────────────────────── */ diff --git a/bsp/display/src/display.c b/bsp/display/src/display.c index 513945e..7c10d94 100644 --- a/bsp/display/src/display.c +++ b/bsp/display/src/display.c @@ -377,15 +377,15 @@ bsp_status_t bsp_display_set_rotation(bsp_display_rotation_t rotation) lr_value = 1U; ud_value = 0U; break; - case BSP_DISPLAY_ROTATE_90: + case BSP_DISPLAY_FLIP_VERTICAL: lr_value = 1U; ud_value = 1U; break; - case BSP_DISPLAY_ROTATE_180: + case BSP_DISPLAY_FLIP_BOTH: lr_value = 0U; ud_value = 1U; break; - case BSP_DISPLAY_ROTATE_270: + case BSP_DISPLAY_FLIP_HORIZONTAL: lr_value = 0U; ud_value = 0U; break; diff --git a/firmware/test/src/cli.c b/firmware/test/src/cli.c index 90e33fe..d398984 100644 --- a/firmware/test/src/cli.c +++ b/firmware/test/src/cli.c @@ -37,6 +37,11 @@ static const char K_FIELD_CMD[] = "\"cmd\""; static const char K_FIELD_ID[] = "\"id\""; static const char K_FIELD_CONFIRMED[] = "\"confirmed\""; +/** @brief Буфер непрочитанного остатка chunk после вызова process_line(). */ +static uint8_t g_s_chunk_buf[CLI_LINE_BUF_SIZE]; +static size_t g_s_chunk_len = 0U; +static size_t g_s_chunk_pos = 0U; + /* ── RX line buffer ────────────────────────────────────────────────────── */ static uint8_t g_s_line_buf[CLI_LINE_BUF_SIZE]; @@ -282,7 +287,9 @@ static void process_line(const char *p_line) void cli_init(void) { - g_s_line_len = 0U; + g_s_line_len = 0U; + g_s_chunk_len = 0U; + g_s_chunk_pos = 0U; } void cli_send(const char *p_resp) @@ -292,41 +299,49 @@ void cli_send(const char *p_resp) void cli_process(void) { - uint8_t chunk[CLI_LINE_BUF_SIZE]; - size_t nbytes = bsp_usb_cdc_read(chunk, sizeof(chunk)); - - for (size_t byte_idx = 0U; byte_idx < nbytes; byte_idx++) + /* Если в g_s_chunk_buf есть остаток от предыдущего вызова — + * продолжаем его обработку. Иначе читаем новый chunk. + * + * Это решает проблему потери confirm при блокирующем dispatch_test: + * confirm приходит в chunk вместе с командой run, но process_line() + * уходит в блокировку не дочитав chunk до конца. При следующем вызове + * cli_process() (из inner polling loop в test_runner_wait_confirm) + * мы продолжаем обработку остатка chunk, уже после arm_confirm(). + */ + if (g_s_chunk_pos >= g_s_chunk_len) { - uint8_t byte = chunk[byte_idx]; + g_s_chunk_len = bsp_usb_cdc_read(g_s_chunk_buf, sizeof(g_s_chunk_buf)); + g_s_chunk_pos = 0U; + } + + while (g_s_chunk_pos < g_s_chunk_len) + { + uint8_t byte = g_s_chunk_buf[g_s_chunk_pos]; + g_s_chunk_pos++; if (g_s_line_len >= (CLI_LINE_BUF_SIZE - 1U)) { g_s_line_len = 0U; protocol_send_error("LINE_TOO_LONG"); - continue; + return; } if (byte == (uint8_t) '\n') { - /* Отбросить '\r' если терминал шлёт CR+LF */ if (g_s_line_len > 0U && g_s_line_buf[g_s_line_len - 1U] == (uint8_t) '\r') { g_s_line_len--; } - g_s_line_buf[g_s_line_len] = '\0'; - - if (g_s_line_len > 0U) + size_t completed_len = g_s_line_len; + g_s_line_len = 0U; /* ← сбросить ДО process_line */ + if (completed_len > 0U) { process_line((const char *) g_s_line_buf); } + } - g_s_line_len = 0U; - } - else - { - g_s_line_buf[g_s_line_len] = byte; - g_s_line_len++; - } + g_s_line_buf[g_s_line_len] = byte; + g_s_line_len++; } } \ No newline at end of file diff --git a/firmware/test/src/tests/README.md b/firmware/test/src/tests/README.md index ce7db8e..3fdd261 100644 --- a/firmware/test/src/tests/README.md +++ b/firmware/test/src/tests/README.md @@ -9,13 +9,13 @@ --- -## Содержание +## 1 Содержание - [Как читать эту таблицу](#как-читать-эту-таблицу) - [test_sdram — SDRAM 32 MB](#test_sdram--sdram-32-mb) - [test_qspi — QSPI Flash W25Qxx](#test_qspi--qspi-flash-w25qxx) - [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_can — CAN loopback](#test_can--can-loopback) *(запланирован)* - [test_uart_ttl — UART TTL](#test_uart_ttl--uart-ttl) *(запланирован)* @@ -24,7 +24,7 @@ --- -## Как читать эту таблицу +## 2 Как читать эту таблицу **Critical** — при провале в `run_all` все последующие тесты получают `SKIP`. Некритические тесты могут упасть без остановки прогона. @@ -42,13 +42,13 @@ --- -## test_sdram — SDRAM 32 MB +## 3 test_sdram — SDRAM 32 MB **Файл:** `test_sdram.c` **ID:** `sdram` **Critical:** ✅ | **HIL:** ❌ | **Confirm:** — -### Аппаратный контекст +### 3.1 Аппаратный контекст | Параметр | Значение | | --------------- | ------------------------------------------------- | @@ -60,7 +60,7 @@ | Тестовый регион | 0x80200000 — 0x80A00000 | | MPU | Region 8: Normal WB cacheable (BOARD_MPU_SDRAM=1) | -### Инварианты выполнения +### 3.2 Инварианты выполнения - SEMC инициализирован DCD **до** `main()` — `bsp_sdram_init()` только верифицирует. - MPU Region 8 (`Normal WB`) активен — кэш включён для SDRAM. @@ -70,9 +70,9 @@ - Сброс кэша (`SCB_CleanDCache_by_Addr`) выполняется **после каждого** write-прохода — readback всегда из физической SDRAM, не из кэша. -### Что тестирует — четыре фазы +### 3.3 Что тестирует — четыре фазы -#### Фаза 1: Address bus (~1 мс) +#### 3.3.1 Фаза 1: Address bus (~1 мс) Записывает уникальный байт в 24 позиции на степенях двойки (2^0..2^23 от TEST_BASE), каждую с индивидуальным cache line flush. @@ -90,7 +90,7 @@ --- -#### Фаза 2: Data bus (~1 с) +#### 3.3.2 Фаза 2: Data bus (~1 с) Walking ones (0x01, 0x02, ..., 0x80, 0x01, ...) и его инверсия на регионе 64 KB. @@ -108,7 +108,7 @@ Walking ones (0x01, 0x02, ..., 0x80, 0x01, ...) и его инверсия --- -#### Фаза 3: Sequential integrity (~25 с) +#### 3.3.3 Фаза 3: Sequential integrity (~25 с) Address pattern (`offset & 0xFF`) и его инверсия на регионе 2 MB, два паттерна × два прохода (write → flush → verify). @@ -127,7 +127,7 @@ Address pattern (`offset & 0xFF`) и его инверсия на регионе --- -#### Фаза 4: Retention (~2 с) +#### 3.3.4Фаза 4: Retention (~2 с) Address pattern на 256 KB: запись → `flush_dcache` → ожидание 200 мс → верификация. @@ -146,7 +146,7 @@ Address pattern на 256 KB: запись → `flush_dcache` → ожидани --- -### Гарантии теста при PASS +### 3.4Гарантии теста при PASS - Все 24 адресных бита работают независимо без aliasing. - Все 8 бит шины данных переключаются корректно. @@ -154,7 +154,7 @@ Address pattern на 256 KB: запись → `flush_dcache` → ожидани - 256 KB удерживают данные минимум через 3 refresh-цикла. - SEMC контроллер инициализирован и отвечает. -### Интерпретация FAIL +### 3.5 Интерпретация FAIL `detail` содержит: `addr=0xXXXXXXXX exp=0xXX got=0xXX` @@ -167,14 +167,14 @@ Address pattern на 256 KB: запись → `flush_dcache` → ожидани **Анализ `exp` XOR `got`:** биты где `(exp ^ got) != 0` — сбойные линии шины данных. -### Ограничения +### 3.6 Ограничения - Не покрывает все 32 MB (только 2 MB для sequential). - Не проверяет retention при нагреве или низком напряжении питания. - Не является заменой полного March C− алгоритма. - Во время теста (~28 с) USB CDC занят, `ping` не отвечает. -### Типичное время выполнения +### 3.7 Типичное время выполнения | Фаза | ~Время | | ----------- | --------- | @@ -186,13 +186,13 @@ Address pattern на 256 KB: запись → `flush_dcache` → ожидани --- -## test_qspi — QSPI Flash W25Qxx +## 4 test_qspi — QSPI Flash W25Qxx **Файл:** `test_qspi.c` **ID:** `qspi` **Critical:** ✅ | **HIL:** ❌ | **Confirm:** — -### Аппаратный контекст +### 4.1 Аппаратный контекст | Параметр | Значение | | --------------- | ------------------------------------------- | @@ -202,7 +202,7 @@ Address pattern на 256 KB: запись → `flush_dcache` → ожидани | BSP | `bsp_qspi` | | Тестовый сектор | `flash_size - 4KB` (последний, динамически) | -### Инварианты выполнения +### 4.2Инварианты выполнения - `bsp_qspi_init()` вызывается в `init()` — если чип не опознан, `run()` возвращает FAIL. - Слот 0 FlexSPI LUT (XIP read, задан FDCB) **никогда не изменяется** — прошивка @@ -215,9 +215,9 @@ Address pattern на 256 KB: запись → `flush_dcache` → ожидани - `bsp_usb_cdc_poll()` вызывается вокруг каждой блокирующей операции — USB CDC остаётся отзывчивым во время теста. -### Что тестирует — четыре шага +### 4.3 Что тестирует — четыре шага -#### Шаг 1: JEDEC ID (~1 мс) +#### 4.3.1 Шаг 1: JEDEC ID (~1 мс) Команда 0x9F. Читает manufacturer ID и device ID. @@ -242,7 +242,7 @@ Address pattern на 256 KB: запись → `flush_dcache` → ожидани --- -#### Шаг 2: Erase + Verify (~500 мс) +#### 4.3.2 Шаг 2: Erase + Verify (~500 мс) Стирает тестовый сектор (4KB). Читает первые 256 байт сектора. Все байты должны быть `0xFF`. @@ -261,7 +261,7 @@ Address pattern на 256 KB: запись → `flush_dcache` → ожидани --- -#### Шаг 3: Write + Read + Compare (~200 мс) +#### 4.3.3 Шаг 3: Write + Read + Compare (~200 мс) Паттерн `byte[i] = i & 0xFF` (256 байт). Записывает первую страницу тестового сектора командой Quad Page Program. Читает обратно командой @@ -283,7 +283,7 @@ IP Quad Output Read (слот 11). Сравнивает побайтово. --- -#### Шаг 4: Address range — только W25Q256/512 (~100 мс) +#### 4.3.4 Шаг 4: Address range — только W25Q256/512 (~100 мс) Для чипов с `flash_size > 16 MB`. Для W25Q64/128 шаг пропускается (24-bit покрывает весь чип). @@ -293,7 +293,7 @@ IP Quad Output Read (слот 11). Сравнивает побайтово. При 24-bit адресации адрес обрезается до 24 бит. Для W25Q256/512 last-sector находится выше 16 MB: -``` +```bash W25Q256: last sector = 0x1FFF000, 0x1FFF000 & 0xFFFFFF = 0xFFF000 W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000 ``` @@ -322,7 +322,7 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000 --- -### Гарантии теста при PASS +### 4.4 Гарантии теста при PASS - FlexSPI1 инициализирован и отвечает. - Подключён поддерживаемый Winbond Flash с ожидаемым JEDEC ID. @@ -330,7 +330,7 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000 - Для W25Q256/512: dedicated 4-byte address opcodes функционируют и адресуют физически разные секторы выше и ниже 16 MB. -### Интерпретация FAIL +### 4.5 Интерпретация FAIL `detail` содержит текстовое описание шага и причины: @@ -349,14 +349,14 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000 **Анализ `rw mismatch`:** `exp XOR got` — биты, где `(exp ^ got) != 0`, соответствуют сбойным линиям Quad-шины. -### Ограничения +### 4.6 Ограничения - Тестирует только **1 страницу** (256 байт) — не покрывает весь объём чипа. - Не проверяет endurance (многократные write/erase циклы). - Не является заменой полного тестирования Flash (march-алгоритмы). - Шаг 4 не проверяет адреса в диапазоне 16–32 MB (только крайние точки). -### Типичное время выполнения +### 4.7 Типичное время выполнения | Шаг | Чип | ~Время | | ----------------- | ----------- | ----------- | @@ -370,13 +370,13 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000 --- -## test_usd — uSD SDIO +## 5 test_usd — uSD SDIO **Файл:** `test_usd.c` **ID:** `usd` **Critical:** ❌ | **HIL:** ❌ | **Confirm:** pre_confirm -### Аппаратный контекст +### 5.1 Аппаратный контекст | Параметр | Значение | | ----------------- | --------------------------------------------------------------- | @@ -387,7 +387,7 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000 | Drive FatFS | `2:/` | | BSP | `bsp_sd` (host init/deinit/card detect) + `firmware_test_fatfs` | -### Инварианты выполнения +### 5.2 Инварианты выполнения - `bsp_sd_init()` вызывается в `init()`. При провале `run()` возвращает FAIL немедленно. - Все ресурсы (файл, mount, host) освобождаются в `deinit()` — вызывается test_runner @@ -395,7 +395,7 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000 - Тестовый файл `2:/FWTEST.TMP` удаляется при любом исходе. - `bsp_usb_cdc_poll()` вызывается вокруг каждой блокирующей операции. -### Что тестирует — четыре шага +### 5.3 Что тестирует — четыре шага | Шаг | Действие | Progress event | ~Время | | ------------ | ------------------------------------------ | ------------------- | -------- | @@ -406,14 +406,14 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000 **Паттерн:** `byte[i] = i & 0xFF`, 4096 байт. Детектирует stuck-at-0/1 и partial write. -### Гарантии теста при PASS +### 5.4 Гарантии теста при PASS - USDHC host инициализирован и карта подключена. - FatFS корректно монтирует раздел (FAT32 / exFAT). - Запись и чтение 4 KB совпадают побайтово. - Тестовый файл удалён с карты. -### Интерпретация FAIL +### 5.5 Интерпретация FAIL | Паттерн `detail` | Причина | | ---------------------------------------------- | ----------------------------------------------------- | @@ -425,13 +425,13 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000 | `read failed: N` | Ошибка чтения | | `compare failed at offset N` | Данные не совпадают — битый сектор карты | -### Поведение без карты / отказ оператора +### 5.6 Поведение без карты / отказ оператора - Оператор не ответил за 30 с → `TEST_STATUS_SKIP`, `detail: "confirm timeout"`. - Оператор нажал "отказ" → `TEST_STATUS_SKIP`, `detail: "operator declined"`. - Тест не critical → `run_all` продолжает выполнение остальных тестов. -### Типичное время выполнения +### 5.7 Типичное время выполнения | Этап | ~Время | | ------------------- | ----------- | @@ -443,44 +443,155 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000 --- -## test_display — Display RGB888 +## 6 test_display — TFT-дисплей RGB888 -**Файл:** `test_display.c` *(не реализован — Этап 5)* +**Файл:** `test_display.c` **ID:** `display` **Critical:** ❌ | **HIL:** ❌ | **Confirm:** в run() -### Что будет тестировать +### 6.1 Аппаратный контекст -Четыре шага с визуальным подтверждением оператора: +| Параметр | Значение | +| -------------- | --------------------------------------------------- | +| Контроллер | ELCDIF (RGB888, DE mode) | +| Дисплейный IC | HX8264-D02 (TFT7/TFT8/TFT10) | +| Фреймбуфер | NonCacheable SDRAM, `AT_NONCACHEABLE_SECTION_ALIGN` | +| Подсветка | GPIO1[20] (LcdLed), active-high | +| Горизонт. скан | GPIO1[28] (LcdLR / SHLR контроллера) | +| Вертикал. скан | GPIO1[30] (LcdUd / UPDN контроллера) | +| MODE | GPIO1[29] — HIGH обязательно (DE mode для ELCDIF) | +| DITHB | GPIO1[31] — HIGH (dithering disable, IC default) | +| Тип дисплея | CMake-define `DISPLAY_TEST_TYPE` (сейчас TFT8) | -| Шаг | ID confirm | Промпт | Таймаут | -| ------- | --------------- | ---------------------- | ------- | -| Красный | `display_red` | "Экран залит красным?" | 15 с | -| Зелёный | `display_green` | "Экран залит зелёным?" | 15 с | -| Синий | `display_blue` | "Экран залит синим?" | 15 с | -| Белый | `display_white` | "Экран залит белым?" | 15 с | +### 6.2 Инварианты выполнения -Итог = AND всех четырёх подтверждений. +- `bsp_display_init()` вызывается в `init()`. При провале `run()` возвращает FAIL немедленно. +- Фреймбуфер `g_s_framebuf` статический, в NonCacheable SDRAM — ELCDIF DMA + всегда читает актуальные данные без cache flush. +- `bsp_usb_cdc_poll()` вызывается в циклах заливки буфера и ожидания FRAME_DONE — + USB CDC остаётся отзывчивым во время теста. +- После теста `bsp_display_deinit()` восстанавливает ROTATE_0 и выключает подсветку. -### Гарантии при PASS +### 6.3 Что тестирует — два этапа -- RGB888 интерфейс выводит каждый цвет независимо. -- Подсветка PWM активна (оператор видит экран). +#### 6.3.1 Этап 1: Цвет (все типы дисплеев) -### Интерпретация FAIL +Четыре шага: Red → Green → Blue → White. -`detail` содержит ID шага, который не был подтверждён: `"display_blue not confirmed"`. -Это указывает на конкретный цветовой канал с дефектом. +На каждом шаге прошивка заливает фреймбуфер сплошным цветом XRGB8888, +ждёт ISR FRAME_DONE, затем ждёт визуального подтверждения оператора +(таймаут 15 с). + +**Ловит:** + +- Неработающую подсветку. +- Обрыв или неправильное подключение отдельных RGB-каналов шины данных. +- Полное отсутствие изображения (ELCDIF / питание дисплея). + +**Не ловит:** + +- Деградацию отдельных пикселей (stuck pixel). +- Проблемы с яркостью и гамма-коррекцией. --- -## test_buttons — Test_But_1 / Test_But_2 +#### 6.3.2 Этап 2: Проверка LR/UD пинов (TFT7/TFT8/TFT10) + +Диагностирует непропаянные ножки `LcdLR` (GPIO1[28]) и `LcdUd` (GPIO1[30]). + +**Паттерн:** левая половина экрана RED, правая BLUE. + +**Шаг 1 — базовая ориентация (`ROTATE_0`):** +прошивка устанавливает `LR=1 UD=0`, заливает паттерн, оператор +подтверждает что левая зона красная, правая синяя. + +**Шаг 2 — горизонтальный флип (`FLIP_HORIZONTAL`):** +прошивка устанавливает `LR=0 UD=0` — контроллер HX8264-D02 меняет +направление источника (SHLR). При исправном LR-пине цветовые зоны +меняются местами. Оператор подтверждает изменение. + +После теста прошивка восстанавливает `ROTATE_0` независимо от результата. + +> **Примечание по контроллеру HX8264-D02:** +> LR (SHLR) управляет **горизонтальным** направлением источников — +> меняет местами левую/правую части. UD (UPDN) управляет **вертикальным** +> направлением затвора. Для детектирования непропаянного LR-пина +> используется горизонтальный паттерн и `FLIP_HORIZONTAL`. + +**Ловит:** + +- Непропаянный `LcdLR` (GPIO1[28]) — горизонтальные зоны не меняются. +- Непропаянный `LcdUd` (GPIO1[30]) — тест не проверяет UD напрямую, + но бракованная плата с обоими непропаянными пинами также не пройдёт + шаг 1 (непредсказуемая ориентация при старте). + +**Не ловит:** + +- Изолированный непропай `LcdUd` при исправном `LcdLR`. + +--- + +### 6.4 Гарантии теста при PASS + +- ELCDIF инициализирован, фреймбуфер DMA работает корректно. +- Все три RGB-канала шины данных функционируют. +- Подсветка включается и отключается. +- Пин `LcdLR` (GPIO1[28]) физически пропаян и реагирует на GPIO-запись. + +### 6.5 Интерпретация FAIL + +| `detail` | Вероятный диагноз | +| -------------------------------- | -------------------------------------------- | +| `display init failed` | Неверный `DISPLAY_TEST_TYPE` или нет питания | +| `display_red not confirmed` | Нет изображения / канал R / подсветка | +| `display_green not confirmed` | Канал G мёртв | +| `display_blue not confirmed` | Канал B мёртв | +| `display_white not confirmed` | Подсветка или несколько RGB-каналов | +| `display_rot_base not confirmed` | Нет изображения в паттерне (ELCDIF) | +| `display_rot_lr not confirmed` | **LcdLR (GPIO1[28]) не пропаян** | + +### 6.6 Последовательность событий протокола + +```bash +test_begin +→ confirm_request(display_red) ← оператор видит сплошной красный +→ confirm_request(display_green) ← сплошной зелёный +→ confirm_request(display_blue) ← сплошной синий +→ confirm_request(display_white) ← сплошной белый +→ confirm_request(display_rot_base) ← левая RED / правая BLUE (ROTATE_0) +→ confirm_request(display_rot_lr) ← цветовые зоны поменялись (FLIP_HORIZONTAL) +test_result +``` + +> **Важно:** `test_display` не использует `pre_confirm_prompt`. +> Первый confirm_request (`display_red`) отправляется изнутри `run()` +> после `test_begin`. Хост должен отвечать только на события типа +> `confirm_request`, не опережая их. + +### 6.7 Зависимости + +| Зависимость | Описание | +| ------------- | ------------------------------------------ | +| `bsp_display` | ELCDIF init/deinit, rotation, frame buffer | +| `bsp_usb_cdc` | `bsp_usb_cdc_poll()` в циклах ожидания | + +### 6.8 Типичное время выполнения + +| Этап | ~Время | +| -------------------- | -------------- | +| init (ELCDIF + GPIO) | < 10 мс | +| 4 × цветовой шаг | 4 × 15 с (max) | +| 2 × шаг ротации | 2 × 15 с (max) | +| **Итого (max)** | **~90 с** | +| **Итого (типичный)** | **~40–60 с** | + +## 7 test_buttons — Test_But_1 / Test_But_2 **Файл:** `test_buttons.c` *(не реализован — Этап 5)* **ID:** `buttons` **Critical:** ❌ | **HIL:** ❌ | **Confirm:** prompt only -### Аппаратный контекст +### 7.1 Аппаратный контекст | Кнопка | Пин MCU | GPIO | | ---------- | ---------- | --------- | @@ -616,4 +727,4 @@ static const test_module_t *const k_registry[] = { ``` Critical тесты идут первыми — при их провале HIL и интерактивные тесты -пропускаются автоматически, экономя время диагностики. \ No newline at end of file +пропускаются автоматически, экономя время диагностики. diff --git a/firmware/test/src/tests/test_display.c b/firmware/test/src/tests/test_display.c index 3768f80..a367f05 100644 --- a/firmware/test/src/tests/test_display.c +++ b/firmware/test/src/tests/test_display.c @@ -48,6 +48,15 @@ #define DISPLAY_TEST_TYPE BSP_DISPLAY_TFT8 #endif +/* ── Вспомогательные функции ────────────────────────────────────────────── */ + +static test_result_t make_fail(const char *p_detail) +{ + test_result_t result = { .status = TEST_STATUS_FAIL, .duration_ms = 0U }; + (void) snprintf(result.detail, TEST_DETAIL_SIZE, "%s", p_detail); + return result; +} + /* ── Константы ───────────────────────────────────────────────────────── */ /** @brief XRGB8888 цвета для тестовых заливок. */ @@ -216,8 +225,8 @@ static bool step_rotation(test_result_t *p_out) "Screen: left RED, right BLUE?", p_out); if (ok) { - ok = step_rot_apply_and_confirm(BSP_DISPLAY_ROTATE_90, "display_rot90", - "Color zones changed orientation?", p_out); + ok = step_rot_apply_and_confirm(BSP_DISPLAY_FLIP_HORIZONTAL, "display_rot_base", + "Left RED and right BLUE swapped sides?", p_out); } /* Восстановить ROTATE_0 в любом исходе */ @@ -246,12 +255,10 @@ static void display_test_init(void) static test_result_t display_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", - "display init failed — check DISPLAY_TEST_TYPE"); - return result; + return make_fail("display init failed"); } test_result_t fail_result = { .status = TEST_STATUS_FAIL, .duration_ms = 0U, .detail = { 0 } }; diff --git a/just/build.just b/just/build.just index 7a77ae1..d2d95c7 100644 --- a/just/build.just +++ b/just/build.just @@ -2,7 +2,7 @@ # scripts/build.just — сборка, тесты, HAB-образы # Выполняется ТОЛЬКО внутри devcontainer (из терминала VSCode или tasks). # -# Рабочая директория — корень репозитория (не scripts/). +# Рабочая директория — корень репозитория. # ============================================================================= # === Импорт общих переменных === diff --git a/just/ci.just b/just/ci.just index cfc7928..2ece1f2 100644 --- a/just/ci.just +++ b/just/ci.just @@ -32,7 +32,7 @@ test: lint: @echo "TODO: enable just build::lint / just build::check after local stabilization" -[doc('Собрать HAB Release-артефакты для выкладки')] +[doc('Собрать HAB Release-артефакты для деплоя')] [group('ci')] release: build just build::hab-all-release diff --git a/just/host.just b/just/host.just index f097b4b..9218b9e 100644 --- a/just/host.just +++ b/just/host.just @@ -224,7 +224,7 @@ setup-m5-udev: # ============================================================================= # ГРУППА: flash-sdp — USB Serial Downloader (SDP через ROM-загрузчик) -# Требует: плата в режиме Serial Downloader (BOOT_MODE = 01) +# Требование: плата в режиме Serial Downloader (BOOT_MODE = 01) # ============================================================================= [doc('Прошить образ через USB SDP: flash ')] @@ -272,8 +272,6 @@ flash-production: # ============================================================================= # ГРУППА: flash-swd — прошивка через SWD (MCU-Link, без смены BOOT_MODE) -# Требует: GDB-сервер НЕ запущен (pyOCD занимает пробник монопольно) -# После записи — обязательный power cycle платы # ============================================================================= _flash_swd := TOOLS_DIR / "flash_swd.py" @@ -379,7 +377,7 @@ hil-usb-cdc: # just host::debug-server → VSCode: 🐛 Debug: → F5 # # Рабочий цикл Б — прошить и отладить без USB SDP: -# just host::flash-swd-test-debug → power cycle → +# just host::flash-swd-test-debug # just host::debug-server → VSCode: 🐛 Debug: firmware_test → F5 # # Важно: debug-server и flash-swd используют MCU-Link монопольно. @@ -506,12 +504,6 @@ sdp-status: flashloader-status: cd "{{ TOOLS_DIR }}" && UV_PROJECT_ENVIRONMENT={{ _venv }} uv run blhost -u 0x15A2,0x0073 -- get-property 1 0 -[doc('Обновить spsdk до указанной версии: upgrade-tools ')] -[group('util')] -upgrade-tools version: - cd "{{ TOOLS_DIR }}" && uv add "spsdk=={{ version }}" && uv sync - @echo " ✅ spsdk upgraded to {{ version }}" - [doc('Открыть UART монитор на MCU-Link VCOM (кроссплатформенный)')] [group('util')] uart-monitor: diff --git a/utils/log/README.md b/utils/log/README.md index ebf1a38..5a8a0a6 100644 --- a/utils/log/README.md +++ b/utils/log/README.md @@ -6,24 +6,24 @@ USB CDC, Flash и т.д. Транспорт подключается через `log_init()` в виде callback-функции. Адаптеры живут в `port/log/`. -| Параметр | Значение | -|---|---| -| Формат | `[ timestamp][L][TAG] сообщение\r\n` | -| Буфер строки | 256 байт (переопределяется через `LOG_BUF_SIZE`) | -| Управление уровнем | `LOG_LEVEL` через CMake `-DLOG_LEVEL=N` | -| Thread-safety | мьютекс через weak-хуки (`log_mutex_lock/unlock`) | -| Зависимости | ``, ``, `` | +| Параметр | Значение | +| ------------------ | ------------------------------------------------- | +| Формат | `[ timestamp][L][TAG] сообщение\r\n` | +| Буфер строки | 256 байт (переопределяется через `LOG_BUF_SIZE`) | +| Управление уровнем | `LOG_LEVEL` через CMake `-DLOG_LEVEL=N` | +| Thread-safety | мьютекс через weak-хуки (`log_mutex_lock/unlock`) | +| Зависимости | ``, ``, `` | ## Уровни -| N | Макрос | Имя | -|---|--------|-----| -| 0 | — | off — все `LOG_*` → `((void)0)`, нулевой ROM | -| 1 | `LOG_E` | error | -| 2 | `LOG_W` | warn | -| 3 | `LOG_I` | info | -| 4 | `LOG_D` | debug | -| 5 | `LOG_V` | verbose | +| N | Макрос | Имя | +| --- | ------- | -------------------------------------------- | +| 0 | — | off — все `LOG_*` → `((void)0)`, нулевой ROM | +| 1 | `LOG_E` | error | +| 2 | `LOG_W` | warn | +| 3 | `LOG_I` | info | +| 4 | `LOG_D` | debug | +| 5 | `LOG_V` | verbose | По умолчанию: `VERBOSE` в Debug-сборке, `OFF` в Release (`NDEBUG`). @@ -63,8 +63,8 @@ typedef void (*log_write_cb_t)(const char *p_buf, size_t len, void *p_ctx); Готовые адаптеры в `port/log/`: -| Адаптер | Транспорт | -|---------|-----------| +| Адаптер | Транспорт | +| ---------- | ---------------------------------------- | | `log_uart` | `bsp_uart_host` (LPUART1, MCU-Link VCOM) | ## Мьютекс и временна́я метка (FreeRTOS) diff --git a/utils/prio_queue/README.md b/utils/prio_queue/README.md index 6dea0a0..b2a6f94 100644 --- a/utils/prio_queue/README.md +++ b/utils/prio_queue/README.md @@ -7,15 +7,15 @@ где число элементов заранее известно и невелико (≤ ~32). При полном буфере новый элемент с более высоким приоритетом вытесняет наименее приоритетный. -| Параметр | Значение | -|---|---| -| Элемент | любой тип, задаётся через `item_size` | -| Ёмкость | любая, задаётся при `prio_queue_init`; фиксирована на всё время жизни | -| Порядок | определяется `cmp`-функцией пользователя (аналог `qsort`) | -| Вставка | O(n) сдвиг; оптимально при n ≤ 32 | -| Peek-top | O(1) | +| Параметр | Значение | +| ------------- | ----------------------------------------------------------------------- | +| Элемент | любой тип, задаётся через `item_size` | +| Ёмкость | любая, задаётся при `prio_queue_init`; фиксирована на всё время жизни | +| Порядок | определяется `cmp`-функцией пользователя (аналог `qsort`) | +| Вставка | O(n) сдвиг; оптимально при n ≤ 32 | +| Peek-top | O(1) | | Thread-safety | нет; при использовании из нескольких контекстов — внешняя синхронизация | -| Зависимости | ``, ``, `` | +| Зависимости | ``, ``, `` | ## Быстрый старт @@ -54,7 +54,7 @@ prio_queue_remove_at(&q, 0); ## Политика вытеснения при полном буфере -``` +```bash Очередь полна [A(1) B(2) C(3)], вставляем D(2): → D приоритетнее C(3) → C вытесняется → [A(1) B(2) D(2)] PQ_EVICTED @@ -70,11 +70,11 @@ prio_queue_remove_at(&q, 0); typedef int (*pq_cmp_fn)(const void *a, const void *b); ``` -| Возврат | Смысл | -|---|---| -| `< 0` | `a` стоит перед `b` (a приоритетнее) | -| `> 0` | `b` стоит перед `a` | -| `0` | равнозначны; порядок вставки сохраняется | +| Возврат | Смысл | +| ------- | ---------------------------------------- | +| `< 0` | `a` стоит перед `b` (a приоритетнее) | +| `> 0` | `b` стоит перед `a` | +| `0` | равнозначны; порядок вставки сохраняется | ## API diff --git a/utils/ring_buffer/README.md b/utils/ring_buffer/README.md index 986f5e5..12c4a9c 100644 --- a/utils/ring_buffer/README.md +++ b/utils/ring_buffer/README.md @@ -6,12 +6,12 @@ читает. Не требует отключения прерываний при условии единственного producer и единственного consumer. -| Параметр | Значение | -|---|---| -| Элемент | 1 байт (`uint8_t`) | -| Ёмкость | любая степень двойки, задаётся при `ring_buffer_init` | +| Параметр | Значение | +| ------------- | ------------------------------------------------------------------------------ | +| Элемент | 1 байт (`uint8_t`) | +| Ёмкость | любая степень двойки, задаётся при `ring_buffer_init` | | Thread-safety | SPSC без блокировок; multi-producer/consumer — только с внешней синхронизацией | -| Зависимости | ``, ``, `` | +| Зависимости | ``, ``, `` | ## Быстрый старт