From 0b16f5cf19c23974d0e7068a36b27f745f6f5c08 Mon Sep 17 00:00:00 2001 From: Ezra Maccabee Date: Tue, 30 Jun 2026 16:28:48 +0300 Subject: [PATCH] =?UTF-8?q?#=20bsp:=20usd=20-=20=D0=B8=D1=81=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B1=D0=B0=D0=B3?= =?UTF-8?q?=D0=B0=20=D1=81=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B8=D0=BD=D0=B8?= =?UTF-8?q?=D1=86=D0=B8=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B5?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bsp/sd/src/sd.c | 37 +++++++++++++++++++++++++ firmware/test/src/tests/test_buttons.c | 10 +++---- firmware/test/src/tests/test_can.c | 2 +- firmware/test/src/tests/test_display.c | 14 +++++----- firmware/test/src/tests/test_mqs.c | 6 ++-- firmware/test/src/tests/test_opto.c | 2 +- firmware/test/src/tests/test_usd.c | 4 +-- tools/production/app/app.tcss | 4 +++ tools/production/app/firmware_client.py | 4 +-- tools/production/app/m5_client.py | 4 +-- tools/production/service_tui.log | 22 +++++++++++++++ 11 files changed, 86 insertions(+), 23 deletions(-) diff --git a/bsp/sd/src/sd.c b/bsp/sd/src/sd.c index d2ecb45..05c5df2 100644 --- a/bsp/sd/src/sd.c +++ b/bsp/sd/src/sd.c @@ -5,8 +5,10 @@ #include "bsp/sd.h" #include "fsl_sd.h" +#include "fsl_usdhc.h" /* USDHC_Reset — аппаратный сброс FIFO/state machine */ #include "sdmmc_config.h" /* BOARD_SD_Config, BOARD_SDMMC_SD_HOST_BASEADDR */ +#include /* memset */ /* --------------------------------------------------------------------------- * Глобальный дескриптор карты — нужен SDK-стеку (передаётся по указателю * в BOARD_SD_Config и sd_disk_initialize через g_sd). @@ -51,6 +53,33 @@ bsp_status_t bsp_sd_init(void) return BSP_OK; } + /* + * Аппаратный сброс USDHC FIFO + command/data state machine ПЕРЕД + * повторной инициализацией. Без этого non-blocking host driver SDK + * (fsl_sdmmc_host.c) может остаться в состоянии "ожидание завершения + * предыдущей транзакции" после SD_HostDeinit() на прошлом прогоне — + * физическая транзакция уже умерла вместе с deinit, но внутренний + * флаг ожидания interrupt остаётся выставленным, и следующий f_mount() + * блокируется навсегда в ожидании события, которое никогда не придёт. + * + * USDHC_Reset с маской kUSDHC_ResetAll сбрасывает контроллер на + * регистровом уровне, не полагаясь на состояние, оставленное + * предыдущей сессией. Безопасно вызывать даже при первом запуске — + * базовый адрес уже доступен через BOARD_SDMMC_SD_HOST_BASEADDR + * (clock на этот момент должен быть включён, см. ниже). + */ + CLOCK_EnableClock(kCLOCK_Usdhc1); /* тактирование нужно ДО сброса регистров */ + USDHC_Reset(BOARD_SDMMC_SD_HOST_BASEADDR, kUSDHC_ResetAll, 100U); + + /* + * Обнулить g_sd целиком перед повторной конфигурацией. BOARD_SD_Config() + * перезаписывает только часть полей — указатели на callback-структуры + * non-blocking host driver и внутренние DMA-дескрипторы могут остаться + * от предыдущей сессии, если их явно не сбросить. + */ + (void) memset(&g_sd, 0, sizeof(g_sd)); + g_s_host_configured = false; /* форсируем повторный BOARD_SD_Config ниже */ + ensure_host_configured(); /* только BOARD_SD_Config — заполняет g_sd */ /* @@ -72,6 +101,14 @@ bsp_status_t bsp_sd_deinit(void) SD_HostDeinit(&g_sd); SD_SetCardPower(&g_sd, false); + /* + * Дополнительный аппаратный сброс сразу после deinit — гарантирует, + * что FIFO и state machine USDHC не останутся в промежуточном + * состоянии независимо от того, что делает (или не делает) + * SD_HostDeinit() из SDK на уровне регистров. + */ + USDHC_Reset(BOARD_SDMMC_SD_HOST_BASEADDR, kUSDHC_ResetAll, 100U); + g_s_initialized = false; g_s_host_configured = false; return BSP_OK; diff --git a/firmware/test/src/tests/test_buttons.c b/firmware/test/src/tests/test_buttons.c index c5764b4..acdd6ef 100644 --- a/firmware/test/src/tests/test_buttons.c +++ b/firmware/test/src/tests/test_buttons.c @@ -143,14 +143,14 @@ static void buttons_test_init(void) */ static test_result_t buttons_test_run(void) { - if (!wait_button_press(BSP_BUTTON_1, "btn1_press", "Press Test_But_1", BTN_PRESS_TIMEOUT_MS)) + if (!wait_button_press(BSP_BUTTON_1, "btn1_press", "Нажмите кнопку 1", BTN_PRESS_TIMEOUT_MS)) { - return make_skip("btn1_press timeout"); + return make_skip("Кнопка 1 - таймаут"); } - if (!wait_button_press(BSP_BUTTON_2, "btn2_press", "Press Test_But_2", BTN_PRESS_TIMEOUT_MS)) + if (!wait_button_press(BSP_BUTTON_2, "btn2_press", "Нажмите кнопку 2", BTN_PRESS_TIMEOUT_MS)) { - return make_skip("btn2_press timeout"); + return make_skip("Кнопка 2 - таймаут"); } return (test_result_t){ @@ -165,7 +165,7 @@ static test_result_t buttons_test_run(void) /** @brief Дескриптор тест-модуля кнопок для реестра test_runner. */ const test_module_t K_TEST_BUTTONS = { .id = "buttons", - .name = "Buttons", + .name = "Тактовые кнопки", .critical = false, .requires_hil = false, .pre_confirm_prompt = NULL, diff --git a/firmware/test/src/tests/test_can.c b/firmware/test/src/tests/test_can.c index 02aed4a..1ca7484 100644 --- a/firmware/test/src/tests/test_can.c +++ b/firmware/test/src/tests/test_can.c @@ -197,7 +197,7 @@ static test_result_t can_run(void) /** @brief Дескриптор для регистрации в test_runner. */ const test_module_t K_TEST_CAN = { .id = "can", - .name = "CAN Network", + .name = "CAN шина", .critical = false, .requires_hil = true, .pre_confirm_prompt = NULL, diff --git a/firmware/test/src/tests/test_display.c b/firmware/test/src/tests/test_display.c index 9af17ac..1a1a3ad 100644 --- a/firmware/test/src/tests/test_display.c +++ b/firmware/test/src/tests/test_display.c @@ -222,11 +222,11 @@ static bool step_rot_apply_and_confirm(bsp_display_rotation_t rotation, const ch static bool step_rotation(test_result_t *p_out) { bool ok = step_rot_apply_and_confirm(BSP_DISPLAY_ROTATE_0, "display_rot0", - "Screen: left RED, right BLUE?", p_out); + "Дисплей: слева - красный, справа - синий?", p_out); if (ok) { ok = step_rot_apply_and_confirm(BSP_DISPLAY_FLIP_HORIZONTAL, "display_rot_base", - "Left RED and right BLUE swapped sides?", p_out); + "Дисплей: слева - синий, справа - красный?", p_out); } /* Восстановить ROTATE_0 в любом исходе */ @@ -264,19 +264,19 @@ static test_result_t display_test_run(void) test_result_t fail_result = { .status = TEST_STATUS_FAIL, .duration_ms = 0U, .detail = { 0 } }; /* Этап 1: цвет */ - if (!step_color(DISPLAY_COLOR_RED, "display_red", "Screen is solid red?", &fail_result)) + if (!step_color(DISPLAY_COLOR_RED, "display_red", "Дисплей залит красным?", &fail_result)) { return fail_result; } - if (!step_color(DISPLAY_COLOR_GREEN, "display_green", "Screen is solid green?", &fail_result)) + if (!step_color(DISPLAY_COLOR_GREEN, "display_green", "Дисплей залит зеленым?", &fail_result)) { return fail_result; } - if (!step_color(DISPLAY_COLOR_BLUE, "display_blue", "Screen is solid blue?", &fail_result)) + if (!step_color(DISPLAY_COLOR_BLUE, "display_blue", "Дисплей залит синим?", &fail_result)) { return fail_result; } - if (!step_color(DISPLAY_COLOR_WHITE, "display_white", "Screen is solid white?", &fail_result)) + if (!step_color(DISPLAY_COLOR_WHITE, "display_white", "Дисплей залит белым?", &fail_result)) { return fail_result; } @@ -308,7 +308,7 @@ static void display_test_deinit(void) const test_module_t K_TEST_DISPLAY = { .id = "display", - .name = "TFT Display", + .name = "TFT дисплей", .critical = false, .requires_hil = false, .pre_confirm_prompt = NULL, diff --git a/firmware/test/src/tests/test_mqs.c b/firmware/test/src/tests/test_mqs.c index aefb362..67976a7 100644 --- a/firmware/test/src/tests/test_mqs.c +++ b/firmware/test/src/tests/test_mqs.c @@ -190,7 +190,7 @@ static test_result_t test_mqs_run(void) */ const confirm_params_t K_PARAMS = { .id = "mqs_tone", - .prompt = "Do you hear a tone?", + .prompt = "Звук слышен?", .timeout_ms = 15000U, }; @@ -200,7 +200,7 @@ static test_result_t test_mqs_run(void) { return (test_result_t){ .status = TEST_STATUS_FAIL, - .detail = "operator: no sound", + .detail = "Звук не был услышан", }; } @@ -220,7 +220,7 @@ static void test_mqs_deinit(void) const test_module_t K_TEST_MQS = { .id = "mqs", - .name = "Audio Out", + .name = "Аудио-выход", .critical = false, .requires_hil = false, .pre_confirm_prompt = NULL, diff --git a/firmware/test/src/tests/test_opto.c b/firmware/test/src/tests/test_opto.c index 858a566..1b300b1 100644 --- a/firmware/test/src/tests/test_opto.c +++ b/firmware/test/src/tests/test_opto.c @@ -182,7 +182,7 @@ static test_result_t opto_run(void) /** @brief Дескриптор для регистрации в test_runner. */ const test_module_t K_TEST_OPTO = { .id = "opto", - .name = "Opto Inputs", + .name = "Оптопары", .critical = false, .requires_hil = true, .pre_confirm_prompt = NULL, diff --git a/firmware/test/src/tests/test_usd.c b/firmware/test/src/tests/test_usd.c index f136f6d..2eeb7da 100644 --- a/firmware/test/src/tests/test_usd.c +++ b/firmware/test/src/tests/test_usd.c @@ -343,10 +343,10 @@ static void usd_deinit(void) const test_module_t K_TEST_USD = { .id = "usd", - .name = "microSD Card", + .name = "microSD карта", .critical = false, .requires_hil = false, - .pre_confirm_prompt = "Insert microSD card and press OK", + .pre_confirm_prompt = "Карта вставлена в слот?", .init = usd_init, .run = usd_run, .deinit = usd_deinit, diff --git a/tools/production/app/app.tcss b/tools/production/app/app.tcss index 53bf7f0..b967115 100644 --- a/tools/production/app/app.tcss +++ b/tools/production/app/app.tcss @@ -287,6 +287,10 @@ ResultsPanel .section-title { width: 100%; } +#results-empty.hidden { + display: none; +} + #results-empty-text { color: $text-muted; text-align: center; diff --git a/tools/production/app/firmware_client.py b/tools/production/app/firmware_client.py index 342869a..b872bf6 100644 --- a/tools/production/app/firmware_client.py +++ b/tools/production/app/firmware_client.py @@ -134,7 +134,7 @@ class FirmwareClient: """Сериализовать dict в JSON и отправить строку (blocking).""" assert self._ser is not None line = json.dumps(obj, separators=(",", ":")) + "\n" - self._ser.write(line.encode("ascii")) + self._ser.write(line.encode("utf-8")) self._ser.flush() def _read_line(self) -> Optional[dict]: @@ -143,7 +143,7 @@ class FirmwareClient: raw = self._ser.readline() if not raw: return None - return _parse_event(raw.decode("ascii", errors="replace")) + return _parse_event(raw.decode("utf-8", errors="replace")) async def _send(self, obj: dict) -> None: """Отправить JSON-команду (async wrapper).""" diff --git a/tools/production/app/m5_client.py b/tools/production/app/m5_client.py index 99fe3e8..03ea043 100644 --- a/tools/production/app/m5_client.py +++ b/tools/production/app/m5_client.py @@ -121,13 +121,13 @@ class M5Client: """Отправить команду, прочитать ответ (blocking).""" assert self._ser is not None line = json.dumps(cmd, separators=(",", ":")) + "\n" - self._ser.write(line.encode("ascii")) + self._ser.write(line.encode("utf-8")) self._ser.flush() raw = self._ser.readline() if not raw: return None try: - return json.loads(raw.decode("ascii", errors="replace").strip()) + return json.loads(raw.decode("utf-8", errors="replace").strip()) except json.JSONDecodeError: return None diff --git a/tools/production/service_tui.log b/tools/production/service_tui.log index 8e48d2f..7ef503c 100644 --- a/tools/production/service_tui.log +++ b/tools/production/service_tui.log @@ -1843,3 +1843,25 @@ 2026-06-30 15:08:26,321 INFO app.m5_client M5StampPLC не найден 2026-06-30 15:08:55,013 DEBUG asyncio Using selector: KqueueSelector 2026-06-30 15:08:56,561 INFO app.m5_client M5StampPLC не найден +2026-06-30 15:09:59,613 DEBUG asyncio Using selector: KqueueSelector +2026-06-30 15:10:01,161 INFO app.m5_client M5StampPLC не найден +2026-06-30 15:12:33,081 WARNING app.firmware_client _recv_until timeout after 120.0 s +2026-06-30 15:14:40,777 WARNING app.firmware_client _recv_until timeout after 120.0 s +2026-06-30 15:33:29,207 DEBUG asyncio Using selector: KqueueSelector +2026-06-30 15:33:54,602 DEBUG asyncio Using selector: KqueueSelector +2026-06-30 15:33:56,280 INFO app.m5_client M5StampPLC не найден +2026-06-30 15:37:25,364 DEBUG asyncio Using selector: KqueueSelector +2026-06-30 15:37:27,091 INFO app.m5_client M5StampPLC не найден +2026-06-30 15:42:36,030 DEBUG asyncio Using selector: KqueueSelector +2026-06-30 15:42:37,715 INFO app.m5_client M5StampPLC не найден +2026-06-30 15:43:19,205 ERROR app.screens.diag run_worker error: write failed: [Errno 6] Device not configured +2026-06-30 15:43:22,852 ERROR app.screens.diag run_worker error: write failed: [Errno 6] Device not configured +2026-06-30 15:43:23,345 ERROR app.screens.diag run_worker error: write failed: [Errno 6] Device not configured +2026-06-30 15:43:23,701 ERROR app.screens.diag run_worker error: write failed: [Errno 6] Device not configured +2026-06-30 15:43:28,104 DEBUG asyncio Using selector: KqueueSelector +2026-06-30 15:43:29,654 INFO app.m5_client M5StampPLC не найден +2026-06-30 16:19:09,052 DEBUG asyncio Using selector: KqueueSelector +2026-06-30 16:19:10,697 INFO app.m5_client M5StampPLC не найден +2026-06-30 16:20:10,951 ERROR app.orchestrator HIL confirm без M5: opto_in1_active +2026-06-30 16:27:42,558 DEBUG asyncio Using selector: KqueueSelector +2026-06-30 16:27:44,211 INFO app.m5_client M5StampPLC не найден