tcp_client配置
第一步:资料下载
以太网协议栈芯片 CH395 - 南京沁恒微电子股份有限公司
第二步:准备工程
(1) 首先准备一个编译无报错、可以正常打印和延时的工程文件,官方例程采用STM32F1芯片,但本文采用GD32F470芯片
(2)将例程代码中的PUB文件夹加入,keil工程添加CH395CMD.c文件和CH395SPI_HW.c文件
(3)将例程代码中的main.c内容全部加入到自己的main.c文件中,如果觉得函数较多,可以自己放置到CH398CMD.c函数中
(4)例程代码使用SPI_DMA,看个人需要,使用则配置,不使用则屏蔽
(5)此时编译有很多错误,继续下一步
第三步:引脚配置
ch395Q支持SPI和串口通信,本文采用SPI通信,我们需要配置6个GPIO口,分别如下
一、SPI通信基础引脚
- CS(片选引脚,配置为输出模式,软件片选,不用复用)
- SCK(时钟引脚)
- MOSI(主出从入引脚)
- 功能:主设备向CH395发送数据。
- 配置:需连接到STM32的SPI外设MOSI线(如SPI1_MOSI)
- MISO(主入从出引脚)
- 功能:CH395向主设备返回数据。
- 配置:需连接到STM32的SPI外设MISO线(如SPI1_MISO)
二、辅助控制引脚
CH395_PORT_INIT();//SPI初始化
CH395_GPIO_INIT();//RST和INT引脚初始化
CH395Reset();//修改RST对应引脚
xCH395CmdStart();//修改CS对应引脚,CMD_START_HANDEL();可以删除,无作用
xEndCH395Cmd();//修改CS对应引脚,CMD_END_HANDEL();可以删除,无作用
#define Query395Interrupt() gpio_input_bit_get(GPIOC, GPIO_PIN_13)//修改为INT引脚
第四步:延时函数配置
例程代码使用debug.c文件中配置的延时函数,本文采用自定义函数,或者直接全文替换
void Delay_Us(uint32_t us)
{
us *= 168; // 72MHz下1us≈72个周期(每条循环指令约3周期)
while (us--) {
__NOP(); // 内联汇编NOP指令
}
}
void Delay_Ms(uint32_t ms)
{
delay_1ms(ms);
}
第五步:编译
此处编译后肯定会多处报错,比如UINT8没有定义,stm32f10x_dma.h文件找不到等等,这些都是正常的,按照自己的经验进行修改即可,直到编译无警告,无报错
第六步:ip修改
InitCH395InfParam()
/* CH395 Related definition */
const uint8_t CH395IPAddr[4] = {192, 168, 1, 101}; /* CH395 IP */
const uint8_t CH395GWIPAddr[4] = {192, 168, 1, 1}; /* CH395 gateway */
const uint8_t CH395IPMask[4] = {255, 255, 255, 0}; /* CH395 mask */
/* Socket definitions */
const uint8_t Socket0DesIP[4] = {192, 168, 1, 123}; /* Destination IP address for Socket 0 */
const uint16_t Socket0DesPort = 1000; /* Destination port for Socket 0 */
const uint16_t Socket0SourPort = 5000; /* Source port for Socket 0 */
第七步:查看保活机制
keeplive_set()
#define DEF_KEEP_LIVE_IDLE (15 * 1000) /* Idle time */
#define DEF_KEEP_LIVE_PERIOD (20 * 1000) /* Send a KEEPALIVE packet every 20 seconds */
#define DEF_KEEP_LIVE_CNT 200 /* Number of retry attempts */
DEF_KEEP_LIVE_IDLE:空闲时间,可能指的是在TCP连接建立后,如果在15秒内没有数据传输,则开始发送保活包。这与TCP的保活机制中的空闲时间类似,用于确定何时开始检测连接是否有效。
DEF_KEEP_LIVE_PERIOD:每隔20秒发送一次保活包。这个参数可能控制保活包的发送频率,确保在空闲期间定期检测连接状态。
DEF_KEEP_LIVE_CNT:重试次数200次。当保活包发送后未收到响应时,会进行重试,这个参数指定最大重试次数,超过后认为连接已断开。
- 保活机制主要用于维持TCP连接的活跃状态,防止因网络中断或空闲导致连接被中间设备(如路由器、防火墙)主动断开
- 应对网络环境不稳定:在工业控制、远程监控等场景中,网络可能因电磁干扰、信号衰减等问题出现瞬时中断。保活机制通过周期性发送心跳包(空数据包),可快速检测链路异常并触发重连,避免数据丢失。
- 支持物联网长连接需求:CH395常用于MQTT、HTTP长连接等物联网协议,需保持设备与服务器持续通信。若长时间无数据传输,服务器或网关可能关闭连接,保活包能维持连接有效性
第八步:全局中断
CH395GlobalInterrupt --》 GINT_STAT_SOCK0
针对一个TCP连接,主要有以下几个中断
void CH395SocketInterrupt(uint8_t sockindex)
{
sock_int_status[sockindex] |= CH395CMDGetSocketInt(sockindex); /* Gets the socket interrupt status */
if (sock_int_status[sockindex] & SINT_STAT_RECV) /* Receive interruption */
{
//接收中断,接受到数据后会进来这里,但例程代码在Data_Loop函数中处理
// Handle it in the main program
}
if (sock_int_status[sockindex] & SINT_STAT_SENDBUF_FREE) /* The send buffer is free and can continue writing data to be sent */
{
//发送缓冲区为空,可以发送数据,但例程代码在Data_Loop函数中处理
// Handle it in the main program
}
if (sock_int_status[sockindex] & SINT_STAT_SEND_OK) /* Send completion interrupt */
{
//发送完成中断
sock_int_status[sockindex] &= ~SINT_STAT_SEND_OK;
}
if (sock_int_status[sockindex] & SINT_STAT_CONNECT) /* The connection is interrupted, valid only in TCP mode */
{
//连接成功中断
sock_int_status[sockindex] &= ~SINT_STAT_CONNECT;
CH395CMDSetKeepLive(sockindex, 1); /* Enable the KEEPALIVE timer */
LOG("SINT_STAT_CONNECT\r\n");
}
if (sock_int_status[sockindex] & SINT_STAT_DISCONNECT) /* Disconnect interrupt, valid only in TCP mode */
{
//断开连接中断
sock_int_status[sockindex] &= ~SINT_STAT_DISCONNECT;
LOG("SINT_STAT_DISCONNECT \r\n");
}
if (sock_int_status[sockindex] & SINT_STAT_TIM_OUT) /* Timeout interrupt, valid only in TCP mode */
{
//连接超时中断,比如服务器未打开
sock_int_status[sockindex] &= ~SINT_STAT_TIM_OUT;
LOG("SINT_STAT_TIM_OUT\r\n");
}
}
Data_Loop函数,主要就是将接收到的数据发出去
第九步:测试
DHCP配置
第一步:加入DHCP相关变量
#define CH395_DHCP 1
UINT8 flag = 0; /* DHCP success flag */
第二步:增加DHCP中断处理
CH395GlobalInterrupt函数
UINT16 i;
UINT8 buf[20];
if (init_status & GINT_STAT_DHCP)
{
#if CH395_DHCP
i = CH395CMDGetDHCPStatus();
if (i == 0)
{
flag = 1;
CH395CMDGetIPInf(buf);
printf("IP:%02d.%02d.%02d.%02d\n", (UINT16)buf[0], (UINT16)buf[1], (UINT16)buf[2], (UINT16)buf[3]);
printf("GWIP:%02d.%02d.%02d.%02d\n", (UINT16)buf[4], (UINT16)buf[5], (UINT16)buf[6], (UINT16)buf[7]);
printf("Mask:%02d.%02d.%02d.%02d\n", (UINT16)buf[8], (UINT16)buf[9], (UINT16)buf[10], (UINT16)buf[11]);
printf("DNS1:%02d.%02d.%02d.%02d\n", (UINT16)buf[12], (UINT16)buf[13], (UINT16)buf[14], (UINT16)buf[15]);
printf("DNS2:%02d.%02d.%02d.%02d\n", (UINT16)buf[16], (UINT16)buf[17], (UINT16)buf[18], (UINT16)buf[19]);
}
#endif
}
第三步:取消ip,gw,mask初始化
CH395Init函数
#if !CH395_DHCP /* If DHCP is enabled, the IP, gateway, and subnet mask do not need to be set */
CH395CMDSetIPAddr(CH395Inf.IPAddr); /* Set the CH395's IP address */
CH395CMDSetGWIPAddr(CH395Inf.GWIPAddr); /* Set the gateway address */
CH395CMDSetMASKAddr(CH395Inf.MASKAddr); /* Set the subnet mask, default is 255.255.255.0 */
#endif
第四步:保活机制新增宏定义
#define KEEP_LIVE 1
main()函数
#if KEEP_LIVE
keeplive_set();
#endif
CH395SocketInterrupt()函数
if (sock_int_status[sockindex] & SINT_STAT_CONNECT) /* The connection is interrupted, valid only in TCP mode */
{
sock_int_status[sockindex] &= ~SINT_STAT_CONNECT;
#if KEEP_LIVE
CH395CMDSetKeepLive(sockindex, 1); /* Enable the KEEPALIVE timer */
#endif
LOG("SINT_STAT_CONNECT\r\n");
}
第五步:使能DHCP功能
#if CH395_DHCP
printf("Start DHCP\n");
i = CH395CMDDHCPEnable(1);
mStopIfError(i);
#endif
CH395CMDDHCPEnable工作流程
1、调用CH395CMDDHCPEnable后,CH395会主动向局域网内的DHCP服务器发送请求,获取动态IP地址、子网掩码、网关等信息。
2、地址绑定,若DHCP服务器响应成功,CH395会将获取的IP地址自动绑定到其网络接口,无需用户手动设置。通过CH395CMDGetIPInf命令可查询当前分配的IP地址,验证是否绑定成功(在DHCP中断中实现)
第六步:改变tcp连接地方(DHCP分配成功后调用连接服务器函数)
int main(void)
{
systick_config();
CH395_PORT_INIT();//SPI初始化
LOG("CH395EVT Test Demo\r\n");
CH395_GPIO_INIT();//RST和INT引脚初始化
CH395Reset();
Delay_Ms(100);
uint8_t i = CH395CMDGetVer();
LOG("CH395VER : %2x\r\n", (uint16_t)i);
InitCH395InfParam(); /* Initialize CH395Inf parameters */
i = CH395Init(); /* Initialize the CH395 */
mStopIfError(i);
#if CH395_DHCP
LOG("Start DHCP\n");
i = CH395CMDDHCPEnable(1);
mStopIfError(i);
#endif
#if KEEP_LIVE
keeplive_set();
#endif
while (1)
{ /* Wait for the CH395 Connect Ethernet*/
if (CH395CMDGetPHYStatus() == PHY_DISCONN) /* Example Query whether the CH395 is connected */
{
Delay_Ms(200); /* If no, wait for 200MS and query again */
}
else
{
LOG("CH395 Connect Ethernet\r\n"); /*When the CH395 is connected to the Ethernet, an interruption occurs */
break;
}
}
#if !CH395_DHCP
InitSocketParam(); /* Initialize socket related variables */
CH395SocketInitOpen();
#endif
int preflag = 0;
while (1)
{
if (Query395Interrupt() == 0)
{
CH395GlobalInterrupt();
}
#if !CH395_DHCP
Data_Loop(0);
#endif
#if CH395_DHCP
if (flag)
{
flag = 0;
preflag = 1;
InitSocketParam(); /* Initialize socket related variables */
CH395SocketInitOpen();
}
if(preflag==1 &&flag==0)
{
Data_Loop(0);
}
#endif
}
}
第七步:测试
观看下图可知,DHCP分配ip成功
调试心得
(1)当服务器主动关闭连接时,会进入TIME_WAIT状态(持续2*MSL,默认约60秒),导致原端口暂时无法复用,如果服务器没有做端口复用这个功能,可能会出现这个现象
(2)运行例程发现触发GINT_STAT_PHY_CHANGE中断,产生原因:当PHY芯片检测到物理连接状态变化(如网线插入/拔出、网络断开/恢复)时触发,如果频繁误触发PHY中断,可能是电源噪声或物理连接不稳定导致PHY状态抖动。