实施A2/A3/A4并通过真机验证:net_task失败重试自检、ch395f有限超时故障锁存、remo_send短写阻断;@test 7/7,A1含配置持久化闭环

This commit is contained in:
2026-10-03 09:05:57 +08:00
parent ddbba714f7
commit 1780e5320b
8 changed files with 165 additions and 33 deletions
+49 -9
View File
@@ -8,6 +8,8 @@
* 修改记录:
* 2026-03-14 王建锋 创建初始版本,按代码规范重构
* 2026-07-18 王建锋 改进write_send_buf/read_recv_buf使用SPI2 DMA批量传输
* 2026-10-02 王建锋 E-34:3 处字节级 SPI 调用由 HAL_MAX_DELAY 改有限超时(100ms)
* + 返回值检查 + 故障锁存(沿程只记一条日志);原 SPI 异常时调用方永久挂死
*/
/* 头文件包含区 - 先包含系统头文件,再包含自定义头文件 */
@@ -30,6 +32,7 @@
#define CH395F_RESET_DELAY_MS 20U /* 复位后等待延时(ms) */
#define CH395F_INIT_TIMEOUT_MS 500U /* 初始化超时时间(ms) */
#define CH395F_SPI_BEGIN_DELAY_MS 1U /* SPI事务开始延时(ms) */
#define CH395F_SPI_BYTE_TIMEOUT_MS 100U /* E-34:字节级SPI有限超时(原 HAL_MAX_DELAY) */
/* DMA缓冲大小使用ch395f.h中公开的CH395F_SPI_DMA_BUF_SIZE */
#define CH395F_DMA_BUF_SIZE CH395F_SPI_DMA_BUF_SIZE
@@ -37,6 +40,7 @@
* 静态变量定义区 - DMA传输相关
*/
static volatile uint8_t s_spi2_dma_done = 0U; /* DMA传输完成标志 */
static uint8_t s_spi_byte_fault = 0U; /* E-34:字节级SPI故障锁存(超时/出错置位,MCU复位清除) */
__attribute__((aligned(4)))
static uint8_t s_spi2_dma_tx_buf[CH395F_DMA_BUF_SIZE]; /* DMA发送缓冲区 */
@@ -49,6 +53,7 @@ static uint8_t s_spi2_dma_rx_buf[CH395F_DMA_BUF_SIZE]; /* DMA接收缓冲区 */
static void ch395f_spi_begin(void);
static void ch395f_spi_end(void);
static void ch395f_spi_dma_transfer(uint16_t len);
static void ch395f_spi_byte_fault_set(void);
/*
* 私有函数定义区
@@ -76,6 +81,21 @@ static void ch395f_spi_end(void) {
HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_SET);
}
/*
* 函数功能:锁存一次字节级SPI故障(E-34:仅首条故障记日志)
* 入口参数:无
* 返回值:无
* 限定条件:无
* 函数说明:任一字节级SPI调用超时/失败时调用;置位 s_spi_byte_fault 并记一条日志,
* 后续故障静默(防串口刷屏);上层经 ch395f_spi_byte_fault() 查询
*/
static void ch395f_spi_byte_fault_set(void) {
if (s_spi_byte_fault == 0U) {
s_spi_byte_fault = 1U;
DBG_ERROR("SPI byte op fail, channel latched");
}
}
/*
* 函数功能:DMA全双工传输并等待完成
* 入口参数:len - 传输长度(字节) uint16_t 1 - CH395F_DMA_BUF_SIZE
@@ -175,10 +195,13 @@ void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) {
* 函数说明:SPI模式下命令和数据字节之间无需间隔
*/
uint8_t ch395f_write_cmd(uint8_t cmd) {
uint8_t rx_data;
uint8_t rx_data = 0xFFU;
/* 通过SPI发送命令并接收回显 */
HAL_SPI_TransmitReceive(&hspi2, &cmd, &rx_data, 1, HAL_MAX_DELAY);
/* 通过SPI发送命令并接收回显(E-34:有限超时+返回值检查;失败返回0xFF占位并锁存故障) */
if (HAL_SPI_TransmitReceive(&hspi2, &cmd, &rx_data, 1,
CH395F_SPI_BYTE_TIMEOUT_MS) != HAL_OK) {
ch395f_spi_byte_fault_set();
}
return rx_data;
}
@@ -189,10 +212,13 @@ uint8_t ch395f_write_cmd(uint8_t cmd) {
* 限定条件:必须在ch395f_spi_begin()和ch395f_spi_end()之间调用
*/
uint8_t ch395f_write_data(uint8_t data) {
uint8_t rx_data;
uint8_t rx_data = 0xFFU;
/* 通过SPI发送数据并接收回显 */
HAL_SPI_TransmitReceive(&hspi2, &data, &rx_data, 1, HAL_MAX_DELAY);
/* 通过SPI发送数据并接收回显(E-34:有限超时+返回值检查) */
if (HAL_SPI_TransmitReceive(&hspi2, &data, &rx_data, 1,
CH395F_SPI_BYTE_TIMEOUT_MS) != HAL_OK) {
ch395f_spi_byte_fault_set();
}
return rx_data;
}
@@ -204,13 +230,27 @@ uint8_t ch395f_write_data(uint8_t data) {
*/
uint8_t ch395f_read_data(void) {
uint8_t tx_dummy = 0xFFU;
uint8_t rx_data;
uint8_t rx_data = 0xFFU;
/* 发送0xFF作为时钟源,读取芯片返回数据 */
HAL_SPI_TransmitReceive(&hspi2, &tx_dummy, &rx_data, 1, HAL_MAX_DELAY);
/* 发送0xFF作为时钟源,读取芯片返回数据(E-34:有限超时+返回值检查) */
if (HAL_SPI_TransmitReceive(&hspi2, &tx_dummy, &rx_data, 1,
CH395F_SPI_BYTE_TIMEOUT_MS) != HAL_OK) {
ch395f_spi_byte_fault_set();
}
return rx_data;
}
/*
* 函数功能:查询字节级SPI故障锁存(E-34)
* 返回值:0 - 正常 1 - 曾发生字节级SPI超时/错误
* 限定条件:无
* 函数说明:锁存式(不自动清除,MCU 复位前一直有效);上层据此判定
* "SPI 通道异常"(区别于"无芯片"/"PHY 未链接");仅首条故障记日志
*/
uint8_t ch395f_spi_byte_fault(void) {
return s_spi_byte_fault;
}
/*
* 函数功能:检测CH395芯片是否存在且通信正常
* 返回值:CH395F_STATUS_OK - 芯片检测成功 ch395f_status_t
+9
View File
@@ -301,6 +301,15 @@ uint8_t ch395f_write_data(uint8_t data);
*/
uint8_t ch395f_read_data(void);
/*
* 函数功能:查询字节级SPI故障锁存(E-34)
* 返回值:0 - 正常 1 - 曾发生字节级SPI超时/错误
* 限定条件:无
* 函数说明:锁存式(MCU 复位前一直有效);上层据此判定"SPI 通道异常"
* (区别于"无芯片"/"PHY 未链接");仅首条故障记日志
*/
uint8_t ch395f_spi_byte_fault(void);
/*
* 函数声明区 - 芯片检测与版本
*/
+47 -8
View File
@@ -12,6 +12,9 @@
* 2026-09-10 王建锋 改为双端共用:底层改用 net_* API + 平台后端;接收改为逐字节
* 状态机(失步自动重同步);断开/网络重建后经 net_get_sock 判活
* 并重建监听(修复 MCU 单连接模型断线后无法重连的隐患)
* 2026-10-02 王建锋 E-04:remo_send 校验 net_send 返回值(原丢弃)——短写/失败
* 告警,连续 3 次失败阻断推送,新连接/网络重建解除阻断
* (MCU 单连接模型不得 net_close 数据 fd,会连监听一起毁掉)
*/
#include "ui_net_sink.h"
@@ -23,6 +26,10 @@
#include "test_config.h" /* TEST_LATENCY_PROBE(链路时延探针编译开关) */
#include "net_latency_test.h" /* M3 探针:prod_cycle_mcu(LCDMEM 处理净成本) */
/* E-04(2026-10-02):发送短写/失败告警(MCU=控制台日志,模拟器=printf 桩) */
#define DBG_TAG "[UI_NET_SINK]"
#include "dbg_log.h"
/* RemoDispBus 标签与命令(与 ui_net_sink.h 的 UI_REMO_* 常量对齐) */
#define TAG_CLIENT UI_REMO_TAG_CLIENT
#define TAG_DEVICE UI_REMO_TAG_DEVICE
@@ -47,6 +54,8 @@ static int g_listen = -1; /* 监听 Socket fd(未启动或已停止时
static int g_client = -1; /* 当前客户端 Socket fd(无连接时为 -1) */
static uint16_t g_port; /* 监听端口(网络重建 / 断线后重监听用) */
static uint32_t g_gen; /* 建立监听时的网络代次号(变化 = 网络已重建) */
static uint8_t s_send_fail_run; /* 连续发送失败计数(E-04:0 = 正常) */
static uint8_t s_sink_blocked; /* 推送阻断标志(1 = 停发帧,等客户端重连解除) */
/*
* 函数功能:计算 RemoDispBus 帧 payload 的 CRC 校验值
@@ -95,16 +104,25 @@ static void put_u32(uint8_t *p, uint32_t v) {
/*
* 函数功能:构造并发送一帧 RemoDispBus 响应(设备→客户端)
* 入口参数:cmd - 命令码;data - payload 指针(len 非 0 时须非空);len - payload 长度
* 返回值:无
* 返回值:0 - 整帧发出;-1 - 发送失败/短写/推送已阻断
* 限定条件:g_tx 空间足够容纳 4+len+1 字节;g_client >= 0(已有连接)
* 函数说明:帧格式 [TAG_DEVICE][CMD][LEN_H][LEN_LO][DATA][CRC];CRC 为 payload 异或;
* 无连接时直接返回
* E-04(2026-10-02):校验 net_send 返回值(原丢弃)——短写 = 客户端收到坏帧
* (CRC 不符 → 画面卡死黑屏,而设备侧日志全正常);连续 3 次失败 →
* 停发:客户端察觉无帧自行重连,芯片侧 DISCONNECT/TIMEOUT 自动重开
* 监听重建连接;MCU 单连接模型严禁 net_close 数据 fd(会连监听一起毁)
*/
static void remo_send(uint8_t cmd, const uint8_t *data, uint16_t len) {
static int remo_send(uint8_t cmd, const uint8_t *data, uint16_t len) {
uint8_t *p;
int total;
int ret;
if (g_client < 0) {
return;
return -1;
}
if (s_sink_blocked) {
return -1; /* 推送已阻断:等客户端重连解除 */
}
p = g_tx;
@@ -116,7 +134,24 @@ static void remo_send(uint8_t cmd, const uint8_t *data, uint16_t len) {
memcpy(p + 4, data, len);
}
p[4 + len] = remo_crc(data, len);
net_send(g_client, p, (int)(4 + len + 1), 0);
total = (int)(4 + len + 1);
ret = net_send(g_client, p, total, 0);
if (ret == total) {
s_send_fail_run = 0U;
return 0;
}
/* 短写/发送失败:告警 + 累计连续失败数 */
if (s_send_fail_run < 255U) {
s_send_fail_run++;
}
DBG_ERROR("remo send short: ret=%d/%d run=%u", ret, total,
(unsigned)s_send_fail_run);
if (s_send_fail_run >= 3U) {
s_sink_blocked = 1U;
DBG_ERROR("remo sink blocked, await client reconnect");
}
return -1;
}
/*
@@ -135,7 +170,7 @@ static void remo_send_init(void) {
put_u16(init, UI_W);
put_u16(init + 2, UI_H);
put_u32(init + 4, UI_MEMSIZE);
remo_send(CMD_INIT, init, 8);
(void)remo_send(CMD_INIT, init, 8);
}
/*
@@ -196,7 +231,7 @@ static void remo_handle(const uint8_t *f, int n) {
switch (cmd) {
case CMD_KEEPLIVE:
/* 心跳保活:原样回显一个空 KEEPLIVE 帧,维持连接活性 */
remo_send(CMD_KEEPLIVE, NULL, 0);
(void)remo_send(CMD_KEEPLIVE, NULL, 0);
break;
case CMD_INIT:
/* 查询屏幕参数:回送 [宽u16][高u16][帧字节数u32](大端,共 6 字节) */
@@ -208,7 +243,7 @@ static void remo_handle(const uint8_t *f, int n) {
ui_net_sink_inject_key(data[0]);
}
/* 回空帧作“已接收”确认(LEN=0,CRC=0) */
remo_send(CMD_KEY, NULL, 0);
(void)remo_send(CMD_KEY, NULL, 0);
break;
case CMD_LCDMEM: {
/* 读取显存:数据段为 4 字节大端起始偏移,不足 4 字节判非法丢弃 */
@@ -236,7 +271,7 @@ static void remo_handle(const uint8_t *f, int n) {
if (ui_net_sink_get_lcdmem(g_lcdmem_data + 4, UI_MEMSIZE) == 0) {
/* 应答数据段 = [偏移u32(回填请求原值)] + [1bpp 位图 3200B] */
memcpy(g_lcdmem_data, data, 4);
remo_send(CMD_LCDMEM, g_lcdmem_data, (uint16_t)(4 + UI_MEMSIZE));
(void)remo_send(CMD_LCDMEM, g_lcdmem_data, (uint16_t)(4 + UI_MEMSIZE));
}
#if defined(TEST_LATENCY_PROBE)
lat_probe_add_seg("prod_cycle_mcu", lat_mark_us(&tm));
@@ -433,6 +468,8 @@ void ui_net_sink_poll(void) {
}
g_client = -1;
rm_parser_reset();
s_send_fail_run = 0U; /* 网络重建:失败累计清零、解除推送阻断(E-04) */
s_sink_blocked = 0U;
(void)ui_net_sink_start(g_port);
return;
}
@@ -449,6 +486,8 @@ void ui_net_sink_poll(void) {
if (c >= 0) {
g_client = c;
rm_parser_reset();
s_send_fail_run = 0U; /* 新连接:失败累计清零、解除推送阻断(E-04) */
s_sink_blocked = 0U;
}
return;
}