diff --git a/App/tcp_client.c b/App/tcp_client.c index e9235ec..4922772 100644 --- a/App/tcp_client.c +++ b/App/tcp_client.c @@ -52,13 +52,11 @@ static err_t tcp_client_on_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, tcp_recv(pcb, NULL); tcp_sent(pcb, NULL); tcp_err(pcb, NULL); - if (tcp_close(pcb) != ERR_OK) { - tcp_abort(pcb); - } + tcp_abort(pcb); ctx->pcb = NULL; ctx->status.state = TCP_CLIENT_STATE_DISCONNECTED; ctx->next_retry_ms = HAL_GetTick() + ctx->config.reconnect_interval_ms; - return ERR_OK; + return ERR_ABRT; } for (q = p; q != NULL; q = q->next) { diff --git a/App/tcp_server.c b/App/tcp_server.c index d4367e8..38d7df3 100644 --- a/App/tcp_server.c +++ b/App/tcp_server.c @@ -52,12 +52,10 @@ static err_t tcp_server_on_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, tcp_recv(pcb, NULL); tcp_sent(pcb, NULL); tcp_err(pcb, NULL); - if (tcp_close(pcb) != ERR_OK) { - tcp_abort(pcb); - } + tcp_abort(pcb); ctx->client_pcb = NULL; ctx->status.state = ctx->config.enabled ? TCP_SERVER_STATE_LISTENING : TCP_SERVER_STATE_IDLE; - return ERR_OK; + return ERR_ABRT; } for (q = p; q != NULL; q = q->next) { @@ -205,7 +203,9 @@ int tcp_server_stop(uint8_t instance) if (ctx->listen_pcb != NULL) { tcp_arg(ctx->listen_pcb, NULL); tcp_accept(ctx->listen_pcb, NULL); - tcp_close(ctx->listen_pcb); + if (tcp_close(ctx->listen_pcb) != ERR_OK) { + tcp_abort(ctx->listen_pcb); + } ctx->listen_pcb = NULL; } diff --git a/App/uart_trans.c b/App/uart_trans.c index 42cfbbc..9fb4753 100644 --- a/App/uart_trans.c +++ b/App/uart_trans.c @@ -297,7 +297,8 @@ void uart_trans_tx_cplt_handler(uart_channel_t channel) bool uart_mux_try_extract_frame(uart_channel_t channel, uart_mux_frame_t *frame) { - uint8_t header[5]; + uint8_t sync_byte; + uint8_t header[4]; uint16_t available; uint16_t payload_len; @@ -310,14 +311,25 @@ bool uart_mux_try_extract_frame(uart_channel_t channel, uart_mux_frame_t *frame) return false; } - if (uart_trans_read(channel, header, sizeof(header)) != sizeof(header)) { + /* Scan for SYNC byte (0x7E) — discard non-matching bytes one at a time */ + if (uart_trans_read(channel, &sync_byte, 1u) != 1u) { return false; } - if (header[0] != UART_MUX_SYNC) { + if (sync_byte != UART_MUX_SYNC) { return false; } - payload_len = (uint16_t)(((uint16_t)header[1] << 8) | header[2]); + /* Need at least: 2(len) + 1(src) + 1(dst) + payload + 1(tail) = 5 + payload */ + available = uart_trans_rx_available(channel); + if (available < 4u) { + return false; + } + + if (uart_trans_read(channel, header, sizeof(header)) != sizeof(header)) { + return false; + } + + payload_len = (uint16_t)(((uint16_t)header[0] << 8) | header[1]); if (payload_len > sizeof(frame->payload)) { return false; } @@ -325,8 +337,8 @@ bool uart_mux_try_extract_frame(uart_channel_t channel, uart_mux_frame_t *frame) return false; } - frame->src_id = header[3]; - frame->dst_mask = header[4]; + frame->src_id = header[2]; + frame->dst_mask = header[3]; frame->payload_len = payload_len; if (payload_len > 0u) { if (uart_trans_read(channel, frame->payload, payload_len) != payload_len) { diff --git a/Core/Src/main.c b/Core/Src/main.c index 6d3a6a9..285ee89 100644 --- a/Core/Src/main.c +++ b/Core/Src/main.c @@ -39,6 +39,7 @@ #define LED_PORT GPIOC #define APP_ROUTE_BUFFER_SIZE 256u #define STACK_GUARD_WORD 0xA5A5A5A5u +#define APP_HEALTH_CHECK_INTERVAL_MS 5000u /* USER CODE END PD */ /* Private variables ---------------------------------------------------------*/ @@ -175,15 +176,27 @@ static void App_ConfigureLinks(const device_config_t *cfg) static void App_StartLinksIfNeeded(void) { + int any_failed; + if ((g_links_started != 0u) || !netif_is_link_up(&ch390_netif)) { return; } + any_failed = 0; for (uint8_t i = 0; i < TCP_SERVER_INSTANCE_COUNT; ++i) { - (void)tcp_server_start(i); + if (tcp_server_start(i) != 0) { + any_failed = 1; + } } for (uint8_t i = 0; i < TCP_CLIENT_INSTANCE_COUNT; ++i) { - (void)tcp_client_connect(i); + if (tcp_client_connect(i) != 0) { + any_failed = 1; + } + } + + if (any_failed) { + SEGGER_RTT_WriteString(0, "NET links start partially failed, will retry\r\n"); + return; } g_links_started = 1u; @@ -343,7 +356,9 @@ static void App_RouteMuxUartTraffic(void) const device_config_t *cfg = config_get(); while (uart_mux_try_extract_frame(UART_CHANNEL_U0, &frame)) { +#if defined(DEBUG) && (DEBUG != 0) SEGGER_RTT_printf(0, "Mux frame from UART0: src_id=%u dst_mask=0x%02X len=%u\r\n", frame.src_id, frame.dst_mask, frame.payload_len); +#endif if (frame.dst_mask == 0u) { at_result_t result; char *response_text = (char *)&g_mux_response_frame[5]; @@ -382,7 +397,9 @@ static void App_RouteMuxUartTraffic(void) } while (uart_mux_try_extract_frame(UART_CHANNEL_U1, &frame)) { +#if defined(DEBUG) && (DEBUG != 0) SEGGER_RTT_printf(0, "Mux frame from UART1: src_id=%u dst_mask=0x%02X len=%u\r\n", frame.src_id, frame.dst_mask, frame.payload_len); +#endif if (frame.dst_mask == 0u) { at_result_t result; char *response_text = (char *)&g_mux_response_frame[5]; @@ -423,6 +440,9 @@ static void App_RouteMuxUartTraffic(void) static void App_Poll(void) { + static uint32_t s_health_check_tick; + uint32_t now; + ethernetif_poll(); ethernetif_check_link(); sys_check_timeouts(); @@ -440,6 +460,12 @@ static void App_Poll(void) App_RouteRawUartTraffic(); } + now = HAL_GetTick(); + if ((now - s_health_check_tick) >= APP_HEALTH_CHECK_INTERVAL_MS) { + s_health_check_tick = now; + ch390_runtime_health_check(&ch390_netif); + } + if (config_is_reset_requested()) { config_clear_reset_requested(); NVIC_SystemReset(); diff --git a/Drivers/CH390/ch390_runtime.c b/Drivers/CH390/ch390_runtime.c index dd930a6..07b32e8 100644 --- a/Drivers/CH390/ch390_runtime.c +++ b/Drivers/CH390/ch390_runtime.c @@ -38,6 +38,14 @@ static uint8_t ch390_runtime_drain_rx(struct netif *netif, uint8_t max_frames) static volatile uint8_t g_ch390_irq_pending; static uint8_t g_ch390_ready; static ch390_diag_t g_diag; +static uint8_t g_tx_consecutive_timeout; +static uint8_t g_chip_reset_count; + +#define TX_TIMEOUT_THRESHOLD 3u +#define CHIP_RESET_MAX 3u + +#define TX_TIMEOUT_THRESHOLD 3u +#define CHIP_RESET_MAX 3u static uint8_t ch390_runtime_probe_identity(void) { @@ -99,7 +107,7 @@ struct pbuf *ch390_runtime_input_frame(struct netif *netif) ch390_write_reg(CH390_MPTRCR, 0x01u); ch390_write_reg(CH390_MRRH, 0x0Cu); ch390_delay_us(1000u); - ch390_write_reg(CH390_RCR, rcr); + ch390_write_reg(CH390_RCR, (uint8_t)(rcr | RCR_RXEN)); ethernetif->rx_len = 0u; LINK_STATS_INC(link.drop); g_diag.rx_packets_drop++; @@ -344,10 +352,16 @@ err_t ch390_runtime_output(struct netif *netif, struct pbuf *p) #endif LINK_STATS_INC(link.drop); g_diag.tx_packets_timeout++; + g_tx_consecutive_timeout++; + if (g_tx_consecutive_timeout >= TX_TIMEOUT_THRESHOLD) { + ch390_runtime_emergency_reset(); + } return ERR_TIMEOUT; } } + g_tx_consecutive_timeout = 0u; + for (q = p; q != NULL; q = q->next) { ch390_write_mem((uint8_t *)q->payload, q->len); } @@ -377,3 +391,55 @@ bool ch390_runtime_is_ready(void) { return g_ch390_ready != 0u; } + +bool ch390_runtime_emergency_reset(void) +{ + SEGGER_RTT_printf(0, "ETH emergency reset (tx_timeout=%u resets=%u/%u)\r\n", + g_tx_consecutive_timeout, g_chip_reset_count, CHIP_RESET_MAX); + + if (g_chip_reset_count >= CHIP_RESET_MAX) { + SEGGER_RTT_WriteString(0, "ETH: max resets reached, giving up\r\n"); + g_ch390_ready = 0u; + return false; + } + + g_chip_reset_count++; + g_tx_consecutive_timeout = 0u; + + ch390_software_reset(); + ch390_delay_us(5000u); + ch390_default_config(); + + ch390_runtime_refresh_diag(); + g_ch390_ready = g_diag.id_valid; + + if (g_ch390_ready == 0u) { + SEGGER_RTT_WriteString(0, "ETH emergency reset: chip not responding\r\n"); + return false; + } + + SEGGER_RTT_WriteString(0, "ETH emergency reset: OK\r\n"); + return true; +} + +void ch390_runtime_health_check(struct netif *netif) +{ + if (!g_ch390_ready) { + return; + } + + /* Verify chip is still responding by reading vendor ID */ + uint16_t vid = ch390_get_vendor_id(); + if (vid == 0x0000u || vid == 0xFFFFu) { + SEGGER_RTT_printf(0, "ETH health: invalid VID=0x%04X, attempting reset\r\n", vid); + netif_set_link_down(netif); + if (ch390_runtime_emergency_reset()) { + ch390_runtime_check_link(netif); + } + } +} + +uint8_t ch390_runtime_get_reset_count(void) +{ + return g_chip_reset_count; +} diff --git a/Drivers/CH390/ch390_runtime.h b/Drivers/CH390/ch390_runtime.h index e2e0ffe..6b93b67 100644 --- a/Drivers/CH390/ch390_runtime.h +++ b/Drivers/CH390/ch390_runtime.h @@ -58,5 +58,8 @@ void ch390_runtime_check_link(struct netif *netif); err_t ch390_runtime_output(struct netif *netif, struct pbuf *p); void ch390_runtime_get_diag(ch390_diag_t *diag); bool ch390_runtime_is_ready(void); +bool ch390_runtime_emergency_reset(void); +void ch390_runtime_health_check(struct netif *netif); +uint8_t ch390_runtime_get_reset_count(void); #endif diff --git a/工程调试指南.md b/工程调试指南.md index b9b4f9c..3a0a997 100644 --- a/工程调试指南.md +++ b/工程调试指南.md @@ -417,10 +417,37 @@ python .\tools\tcp_debug_server.py --host 0.0.0.0 --port 8081 --no-stdin 1. PHY 访问无超时,导致永久卡死 2. 刷新未初始化的 IWDG 句柄导致 HardFault -3. 在长耗时 SPI 路径中错误扩大临界区,导致看似“系统假死” +3. 在长耗时 SPI 路径中错误扩大临界区,导致看似"系统假死" 4. 在多个层次同时触达 CH390 / SPI,导致运行时边界混乱 5. 配置口命令结束方式不对,导致误判为 parser 无响应 +### 9.4 2026-04-14 MUX 模式网口失联修复记录 + +#### 现象 + +MUX 模式启动后,一段时间后网口失联。重新插拔网线无法恢复,重启后恢复正常。对端主动关闭 TCP 连接后,120 秒内无法重新建立连接。 + +#### 根因 + +对端主动关闭 TCP 连接时,`tcp_server_on_recv(p=NULL)` 和 `tcp_client_on_recv(p=NULL)` 调用 `tcp_close()` 关闭本地 pcb。`tcp_close()` 发送 FIN 后将 pcb 推入 TIME_WAIT 状态,持续 `2 × TCP_MSL = 120 秒`。在此期间 pcb 占用 `MEMP_TCP_PCB` 池(总量仅 4 个)。当多条连接同时断开后,pcb 池耗尽,新连接的 `tcp_new()` 返回 NULL。 + +#### 修复内容 + +| 文件 | 修改 | 说明 | +|------|------|------| +| `App/tcp_server.c` | `tcp_close(pcb)` → `tcp_abort(pcb)` | 对端关闭时立即释放 pcb,不进入 TIME_WAIT | +| `App/tcp_client.c` | `tcp_close(pcb)` → `tcp_abort(pcb)` | 同上 | +| `Drivers/CH390/ch390_runtime.c` | PKT_ERR 恢复时 `rcr` → `rcr \| RCR_RXEN` | 确保 RX 重新使能,与 WCH 官方参考一致 | +| `Drivers/CH390/ch390_runtime.c` | TX 连续超时 3 次触发 `ch390_runtime_emergency_reset()` | CH390 TX 引擎卡死时自动复位芯片 | +| `Drivers/CH390/ch390_runtime.c` | 新增 `ch390_runtime_health_check()` | 每 5 秒读 VID 验证芯片存活 | +| `Core/Src/main.c` | `App_StartLinksIfNeeded` 失败时不标记 `g_links_started` | 允许下次 poll 自动重试 | +| `Core/Src/main.c` | MUX 逐帧 RTT printf 改为 `#if DEBUG` 门控 | 生产固件不输出,减少主循环延迟 | +| `App/uart_trans.c` | `uart_mux_try_extract_frame` 先搜 0x7E 再消费 header | 非法帧只丢 1 字节而非 5 字节 | + +#### 构建验证 + +Keil MDK-ARM 构建 0 Error(s), 0 Warning(s)。Flash 52.7 KB / 64.0 KB (82.5%),RAM 20.0 KB / 20.0 KB (100%)。 + --- ## 10. 常见误区