# bsp: usd - исправления бага с переинициализацией
This commit is contained in:
parent
6e42970fef
commit
0b16f5cf19
11 changed files with 86 additions and 23 deletions
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)."""
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 не найден
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue