/* * 模块名称:CH395F SPI以太网协议栈驱动 * 模块功能:CH395F以太网协议栈芯片SPI驱动实现,提供芯片检测、初始化、 * 网络参数配置、PHY管理、DHCP、Socket管理及数据收发功能 * 适用平台:STM32F4系列(SPI2接口) * 作者:王建锋 * 创建日期:2026-03-14 * 修改记录: * 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 异常时调用方永久挂死 * 2026-10-04 王建锋 P2-2:ch395f_spi_dma_transfer 由 void 改为返回状态(0 成功/-1 失败), * DMA 初始化失败/超时中止均锁存通道故障;read_recv_buf 在 DMA 失败时不再把 * rx_buf 内容拷入调用方缓冲(改零填充),消除"失败当成功"的静默数据损坏 */ /* 头文件包含区 - 先包含系统头文件,再包含自定义头文件 */ #include #include #include "ch395f.h" #include "spi.h" #include "usart.h" /* * 调试输出配置 * 深调用链模块(ch395f_spi_fault_set 等出现在 netTask/ftpTask/timeSyncTask * 最坏链尾部):日志行缓冲收到 128 B(默认 192)。本模块最长行 * "HAL_SPI_TransmitReceive_DMA FAIL: %d, SPI_State=%d, SPI_Error=0x%lX, DMA_State=%d" * 渲染后 ≈ 102 B(含时间戳 21 + 标签)< 128 → 不截断 */ #define DBG_LINE_BUF_MAX 128U #define DBG_TAG "[CH395]" #include "dbg_log.h" /* * 私有宏定义区 */ #define CH395F_CHECK_EXIST_TEST_DATA 0x57U /* 芯片检测测试数据 */ #define CH395F_RESET_DELAY_MS 20U /* 复位后等待延时(ms) */ #define CH395F_INIT_TIMEOUT_MS 500U /* 初始化超时时间(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 /* * 静态变量定义区 - DMA传输相关 */ static volatile uint8_t s_spi2_dma_done = 0U; /* DMA传输完成标志 */ static uint8_t s_spi_fault = 0U; /* E-34/P2-2:SPI 通道故障锁存(字节级或 DMA 级超时/出错置位,MCU复位清除) */ __attribute__((aligned(4))) static uint8_t s_spi2_dma_tx_buf[CH395F_DMA_BUF_SIZE]; /* DMA发送缓冲区 */ __attribute__((aligned(4))) 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 int ch395f_spi_dma_transfer(uint16_t len); static void ch395f_spi_fault_set(const char *kind); /* * 私有函数定义区 */ /* * 函数功能:启动一次SPI事务(CS高→低) * 限定条件:SPI2外设已初始化 * 函数说明:1. 先将CS置高再置低,满足片选信号最小间隔要求 * 2. SPI模式下字节间无需额外延时 */ static void ch395f_spi_begin(void) { /* CS置高确保空闲状态 */ HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_SET); /* CS置低启动事务(无需额外延时,SPI时序由SCK频率决定) */ HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_RESET); } /* * 函数功能:结束一次SPI事务(CS置高) * 限定条件:SPI2外设已初始化 */ static void ch395f_spi_end(void) { /* CS置高结束事务 */ HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_SET); } /* * 函数功能:锁存一次 SPI 通道故障(E-34/P2-2:仅首条故障记日志) * 入口参数:kind - 故障类别标签("byte"/"dma-hal"/"dma-timeout") const char* * 返回值:无 * 限定条件:无 * 函数说明:任一字节级或 DMA 级 SPI 调用超时/失败时调用;置位 s_spi_fault 并记一条日志, * 后续故障静默(防串口刷屏);上层经 ch395f_spi_byte_fault() 查询 */ static void ch395f_spi_fault_set(const char *kind) { if (s_spi_fault == 0U) { s_spi_fault = 1U; DBG_ERROR("SPI channel fault latched: %s", kind); } } /* * 函数功能:DMA全双工传输并等待完成 * 入口参数:len - 传输长度(字节) uint16_t 1 - CH395F_DMA_BUF_SIZE * 返回值:0 - 传输完成且无错误 -1 - 传输失败(HAL 启动失败 / 超时中止) * 限定条件:1. s_spi2_dma_tx_buf中已填充待发送数据 * 2. 需确保DMA传输缓冲区已对齐到4字节边界 * 函数说明:1. 发送s_spi2_dma_tx_buf内容,同时接收数据存入s_spi2_dma_rx_buf * 2. 阻塞等待DMA完成回调置标志位 * 3. 超时(1秒)时强制中止SPI DMA并打印错误信息 * 4. 传输完成后检查SPI错误码并清除溢出标志 * 5. P2-2(2026-10-04):失败路径锁存通道故障并返回 -1,调用方据此 * 判定 rx_buf 内容不可信,不再"失败当成功"继续消费 */ static int ch395f_spi_dma_transfer(uint16_t len) { HAL_StatusTypeDef status; uint32_t tickstart; volatile uint32_t loop_cnt = 0U; s_spi2_dma_done = 0U; status = HAL_SPI_TransmitReceive_DMA(&hspi2, s_spi2_dma_tx_buf, s_spi2_dma_rx_buf, len); if (status != HAL_OK) { DBG_ERROR("HAL_SPI_TransmitReceive_DMA FAIL: %d, SPI_State=%d, SPI_Error=0x%lX, DMA_State=%d", (int)status, (int)hspi2.State, hspi2.ErrorCode, (int)hspi2.hdmarx->State); ch395f_spi_fault_set("dma-hal"); return -1; } tickstart = HAL_GetTick(); while (s_spi2_dma_done == 0U) { loop_cnt++; if ((HAL_GetTick() - tickstart) > 1000U) { DBG_ERROR("TIMEOUT after 1s, len=%d, loop=%lu, SPI_State=%d, SPI_Error=0x%lX", (int)len, loop_cnt, (int)hspi2.State, hspi2.ErrorCode); DBG_ERROR(" DMA_RX: State=%d, Dir=%lu, CNT=%lu", (int)hspi2.hdmarx->State, hspi2.hdmarx->Init.Direction, __HAL_DMA_GET_COUNTER(hspi2.hdmarx)); DBG_ERROR(" DMA_TX: State=%d, Dir=%lu, CNT=%lu", (int)hspi2.hdmatx->State, hspi2.hdmatx->Init.Direction, __HAL_DMA_GET_COUNTER(hspi2.hdmatx)); DBG_ERROR(" SPI2_CR1=0x%lX, CR2=0x%lX, SR=0x%lX", SPI2->CR1, SPI2->CR2, SPI2->SR); /* 强制中止SPI2 DMA状态,避免死锁 */ HAL_SPI_Abort(&hspi2); ch395f_spi_fault_set("dma-timeout"); return -1; } } /* 检查传输是否成功:SPI错误码必须为0 */ if (hspi2.ErrorCode != 0U) { DBG_INFO("SPI DMA done with errors: ErrorCode=0x%lX, SPI_State=%d", hspi2.ErrorCode, (int)hspi2.State); __HAL_SPI_CLEAR_OVRFLAG(&hspi2); } return 0; } /* * 函数功能:SPI传输完成回调函数 * 入口参数:hspi - SPI句柄指针 SPI_HandleTypeDef* * 函数说明:由HAL SPI驱动在DMA TX+RX完成后调用 */ /* SPI1 DMA完成标志(定义在gd5f2gq5ue.c中) */ extern volatile uint8_t g_spi1_dma_done; void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance == SPI2) { s_spi2_dma_done = 1U; } else if (hspi->Instance == SPI1) { g_spi1_dma_done = 1U; } } /* * 函数功能:SPI错误回调函数 * 入口参数:hspi - SPI句柄指针 SPI_HandleTypeDef* * 函数说明:由HAL SPI驱动在DMA传输错误时调用,释放等待者 */ void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance == SPI2) { s_spi2_dma_done = 1U; } else if (hspi->Instance == SPI1) { g_spi1_dma_done = 1U; } } /* * 公共函数定义区 */ /* * 函数功能:向CH395写入一个命令字节 * 入口参数:cmd - 命令字节 uint8_t 0x00 - 0xFF * 返回值:命令回显字节 uint8_t * 限定条件:必须在ch395f_spi_begin()和ch395f_spi_end()之间调用 * 函数说明:SPI模式下命令和数据字节之间无需间隔 */ uint8_t ch395f_write_cmd(uint8_t cmd) { uint8_t rx_data = 0xFFU; /* 通过SPI发送命令并接收回显(E-34:有限超时+返回值检查;失败返回0xFF占位并锁存故障) */ if (HAL_SPI_TransmitReceive(&hspi2, &cmd, &rx_data, 1, CH395F_SPI_BYTE_TIMEOUT_MS) != HAL_OK) { ch395f_spi_fault_set("byte"); } return rx_data; } /* * 函数功能:向CH395写入一个数据字节 * 入口参数:data - 数据字节 uint8_t 0x00 - 0xFF * 返回值:数据回显字节 uint8_t * 限定条件:必须在ch395f_spi_begin()和ch395f_spi_end()之间调用 */ uint8_t ch395f_write_data(uint8_t data) { uint8_t rx_data = 0xFFU; /* 通过SPI发送数据并接收回显(E-34:有限超时+返回值检查) */ if (HAL_SPI_TransmitReceive(&hspi2, &data, &rx_data, 1, CH395F_SPI_BYTE_TIMEOUT_MS) != HAL_OK) { ch395f_spi_fault_set("byte"); } return rx_data; } /* * 函数功能:从CH395读取一个数据字节 * 返回值:接收到的数据字节 uint8_t * 限定条件:必须在ch395f_spi_begin()和ch395f_spi_end()之间调用 * 函数说明:发送0xFF作为SPI时钟源,同时读取芯片返回的数据 */ uint8_t ch395f_read_data(void) { uint8_t tx_dummy = 0xFFU; uint8_t rx_data = 0xFFU; /* 发送0xFF作为时钟源,读取芯片返回数据(E-34:有限超时+返回值检查) */ if (HAL_SPI_TransmitReceive(&hspi2, &tx_dummy, &rx_data, 1, CH395F_SPI_BYTE_TIMEOUT_MS) != HAL_OK) { ch395f_spi_fault_set("byte"); } return rx_data; } /* * 函数功能:查询 SPI 通道故障锁存(E-34 / P2-2) * 返回值:0 - 正常 1 - 曾发生 SPI 超时/错误(字节级或 DMA 级) * 限定条件:无 * 函数说明:锁存式(不自动清除,MCU 复位前一直有效);上层据此判定 * "SPI 通道异常"(区别于"无芯片"/"PHY 未链接");仅首条故障记日志 */ uint8_t ch395f_spi_byte_fault(void) { return s_spi_fault; } /* * 函数功能:检测CH395芯片是否存在且通信正常 * 返回值:CH395F_STATUS_OK - 芯片检测成功 ch395f_status_t * CH395F_STATUS_NOT_DETECTED - 芯片未检测到 ch395f_status_t * 限定条件:SPI2外设已正确初始化 * 函数说明:1. 发送CMD_CHECK_EXIST(0x06)后跟测试数据0x57 * 2. 芯片应返回测试数据的按位取反结果0xA8 * 3. 回显匹配则返回成功,否则返回未检测到 */ ch395f_status_t ch395f_check_exist(void) { uint8_t test_data = CH395F_CHECK_EXIST_TEST_DATA; uint8_t reply_data; /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送检测命令 */ ch395f_write_cmd(CH395F_CMD_CHECK_EXIST); /* 发送测试数据 */ ch395f_write_data(test_data); /* 读取芯片回复 */ reply_data = ch395f_read_data(); /* 结束SPI事务 */ ch395f_spi_end(); /* 验证回复是否为测试数据的按位取反 */ if (reply_data == (uint8_t)(~test_data)) { return CH395F_STATUS_OK; } return CH395F_STATUS_NOT_DETECTED; } /* * 函数功能:读取CH395芯片版本号 * 返回值:版本字节(bits 5:0 = 版本号) uint8_t * 限定条件:芯片已通过ch395f_check_exist()检测 */ uint8_t ch395f_get_version(void) { uint8_t version; /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送版本读取命令 */ ch395f_write_cmd(CH395F_CMD_GET_IC_VER); /* 读取版本号 */ version = ch395f_read_data(); /* 结束SPI事务 */ ch395f_spi_end(); return version; } /* * 函数功能:获取远程IP地址和端口号 * 入口参数:sock - Socket索引 uint8_t 0 - 7 * p_buf - 6字节输出缓冲区指针(4字节IP + 2字节端口) uint8_t* * 限定条件:1. p_buf不可为NULL * 2. 需在TCP Server模式下连接建立后调用 * 函数说明:IP和端口均为低字节在前 */ void ch395f_get_remot_ipp(uint8_t sock, uint8_t *p_buf) { uint8_t i; /* 检查输入参数合法性 */ if (p_buf == NULL) { return; } /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送获取远程IP和端口命令 */ ch395f_write_cmd(CH395F_CMD_GET_REMOT_IPP_SN); /* 发送Socket索引 */ ch395f_write_data(sock); /* 读取4字节IP地址 */ for (i = 0; i < 4; i++) { p_buf[i] = ch395f_read_data(); } /* 读取2字节端口号 */ for (i = 4; i < 6; i++) { p_buf[i] = ch395f_read_data(); } /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:设置CH395功能参数 * 入口参数:para - 4字节功能参数 uint32_t * 限定条件:必须在ch395f_init()之前调用 */ void ch395f_set_fun_para(uint32_t para) { /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置功能参数命令 */ ch395f_write_cmd(CH395F_CMD_SET_FUN_PARA); /* 发送4字节参数(低字节在前) */ ch395f_write_data((uint8_t)(para)); ch395f_write_data((uint8_t)(para >> 8)); ch395f_write_data((uint8_t)(para >> 16)); ch395f_write_data((uint8_t)(para >> 24)); /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:设置 TCP 最大分段大小(MSS) * 入口参数:mss - MSS值 uint16_t 60 - 1460 * 限定条件:必须在 ch395f_init() 之前调用 */ void ch395f_set_tcp_mss(uint16_t mss) { /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置TCP MSS命令 */ ch395f_write_cmd(CH395F_CMD_SET_TCP_MSS); /* 发送2字节MSS值(低字节在前) */ ch395f_write_data(mss & 0xFFU); ch395f_write_data((mss >> 8) & 0xFFU); /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:设置Socket接收缓冲区 * 入口参数:sock - Socket索引 uint8_t 0 - 7 * start_block - 起始块地址 uint8_t * block_count - 块数量 uint8_t * 限定条件:必须在OPEN_SOCKET_SN之前调用 */ void ch395f_set_recv_buf(uint8_t sock, uint8_t start_block, uint8_t block_count) { /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置接收缓冲区命令 */ ch395f_write_cmd(CH395F_CMD_SET_RECV_BUF); /* 发送Socket索引 */ ch395f_write_data(sock); /* 发送起始块地址 */ ch395f_write_data(start_block); /* 发送块数量 */ ch395f_write_data(block_count); /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:设置Socket发送缓冲区 * 入口参数:sock - Socket索引 uint8_t 0 - 7 * start_block - 起始块地址 uint8_t * block_count - 块数量 uint8_t * 限定条件:必须在OPEN_SOCKET_SN之前调用 */ void ch395f_set_send_buf(uint8_t sock, uint8_t start_block, uint8_t block_count) { /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置发送缓冲区命令 */ ch395f_write_cmd(CH395F_CMD_SET_SEND_BUF); /* 发送Socket索引 */ ch395f_write_data(sock); /* 发送起始块地址 */ ch395f_write_data(start_block); /* 发送块数量 */ ch395f_write_data(block_count); /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:清除Socket接收缓冲区 * 入口参数:sock - Socket索引 uint8_t 0 - 7 * 限定条件:Socket必须已打开 */ void ch395f_clear_recv_buf(uint8_t sock) { /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送清除接收缓冲区命令 */ ch395f_write_cmd(CH395F_CMD_CLEAR_RECV_BUF_SN); /* 发送Socket索引 */ ch395f_write_data(sock); /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:设置TCP KeepAlive空闲时间 * 入口参数:time - 空闲时间(毫秒,须为 500 的倍数) uint32_t * 限定条件:必须在ch395f_init()之前调用 */ void ch395f_set_keepalive_idle(uint32_t time) { /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置KeepAlive空闲时间命令 */ ch395f_write_cmd(CH395F_CMD_SET_KEEPALIVE_IDLE); /* 发送4字节时间值(低字节在前) */ ch395f_write_data((uint8_t)(time)); ch395f_write_data((uint8_t)(time >> 8)); ch395f_write_data((uint8_t)(time >> 16)); ch395f_write_data((uint8_t)(time >> 24)); /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:设置TCP KeepAlive探测间隔 * 入口参数:time - 探测间隔(毫秒,须为 500 的倍数) uint32_t * 限定条件:必须在ch395f_init()之前调用 */ void ch395f_set_keepalive_intvl(uint32_t time) { /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置KeepAlive探测间隔命令 */ ch395f_write_cmd(CH395F_CMD_SET_KEEPALIVE_INTVL); /* 发送4字节时间值(低字节在前) */ ch395f_write_data((uint8_t)(time)); ch395f_write_data((uint8_t)(time >> 8)); ch395f_write_data((uint8_t)(time >> 16)); ch395f_write_data((uint8_t)(time >> 24)); /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:设置TCP KeepAlive探测次数 * 入口参数:cnt - 探测次数 uint8_t * 限定条件:必须在ch395f_init()之前调用 */ void ch395f_set_keepalive_cnt(uint8_t cnt) { /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置KeepAlive探测次数命令 */ ch395f_write_cmd(CH395F_CMD_SET_KEEPALIVE_CNT); /* 发送探测次数 */ ch395f_write_data(cnt); /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:使能/禁能指定Socket的KeepAlive功能 * 入口参数:sock - Socket索引 uint8_t 0 - 7 * enable - 使能标志 uint8_t 1 = 使能,0 = 禁能 * 限定条件:Socket必须已打开 */ void ch395f_set_keepalive_enable(uint8_t sock, uint8_t enable) { /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置KeepAlive使能命令 */ ch395f_write_cmd(CH395F_CMD_SET_KEEPALIVE_SN); /* 发送Socket索引 */ ch395f_write_data(sock); /* 发送使能标志 */ ch395f_write_data(enable); /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:设置TCP重传次数 * 入口参数:cnt - 重传次数 uint8_t 0 = 使用默认值 * 限定条件:必须在ch395f_init()之前调用 */ void ch395f_set_retrans_count(uint8_t cnt) { /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置重传次数命令 */ ch395f_write_cmd(CH395F_CMD_SET_RETRAN_COUNT); /* 发送重传次数 */ ch395f_write_data(cnt); /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:设置TCP重传周期 * 入口参数:period - 重传周期(毫秒) uint16_t * 限定条件:必须在ch395f_init()之前调用 */ void ch395f_set_retrans_period(uint16_t period) { /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置重传周期命令 */ ch395f_write_cmd(CH395F_CMD_SET_RETRAN_PERIOD); /* 发送2字节周期值(低字节在前) */ ch395f_write_data(period & 0xFFU); ch395f_write_data((period >> 8) & 0xFFU); /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:软件复位CH395芯片 * 返回值:CH395F_STATUS_OK - 复位成功 ch395f_status_t * 限定条件:SPI2外设已正确初始化 * 函数说明:复位耗时约15ms(TE0),函数内部包含延时等待 */ ch395f_status_t ch395f_reset(void) { ch395f_spi_begin(); ch395f_write_cmd(CH395F_CMD_RESET_ALL); ch395f_spi_end(); HAL_Delay(CH395F_RESET_DELAY_MS); return CH395F_STATUS_OK; } /* * 函数功能:查询命令执行状态 * 返回值:状态字节(CH395F_ERR_SUCCESS / CH395F_ERR_BUSY / ...) uint8_t * 限定条件:仅用于耗时较长的命令(如初始化)之后查询 */ uint8_t ch395f_get_cmd_status(void) { uint8_t status; /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送状态查询命令 */ ch395f_write_cmd(CH395F_CMD_GET_CMD_STATUS); /* 读取状态字节 */ status = ch395f_read_data(); /* 结束SPI事务 */ ch395f_spi_end(); return status; } /* * 函数功能:轮询等待命令执行完成 * 入口参数:timeout_ms - 超时时间(毫秒) uint32_t * 返回值:命令执行状态(CH395F_ERR_SUCCESS = 成功,CH395F_ERR_BUSY = 超时) uint8_t * 限定条件:需在发送耗时较长的命令后调用 * 函数说明:每20ms查询一次,直到命令完成或超时 */ uint8_t ch395f_poll_cmd_status(uint32_t timeout_ms) { uint32_t retry_count = 0U; uint32_t max_retries = (timeout_ms + 20U - 1U) / 20U; while (1) { HAL_Delay(20U); uint8_t status = ch395f_get_cmd_status(); if (status != CH395F_ERR_BUSY) { return status; } if (++retry_count >= max_retries) { return CH395F_ERR_BUSY; /* 超时 */ } } } /* * 函数功能:初始化CH395(包括MAC、PHY、TCP/IP协议栈) * 返回值:CH395F_STATUS_OK - 初始化成功 ch395f_status_t * CH395F_STATUS_TIMEOUT - 初始化超时 ch395f_status_t * CH395F_STATUS_ERROR - 初始化失败 ch395f_status_t * 限定条件:芯片已通过ch395f_reset()复位 * 函数说明:芯片初始化需约200ms,函数内等待最多4秒 */ ch395f_status_t ch395f_init(void) { uint8_t status; ch395f_spi_begin(); ch395f_write_cmd(CH395F_CMD_INIT_CH395); ch395f_spi_end(); status = ch395f_poll_cmd_status(4000U); if (status == CH395F_ERR_SUCCESS) { return CH395F_STATUS_OK; } return (status == CH395F_ERR_BUSY) ? CH395F_STATUS_TIMEOUT : CH395F_STATUS_ERROR; } /* * 函数功能:设置CH395的IP地址(点分十进制序:p_ip[0]=第一段,见头文件声明注) * 入口参数:p_ip - 4字节IP地址指针 uint8_t* 192.168.x.x格式 * 限定条件:1. p_ip不可为NULL * 2. 必须在 ch395f_init() 之前调用(手册 §9.2.1:SET_IP 先于 INIT_CH395) */ void ch395f_set_ip_addr(uint8_t *p_ip) { uint8_t i; /* 检查输入参数合法性 */ if (p_ip == NULL) { return; } /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置IP命令 */ ch395f_write_cmd(CH395F_CMD_SET_IP_ADDR); /* 逐字节发送IP地址 */ for (i = 0; i < 4; i++) { ch395f_write_data(p_ip[i]); } /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:设置CH395的网关IP地址(点分十进制序,同 set_ip_addr) * 入口参数:p_gwip - 4字节网关IP指针 uint8_t* * 限定条件:1. p_gwip不可为NULL * 2. 必须在 ch395f_init() 之前调用(手册 §9.2.1:SET_GWIP 先于 INIT_CH395) */ void ch395f_set_gwip_addr(uint8_t *p_gwip) { uint8_t i; /* 检查输入参数合法性 */ if (p_gwip == NULL) { return; } /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置网关命令 */ ch395f_write_cmd(CH395F_CMD_SET_GWIP_ADDR); /* 逐字节发送网关IP地址 */ for (i = 0; i < 4; i++) { ch395f_write_data(p_gwip[i]); } /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:设置CH395的子网掩码(点分十进制序,同 set_ip_addr) * 入口参数:p_mask - 4字节子网掩码指针 uint8_t* * 限定条件:1. p_mask不可为NULL * 2. 必须在 ch395f_init() 之前调用(手册 §9.2.1:SET_MASK 先于 INIT_CH395) */ void ch395f_set_mask_addr(uint8_t *p_mask) { uint8_t i; /* 检查输入参数合法性 */ if (p_mask == NULL) { return; } /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置掩码命令 */ ch395f_write_cmd(CH395F_CMD_SET_MASK_ADDR); /* 逐字节发送子网掩码 */ for (i = 0; i < 4; i++) { ch395f_write_data(p_mask[i]); } /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:设置CH395的MAC地址(低字节在前,存入EEPROM) * 入口参数:p_mac - 6字节MAC地址指针 uint8_t* * 限定条件:1. p_mac不可为NULL * 2. 必须在 ch395f_init() 之前调用(手册 §9.2.1 步骤①:SET_MAC 写 EEPROM, * INIT_CH395 加载 MAC;顺序反了则本次上电不生效,回落芯片内置/上次值) * 函数说明:执行耗时约30us(TE5);写芯片 EEPROM,每上电重放同值幂等 */ void ch395f_set_mac_addr(uint8_t *p_mac) { uint8_t i; /* 检查输入参数合法性 */ if (p_mac == NULL) { return; } /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置MAC命令 */ ch395f_write_cmd(CH395F_CMD_SET_MAC_ADDR); /* 逐字节发送MAC地址 */ for (i = 0; i < 6; i++) { ch395f_write_data(p_mac[i]); } /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:读取CH395的MAC地址 * 出口参数:p_mac - 6字节MAC地址缓冲区指针 uint8_t* * 限定条件:p_mac不可为NULL */ void ch395f_get_mac_addr(uint8_t *p_mac) { uint8_t i; /* 检查输入参数合法性 */ if (p_mac == NULL) { return; } /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送读取MAC命令 */ ch395f_write_cmd(CH395F_CMD_GET_MAC_ADDR); /* 逐字节读取MAC地址 */ for (i = 0; i < 6; i++) { p_mac[i] = ch395f_read_data(); } /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:读取IP信息(IP地址 + 子网掩码 + DNS,共20字节) * 出口参数:p_buf - 20字节缓冲区指针 uint8_t* * 限定条件:p_buf不可为NULL */ void ch395f_get_ip_inf(uint8_t *p_buf) { uint8_t i; /* 检查输入参数合法性 */ if (p_buf == NULL) { return; } /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送读取IP信息命令 */ ch395f_write_cmd(CH395F_CMD_GET_IP_INF); /* 逐字节读取IP信息(共20字节) */ for (i = 0; i < 20; i++) { p_buf[i] = ch395f_read_data(); } /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:获取PHY连接状态 * 返回值:PHY状态码(CH395F_PHY_DISCONN / CH395F_PHY_10M_FULL / ...) uint8_t * 限定条件:芯片已初始化 */ uint8_t ch395f_get_phy_status(void) { uint8_t status; /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送读取PHY状态命令 */ ch395f_write_cmd(CH395F_CMD_GET_PHY_STATUS); /* 读取PHY状态 */ status = ch395f_read_data(); /* 结束SPI事务 */ ch395f_spi_end(); return status; } /* * 函数功能:设置PHY连接模式 * 入口参数:phystat - 连接模式 uint8_t 0x20 = 自动协商 * 限定条件:芯片已初始化 */ void ch395f_set_phy(uint8_t phystat) { /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置PHY命令 */ ch395f_write_cmd(CH395F_CMD_SET_PHY); /* 发送连接模式参数 */ ch395f_write_data(phystat); /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:使能/禁能CH395的PING响应 * 入口参数:enable - 使能标志 uint8_t 1 = 使能PING响应,0 = 禁能 * 限定条件:芯片已初始化 * 函数说明:参考手册5.37节CMD_PING_ENABLE,默认禁能 */ void ch395f_ping_enable(uint8_t enable) { /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送PING使能命令 */ ch395f_write_cmd(CH395F_CMD_PING_ENABLE); /* 发送使能标志 */ ch395f_write_data(enable); /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:使能/禁能DHCP * 入口参数:enable - 使能标志 uint8_t 1 = 使能,0 = 禁能 * 限定条件:芯片已初始化 */ void ch395f_set_dhcp(uint8_t enable) { /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送DHCP使能命令 */ ch395f_write_cmd(CH395F_CMD_DHCP_ENABLE); /* 发送使能/禁能参数 */ ch395f_write_data(enable); /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:获取DHCP状态 * 返回值:0 = 成功,非零 = 错误/超时 uint8_t * 限定条件:芯片已初始化且DHCP已使能 */ uint8_t ch395f_get_dhcp_status(void) { uint8_t status; /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送读取DHCP状态命令 */ ch395f_write_cmd(CH395F_CMD_GET_DHCP_STATUS); /* 读取DHCP状态 */ status = ch395f_read_data(); /* 结束SPI事务 */ ch395f_spi_end(); return status; } /* * 函数功能:设置Socket协议类型 * 入口参数:sock - Socket索引 uint8_t 0 - 7 * proto - 协议类型 uint8_t CH395F_PROTO_TYPE_xxx * 限定条件:Socket必须处于关闭状态 */ void ch395f_set_proto_type(uint8_t sock, uint8_t proto) { /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置协议类型命令 */ ch395f_write_cmd(CH395F_CMD_SET_PROTO_TYPE_SN); /* 发送Socket索引 */ ch395f_write_data(sock); /* 发送协议类型 */ ch395f_write_data(proto); /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:设置Socket目标IP地址 * 入口参数:sock - Socket索引 uint8_t 0 - 7 * p_ip - 4字节目标IP指针 uint8_t* * 限定条件:p_ip不可为NULL */ void ch395f_set_des_ip(uint8_t sock, uint8_t *p_ip) { uint8_t i; /* 检查输入参数合法性 */ if (p_ip == NULL) { return; } /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置目标IP命令 */ ch395f_write_cmd(CH395F_CMD_SET_IP_ADDR_SN); /* 发送Socket索引 */ ch395f_write_data(sock); /* 逐字节发送目标IP地址 */ for (i = 0; i < 4; i++) { ch395f_write_data(p_ip[i]); } /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:设置Socket目标端口(小端格式) * 入口参数:sock - Socket索引 uint8_t 0 - 7 * port - 目标端口号 uint16_t */ void ch395f_set_des_port(uint8_t sock, uint16_t port) { /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置目标端口命令 */ ch395f_write_cmd(CH395F_CMD_SET_DES_PORT_SN); /* 发送Socket索引 */ ch395f_write_data(sock); /* 发送端口低字节 */ ch395f_write_data(port & 0xFFU); /* 发送端口高字节 */ ch395f_write_data((port >> 8) & 0xFFU); /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:设置Socket源端口(小端格式) * 入口参数:sock - Socket索引 uint8_t 0 - 7 * port - 源端口号 uint16_t */ void ch395f_set_sour_port(uint8_t sock, uint16_t port) { /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送设置源端口命令 */ ch395f_write_cmd(CH395F_CMD_SET_SOUR_PORT_SN); /* 发送Socket索引 */ ch395f_write_data(sock); /* 发送端口低字节 */ ch395f_write_data(port & 0xFFU); /* 发送端口高字节 */ ch395f_write_data((port >> 8) & 0xFFU); /* 结束SPI事务 */ ch395f_spi_end(); } /* * 函数功能:打开Socket * 入口参数:sock - Socket索引 uint8_t 0 - 7 * 返回值:命令执行状态(CH395F_ERR_SUCCESS = 成功) uint8_t * 限定条件:协议类型、目标IP和端口必须已设置 */ uint8_t ch395f_open_socket(uint8_t sock) { ch395f_spi_begin(); ch395f_write_cmd(CH395F_CMD_OPEN_SOCKET_SN); ch395f_write_data(sock); ch395f_spi_end(); return ch395f_poll_cmd_status(4000U); } /* * 函数功能:关闭Socket * 入口参数:sock - Socket索引 uint8_t 0 - 7 * 返回值:命令执行状态(CH395F_ERR_SUCCESS = 成功) uint8_t * 限定条件:Socket必须已打开 */ uint8_t ch395f_close_socket(uint8_t sock) { ch395f_spi_begin(); ch395f_write_cmd(CH395F_CMD_CLOSE_SOCKET_SN); ch395f_write_data(sock); ch395f_spi_end(); return ch395f_poll_cmd_status(4000U); } /* * 函数功能:启动TCP监听模式 * 入口参数:sock - Socket索引 uint8_t 0 - 7 * 返回值:命令执行状态(CH395F_ERR_SUCCESS = 成功) uint8_t * 限定条件:Socket必须已打开且协议类型为TCP */ uint8_t ch395f_tcp_listen(uint8_t sock) { ch395f_spi_begin(); ch395f_write_cmd(CH395F_CMD_TCP_LISTEN_SN); ch395f_write_data(sock); ch395f_spi_end(); return ch395f_poll_cmd_status(4000U); } /* * 函数功能:发起TCP连接 * 入口参数:sock - Socket索引 uint8_t 0 - 7 * 返回值:命令执行状态(CH395F_ERR_SUCCESS = 成功) uint8_t * 限定条件:Socket必须已打开且协议类型为TCP */ uint8_t ch395f_tcp_connect(uint8_t sock) { ch395f_spi_begin(); ch395f_write_cmd(CH395F_CMD_TCP_CONNECT_SN); ch395f_write_data(sock); ch395f_spi_end(); return ch395f_poll_cmd_status(4000U); } /* * 函数功能:断开TCP连接 * 入口参数:sock - Socket索引 uint8_t 0 - 7 * 返回值:命令执行状态(CH395F_ERR_SUCCESS = 成功) uint8_t * 限定条件:TCP连接必须已建立 */ uint8_t ch395f_tcp_disconnect(uint8_t sock) { ch395f_spi_begin(); ch395f_write_cmd(CH395F_CMD_TCP_DISCONNECT_SN); ch395f_write_data(sock); ch395f_spi_end(); return ch395f_poll_cmd_status(4000U); } /* * 函数功能:向Socket发送缓冲区写入数据(DMA批量传输) * 入口参数:sock - Socket索引 uint8_t 0 - 7 * p_data - 数据指针 uint8_t* * len - 数据长度 uint16_t * 限定条件:1. p_data不可为NULL * 2. 数据长度必须大于0 * 3. TCP必须已连接或UDP已打开 * 函数说明:1. 将命令头和数据打包到DMA发送缓冲区 * 2. 一次性SPI DMA传输完成 * 3. 数据同步屏障确保memcpy写入对DMA可见 */ void ch395f_write_send_buf(uint8_t sock, uint8_t *p_data, uint16_t len) { uint16_t total; /* 检查输入参数合法性 */ if (p_data == NULL) { return; } /* 计算总长度:1字节命令 + 1字节Socket + 2字节长度 + 数据 */ total = 4U + len; if (total > CH395F_DMA_BUF_SIZE) { /* 异常金丝雀:与读路径对称——静默丢弃会让数据与错误一起消失(无从诊断), * 故必须留痕。正常路径不会到达此处(NET 层按 NET_SEND_CHUNK_MAX 分块)。 */ DBG_ERROR("write_send_buf OVERSIZE: sock %u req %u > max %u, write dropped", (unsigned)sock, (unsigned)len, (unsigned)(CH395F_DMA_BUF_SIZE - 4U)); return; } if (len > (uint16_t)CH395F_SEND_BUF_SIZE_BYTES) { /* §5.32:单次写入长度不得大于该 Socket 的发送缓冲区(本仓 = 2 块 = 1KB)。 * 超出不会被命令拒绝,而是写穿发送缓冲导致数据错乱(TRAP-NET-16)→ 只告警不拦截, * 分块责任在调用方(NET 层 NET_SEND_CHUNK_MAX)。 */ DBG_ERROR("write_send_buf OVERSIZE-SEND BUF: sock %u len %u > send buf %u (TRAP-NET-16)", (unsigned)sock, (unsigned)len, (unsigned)CH395F_SEND_BUF_SIZE_BYTES); } /* 打包命令头到DMA发送缓冲区 */ s_spi2_dma_tx_buf[0] = CH395F_CMD_WRITE_SEND_BUF_SN; s_spi2_dma_tx_buf[1] = sock; s_spi2_dma_tx_buf[2] = len & 0xFFU; s_spi2_dma_tx_buf[3] = (len >> 8) & 0xFFU; /* 拷贝用户数据到DMA发送缓冲区 */ memcpy(&s_spi2_dma_tx_buf[4], p_data, len); /* 数据同步屏障:确保memcpy写入对DMA可见 */ __DSB(); /* CS低 → DMA批量传输 → CS高(P2-2:失败已由 transfer 锁存通道故障并记日志, * 调用方经 ch395f_spi_byte_fault() 查询;此处无后续处理,失败即放弃本次发送) */ ch395f_spi_begin(); (void)ch395f_spi_dma_transfer(total); ch395f_spi_end(); } /* * 函数功能:从Socket接收缓冲区读取数据(DMA批量传输) * 入口参数:sock - Socket索引 uint8_t 0 - 7 * p_data - 输出缓冲区指针 uint8_t* * len - 待读取数据长度 uint16_t * 限定条件:1. p_data不可为NULL * 2. 数据长度必须大于0 * 3. 接收缓冲区中必须有数据 * 函数说明:1. 命令头打包到发送缓冲区(数据部分填充0xFF作为时钟源) * 2. 一次性SPI DMA全双工传输 * 3. 有效数据从接收缓冲区偏移4处拷贝到用户缓冲区 */ void ch395f_read_recv_buf(uint8_t sock, uint8_t *p_data, uint16_t len) { uint16_t total; int rc; /* 检查输入参数合法性 */ if (p_data == NULL) { return; } /* 计算总长度:1字节命令 + 1字节Socket + 2字节长度 + 数据 */ total = 4U + len; if (total > CH395F_DMA_BUF_SIZE) { /* 异常金丝雀:接收读不应超过整个接收缓冲(2KB,recv_len 不可能超过缓冲大小); * 触发即调用方参数异常。静默丢弃会让数据与错误一起消失(无从诊断), * 故必须留痕。正常路径不会到达此处。 */ DBG_ERROR("read_recv_buf OVERSIZE: sock %u req %u > max %u, read dropped", (unsigned)sock, (unsigned)len, (unsigned)(CH395F_DMA_BUF_SIZE - 4U)); return; } /* 打包命令头到DMA发送缓冲区 */ s_spi2_dma_tx_buf[0] = CH395F_CMD_READ_RECV_BUF_SN; s_spi2_dma_tx_buf[1] = sock; s_spi2_dma_tx_buf[2] = len & 0xFFU; s_spi2_dma_tx_buf[3] = (len >> 8) & 0xFFU; /* 数据部分填充0xFF(作为SPI时钟源) */ memset(&s_spi2_dma_tx_buf[4], 0xFFU, len); /* CS低 → DMA全双工传输(发送命令头 + 接收数据) → CS高 */ ch395f_spi_begin(); rc = ch395f_spi_dma_transfer(total); ch395f_spi_end(); /* P2-2(2026-10-04):DMA 失败时 rx_buf 内容不可信,禁止拷入调用方缓冲 * (否则把总线残留/半截数据当有效载荷 → 静默数据损坏);改零填充并返回, * 调用方据 ch395f_spi_byte_fault() 判定通道异常。 */ if (rc != 0) { memset(p_data, 0, len); return; } /* 从DMA接收缓冲区偏移4处拷贝有效数据到用户缓冲区 */ memcpy(p_data, &s_spi2_dma_rx_buf[4], len); } /* * 函数功能:获取Socket接收缓冲区中数据长度 * 入口参数:sock - Socket索引 uint8_t 0 - 7 * 返回值:接收数据长度 uint16_t,实际上 ch395F 接收缓冲区最大也就 8192 * 限定条件:Socket必须已打开 */ uint16_t ch395f_get_recv_len(uint8_t sock) { uint16_t recv_len; /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送获取接收长度命令 */ ch395f_write_cmd(CH395F_CMD_GET_RECV_LEN_SN); /* 发送Socket索引 */ ch395f_write_data(sock); /* 读取长度低字节 */ recv_len = ch395f_read_data(); /* 读取长度高字节并组合 */ recv_len |= (uint16_t)ch395f_read_data() << 8; /* 结束SPI事务 */ ch395f_spi_end(); return recv_len; } /* * 函数功能:获取并清除全局中断状态(2字节版,支持Socket 0~7) * 返回值:中断状态 uint16_t(低字节 = Socket 0~3 + PHY/DHCP, * 高字节 = Socket 4~7 + DHCPv6) * 限定条件:芯片已初始化 * 注意事项:读取后就自动清除全局中断状态了 */ uint16_t ch395f_get_glob_int_status_all(void) { uint16_t status; ch395f_spi_begin(); ch395f_write_cmd(CH395F_CMD_GET_GLOB_INT_STATUS_ALL); status = (uint16_t)ch395f_read_data(); status |= ((uint16_t)ch395f_read_data() << 8U); ch395f_spi_end(); return status; } /* * 函数功能:获取并清除 Socket 中断状态 * 入口参数:sock - Socket索引 uint8_t 0 - 7 * 返回值:Socket中断状态字节 uint8_t * 注意事项:读取后就自动清除 Socket 中断 * 限定条件:芯片已初始化 */ uint8_t ch395f_get_sock_int_status(uint8_t sock) { uint8_t status; /* 启动SPI事务 */ ch395f_spi_begin(); /* 发送读取Socket中断状态命令 */ ch395f_write_cmd(CH395F_CMD_GET_INT_STATUS_SN); /* 发送Socket索引 */ ch395f_write_data(sock); /* 读取中断状态 */ status = ch395f_read_data(); /* 结束SPI事务 */ ch395f_spi_end(); return status; } /* * 函数功能:获取Socket状态(2字节输出) * 入口参数:sock - Socket索引 uint8_t 0 - 7 * p_status - 2字节输出缓冲区指针 uint8_t* * [0] = Socket状态, [1] = TCP状态 * 限定条件:1. p_status 不可为 NULL * 2. 芯片已初始化 * 函数说明:Socket状态: 00H=CLOSED, 05H=OPEN * TCP状态: 00H=CLOSED, 01H=LISTEN, 02H=SYN_SENT, * 03H=SYN_RCVD, 04H=ESTABLISHED */ void ch395f_get_socket_status(uint8_t sock, uint8_t *p_status) { if (p_status == NULL) { return; } ch395f_spi_begin(); ch395f_write_cmd(CH395F_CMD_GET_SOCKET_STATUS_SN); ch395f_write_data(sock); p_status[0] = ch395f_read_data(); p_status[1] = ch395f_read_data(); ch395f_spi_end(); } /* * 函数功能:设置ARP重传参数 * 入口参数:period_100ms - ARP重传周期(单位:100ms,默认:10 = 1s) uint8_t 0 - 255 * cnt - ARP重传次数(默认:3) uint8_t 0 - 255 * 限定条件:应在ch395f_init()之后调用 * 函数说明:最大周期约25.5秒,255表示无限重传 */ void ch395f_set_arp(uint8_t period_100ms, uint8_t cnt) { ch395f_spi_begin(); ch395f_write_cmd(CH395F_CMD_SET_ARP); ch395f_write_data(period_100ms); ch395f_write_data(cnt); ch395f_spi_end(); } /* * 函数功能:设置Socket的TTL值 * 入口参数:sock - Socket索引 uint8_t 0 - 7 * ttl - TTL值 uint8_t 最大128 * 限定条件:Socket必须已打开,TCP Server模式下不生效 */ void ch395f_set_ttl(uint8_t sock, uint8_t ttl) { ch395f_spi_begin(); ch395f_write_cmd(CH395F_CMD_SET_TTL); ch395f_write_data(sock); ch395f_write_data(ttl); ch395f_spi_end(); } /* * 函数功能:获取不可达信息(用于调试连接失败原因) * 出口参数:p_buf - 8字节输出缓冲区指针(类型+协议+端口+IP) uint8_t* * 限定条件:收到GINT_STAT_UNREACH中断后调用 */ void ch395f_get_unreach_info(uint8_t *p_buf) { uint8_t i = 0; ch395f_spi_begin(); ch395f_write_cmd(CH395F_CMD_GET_UNREACH_IPPT); for (i = 0; i < 8; i++) { p_buf[i] = ch395f_read_data(); } ch395f_spi_end(); } /* ============================================================================ * 自愈收敛阶段 2 驱动原语(设计 docs/网络/网络自愈架构设计.md §4.4; * 形态定案锚点 同文档 §5.2 E2/E3/E4,2026-10-13 台架实证) * 只加不接线:net_poll 全量刷新 / 两级预清改调用 / 链路 feed 接线 = 阶段 3。 * ========================================================================== */ /* 每 Socket 状态缓存(刷新点:主动回读 ch395f_get_sock_state; * 阶段 3 起 net_poll 每轮对在用 Socket 各刷一次——E2 实测 18.1us/次, * 4 监听 ~72us 远小于 1ms 轮询周期) */ static ch395f_sock_state_t g_sock_state[8]; /* sock_reset 非阻塞状态机槽位(每 Socket 一个;t0 用 16 位 tick 差值, * 各阶段超时 <=2s,远小于 65535ms 回绕窗) */ #define CH395F_RST_ST_IDLE 0U #define CH395F_RST_ST_RX_DRAIN 1U #define CH395F_RST_ST_CLOSE 2U #define CH395F_RST_ST_MIN_WAIT 3U #define CH395F_RST_ST_EVT_DRAIN 4U #define CH395F_RST_ST_WAIT_CLOSE 5U #define CH395F_RST_RX_MAX_ROUNDS 4U /* RX 缓冲 2KB,单读即空;4 为安全上限 */ #define CH395F_RST_QUIESCENT 2U /* 排空事件判据:连续 2 轮无新位(E8 M5 口径) */ #define CH395F_RST_RX_TMO_MS 500U #define CH395F_RST_EVT_TMO_MS 500U #define CH395F_RST_CLS_TMO_MS 2000U typedef struct { uint16_t t0; /* 当前阶段起始 tick */ uint8_t state; /* CH395F_RST_ST_* */ uint8_t flags; /* bit7=mode(1=QUARANTINE) bit6..4=quiescent 轮数 bit3..0=drain 轮数(饱和) */ } ch395f_rst_slot_t; static ch395f_rst_slot_t g_rst[8]; /* 链路态缓存(F-DRV-13) */ static uint8_t g_link_up; static uint8_t g_link_valid; static uint16_t g_link_changes; /* 边界 fault 计数(sn 越界等;日志只留痕一次,其余只计数) */ static uint16_t g_p2_faults; static uint8_t g_p2_fault_logged; /* * 函数功能:阶段 2 原语边界异常留痕(首次 DBG_ERROR,之后只计数) * 入口参数:tag - 出错点短标签 const char* */ static void ch395f_p2_fault_note(const char *tag) { g_p2_faults++; if (g_p2_fault_logged == 0U) { g_p2_fault_logged = 1U; DBG_ERROR("ch395f P2 boundary fault @%s (further ones counted only)", tag); } } /* * 函数功能:读空并丢弃 RX(排空契约形态=整缓冲单读,与 ch395f_read_recv_buf * 同形但不拷出——复位残渣是垃圾,零新增 RAM) * 入口参数:sn - Socket 索引 uint8_t * len - 本次读出字节数(<=CH395F_SPI_DMA_MAX_PAYLOAD) uint16_t */ static void ch395f_rst_rx_discard(uint8_t sn, uint16_t len) { s_spi2_dma_tx_buf[0] = CH395F_CMD_READ_RECV_BUF_SN; s_spi2_dma_tx_buf[1] = sn; s_spi2_dma_tx_buf[2] = len & 0xFFU; s_spi2_dma_tx_buf[3] = (len >> 8) & 0xFFU; memset(&s_spi2_dma_tx_buf[4], 0xFFU, len); ch395f_spi_begin(); (void)ch395f_spi_dma_transfer(4U + len); ch395f_spi_end(); } /* * 函数功能:主动回读本 Socket 状态并刷新驱动缓存(缓存刷新点之一,另一个 * 是阶段 3 的 net_poll 每轮全量刷新) * 入口参数:sn - Socket 索引 uint8_t 0 - 7 * 返回值:类型化状态;sn>7 返回 {CLOSED, 哨兵} 并计一次边界 fault * 限定条件:芯片已初始化;netTask 上下文(与其它 CH395F 事务串行) */ ch395f_sock_state_t ch395f_get_sock_state(uint8_t sn) { ch395f_sock_state_t st; uint8_t raw[2]; if (sn >= 8U) { ch395f_p2_fault_note("get_state sn"); st.open = CH395F_SOCKET_CLOSED; st.tcp_state = CH395F_SOCK_STATE_NONE; return st; } ch395f_get_socket_status(sn, raw); st.open = raw[0]; st.tcp_state = (raw[0] == CH395F_SOCKET_OPEN) ? raw[1] : CH395F_SOCK_STATE_NONE; g_sock_state[sn] = st; return st; } /* * 函数功能:读驱动缓存(零 SPI 成本;新鲜度取决于刷新点,见上) * 入口参数:sn - Socket 索引 uint8_t 0 - 7 * 返回值:最近一次刷新的状态;sn>7 返回 {CLOSED, 哨兵} * 限定条件:芯片已初始化 */ ch395f_sock_state_t ch395f_sock_state_cached(uint8_t sn) { if (sn >= 8U) { ch395f_sock_state_t st; ch395f_p2_fault_note("cached sn"); st.open = CH395F_SOCKET_CLOSED; st.tcp_state = CH395F_SOCK_STATE_NONE; return st; } return g_sock_state[sn]; } /* * 函数功能:判"本 Socket 正在监听"(读缓存:open=OPEN 且 TCP=LISTEN) * 入口参数:sn - Socket 索引 uint8_t 0 - 7 * 返回值:1=在听 0=不在听 int * 限定条件:缓存新鲜度同 ch395f_sock_state_cached */ int ch395f_sock_is_listening(uint8_t sn) { ch395f_sock_state_t c = ch395f_sock_state_cached(sn); return (c.open == CH395F_SOCKET_OPEN && c.tcp_state == CH395F_TCP_LISTEN) ? 1 : 0; } /* * 函数功能:事件护栏——只读本 Socket SINT(读即清),永不触碰全局寄存器 * (F-NET-7 收窄:两级预清会清掉其它 Socket 的事件位) * 入口参数:sn - Socket 索引 uint8_t 0 - 7 * 返回值:SINT 位图;sn>7 返回 0 uint8_t * 限定条件:芯片已初始化;全局寄存器唯一读者=net_poll(驱动侧契约) */ uint8_t ch395f_sock_events(uint8_t sn) { if (sn >= 8U) { ch395f_p2_fault_note("events sn"); return 0U; } /* 契约:只读本 Socket SINT(0x30 读即清),永不触碰全局寄存器 0x19—— * 全局寄存器读即清且含全部 Socket 事件位,唯一读者=net_poll(F-NET-7) */ return ch395f_get_sock_int_status(sn); } /* * 函数功能:启动 Socket 复合复位(非阻塞状态机,每 net_poll 步进;E4 定案序列 * 读空 RX -> CLOSE -> 排空事件(quiescent) -> 轮询 byte0=00H) * 入口参数:sn - Socket 索引 uint8_t 0 - 7 * mode - 复位模式(GRACEFUL 100ms / QUARANTINE 400ms 最短等待) * 返回值:0=已启动 -1=参数错或已在运行(调用方逻辑错误) int * 限定条件:netTask 上下文;调用前本层已放弃该会话(复位丢弃 RX 残渣) */ int ch395f_sock_reset_begin(uint8_t sn, ch395f_reset_mode_t mode) { if (sn >= 8U) { ch395f_p2_fault_note("reset_begin sn"); return -1; } if (g_rst[sn].state != CH395F_RST_ST_IDLE) { /* 已在运行:调用方逻辑错误(同一 Socket 不允许并发复位) */ return -1; } g_rst[sn].state = CH395F_RST_ST_RX_DRAIN; g_rst[sn].flags = (mode == CH395F_RESET_QUARANTINE) ? 0x80U : 0x00U; g_rst[sn].t0 = (uint16_t)HAL_GetTick(); return 0; } /* * 函数功能:复位状态机步进(net_poll 或测试循环周期调用,禁复制忙等) * 入口参数:sn - Socket 索引 uint8_t 0 - 7 * 返回值:0=完成(含未启动,幂等) 1=进行中 -1=失败(超时/参数,槽位已复位可重开) int * 限定条件:netTask 上下文 */ int ch395f_sock_reset_poll(uint8_t sn) { ch395f_rst_slot_t *p; uint16_t now; uint16_t el; if (sn >= 8U) { ch395f_p2_fault_note("reset_poll sn"); return -1; } p = &g_rst[sn]; if (p->state == CH395F_RST_ST_IDLE) { return 0; /* 未启动/已完成:幂等 */ } now = (uint16_t)HAL_GetTick(); el = (uint16_t)(now - p->t0); switch (p->state) { case CH395F_RST_ST_RX_DRAIN: { /* E4 定案第 1 步:close 前 RX 必须为空(带脏缓冲关闭 -> 拆链停滞 -> * 毒化下一会话且级联自持,C 变体 0/20 实证) */ uint16_t len = ch395f_get_recv_len(sn); uint8_t rounds = (uint8_t)(p->flags & 0x0FU); if (len == 0U) { p->state = CH395F_RST_ST_CLOSE; p->t0 = now; break; } if (rounds < CH395F_RST_RX_MAX_ROUNDS) { ch395f_rst_rx_discard(sn, (len > (uint16_t)CH395F_SPI_DMA_MAX_PAYLOAD) ? (uint16_t)CH395F_SPI_DMA_MAX_PAYLOAD : len); p->flags = (uint8_t)((p->flags & 0xF0U) | (rounds + 1U)); } else { /* 排不空(异常形态):不再恋战,继续 CLOSE,失败由 WAIT_CLOSE 超时兜底 */ ch395f_p2_fault_note("rx drain stall"); p->state = CH395F_RST_ST_CLOSE; p->t0 = now; } if ((uint16_t)(now - p->t0) > CH395F_RST_RX_TMO_MS) { p->state = CH395F_RST_ST_IDLE; ch395f_p2_fault_note("rx drain tmo"); return -1; } break; } case CH395F_RST_ST_CLOSE: /* E4 定案第 2 步:CLOSE(裸命令,不复制 poll_cmd_status 忙等—— * E3 实证其对 CLOSE 无判别力;完成判据=状态回读,E3 定案) */ ch395f_spi_begin(); ch395f_write_cmd(CH395F_CMD_CLOSE_SOCKET_SN); ch395f_write_data(sn); ch395f_spi_end(); p->state = CH395F_RST_ST_MIN_WAIT; p->t0 = now; break; case CH395F_RST_ST_MIN_WAIT: { uint16_t need = ((p->flags & 0x80U) != 0U) ? 400U : 100U; if (el >= need) { p->state = CH395F_RST_ST_EVT_DRAIN; p->t0 = now; p->flags = (uint8_t)(p->flags & 0x8FU); /* quiescent 计数清零 */ } break; } case CH395F_RST_ST_EVT_DRAIN: { /* E4 定案第 3 步:排空事件至 quiescent(陈旧粘滞位会误导事件驱动 * accept——E4 C 变体签名 0x04 陈旧 RECV;读即清,连续 2 轮无新位即静) */ uint8_t bits = ch395f_sock_events(sn); if (bits == 0U) { uint8_t q = (uint8_t)((p->flags >> 4) & 0x07U); if ((q + 1U) >= CH395F_RST_QUIESCENT) { p->state = CH395F_RST_ST_WAIT_CLOSE; p->t0 = now; } else { p->flags = (uint8_t)((p->flags & 0x8FU) | ((q + 1U) << 4)); } } else { p->flags = (uint8_t)(p->flags & 0x8FU); /* 有新位:quiescent 计数清零 */ } if (el > CH395F_RST_EVT_TMO_MS) { p->state = CH395F_RST_ST_IDLE; ch395f_p2_fault_note("evt drain tmo"); return -1; } break; } case CH395F_RST_ST_WAIT_CLOSE: /* E4 定案第 4 步:轮询 byte0=00H(E4 实测 closed_ms=0——命令落定即空, * 但为契约必需步) */ if (ch395f_cmd_done(sn, CH395F_DONE_CLOSE) == 1) { p->state = CH395F_RST_ST_IDLE; return 0; } /* 二段关闭实证(TC1501 台架 2026-10-13):LISTEN 态 Socket 的 CLOSE 返 * SUCCESS 但只拆监听(TCP 01->00),byte0 恒 05;需再发一次 CLOSE 才落 * 00(生产 net 层靠 TRAP-NET-09 open 重试吸收此行为)。复位状态机自行补发, * 补发标记=drain 字段置 0xF(本阶段已用尽,复用安全)。 */ { ch395f_sock_state_t c = ch395f_sock_state_cached(sn); if (c.open == CH395F_SOCKET_OPEN && c.tcp_state == CH395F_TCP_CLOSED && (p->flags & 0x0FU) != 0x0FU) { p->flags = (uint8_t)((p->flags & 0xF0U) | 0x0FU); ch395f_spi_begin(); ch395f_write_cmd(CH395F_CMD_CLOSE_SOCKET_SN); ch395f_write_data(sn); ch395f_spi_end(); } } if (el > CH395F_RST_CLS_TMO_MS) { p->state = CH395F_RST_ST_IDLE; ch395f_p2_fault_note("wait closed tmo"); return -1; } break; default: p->state = CH395F_RST_ST_IDLE; ch395f_p2_fault_note("bad rst state"); return -1; } return 1; } /* * 函数功能:非阻塞命令完成查询(E3 定案=状态回读判据;单次回读 18.1us,E2 背书) * 入口参数:sn - Socket 索引 uint8_t 0 - 7 * what - 完成判据(OPEN=byte0 05H / LISTEN=+TCP 01H / CLOSE=byte0 00H) * 返回值:1=已完成 0=未完成 -1=参数错 int * 限定条件:netTask 上下文;勿复制 poll_cmd_status 忙等(E3 实证无判别力) */ int ch395f_cmd_done(uint8_t sn, ch395f_done_what_t what) { ch395f_sock_state_t st; if (sn >= 8U) { return -1; } st = ch395f_get_sock_state(sn); /* 主动回读,单次 18.1us(E2) */ switch (what) { case CH395F_DONE_OPEN: return (st.open == CH395F_SOCKET_OPEN) ? 1 : 0; case CH395F_DONE_LISTEN: return (st.open == CH395F_SOCKET_OPEN && st.tcp_state == CH395F_TCP_LISTEN) ? 1 : 0; case CH395F_DONE_CLOSE: return (st.open == CH395F_SOCKET_CLOSED) ? 1 : 0; default: return -1; } } /* * 函数功能:喂全局中断位(阶段 3 接线:net_poll 读全局后喂 bit0~3; * bit2 PHY_CHANGE 置位时重读 PHY 更新 link_up 缓存 + 变化计数) * 入口参数:glob_bits - 全局中断状态低字节语义位图 uint16_t * 返回值:无 * 限定条件:netTask 上下文(会发 SPI 读 PHY) */ void ch395f_glob_event_feed(uint16_t glob_bits) { uint8_t up; if ((glob_bits & CH395F_GINT_STAT_PHY_CHANGE) == 0U) { return; } /* B9 判据(TRAP-NET-09 修正):PHY != DISCONN 即 link up */ up = (ch395f_get_phy_status() != CH395F_PHY_DISCONN) ? 1U : 0U; if (g_link_valid == 0U) { g_link_valid = 1U; g_link_up = up; } else if (up != g_link_up) { g_link_up = up; g_link_changes++; } } /* * 函数功能:读链路态缓存(首次查询惰性读 PHY 建档,不依赖 feed 已发生) * 入口参数:无 * 返回值:1=link up 0=down uint8_t(B9 判据:PHY != DISCONN 即 up) * 限定条件:芯片已初始化 */ uint8_t ch395f_link_up_cached(void) { if (g_link_valid == 0U) { /* 惰性建档(首次查询):不依赖 feed 已发生过 */ g_link_up = (ch395f_get_phy_status() != CH395F_PHY_DISCONN) ? 1U : 0U; g_link_valid = 1U; } return g_link_up; } /* * 函数功能:读链路变化计数(net 层订阅:计数变化 -> 日志/可选重配) * 入口参数:无 * 返回值:自建档以来的 PHY 连接状态翻转次数 uint16_t * 限定条件:无 */ uint16_t ch395f_link_change_count(void) { return g_link_changes; }