# Created USB-CDC(ACM) stack + HIL tests
This commit is contained in:
parent
dc5cfac386
commit
4b190eba79
10 changed files with 325 additions and 3 deletions
|
|
@ -46,3 +46,7 @@ HIL_READY_TIMEOUT=5.0
|
||||||
HIL_M5_TIMEOUT=3.0
|
HIL_M5_TIMEOUT=3.0
|
||||||
HIL_PYOCD_FREQUENCY=1000000
|
HIL_PYOCD_FREQUENCY=1000000
|
||||||
HIL_BUILD_DIR=build/target-debug
|
HIL_BUILD_DIR=build/target-debug
|
||||||
|
# Настройки порта USB CDC ACM на таргете
|
||||||
|
HIL_USB_CDC_PORT=/dev/cu.usbmodemZZZZ
|
||||||
|
HIL_USB_CDC_BAUD=115200
|
||||||
|
HIL_USB_CDC_TIMEOUT=5.0
|
||||||
|
|
@ -174,6 +174,7 @@
|
||||||
"test_host_uart",
|
"test_host_uart",
|
||||||
"test_hil_button",
|
"test_hil_button",
|
||||||
"test_hil_can",
|
"test_hil_can",
|
||||||
|
"test_hil_usb_cdc",
|
||||||
"test_hil_opto"
|
"test_hil_opto"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
const uint16_t DELAY_MS = 100;
|
const uint16_t DELAY_MS = 100;
|
||||||
|
|
@ -38,6 +38,12 @@ int main(void)
|
||||||
LOG_I("BOOT", "USB CDC ACM ready");
|
LOG_I("BOOT", "USB CDC ACM ready");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_connection_established == true)
|
||||||
|
{
|
||||||
|
const char *msg = "Hello from TFT Board\r\n";
|
||||||
|
bsp_usb_cdc_write((const uint8_t *) msg, strlen(msg));
|
||||||
|
}
|
||||||
|
|
||||||
bsp_usb_cdc_poll();
|
bsp_usb_cdc_poll();
|
||||||
bsp_led_toggle(LED_HEARTBEAT);
|
bsp_led_toggle(LED_HEARTBEAT);
|
||||||
bsp_delay(DELAY_MS);
|
bsp_delay(DELAY_MS);
|
||||||
|
|
|
||||||
|
|
@ -334,7 +334,7 @@ _hil_build := BUILD_DIR / "target-debug"
|
||||||
[group('hil')]
|
[group('hil')]
|
||||||
hil-run:
|
hil-run:
|
||||||
HIL_BUILD_DIR={{ _hil_build }} \
|
HIL_BUILD_DIR={{ _hil_build }} \
|
||||||
uv run --directory {{ HIL_DIR }} pytest -m "not interactive" -v
|
uv run --directory {{ HIL_DIR }} pytest -m "not interactive and not usb_vcom" -v
|
||||||
|
|
||||||
[doc('Запустить все интерактивные HIL-тесты: кнопки, дисплей')]
|
[doc('Запустить все интерактивные HIL-тесты: кнопки, дисплей')]
|
||||||
[group('hil')]
|
[group('hil')]
|
||||||
|
|
@ -366,6 +366,12 @@ hil-can:
|
||||||
HIL_BUILD_DIR={{ _hil_build }} \
|
HIL_BUILD_DIR={{ _hil_build }} \
|
||||||
uv run --directory {{ HIL_DIR }} pytest 03_test_can.py -v
|
uv run --directory {{ HIL_DIR }} pytest 03_test_can.py -v
|
||||||
|
|
||||||
|
[doc('Запустить HIL-тест USB CDC')]
|
||||||
|
[group('hil')]
|
||||||
|
hil-usb-cdc:
|
||||||
|
HIL_BUILD_DIR={{ _hil_build }} \
|
||||||
|
uv run --directory {{ HIL_DIR }} pytest 05_test_usb_cdc.py -v
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# ГРУППА: debug — GDB-сервер для отладки из VSCode (devcontainer)
|
# ГРУППА: debug — GDB-сервер для отладки из VSCode (devcontainer)
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,4 @@ add_subdirectory(host_uart)
|
||||||
add_subdirectory(hil_opto)
|
add_subdirectory(hil_opto)
|
||||||
add_subdirectory(hil_can)
|
add_subdirectory(hil_can)
|
||||||
add_subdirectory(hil_button)
|
add_subdirectory(hil_button)
|
||||||
|
add_subdirectory(hil_usb_cdc)
|
||||||
|
|
|
||||||
21
tests/target/hil_usb_cdc/CMakeLists.txt
Normal file
21
tests/target/hil_usb_cdc/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
set(TARGET_NAME test_hil_usb_cdc)
|
||||||
|
|
||||||
|
add_executable(${TARGET_NAME} main.c ${BSP_GENERATED}/clock_config.c
|
||||||
|
${BSP_STARTUP_FILE} ${BSP_SYSCALLS_FILE})
|
||||||
|
|
||||||
|
target_link_options(
|
||||||
|
${TARGET_NAME}
|
||||||
|
PRIVATE
|
||||||
|
-T${CMAKE_SOURCE_DIR}/cmake/linker/MIMXRT1052xxxxx_ram.ld
|
||||||
|
-Wl,--gc-sections
|
||||||
|
-Wl,--print-memory-usage
|
||||||
|
-Wl,-Map=${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}.map)
|
||||||
|
|
||||||
|
target_link_libraries(${TARGET_NAME} PRIVATE bsp_boot_ram bsp_board bsp_led
|
||||||
|
bsp_tick bsp_uart_host bsp_usb_cdc)
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
TARGET ${TARGET_NAME}
|
||||||
|
POST_BUILD
|
||||||
|
COMMAND ${CMAKE_SIZE} $<TARGET_FILE:${TARGET_NAME}>
|
||||||
|
COMMENT "Size: ${TARGET_NAME}")
|
||||||
122
tests/target/hil_usb_cdc/main.c
Normal file
122
tests/target/hil_usb_cdc/main.c
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
#include "board.h"
|
||||||
|
#include "bsp/led.h"
|
||||||
|
#include "bsp/tick.h"
|
||||||
|
#include "bsp/uart_host.h"
|
||||||
|
#include "bsp/usb_cdc.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define CLI_BAUD_RATE 115200U
|
||||||
|
#define CLI_LINE_MAX 128U
|
||||||
|
#define CLI_RX_TIMEOUT 50U
|
||||||
|
|
||||||
|
/* ---- UART CLI (управляющий канал через MCU-Link VCOM) ---- */
|
||||||
|
|
||||||
|
static size_t cli_read_line(uint8_t *p_buf, size_t max_len)
|
||||||
|
{
|
||||||
|
size_t pos = 0U;
|
||||||
|
|
||||||
|
while (pos < (max_len - 1U))
|
||||||
|
{
|
||||||
|
int32_t byte = bsp_uart_host_read_byte(CLI_RX_TIMEOUT);
|
||||||
|
|
||||||
|
if (byte < 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ((char) byte == '\r')
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((char) byte == '\n')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
p_buf[pos++] = (uint8_t) byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
p_buf[pos] = '\0';
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cli_process_line(const char *p_line)
|
||||||
|
{
|
||||||
|
if (strncmp(p_line, "PING", 4U) == 0)
|
||||||
|
{
|
||||||
|
bsp_uart_host_write_str("PONG\r\n");
|
||||||
|
}
|
||||||
|
else if (strncmp(p_line, "USB_READY", 9U) == 0)
|
||||||
|
{
|
||||||
|
/* Проверка что USB CDC enumeration завершён и хост открыл порт. */
|
||||||
|
if (bsp_usb_cdc_is_ready())
|
||||||
|
{
|
||||||
|
bsp_uart_host_write_str("1\r\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bsp_uart_host_write_str("0\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (p_line[0] != '\0')
|
||||||
|
{
|
||||||
|
bsp_uart_host_write_str("ERR_UNKNOWN\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- USB CDC echo (тестируемый канал) ---- */
|
||||||
|
|
||||||
|
static void usb_cdc_echo_process(void)
|
||||||
|
{
|
||||||
|
static uint8_t s_usb_rx_buf[BSP_USB_CDC_MAX_PACKET_SIZE];
|
||||||
|
|
||||||
|
size_t n = bsp_usb_cdc_read(s_usb_rx_buf, sizeof(s_usb_rx_buf));
|
||||||
|
|
||||||
|
if ((n > 0U) && bsp_usb_cdc_write_ready())
|
||||||
|
{
|
||||||
|
bsp_usb_cdc_write(s_usb_rx_buf, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- main ---- */
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
board_hw_init();
|
||||||
|
bsp_tick_init();
|
||||||
|
bsp_led_init();
|
||||||
|
bsp_uart_host_init(CLI_BAUD_RATE);
|
||||||
|
|
||||||
|
/* Инициализация USB CDC — тестируемый модуль. */
|
||||||
|
bsp_usb_cdc_init();
|
||||||
|
|
||||||
|
bsp_led_on(LED_HEARTBEAT);
|
||||||
|
|
||||||
|
/* Шлём READY пока хост не открыл UART порт. */
|
||||||
|
while (bsp_uart_host_rx_available() == 0U)
|
||||||
|
{
|
||||||
|
bsp_uart_host_write_str("READY\r\n");
|
||||||
|
bsp_delay(200U);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t s_line_buf[CLI_LINE_MAX];
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
/* 1. Обработка UART CLI команд. */
|
||||||
|
size_t len = cli_read_line(s_line_buf, sizeof(s_line_buf));
|
||||||
|
|
||||||
|
if (len > 0U)
|
||||||
|
{
|
||||||
|
cli_process_line((const char *) s_line_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. USB CDC echo — всё что пришло по USB, отправляем обратно. */
|
||||||
|
usb_cdc_echo_process();
|
||||||
|
|
||||||
|
/* 3. Поллинг USB стека. */
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
103
tools/hil/05_test_usb_cdc.py
Normal file
103
tools/hil/05_test_usb_cdc.py
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
"""
|
||||||
|
05_test_usb_cdc.py — HIL тест bsp_usb_cdc.
|
||||||
|
|
||||||
|
Схема:
|
||||||
|
UART (MCU-Link VCOM) — управляющий канал (PING/PONG, USB_READY)
|
||||||
|
USB CDC (TFT Board) — тестируемый канал (echo)
|
||||||
|
|
||||||
|
conftest.loaded_hil_usb_cdc → pyocd_utils: load ELF
|
||||||
|
conftest.uart_hil_usb_cdc → UART: ждать READY
|
||||||
|
conftest.usb_cdc_port → USB CDC: открыть порт, установить DTR
|
||||||
|
|
||||||
|
Запуск:
|
||||||
|
just host::hil-usb-cdc
|
||||||
|
uv run --directory tools/hil pytest 05_test_usb_cdc.py -v
|
||||||
|
|
||||||
|
Переменные окружения:
|
||||||
|
HIL_USB_CDC_PORT — порт USB CDC устройства (/dev/cu.usbmodemXXXX)
|
||||||
|
HIL_USB_CDC_BAUD — baudrate (по умолчанию 115200)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from conftest import uart_cmd
|
||||||
|
|
||||||
|
# Таймаут чтения USB CDC — достаточный для HS bulk transfer.
|
||||||
|
USB_CDC_READ_TIMEOUT_S = 1.0
|
||||||
|
|
||||||
|
|
||||||
|
def usb_cdc_echo(ser, data: bytes, timeout_s: float = USB_CDC_READ_TIMEOUT_S) -> bytes:
|
||||||
|
"""Отправить данные по USB CDC и прочитать echo."""
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
ser.write(data)
|
||||||
|
ser.flush()
|
||||||
|
|
||||||
|
result = b""
|
||||||
|
deadline = time.monotonic() + timeout_s
|
||||||
|
|
||||||
|
while len(result) < len(data) and time.monotonic() < deadline:
|
||||||
|
chunk = ser.read(len(data) - len(result))
|
||||||
|
if chunk:
|
||||||
|
result += chunk
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@pytest.mark.usb_vcom
|
||||||
|
class TestUsbCdcBasic:
|
||||||
|
"""Базовые тесты USB CDC ACM — enumeration, echo, payload sizes."""
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _setup(self, uart_hil_usb_cdc, usb_cdc_port):
|
||||||
|
self.uart = uart_hil_usb_cdc
|
||||||
|
self.cdc = usb_cdc_port
|
||||||
|
|
||||||
|
# ---- UART control channel ----
|
||||||
|
|
||||||
|
def test_uart_ping(self):
|
||||||
|
"""UART канал работает — прошивка запущена."""
|
||||||
|
assert uart_cmd(self.uart, "PING") == "PONG"
|
||||||
|
|
||||||
|
def test_usb_ready(self):
|
||||||
|
"""USB CDC enumeration завершён, хост подключён."""
|
||||||
|
assert uart_cmd(self.uart, "USB_READY") == "1"
|
||||||
|
|
||||||
|
# ---- USB CDC echo ----
|
||||||
|
|
||||||
|
def test_echo_short(self):
|
||||||
|
"""Echo 5 байт — минимальный пакет."""
|
||||||
|
data = b"hello"
|
||||||
|
assert usb_cdc_echo(self.cdc, data) == data
|
||||||
|
|
||||||
|
def test_echo_with_newlines(self):
|
||||||
|
"""Echo с CR/LF — проверка что USB CDC бинарный, не line-based."""
|
||||||
|
data = b"line1\r\nline2\r\n"
|
||||||
|
assert usb_cdc_echo(self.cdc, data) == data
|
||||||
|
|
||||||
|
def test_echo_binary(self):
|
||||||
|
"""Echo бинарных данных — все значения 0x00..0xFF."""
|
||||||
|
data = bytes(range(256))
|
||||||
|
assert usb_cdc_echo(self.cdc, data) == data
|
||||||
|
|
||||||
|
def test_echo_repeated(self):
|
||||||
|
"""10 echo подряд — нет зависаний, нет потерь."""
|
||||||
|
for i in range(10):
|
||||||
|
data = f"packet_{i:03d}".encode()
|
||||||
|
result = usb_cdc_echo(self.cdc, data)
|
||||||
|
assert result == data, f"Сбой на итерации {i}: {result!r} != {data!r}"
|
||||||
|
|
||||||
|
def test_echo_64_bytes(self):
|
||||||
|
"""Echo 64 байт — граница FS bulk packet."""
|
||||||
|
data = b"A" * 64
|
||||||
|
assert usb_cdc_echo(self.cdc, data) == data
|
||||||
|
|
||||||
|
def test_echo_512_bytes(self):
|
||||||
|
"""Echo 512 байт — граница HS bulk packet (BSP_USB_CDC_MAX_PACKET_SIZE)."""
|
||||||
|
data = bytes([i & 0xFF for i in range(512)])
|
||||||
|
assert usb_cdc_echo(self.cdc, data) == data
|
||||||
|
|
||||||
|
# ---- Error recovery ----
|
||||||
|
|
||||||
|
def test_uart_after_cdc(self):
|
||||||
|
"""UART канал работает после серии USB CDC операций."""
|
||||||
|
assert uart_cmd(self.uart, "PING") == "PONG"
|
||||||
|
|
@ -17,7 +17,7 @@ import serial
|
||||||
|
|
||||||
import env_config as cfg
|
import env_config as cfg
|
||||||
from pyocd_utils import flexram_init, load_elf, open_target, run_from_vectors
|
from pyocd_utils import flexram_init, load_elf, open_target, run_from_vectors
|
||||||
|
import os
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Задержка после включения питания таргета (мс стабилизации + POR)
|
# Задержка после включения питания таргета (мс стабилизации + POR)
|
||||||
|
|
@ -263,6 +263,13 @@ def loaded_hil_can(request: pytest.FixtureRequest, m5: M5Agent) -> None:
|
||||||
Path(cfg.BUILD_DIR) / "tests/target/hil_can/test_hil_can.elf",
|
Path(cfg.BUILD_DIR) / "tests/target/hil_can/test_hil_can.elf",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def loaded_hil_usb_cdc(request: pytest.FixtureRequest, m5: M5Agent) -> None:
|
||||||
|
_load_elf(
|
||||||
|
request,
|
||||||
|
Path(cfg.BUILD_DIR) / "tests/target/hil_usb_cdc/test_hil_usb_cdc.elf",
|
||||||
|
)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Фикстуры UART
|
# Фикстуры UART
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
@ -301,6 +308,56 @@ def uart_button(
|
||||||
ser = _open_uart_and_wait_ready(request)
|
ser = _open_uart_and_wait_ready(request)
|
||||||
yield ser
|
yield ser
|
||||||
ser.close()
|
ser.close()
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def uart_hil_usb_cdc(
|
||||||
|
request: pytest.FixtureRequest,
|
||||||
|
loaded_hil_usb_cdc,
|
||||||
|
) -> Generator[serial.Serial, None, None]:
|
||||||
|
ser = _open_uart_and_wait_ready(request)
|
||||||
|
yield ser
|
||||||
|
ser.close()
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# hil_usb_cdc — USB CDC ACM тест (два канала: UART + USB CDC)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def usb_cdc_port(
|
||||||
|
uart_hil_usb_cdc,
|
||||||
|
) -> Generator[serial.Serial, None, None]:
|
||||||
|
"""Открыть USB CDC порт таргета. Ждёт появления порта и DTR ready."""
|
||||||
|
import time
|
||||||
|
|
||||||
|
port = os.environ.get("HIL_USB_CDC_PORT", "")
|
||||||
|
if not port:
|
||||||
|
pytest.skip("HIL_USB_CDC_PORT not set")
|
||||||
|
|
||||||
|
baud = int(os.environ.get("HIL_USB_CDC_BAUD", "115200"))
|
||||||
|
timeout_s = float(os.environ.get("HIL_USB_CDC_TIMEOUT", "5.0"))
|
||||||
|
|
||||||
|
# Ждём появления USB CDC порта (enumeration после загрузки ELF).
|
||||||
|
deadline = time.monotonic() + timeout_s
|
||||||
|
ser = None
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
try:
|
||||||
|
ser = serial.Serial(port, baud, timeout=0.5)
|
||||||
|
break
|
||||||
|
except serial.SerialException:
|
||||||
|
time.sleep(0.3)
|
||||||
|
|
||||||
|
if ser is None:
|
||||||
|
pytest.fail(f"USB CDC port {port} not available after {timeout_s}s")
|
||||||
|
|
||||||
|
# Установить DTR чтобы firmware увидела DTE presence.
|
||||||
|
ser.dtr = True
|
||||||
|
time.sleep(0.3)
|
||||||
|
|
||||||
|
# Сбросить входной буфер — могут быть мусорные байты от enumeration.
|
||||||
|
ser.reset_input_buffer()
|
||||||
|
|
||||||
|
yield ser
|
||||||
|
ser.close()
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Утилита для тестов
|
# Утилита для тестов
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ python_files = ["test_*.py", "*_test.py", "??_test_*.py"]
|
||||||
markers = [
|
markers = [
|
||||||
"gpio: тесты GPIO",
|
"gpio: тесты GPIO",
|
||||||
"interactive: тест требует действий оператора",
|
"interactive: тест требует действий оператора",
|
||||||
|
"usb_vcom: основной канал связи USB_CDC",
|
||||||
"uart: тесты UART CLI",
|
"uart: тесты UART CLI",
|
||||||
"opto: тесты оптоизолированных входов",
|
"opto: тесты оптоизолированных входов",
|
||||||
"m5: тесты с участием M5StampPLC",
|
"m5: тесты с участием M5StampPLC",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue