W5500——添加DHCP功能

 一、前言

        上一篇介绍了DNS功能(如何通过域名去获取服务器的IP地址),里面W5500作为客户端它的IP等网络参数是我们自己设置的,但是在一个局域网内如果有很多设备,这种方式肯定是不合理的。一个是容易冲突出错,另外灵活性和利用率也不够高,这个时候就需要DHCP( Dynamic Host Configuration Protocol, 动态主机配置协议)上场了。

        DHCP是一种基于客户/服务器模式的应用层网络协议,旨在为网络设备自动分配IP地址以及其他网络配置信息,如子网掩码、默认网关和DNS服务器。

二、DHCP介绍

2.1、DHCP分配方式

        DHCP给客户机分配IP共有三种方式:

  • 动态分配——DHCP给主机指定一个有时间限制的IP地址,到达使用期限后或主机明确表示放弃这个地址时,客户端需要重新申请地址; 如果客户端没有重新申请,则这个地址将可能被其它的主机使用; 绝大多数客户端得到的都是这种动态分配的地址(可以解决IP地址不够用的困扰);

  • 手动分配——由 管理员为某些特定客户端静态绑定固定的IP地址,通过DHCP将配置的固定IP地址发给客户端;

  • 自动分配——客户端分配租期为永久的IP地址;

2.2、DHCP工作流程 

2.2.1、租约

  1.  发现阶段(DISCOVER) , DHCP客户端启动时,计算机发现本机上没有任何IP地址设定,将以广播方式通过UDP通讯向端口67发送DHCP——DISCOVER请求报文(包含客户端的MAC地址信息),来寻找DHCP服务器,请求IP地址租约,因为客户机还不知道自己属于哪一个网络,所以封包的源地址为0.0.0.0,目的地址为255.255.255.255,向网络发送特定的广播信息。网络上每一台安装了TCP/IP协议的主机都会接收这个广播信息,但只有DHCP服务器才会做出响应。

  2. 提供阶段(OFFER ),该客户对应的租约已存在且未被再次分配(包括租约到期和未到期的租约),则直接分配已记录的地址信息。该客户对应的租约不存在,服务器响应DHCP——OFFER,会从对应的地址池中查找(注意:该处地址池应该有个前提条件,就是可用的地址池:应当和接收口的IP为同一网段,或同giaddr字段同一网段,否则无法分配);然后从尚未出租的IP地址中挑选一个最前面的ip地址连同其它的参数设定,响应给客户端一个DHCP——OFFER (单播)报文,报文里面包括客户机MAC地址信息,TCP/IP的一些配置信息。

  3. 选择阶段(REQUEST),DHCP客户端可以接收到多个DHCP服务器的DHCP——OFFER数据包,但只会挑选其中一个 DHCP——OFFER(通常只接受收到的第一个DHCP——OFFER数据包),然后会向网络发送一个DHCP——REQUEST广播报文(报文中包含客户端的MAC地址、接受的租约中的IP地址、提供此租约的DHCP服务器地址等),告诉所有DHCP服务器它将指定接受哪一台服务器提供的 IP 地址,这样其他的服务器就会释放之前预分配给客户端的IP地址。同时,客户端还会向网络发送一个ARP 报文,查询网络上面有没有其它设备使用该 IP 地址,如果发现该 IP 已经被占用,客户端则会送出一个DHCP——DECLIENT报文给 DHCP 服务器,拒绝接受其 DHCP——OFFER,并重新发送 DHCP——DISCOVER信息。此时,由于还没有得到DHCP服务器的最后确认,客户端仍然使用0.0.0.0为源IP地址,255.255.255.255为目标地址进行广播。

  4. 确认阶段(ACK ),当DHCP的Server收到DHCP的Client发送的DHCP——Request后,确认要为该DHCP的Client提供的IP地址,并根据客户机的MAC地址记录该次租约行为后,并便向该DHCP的Client响应一个ACK报文(包含该IP地址,默认网关,DNS服务器等网络配置信息),来告诉DHCP的Client可以使用该IP地址了,并告知客户端这个网络参数租约的期限,并且开始租约计时。然后DHCP的Client就可以将该IP地址与网卡绑定,完成初始化过程。另外其他DHCP的Server都将收回自己之前为DHCP的Client提供的IP地址。客户端在接收到DHCP——ACK后,会向网络发送针对此IP地址的ARP解析请求以执行冲突检测,查询网络上有没有其它机器使用该IP地址;如果发现该IP地址已经被使用,客户机会发出一个DHCP DECLINE数据包给DHCP服务器,拒绝此IP地址租约,并重新发送DHCP DISCOVER信息。当用户不再需要使用分配IP地址时,就会"主动"向DHCP服务器发送RELEASE报文,告知服务器用户不再需要分配IP地址,DHCP服务器会释放被绑定的租约(在数据库中清除某个MAC对某个IP的租约记录,这样,这个IP就可以分配给下一个请求租约的MAC)。

2.2.2、续约

  1. 在使用租期过去50%时刻处, 客户端向服务器发送单播DHCP REQUEST报文续延租期。
  2. 如果收到服务器的DHCP ACK报文,则租期相应向前延长,续租成功。如果没有收到DHCP ACK报文,则客户端继续使用这个IP地址。在使用租期过去**87.5%**时刻处,向服务器发送广播DHCP REQUEST报文续延租期。
  3. 如果收到服务器的DHCP ACK报文,则租期相应向前延长,续租成功。如果没有收到DHCP ACK报文,则客户端继续使用这个IP地址。在使用租期到期时,客户端自动放弃使用这个IP地址,并开始新的DHCP过程。

2.3、协议包

2.3.1、DHCP协议包概述

        DHCP报文格式及各个字段含义如下所示:

DHCP报文格式

 

字段长度(Byte)含义
op1

1:客户端传给服务器;

2:服务器传给客户端;

htype1

1:表示传输速度10Mb/s的硬件;

2:表示传输速度100Mb/s的硬件;

hlen1表示硬件地址长度,为6
hops1表示当前的DHCP报文经过的DHCP Relay的数目。该字段由客户端或服务器设置为0,每经过一个DHCP Relay时,该字段加1。此字段的作用是限制DHCP报文所经过的DHCP Relay数目
xid4表示DHCP客户端选取的随机数,DHCP REQUEST 时随机生成的一段字符串,服务器和客户端用来在它们之间交流请求和响 应,客户端用它对请求和应答进行匹配两个数据包拥有相同的xid说明他们属于同一次会话
secs2由客户端填充,表示从客户端开始获得IP地址或IP地址续借后所使用了的秒数
flags2表示标志字段。只有标志字段的最高位才有意义,内容如下所示:

0:客户端请求服务器以单播形式发送响应报文

1:客户端请求服务器以广播形式发送响应报文

ciaddr4表示客户端的IP地址。可以是服务器分配给客户端的IP地址或者客户端已有的IP地址。客户端在初始化状态时没有IP地址,此字段为0.0.0.0
yiaddr4表示服务器分配给客户端的IP地址

siaddr

4一般来说是服务器的ip地址

giaddr

4如果需要跨子网进行DHCP地址发放,则在此处填入经过的路由器的ip地址

chaddr

16表示客户端的MAC地址,只占用6字节

sname

64表示客户端获取配置信息的服务器名字

file

128表示客户端需要获取的启动配置文件名
OPT312表示DHCP的选项字段,最多为312字节。DHCP通过此字段包含了DHCP报文类型,服务器分配给终端的配置信息,如网关IP地址,DNS服务器的IP地址,客户端可以使用IP地址的有效租期等信息

2.3.2、DHCP协议包OPT字段

        DHCP报文中的Options字段可以用来存放普通协议中没有定义的控制信息和参数。如果用户在DHCP服务器端配置了Options字段,DHCP客户端在申请IP地址的时候,会通过服务器端回应的DHCP报文获得Options字段中的配置信息。如下是W5500源码中列举的option字段选项,参考RFC1533

enum
{
   padOption               = 0,
   subnetMask              = 1,
   timerOffset             = 2,
   routersOnSubnet         = 3,
   timeServer              = 4,
   nameServer              = 5,
   dns                     = 6,
   logServer               = 7,
   cookieServer            = 8,
   lprServer               = 9,
   impressServer           = 10,
   resourceLocationServer	= 11,
   hostName                = 12,
   bootFileSize            = 13,
   meritDumpFile           = 14,
   domainName              = 15,
   swapServer              = 16,
   rootPath                = 17,
   extentionsPath          = 18,
   IPforwarding            = 19,
   nonLocalSourceRouting   = 20,
   policyFilter            = 21,
   maxDgramReasmSize       = 22,
   defaultIPTTL            = 23,
   pathMTUagingTimeout     = 24,
   pathMTUplateauTable     = 25,
   ifMTU                   = 26,
   allSubnetsLocal         = 27,
   broadcastAddr           = 28,
   performMaskDiscovery    = 29,
   maskSupplier            = 30,
   performRouterDiscovery  = 31,
   routerSolicitationAddr  = 32,
   staticRoute             = 33,
   trailerEncapsulation    = 34,
   arpCacheTimeout         = 35,
   ethernetEncapsulation   = 36,
   tcpDefaultTTL           = 37,
   tcpKeepaliveInterval    = 38,
   tcpKeepaliveGarbage     = 39,
   nisDomainName           = 40,
   nisServers              = 41,
   ntpServers              = 42,
   vendorSpecificInfo      = 43,
   netBIOSnameServer       = 44,
   netBIOSdgramDistServer	= 45,
   netBIOSnodeType         = 46,
   netBIOSscope            = 47,
   xFontServer             = 48,
   xDisplayManager         = 49,
   dhcpRequestedIPaddr     = 50,
   dhcpIPaddrLeaseTime     = 51,
   dhcpOptionOverload      = 52,
   dhcpMessageType         = 53,
   dhcpServerIdentifier    = 54,
   dhcpParamRequest        = 55,
   dhcpMsg                 = 56,
   dhcpMaxMsgSize          = 57,
   dhcpT1value             = 58,
   dhcpT2value             = 59,
   dhcpClassIdentifier     = 60,
   dhcpClientIdentifier    = 61,
   endOption               = 255
};

         下图是W5500解析服务端应答的DHCP消息用到的几个option中的字段。

case endOption :/*表示用户添加的有效信息结束*/
case padOption : /*用于字节对齐,占一个字节*/
case dhcpMessageType : /*表示DHCP消息类型,共占3个字节,DHCP消息类型如下
           Value   Message Type
           -----   ------------
             1     DHCPDISCOVER
             2     DHCPOFFER
             3     DHCPREQUEST
             4     DHCPDECLINE
             5     DHCPACK
             6     DHCPNAK
             7     DHCPRELEASE

    Code   Len  Type
   +-----+-----+-----+
   |  53 |  1  | 1-7 |
   +-----+-----+-----+
case subnetMask :/*表示子网掩码信息,由Type、Length和Value三部分组成,共占6个字节。这三部分的表示含义如下所示
    Code   Len        Subnet Mask
   +-----+-----+-----+-----+-----+-----+
   |  1  |  4  |  m1 |  m2 |  m3 |  m4 |
   +-----+-----+-----+-----+-----+-----+*/
case routersOnSubnet : /*表示网关地址,网关地址可能有多个,n为4的倍数
   Code   Len         Address 1               Address 2
   +-----+-----+-----+-----+-----+-----+-----+-----+--
   |  3  |  n  |  a1 |  a2 |  a3 |  a4 |  a1 |  a2 |  ...
   +-----+-----+-----+-----+-----+-----+-----+-----+--
case dns : /*DNS域名服务器,可能也有多个,n为4的倍数
    Code   Len         Address 1               Address 2
   +-----+-----+-----+-----+-----+-----+-----+-----+--
   |  6  |  n  |  a1 |  a2 |  a3 |  a4 |  a1 |  a2 |  ...
   +-----+-----+-----+-----+-----+-----+-----+-----+--
case dhcpIPaddrLeaseTime : /*DHCP租约时间,单位s,共占6字节
    Code   Len         Lease Time
   +-----+-----+-----+-----+-----+-----+
   |  51 |  4  |  t1 |  t2 |  t3 |  t4 |
   +-----+-----+-----+-----+-----+-----+
case dhcpServerIdentifier : /*服务器标识即服务器IP,共占6字节,用于指示客户端接收哪个服务器的租约
    Code   Len            Address
   +-----+-----+-----+-----+-----+-----+
   |  54 |  4  |  a1 |  a2 |  a3 |  a4 |
   +-----+-----+-----+-----+-----+-----+

三、W5500——DHCP

3.1、DHCP移植

        w5500库文件移植参考第一篇,除了之前已经移植的文件外,我们还需要添加Internet文件下DHCP文件夹。

3.2、源码分析

  • DHCP_init初始化,需要分配一个端口号(0~7)和DHCP交互协议的数据缓存,接下来就是mac地址、IP和网关的检测,如果对应寄存器中没有设置则分配一个默认mac地址,IP和网关则全部设置为0。另外就是DHCP里面超时机制的复位和状态机的状态设置为初始化,这个后面再说。
void DHCP_init(uint8_t s, uint8_t * buf)
{
   uint8_t zeroip[4] = {0,0,0,0};
   getSHAR(DHCP_CHADDR);
   if((DHCP_CHADDR[0] | DHCP_CHADDR[1]  | DHCP_CHADDR[2] | DHCP_CHADDR[3] | DHCP_CHADDR[4] | DHCP_CHADDR[5]) == 0x00)
   {
      // assigning temporary mac address, you should be set SHAR before call this function. 
      DHCP_CHADDR[0] = 0x00;
      DHCP_CHADDR[1] = 0x08;
      DHCP_CHADDR[2] = 0xdc;      
      DHCP_CHADDR[3] = 0x00;
      DHCP_CHADDR[4] = 0x00;
      DHCP_CHADDR[5] = 0x00; 
      setSHAR(DHCP_CHADDR);     
   }

	DHCP_SOCKET = s; // SOCK_DHCP
	pDHCPMSG = (RIP_MSG*)buf;
	DHCP_XID = 0x12345678;
	{
		DHCP_XID += DHCP_CHADDR[3];
		DHCP_XID += DHCP_CHADDR[4];
		DHCP_XID += DHCP_CHADDR[5];
		DHCP_XID += (DHCP_CHADDR[3] ^ DHCP_CHADDR[4] ^ DHCP_CHADDR[5]);
	}
	// WIZchip Netinfo Clear
	setSIPR(zeroip);
	setGAR(zeroip);

	reset_DHCP_timeout();
	dhcp_state = STATE_DHCP_INIT;
}
  •  DHCP_time_handler,和DNS相同,在DHCP里面也有一个超时机制,DHCP_time_handler提供时间计数,在stm32中可以分配一个定时器中断来处理它;
void DHCP_time_handler(void)
{
	dhcp_tick_1s++;
}
  • reg_dhcp_cbfunc回调函数注册,这里应用者可以分别自己实现这三个函数并进行注册,如果自己不实现则调用默认接口。a)dhcp_ip_assign是在客户端发送REQUEST请求后如果确认IP可以使用时调用的,默认的就是把服务端分配的网络配置设置到相应的寄存器中;b)dhcp_ip_update在客户端发送续租请求且续租成功时调用;c)dhcp_ip_conflict则是在服务端分配的网络配置监测冲突后调用;
/* 
 * @brief Register call back function 
 * @param ip_assign   - callback func when IP is assigned from DHCP server first
 * @param ip_update   - callback func when IP is changed
 * @param ip_conflict - callback func when the assigned IP is conflict with others.
 */
void reg_dhcp_cbfunc(void(*ip_assign)(void), void(*ip_update)(void), void(*ip_conflict)(void))
{
   dhcp_ip_assign   = default_ip_assign;
   dhcp_ip_update   = default_ip_update;
   dhcp_ip_conflict = default_ip_conflict;
   if(ip_assign)   dhcp_ip_assign = ip_assign;
   if(ip_update)   dhcp_ip_update = ip_update;
   if(ip_conflict) dhcp_ip_conflict = ip_conflict;
}

/* The default handler of ip assign first */
void default_ip_assign(void)
{
   setSIPR(DHCP_allocated_ip);
   setSUBR(DHCP_allocated_sn);
   setGAR (DHCP_allocated_gw);
}

/* The default handler of ip changed */
void default_ip_update(void)
{
	/* WIZchip Software Reset */
   setMR(MR_RST);
   getMR(); // for delay
   default_ip_assign();
   setSHAR(DHCP_CHADDR);
}

/* The default handler of ip changed */
void default_ip_conflict(void)
{
	// WIZchip Software Reset
	setMR(MR_RST);
	getMR(); // for delay
	setSHAR(DHCP_CHADDR);
}
  • 数据发送相关接口,如下都是客户端发送DHCP协议相关的接口,对照协议说明来看的话比较清晰,这里不细说了。而且也不需要外部调用,都在状态机DHCP_run里。另外DHCP数据传输底层采用的还是UDP传输;
void makeDHCPMSG(void);//DHCP协议非OPT字段组包,公用接口供下面三个调用
void send_DHCP_DISCOVER(void);//DISCOVER报文OPT段组包
void send_DHCP_REQUEST(void);//REQUEST报文OPT段组包
void send_DHCP_DECLINE(void);//DECLINE报文OPT段组包
  • parseDHCPMSG,服务端应答信息解析接口,也是在状态机DHCP_run里。while循环里面结合2.3.2章节的内容理解起来也比较简单;
int8_t parseDHCPMSG(void)
{
	uint8_t svr_addr[6];
	uint16_t  svr_port;
	uint16_t len;

	uint8_t * p;
	uint8_t * e;
	uint8_t type = 0;
	uint8_t opt_len;
   
   if((len = getSn_RX_RSR(DHCP_SOCKET)) > 0)
   {
   	len = recvfrom(DHCP_SOCKET, (uint8_t *)pDHCPMSG, len, svr_addr, &svr_port);
   #ifdef _DHCP_DEBUG_   
      printf("DHCP message : %d.%d.%d.%d(%d) %d received. \r\n",svr_addr[0],svr_addr[1],svr_addr[2], svr_addr[3],svr_port, len);
   #endif   
   }
   else return 0;
	if (svr_port == DHCP_SERVER_PORT) {
      // compare mac address
		if ( (pDHCPMSG->chaddr[0] != DHCP_CHADDR[0]) || (pDHCPMSG->chaddr[1] != DHCP_CHADDR[1]) ||
		     (pDHCPMSG->chaddr[2] != DHCP_CHADDR[2]) || (pDHCPMSG->chaddr[3] != DHCP_CHADDR[3]) ||
		     (pDHCPMSG->chaddr[4] != DHCP_CHADDR[4]) || (pDHCPMSG->chaddr[5] != DHCP_CHADDR[5])   )
		{
#ifdef _DHCP_DEBUG_
            printf("No My DHCP Message. This message is ignored.\r\n");
#endif
         return 0;
		}
        //compare DHCP server ip address
        if((DHCP_SIP[0]!=0) || (DHCP_SIP[1]!=0) || (DHCP_SIP[2]!=0) || (DHCP_SIP[3]!=0)){
            if( ((svr_addr[0]!=DHCP_SIP[0])|| (svr_addr[1]!=DHCP_SIP[1])|| (svr_addr[2]!=DHCP_SIP[2])|| (svr_addr[3]!=DHCP_SIP[3])) &&
                ((svr_addr[0]!=DHCP_REAL_SIP[0])|| (svr_addr[1]!=DHCP_REAL_SIP[1])|| (svr_addr[2]!=DHCP_REAL_SIP[2])|| (svr_addr[3]!=DHCP_REAL_SIP[3]))  )
            {
#ifdef _DHCP_DEBUG_
                printf("Another DHCP sever send a response message. This is ignored.\r\n");
#endif
                return 0;
            }
        }
		p = (uint8_t *)(&pDHCPMSG->op);
		p = p + 240;      // 240 = sizeof(RIP_MSG) + MAGIC_COOKIE size in RIP_MSG.opt - sizeof(RIP_MSG.opt)
		e = p + (len - 240);

		while ( p < e ) {

			switch ( *p ) {

   			case endOption :
   			   p = e;   // for break while(p < e)
   				break;
            case padOption :
   				p++;
   				break;
   			case dhcpMessageType :
   				p++;
   				p++;
   				type = *p++;
   				break;
   			case subnetMask :
   				p++;
   				p++;
   				DHCP_allocated_sn[0] = *p++;
   				DHCP_allocated_sn[1] = *p++;
   				DHCP_allocated_sn[2] = *p++;
   				DHCP_allocated_sn[3] = *p++;
   				break;
   			case routersOnSubnet :
   				p++;
   				opt_len = *p++;       
   				DHCP_allocated_gw[0] = *p++;
   				DHCP_allocated_gw[1] = *p++;
   				DHCP_allocated_gw[2] = *p++;
   				DHCP_allocated_gw[3] = *p++;
   				p = p + (opt_len - 4);
   				break;
   			case dns :
   				p++;                  
   				opt_len = *p++;       
   				DHCP_allocated_dns[0] = *p++;
   				DHCP_allocated_dns[1] = *p++;
   				DHCP_allocated_dns[2] = *p++;
   				DHCP_allocated_dns[3] = *p++;
   				p = p + (opt_len - 4);
   				break;
   			case dhcpIPaddrLeaseTime :
   				p++;
   				opt_len = *p++;
   				dhcp_lease_time  = *p++;
   				dhcp_lease_time  = (dhcp_lease_time << 8) + *p++;
   				dhcp_lease_time  = (dhcp_lease_time << 8) + *p++;
   				dhcp_lease_time  = (dhcp_lease_time << 8) + *p++;
            #ifdef _DHCP_DEBUG_  
               dhcp_lease_time = 10;
 				#endif
   				break;
   			case dhcpServerIdentifier :
   				p++;
   				opt_len = *p++;
   				DHCP_SIP[0] = *p++;
   				DHCP_SIP[1] = *p++;
   				DHCP_SIP[2] = *p++;
   				DHCP_SIP[3] = *p++;
                DHCP_REAL_SIP[0]=svr_addr[0];
                DHCP_REAL_SIP[1]=svr_addr[1];
                DHCP_REAL_SIP[2]=svr_addr[2];
                DHCP_REAL_SIP[3]=svr_addr[3];
   				break;
   			default :
   				p++;
   				opt_len = *p++;
   				p += opt_len;
   				break;
			} // switch
		} // while
	} // if
	return	type;
}
  •  DHCP_run,DHCP交互的主循环接口也是最核心的部分。a)初始化后dhcp_state状态为STATE_DHCP_INIT,客户端发送DISCOVER报文来寻找DHCP服务器,请求IP地址租约,因为客户机还不知道自己属于哪一个网络,所以封包的源地址为0.0.0.0,目的地址为255.255.255.255;b)如果收到任意服务器的offer应答报文(表示有可用的IP分配给客户端),客户端继续发送REQUEST报文,报文的OPT字段会包含客户端响应的服务器所分配的IP信息和服务器的标识即IP地址;c)如果客户端收到响应服务器对REQUEST报文的ACK,客户端还会继续发送一个APR请求(w5500是往该IP上发送UDP数据测试是否超时)确认IP是否被使用。如果IP没有冲突则就可以将这些配置写入W5500寄存器中正常使用了,反之需要重新发送DISCOVER报文重复上述步骤;d)如果已经有租约,w5500会在第一次租约时间达到租约时间的50%时发送REQUEST报文去续租,同时等待服务器响应。
uint8_t DHCP_run(void)
{
	uint8_t  type;
	uint8_t  ret;

	if(dhcp_state == STATE_DHCP_STOP) return DHCP_STOPPED;

	if(getSn_SR(DHCP_SOCKET) != SOCK_UDP)
	   socket(DHCP_SOCKET, Sn_MR_UDP, DHCP_CLIENT_PORT, 0x00);

	ret = DHCP_RUNNING;
	type = parseDHCPMSG();

	switch ( dhcp_state ) {
	   case STATE_DHCP_INIT     :
         DHCP_allocated_ip[0] = 0;
         DHCP_allocated_ip[1] = 0;
         DHCP_allocated_ip[2] = 0;
         DHCP_allocated_ip[3] = 0;
   		send_DHCP_DISCOVER();
   		dhcp_state = STATE_DHCP_DISCOVER;
   		break;
		case STATE_DHCP_DISCOVER :
			if (type == DHCP_OFFER){
#ifdef _DHCP_DEBUG_
				printf("> Receive DHCP_OFFER\r\n");
#endif
            DHCP_allocated_ip[0] = pDHCPMSG->yiaddr[0];
            DHCP_allocated_ip[1] = pDHCPMSG->yiaddr[1];
            DHCP_allocated_ip[2] = pDHCPMSG->yiaddr[2];
            DHCP_allocated_ip[3] = pDHCPMSG->yiaddr[3];

				send_DHCP_REQUEST();
				dhcp_state = STATE_DHCP_REQUEST;
			} else ret = check_DHCP_timeout();
         break;

		case STATE_DHCP_REQUEST :
			if (type == DHCP_ACK) {

#ifdef _DHCP_DEBUG_
				printf("> Receive DHCP_ACK\r\n");
#endif
				if (check_DHCP_leasedIP()) {
					// Network info assignment from DHCP
					dhcp_ip_assign();
					reset_DHCP_timeout();

					dhcp_state = STATE_DHCP_LEASED;
				} else {
					// IP address conflict occurred
					reset_DHCP_timeout();
					dhcp_ip_conflict();
				    dhcp_state = STATE_DHCP_INIT;
				}
			} else if (type == DHCP_NAK) {

#ifdef _DHCP_DEBUG_
				printf("> Receive DHCP_NACK\r\n");
#endif

				reset_DHCP_timeout();

				dhcp_state = STATE_DHCP_DISCOVER;
			} else ret = check_DHCP_timeout();
		break;

		case STATE_DHCP_LEASED :
		   ret = DHCP_IP_LEASED;
			if ((dhcp_lease_time != INFINITE_LEASETIME) && ((dhcp_lease_time/2) < dhcp_tick_1s)) {
				
#ifdef _DHCP_DEBUG_
 				printf("> Maintains the IP address \r\n");
#endif

				type = 0;
				OLD_allocated_ip[0] = DHCP_allocated_ip[0];
				OLD_allocated_ip[1] = DHCP_allocated_ip[1];
				OLD_allocated_ip[2] = DHCP_allocated_ip[2];
				OLD_allocated_ip[3] = DHCP_allocated_ip[3];
				
				DHCP_XID++;

				send_DHCP_REQUEST();

				reset_DHCP_timeout();

				dhcp_state = STATE_DHCP_REREQUEST;
			}
		break;

		case STATE_DHCP_REREQUEST :
		   ret = DHCP_IP_LEASED;
			if (type == DHCP_ACK) {
				dhcp_retry_count = 0;
				if (OLD_allocated_ip[0] != DHCP_allocated_ip[0] || 
				    OLD_allocated_ip[1] != DHCP_allocated_ip[1] ||
				    OLD_allocated_ip[2] != DHCP_allocated_ip[2] ||
				    OLD_allocated_ip[3] != DHCP_allocated_ip[3]) 
				{
					ret = DHCP_IP_CHANGED;
					dhcp_ip_update();
               #ifdef _DHCP_DEBUG_
                  printf(">IP changed.\r\n");
               #endif
					
				}
         #ifdef _DHCP_DEBUG_
            else printf(">IP is continued.\r\n");
         #endif            				
				reset_DHCP_timeout();
				dhcp_state = STATE_DHCP_LEASED;
			} else if (type == DHCP_NAK) {

#ifdef _DHCP_DEBUG_
				printf("> Receive DHCP_NACK, Failed to maintain ip\r\n");
#endif

				reset_DHCP_timeout();

				dhcp_state = STATE_DHCP_DISCOVER;
			} else ret = check_DHCP_timeout();
	   	break;
		default :
   		break;
	}

	return ret;
}

四、测试例程 

        DHCP测试例程在W5500官网上也有,可以自己去下一个。测试的话还需要准备一个路由器,后面电脑和W5500都要接到路由器上,路由器需要开启DHCP服务。

//配置网络配置信息结构体
wiz_NetInfo net_info = {
    .mac = {0x00, 0x08, 0xdc, 0x16, 0xed, 0x2e},
    .ip = {192, 168, 1, 120},
    .sn = {255, 255, 255, 0},
    .gw = {192, 168, 1, 1},
    .dns = {8, 8, 8, 8},
    .dhcp = NETINFO_DHCP};

//DHCP初始化函数
void dhcp_init(void)
{
    setSHAR(net_info.mac);//寄存器设置mac地址
    printf(" DHCP client running \r\n");
    DHCP_init(SOCKET_DHCP,ethernet_buf);
    reg_dhcp_cbfunc(dhcp_assign, dhcp_update, dhcp_conflict);
}

void dhcp_update(void)
{
    dhcp_assign();
}

//IP地址冲突函数
void dhcp_conflict(void)
{
    printf("CONFLICT IP from DHCP\r\n");
    while(1);
}
 
//请求IP地址函数
void dhcp_assign(void)
{
    getIPfromDHCP(net_info.ip);
    getGWfromDHCP(net_info.gw);
    getSNfromDHCP(net_info.sn);
    getDNSfromDHCP(net_info.dns);
 
    net_info.dhcp = NETINFO_DHCP;
 
    network_initialize(net_info);
    print_network_information(net_info);
    printf("DHCP LEASED TIME:%ld Sec.\r\n",getDHCPLeasetime());
}
 
//定时器回调函数,目的是调用DHCP定时中断函数
void dhcp_timHandler(void)
{
    DHCP_time_handler();
}
 
int main()
{
    /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
    HAL_Init();

    /* Configure the system clock */
    SystemClock_Config();

	uart_init(consoleInfo.uart_x, consoleInfo.baudrate);

    wizchip_initialize();//spi接口初始化,链路初始化检测

	MX_TIM2_Init();
	halTim2_1MS_CallBackRegister(dhcp_timHandler, 1000);

    dhcp_init();
    while(1)
    {
        uint8_t retval = 0;
        uint8_t dhcp_retry = 0;
        if (net_info.dhcp == NETINFO_DHCP)
        {
            retval = DHCP_run();
            if (retval == DHCP_IP_LEASED)
            {
                if (dhcp_get_ip_flag == 0)
                {
                    printf("DHCP success\r\n");
                    dhcp_get_ip_flag = 1;
                }
            }
            else if (retval == DHCP_FAILED)
            {
                dhcp_get_ip_flag = 0;
                dhcp_retry++;
                if (dhcp_retry <= DHCP_RETRY_COUNT)
                {
                    printf(" DHCP timeout occurred and retry %d \r\n", dhcp_retry);
                }
            }
 
            if (dhcp_retry > DHCP_RETRY_COUNT)
            {
                printf(" DHCP failed \r\n");
                DHCP_stop();
                while (true);
            }
        }
    }
}

### RT-Thread与W5500网络驱动的配置方法 #### 1. 环境搭建 在RT-Thread Studio开发环境中,首先需要创建一个基于目标芯片(如STM32F407VET6)的新项目。完成项目的初始配置后,需确认编译无误[^1]。 #### 2. 添加W5500设备驱动 通过RT-Thread Settings工具,进入菜单配置界面,添加支持W5500的相关驱动程序。具体操作包括启用SPI外设以及指定对应的引脚映射关系。例如,在某些开发板上,INT信号可能连接到GPIO93,而RST则连接至GPIO132[^3]。 #### 3. 更新软件包 为了确保使用的组件是最新的版本,建议执行`pkgs --upgrade`命令来升级软件源。之后可以在在线包管理器中搜索并安装由WIZNET官方提供的针对W5500的支持库文件。 #### 4. 启用Socket抽象层(SAL) 在网络应用开发过程中,推荐开启RT-Thread中的Socket抽象层功能。这一步骤通常是在menuconfig图形化界面上完成的。激活SAL选项能够简化应用程序层面对于底层协议栈的操作流程。 #### 5. 配置硬件参数 依据实际电路设计情况调整相应的硬件设定值。比如如果使用的是spi2作为通讯接口,则应在系统初始化阶段对此做出相应安排;同时也要注意中断请求线和复位控制线上所绑定的具体物理管脚编号。 #### 6. 设置IP地址方案 考虑到不同应用场景下的需求差异,默认情况下可能会尝试利用DHCP服务动态获取IP地址。然而当面对像PC机直接相连这种特殊情形时,由于缺乏合适的服务器支持,往往会造成分配失败现象发生。此时可以考虑切换成手动指定固定IP的方式解决该问题。 #### 7. 测试联网能力 最后一步就是验证整个系统的功能性是否正常运作。可以通过简单的Ping测试检验双方之间的可达性状况——即不仅主机应该能回应来自远程节点的数据包探测请求,反过来也应当具备同样的响应机制以证明双向通信链路均已建立良好。 ```python import usocket as socket def create_socket(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) return sock if __name__ == "__main__": s = create_socket() try: s.connect(('www.example.com', 80)) print('Connection successful') except Exception as e: print(f'Error occurred {e}') finally: s.close() ``` 上述代码片段展示了一个基本TCP客户端实例,用于演示如何借助已有的网络环境发起对外部资源访问请求的过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值