# bsp: usd - исправления бага с переинициализацией

This commit is contained in:
Dmitry Akimov 2026-06-30 16:28:48 +03:00
parent 6e42970fef
commit 0b16f5cf19
11 changed files with 86 additions and 23 deletions

View file

@ -5,8 +5,10 @@
#include "bsp/sd.h" #include "bsp/sd.h"
#include "fsl_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 "sdmmc_config.h" /* BOARD_SD_Config, BOARD_SDMMC_SD_HOST_BASEADDR */
#include <string.h> /* memset */
/* --------------------------------------------------------------------------- /* ---------------------------------------------------------------------------
* Глобальный дескриптор карты нужен SDK-стеку (передаётся по указателю * Глобальный дескриптор карты нужен SDK-стеку (передаётся по указателю
* в BOARD_SD_Config и sd_disk_initialize через g_sd). * в BOARD_SD_Config и sd_disk_initialize через g_sd).
@ -51,6 +53,33 @@ bsp_status_t bsp_sd_init(void)
return BSP_OK; 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 */ ensure_host_configured(); /* только BOARD_SD_Config — заполняет g_sd */
/* /*
@ -72,6 +101,14 @@ bsp_status_t bsp_sd_deinit(void)
SD_HostDeinit(&g_sd); SD_HostDeinit(&g_sd);
SD_SetCardPower(&g_sd, false); 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_initialized = false;
g_s_host_configured = false; g_s_host_configured = false;
return BSP_OK; return BSP_OK;

View file

@ -143,14 +143,14 @@ static void buttons_test_init(void)
*/ */
static test_result_t buttons_test_run(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){ return (test_result_t){
@ -165,7 +165,7 @@ static test_result_t buttons_test_run(void)
/** @brief Дескриптор тест-модуля кнопок для реестра test_runner. */ /** @brief Дескриптор тест-модуля кнопок для реестра test_runner. */
const test_module_t K_TEST_BUTTONS = { const test_module_t K_TEST_BUTTONS = {
.id = "buttons", .id = "buttons",
.name = "Buttons", .name = "Тактовые кнопки",
.critical = false, .critical = false,
.requires_hil = false, .requires_hil = false,
.pre_confirm_prompt = NULL, .pre_confirm_prompt = NULL,

View file

@ -197,7 +197,7 @@ static test_result_t can_run(void)
/** @brief Дескриптор для регистрации в test_runner. */ /** @brief Дескриптор для регистрации в test_runner. */
const test_module_t K_TEST_CAN = { const test_module_t K_TEST_CAN = {
.id = "can", .id = "can",
.name = "CAN Network", .name = "CAN шина",
.critical = false, .critical = false,
.requires_hil = true, .requires_hil = true,
.pre_confirm_prompt = NULL, .pre_confirm_prompt = NULL,

View file

@ -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) static bool step_rotation(test_result_t *p_out)
{ {
bool ok = step_rot_apply_and_confirm(BSP_DISPLAY_ROTATE_0, "display_rot0", bool ok = step_rot_apply_and_confirm(BSP_DISPLAY_ROTATE_0, "display_rot0",
"Screen: left RED, right BLUE?", p_out); "Дисплей: слева - красный, справа - синий?", p_out);
if (ok) if (ok)
{ {
ok = step_rot_apply_and_confirm(BSP_DISPLAY_FLIP_HORIZONTAL, "display_rot_base", 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 в любом исходе */ /* Восстановить 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 } }; test_result_t fail_result = { .status = TEST_STATUS_FAIL, .duration_ms = 0U, .detail = { 0 } };
/* Этап 1: цвет */ /* Этап 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; 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; 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; 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; return fail_result;
} }
@ -308,7 +308,7 @@ static void display_test_deinit(void)
const test_module_t K_TEST_DISPLAY = { const test_module_t K_TEST_DISPLAY = {
.id = "display", .id = "display",
.name = "TFT Display", .name = "TFT дисплей",
.critical = false, .critical = false,
.requires_hil = false, .requires_hil = false,
.pre_confirm_prompt = NULL, .pre_confirm_prompt = NULL,

View file

@ -190,7 +190,7 @@ static test_result_t test_mqs_run(void)
*/ */
const confirm_params_t K_PARAMS = { const confirm_params_t K_PARAMS = {
.id = "mqs_tone", .id = "mqs_tone",
.prompt = "Do you hear a tone?", .prompt = "Звук слышен?",
.timeout_ms = 15000U, .timeout_ms = 15000U,
}; };
@ -200,7 +200,7 @@ static test_result_t test_mqs_run(void)
{ {
return (test_result_t){ return (test_result_t){
.status = TEST_STATUS_FAIL, .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 = { const test_module_t K_TEST_MQS = {
.id = "mqs", .id = "mqs",
.name = "Audio Out", .name = "Аудио-выход",
.critical = false, .critical = false,
.requires_hil = false, .requires_hil = false,
.pre_confirm_prompt = NULL, .pre_confirm_prompt = NULL,

View file

@ -182,7 +182,7 @@ static test_result_t opto_run(void)
/** @brief Дескриптор для регистрации в test_runner. */ /** @brief Дескриптор для регистрации в test_runner. */
const test_module_t K_TEST_OPTO = { const test_module_t K_TEST_OPTO = {
.id = "opto", .id = "opto",
.name = "Opto Inputs", .name = "Оптопары",
.critical = false, .critical = false,
.requires_hil = true, .requires_hil = true,
.pre_confirm_prompt = NULL, .pre_confirm_prompt = NULL,

View file

@ -343,10 +343,10 @@ static void usd_deinit(void)
const test_module_t K_TEST_USD = { const test_module_t K_TEST_USD = {
.id = "usd", .id = "usd",
.name = "microSD Card", .name = "microSD карта",
.critical = false, .critical = false,
.requires_hil = false, .requires_hil = false,
.pre_confirm_prompt = "Insert microSD card and press OK", .pre_confirm_prompt = "Карта вставлена в слот?",
.init = usd_init, .init = usd_init,
.run = usd_run, .run = usd_run,
.deinit = usd_deinit, .deinit = usd_deinit,

View file

@ -287,6 +287,10 @@ ResultsPanel .section-title {
width: 100%; width: 100%;
} }
#results-empty.hidden {
display: none;
}
#results-empty-text { #results-empty-text {
color: $text-muted; color: $text-muted;
text-align: center; text-align: center;

View file

@ -134,7 +134,7 @@ class FirmwareClient:
"""Сериализовать dict в JSON и отправить строку (blocking).""" """Сериализовать dict в JSON и отправить строку (blocking)."""
assert self._ser is not None assert self._ser is not None
line = json.dumps(obj, separators=(",", ":")) + "\n" line = json.dumps(obj, separators=(",", ":")) + "\n"
self._ser.write(line.encode("ascii")) self._ser.write(line.encode("utf-8"))
self._ser.flush() self._ser.flush()
def _read_line(self) -> Optional[dict]: def _read_line(self) -> Optional[dict]:
@ -143,7 +143,7 @@ class FirmwareClient:
raw = self._ser.readline() raw = self._ser.readline()
if not raw: if not raw:
return None 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: async def _send(self, obj: dict) -> None:
"""Отправить JSON-команду (async wrapper).""" """Отправить JSON-команду (async wrapper)."""

View file

@ -121,13 +121,13 @@ class M5Client:
"""Отправить команду, прочитать ответ (blocking).""" """Отправить команду, прочитать ответ (blocking)."""
assert self._ser is not None assert self._ser is not None
line = json.dumps(cmd, separators=(",", ":")) + "\n" line = json.dumps(cmd, separators=(",", ":")) + "\n"
self._ser.write(line.encode("ascii")) self._ser.write(line.encode("utf-8"))
self._ser.flush() self._ser.flush()
raw = self._ser.readline() raw = self._ser.readline()
if not raw: if not raw:
return None return None
try: try:
return json.loads(raw.decode("ascii", errors="replace").strip()) return json.loads(raw.decode("utf-8", errors="replace").strip())
except json.JSONDecodeError: except json.JSONDecodeError:
return None return None

View file

@ -1843,3 +1843,25 @@
2026-06-30 15:08:26,321 INFO app.m5_client M5StampPLC не найден 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:55,013 DEBUG asyncio Using selector: KqueueSelector
2026-06-30 15:08:56,561 INFO app.m5_client M5StampPLC не найден 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 не найден