/* * 模块名称:CH395F SPI Ethernet Protocol Stack Driver * 模块功能:CH395F 以太网协议栈芯片 SPI 驱动实现,提供芯片检测、初始化�? * 网络参数配置、PHY 管理、DHCP、Socket 管理及数据收发功�? * 适用平台:STM32F4 系列(SPI2 接口�? * 作者:王建�? * 创建日期�?026-03-14 * 修改记录�? * 2026-03-14 王建�? 创建初始版本,按代码规范重构 * 2026-07-18 王建�? 改�?write_send_buf / read_recv_buf 使用 SPI2 DMA 批量传输 */ /* 头文件包含区 - 先包含系统头文件,再包含本模块头文件 */ #include #include #include "ch395f.h" #include "spi.h" #include "usart.h" /* * 调试输出配置 */ #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_BEGIN_DELAY_MS 1U /* SPI 事务开始延时(ms�?*/ /* 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 传输完成标志 */ __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 void ch395f_spi_dma_transfer(uint16_t len); /* * 私有函数实现�? */ /* * 函数功能:开�?SPI 事务(CS 拉高 -> 拉低�? * 函数说明:手册要�?SPI 模式下字节间无需间隔�? * CS 拉高再拉低满足最小片选间隔要�? */ 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 拉高�? */ static void ch395f_spi_end(void) { /* CS 拉高,结束事�?*/ HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_SET); } /* * 函数功能:DMA 全双工传输并等待完成 * 入口参数:len - 传输字节�? uint16_t * 函数说明:发�?s_spi2_dma_tx_buf,接收到 s_spi2_dma_rx_buf�? * 阻塞等待 DMA 完成回调置位标志 */ static void 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); return; } 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); return; } } /* 检查传输是否成功: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); } } /* * 函数功能:SPI 收发完成回调 * 函数说明:由 HAL SPI 驱动�?DMA TX+RX 完成后调用(HAL_SPI_TransmitReceive_DMA 触发此回调) */ /* 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 错误回调 * 函数说明:由 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; /* 通过 SPI 发送命令并接收回显 */ HAL_SPI_TransmitReceive(&hspi2, &cmd, &rx_data, 1, HAL_MAX_DELAY); 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; /* 通过 SPI 发送数据并接收回显 */ HAL_SPI_TransmitReceive(&hspi2, &data, &rx_data, 1, HAL_MAX_DELAY); return rx_data; } /* * 函数功能:从 CH395 读取数据字节 * 返回值:接收到的数据字节 uint8_t * 限定条件:需�?ch395f_spi_begin() �?ch395f_spi_end() 之间调用 */ uint8_t ch395f_read_data(void) { uint8_t tx_dummy = 0xFFU; uint8_t rx_data; /* 发�?0xFF 作为时钟源,读取芯片返回的数�?*/ HAL_SPI_TransmitReceive(&hspi2, &tx_dummy, &rx_data, 1, HAL_MAX_DELAY); return rx_data; } /* * 函数功能:检�?CH395 芯片是否存在且通信正常 * 返回值:CH395F_STATUS_OK - 检测到芯片,CH395F_STATUS_NOT_DETECTED - 未检测到 * 限定条件:SPI2 已正确初始化 * 函数说明:发�?CMD_CHECK_EXIST (0x06) 及测试字�?0x57�? * 芯片应返回按位取反�?0xA8 */ 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 芯片版本�? * 返回值:版本字节(bit5: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字节端口�? * 限定条件: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 - 空闲时间(秒�? 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 - 间隔时间(秒�? 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 - 1 启用�? 禁用 uint8_t * 限定条件: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�?=使用默认�?�? * 限定条件:必须在 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 * 限定条件:SPI2 已正确初始化 * 函数说明:复位耗时�?15ms(TE0),函数内部已包含延�? */ ch395f_status_t ch395f_reset(void) { DBG_INFO("ch395f_reset: spi_begin"); ch395f_spi_begin(); DBG_INFO("ch395f_reset: write_cmd RESET_ALL"); ch395f_write_cmd(CH395F_CMD_RESET_ALL); DBG_INFO("ch395f_reset: spi_end"); ch395f_spi_end(); DBG_INFO("ch395f_reset: HAL_Delay(%d)", CH395F_RESET_DELAY_MS); DBG_INFO("ch395f_reset: tick=%lu basepri=0x%02X", HAL_GetTick(), __get_BASEPRI()); HAL_Delay(CH395F_RESET_DELAY_MS); DBG_INFO("ch395f_reset: done"); 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_CMD_RET_ABORT�? * 限定条件:在发送长执行命令后调�? * 函数说明:每 20ms 查询一次,直到命令完成或超�? */ uint8_t ch395f_poll_cmd_status(uint32_t timeout_ms) { uint8_t status; uint32_t retry_count = 0U; uint32_t max_retries = (timeout_ms + 20U - 1U) / 20U; while (1) { HAL_Delay(20U); 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_TIMEOUT - 超时失败 * 限定条件:芯片已通过 ch395f_reset() 复位 * 函数说明:阻塞等待初始化完成或超时(芯片需要约 200ms,这里等最�?4s�? */ 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 - 4 字节 IP 地址指针 uint8_t* * 限定条件:指针非空,芯片已初始化 */ 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(); } /* * 函数功能:设置网�?IP 地址(低字节在前�? * 入口参数:p_gwip - 4 字节网关 IP 指针 uint8_t* * 限定条件:指针非空,芯片已初始化 */ 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(); } /* * 函数功能:设置子网掩码(低字节在前) * 入口参数:p_mask - 4 字节子网掩码指针 uint8_t* * 限定条件:指针非空,芯片已初始化 */ 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(); } /* * 函数功能:设�?MAC 地址(低字节在前,存储于 EEPROM�? * 入口参数:p_mac - 6 字节 MAC 地址指针 uint8_t* * 限定条件:指针非空,芯片已初始化 * 函数说明:执行耗时�?30us(TE5�? */ 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* * 限定条件:指针非�? */ 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* * 限定条件:指针非�? */ 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 - 1 使能 PING 响应�? 关闭 * 限定条件:芯片已初始�? * 函数说明:手�?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 - 1 启用�? 禁用 uint8_t * 限定条件:芯片已初始�? */ 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 - 3 * 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 - 3 * p_ip - 4 字节目标 IP 指针 uint8_t* * 限定条件:指针非�? */ 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 - 3 * 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 - 3 * 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 - 3 * p_data - 数据指针 uint8_t* * len - 数据长度 uint16_t * 限定条件:指针非空,长度大于 0,TCP 已连接或 UDP 已打开 * 函数说明:将命令头和数据打包�?DMA 缓冲区,一�?SPI 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(cmd) + 1(sock) + 2(len) + data */ total = 4U + len; if (total > CH395F_DMA_BUF_SIZE) { return; } /* 打包命令头到 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 拉高 */ ch395f_spi_begin(); ch395f_spi_dma_transfer(total); ch395f_spi_end(); } /* * 函数功能:从 Socket 接收缓冲区读取数据(DMA 批量传输�? * 入口参数:sock - Socket 索引 uint8_t 0 - 3 * p_data - 输出缓冲区指�? uint8_t* * len - 待读取数据长�? uint16_t * 限定条件:指针非空,长度大于 0,接收缓冲区有数�? * 函数说明:命令头打包到发送缓冲区(数据部分填 0xFF 作时钟)�? * 一�?SPI DMA 全双工传输,有效数据从接收缓冲区偏移 4 字节处拷�? */ void ch395f_read_recv_buf(uint8_t sock, uint8_t *p_data, uint16_t len) { uint16_t total; /* 检查输入参数合法�?*/ if (p_data == NULL) { return; } /* 计算总长度:1(cmd) + 1(sock) + 2(len) + data */ total = 4U + len; if (total > CH395F_DMA_BUF_SIZE) { 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(); ch395f_spi_dma_transfer(total); ch395f_spi_end(); /* �?DMA 接收缓冲区偏�?4 字节处拷贝有效数据到用户缓冲�?*/ memcpy(p_data, &s_spi2_dma_rx_buf[4], len); } /* * 函数功能:获�?Socket 接收缓冲区数据长�? * 入口参数:sock - Socket 索引 uint8_t 0 - 3 * 返回值:接收数据长度 uint16_t * 限定条件: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; } /* * 函数功能:获取全局中断状�? * 返回值:中断状态字�? uint8_t * 限定条件:芯片已初始�? */ uint8_t ch395f_get_glob_int_status(void) { uint8_t status; /* 开�?SPI 事务 */ ch395f_spi_begin(); /* 发送读取全局中断状态命�?*/ ch395f_write_cmd(CH395F_CMD_GET_GLOB_INT_STATUS); /* 读取中断状�?*/ status = ch395f_read_data(); /* 结束 SPI 事务 */ ch395f_spi_end(); return status; } /* * 函数功能:获取全局中断状态(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 * 限定条件:芯片已初始�? */ 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 * status - 2 字节输出缓冲�?[0]=Socket状�? [1]=TCP状�? * 限定条件:芯片已初始�? */ void ch395f_get_socket_status(uint8_t sock, uint8_t *p_status) { 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�?秒) uint8_t * cnt - ARP 重传次数�?=默认�?3�? uint8_t * 限定条件:应�?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�? * 限定条件:收�?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(); }