一、前言
上一篇介绍了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、租约
-
发现阶段(DISCOVER) , DHCP客户端启动时,计算机发现本机上没有任何IP地址设定,将以广播方式通过UDP通讯向端口67发送DHCP——DISCOVER请求报文(包含客户端的MAC地址信息),来寻找DHCP服务器,请求IP地址租约,因为客户机还不知道自己属于哪一个网络,所以封包的源地址为0.0.0.0,目的地址为255.255.255.255,向网络发送特定的广播信息。网络上每一台安装了TCP/IP协议的主机都会接收这个广播信息,但只有DHCP服务器才会做出响应。
-
提供阶段(OFFER ),该客户对应的租约已存在且未被再次分配(包括租约到期和未到期的租约),则直接分配已记录的地址信息。该客户对应的租约不存在,服务器响应DHCP——OFFER,会从对应的地址池中查找(注意:该处地址池应该有个前提条件,就是可用的地址池:应当和接收口的IP为同一网段,或同giaddr字段同一网段,否则无法分配);然后从尚未出租的IP地址中挑选一个最前面的ip地址连同其它的参数设定,响应给客户端一个DHCP——OFFER (单播)报文,报文里面包括客户机MAC地址信息,TCP/IP的一些配置信息。
-
选择阶段(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为目标地址进行广播。
-
确认阶段(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、续约
- 在使用租期过去50%时刻处, 客户端向服务器发送单播DHCP REQUEST报文续延租期。
- 如果收到服务器的DHCP ACK报文,则租期相应向前延长,续租成功。如果没有收到DHCP ACK报文,则客户端继续使用这个IP地址。在使用租期过去**87.5%**时刻处,向服务器发送广播DHCP REQUEST报文续延租期。
- 如果收到服务器的DHCP ACK报文,则租期相应向前延长,续租成功。如果没有收到DHCP ACK报文,则客户端继续使用这个IP地址。在使用租期到期时,客户端自动放弃使用这个IP地址,并开始新的DHCP过程。
2.3、协议包
2.3.1、DHCP协议包概述
DHCP报文格式及各个字段含义如下所示:

字段 | 长度(Byte) | 含义 |
op | 1 | 1:客户端传给服务器; 2:服务器传给客户端; |
htype | 1 | 1:表示传输速度10Mb/s的硬件; 2:表示传输速度100Mb/s的硬件; |
hlen | 1 | 表示硬件地址长度,为6 |
hops | 1 | 表示当前的DHCP报文经过的DHCP Relay的数目。该字段由客户端或服务器设置为0,每经过一个DHCP Relay时,该字段加1。此字段的作用是限制DHCP报文所经过的DHCP Relay数目 |
xid | 4 | 表示DHCP客户端选取的随机数,DHCP REQUEST 时随机生成的一段字符串,服务器和客户端用来在它们之间交流请求和响 应,客户端用它对请求和应答进行匹配两个数据包拥有相同的xid说明他们属于同一次会话 |
secs | 2 | 由客户端填充,表示从客户端开始获得IP地址或IP地址续借后所使用了的秒数 |
flags | 2 | 表示标志字段。只有标志字段的最高位才有意义,内容如下所示: 0:客户端请求服务器以单播形式发送响应报文 1:客户端请求服务器以广播形式发送响应报文 |
ciaddr | 4 | 表示客户端的IP地址。可以是服务器分配给客户端的IP地址或者客户端已有的IP地址。客户端在初始化状态时没有IP地址,此字段为0.0.0.0 |
yiaddr | 4 | 表示服务器分配给客户端的IP地址 |
siaddr | 4 | 一般来说是服务器的ip地址 |
giaddr | 4 | 如果需要跨子网进行DHCP地址发放,则在此处填入经过的路由器的ip地址 |
chaddr | 16 | 表示客户端的MAC地址,只占用6字节 |
sname | 64 | 表示客户端获取配置信息的服务器名字 |
file | 128 | 表示客户端需要获取的启动配置文件名 |
OPT | 312 | 表示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);
}
}
}
}