From bbd02f840e5372941c61e02402c4dac96575d213 Mon Sep 17 00:00:00 2001 From: Ezra Maccabee Date: Tue, 30 Jun 2026 10:28:34 +0300 Subject: [PATCH] # fixes: pyformat + O1 release opt level --- .vscode/settings.json | 6 + cmake/toolchain_arm.cmake | 6 +- tools/hil/01_test_uart.py | 2 +- tools/hil/02_test_opto.py | 12 +- tools/hil/03_test_can.py | 94 ++++++++------- tools/hil/04_test_button.py | 4 +- tools/hil/05_test_usb_cdc.py | 3 +- tools/hil/06_test_firmware_can.py | 23 ++-- tools/hil/06_test_firmware_opto.py | 20 ++- tools/hil/conftest.py | 69 +++++++---- tools/hil/env_config.py | 24 ++-- tools/hil/load_and_run.py | 2 +- tools/hil/pyocd_utils.py | 2 +- tools/host/flash_swd.py | 45 ++++--- tools/host/flash_usb.py | 187 +++++++++++++++++++++-------- 15 files changed, 322 insertions(+), 177 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2919f0d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "[python]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "charliermarsh.ruff" + } +} \ No newline at end of file diff --git a/cmake/toolchain_arm.cmake b/cmake/toolchain_arm.cmake index 0133953..e4acd39 100644 --- a/cmake/toolchain_arm.cmake +++ b/cmake/toolchain_arm.cmake @@ -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 — подавить diff --git a/tools/hil/01_test_uart.py b/tools/hil/01_test_uart.py index 464abdd..00a5762 100644 --- a/tools/hil/01_test_uart.py +++ b/tools/hil/01_test_uart.py @@ -64,4 +64,4 @@ class TestUartBasic: def test_unknown_then_ping(self): """После неизвестной команды канал продолжает работать.""" uart_cmd(self.ser, "FOOBAR") - assert uart_cmd(self.ser, "PING") == "PONG" \ No newline at end of file + assert uart_cmd(self.ser, "PING") == "PONG" diff --git a/tools/hil/02_test_opto.py b/tools/hil/02_test_opto.py index c40eaf5..509bd21 100644 --- a/tools/hil/02_test_opto.py +++ b/tools/hil/02_test_opto.py @@ -36,7 +36,7 @@ from conftest import uart_cmd # process() цикл ~10 мс (CLI_RX_TIMEOUT) # UART round-trip ~2 мс # Итого минимум ~33 мс. Берём с запасом × 3. -RELAY_ON_S = 0.1 # ждать после включения реле +RELAY_ON_S = 0.1 # ждать после включения реле # После выключения нужно убедиться что INACTIVE подтверждён дебаунсом. # Берём чуть больше чем RELAY_ON_S — дебаунс работает симметрично. @@ -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() + коллбэк корректно @@ -233,4 +239,4 @@ class TestOptoEvents: last = uart_cmd(self.ser, "OPTO_LAST_EVENT") ch_str, _ = last.split() - assert ch_str == "2", f"Ожидали ch=2, получили {ch_str!r}" \ No newline at end of file + assert ch_str == "2", f"Ожидали ch=2, получили {ch_str!r}" diff --git a/tools/hil/03_test_can.py b/tools/hil/03_test_can.py index 0217a25..c3f7bb9 100644 --- a/tools/hil/03_test_can.py +++ b/tools/hil/03_test_can.py @@ -37,7 +37,7 @@ from conftest import uart_cmd # При 125 kbit/s один фрейм занимает ~0.1 мс. # Добавляем запас на задержку USB CDC (M5 ↔ хост) и polling в прошивке. -CAN_SETTLE_S = 0.05 # ждать после отправки перед чтением +CAN_SETTLE_S = 0.05 # ждать после отправки перед чтением # Таймаут CAN_RECV на таргете (мс) — передаётся в команду. # Должен быть достаточным для round-trip через шину + USB CDC M5. @@ -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) @@ -70,10 +71,10 @@ def can_send_target(ser, can_id: int, data: list[int], ext: bool = False) -> str Таргет отправляет CAN-фрейм. Возвращает ответ прошивки: 'OK', 'ERR_TIMEOUT', 'ERR_BUSY', 'ERR_PARAM'. """ - dlc = len(data) - data_str = " ".join(str(b) for b in data) - ext_flag = 1 if ext else 0 - cmd = f"CAN_SEND {can_id} {ext_flag} {dlc}" + dlc = len(data) + data_str = " ".join(str(b) for b in data) + ext_flag = 1 if ext else 0 + cmd = f"CAN_SEND {can_id} {ext_flag} {dlc}" if dlc > 0: cmd += f" {data_str}" return uart_cmd(ser, cmd) @@ -91,9 +92,9 @@ def can_recv_target(ser, timeout_ms: int = CAN_RECV_TIMEOUT_MS) -> dict | None: if len(parts) < 3: raise ValueError(f"Неожиданный ответ CAN_RECV: {resp!r}") can_id = int(parts[0]) - ext = bool(int(parts[1])) - dlc = int(parts[2]) - data = [int(b) for b in parts[3:3 + dlc]] + ext = bool(int(parts[1])) + dlc = int(parts[2]) + data = [int(b) for b in parts[3 : 3 + dlc]] return {"id": can_id, "ext": ext, "dlc": dlc, "data": data} @@ -112,13 +113,14 @@ def reset_events(ser) -> None: # Проверка каналов связи # --------------------------------------------------------------------------- + class TestCanConnectivity: """Базовая проверка: таргет и M5 отвечают.""" @pytest.fixture(autouse=True) def _setup(self, uart_can, m5): self.ser = uart_can - self.m5 = m5 + self.m5 = m5 def test_target_ping(self): """PING → PONG: UART-канал host↔target работает.""" @@ -131,20 +133,23 @@ 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 отправляет фрейм — таргет принимает.""" @pytest.fixture(autouse=True) def _setup(self, uart_can, m5): self.ser = uart_can - self.m5 = m5 + self.m5 = m5 uart_cmd(self.ser, "CAN_ACCEPT_ALL") reset_events(self.ser) @@ -153,10 +158,10 @@ class TestCanM5ToTarget: payload = [0x11, 0x22, 0x33] can_send_m5(self.m5, 0x123, payload) frame = can_recv_target(self.ser) - assert frame is not None, "Таргет не принял фрейм от M5" - assert frame["id"] == 0x123, f"Неверный ID: {frame['id']:#x}" - assert frame["ext"] is False, "Ожидали STD-фрейм" - assert frame["dlc"] == 3, f"Неверный DLC: {frame['dlc']}" + assert frame is not None, "Таргет не принял фрейм от M5" + assert frame["id"] == 0x123, f"Неверный ID: {frame['id']:#x}" + assert frame["ext"] is False, "Ожидали STD-фрейм" + assert frame["dlc"] == 3, f"Неверный DLC: {frame['dlc']}" assert frame["data"] == payload, f"Неверные данные: {frame['data']}" def test_ext_frame_received(self): @@ -164,10 +169,10 @@ class TestCanM5ToTarget: payload = [0xAA, 0xBB] can_send_m5(self.m5, 0x1ABCDEF, payload, ext=True) frame = can_recv_target(self.ser) - assert frame is not None, "Таргет не принял EXT-фрейм" - assert frame["id"] == 0x1ABCDEF, f"Неверный EXT ID: {frame['id']:#x}" - assert frame["ext"] is True, "Ожидали EXT-фрейм" - assert frame["data"] == payload, f"Неверные данные: {frame['data']}" + assert frame is not None, "Таргет не принял EXT-фрейм" + assert frame["id"] == 0x1ABCDEF, f"Неверный EXT ID: {frame['id']:#x}" + assert frame["ext"] is True, "Ожидали EXT-фрейм" + assert frame["data"] == payload, f"Неверные данные: {frame['data']}" def test_max_dlc_frame(self): """M5 TX 8-байтовый фрейм — таргет принимает все 8 байт корректно.""" @@ -175,7 +180,7 @@ class TestCanM5ToTarget: can_send_m5(self.m5, 0x7FF, payload) frame = can_recv_target(self.ser) assert frame is not None - assert frame["dlc"] == 8 + assert frame["dlc"] == 8 assert frame["data"] == payload def test_zero_dlc_frame(self): @@ -183,7 +188,7 @@ class TestCanM5ToTarget: can_send_m5(self.m5, 0x001, []) frame = can_recv_target(self.ser) assert frame is not None - assert frame["dlc"] == 0 + assert frame["dlc"] == 0 assert frame["data"] == [] def test_rx_event_counter_increments(self): @@ -200,21 +205,22 @@ class TestCanM5ToTarget: # Таргет → M5 # --------------------------------------------------------------------------- + class TestCanTargetToM5: """Таргет отправляет фрейм — M5 принимает.""" @pytest.fixture(autouse=True) def _setup(self, uart_can, m5): self.ser = uart_can - self.m5 = m5 + self.m5 = m5 def test_std_frame_sent(self): """Таргет TX STD → M5 принимает с верным ID и данными.""" payload = [0xDE, 0xAD, 0xBE, 0xEF] assert can_send_target(self.ser, 0x456, payload) == "OK" frame = can_recv_m5(self.m5) - assert frame["id"] == 0x456, f"Неверный ID: {frame['id']:#x}" - assert frame["ext"] is False, "Ожидали STD-фрейм" + assert frame["id"] == 0x456, f"Неверный ID: {frame['id']:#x}" + assert frame["ext"] is False, "Ожидали STD-фрейм" assert frame["data"] == payload, f"Неверные данные: {frame['data']}" def test_ext_frame_sent(self): @@ -222,8 +228,8 @@ class TestCanTargetToM5: payload = [0x01, 0x02] assert can_send_target(self.ser, 0x1FFFFFF, payload, ext=True) == "OK" frame = can_recv_m5(self.m5) - assert frame["id"] == 0x1FFFFFF, f"Неверный EXT ID: {frame['id']:#x}" - assert frame["ext"] is True, "Ожидали EXT-фрейм" + assert frame["id"] == 0x1FFFFFF, f"Неверный EXT ID: {frame['id']:#x}" + assert frame["ext"] is True, "Ожидали EXT-фрейм" def test_max_std_id(self): """Максимальный достижимый STD ID через M5 recv — проверяем 0x7FE.""" @@ -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: только нужные фреймы проходят, @@ -266,7 +274,7 @@ class TestCanFiltering: @pytest.fixture(autouse=True) def _setup(self, uart_can, m5): self.ser = uart_can - self.m5 = m5 + self.m5 = m5 reset_events(self.ser) def test_accept_all_receives_any_id(self): @@ -288,24 +296,27 @@ 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 is not None, "Фрейм с нужным ID не принят" + assert frame["id"] == target_id, ( + f"Получен ID {frame['id']:#x}, ожидали {target_id:#x}" + ) def test_filter_wrong_id_blocked(self): """set_filter с точной маской блокирует другой ID.""" target_id = 0x123 - wrong_id = 0x456 + wrong_id = 0x456 assert set_filter_target(self.ser, 0, target_id, 0x7FF) == "OK" 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 с одинаковыми старшими битами.""" base_id = 0x120 - mask = 0x7F0 # проверять биты 11..4, биты 3..0 — игнорировать + mask = 0x7F0 # проверять биты 11..4, биты 3..0 — игнорировать assert set_filter_target(self.ser, 0, base_id, mask) == "OK" @@ -327,8 +338,8 @@ class TestCanFiltering: can_send_m5(self.m5, target_id, [0x42], ext=True) frame = can_recv_target(self.ser) - assert frame is not None, "EXT-фрейм с нужным ID не принят" - assert frame["id"] == target_id + assert frame is not None, "EXT-фрейм с нужным ID не принят" + assert frame["id"] == target_id assert frame["ext"] is True def test_accept_all_after_filter(self): @@ -353,13 +364,14 @@ class TestCanFiltering: # STD и EXT раздельно # --------------------------------------------------------------------------- + class TestCanFrameTypes: """STD и EXT фреймы не перепутываются — is_extended корректен.""" @pytest.fixture(autouse=True) def _setup(self, uart_can, m5): self.ser = uart_can - self.m5 = m5 + self.m5 = m5 uart_cmd(self.ser, "CAN_ACCEPT_ALL") def test_std_frame_is_not_ext(self): @@ -374,15 +386,15 @@ class TestCanFrameTypes: can_send_m5(self.m5, 0x1FF, [0x01], ext=True) frame = can_recv_target(self.ser) assert frame is not None - assert frame["ext"] is True, "EXT-фрейм ошибочно помечен как STD" + assert frame["ext"] is True, "EXT-фрейм ошибочно помечен как STD" def test_std_id_range_boundary(self): """Граничные STD ID: 0x000 и 0x7FF.""" for can_id in [0x000, 0x7FF]: can_send_m5(self.m5, can_id, [0xBB]) frame = can_recv_target(self.ser) - assert frame is not None, f"ID {can_id:#x} не принят" - assert frame["id"] == can_id, f"ID {frame['id']:#x} ≠ {can_id:#x}" + assert frame is not None, f"ID {can_id:#x} не принят" + assert frame["id"] == can_id, f"ID {frame['id']:#x} ≠ {can_id:#x}" assert frame["ext"] is False def test_ext_id_range_boundary(self): @@ -390,6 +402,6 @@ class TestCanFrameTypes: for can_id in [0x000, 0x1FFFFFFF]: can_send_m5(self.m5, can_id, [0xCC], ext=True) frame = can_recv_target(self.ser) - assert frame is not None, f"EXT ID {can_id:#x} не принят" + assert frame is not None, f"EXT ID {can_id:#x} не принят" assert frame["id"] == can_id, f"EXT ID {frame['id']:#x} ≠ {can_id:#x}" - assert frame["ext"] is True \ No newline at end of file + assert frame["ext"] is True diff --git a/tools/hil/04_test_button.py b/tools/hil/04_test_button.py index 7318430..f392960 100644 --- a/tools/hil/04_test_button.py +++ b/tools/hil/04_test_button.py @@ -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 @@ -133,4 +133,4 @@ class TestHilButton: _operator_prompt("Нажми и УДЕРЖИ ТОЛЬКО кнопку 2 (TactBut2), кнопка 1 свободна") assert uart_cmd(self.ser, "STATE 0") == "0", "Кнопка 1 не должна быть нажата" assert uart_cmd(self.ser, "STATE 1") == "1", "Кнопка 2 должна быть нажата" - _operator_prompt("ОТПУСТИ кнопку 2 (TactBut2)") \ No newline at end of file + _operator_prompt("ОТПУСТИ кнопку 2 (TactBut2)") diff --git a/tools/hil/05_test_usb_cdc.py b/tools/hil/05_test_usb_cdc.py index 4b63771..8252032 100644 --- a/tools/hil/05_test_usb_cdc.py +++ b/tools/hil/05_test_usb_cdc.py @@ -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.""" @@ -100,4 +101,4 @@ class TestUsbCdcBasic: def test_uart_after_cdc(self): """UART канал работает после серии USB CDC операций.""" - assert uart_cmd(self.uart, "PING") == "PONG" \ No newline at end of file + assert uart_cmd(self.uart, "PING") == "PONG" diff --git a/tools/hil/06_test_firmware_can.py b/tools/hil/06_test_firmware_can.py index a91504d..5518d33 100644 --- a/tools/hil/06_test_firmware_can.py +++ b/tools/hil/06_test_firmware_can.py @@ -20,14 +20,15 @@ import pytest from conftest import FirmwareCdc, M5Agent # CAN параметры — должны совпадать с test_can.c -CAN_RX_ID = 0x100 +CAN_RX_ID = 0x100 CAN_RX_DATA = [0xDE, 0xAD, 0xBE, 0xEF] -CAN_TX_ID = 0x200 +CAN_TX_ID = 0x200 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-теста. @@ -50,7 +50,7 @@ class TestFirmwareCan: Таргет уже отправил фрейм ДО confirm_request. M5 принимает фрейм, проверяет id+data, возвращает результат. """ - + if confirm_id == "can_rx_ready": self.m5.can_send(CAN_RX_ID, CAN_RX_DATA) # Небольшая пауза чтобы фрейм успел уйти на шину @@ -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", []) + "M5 received frame: id=0x%X data=%s", + 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 @@ -90,7 +93,6 @@ class TestFirmwareCan: def test_ping(self) -> None: """Базовая проверка CDC-канала.""" self.cdc.ping() - def test_can_pass(self) -> None: """ @@ -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}" - ) \ No newline at end of file + assert "no frame" in detail, f"detail должен содержать 'no frame': {detail!r}" diff --git a/tools/hil/06_test_firmware_opto.py b/tools/hil/06_test_firmware_opto.py index 7a8979a..b9a2a7a 100644 --- a/tools/hil/06_test_firmware_opto.py +++ b/tools/hil/06_test_firmware_opto.py @@ -25,20 +25,21 @@ import pytest from conftest import FirmwareCdc, M5Agent -RELAY_ON_S = 0.1 # реле замыкается быстро -RELAY_OFF_S = 0.25 # размыкание + дебаунс прошивки с запасом +RELAY_ON_S = 0.1 # реле замыкается быстро +RELAY_OFF_S = 0.25 # размыкание + дебаунс прошивки с запасом # Маппинг confirm_id → (opto_ch, state) _OPTO_ACTIONS: dict[str, tuple[int, bool]] = { - "opto_in1_active": (1, True), + "opto_in1_active": (1, True), "opto_in1_inactive": (1, False), - "opto_in2_active": (2, True), + "opto_in2_active": (2, True), "opto_in2_inactive": (2, False), - "opto_rs_active": (3, True), - "opto_rs_inactive": (3, False), + "opto_rs_active": (3, True), + "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}" - ) \ No newline at end of file + assert "mismatch" in detail, f"detail должен содержать 'mismatch': {detail!r}" diff --git a/tools/hil/conftest.py b/tools/hil/conftest.py index 15d5e77..649f9d2 100644 --- a/tools/hil/conftest.py +++ b/tools/hil/conftest.py @@ -25,10 +25,10 @@ log = logging.getLogger(__name__) # CLI-опции pytest (перекрывают .env и os.environ) # --------------------------------------------------------------------------- def pytest_addoption(parser: pytest.Parser) -> None: - parser.addoption("--elf", default=None, help="Путь к .elf файлу") - parser.addoption("--vcom", default=cfg.VCOM_PORT, help="VCOM-порт MCU-Link") - parser.addoption("--m5-port", default=None, help="M5StampPLC serial port") - parser.addoption("--no-load", action="store_true", help="ELF уже запущен") + parser.addoption("--elf", default=None, help="Путь к .elf файлу") + parser.addoption("--vcom", default=cfg.VCOM_PORT, help="VCOM-порт MCU-Link") + parser.addoption("--m5-port", default=None, help="M5StampPLC serial port") + parser.addoption("--no-load", action="store_true", help="ELF уже запущен") # --------------------------------------------------------------------------- @@ -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,14 +122,15 @@ class M5Agent: def opto_all_off(self) -> None: self.cmd("opto_all_off") + def info(self) -> dict: """Запросить информацию об агенте (включая can_ok).""" return self.cmd("info") - + def can_send(self, can_id: int, data: list, ext: bool = False) -> None: """Отправить CAN-фрейм с шины M5.""" self.cmd("can_send", id=can_id, data=list(data), ext=ext) - + def can_recv(self, timeout_ms: int = 500) -> dict: """ Принять CAN-фрейм на M5. @@ -205,11 +204,12 @@ 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: """ Загрузить test_hil_button.elf на таргет по SWD - + Явная зависимость от m5 гарантирует порядок: 1. m5 создаётся первым → питание таргета включено 2. только потом pyOCD подключается и грузит ELF @@ -219,11 +219,12 @@ 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: """ Загрузить test_hil_button.elf на таргет по SWD - + Явная зависимость от m5 гарантирует порядок: 1. m5 создаётся первым → питание таргета включено 2. только потом pyOCD подключается и грузит ELF @@ -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,11 +251,12 @@ 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: """ Загрузить test_hil_can.elf на таргет по SWD - + Явная зависимость от m5 гарантирует порядок: 1. m5 создаётся первым → питание таргета включено 2. только потом pyOCD подключается и грузит ELF @@ -263,11 +266,12 @@ 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: """ Загрузить test_hil_usb_cdc.elf на таргет по SWD - + Явная зависимость от m5 гарантирует порядок: 1. m5 создаётся первым → питание таргета включено 2. только потом pyOCD подключается и грузит ELF @@ -277,29 +281,38 @@ 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) # Выполняется один раз на тест # --------------------------------------------------------------------------- _UART_FIXTURE_MAP = { - "uart": "loaded_host_uart", - "uart_opto": "loaded_hil_opto", - "uart_can": "loaded_hil_can", - "uart_button": "loaded_hil_button", + "uart": "loaded_host_uart", + "uart_opto": "loaded_hil_opto", + "uart_can": "loaded_hil_can", + "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. @@ -334,7 +351,7 @@ def usb_cdc_port( ser.reset_input_buffer() yield ser finally: - ser.close() + ser.close() # --------------------------------------------------------------------------- @@ -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: @@ -528,4 +547,4 @@ def firmware_cdc(m5: M5Agent) -> Generator[FirmwareCdc, None, None]: yield cdc finally: - ser.close() \ No newline at end of file + ser.close() diff --git a/tools/hil/env_config.py b/tools/hil/env_config.py index 99a866d..eb41a82 100644 --- a/tools/hil/env_config.py +++ b/tools/hil/env_config.py @@ -14,28 +14,28 @@ 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")) -PYOCD_FREQUENCY: int = int(_get("HIL_PYOCD_FREQUENCY", "1000000")) +BUILD_DIR: str = _get("HIL_BUILD_DIR", str(_REPO_ROOT / "build" / "target-debug")) +PYOCD_FREQUENCY: int = int(_get("HIL_PYOCD_FREQUENCY", "1000000")) POWER_ON_SETTLE_S = float(_get("HIL_TARGET_POWER_SETTLE_S", "1.5")) # VCOM на программаторе NXP MCU-Link -VCOM_PORT: str = _get("HIL_VCOM_PORT", "/dev/ttyACM0") -VCOM_BAUD: int = int(_get("HIL_VCOM_BAUD", "115200")) -READY_TIMEOUT: float = float(_get("HIL_READY_TIMEOUT", "5.0")) +VCOM_PORT: str = _get("HIL_VCOM_PORT", "/dev/ttyACM0") +VCOM_BAUD: int = int(_get("HIL_VCOM_BAUD", "115200")) +READY_TIMEOUT: float = float(_get("HIL_READY_TIMEOUT", "5.0")) # M5Stack StamPLC (промежуточная платформа для HIL) -M5_PORT: str = _get("HIL_M5_PORT", "/dev/ttyACM1") -M5_BAUD: int = int(_get("HIL_M5_BAUD", "115200")) -M5_TIMEOUT: float = float(_get("HIL_M5_TIMEOUT", "3.0")) +M5_PORT: str = _get("HIL_M5_PORT", "/dev/ttyACM1") +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")) \ No newline at end of file +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")) diff --git a/tools/hil/load_and_run.py b/tools/hil/load_and_run.py index 711947e..55ab376 100644 --- a/tools/hil/load_and_run.py +++ b/tools/hil/load_and_run.py @@ -20,4 +20,4 @@ logging.basicConfig( if __name__ == "__main__": elf = sys.argv[1] if len(sys.argv) > 1 else "firmware_test.elf" load_and_run(elf) - print("Done — смотри на LED!") \ No newline at end of file + print("Done — смотри на LED!") diff --git a/tools/hil/pyocd_utils.py b/tools/hil/pyocd_utils.py index 6a18549..7aab11d 100644 --- a/tools/hil/pyocd_utils.py +++ b/tools/hil/pyocd_utils.py @@ -126,4 +126,4 @@ def load_and_run(elf_path: str, frequency: int = 1_000_000) -> None: with open_target(frequency) as target: flexram_init(target) load_elf(target, elf_path) - run_from_vectors(target) \ No newline at end of file + run_from_vectors(target) diff --git a/tools/host/flash_swd.py b/tools/host/flash_swd.py index fdf1b82..81213ac 100644 --- a/tools/host/flash_swd.py +++ b/tools/host/flash_swd.py @@ -48,16 +48,18 @@ 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_TARGET = _env("PYOCD_TARGET", "mimxrt1050_quadspi") PYOCD_FREQUENCY = _env("PYOCD_FREQUENCY", "4000000") -BUILD_DIR = Path(_env("BUILD_DIR", str(REPO_ROOT / "build"))) -FCB_PATH = Path(_env("FCB_PATH", str(REPO_ROOT / "tools/host/dcd/w25q128_fdcb.bin"))) +BUILD_DIR = Path(_env("BUILD_DIR", str(REPO_ROOT / "build"))) +FCB_PATH = Path(_env("FCB_PATH", str(REPO_ROOT / "tools/host/dcd/w25q128_fdcb.bin"))) if not FCB_PATH.is_absolute(): FCB_PATH = REPO_ROOT / FCB_PATH -HIL_DIR = REPO_ROOT / "tools" / "hil" +HIL_DIR = REPO_ROOT / "tools" / "hil" # IVT располагается по смещению 0x1000 от начала Flash (ivtOffset в HAB yaml) IVT_OFFSET = 0x1000 @@ -65,12 +67,13 @@ IVT_OFFSET = 0x1000 # Маппинг имён прошивок → имена HAB-файлов (генерируются build::hab-*) HAB_NAMES = { "firmware_test": "firmware_test_hab.bin", - "bootloader": "bootloader_hab.bin", - "app": "app_hab.bin", + "bootloader": "bootloader_hab.bin", + "app": "app_hab.bin", } # ── Helpers ─────────────────────────────────────────────────────────────────── + def build_full_image(fcb_path: Path, hab_path: Path) -> bytes: """Объединить FCB + padding + HAB в единый образ для записи с 0x60000000.""" fcb = fcb_path.read_bytes() @@ -82,7 +85,7 @@ def build_full_image(fcb_path: Path, hab_path: Path) -> bytes: ) padding = b"\xff" * (IVT_OFFSET - len(fcb)) # 0xFF = erased flash value - image = fcb + padding + hab + image = fcb + padding + hab print(f" FCB: {len(fcb):>6} bytes @ 0x60000000") print(f" Padding: {len(padding):>6} bytes @ 0x{0x60000000 + len(fcb):08X}") @@ -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,10 +120,11 @@ 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). " - "Prepends FCB to HAB image before programming.", + "Prepends FCB to HAB image before programming.", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=__doc__, ) @@ -170,7 +181,7 @@ def main() -> int: if not hab_path.exists(): print(f" ❌ HAB image not found: {hab_path}", file=sys.stderr) fw_slug = args.firmware.replace("_", "-") - bt_slug = args.build_type.lower() + bt_slug = args.build_type.lower() print( f" Hint: run just build::hab-{fw_slug}-{bt_slug} " "inside devcontainer first.", @@ -216,4 +227,4 @@ def main() -> int: if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file + sys.exit(main()) diff --git a/tools/host/flash_usb.py b/tools/host/flash_usb.py index c0c4fc5..f3263dd 100644 --- a/tools/host/flash_usb.py +++ b/tools/host/flash_usb.py @@ -35,38 +35,40 @@ import time from pathlib import Path # ─── Пути ───────────────────────────────────────────────────────────────────── -SCRIPT_DIR = Path(__file__).parent.resolve() -REPO_ROOT = SCRIPT_DIR.parent.parent +SCRIPT_DIR = Path(__file__).parent.resolve() +REPO_ROOT = SCRIPT_DIR.parent.parent FLASHLOADER = SCRIPT_DIR / "dcd" / "ivt_flashloader.bin" # BUILD_DIR: берём из окружения (just экспортирует из .env как абсолютный путь), # 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: - + vid = os.environ.get(vid_key, vid_default).strip().upper().lstrip("0X") 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 + +SDP_USB = _usb("BOOTROM_VID", "1fc9", "BOOTROM_PID", "0130") # BootROM SDP +BLHOST_USB = _usb("FLASHLOADER_VID", "15a2", "FLASHLOADER_PID", "0073") # Flashloader # ─── Аппаратные константы (часть логики прошивки, не конфигурация) ──────────── # FlexSPI NOR config option word: 0xC0000007 # bits[31:28]=0xC — tag (QuadSPI NOR) # bits[3:0]=0x7 — option size -FLEXSPI_OPTION_ADDR = "0x2000" +FLEXSPI_OPTION_ADDR = "0x2000" FLEXSPI_OPTION_VALUE = "0xC0000007" -FLEXSPI_MEMORY_ID = "9" # FlexSPI NOR memory interface ID +FLEXSPI_MEMORY_ID = "9" # FlexSPI NOR memory interface ID # Option word для записи FCB: tag=0xF → Write FCB command -FLEXSPI_FCB_VALUE = "0xF000000F" +FLEXSPI_FCB_VALUE = "0xF000000F" # Flash layout -FLASH_BASE = 0x60000000 -HAB_OFFSET = 0x1000 # IVT offset: write address = FLASH_BASE + HAB_OFFSET +FLASH_BASE = 0x60000000 +HAB_OFFSET = 0x1000 # IVT offset: write address = FLASH_BASE + HAB_OFFSET def run(cmd: list[str], check: bool = True) -> subprocess.CompletedProcess: @@ -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,28 +285,32 @@ 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, - 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") + 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" + ) 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) if __name__ == "__main__": - main() \ No newline at end of file + main()