Files
STM32F4-Base/Drivers/BSP/CH395F/ch395f.c
T
wandering1 925058a5ae fix(NET)+docs: lftpd 对端地址修复、E6 裁决收口、陷阱编号迁移重排 TRAP-NET-01~21
- lftpd:net_inet_ntoa 段序改 MSB 首 + 新增 net_getpeername,FTP 日志打印
  真实对端地址(真机实证 192.168.1.2:55524,登记 TRAP-NET-21)
- E6:NET_U15_PROBE 命中=0(含压测)→ 阶段 3 项 4 不立项,U15 登记观察项;
  插桩留仓(宏=0 零成本),soak 驱动 test/host/e6_soak.py 入库
- 编号:全库 Trap NN → TRAP-NET-<nn> 迁移并重排连续 01~21(单调映射,
  沿革见 CH395F_Trap_Records.md 头注);死号 12/13/19/21 引用全部清除;
  GD5F 域引用补正为 TRAP-GD5F-NN
- 陷阱文档全新化:索引 21 行行行真陷阱;删误判存档/发现时间/git 考古/
  FTP 附录;台架纪律教训沉淀第六章方法论
- RAM:Code 211348→211456(+108 lftpd 修复),RW+ZI 净增 +4B;
  优化方案/台账受管区/tsv 同笔同步
- ftp_abort_probe.py 挪 test/host/;引用关系图重生成
- 回归:@build 0/0;@test PASS;ftp_abort 10/10;relisten :7003 20/20;
  ci_local L0+L0.5 全绿;E6 窗口零复位,7 条 WD stall 登记压测假象备查
2026-10-13 20:54:31 +08:00

1778 lines
56 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称: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 <string.h>
#include <stdio.h>
#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();
}
/* ============================================================================
* N34 阶段 2 驱动原语(实施方案 docs/网络/N34-自愈审计/实施方案.md §4;
* 形态定案 docs/网络/N34-自愈审计/实验方案.md 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;
}