# test_display: finished

This commit is contained in:
Dmitry Akimov 2026-05-25 18:19:55 +03:00
parent be36d56170
commit 8d85ffe877
13 changed files with 289 additions and 170 deletions

View file

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

View file

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

View file

@ -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;
/* ── Размер дисплея ──────────────────────────────────────────────────── */

View file

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

View file

@ -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++;
}
}

View file

@ -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 не проверяет адреса в диапазоне 1632 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 с** |
| **Итого (типичный)** | **~4060 с** |
## 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 и интерактивные тесты
пропускаются автоматически, экономя время диагностики.
пропускаются автоматически, экономя время диагностики.

View file

@ -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 } };

View file

@ -2,7 +2,7 @@
# scripts/build.just — сборка, тесты, HAB-образы
# Выполняется ТОЛЬКО внутри devcontainer (из терминала VSCode или tasks).
#
# Рабочая директория — корень репозитория (не scripts/).
# Рабочая директория — корень репозитория.
# =============================================================================
# === Импорт общих переменных ===

View file

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

View file

@ -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 <firmware_test|bootloader|app> <debug|release>')]
@ -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: <project> → 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 <version>')]
[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:

View file

@ -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`) |
| Зависимости | `<stdarg.h>`, `<stdio.h>`, `<stddef.h>` |
| Параметр | Значение |
| ------------------ | ------------------------------------------------- |
| Формат | `[ timestamp][L][TAG] сообщение\r\n` |
| Буфер строки | 256 байт (переопределяется через `LOG_BUF_SIZE`) |
| Управление уровнем | `LOG_LEVEL` через CMake `-DLOG_LEVEL=N` |
| Thread-safety | мьютекс через weak-хуки (`log_mutex_lock/unlock`) |
| Зависимости | `<stdarg.h>`, `<stdio.h>`, `<stddef.h>` |
## Уровни
| 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)

View file

@ -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 | нет; при использовании из нескольких контекстов — внешняя синхронизация |
| Зависимости | `<stdint.h>`, `<stddef.h>`, `<string.h>` |
| Зависимости | `<stdint.h>`, `<stddef.h>`, `<string.h>` |
## Быстрый старт
@ -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

View file

@ -6,12 +6,12 @@
читает. Не требует отключения прерываний при условии единственного producer и
единственного consumer.
| Параметр | Значение |
|---|---|
| Элемент | 1 байт (`uint8_t`) |
| Ёмкость | любая степень двойки, задаётся при `ring_buffer_init` |
| Параметр | Значение |
| ------------- | ------------------------------------------------------------------------------ |
| Элемент | 1 байт (`uint8_t`) |
| Ёмкость | любая степень двойки, задаётся при `ring_buffer_init` |
| Thread-safety | SPSC без блокировок; multi-producer/consumer — только с внешней синхронизацией |
| Зависимости | `<stdint.h>`, `<stddef.h>`, `<stdbool.h>` |
| Зависимости | `<stdint.h>`, `<stddef.h>`, `<stdbool.h>` |
## Быстрый старт