# 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' mod host 'just/host.just'
# build.just — сборка в DevContainer # build.just — сборка в DevContainer
mod build 'just/build.just' mod build 'just/build.just'
# ci.just — CI/CD сценарии (опционально) # ci.just — CI/CD сценарии
mod ci 'just/ci.just' mod ci 'just/ci.just'
# === Default рецепт === # === Default рецепт ===
@ -42,17 +42,11 @@ mod ci 'just/ci.just'
default: default:
@just --list --list-submodules @just --list --list-submodules
# === Популярные алиасы (для удобства команды) === # === Популярные алиасы ===
# Сокращают длинные вызовы модулей
[doc('Первичная настройка окружения после git clone (на хосте)')] [doc('Первичная настройка окружения после git clone (на хосте)')]
init: init:
@just host::bootstrap @just host::bootstrap
[doc('Прошить firmware_test Debug во Flash через USB ROM')]
flash:
@just host::flash-test-debug
[doc('Собрать все HAB-образы в Release')] [doc('Собрать все HAB-образы в Release')]
build-all: build-all:
@just build::hab-all-release @just build::hab-all-release

View file

@ -13,28 +13,28 @@
## Аппаратный контекст ## Аппаратный контекст
| Сигнал / параметр | Аппаратное назначение | | Сигнал / параметр | Аппаратное назначение |
| ----------------- | ---------------------------------------------------------------- | | ----------------- | ------------------------------------------------------------------------------------------------ |
| ELCDIF | NXP ELCDIF, RGB-режим, формат пикселя `kELCDIF_PixelFormatXRGB8888`, шина `kELCDIF_DataBus24Bit` | | ELCDIF | NXP ELCDIF, RGB-режим, формат пикселя `kELCDIF_PixelFormatXRGB8888`, шина `kELCDIF_DataBus24Bit` |
| Подсветка | `GPIO1[20]` — active-high | | Подсветка | `GPIO1[20]` — active-high |
| LR (горизонт.) | `GPIO1[28]` (`Lcdlr_value`) | | LR (горизонт.) | `GPIO1[28]` (`Lcdlr_value`) |
| MODE | `GPIO1[29]` — HIGH = DE mode (обязательно для ELCDIF) | | MODE | `GPIO1[29]` — HIGH = DE mode (обязательно для ELCDIF) |
| UD (вертикаль) | `GPIO1[30]` | | UD (вертикаль) | `GPIO1[30]` |
| DITHB | `GPIO1[31]` — HIGH = dithering disable (IC default) | | DITHB | `GPIO1[31]` — HIGH = dithering disable (IC default) |
| Пиксельный клок | PLL2 (`mux=0`) для TFT7/TFT8; Video PLL (`mux=2`) для TFT4 | | Пиксельный клок | PLL2 (`mux=0`) для TFT7/TFT8; Video PLL (`mux=2`) для TFT4 |
| IRQ | `LCDIF_IRQHandler` в ITCM, приоритет `DISPLAY_IRQ_PRIORITY = 2` | | IRQ | `LCDIF_IRQHandler` в ITCM, приоритет `DISPLAY_IRQ_PRIORITY = 2` |
`IOMUXC` конфигурируется в `BOARD_InitPins()` за пределами модуля — здесь `IOMUXC` конфигурируется в `BOARD_InitPins()` за пределами модуля — здесь
выполняется только `GPIO_PinWrite`. выполняется только `GPIO_PinWrite`.
Делители пиксельного клока (исходный код, PLL2 = 528 МГц): Делители пиксельного клока (исходный код, PLL2 = 528 МГц):
| Дисплей | clk_mux | pre_div | div | Эффективная частота | | Дисплей | clk_mux | pre_div | div | Эффективная частота |
| ------- | ------------- | ------- | --- | ------------------- | | ------- | --------- | -------- | --- | ------------------------------ |
| TFT7 | PLL2 | 2 | 4 | `528/3/5 = 35.2 МГц`| | TFT7 | PLL2 | 2 | 4 | `528/3/5 = 35.2 МГц` |
| TFT8 | PLL2 | 2 | 3 | `528/3/4 = 44.0 МГц`| | TFT8 | PLL2 | 2 | 3 | `528/3/4 = 44.0 МГц` |
| TFT4 | Video PLL | — (TODO)| — | требует `CLOCK_InitVideoPll` | | TFT4 | Video PLL | — (TODO) | — | требует `CLOCK_InitVideoPll` |
| TFT10 | — | — | — | таблица не заполнена (`{ 0 }`) | | TFT10 | — | — | — | таблица не заполнена (`{ 0 }`) |
Тайминги HSW/HFP/HBP/VSW/VFP/VBP заданы константами в `display.c` Тайминги HSW/HFP/HBP/VSW/VFP/VBP заданы константами в `display.c`
(`DISPLAY_TFT7_*`, `DISPLAY_TFT8_*`, `DISPLAY_TFT4_*`). (`DISPLAY_TFT7_*`, `DISPLAY_TFT8_*`, `DISPLAY_TFT4_*`).
@ -43,7 +43,7 @@
## Состав модуля ## Состав модуля
``` ```bash
bsp/display/ bsp/display/
├── include/bsp/display.h # публичный заголовок ├── include/bsp/display.h # публичный заголовок
├── src/display.c # реализация API + LCDIF_IRQHandler ├── src/display.c # реализация API + LCDIF_IRQHandler
@ -129,11 +129,11 @@ bsp_display_type_t bsp_display_get_type(void);
Коды возврата: Коды возврата:
| Код | Когда | | Код | Когда |
| ---------------------- | ----------------------------------------------------------- | | ----------------------- | --------------------------------------------------------------------------------------------------------- |
| `BSP_OK` | Дисплей инициализирован (или уже был инициализирован). | | `BSP_OK` | Дисплей инициализирован (или уже был инициализирован). |
| `BSP_ERR_PARAM` | `type >= BSP_DISPLAY_COUNT`. | | `BSP_ERR_PARAM` | `type >= BSP_DISPLAY_COUNT`. |
| `BSP_ERR_NOT_SUPPORTED`| `type` требует Video PLL (TFT4) — `init_pixelclock` возвращает ошибку до реализации `CLOCK_InitVideoPll`. | | `BSP_ERR_NOT_SUPPORTED` | `type` требует Video PLL (TFT4) — `init_pixelclock` возвращает ошибку до реализации `CLOCK_InitVideoPll`. |
> Поведение для `BSP_DISPLAY_TFT10` целостно не описано в коде: запись в > Поведение для `BSP_DISPLAY_TFT10` целостно не описано в коде: запись в
> `K_HW_CFG[BSP_DISPLAY_TFT10]` сделана как `{ 0 }`. Использовать 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 (из `display.c`):
| Rotation | LR | UD | | Rotation | LR | UD |
| ---------------------- | -- | -- | | ----------------------------- | --- | --- |
| `BSP_DISPLAY_ROTATE_0` | 1 | 0 | | `BSP_DISPLAY_ROTATE_0` | 1 | 0 |
| `BSP_DISPLAY_ROTATE_90`| 1 | 1 | | `BSP_DISPLAY_FLIP_VERTICAL` | 1 | 1 |
| `BSP_DISPLAY_ROTATE_180`| 0 | 1 | | `BSP_DISPLAY_FLIP_BOTH` | 0 | 1 |
| `BSP_DISPLAY_ROTATE_270`| 0 | 0 | | `BSP_DISPLAY_FLIP_HORIZONTAL` | 0 | 0 |
### `bsp_display_set_next_buffer` ### `bsp_display_set_next_buffer`

View file

@ -37,10 +37,10 @@ typedef enum bsp_display_type_e
typedef enum bsp_display_rotation_e typedef enum bsp_display_rotation_e
{ {
BSP_DISPLAY_ROTATE_0 = 0U, /**< Горизонтальная (нормальная). LR=1 UD=0. */ BSP_DISPLAY_ROTATE_0 = 0U, /**< LR=1 UD=0. Нормальная ориентация. */
BSP_DISPLAY_ROTATE_90, /**< Вертикальная CW 90°. LR=1 UD=1. */ BSP_DISPLAY_FLIP_VERTICAL, /**< LR=1 UD=1. Вертикальный флип (UPDN). */
BSP_DISPLAY_ROTATE_180, /**< Горизонтальная перевёрнутая. LR=0 UD=1. */ BSP_DISPLAY_FLIP_BOTH, /**< LR=0 UD=1. Горизонтальный + вертикальный. */
BSP_DISPLAY_ROTATE_270, /**< Вертикальная CCW 90°. LR=0 UD=0. */ BSP_DISPLAY_FLIP_HORIZONTAL, /**< LR=0 UD=0. Горизонтальный флип (SHLR). */
} bsp_display_rotation_t; } 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; lr_value = 1U;
ud_value = 0U; ud_value = 0U;
break; break;
case BSP_DISPLAY_ROTATE_90: case BSP_DISPLAY_FLIP_VERTICAL:
lr_value = 1U; lr_value = 1U;
ud_value = 1U; ud_value = 1U;
break; break;
case BSP_DISPLAY_ROTATE_180: case BSP_DISPLAY_FLIP_BOTH:
lr_value = 0U; lr_value = 0U;
ud_value = 1U; ud_value = 1U;
break; break;
case BSP_DISPLAY_ROTATE_270: case BSP_DISPLAY_FLIP_HORIZONTAL:
lr_value = 0U; lr_value = 0U;
ud_value = 0U; ud_value = 0U;
break; 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_ID[] = "\"id\"";
static const char K_FIELD_CONFIRMED[] = "\"confirmed\""; 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 ────────────────────────────────────────────────────── */ /* ── RX line buffer ────────────────────────────────────────────────────── */
static uint8_t g_s_line_buf[CLI_LINE_BUF_SIZE]; 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) 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) void cli_send(const char *p_resp)
@ -292,41 +299,49 @@ void cli_send(const char *p_resp)
void cli_process(void) void cli_process(void)
{ {
uint8_t chunk[CLI_LINE_BUF_SIZE]; /* Если в g_s_chunk_buf есть остаток от предыдущего вызова —
size_t nbytes = bsp_usb_cdc_read(chunk, sizeof(chunk)); * продолжаем его обработку. Иначе читаем новый chunk.
*
for (size_t byte_idx = 0U; byte_idx < nbytes; byte_idx++) * Это решает проблему потери 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)) if (g_s_line_len >= (CLI_LINE_BUF_SIZE - 1U))
{ {
g_s_line_len = 0U; g_s_line_len = 0U;
protocol_send_error("LINE_TOO_LONG"); protocol_send_error("LINE_TOO_LONG");
continue; return;
} }
if (byte == (uint8_t) '\n') 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') 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_len--;
} }
g_s_line_buf[g_s_line_len] = '\0'; g_s_line_buf[g_s_line_len] = '\0';
size_t completed_len = g_s_line_len;
if (g_s_line_len > 0U) g_s_line_len = 0U; /* ← сбросить ДО process_line */
if (completed_len > 0U)
{ {
process_line((const char *) g_s_line_buf); process_line((const char *) g_s_line_buf);
} }
}
g_s_line_len = 0U; g_s_line_buf[g_s_line_len] = byte;
} g_s_line_len++;
else
{
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_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) *(запланирован)*
- [test_uart_ttl — UART TTL](#test_uart_ttl--uart-ttl) *(запланирован)* - [test_uart_ttl — UART TTL](#test_uart_ttl--uart-ttl) *(запланирован)*
@ -24,7 +24,7 @@
--- ---
## Как читать эту таблицу ## 2 Как читать эту таблицу
**Critical** — при провале в `run_all` все последующие тесты получают `SKIP`. **Critical** — при провале в `run_all` все последующие тесты получают `SKIP`.
Некритические тесты могут упасть без остановки прогона. Некритические тесты могут упасть без остановки прогона.
@ -42,13 +42,13 @@
--- ---
## test_sdram — SDRAM 32 MB ## 3 test_sdram — SDRAM 32 MB
**Файл:** `test_sdram.c` **Файл:** `test_sdram.c`
**ID:** `sdram` **ID:** `sdram`
**Critical:** ✅ | **HIL:** ❌ | **Confirm:** **Critical:** ✅ | **HIL:** ❌ | **Confirm:**
### Аппаратный контекст ### 3.1 Аппаратный контекст
| Параметр | Значение | | Параметр | Значение |
| --------------- | ------------------------------------------------- | | --------------- | ------------------------------------------------- |
@ -60,7 +60,7 @@
| Тестовый регион | 0x80200000 — 0x80A00000 | | Тестовый регион | 0x80200000 — 0x80A00000 |
| MPU | Region 8: Normal WB cacheable (BOARD_MPU_SDRAM=1) | | MPU | Region 8: Normal WB cacheable (BOARD_MPU_SDRAM=1) |
### Инварианты выполнения ### 3.2 Инварианты выполнения
- SEMC инициализирован DCD **до** `main()``bsp_sdram_init()` только верифицирует. - SEMC инициализирован DCD **до** `main()``bsp_sdram_init()` только верифицирует.
- MPU Region 8 (`Normal WB`) активен — кэш включён для SDRAM. - MPU Region 8 (`Normal WB`) активен — кэш включён для SDRAM.
@ -70,9 +70,9 @@
- Сброс кэша (`SCB_CleanDCache_by_Addr`) выполняется **после каждого** write-прохода — - Сброс кэша (`SCB_CleanDCache_by_Addr`) выполняется **после каждого** write-прохода —
readback всегда из физической SDRAM, не из кэша. readback всегда из физической SDRAM, не из кэша.
### Что тестирует — четыре фазы ### 3.3 Что тестирует — четыре фазы
#### Фаза 1: Address bus (~1 мс) #### 3.3.1 Фаза 1: Address bus (~1 мс)
Записывает уникальный байт в 24 позиции на степенях двойки Записывает уникальный байт в 24 позиции на степенях двойки
(2^0..2^23 от TEST_BASE), каждую с индивидуальным cache line flush. (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, ...) и его инверсия Walking ones (0x01, 0x02, ..., 0x80, 0x01, ...) и его инверсия
на регионе 64 KB. на регионе 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, Address pattern (`offset & 0xFF`) и его инверсия на регионе 2 MB,
два паттерна × два прохода (write → flush → verify). два паттерна × два прохода (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 мс → верификация. Address pattern на 256 KB: запись → `flush_dcache` → ожидание 200 мс → верификация.
@ -146,7 +146,7 @@ Address pattern на 256 KB: запись → `flush_dcache` → ожидани
--- ---
### Гарантии теста при PASS ### 3.4Гарантии теста при PASS
- Все 24 адресных бита работают независимо без aliasing. - Все 24 адресных бита работают независимо без aliasing.
- Все 8 бит шины данных переключаются корректно. - Все 8 бит шины данных переключаются корректно.
@ -154,7 +154,7 @@ Address pattern на 256 KB: запись → `flush_dcache` → ожидани
- 256 KB удерживают данные минимум через 3 refresh-цикла. - 256 KB удерживают данные минимум через 3 refresh-цикла.
- SEMC контроллер инициализирован и отвечает. - SEMC контроллер инициализирован и отвечает.
### Интерпретация FAIL ### 3.5 Интерпретация FAIL
`detail` содержит: `addr=0xXXXXXXXX exp=0xXX got=0xXX` `detail` содержит: `addr=0xXXXXXXXX exp=0xXX got=0xXX`
@ -167,14 +167,14 @@ Address pattern на 256 KB: запись → `flush_dcache` → ожидани
**Анализ `exp` XOR `got`:** биты где `(exp ^ got) != 0` — сбойные линии шины данных. **Анализ `exp` XOR `got`:** биты где `(exp ^ got) != 0` — сбойные линии шины данных.
### Ограничения ### 3.6 Ограничения
- Не покрывает все 32 MB (только 2 MB для sequential). - Не покрывает все 32 MB (только 2 MB для sequential).
- Не проверяет retention при нагреве или низком напряжении питания. - Не проверяет retention при нагреве или низком напряжении питания.
- Не является заменой полного March C алгоритма. - Не является заменой полного March C алгоритма.
- Во время теста (~28 с) USB CDC занят, `ping` не отвечает. - Во время теста (~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` **Файл:** `test_qspi.c`
**ID:** `qspi` **ID:** `qspi`
**Critical:** ✅ | **HIL:** ❌ | **Confirm:** **Critical:** ✅ | **HIL:** ❌ | **Confirm:**
### Аппаратный контекст ### 4.1 Аппаратный контекст
| Параметр | Значение | | Параметр | Значение |
| --------------- | ------------------------------------------- | | --------------- | ------------------------------------------- |
@ -202,7 +202,7 @@ Address pattern на 256 KB: запись → `flush_dcache` → ожидани
| BSP | `bsp_qspi` | | BSP | `bsp_qspi` |
| Тестовый сектор | `flash_size - 4KB` (последний, динамически) | | Тестовый сектор | `flash_size - 4KB` (последний, динамически) |
### Инварианты выполнения ### 4.2Инварианты выполнения
- `bsp_qspi_init()` вызывается в `init()` — если чип не опознан, `run()` возвращает FAIL. - `bsp_qspi_init()` вызывается в `init()` — если чип не опознан, `run()` возвращает FAIL.
- Слот 0 FlexSPI LUT (XIP read, задан FDCB) **никогда не изменяется** — прошивка - Слот 0 FlexSPI LUT (XIP read, задан FDCB) **никогда не изменяется** — прошивка
@ -215,9 +215,9 @@ Address pattern на 256 KB: запись → `flush_dcache` → ожидани
- `bsp_usb_cdc_poll()` вызывается вокруг каждой блокирующей операции — USB CDC - `bsp_usb_cdc_poll()` вызывается вокруг каждой блокирующей операции — USB CDC
остаётся отзывчивым во время теста. остаётся отзывчивым во время теста.
### Что тестирует — четыре шага ### 4.3 Что тестирует — четыре шага
#### Шаг 1: JEDEC ID (~1 мс) #### 4.3.1 Шаг 1: JEDEC ID (~1 мс)
Команда 0x9F. Читает manufacturer ID и device ID. Команда 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 байт сектора. Стирает тестовый сектор (4KB). Читает первые 256 байт сектора.
Все байты должны быть `0xFF`. Все байты должны быть `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 байт). Записывает первую страницу Паттерн `byte[i] = i & 0xFF` (256 байт). Записывает первую страницу
тестового сектора командой Quad Page Program. Читает обратно командой тестового сектора командой 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`. Для чипов с `flash_size > 16 MB`.
Для W25Q64/128 шаг пропускается (24-bit покрывает весь чип). Для W25Q64/128 шаг пропускается (24-bit покрывает весь чип).
@ -293,7 +293,7 @@ IP Quad Output Read (слот 11). Сравнивает побайтово.
При 24-bit адресации адрес обрезается до 24 бит. Для W25Q256/512 При 24-bit адресации адрес обрезается до 24 бит. Для W25Q256/512
last-sector находится выше 16 MB: last-sector находится выше 16 MB:
``` ```bash
W25Q256: last sector = 0x1FFF000, 0x1FFF000 & 0xFFFFFF = 0xFFF000 W25Q256: last sector = 0x1FFF000, 0x1FFF000 & 0xFFFFFF = 0xFFF000
W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 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 инициализирован и отвечает. - FlexSPI1 инициализирован и отвечает.
- Подключён поддерживаемый Winbond Flash с ожидаемым JEDEC ID. - Подключён поддерживаемый Winbond Flash с ожидаемым JEDEC ID.
@ -330,7 +330,7 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000
- Для W25Q256/512: dedicated 4-byte address opcodes функционируют и - Для W25Q256/512: dedicated 4-byte address opcodes функционируют и
адресуют физически разные секторы выше и ниже 16 MB. адресуют физически разные секторы выше и ниже 16 MB.
### Интерпретация FAIL ### 4.5 Интерпретация FAIL
`detail` содержит текстовое описание шага и причины: `detail` содержит текстовое описание шага и причины:
@ -349,14 +349,14 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000
**Анализ `rw mismatch`:** `exp XOR got` — биты, где `(exp ^ got) != 0`, **Анализ `rw mismatch`:** `exp XOR got` — биты, где `(exp ^ got) != 0`,
соответствуют сбойным линиям Quad-шины. соответствуют сбойным линиям Quad-шины.
### Ограничения ### 4.6 Ограничения
- Тестирует только **1 страницу** (256 байт) — не покрывает весь объём чипа. - Тестирует только **1 страницу** (256 байт) — не покрывает весь объём чипа.
- Не проверяет endurance (многократные write/erase циклы). - Не проверяет endurance (многократные write/erase циклы).
- Не является заменой полного тестирования Flash (march-алгоритмы). - Не является заменой полного тестирования Flash (march-алгоритмы).
- Шаг 4 не проверяет адреса в диапазоне 1632 MB (только крайние точки). - Шаг 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` **Файл:** `test_usd.c`
**ID:** `usd` **ID:** `usd`
**Critical:** ❌ | **HIL:** ❌ | **Confirm:** pre_confirm **Critical:** ❌ | **HIL:** ❌ | **Confirm:** pre_confirm
### Аппаратный контекст ### 5.1 Аппаратный контекст
| Параметр | Значение | | Параметр | Значение |
| ----------------- | --------------------------------------------------------------- | | ----------------- | --------------------------------------------------------------- |
@ -387,7 +387,7 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000
| Drive FatFS | `2:/` | | Drive FatFS | `2:/` |
| BSP | `bsp_sd` (host init/deinit/card detect) + `firmware_test_fatfs` | | BSP | `bsp_sd` (host init/deinit/card detect) + `firmware_test_fatfs` |
### Инварианты выполнения ### 5.2 Инварианты выполнения
- `bsp_sd_init()` вызывается в `init()`. При провале `run()` возвращает FAIL немедленно. - `bsp_sd_init()` вызывается в `init()`. При провале `run()` возвращает FAIL немедленно.
- Все ресурсы (файл, mount, host) освобождаются в `deinit()` — вызывается test_runner - Все ресурсы (файл, mount, host) освобождаются в `deinit()` — вызывается test_runner
@ -395,7 +395,7 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000
- Тестовый файл `2:/FWTEST.TMP` удаляется при любом исходе. - Тестовый файл `2:/FWTEST.TMP` удаляется при любом исходе.
- `bsp_usb_cdc_poll()` вызывается вокруг каждой блокирующей операции. - `bsp_usb_cdc_poll()` вызывается вокруг каждой блокирующей операции.
### Что тестирует — четыре шага ### 5.3 Что тестирует — четыре шага
| Шаг | Действие | Progress event | ~Время | | Шаг | Действие | Progress event | ~Время |
| ------------ | ------------------------------------------ | ------------------- | -------- | | ------------ | ------------------------------------------ | ------------------- | -------- |
@ -406,14 +406,14 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000
**Паттерн:** `byte[i] = i & 0xFF`, 4096 байт. Детектирует stuck-at-0/1 и partial write. **Паттерн:** `byte[i] = i & 0xFF`, 4096 байт. Детектирует stuck-at-0/1 и partial write.
### Гарантии теста при PASS ### 5.4 Гарантии теста при PASS
- USDHC host инициализирован и карта подключена. - USDHC host инициализирован и карта подключена.
- FatFS корректно монтирует раздел (FAT32 / exFAT). - FatFS корректно монтирует раздел (FAT32 / exFAT).
- Запись и чтение 4 KB совпадают побайтово. - Запись и чтение 4 KB совпадают побайтово.
- Тестовый файл удалён с карты. - Тестовый файл удалён с карты.
### Интерпретация FAIL ### 5.5 Интерпретация FAIL
| Паттерн `detail` | Причина | | Паттерн `detail` | Причина |
| ---------------------------------------------- | ----------------------------------------------------- | | ---------------------------------------------- | ----------------------------------------------------- |
@ -425,13 +425,13 @@ W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000
| `read failed: N` | Ошибка чтения | | `read failed: N` | Ошибка чтения |
| `compare failed at offset N` | Данные не совпадают — битый сектор карты | | `compare failed at offset N` | Данные не совпадают — битый сектор карты |
### Поведение без карты / отказ оператора ### 5.6 Поведение без карты / отказ оператора
- Оператор не ответил за 30 с`TEST_STATUS_SKIP`, `detail: "confirm timeout"`. - Оператор не ответил за 30 с`TEST_STATUS_SKIP`, `detail: "confirm timeout"`.
- Оператор нажал "отказ" → `TEST_STATUS_SKIP`, `detail: "operator declined"`. - Оператор нажал "отказ" → `TEST_STATUS_SKIP`, `detail: "operator declined"`.
- Тест не critical → `run_all` продолжает выполнение остальных тестов. - Тест не 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` **ID:** `display`
**Critical:** ❌ | **HIL:** ❌ | **Confirm:** в run() **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 | Промпт | Таймаут | ### 6.2 Инварианты выполнения
| ------- | --------------- | ---------------------- | ------- |
| Красный | `display_red` | "Экран залит красным?" | 15 с |
| Зелёный | `display_green` | "Экран залит зелёным?" | 15 с |
| Синий | `display_blue` | "Экран залит синим?" | 15 с |
| Белый | `display_white` | "Экран залит белым?" | 15 с |
Итог = 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 интерфейс выводит каждый цвет независимо. #### 6.3.1 Этап 1: Цвет (все типы дисплеев)
- Подсветка PWM активна (оператор видит экран).
### Интерпретация 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)* **Файл:** `test_buttons.c` *(не реализован — Этап 5)*
**ID:** `buttons` **ID:** `buttons`
**Critical:** ❌ | **HIL:** ❌ | **Confirm:** prompt only **Critical:** ❌ | **HIL:** ❌ | **Confirm:** prompt only
### Аппаратный контекст ### 7.1 Аппаратный контекст
| Кнопка | Пин MCU | GPIO | | Кнопка | Пин MCU | GPIO |
| ---------- | ---------- | --------- | | ---------- | ---------- | --------- |

View file

@ -48,6 +48,15 @@
#define DISPLAY_TEST_TYPE BSP_DISPLAY_TFT8 #define DISPLAY_TEST_TYPE BSP_DISPLAY_TFT8
#endif #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 цвета для тестовых заливок. */ /** @brief XRGB8888 цвета для тестовых заливок. */
@ -216,8 +225,8 @@ static bool step_rotation(test_result_t *p_out)
"Screen: left RED, right BLUE?", p_out); "Screen: left RED, right BLUE?", p_out);
if (ok) if (ok)
{ {
ok = step_rot_apply_and_confirm(BSP_DISPLAY_ROTATE_90, "display_rot90", ok = step_rot_apply_and_confirm(BSP_DISPLAY_FLIP_HORIZONTAL, "display_rot_base",
"Color zones changed orientation?", p_out); "Left RED and right BLUE swapped sides?", p_out);
} }
/* Восстановить ROTATE_0 в любом исходе */ /* Восстановить ROTATE_0 в любом исходе */
@ -246,12 +255,10 @@ static void display_test_init(void)
static test_result_t display_test_run(void) static test_result_t display_test_run(void)
{ {
if (!g_s_ready) if (!g_s_ready)
{ {
test_result_t result = { .status = TEST_STATUS_FAIL, .duration_ms = 0U }; return make_fail("display init failed");
(void) snprintf(result.detail, TEST_DETAIL_SIZE, "%s",
"display init failed — check DISPLAY_TEST_TYPE");
return result;
} }
test_result_t fail_result = { .status = TEST_STATUS_FAIL, .duration_ms = 0U, .detail = { 0 } }; test_result_t fail_result = { .status = TEST_STATUS_FAIL, .duration_ms = 0U, .detail = { 0 } };

View file

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

View file

@ -32,7 +32,7 @@ test:
lint: lint:
@echo "TODO: enable just build::lint / just build::check after local stabilization" @echo "TODO: enable just build::lint / just build::check after local stabilization"
[doc('Собрать HAB Release-артефакты для выкладки')] [doc('Собрать HAB Release-артефакты для деплоя')]
[group('ci')] [group('ci')]
release: build release: build
just build::hab-all-release just build::hab-all-release

View file

@ -224,7 +224,7 @@ setup-m5-udev:
# ============================================================================= # =============================================================================
# ГРУППА: flash-sdp — USB Serial Downloader (SDP через ROM-загрузчик) # ГРУППА: 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>')] [doc('Прошить образ через USB SDP: flash <firmware_test|bootloader|app> <debug|release>')]
@ -272,8 +272,6 @@ flash-production:
# ============================================================================= # =============================================================================
# ГРУППА: flash-swd — прошивка через SWD (MCU-Link, без смены BOOT_MODE) # ГРУППА: flash-swd — прошивка через SWD (MCU-Link, без смены BOOT_MODE)
# Требует: GDB-сервер НЕ запущен (pyOCD занимает пробник монопольно)
# После записи — обязательный power cycle платы
# ============================================================================= # =============================================================================
_flash_swd := TOOLS_DIR / "flash_swd.py" _flash_swd := TOOLS_DIR / "flash_swd.py"
@ -379,7 +377,7 @@ hil-usb-cdc:
# just host::debug-server → VSCode: 🐛 Debug: <project> → F5 # just host::debug-server → VSCode: 🐛 Debug: <project> → F5
# #
# Рабочий цикл Б — прошить и отладить без USB SDP: # Рабочий цикл Б — прошить и отладить без 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 # just host::debug-server → VSCode: 🐛 Debug: firmware_test → F5
# #
# Важно: debug-server и flash-swd используют MCU-Link монопольно. # Важно: debug-server и flash-swd используют MCU-Link монопольно.
@ -506,12 +504,6 @@ sdp-status:
flashloader-status: flashloader-status:
cd "{{ TOOLS_DIR }}" && UV_PROJECT_ENVIRONMENT={{ _venv }} uv run blhost -u 0x15A2,0x0073 -- get-property 1 0 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 (кроссплатформенный)')] [doc('Открыть UART монитор на MCU-Link VCOM (кроссплатформенный)')]
[group('util')] [group('util')]
uart-monitor: uart-monitor:

View file

@ -6,24 +6,24 @@
USB CDC, Flash и т.д. Транспорт подключается через `log_init()` в виде USB CDC, Flash и т.д. Транспорт подключается через `log_init()` в виде
callback-функции. Адаптеры живут в `port/log/`. callback-функции. Адаптеры живут в `port/log/`.
| Параметр | Значение | | Параметр | Значение |
|---|---| | ------------------ | ------------------------------------------------- |
| Формат | `[ timestamp][L][TAG] сообщение\r\n` | | Формат | `[ timestamp][L][TAG] сообщение\r\n` |
| Буфер строки | 256 байт (переопределяется через `LOG_BUF_SIZE`) | | Буфер строки | 256 байт (переопределяется через `LOG_BUF_SIZE`) |
| Управление уровнем | `LOG_LEVEL` через CMake `-DLOG_LEVEL=N` | | Управление уровнем | `LOG_LEVEL` через CMake `-DLOG_LEVEL=N` |
| Thread-safety | мьютекс через weak-хуки (`log_mutex_lock/unlock`) | | Thread-safety | мьютекс через weak-хуки (`log_mutex_lock/unlock`) |
| Зависимости | `<stdarg.h>`, `<stdio.h>`, `<stddef.h>` | | Зависимости | `<stdarg.h>`, `<stdio.h>`, `<stddef.h>` |
## Уровни ## Уровни
| N | Макрос | Имя | | N | Макрос | Имя |
|---|--------|-----| | --- | ------- | -------------------------------------------- |
| 0 | — | off — все `LOG_*``((void)0)`, нулевой ROM | | 0 | — | off — все `LOG_*``((void)0)`, нулевой ROM |
| 1 | `LOG_E` | error | | 1 | `LOG_E` | error |
| 2 | `LOG_W` | warn | | 2 | `LOG_W` | warn |
| 3 | `LOG_I` | info | | 3 | `LOG_I` | info |
| 4 | `LOG_D` | debug | | 4 | `LOG_D` | debug |
| 5 | `LOG_V` | verbose | | 5 | `LOG_V` | verbose |
По умолчанию: `VERBOSE` в Debug-сборке, `OFF` в Release (`NDEBUG`). По умолчанию: `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/`: Готовые адаптеры в `port/log/`:
| Адаптер | Транспорт | | Адаптер | Транспорт |
|---------|-----------| | ---------- | ---------------------------------------- |
| `log_uart` | `bsp_uart_host` (LPUART1, MCU-Link VCOM) | | `log_uart` | `bsp_uart_host` (LPUART1, MCU-Link VCOM) |
## Мьютекс и временна́я метка (FreeRTOS) ## Мьютекс и временна́я метка (FreeRTOS)

View file

@ -7,15 +7,15 @@
где число элементов заранее известно и невелико (≤ ~32). При полном буфере новый где число элементов заранее известно и невелико (≤ ~32). При полном буфере новый
элемент с более высоким приоритетом вытесняет наименее приоритетный. элемент с более высоким приоритетом вытесняет наименее приоритетный.
| Параметр | Значение | | Параметр | Значение |
|---|---| | ------------- | ----------------------------------------------------------------------- |
| Элемент | любой тип, задаётся через `item_size` | | Элемент | любой тип, задаётся через `item_size` |
| Ёмкость | любая, задаётся при `prio_queue_init`; фиксирована на всё время жизни | | Ёмкость | любая, задаётся при `prio_queue_init`; фиксирована на всё время жизни |
| Порядок | определяется `cmp`-функцией пользователя (аналог `qsort`) | | Порядок | определяется `cmp`-функцией пользователя (аналог `qsort`) |
| Вставка | O(n) сдвиг; оптимально при n ≤ 32 | | Вставка | O(n) сдвиг; оптимально при n ≤ 32 |
| Peek-top | O(1) | | Peek-top | O(1) |
| Thread-safety | нет; при использовании из нескольких контекстов — внешняя синхронизация | | 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): Очередь полна [A(1) B(2) C(3)], вставляем D(2):
→ D приоритетнее C(3) → C вытесняется → [A(1) B(2) D(2)] PQ_EVICTED → 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); typedef int (*pq_cmp_fn)(const void *a, const void *b);
``` ```
| Возврат | Смысл | | Возврат | Смысл |
|---|---| | ------- | ---------------------------------------- |
| `< 0` | `a` стоит перед `b` (a приоритетнее) | | `< 0` | `a` стоит перед `b` (a приоритетнее) |
| `> 0` | `b` стоит перед `a` | | `> 0` | `b` стоит перед `a` |
| `0` | равнозначны; порядок вставки сохраняется | | `0` | равнозначны; порядок вставки сохраняется |
## API ## API

View file

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