Files
STM32F4-Base/Drivers/BSP/CH395F/ch395f.c
2026-07-19 13:09:06 +08:00

1406 lines
35 KiB
C
Raw Permalink 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 Ethernet Protocol Stack Driver
* 模块功能CH395F 以太网协议栈芯片 SPI 驱动实现,提供芯片检测、初始化、
* 网络参数配置、PHY 管理、DHCP、Socket 管理及数据收发功能
* 适用平台STM32F4 系列SPI2 接口)
* 作者:王建锋
* 创建日期2026-03-14
* 修改记录:
* 2026-03-14 王建锋 创建初始版本,按代码规范重构
* 2026-07-18 王建锋 改造 write_send_buf / read_recv_buf 使用 SPI2 DMA 批量传输
*/
/* 头文件包含区 - 先包含本模块头文件,再包含依赖头文件 */
#include "ch395f.h"
#include "spi.h"
#include "usart.h"
#include <string.h>
#include <stdio.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 */
#define CH395F_DMA_BUF_SIZE 1500U /* DMA 缓冲区最大字节数 */
/*
* 私有变量区 - 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 触发此回调)
*/
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI2)
{
s_spi2_dma_done = 1U;
}
}
/*
* 函数功能SPI 错误回调
* 函数说明:由 HAL SPI 驱动在 DMA 传输出错时调用,释放等待
*/
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI2)
{
s_spi2_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 启用0 禁用 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_t0=使用默认值5
* 限定条件:必须在 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 已正确初始化
* 函数说明:复位耗时约 15msTE0函数内部已包含延时
*/
ch395f_status_t ch395f_reset(void)
{
/* 开始 SPI 事务 */
ch395f_spi_begin();
/* 发送复位命令 */
ch395f_write_cmd(CH395F_CMD_RESET_ALL);
/* 结束 SPI 事务 */
ch395f_spi_end();
/* 等待复位完成TE0 = 15ms 典型值,等待 20ms */
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_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*
* 限定条件:指针非空,芯片已初始化
* 函数说明:执行耗时约 30usTE5
*/
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 响应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 - 1 启用0 禁用 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
* 限定条件:指针非空,长度大于 0TCP 已连接或 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;
/* 数据部分填充 0xFFSPI 时钟驱动用) */
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 *status)
{
ch395f_spi_begin();
ch395f_write_cmd(CH395F_CMD_GET_SOCKET_STATUS_SN);
ch395f_write_data(sock);
status[0] = ch395f_read_data();
status[1] = ch395f_read_data();
ch395f_spi_end();
}
/*
* 函数功能:设置 ARP 重传参数
* 入口参数period_100ms - ARP 重传周期(单位 100ms0=默认值=10即1秒 uint8_t
* cnt - ARP 重传次数0=默认值=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;
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();
}