# fixes: pyformat + O1 release opt level
This commit is contained in:
parent
9be4a2aecc
commit
bbd02f840e
15 changed files with 322 additions and 177 deletions
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"[python]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "charliermarsh.ruff"
|
||||
}
|
||||
}
|
||||
|
|
@ -109,17 +109,17 @@ set(CMAKE_ASM_FLAGS_DEBUG
|
|||
CACHE INTERNAL "")
|
||||
|
||||
set(CMAKE_C_FLAGS_RELEASE
|
||||
"-O3 -DNDEBUG"
|
||||
"-O1 -DNDEBUG"
|
||||
CACHE INTERNAL "")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE
|
||||
"-O3 -DNDEBUG"
|
||||
"-O1 -DNDEBUG"
|
||||
CACHE INTERNAL "")
|
||||
set(CMAKE_ASM_FLAGS_RELEASE
|
||||
""
|
||||
CACHE INTERNAL "")
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# C runtime библиотека — newlib-nano (меньше размер, подходит для embedded)
|
||||
# C runtime библиотека — newlib-nano
|
||||
# -----------------------------------------------------------------------------
|
||||
# --specs=nano.specs — использовать newlib-nano: облегчённая stdlib,
|
||||
# меньший размер printf/malloc -Wl,--no-warn-rwx-segments — подавить
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ RELAY_OFF_S = 0.1
|
|||
# Вспомогательные функции
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def opto_read(ser, ch: int) -> str:
|
||||
"""Прочитать состояние канала (1-based). Возвращает 'ACTIVE' или 'INACTIVE'."""
|
||||
return uart_cmd(ser, f"OPTO_READ {ch}")
|
||||
|
|
@ -87,6 +88,7 @@ def all_off_and_confirm(m5, ser, timeout_s: float = 1.0) -> None:
|
|||
# Проверка каналов связи
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestOptoConnectivity:
|
||||
"""Базовая проверка: таргет и M5 отвечают."""
|
||||
|
||||
|
|
@ -108,6 +110,7 @@ class TestOptoConnectivity:
|
|||
# Состояние по умолчанию (все реле выключены)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestOptoReadDefault:
|
||||
"""Все каналы INACTIVE без внешнего воздействия."""
|
||||
|
||||
|
|
@ -132,6 +135,7 @@ class TestOptoReadDefault:
|
|||
# Активация / деактивация каналов
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestOptoActivateDeactivate:
|
||||
"""Активация/деактивация каждого канала по отдельности через M5."""
|
||||
|
||||
|
|
@ -164,14 +168,16 @@ class TestOptoActivateDeactivate:
|
|||
self.m5.opto_set(ch, True)
|
||||
time.sleep(RELAY_ON_S)
|
||||
for other in others:
|
||||
assert opto_read(self.ser, other) == "INACTIVE", \
|
||||
assert opto_read(self.ser, other) == "INACTIVE", (
|
||||
f"ch{other} должен быть INACTIVE когда активен только ch{ch}"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Callback-события (проверяет ISR + debounce + bsp_opto_process)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestOptoEvents:
|
||||
"""
|
||||
Проверяет что прерывание + bsp_opto_process() + коллбэк корректно
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ CAN_RECV_EMPTY_MS = 100
|
|||
# Вспомогательные функции
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def can_send_m5(m5, can_id: int, data: list[int], ext: bool = False) -> None:
|
||||
"""M5 отправляет CAN-фрейм на шину."""
|
||||
m5.can_send(can_id, data, ext=ext)
|
||||
|
|
@ -93,7 +94,7 @@ def can_recv_target(ser, timeout_ms: int = CAN_RECV_TIMEOUT_MS) -> dict | None:
|
|||
can_id = int(parts[0])
|
||||
ext = bool(int(parts[1]))
|
||||
dlc = int(parts[2])
|
||||
data = [int(b) for b in parts[3:3 + dlc]]
|
||||
data = [int(b) for b in parts[3 : 3 + dlc]]
|
||||
return {"id": can_id, "ext": ext, "dlc": dlc, "data": data}
|
||||
|
||||
|
||||
|
|
@ -112,6 +113,7 @@ def reset_events(ser) -> None:
|
|||
# Проверка каналов связи
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestCanConnectivity:
|
||||
"""Базовая проверка: таргет и M5 отвечают."""
|
||||
|
||||
|
|
@ -131,13 +133,16 @@ class TestCanConnectivity:
|
|||
def test_m5_can_available(self):
|
||||
"""M5 сообщает что CAN инициализирован (can_ok=true в info)."""
|
||||
info = self.m5.info()
|
||||
assert info.get("can_ok"), "M5 CAN не инициализирован — проверьте трансивер и agent.py"
|
||||
assert info.get("can_ok"), (
|
||||
"M5 CAN не инициализирован — проверьте трансивер и agent.py"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# M5 → таргет
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestCanM5ToTarget:
|
||||
"""M5 отправляет фрейм — таргет принимает."""
|
||||
|
||||
|
|
@ -200,6 +205,7 @@ class TestCanM5ToTarget:
|
|||
# Таргет → M5
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestCanTargetToM5:
|
||||
"""Таргет отправляет фрейм — M5 принимает."""
|
||||
|
||||
|
|
@ -249,14 +255,16 @@ class TestCanTargetToM5:
|
|||
payload = [0x00, 0xFF, 0x55, 0xAA, 0x0F, 0xF0, 0x01, 0xFE]
|
||||
assert can_send_target(self.ser, 0x300, payload) == "OK"
|
||||
frame = can_recv_m5(self.m5)
|
||||
assert frame["data"] == payload, \
|
||||
assert frame["data"] == payload, (
|
||||
f"Данные искажены: ожидали {payload}, получили {frame['data']}"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Фильтрация
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestCanFiltering:
|
||||
"""
|
||||
Проверка фильтрации по ID: только нужные фреймы проходят,
|
||||
|
|
@ -289,7 +297,9 @@ class TestCanFiltering:
|
|||
can_send_m5(self.m5, target_id, [0xAA])
|
||||
frame = can_recv_target(self.ser)
|
||||
assert frame is not None, "Фрейм с нужным ID не принят"
|
||||
assert frame["id"] == target_id, f"Получен ID {frame['id']:#x}, ожидали {target_id:#x}"
|
||||
assert frame["id"] == target_id, (
|
||||
f"Получен ID {frame['id']:#x}, ожидали {target_id:#x}"
|
||||
)
|
||||
|
||||
def test_filter_wrong_id_blocked(self):
|
||||
"""set_filter с точной маской блокирует другой ID."""
|
||||
|
|
@ -299,8 +309,9 @@ class TestCanFiltering:
|
|||
|
||||
can_send_m5(self.m5, wrong_id, [0xBB])
|
||||
frame = can_recv_target(self.ser, timeout_ms=CAN_RECV_EMPTY_MS)
|
||||
assert frame is None, \
|
||||
assert frame is None, (
|
||||
f"Фрейм с ID {wrong_id:#x} прошёл фильтр, хотя не должен был"
|
||||
)
|
||||
|
||||
def test_filter_mask_passes_group(self):
|
||||
"""Маска 0x7F0 пропускает группу ID с одинаковыми старшими битами."""
|
||||
|
|
@ -353,6 +364,7 @@ class TestCanFiltering:
|
|||
# STD и EXT раздельно
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestCanFrameTypes:
|
||||
"""STD и EXT фреймы не перепутываются — is_extended корректен."""
|
||||
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ def _operator_prompt(msg: str) -> None:
|
|||
input(f"\n >>> {msg}\n Нажмите Enter когда готово...")
|
||||
time.sleep(DEBOUNCE_SETTLE_S)
|
||||
|
||||
|
||||
@pytest.mark.interactive
|
||||
class TestHilButton:
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _setup(self, uart_button):
|
||||
self.ser = uart_button
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ def usb_cdc_echo(ser, data: bytes, timeout_s: float = USB_CDC_READ_TIMEOUT_S) ->
|
|||
|
||||
return result
|
||||
|
||||
|
||||
@pytest.mark.usb_vcom
|
||||
class TestUsbCdcBasic:
|
||||
"""Базовые тесты USB CDC ACM — enumeration, echo, payload sizes."""
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ CAN_TX_DATA = [0xCA, 0xFE, 0xBA, 0xBE]
|
|||
# Таймаут для M5 can_recv — с запасом относительно CAN_RX_TIMEOUT_MS (500 мс) в прошивке
|
||||
CAN_RECV_TIMEOUT_MS = 1000
|
||||
|
||||
|
||||
@pytest.mark.usb_vcom
|
||||
class TestFirmwareCan:
|
||||
"""Тест CAN-интерфейса через firmware_test CDC (протокол v2)."""
|
||||
|
|
@ -37,7 +38,6 @@ class TestFirmwareCan:
|
|||
self.cdc = firmware_cdc
|
||||
self.m5 = m5
|
||||
|
||||
|
||||
def _on_confirm(self, confirm_id: str) -> bool:
|
||||
"""
|
||||
Оркестратор CAN-теста.
|
||||
|
|
@ -61,12 +61,15 @@ class TestFirmwareCan:
|
|||
try:
|
||||
frame = self.m5.can_recv(timeout_ms=CAN_RECV_TIMEOUT_MS)
|
||||
import logging
|
||||
|
||||
logging.getLogger(__name__).info(
|
||||
"M5 received frame: id=0x%X data=%s",
|
||||
frame.get("id", -1), frame.get("data", [])
|
||||
frame.get("id", -1),
|
||||
frame.get("data", []),
|
||||
)
|
||||
except (TimeoutError, RuntimeError) as e:
|
||||
import logging
|
||||
|
||||
logging.getLogger(__name__).warning("M5 can_recv failed: %s", e)
|
||||
return False
|
||||
|
||||
|
|
@ -91,7 +94,6 @@ class TestFirmwareCan:
|
|||
"""Базовая проверка CDC-канала."""
|
||||
self.cdc.ping()
|
||||
|
||||
|
||||
def test_can_pass(self) -> None:
|
||||
"""
|
||||
Полный прогон test_can: RX + TX через M5.
|
||||
|
|
@ -104,8 +106,7 @@ class TestFirmwareCan:
|
|||
)
|
||||
|
||||
assert result.get("status") == "pass", (
|
||||
f"test_can вернул {result.get('status')!r}: "
|
||||
f"{result.get('detail', '')}"
|
||||
f"test_can вернул {result.get('status')!r}: {result.get('detail', '')}"
|
||||
)
|
||||
|
||||
def test_can_rx_fail_no_frame(self) -> None:
|
||||
|
|
@ -131,6 +132,4 @@ class TestFirmwareCan:
|
|||
f"Ожидали fail, получили {result.get('status')!r}"
|
||||
)
|
||||
detail = result.get("detail", "")
|
||||
assert "no frame" in detail, (
|
||||
f"detail должен содержать 'no frame': {detail!r}"
|
||||
)
|
||||
assert "no frame" in detail, f"detail должен содержать 'no frame': {detail!r}"
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ _OPTO_ACTIONS: dict[str, tuple[int, bool]] = {
|
|||
"opto_rs_inactive": (3, False),
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.usb_vcom
|
||||
class TestFirmwareOpto:
|
||||
"""Тест оптовходов через firmware_test CDC (протокол v2)."""
|
||||
|
|
@ -86,8 +87,7 @@ class TestFirmwareOpto:
|
|||
)
|
||||
|
||||
assert result.get("status") == "pass", (
|
||||
f"test_opto вернул {result.get('status')!r}: "
|
||||
f"{result.get('detail', '')}"
|
||||
f"test_opto вернул {result.get('status')!r}: {result.get('detail', '')}"
|
||||
)
|
||||
|
||||
def test_opto_in1_fail_on_inactive(self) -> None:
|
||||
|
|
@ -111,6 +111,4 @@ class TestFirmwareOpto:
|
|||
f"Ожидали fail, получили {result.get('status')!r}"
|
||||
)
|
||||
detail = result.get("detail", "")
|
||||
assert "mismatch" in detail, (
|
||||
f"detail должен содержать 'mismatch': {detail!r}"
|
||||
)
|
||||
assert "mismatch" in detail, f"detail должен содержать 'mismatch': {detail!r}"
|
||||
|
|
|
|||
|
|
@ -105,9 +105,7 @@ class M5Agent:
|
|||
except json.JSONDecodeError:
|
||||
continue
|
||||
if not resp.get("ok"):
|
||||
raise RuntimeError(
|
||||
f"M5 error: {resp.get('err', '?')} (cmd={command})"
|
||||
)
|
||||
raise RuntimeError(f"M5 error: {resp.get('err', '?')} (cmd={command})")
|
||||
return resp
|
||||
|
||||
raise TimeoutError(f"M5: нет ответа на '{command}'")
|
||||
|
|
@ -124,6 +122,7 @@ class M5Agent:
|
|||
|
||||
def opto_all_off(self) -> None:
|
||||
self.cmd("opto_all_off")
|
||||
|
||||
def info(self) -> dict:
|
||||
"""Запросить информацию об агенте (включая can_ok)."""
|
||||
return self.cmd("info")
|
||||
|
|
@ -205,6 +204,7 @@ def m5(request: pytest.FixtureRequest) -> Generator[M5Agent, None, None]:
|
|||
# Фикстуры загрузки (scope=module — один раз на файл с тестами)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def loaded_host_uart(request: pytest.FixtureRequest, m5: M5Agent) -> None:
|
||||
"""
|
||||
|
|
@ -219,6 +219,7 @@ def loaded_host_uart(request: pytest.FixtureRequest, m5: M5Agent) -> None:
|
|||
Path(cfg.BUILD_DIR) / "tests/target/host_uart/test_host_uart.elf",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def loaded_hil_button(request: pytest.FixtureRequest, m5: M5Agent) -> None:
|
||||
"""
|
||||
|
|
@ -233,6 +234,7 @@ def loaded_hil_button(request: pytest.FixtureRequest, m5: M5Agent) -> None:
|
|||
Path(cfg.BUILD_DIR) / "tests/target/hil_button/test_hil_button.elf",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def loaded_hil_opto(request: pytest.FixtureRequest, m5: M5Agent) -> None:
|
||||
"""
|
||||
|
|
@ -249,6 +251,7 @@ def loaded_hil_opto(request: pytest.FixtureRequest, m5: M5Agent) -> None:
|
|||
Path(cfg.BUILD_DIR) / "tests/target/hil_opto/test_hil_opto.elf",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def loaded_hil_can(request: pytest.FixtureRequest, m5: M5Agent) -> None:
|
||||
"""
|
||||
|
|
@ -263,6 +266,7 @@ def loaded_hil_can(request: pytest.FixtureRequest, m5: M5Agent) -> None:
|
|||
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:
|
||||
"""
|
||||
|
|
@ -277,6 +281,7 @@ def loaded_hil_usb_cdc(request: pytest.FixtureRequest, m5: M5Agent) -> None:
|
|||
Path(cfg.BUILD_DIR) / "tests/target/hil_usb_cdc/test_hil_usb_cdc.elf",
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Фикстуры UART (установление соединения с VCOM программатора NXP MCU-Link)
|
||||
# Выполняется один раз на тест
|
||||
|
|
@ -288,18 +293,26 @@ _UART_FIXTURE_MAP = {
|
|||
"uart_button": "loaded_hil_button",
|
||||
"uart_hil_usb_cdc": "loaded_hil_usb_cdc",
|
||||
}
|
||||
|
||||
|
||||
def _make_uart_fixture(loaded_name: str):
|
||||
@pytest.fixture(scope="module")
|
||||
def _fixture(request: pytest.FixtureRequest) -> Generator[serial.Serial, None, None]:
|
||||
def _fixture(
|
||||
request: pytest.FixtureRequest,
|
||||
) -> Generator[serial.Serial, None, None]:
|
||||
request.getfixturevalue(loaded_name) # триггерит зависимость явно
|
||||
port = request.config.getoption("--vcom")
|
||||
with _uart_context(port, cfg.VCOM_BAUD, cfg.READY_TIMEOUT) as ser:
|
||||
yield ser
|
||||
|
||||
return _fixture
|
||||
|
||||
|
||||
# Регистрируем все фикстуры в пространстве имён модуля одной строкой
|
||||
for _name, _dep in _UART_FIXTURE_MAP.items():
|
||||
globals()[_name] = _make_uart_fixture(_dep)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Фикстуры USB CDC (открытие порта, установка DTR)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -318,13 +331,17 @@ def usb_cdc_port(
|
|||
ser = None
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
ser = serial.Serial(port=cfg.TARGET_VCOM_PORT, baudrate=cfg.TARGET_VCOM_BAUD, timeout=0.5)
|
||||
ser = serial.Serial(
|
||||
port=cfg.TARGET_VCOM_PORT, baudrate=cfg.TARGET_VCOM_BAUD, timeout=0.5
|
||||
)
|
||||
break
|
||||
except serial.SerialException:
|
||||
time.sleep(0.3)
|
||||
|
||||
if ser is None:
|
||||
pytest.fail(f"USB CDC port {cfg.TARGET_VCOM_PORT} not available after {cfg.TARGET_VCOM_TIMEOUT}s")
|
||||
pytest.fail(
|
||||
f"USB CDC port {cfg.TARGET_VCOM_PORT} not available after {cfg.TARGET_VCOM_TIMEOUT}s"
|
||||
)
|
||||
|
||||
try:
|
||||
# Установить DTR чтобы firmware увидела DTE presence.
|
||||
|
|
@ -354,6 +371,7 @@ def uart_cmd(ser: serial.Serial, cmd: str) -> str:
|
|||
# firmware_test CDC — фикстура для тестирования firmware_test через протокол v2
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class FirmwareCdc:
|
||||
"""
|
||||
Драйвер firmware_test протокола v2 (JSON-lines через USB CDC).
|
||||
|
|
@ -395,7 +413,9 @@ class FirmwareCdc:
|
|||
msg = self._readline(timeout_s=max(remaining, 0.1))
|
||||
if msg.get("type") == event_type:
|
||||
return msg
|
||||
log.debug("firmware_cdc: пропускаем %r (ждём %r)", msg.get("type"), event_type)
|
||||
log.debug(
|
||||
"firmware_cdc: пропускаем %r (ждём %r)", msg.get("type"), event_type
|
||||
)
|
||||
raise TimeoutError(
|
||||
f"firmware_cdc: событие {event_type!r} не получено за {timeout_s} с"
|
||||
)
|
||||
|
|
@ -504,8 +524,7 @@ def firmware_cdc(m5: M5Agent) -> Generator[FirmwareCdc, None, None]:
|
|||
|
||||
if ser is None:
|
||||
pytest.fail(
|
||||
f"CDC порт {cfg.TARGET_VCOM_PORT} недоступен "
|
||||
f"за {cfg.TARGET_VCOM_TIMEOUT} с"
|
||||
f"CDC порт {cfg.TARGET_VCOM_PORT} недоступен за {cfg.TARGET_VCOM_TIMEOUT} с"
|
||||
)
|
||||
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@ from pathlib import Path
|
|||
|
||||
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def _get(key: str, default: str) -> str:
|
||||
return os.environ.get(key, default)
|
||||
|
||||
|
||||
BUILD_DIR: str = _get("HIL_BUILD_DIR",
|
||||
str(_REPO_ROOT / "build" / "target-debug"))
|
||||
BUILD_DIR: str = _get("HIL_BUILD_DIR", str(_REPO_ROOT / "build" / "target-debug"))
|
||||
PYOCD_FREQUENCY: int = int(_get("HIL_PYOCD_FREQUENCY", "1000000"))
|
||||
|
||||
|
||||
|
|
@ -36,6 +36,6 @@ M5_BAUD: int = int(_get("HIL_M5_BAUD", "115200"))
|
|||
M5_TIMEOUT: float = float(_get("HIL_M5_TIMEOUT", "3.0"))
|
||||
|
||||
# VCOM на плате таргета
|
||||
TARGET_VCOM_PORT:str = _get("HIL_USB_CDC_PORT", "/dev/ttyACM2")
|
||||
TARGET_VCOM_BAUD:int = int(_get("HIL_USB_CDC_BAUD", "115200"))
|
||||
TARGET_VCOM_TIMEOUT:float = float(_get("HIL_USB_CDC_TIMEOUT", "3.0"))
|
||||
TARGET_VCOM_PORT: str = _get("HIL_USB_CDC_PORT", "/dev/ttyACM2")
|
||||
TARGET_VCOM_BAUD: int = int(_get("HIL_USB_CDC_BAUD", "115200"))
|
||||
TARGET_VCOM_TIMEOUT: float = float(_get("HIL_USB_CDC_TIMEOUT", "3.0"))
|
||||
|
|
|
|||
|
|
@ -48,9 +48,11 @@ REPO_ROOT = Path(__file__).resolve().parents[2]
|
|||
|
||||
# ── Конфигурация из окружения (just экспортирует .env через set export) ───────
|
||||
|
||||
|
||||
def _env(key: str, default: str) -> str:
|
||||
return os.environ.get(key, default)
|
||||
|
||||
|
||||
PYOCD_TARGET = _env("PYOCD_TARGET", "mimxrt1050_quadspi")
|
||||
PYOCD_FREQUENCY = _env("PYOCD_FREQUENCY", "4000000")
|
||||
BUILD_DIR = Path(_env("BUILD_DIR", str(REPO_ROOT / "build")))
|
||||
|
|
@ -71,6 +73,7 @@ HAB_NAMES = {
|
|||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def build_full_image(fcb_path: Path, hab_path: Path) -> bytes:
|
||||
"""Объединить FCB + padding + HAB в единый образ для записи с 0x60000000."""
|
||||
fcb = fcb_path.read_bytes()
|
||||
|
|
@ -95,13 +98,20 @@ def build_full_image(fcb_path: Path, hab_path: Path) -> bytes:
|
|||
def run_pyocd_flash(image_path: Path, target: str, frequency: str) -> int:
|
||||
"""Запустить pyocd flash через uv run из tools/hil."""
|
||||
cmd = [
|
||||
"uv", "run",
|
||||
"--directory", str(HIL_DIR),
|
||||
"pyocd", "flash",
|
||||
"--target", target,
|
||||
"--frequency", frequency,
|
||||
"--base-address", "0x60000000",
|
||||
"--erase", "sector",
|
||||
"uv",
|
||||
"run",
|
||||
"--directory",
|
||||
str(HIL_DIR),
|
||||
"pyocd",
|
||||
"flash",
|
||||
"--target",
|
||||
target,
|
||||
"--frequency",
|
||||
frequency,
|
||||
"--base-address",
|
||||
"0x60000000",
|
||||
"--erase",
|
||||
"sector",
|
||||
str(image_path),
|
||||
]
|
||||
print(f"\n Running: {' '.join(cmd)}\n")
|
||||
|
|
@ -110,6 +120,7 @@ def run_pyocd_flash(image_path: Path, target: str, frequency: str) -> int:
|
|||
|
||||
# ── Main ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Flash MIMXRT1052 via SWD (MCU-Link). "
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ FLASHLOADER = SCRIPT_DIR / "dcd" / "ivt_flashloader.bin"
|
|||
# fallback — рассчитываем от расположения скрипта
|
||||
BUILD_DIR = Path(os.environ.get("BUILD_DIR", str(REPO_ROOT / "build")))
|
||||
|
||||
|
||||
# ─── USB VID:PID — из окружения (.env → just set export → uv run) ─────────────
|
||||
def _usb(vid_key: str, vid_default: str, pid_key: str, pid_default: str) -> str:
|
||||
|
||||
|
|
@ -50,8 +51,9 @@ def _usb(vid_key: str, vid_default: str, pid_key: str, pid_default: str) -> str:
|
|||
pid = os.environ.get(pid_key, pid_default).strip().upper().lstrip("0X")
|
||||
return f"0x{vid},0x{pid}"
|
||||
|
||||
|
||||
SDP_USB = _usb("BOOTROM_VID", "1fc9", "BOOTROM_PID", "0130") # BootROM SDP
|
||||
BLHOST_USB = _usb("FLASHLOADER_VID","15a2", "FLASHLOADER_PID","0073") # Flashloader
|
||||
BLHOST_USB = _usb("FLASHLOADER_VID", "15a2", "FLASHLOADER_PID", "0073") # Flashloader
|
||||
|
||||
# ─── Аппаратные константы (часть логики прошивки, не конфигурация) ────────────
|
||||
# FlexSPI NOR config option word: 0xC0000007
|
||||
|
|
@ -79,9 +81,9 @@ def run(cmd: list[str], check: bool = True) -> subprocess.CompletedProcess:
|
|||
|
||||
|
||||
def step(msg: str) -> None:
|
||||
print(f"\n{'─'*60}")
|
||||
print(f"\n{'─' * 60}")
|
||||
print(f" {msg}")
|
||||
print(f"{'─'*60}")
|
||||
print(f"{'─' * 60}")
|
||||
|
||||
|
||||
def wait_for_flashloader(timeout: int = 10) -> bool:
|
||||
|
|
@ -91,10 +93,10 @@ def wait_for_flashloader(timeout: int = 10) -> bool:
|
|||
time.sleep(1)
|
||||
result = subprocess.run(
|
||||
["blhost", "-u", BLHOST_USB, "-j", "--", "get-property", "1", "0"],
|
||||
capture_output=True
|
||||
capture_output=True,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
print(f" OK ({i+1}с)")
|
||||
print(f" OK ({i + 1}с)")
|
||||
return True
|
||||
print(".", end="", flush=True)
|
||||
print(" TIMEOUT")
|
||||
|
|
@ -105,7 +107,7 @@ def load_flashloader() -> None:
|
|||
"""Загружает Flashloader через SDP если ещё не запущен"""
|
||||
result = subprocess.run(
|
||||
["blhost", "-u", BLHOST_USB, "-j", "--", "get-property", "1", "0"],
|
||||
capture_output=True
|
||||
capture_output=True,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
print(" Flashloader уже запущен — пропускаем загрузку")
|
||||
|
|
@ -118,10 +120,19 @@ def load_flashloader() -> None:
|
|||
sys.exit(1)
|
||||
|
||||
step("Загрузка Flashloader через SDP (1FC9:0130)")
|
||||
run(["sdphost", "-u", SDP_USB, "-j", "--",
|
||||
"write-file", "0x20001C00", str(FLASHLOADER)])
|
||||
run(["sdphost", "-u", SDP_USB, "-j", "--",
|
||||
"jump-address", "0x20001C00"])
|
||||
run(
|
||||
[
|
||||
"sdphost",
|
||||
"-u",
|
||||
SDP_USB,
|
||||
"-j",
|
||||
"--",
|
||||
"write-file",
|
||||
"0x20001C00",
|
||||
str(FLASHLOADER),
|
||||
]
|
||||
)
|
||||
run(["sdphost", "-u", SDP_USB, "-j", "--", "jump-address", "0x20001C00"])
|
||||
|
||||
if not wait_for_flashloader():
|
||||
print("[ERROR] Flashloader не ответил. Проверь BOOT_MOD пины и подключение.")
|
||||
|
|
@ -131,10 +142,30 @@ def load_flashloader() -> None:
|
|||
def configure_flexspi() -> None:
|
||||
"""Инициализирует FlexSPI NOR контроллер через Flashloader"""
|
||||
step("Конфигурация FlexSPI NOR (инициализация контроллера)")
|
||||
run(["blhost", "-u", BLHOST_USB, "--",
|
||||
"fill-memory", FLEXSPI_OPTION_ADDR, "4", FLEXSPI_OPTION_VALUE, "word"])
|
||||
run(["blhost", "-u", BLHOST_USB, "--",
|
||||
"configure-memory", FLEXSPI_MEMORY_ID, FLEXSPI_OPTION_ADDR])
|
||||
run(
|
||||
[
|
||||
"blhost",
|
||||
"-u",
|
||||
BLHOST_USB,
|
||||
"--",
|
||||
"fill-memory",
|
||||
FLEXSPI_OPTION_ADDR,
|
||||
"4",
|
||||
FLEXSPI_OPTION_VALUE,
|
||||
"word",
|
||||
]
|
||||
)
|
||||
run(
|
||||
[
|
||||
"blhost",
|
||||
"-u",
|
||||
BLHOST_USB,
|
||||
"--",
|
||||
"configure-memory",
|
||||
FLEXSPI_MEMORY_ID,
|
||||
FLEXSPI_OPTION_ADDR,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def write_fcb() -> None:
|
||||
|
|
@ -145,10 +176,30 @@ def write_fcb() -> None:
|
|||
Option word 0xF000000F: tag=0xF → Write FCB command.
|
||||
"""
|
||||
step("Запись FCB в Flash[0x60000000]")
|
||||
run(["blhost", "-u", BLHOST_USB, "--",
|
||||
"fill-memory", FLEXSPI_OPTION_ADDR, "4", FLEXSPI_FCB_VALUE, "word"])
|
||||
run(["blhost", "-u", BLHOST_USB, "--",
|
||||
"configure-memory", FLEXSPI_MEMORY_ID, FLEXSPI_OPTION_ADDR])
|
||||
run(
|
||||
[
|
||||
"blhost",
|
||||
"-u",
|
||||
BLHOST_USB,
|
||||
"--",
|
||||
"fill-memory",
|
||||
FLEXSPI_OPTION_ADDR,
|
||||
"4",
|
||||
FLEXSPI_FCB_VALUE,
|
||||
"word",
|
||||
]
|
||||
)
|
||||
run(
|
||||
[
|
||||
"blhost",
|
||||
"-u",
|
||||
BLHOST_USB,
|
||||
"--",
|
||||
"configure-memory",
|
||||
FLEXSPI_MEMORY_ID,
|
||||
FLEXSPI_OPTION_ADDR,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def flash(hab_bin: Path, ram_only: bool = False) -> None:
|
||||
|
|
@ -159,10 +210,29 @@ def flash(hab_bin: Path, ram_only: bool = False) -> None:
|
|||
|
||||
if ram_only:
|
||||
step(f"Загрузка в RAM (без записи во Flash): {hab_bin.name}")
|
||||
run(["sdphost", "-u", SDP_USB, "-j", "--",
|
||||
"write-file", f"0x{FLASH_BASE + HAB_OFFSET:08X}", str(hab_bin)])
|
||||
run(["sdphost", "-u", SDP_USB, "-j", "--",
|
||||
"jump-address", f"0x{FLASH_BASE + HAB_OFFSET:08X}"])
|
||||
run(
|
||||
[
|
||||
"sdphost",
|
||||
"-u",
|
||||
SDP_USB,
|
||||
"-j",
|
||||
"--",
|
||||
"write-file",
|
||||
f"0x{FLASH_BASE + HAB_OFFSET:08X}",
|
||||
str(hab_bin),
|
||||
]
|
||||
)
|
||||
run(
|
||||
[
|
||||
"sdphost",
|
||||
"-u",
|
||||
SDP_USB,
|
||||
"-j",
|
||||
"--",
|
||||
"jump-address",
|
||||
f"0x{FLASH_BASE + HAB_OFFSET:08X}",
|
||||
]
|
||||
)
|
||||
return
|
||||
|
||||
load_flashloader()
|
||||
|
|
@ -177,14 +247,33 @@ def flash(hab_bin: Path, ram_only: bool = False) -> None:
|
|||
print(f" Адрес: {write_addr}")
|
||||
print(f" Стирание: 0x{FLASH_BASE:08X} .. +{erase_size} байт")
|
||||
|
||||
run(["blhost", "-u", BLHOST_USB, "--",
|
||||
"flash-erase-region", f"0x{FLASH_BASE:08X}",
|
||||
str(erase_size), "0"])
|
||||
run(
|
||||
[
|
||||
"blhost",
|
||||
"-u",
|
||||
BLHOST_USB,
|
||||
"--",
|
||||
"flash-erase-region",
|
||||
f"0x{FLASH_BASE:08X}",
|
||||
str(erase_size),
|
||||
"0",
|
||||
]
|
||||
)
|
||||
|
||||
write_fcb()
|
||||
|
||||
run(["blhost", "-u", BLHOST_USB, "--",
|
||||
"write-memory", write_addr, str(hab_bin), "0"])
|
||||
run(
|
||||
[
|
||||
"blhost",
|
||||
"-u",
|
||||
BLHOST_USB,
|
||||
"--",
|
||||
"write-memory",
|
||||
write_addr,
|
||||
str(hab_bin),
|
||||
"0",
|
||||
]
|
||||
)
|
||||
|
||||
step("Reset")
|
||||
run(["blhost", "-u", BLHOST_USB, "--", "reset"])
|
||||
|
|
@ -196,25 +285,29 @@ def main() -> None:
|
|||
print(f"DEBUG BLHOST_USB = {repr(BLHOST_USB)}")
|
||||
print(f"DEBUG BUILD_DIR = {repr(BUILD_DIR)}")
|
||||
parser = argparse.ArgumentParser(description="Прошивка MIMXRT1052 через USB")
|
||||
parser.add_argument("--firmware", required=True,
|
||||
parser.add_argument(
|
||||
"--firmware",
|
||||
required=True,
|
||||
choices=["firmware_test", "bootloader", "app"],
|
||||
help="Имя прошивки")
|
||||
parser.add_argument("--build-type", required=True,
|
||||
choices=["Debug", "Release"],
|
||||
help="Тип сборки")
|
||||
parser.add_argument("--ram-only", action="store_true",
|
||||
help="Загрузить в RAM без записи во Flash")
|
||||
help="Имя прошивки",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--build-type", required=True, choices=["Debug", "Release"], help="Тип сборки"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ram-only", action="store_true", help="Загрузить в RAM без записи во Flash"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
hab_bin = BUILD_DIR / args.build_type / f"{args.firmware}_hab.bin"
|
||||
|
||||
print(f"\n{'═'*60}")
|
||||
print(f"\n{'═' * 60}")
|
||||
print(f" MIMXRT1052 Flash Tool")
|
||||
print(f" Прошивка: {args.firmware} [{args.build_type}]")
|
||||
print(f" Образ: {hab_bin}")
|
||||
print(f" SDP USB: {SDP_USB}")
|
||||
print(f" BL USB: {BLHOST_USB}")
|
||||
print(f"{'═'*60}")
|
||||
print(f"{'═' * 60}")
|
||||
|
||||
flash(hab_bin, ram_only=args.ram_only)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue