# bootloader: target test docs

This commit is contained in:
Dmitry Akimov 2026-07-09 10:07:04 +03:00
parent 6a643ae790
commit db2c573642
7 changed files with 305 additions and 4 deletions

View file

@ -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
```
Тест интерактивный — требует присутствия оператора у стенда для нажатия
кнопок по подсказкам в терминале.

View file

@ -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 недоступен.

View file

@ -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` не стартует корректно.

View file

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

View file

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

View file

@ -0,0 +1,54 @@
# test_host_uart
## Модуль под тестом
`bsp/uart_host` — HIL-прошивка `test_host_uart` (`main.c`), поднимающая
текстовый CLI (`PING`, `ECHO <text>`, `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 и прогнать тест
```

View file

@ -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, создаются один раз на весь файл):