CH32V307 DHCP例程介绍
1、DHCP概述
DHCP,全称为Dynamic Host Configuration Protocol,动态主机配置协议,该协议允许服务器向客户端动态分配 IP 地址和配置信息,实现了自动设置IP地址、统一管理IP地址分配,简单理解为实现即插即用。
2、例程介绍
main函数内容如下:
/*********************************************************************
* @fn main
*
* @brief Main program
*
* @return none
*/
int main(void)
{
u8 i;
SystemCoreClockUpdate();
Delay_Init();
USART_Printf_Init(115200); //USART initialize
printf("DHCP Test\r\n");
printf("SystemClk:%d\r\n", SystemCoreClock);
printf("ChipID:%08x\r\n", DBGMCU_GetCHIPID());
printf("net version:%x\n", WCHNET_GetVer());
if( WCHNET_LIB_VER != WCHNET_GetVer())
{
printf("version error.\n");
}
WCHNET_GetMacAddr(MACAddr); //get the chip MAC address
printf("mac addr:");
for(i = 0; i < 6; i++)
printf("%x ", MACAddr[i]);
printf("\n");
TIM2_Init();
WCHNET_DHCPSetHostname("WCHNET"); //Configure DHCP host name
i = ETH_LibInit(IPAddr,GWIPAddr,IPMask,MACAddr); //Ethernet library initialize
mStopIfError(i);
if(i == WCHNET_ERR_SUCCESS)
printf("WCHNET_LibInit Success\r\n");
WCHNET_DHCPStart(WCHNET_DHCPCallBack); //Start DHCP
while(1)
{
/*Ethernet library main task function,
* which needs to be called cyclically*/
WCHNET_MainTask();
/*Query the Ethernet global interrupt,
* if there is an interrupt, call the global interrupt handler*/
if(WCHNET_QueryGlobalInt())
{
WCHNET_HandleGlobalInt();
}
}
}
下为具体介绍:
SystemCoreClockUpdate();
Delay_Init();
USART_Printf_Init(115200); //USART initialize
printf("DHCP Test\r\n");
printf("SystemClk:%d\r\n", SystemCoreClock);
printf("ChipID:%08x\r\n", DBGMCU_GetCHIPID());
printf("net version:%x\n", WCHNET_GetVer());
if( WCHNET_LIB_VER != WCHNET_GetVer())
{
printf("version error.\n");
}
首先是相关函数初始化以及例程相关信息的打印。SystemCoreClockUpdate函数、Delay_Init函数、USART_Printf_Init函数分别为系统时钟初始化、延时函数初始化、串口打印函数初始化,在此不再赘述。
打印函数中,WCHNET_GetVer函数为CH32V307以太网协议栈库函数,功能为获取库的版本号,直接调用即可。获取版本号之后会进行一个判断,若不一致,打印version error.
WCHNET_GetMacAddr(MACAddr); //get the chip MAC address printf("mac addr:"); for(i = 0; i < 6; i++) printf("%x ", MACAddr[i]); printf("\n");
TIM2_Init();
WCHNET_GetMacAddr函数主要用于获取CH32V307的MAC地址。MAC地址,即发送这个帧的设备希望接收这个帧的设备地址,由IEEE为生产商分配,全球唯一,6字节48 位。 CH32V307的MAC地址出厂时已烧录在芯片内部。地址的发送遵循低有效位在前的原则。
TIM2_Init函数主要用于检测以太网PHY连接的状态。定时器10ms进一次中断。在定时器中断函数中,主要对定时器周期进行加法计数。
WCHNET_DHCPSetHostname("WCHNET");
WCHNET_DHCPSetHostname为协议栈库函数,直接调用即可,用于配置DHCP主机名。关于配置的DHCP主机名,可在路由器网页中看到,在设备列表里有设备名称。
i = ETH_LibInit(IPAddr,GWIPAddr,IPMask,MACAddr); //Ethernet library initialize
mStopIfError(i);
if(i == WCHNET_ERR_SUCCESS)
printf("WCHNET_LibInit Success\r\n");
ETH_LibInit为以太网库初始化,ETH_LibInit函数内容如下:
/*********************************************************************
* @fn ETH_LibInit
*
* @brief Ethernet library initialization program
*
* @return command status
*/
uint8_t ETH_LibInit( uint8_t *ip, uint8_t *gwip, uint8_t *mask, 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( macaddr );
return (s);
}
该函数中,首先对_WCH_CFG结构体参数进行配置,_WCH_CFG结构体内容如下:
struct _WCH_CFG
{
uint32_t TxBufSize; //MAC send buffer size, reserved for use
uint32_t TCPMss; //TCP MSS size
uint32_t HeapSize; //heap memory size
uint32_t ARPTableNum; //Number of ARP lists
uint32_t MiscConfig0; //Miscellaneous Configuration 0
/* Bit 0 TCP send buffer copy 1: copy, 0: not copy */
/* Bit 1 TCP receive replication optimization, used for internal debugging */
/* bit 2 delete oldest TCP connection 1: enable, 0: disable */
/* Bits 3-7 Number of PBUFs of IP segments */
/* Bit 8 TCP Delay ACK disable */
uint32_t MiscConfig1; //Miscellaneous Configuration 1
/* Bits 0-7 Number of Sockets*/
/* Bits 8-12 Reserved */
/* Bit 13 PING enable, 1: On 0: Off */
/* Bits 14-18 TCP retransmission times */
/* Bits 19-23 TCP retransmission period, in 50 milliseconds */
/* bit 25 send failed retry, 1: enable, 0: disable */
/* bit 26 Select whether to perform IPv4 checksum check on
* the TCP/UDP/ICMP header of the received frame payload by hardware,
* and calculate and insert the checksum of the IP header and payload of the sent frame by hardware.*/
/* Bits 27-31 period (in 250 milliseconds) of Fine DHCP periodic process */
led_callback led_link; //PHY Link Status Indicator
led_callback led_data; //Ethernet communication indicator
eth_tx_set net_send; //Ethernet send
eth_rx_set net_recv; //Ethernet receive
uint32_t CheckValid; //Configuration value valid flag, fixed value @WCHNET_CFG_VALID
};
TxBufSize主要配置MAC发送缓冲区大小
TCPMss主要配置TCP最大报文段长度
HeapSize主要配置堆大小
ARPTableNum,主要配置ARP列表数量
MiscConfig0为杂项配置,对应位的注释如下:
/*Bit 0 TCP发送缓冲区复制1:复制,0:不复制*/
/*Bit 1 TCP接收复制优化,用于内部调试*/
/*Bit 2删除最旧的TCP连接1:启用,0:禁用*/
/*Bits 3-7 IP段的PBUF数*/
/*Bit 8 TCP延迟ACK禁用*/
MiscConfig1为杂项配置,对应位的注释如下:
/*Bit 0-7Socket数量*/
/*Bit 8-12保留*/
/*Bit 13 PING使能,1:开0:关*/
/*Bit 14-18 TCP重传次数*/
/*Bit 19-23 TCP重传周期,以50毫秒为单位*/
/*Bit 25发送失败重试,1:启用,0:禁用*/
/*Bit 26选择是否对执行IPv4校验和检查
*由硬件接收的帧有效载荷的TCP/UDP/ICMP报头,
*并通过硬件计算和插入发送帧的IP报头和有效载荷的校验和*/
/*Bit 27-31精细DHCP周期过程的周期(以250毫秒为单位)*/
led_link主要配置PHY建立连接指示灯
led_data主要配置PHY数据传输指示灯
关于接口指示灯配置,例程默认配置的是PC0和PC1,可根据自己需求进行修改
net_send主要配置以太网发送以链模式发送数据帧。
CheckValid主要配置配置值有效标志,固定值为WCHNET_CFG_VALID
WCHNET_ConfigLIB为协议栈库函数,主要用于库参数配置,即配置上述介绍参数,返回值为0表示成功。
WCHNET_Init函数为协议栈库函数,主要用于配置库初始化,主要设置IP地址,网关地址、子网掩码以及MAC地址,返回值为0表示成功
ETH_Init函数为以太网初始化函数。
回到main函数,以太网库初始化完成后,调用WCHNET_DHCPStart函数启动DHCP。当DHCP成功或者失败时,库会调用dhcp_callback函数,通知应用层DHCP的状态,WCHNET向本函数传递两个参数,第一个参数为DHCP状态,0为成功,其他值失败,当DHCP成功时,用户可以通过第二个参数获取到一个指针,该指针指向的地址依次保存了 IP 地址,网关地址,子网掩码,主DNS和次DNS,一共20个字节。注意该指针为临时变量dhcp_callback,返回后,该指针失效。
如果当前网络内没有DHCP Server,会产生超时时间,超时时间约为10秒。超时后调用dhcp_callback函数通知应用层,此时DHCP并不会停止,会一直查找DHCP Server。用户可以调用 WCHNET_DHCPStop 来停止DHCP。
使用时注意以下两点:
(1)必须在WCHNET_Init成功之后启动DHCP(必须)。
(2)在DHCP成功之后,再创建socket(推荐)。
(3)DHCP功能会占用一个UDP socket
如果DHCP失败,则可以用WCHNET_Init 时使用的IP地址进行通讯。
while循环中,首先执行WCHNET_MainTask函数,WCHNET_MainTask函数内容如下:
/*********************************************************************
* @fn WCHNET_MainTask
*
* @brief library main task function
*
* @param none.
*
* @return none.
*/
void WCHNET_MainTask(void)
{
WCHNET_NetInput( ); /* Ethernet data input */
WCHNET_PeriodicHandle( ); /* Protocol stack time-related task processing */
WCHNET_HandlePhyNegotiation();
WCHNET_RecProcess();
}
WCHNET_NetInput函数为以太网数据输入函数,总是在主程序中调用,或者在检测到接收中断后调用。
WCHNET_PeriodicHandle函数处理协议栈中的时间相关任务
WCHNET_HandlePhyNegotiation函数处理PHY的协商
if(WCHNET_QueryGlobalInt())
{
WCHNET_HandleGlobalInt();
}
查询全局中断状态,当查询到有相应的中断标志置1之后,执行中断处理函数,中断处理函数内容如下:
/*********************************************************************
* @fn WCHNET_HandleGlobalInt
*
* @brief Global Interrupt Handle
*
* @return none
*/
void WCHNET_HandleGlobalInt(void)
{
u8 intstat;
u16 i;
u8 socketint;
//WCHNET_GetGlobalInt,读全局中断并将全局中断清零,具体状态码请查阅 WCHNET.h
intstat = WCHNET_GetGlobalInt(); //get global interrupt flag,获取全局中断并将全局中断清0
if (intstat & GINT_STAT_UNREACH) //Unreachable interrupt,无法访问的中断
{
printf("GINT_STAT_UNREACH\r\n");
}
if (intstat & GINT_STAT_IP_CONFLI) //IP conflict,IP冲突
{
printf("GINT_STAT_IP_CONFLI\r\n");
}
if (intstat & GINT_STAT_PHY_CHANGE) //PHY status change,PHY状态改变
{
i = WCHNET_GetPHYStatus();
if (i & PHY_Linked_Status)
printf("PHY Link Success\r\n");
}
if (intstat & GINT_STAT_SOCKET) { //socket related interrupt,socket相关中断
for (i = 0; i < WCHNET_MAX_SOCKET_NUM; i++)
{
socketint = WCHNET_GetSocketInt(i); //获取套接字中断,并清除套接字中断。
if (socketint)
WCHNET_HandleSockInt(i, socketint); //socket中断处理函数
}
}
}
该函数具体可参考注释,此处主要注意socket中断处理函数,socket中断处理函数内容如下:
/*********************************************************************
* @fn WCHNET_HandleSockInt
*
* @brief Socket Interrupt Handle
*
* @param socketid - socket id.
* intstat - interrupt status
*
* @return none
*/
void WCHNET_HandleSockInt(u8 socketid, u8 intstat)
{
if (intstat & SINT_STAT_RECV) //receive data
{
WCHNET_DataLoopback(socketid); //Data loopback
}
if (intstat & SINT_STAT_CONNECT) //connect successfully
{
WCHNET_ModifyRecvBuf(socketid, (u32) SocketRecvBuf[socketid], RECE_BUF_LEN);
printf("TCP Connect Success\r\n");
}
if (intstat & SINT_STAT_DISCONNECT) //disconnect
{
printf("TCP Disconnect\r\n");
}
if (intstat & SINT_STAT_TIM_OUT) //timeout disconnect
{
printf("TCP Timeout\r\n");
WCHNET_CreateTcpSocket();
}
}
该函数中主要关注WCHNET_DataLoopback函数,该函数内容如下:
/*********************************************************************
* @fn WCHNET_DataLoopback
*
* @brief Data loopback function.
*
* @param id - socket id.
*
* @return none
*/
void WCHNET_DataLoopback(u8 id)
{
#if 1
u8 i;
u32 len;
u32 endAddr = SocketInf[id].RecvStartPoint + SocketInf[id].RecvBufLen; //Receive buffer end address
if ((SocketInf[id].RecvReadPoint + SocketInf[id].RecvRemLen) > endAddr) { //Calculate the length of the received data
len = endAddr - SocketInf[id].RecvReadPoint;
}
else {
len = SocketInf[id].RecvRemLen;
}
i = WCHNET_SocketSend(id, (u8 *) SocketInf[id].RecvReadPoint, &len); //send data
if (i == WCHNET_ERR_SUCCESS) {
WCHNET_SocketRecv(id, NULL, &len); //Clear sent data
}
#else
u32 len, totallen;
u8 *p = MyBuf;
len = WCHNET_SocketRecvLen(id, NULL); //query length
totallen = len;
WCHNET_SocketRecv(id, MyBuf, &len); //Read the data of the receive buffer into MyBuf
while(1){
len = totallen;
WCHNET_SocketSend(id, p, &len); //Send the data
totallen -= len; //Subtract the sent length from the total length
p += len; //offset buffer pointer
if(totallen)continue; //If the data is not sent, continue to send
break; //After sending, exit
}
#endif
}
该函数主要是将接收的数据发送出去。
以上就是整个DHCP例程的工作流程。