From db2c57364268d08b36750e12da4afc407df95392 Mon Sep 17 00:00:00 2001 From: Ezra Maccabee Date: Thu, 9 Jul 2026 10:07:04 +0300 Subject: [PATCH] # bootloader: target test docs --- tests/target/hil_button/README.md | 59 ++++++++++++++++++++++++++ tests/target/hil_can/README.md | 67 ++++++++++++++++++++++++++++++ tests/target/hil_opto/README.md | 60 ++++++++++++++++++++++++++ tests/target/hil_opto/main.c | 4 +- tests/target/hil_usb_cdc/README.md | 58 ++++++++++++++++++++++++++ tests/target/host_uart/README.md | 54 ++++++++++++++++++++++++ tools/hil/02_test_opto.py | 7 ++-- 7 files changed, 305 insertions(+), 4 deletions(-) create mode 100644 tests/target/hil_button/README.md create mode 100644 tests/target/hil_can/README.md create mode 100644 tests/target/hil_opto/README.md create mode 100644 tests/target/hil_usb_cdc/README.md create mode 100644 tests/target/host_uart/README.md diff --git a/tests/target/hil_button/README.md b/tests/target/hil_button/README.md new file mode 100644 index 0000000..88c0227 --- /dev/null +++ b/tests/target/hil_button/README.md @@ -0,0 +1,59 @@ +# test_hil_button + +## Модуль под тестом + +`bsp/button` — HIL-прошивка `test_hil_button` (`main.c`), поднимающая CLI +(`READ`, `STATE`, `POLL`, `EVENT_P`, `EVENT_R`) поверх двух физических +кнопок на плате таргета (`TactBut1`, `TactBut2`). Фоновый debounce-poll +крутится в главном цикле каждые 5 мс. Прошивка не содержит проверок — она +исполняет команды; assert'ы находятся в pytest на хосте. + +## Категория + +HIL, интерактивный — стенд M5StampPLC для стимула не используется, кнопки +нажимает оператор вручную по подсказкам в консоли (`@pytest.mark.interactive`, +запуск с `-s`). M5StampPLC по-прежнему нужен для управления питанием +таргета (RLY1). + +## Моки + +Программных моков и стенда-стимулятора нет — тестируется физическое +нажатие реальных кнопок человеком-оператором. Роль «стимула» играет +оператор: хелпер `_operator_prompt()` печатает инструкцию и блокируется на +`input()` до нажатия Enter, после чего выдерживается пауза +`DEBOUNCE_SETTLE_S` (100 мс) на срабатывание дебаунса. Все проверки — в +`tools/hil/04_test_button.py`. + +## Что проверяется + +- Канал host↔target работает (`PING`). +- Сырое чтение (`READ`) в состоянии покоя и при нажатии/отпускании для + обеих кнопок. +- Debounce-события нажатия (`EVENT_P`) и отпускания (`EVENT_R`) и их + одноразовое потребление (повторный запрос без нового действия даёт `0`). +- Стабильное состояние (`STATE`) во время удержания кнопки и после + отпускания. +- Независимость кнопок — нажата только одна, вторая остаётся отпущенной. + +## Гарантии + +- Физическое нажатие/отпускание кнопки надёжно регистрируется как в + сыром чтении (`READ`), так и в дебаунсированном состоянии (`STATE`) и в + одноразовых событиях (`EVENT_P`/`EVENT_R`). +- Кнопки физически независимы — нажатие одной не влияет на показания + другой. +- Задержки в тесте (100 мс после действия оператора, фоновый poll каждые + 5 мс) достаточны для гарантированного срабатывания дебаунса (порог 4 + стабильных сэмпла). + +## Запуск + +```bash +just build::build-hil # в devcontainer: собрать HIL-прошивки +just host::hil-button # на хосте: прошить и прогнать тест (интерактивно) +# или все интерактивные тесты сразу: +just host::hil-run-interactive +``` + +Тест интерактивный — требует присутствия оператора у стенда для нажатия +кнопок по подсказкам в терминале. diff --git a/tests/target/hil_can/README.md b/tests/target/hil_can/README.md new file mode 100644 index 0000000..ba2e0c4 --- /dev/null +++ b/tests/target/hil_can/README.md @@ -0,0 +1,67 @@ +# test_hil_can + +## Модуль под тестом + +`bsp/can` — HIL-прошивка `test_hil_can` (`main.c`), поднимающая CLI +(`CAN_SEND`, `CAN_RECV`, `CAN_FILTER`, `CAN_ACCEPT_ALL`, `CAN_RX_EVENTS`, +`CAN_LAST_RX`, `CAN_RESET_EVENTS`) поверх реальной CAN-шины (трансивер +SN65HVD230D на таргете). Прошивка не содержит проверок — она исполняет +команды и передаёт/принимает фреймы на физической шине; assert'ы находятся +в pytest на хосте. + +## Категория + +HIL, требует стенд M5StampPLC (трансивер SIT1044) как второй узел на общей +CAN-шине 125 kbit/s. M5 также управляет питанием таргета (RLY1). + +> `bsp_can_init()` вызывается с `disableSelfReception=true` — таргет не +> слышит собственные фреймы. Сценарий «таргет TX → таргет RX» без +> ретрансляции через M5 не тестируется напрямую. + +## Моки + +Программных моков нет — тестируется реальная передача по физической шине. +Роль второго узла играет M5StampPLC под управлением MicroPython-агента +(`tools/hil/m5/agent.py`): pytest дистанционно отправляет/принимает +CAN-фреймы через JSON-команды по USB CDC (`m5.can_send()`, +`m5.can_recv()`, фикстура `m5` в `conftest.py`). Все проверки — в +`tools/hil/03_test_can.py`. + +## Что проверяется + +- **Connectivity** — таргет отвечает на `PING`, M5-агент отвечает на + `ping` и сообщает `can_ok=true`. +- **M5 → таргет** — приём STD/EXT фрейма с верным ID и данными, + максимальный `DLC=8`, `DLC=0`, инкремент счётчика RX-событий на каждый + принятый фрейм. +- **Таргет → M5** — отправка STD/EXT фрейма, граничные ID (`0x000`, + `0x7FF`, `0x1FFFFFFF`), целостность всех 8 байт данных без искажений. +- **Фильтрация** — `CAN_ACCEPT_ALL` принимает любой ID; точная маска + (`0x7FF`) пропускает только совпадающий ID и блокирует остальные; маска + по группе старших бит (`0x7F0`) пропускает всю группу; повторный + `CAN_ACCEPT_ALL` снимает ранее установленный фильтр. +- **Типы фреймов** — STD и EXT не путаются друг с другом ни в одну, ни в + другую сторону, включая граничные значения ID. + +## Гарантии + +- Фреймы передаются по физической шине в обе стороны без потери и + искажения ID/данных, включая граничные STD (`0x7FF`) и EXT + (`0x1FFFFFFF`) идентификаторы. +- Аппаратная фильтрация по ID/маске работает корректно: точное совпадение, + совпадение по группе бит, полное открытие через `accept_all`. +- Счётчик принятых фреймов увеличивается ровно на каждый физически + принятый фрейм. +- STD- и EXT-фреймы всегда различимы (`is_extended` не искажается при + передаче по шине). + +## Запуск + +```bash +just build::build-hil # в devcontainer: собрать HIL-прошивки +just host::m5-deploy # один раз / после правок agent.py: залить агент на M5 +just host::hil-can # на хосте: прошить по SWD и прогнать тест +``` + +Требует MicroPython v1.25 на M5StampPLC (`tools/hil/m5/firmware/v1.25/`) — +в v1.27 модуль CAN недоступен. diff --git a/tests/target/hil_opto/README.md b/tests/target/hil_opto/README.md new file mode 100644 index 0000000..f8c6d26 --- /dev/null +++ b/tests/target/hil_opto/README.md @@ -0,0 +1,60 @@ +# test_hil_opto + +## Модуль под тестом + +`bsp/opto` — HIL-прошивка `test_hil_opto` (`main.c`), поднимающая CLI +(`OPTO_READ <1|2|3>`, `OPTO_EVENTS`, `OPTO_LAST_EVENT`, +`OPTO_RESET_EVENTS`) поверх трёх реальных оптоизолированных входов +(PS2801-4, active-HIGH). `bsp_opto_process()` крутится в главном цикле +каждую итерацию. Прошивка не содержит проверок — она исполняет команды и +считает callback-события; assert'ы находятся в pytest на хосте. + +## Категория + +HIL, требует стенд M5StampPLC — реле M5 физически замыкают/размыкают цепи +оптопар таргета (`EXT_IN1`/`EXT_IN2`/`RS_RX`, точный маппинг реле↔канал — +см. `docs/testing/hil/HIL_BENCH.md` и `tools/hil/m5/agent.py`). M5 также +управляет питанием таргета (RLY1). + +## Моки + +Программных моков нет — тестируется реальная электрическая цепь +оптопар. Роль стимула играет M5StampPLC под управлением MicroPython-агента +(`tools/hil/m5/agent.py`): pytest дистанционно щёлкает реле через +JSON-команды по USB CDC (`m5.opto_set(ch, bool)`, `m5.opto_all_off()`, +фикстура `m5` в `conftest.py`). Все проверки — в `tools/hil/02_test_opto.py`. + +## Что проверяется + +- **Connectivity** — таргет отвечает на `PING`, M5-агент отвечает на + `ping`. +- **Состояние по умолчанию** — все три канала `INACTIVE` без внешнего + воздействия. +- **Активация/деактивация** каждого канала по отдельности (реле ON → + `ACTIVE`, реле OFF → `INACTIVE`) и **изоляция** — активация одного канала + не меняет состояние остальных. +- **Callback-события** — включение/выключение реле генерирует минимум одно + событие с правильным каналом и состоянием (проверяет цепочку + ISR → debounce → `bsp_opto_process()` → callback), `OPTO_RESET_EVENTS` + обнуляет счётчик, `OPTO_LAST_EVENT` сообщает верный канал при + срабатывании нескольких каналов подряд. + +## Гарантии + +- Физическое замыкание/размыкание оптопары надёжно регистрируется через + ISR + программный debounce (10 мс) и доходит до пользовательского + callback. +- Каналы физически и логически независимы — воздействие на один не влияет + на показания и события других. +- Без внешнего воздействия состояние стабильно `INACTIVE` на всех каналах. + +## Запуск + +```bash +just build::build-hil # в devcontainer: собрать HIL-прошивки +just host::m5-deploy # один раз / после правок agent.py: залить агент на M5 +just host::hil-opto # на хосте: прошить по SWD и прогнать тест +``` + +Требует MicroPython v1.25 на M5StampPLC (`tools/hil/m5/firmware/v1.25/`) — +в v1.27 модуль CAN недоступен и `agent.py` не стартует корректно. diff --git a/tests/target/hil_opto/main.c b/tests/target/hil_opto/main.c index 619a6f8..ff3d4ec 100644 --- a/tests/target/hil_opto/main.c +++ b/tests/target/hil_opto/main.c @@ -12,7 +12,9 @@ * OPTO_RESET_EVENTS -> OK (сбросить счётчики) * * Стенд: - * M5StampPLC: RLY2->EXT_IN1(ch1), RLY3->EXT_IN2(ch2), RLY4->RS_RX(ch3) + * M5StampPLC: RLY2->RS_RX(ch3), RLY3->EXT_IN1(ch1), RLY4->EXT_IN2(ch2) + * (маппинг реле — см. _OPTO_TO_RELAY в tools/hil/m5/agent.py и + * docs/testing/hil/HIL_BENCH.md) * * ВАЖНО: bsp_opto_process() вызывается в каждой итерации main loop. * CLI_RX_TIMEOUT=10ms для быстрого цикла обработки debounce. diff --git a/tests/target/hil_usb_cdc/README.md b/tests/target/hil_usb_cdc/README.md new file mode 100644 index 0000000..98c658e --- /dev/null +++ b/tests/target/hil_usb_cdc/README.md @@ -0,0 +1,58 @@ +# test_hil_usb_cdc + +## Модуль под тестом + +`bsp/usb_cdc` — HIL-прошивка `test_hil_usb_cdc` (`main.c`), поднимающая два +независимых канала: UART (MCU-Link VCOM) — управляющий (`PING`, +`USB_READY`), и USB CDC (порт платы таргета) — тестируемый эхо-канал: +всё, что пришло по USB CDC, отправляется обратно (`usb_cdc_echo_process()` +в главном цикле). Прошивка не содержит проверок — она исполняет команды и +эхо; assert'ы находятся в pytest на хосте. + +## Категория + +HIL, требует физическое USB-подключение платы таргета к хосту (порт +задаётся `HIL_USB_CDC_PORT`). M5StampPLC стенд не используется как +источник сигнала, но нужен для управления питанием таргета (RLY1). + +## Моки + +Программных моков нет — тестируется реальный стек USB CDC ACM (enumeration, +DTR, bulk-транзакции). Все проверки — в `tools/hil/05_test_usb_cdc.py`, +через фикстуры `conftest.py`: `uart_hil_usb_cdc` (управляющий UART, ждёт +`READY`) и `usb_cdc_port` (тестируемый CDC-порт — ждёт появления после +enumeration и выставляет DTR). + +## Что проверяется + +- Управляющий UART отвечает (`PING` → `PONG`), прошивка запущена. +- `USB_READY` подтверждает, что enumeration завершён и хост открыл порт + (`bsp_usb_cdc_is_ready()`). +- Echo коротких данных, данных с `\r\n` (проверка, что канал бинарный, а + не построчный), полного диапазона байт `0x00..0xFF`, 10 итераций подряд + без потерь. +- Echo на границах размера пакета: 64 байта (full-speed bulk) и 512 байт + (high-speed bulk, `BSP_USB_CDC_MAX_PACKET_SIZE`). +- Управляющий UART-канал продолжает работать после серии USB CDC операций. + +## Гарантии + +- USB CDC ACM успешно перечисляется, и прошивка корректно сообщает об этом + через `bsp_usb_cdc_is_ready()`. +- Эхо-канал побайтово точен для печатных, бинарных данных и данных с + управляющими символами — канал не трактуется как построчный и не режет + данные по `\r`/`\n`. +- Корректная работа сохраняется на границах размера пакета (64 и 512 + байт), не только для «удобных» коротких сообщений. +- Активность на USB CDC не блокирует и не роняет параллельный + UART-канал. + +## Запуск + +```bash +just build::build-hil # в devcontainer: собрать HIL-прошивки +just host::hil-usb-cdc # на хосте: прошить по SWD и прогнать тест +``` + +Требует переменные окружения `HIL_USB_CDC_PORT` (порт USB CDC устройства +таргета) и опционально `HIL_USB_CDC_BAUD` (по умолчанию 115200) в `.env`. diff --git a/tests/target/host_uart/README.md b/tests/target/host_uart/README.md new file mode 100644 index 0000000..cb55e46 --- /dev/null +++ b/tests/target/host_uart/README.md @@ -0,0 +1,54 @@ +# test_host_uart + +## Модуль под тестом + +`bsp/uart_host` — HIL-прошивка `test_host_uart` (`main.c`), поднимающая +текстовый CLI (`PING`, `ECHO `, `UART_BUF_SIZE`) поверх реального +LPUART1 (MCU-Link VCOM). Сама прошивка не содержит проверок — она только +исполняет команды; assert'ы находятся в pytest на хосте (см. «Моки»). + +## Категория + +HIL (Hardware-in-the-Loop), автономный — стимул не нужен, достаточно платы +таргета и SWD/VCOM подключения через MCU-Link. M5StampPLC используется +только для управления питанием таргета (реле RLY1), не как источник +сигнала. + +## Моки + +Программных моков нет — тестируется реальный физический UART. +Роль «тестового окружения» играет pytest-стек в `tools/hil/`: + +- `conftest.py` — фикстура `loaded_host_uart` грузит ELF на таргет по SWD + через pyOCD, фикстура `uart` открывает VCOM-порт и ждёт строку `READY` от + прошивки, хелпер `uart_cmd()` шлёт команду и читает одну строку ответа. +- `01_test_uart.py` — все проверки (`assert`) и сценарии. + +## Что проверяется + +- Канал host↔target работает (`PING` → `PONG`), в том числе 10 раз подряд + без потерь и зависаний. +- `ECHO` не искажает данные: простой текст, текст с пробелами, цифры, + payload, близкий к `CLI_LINE_MAX`. +- `UART_BUF_SIZE` возвращает значение, совпадающее с фактически + скомпилированным `BSP_UART_HOST_RX_BUFFER_SIZE` (512, задаётся через + `target_compile_definitions` в CMake). +- Неизвестная команда даёт `ERR_UNKNOWN`, после чего канал продолжает + работать (`PING` снова отвечает `PONG`). + +## Гарантии + +- Канал host↔target не виснет ни после серии команд, ни после неизвестной + команды. +- `ECHO` передаёт полезную нагрузку побайтово точно, без обрезки, вплоть до + почти полного `CLI_LINE_MAX`. +- Размер RX-буфера, о котором сообщает прошивка, соответствует реально + скомпилированному значению — расхождение между прошивкой и ожиданиями + хоста будет замечено. + +## Запуск + +```bash +just build::build-hil # в devcontainer: собрать HIL-прошивки +just host::hil-uart # на хосте: прошить по SWD и прогнать тест +``` diff --git a/tools/hil/02_test_opto.py b/tools/hil/02_test_opto.py index 509bd21..97dd937 100644 --- a/tools/hil/02_test_opto.py +++ b/tools/hil/02_test_opto.py @@ -3,9 +3,10 @@ test_opto.py — HIL тест bsp_opto через M5StampPLC. Стенд: M5StampPLC реле → оптопары таргета (PS2801-4, active-HIGH, неинвертирующие): - RLY2 → EXT_IN1 (ch1, BSP_OPTO_CH_IN1) - RLY3 → EXT_IN2 (ch2, BSP_OPTO_CH_IN2) - RLY4 → RS_RX (ch3, BSP_OPTO_CH_RS) + RLY2 → RS_RX (ch3, BSP_OPTO_CH_RS) + RLY3 → EXT_IN1 (ch1, BSP_OPTO_CH_IN1) + RLY4 → EXT_IN2 (ch2, BSP_OPTO_CH_IN2) + (маппинг реле — источник истины: _OPTO_TO_RELAY в tools/hil/m5/agent.py) Цепочка фикстур (scope=module, создаются один раз на весь файл):