在这篇文章中我将详细介绍CH32V307VCT6的以太网的初始化流程,本次使用的开发板是CH32V307VCT6官方评估板
目录
为什么使用CH32的ETH
CH32V307和STM32F407的其中一个以太网的优秀的区别:CH32V307内部有1G和10M的以太网,这里要注意下,1G是不含有物理层PHY的,如果需要使用是需要外部PHY芯片的,通过相应的接口来控制的,而10M是V307内部就有PHY,我们将10M的接口引出连接网口通过网线连接电脑即可完成TCP/UDP通信,不需要外接以太网模块,本次开发板作为客户端,登录网络中的TCP服务端
以太网初始化流程
获取库函数版本号
在这里首先测试是否已经把WCH的ETH库导入,如果能读到版本号,则继续
获取芯片的MAC地址(Media Access Control Address)局域网地址:
函数原型是
MAC地址和IP地址的区别
1.IP地址是基于网络拓扑设计出来的,改动IP很容易,而MAC地址是生产厂商烧录好的,不可改动(相当于STM32的唯一设备号),我们可以给局域网上的计算机修改IP,但是不可以修改MAC,不同网卡的MAC不一样。
2.IP地址为32位,MAC地址为48位
3.IP地址应用于网络层,MAC应用于数据链路层
以太网库初始化子程序
初始化ETH库
首先在这里初始化了ETH运行所需的结构体
uint8_t ETH_LibInit(const uint8_t *ip,const uint8_t *gwip,const uint8_t *mask,const uint8_t *macaddr)
{
uint8_t s;
struct _WCH_CFG cfg;
memset(&cfg,0,sizeof(cfg));
cfg.TxBufSize = ETH_TX_BUF_SZE;
cfg.TCPMss = WCHNET_TCP_MSS;
cfg.HeapSize = WCHNET_MEM_HEAP_SIZE;
cfg.ARPTableNum = WCHNET_NUM_ARP_TABLE;
cfg.MiscConfig0 = WCHNET_MISC_CONFIG0;
cfg.MiscConfig1 = WCHNET_MISC_CONFIG1;
cfg.led_link = ETH_LedLinkSet;
cfg.led_data = ETH_LedDataSet;
cfg.net_send = ETH_TxPktChainMode;
cfg.CheckValid = WCHNET_CFG_VALID;
s = WCHNET_ConfigLIB(&cfg);
if( s ){
return (s);
}
s = WCHNET_Init(ip,gwip,mask,macaddr);
ETH_Init( );
return (s);
}
结构体内部为
struct _WCH_CFG
{
uint32_t TxBufSize; /* MAC发送缓冲区大小,保留使用 */
uint32_t TCPMss; /* TCP MSS大小 */
uint32_t HeapSize; /* 堆分配内存大小 */
uint32_t ARPTableNum; /* ARP列表个数 */
uint32_t MiscConfig0; /* 杂项配置0 */
/* 位0 TCP发送缓冲区复制 1:复制,0:不复制 */
/* 位1 TCP接收复制优化,内部调试使用 */
/* 位2 删除最早的TCP连接 1:启用,0:禁用 */
/* 位3-7 IP分片的PBUF个数 */
uint32_t MiscConfig1; /* 杂项配置1 */
/* 位0-7 Socket的个数 */
/* 位8-12 保留 */
/* 位13 PING使能,1:开启 0:关闭 */
/* 位14-18 TCP重传次数 */
/* 位19-23 TCP重传周期,单位为50毫秒 */
/* 位25 发送失败重试,1:启用,0:禁用 */
/* 位26-31 保留 */
led_callback led_link; /* PHY Link状态指示灯 */
led_callback led_data; /* 以太网通信指示灯 */
eth_tx_set net_send; /*设置以太网传输*/
uint32_t CheckValid;/* 配置值有效标志,固定值 @WCHNET_CFG_VALID */
};
TxBufSize :发送缓存,保留,不做解释
TCPMss:Mss(Maximum Segment Size),TCP传往另一端的最大块数据的长度。当一个连接建立时,连接的双方都要通告各自的MSS,对于一个以太网, MSS值可达1460字节(1500(最大)-20(IP首部长度)-20(TCp首部长度)),通常TCP通信的双方协商这个值,以避免TCP分片,一般说来,如果没有分段发生, MSS还是越大越好,在这里设置为800byte。
HeapSize:堆分配内存大小,这里设置为4600byte。
ARPTableNum:ARP列表个数,ARP(Address Resolution Protocol)是用来将IP地址解析为MAC地址的协议,ARP列表为网络设备记录 IP地址和MAC地址对应关系的表项
域名通过DNS协议获取到真实的IP地址,例如:可以通过查询DNS服务器中 “百度的IP地址”,DNS服务器返回“它的IP地址168.177xxxxx"
那么,IP地址通过ARP协议可以获取到相应的MAC地址,例如:通过询问”IP地址为168.xxxx的MAC是多少“,IP地址为168.xxxx的服务器返回"MAC地址为xxxx-xxxxx-xxxxx-xxxxx"。
在这里使用了WCH做的ETH的库,WCHNET.h和libwchnet.a。这是沁恒制作的网络库,WCHNET.h里面我们可以看到相关的宏定义的量以及网络控制函数。我们在使用时候就是利用这些函数即可。.a文件就是这些函数的静态库,源码这里是没有的。
在windows下可以看到,它储存了9个ARP列表项
可以查询到windows下的ARP列表,在TCP/IP模型中,ARP协议属于网络层。
MiscConfig0/MiscConfig1:配置一些回传细节,重试次数等小配置项。
led_callback:LED回调结构体,typedef void (*led_callback)( uint8_t setbit );,typedef 定义一个新的数据类型,定义了一个函数指针,可以通过结构体变量控制函数的输出,在这里传入了两个LED状态指示灯。
net_send:发送以太网帧,在这个函数中,typedef uint32_t (*eth_tx_set )( uint16_t len, uint32_t *pBuff );
*********************************************************************
* @fn ETH_TxPktChainMode
*
* @brief MAC 以链模式发送以太网帧
*
* @param Send length
*
* @return Send status.
*/
uint32_t ETH_TxPktChainMode(uint16_t len, uint32_t *pBuff )
{
/* 检查是 DMA传输 or CPU传输 */
if((DMATxDescToSet->Status & ETH_DMATxDesc_OWN) != (u32)RESET)
{
return ETH_ERROR;
}
/* 设置数据长度 */
DMATxDescToSet->ControlBufferSize = (len & ETH_DMATxDesc_TBS1);
DMATxDescToSet->Buffer1Addr = (uint32_t)pBuff;
/*设置最后一段和第一段的BITS(在本例中,帧在一个描述符中传输)*/
DMATxDescToSet->Status |= ETH_DMATxDesc_LS | ETH_DMATxDesc_FS;
/*将缓冲区返回给以太网 DMA*/
DMATxDescToSet->Status |= ETH_DMATxDesc_OWN;
/*设置了 Tx Buffer 不可用标志时:清除它并恢复传输*/
if ((ETH->DMASR & ETH_DMASR_TBUS) != (u32)RESET)
{
/* 清除 TBUS ETHERNET DMA 标志 */
ETH->DMASR = ETH_DMASR_TBUS;
/*复位 DMA 传输*/
ETH->DMATPDR = 0;
}
/* 使用下一个 Tx 描述符更新 ETHERNET DMA 全局 Tx 描述符 */
/* 链接模式 */
/* 为要发送的下一个缓冲区选择下一个 DMA Tx 描述符列表 */
DMATxDescToSet = (ETH_DMADESCTypeDef*) (DMATxDescToSet->Buffer2NextDescAddr);
/* Return SUCCESS */
return ETH_SUCCESS;
}
在这里操作以太网DMA结构体 ETH_DMADESCTypeDef *DMATxDescToSet;
/* ETH DMA structure definition */
typedef struct
{
uint32_t Status; /* Status*/
uint32_t ControlBufferSize; /* Control and Buffer1, Buffer2 lengths */
uint32_t Buffer1Addr; /* Buffer1 address pointer */
uint32_t Buffer2NextDescAddr; /* Buffer2 or next descriptor address pointer */
} ETH_DMADESCTypeDef;
CheckValid:配置值有效标志,固定为WCHNET_CFG_VALID 0x12345678
ETH_LibInit(eth_ip_addr, eth_gwip_addr, eth_ip_mask, eth_mac_addr);
分别输入
uint8_t eth_mac_addr[6] = {}; /*MAC地址,初始化时自动获取,用户不需要设置 */
uint8_t eth_ip_addr[4] = {192,168,1,200}; /*IP地址 */
uint8_t eth_gwip_addr[4] = {192,168,1,1}; /*网关 */
uint8_t eth_ip_mask[4] = {255,255,255,0}; /*子网掩码 */
通俗一点的讲:网关就是要去别的网络的时候,把报文首先发送到的那台设备。稍微专业一点的术语,网关就是当前主机的默认路由。如果你访问的IP地址不是这个范围的,则就投递到网关上,让这台设备来转发。
使用具体的 WCHNET_Init(ip,gwip,mask,macaddr); 初始化ETH库
ETH硬件初始化
/*********************************************************************
* @fn ETH_Init
*
* @brief Ethernet initialization.
*
* @return none
*/
void ETH_Init( void )
{
ETH_SetMCO( );/*设置ETH时钟*/
ETH_LedConfiguration( );/*ETH的LED引脚初始化*/
ETH_Configuration( );/*ETH功能设置,DMA初始化,物理层初始化*/
ETH_DMATxDescChainInit(DMATxDscrTab, MACTxBuf, ETH_TXBUFNB);//ETH_DMA发送描述初始化
ETH_DMARxDescChainInit(DMARxDscrTab, MACRxBuf, ETH_RXBUFNB);//ETH_DMA接收描述初始化
ETH_Start( );//开启ETH
NVIC_EnableIRQ(ETH_IRQn);
}
配置keep live参数
#if (eth_keeplive_enable==1)/*配置keeplive参数*/
_KEEP_CFG_VALUE.KLIdle = 20000;/* 空闲该ms时间启动探测*/
_KEEP_CFG_VALUE.KLIntvl = 15000;/* 间隔该ms探测一次*/
_KEEP_CFG_VALUE.KLCount = 9;/* 探测最大次数 */
WCHNET_ConfigKeepLive( &_KEEP_CFG_VALUE);
TCP_KEEPALIVE功能可以用来检测或保持基于TCP协议的客户端和服务器之间的链路畅通。 当客户端和服务器之间长时间没有数据通信时,协议栈会发送一个特殊的空数据包,用来检测链路是否正常。 接收到这个空数据包的一方会自动发送一个应答包,表示网络链路通信正常。
初始化TCP客户端 连接服务器
配置客户端变量
首先设置了目标服务器的IP地址,也就是连接上电脑之后需要把电脑的这个以太网口设置为设个这个地址,并且指定了外部链接的端口号,以及socke_id ,客户端和服务器 建立连接后,在服务器端会为每一个新连接分配一个socket id,用于唯一标识这个连接。
//初始化设置TCP客户端
void tcp_client1_init(void)
{
/*设置目标服务器的IP地址,要连接的是192.168.1.100,端口6000这个服务器*/
net_tcp_client1.ip_addr_remote[0]=192;
net_tcp_client1.ip_addr_remote[1]=168;
net_tcp_client1.ip_addr_remote[2]=1;
net_tcp_client1.ip_addr_remote[3]=100;
/*设置端口号*/
net_tcp_client1.port_remote =6000;//要连接的服务器端口号
net_tcp_client1.port_local = 2000;//本地端口号
net_tcp_client1.socke_id=255;//设置为255,固定设置
net_tcp_client1.socke_index = 0;//给每个客户端设置一个标识(可以不设置)
/*******************************TCP客户端********************************************/
net_tcp_client_close(&net_tcp_client1);//先尝试关闭(必须加)
/*创建socket*/
net_tcp_client_creat_socket(&net_tcp_client1, tcp_client1_connected, tcp_client1_disconnected, tcp_client1_recv);
net_tcp_client_connect(&net_tcp_client1);//连接服务器
}
关闭客户端
/**
* @brief 关闭socket
* @param socketid
* @param buf 数据地址
* @param len 数据长度
* @retval 0:成功; others:失败
* @warning None
* @example
**/
char net_tcp_client_close(net_tcp_client_struct *net_tcp_client)
{
int socke_id = net_tcp_client->socke_id;
net_tcp_client->socke_id=255;
return WCHNET_SocketClose( socke_id, TCP_CLOSE_NORMAL );
}
创建客户端
使用了函数
char net_tcp_client_creat_socket(net_tcp_client_struct *net_tcp_client,
net_tcp_client_back connected_back, //连接成功回调函数
net_tcp_client_back disconnected_back ,//连接断开回调函数
net_tcp_client_recv_back received_back)//接收数据回调函数
在这个函数里,主要调用了WCH库中的
WCHNET_SocketCreat(&(net_tcp_client->socke_id),&TmpSocketInf); /* 创建socket,将返回的socket索引保存在SocketId中 */
**
* @brief 创建socket
*
* @param(in) *socketid - 内存地址
* @param socinf - 创建socket的配置参数 @SOCK_INF
*
* @param(out) *socketid - socketID值
*
* @return @ERR_T
*/
uint8_t WCHNET_SocketCreat( uint8_t *socketid, SOCK_INF *socinf);
并且通过这个函数连接了不同状态下ETH数据传输的状态
net_tcp_client_creat_socket(&net_tcp_client1, tcp_client1_connected, tcp_client1_disconnected, tcp_client1_recv);//其中tcp_client1_recv 为接收到数据之后的处理函数,
在这里
/*接收到服务器消息回调*/
void tcp_client1_recv(unsigned char sockeid, int socke_index, unsigned char* data, unsigned long length)
{
net_tcp_client_send(&net_tcp_client1, data, length);//发送数据给服务器
usart_send_bytes_it(USART1, data, length);//串口输出
}
完整函数为:
char net_tcp_client_creat_socket(net_tcp_client_struct *net_tcp_client,
net_tcp_client_back connected_back, //连接成功回调函数
net_tcp_client_back disconnected_back ,//连接断开回调函数
net_tcp_client_recv_back received_back)//接收数据回调函数
{
unsigned char state;
SOCK_INF TmpSocketInf; /* 创建临时socket变量 */
net_tcp_client->connected_state=0;
memset((void *)&TmpSocketInf,0,sizeof(SOCK_INF)); /* 库内部会将此变量复制,所以最好将临时变量先全部清零 */
memcpy((void *)TmpSocketInf.IPAddr,net_tcp_client->ip_addr_remote,4); /* 设置目的IP地址 */
TmpSocketInf.DesPort = net_tcp_client->port_remote;
TmpSocketInf.SourPort = net_tcp_client->port_local; /* 设置源端口 */
TmpSocketInf.ProtoType = PROTO_TYPE_TCP; /* 设置socekt类型 */
TmpSocketInf.RecvStartPoint = (uint32_t)net_tcp_client->socket_recv_buf; /* 设置接收缓冲区的接收缓冲区 */
TmpSocketInf.RecvBufLen = net_tcp_client_socket_recv_buf_len ; /* 设置接收缓冲区的接收长度 */
state = WCHNET_SocketCreat(&(net_tcp_client->socke_id),&TmpSocketInf); /* 创建socket,将返回的socket索引保存在SocketId中 */
if(state!=WCHNET_ERR_SUCCESS) /* 检查错误 */
{
debug_printf("net_tcp_client_creat_socket_error: %02X\r\n", (unsigned short)state);
return state;
}
debug_printf("net_tcp_client_creat_socket_id: %d\r\n", net_tcp_client->socke_id);
if(net_tcp_client->keeplive_enable==1){
WCHNET_SocketSetKeepLive( net_tcp_client->socke_id, 1 ); /* 开启socket的KEEPLIVE功能 */
}
if(connected_back != NULL) net_tcp_client->connected_back = connected_back;
if(disconnected_back != NULL) net_tcp_client->disconnected_back = disconnected_back;
if(received_back != NULL) net_tcp_client->received_back = received_back;
return 0;
}
开启客户端并连接服务器
net_tcp_client_connect(&net_tcp_client1);//连接服务器
使用前面的socke_id值连接
void net_tcp_client_connect(net_tcp_client_struct *net_tcp_client)
{
unsigned char state;
if(net_tcp_client->connected_state==0)//未执行连接
{
debug_printf("net_tcp_client_connect......\r\n");
state = WCHNET_SocketConnect(net_tcp_client->socke_id);
if(state!=WCHNET_ERR_SUCCESS) /* 检查错误 */
{
debug_printf("net_tcp_client_connect_error: %02X\r\n", (unsigned short)state);
}
net_tcp_client->connected_state = 1;
}
}
循环调用 while(1)
以太网库主任务函数
/*以太网库主任务函数,需要循环调用*/
WCHNET_MainTask();
查询以太网中断
if(WCHNET_QueryGlobalInt()) /*查询以太网库全局中断,如果有中断,调用全局中断处理函数*/
{
WCHNET_HandleGlobalInt();
}
在中断中
void WCHNET_HandleGlobalInt(void){
u8 initstat;
u16 i;
u8 socketinit;
initstat = WCHNET_GetGlobalInt(); // 获取全局中断标志*/
if(initstat & GINT_STAT_UNREACH){//不可达中断
debug_printf("GINT_STAT_UNREACH\r\n");
}
if(initstat & GINT_STAT_IP_CONFLI){/* IP冲突中断 */
debug_printf("GINT_STAT_IP_CONFLI\r\n");
}
if(initstat & GINT_STAT_PHY_CHANGE){/* PHY状态变化中断 */
i = WCHNET_GetPHYStatus(); /* 获取PHY连接状态*/
if(i & PHY_Linked_Status) {
debug_printf("PHY Link connected\r\n");
#if(NET_DHCP_ENABLE==1)//使能DHCP
WCHNET_DHCPStart(WCHNET_DHCPCallBack);/*开始DHCP,返回结果在WCHNET_DHCPCallBack函数中*/
#endif
}
else {
debug_printf("PHY Link disconnected\r\n");
#if(NET_DHCP_ENABLE==1)//使能DHCP
WCHNET_DHCPStop(); //停止DHCP
#endif
net_tcp_client_reconnect(&net_tcp_client1);//重新连接
}
}
if(initstat & GINT_STAT_SOCKET){//socket通信
for(i = 0; i < WCHNET_MAX_SOCKET_NUM; i++){
socketinit = WCHNET_GetSocketInt(i);
if(socketinit){//有事件需要处理
net_tcp_client_data(&net_tcp_client1, i, socketinit);//处理TCP数据
}
}
}
}
在net_tcp_client_data(&net_tcp_client1, i, socketinit);//处理TCP数据
中处理接收到的TCP数据
主要用了 net_tcp_client_recv_back received_back;//接收数据回调函数
发送数据
/**
* @brief 发送数据
* @param socketid
* @param buf 数据地址
* @param len 数据长度
* @retval 0:成功; others:失败
* @warning None
* @example
**/
char net_tcp_client_send(net_tcp_client_struct *net_tcp_client, unsigned char *buf, unsigned long len)
{
unsigned long length = len;
unsigned long totallen = length;
if(net_tcp_client->connected_state==2)//连接中
{
while(1)
{
length = totallen;
WCHNET_SocketSend(net_tcp_client->socke_id, buf, &length); /* 将MyBuf中的数据发送 */
totallen -= length; /* 将总长度减去以及发送完毕的长度 */
buf += length; /* 将缓冲区指针偏移*/
if(totallen)continue; /* 如果数据未发送完毕,则继续发送*/
break; /* 发送完毕,退出 */
}
}
else{
return 5;
}
return 0;
}
结语
至此,完成了以太网了 初始化-连接-收发数据
接收数据的处理函数
其中接收数据的处理函数是
/*接收到服务器消息回调*/
void tcp_client1_recv(unsigned char sockeid, int socke_index, unsigned char* data, unsigned long length)
{
net_tcp_client_send(&net_tcp_client1, data, length);//发送数据给服务器
usart_send_bytes_it(USART1, data, length);//串口输出
}
发送数据的处理函数
发送数据的函数是
/**
* @brief 发送数据
* @param socketid
* @param buf 数据地址
* @param len 数据长度
* @retval 0:成功; others:失败
* @warning None
* @example
**/
char net_tcp_client_send(net_tcp_client_struct *net_tcp_client, unsigned char *buf, unsigned long len)
{
unsigned long length = len;
unsigned long totallen = length;
if(net_tcp_client->connected_state==2)//连接中
{
while(1)
{
length = totallen;
WCHNET_SocketSend(net_tcp_client->socke_id, buf, &length); /* 将MyBuf中的数据发送 */
totallen -= length; /* 将总长度减去以及发送完毕的长度 */
buf += length; /* 将缓冲区指针偏移*/
if(totallen)continue; /* 如果数据未发送完毕,则继续发送*/
break; /* 发送完毕,退出 */
}
}
else{
return 5;
}
return 0;
}