对于DHCP的介绍,引用一下百度百科的相关内容:
W5500传输网络数据,通过TCP、UDP来实现,也必须要有IP地址。要么给W5500设置一个固定写死的IP地址,这样的话,在不同的网络环境下,并不通用。有的局域网段的IP地址是 192.168.1.XXX;还有的局域网段IP地址是 10.0.0.XXX等等,写死固定IP地址,显然不太合适。那么DHCP的应用,就可以解决复杂的网络环境的IP地址获取。
DHCP是在UDP的基础上实现的,DHCP有它的协议。关于协议内容,可以参考 《DHCP协议详解》。
STM32 W5500要实现DHCP Client的功能,要具备以下几点:
1、STM32驱动W5500入网,可以PING通,可参考 《STM32F103RC驱动W5500入网,并可ping通》
2、STM32 W5500 UDP发数据测试, 可参考 《STM32 W5500 UDP Client 发送数据》
3、代码实现DHCP报文的封装和解析。
按照DHCP协议实现以下过程,就可以获取到IP地址,网关,子网掩码的信息了:
代码实现 dhcp.c:
#include "w5500.h"
#include "socket.h"
#include "dhcp.h"
#include "utility.h"
#include "w5500_conf.h"
u8 DHCP_SIP[4] = {0,}; /*ÒÑ·¢ÏÖµÄDNS·þÎñÆ÷µØÖ·*/
u8 DHCP_REAL_SIP[4] = {0,};
RIP_MSG* pRIPMSG;
u8 func_pack_discover_info(u8 *buf_dhcp, u16 len_buf, u8 *mac, u32 xid)
{
u16 i = 0;
u8 host_name[24];
pRIPMSG = (RIP_MSG*)buf_dhcp;
memset(buf_dhcp, 0, len_buf);
pRIPMSG->op = DHCP_BOOTREQUEST;
pRIPMSG->htype = DHCP_HTYPE10MB;
pRIPMSG->hlen = DHCP_HLENETHERNET;
pRIPMSG->hops = DHCP_HOPS;
pRIPMSG->xid = htonl(xid);
pRIPMSG->secs = htons(DHCP_SECS);
pRIPMSG->flags =htons(DHCP_FLAGSBROADCAST);
memcpy(pRIPMSG->chaddr, mac, 6);
/* MAGIC_COOKIE */
pRIPMSG->OPT[i++] = (u8)((MAGIC_COOKIE >> 24)& 0xFF);
pRIPMSG->OPT[i++] = (u8)((MAGIC_COOKIE >> 16)& 0xFF);
pRIPMSG->OPT[i++] = (u8)((MAGIC_COOKIE >> 8)& 0xFF);
pRIPMSG->OPT[i++] = (u8)(MAGIC_COOKIE& 0xFF);
/* Option Request Param. */
pRIPMSG->OPT[i++] = dhcpMessageType;
pRIPMSG->OPT[i++] = 0x01;
pRIPMSG->OPT[i++] = DHCP_DISCOVER;
/*Client identifier*/
pRIPMSG->OPT[i++] = dhcpClientIdentifier;
pRIPMSG->OPT[i++] = 0x07;
pRIPMSG->OPT[i++] = 0x01;
pRIPMSG->OPT[i++] = mac[0];
pRIPMSG->OPT[i++] = mac[1];
pRIPMSG->OPT[i++] = mac[2];
pRIPMSG->OPT[i++] = mac[3];
pRIPMSG->OPT[i++] = mac[4];
pRIPMSG->OPT[i++] = mac[5];
// host name
pRIPMSG->OPT[i++] = hostName;
// set the host name
sprintf((char*)host_name, "%s-%02X%02X%02X", DEVICE_ID, mac[3], mac[4], mac[5]);
pRIPMSG->OPT[i++] = (u8)strlen((char*)host_name);
strcpy((char*)(&(pRIPMSG->OPT[i])),(char*)host_name);
i+=(u16)strlen((char*)host_name);
pRIPMSG->OPT[i++] = dhcpParamRequest;
pRIPMSG->OPT[i++] = 0x08;
pRIPMSG->OPT[i++] = subnetMask;
pRIPMSG->OPT[i++] = routersOnSubnet;
pRIPMSG->OPT[i++] = dns;
pRIPMSG->OPT[i++] = domainName;
pRIPMSG->OPT[i++] = performRouterDiscovery;
pRIPMSG->OPT[i++] = staticRoute;
pRIPMSG->OPT[i++] = dhcpT1value;
pRIPMSG->OPT[i++] = dhcpT2value;
pRIPMSG->OPT[i++] = endOption;
return 0;
}
u8 func_pack_request_info(u8 *buf_dhcp, u16 len_buf, u8 *mac, u8 *alloc_ip, u8 *dhcps_ip, u32 xid)
{
u16 i = 0;
u8 host_name[24];
pRIPMSG = (RIP_MSG*)buf_dhcp;
memset(buf_dhcp, 0, len_buf);
pRIPMSG->op = DHCP_BOOTREQUEST;
pRIPMSG->htype = DHCP_HTYPE10MB;
pRIPMSG->hlen = DHCP_HLENETHERNET;
pRIPMSG->hops = DHCP_HOPS;
pRIPMSG->xid = htonl(xid);
pRIPMSG->secs = htons(DHCP_SECS);
pRIPMSG->flags =htons(DHCP_FLAGSBROADCAST);// discover and request broadcast ; else unicast
memcpy(pRIPMSG->chaddr, mac, 6);
/* MAGIC_COOKIE */
pRIPMSG->OPT[i++] = (u8)((MAGIC_COOKIE >> 24) & 0xFF);
pRIPMSG->OPT[i++] = (u8)((MAGIC_COOKIE >> 16) & 0xFF);
pRIPMSG->OPT[i++] = (u8)((MAGIC_COOKIE >> 8) & 0xFF);
pRIPMSG->OPT[i++] = (u8)(MAGIC_COOKIE & 0xFF);
/* Option Request Param. */
pRIPMSG->OPT[i++] = dhcpMessageType;
pRIPMSG->OPT[i++] = 0x01;
pRIPMSG->OPT[i++] = DHCP_REQUEST;
pRIPMSG->OPT[i++] = dhcpClientIdentifier;
pRIPMSG->OPT[i++] = 0x07;
pRIPMSG->OPT[i++] = 0x01;
pRIPMSG->OPT[i++] = mac[0];
pRIPMSG->OPT[i++] = mac[1];
pRIPMSG->OPT[i++] = mac[2];
pRIPMSG->OPT[i++] = mac[3];
pRIPMSG->OPT[i++] = mac[4];
pRIPMSG->OPT[i++] = mac[5];
pRIPMSG->OPT[i++] = dhcpRequestedIPaddr;
pRIPMSG->OPT[i++] = 0x04;
pRIPMSG->OPT[i++] = alloc_ip[0];
pRIPMSG->OPT[i++] = alloc_ip[1];
pRIPMSG->OPT[i++] = alloc_ip[2];
pRIPMSG->OPT[i++] = alloc_ip[3];
pRIPMSG->OPT[i++] = dhcpIPaddrLeaseTime;
pRIPMSG->OPT[i++] = 0x04;
pRIPMSG->OPT[i++] = 0xff;
pRIPMSG->OPT[i++] = 0xff;
pRIPMSG->OPT[i++] = 0xff;
pRIPMSG->OPT[i++] = 0xff;
pRIPMSG->OPT[i++] = dhcpServerIdentifier;
pRIPMSG->OPT[i++] = 0x04;
pRIPMSG->OPT[i++] = dhcps_ip[0];
pRIPMSG->OPT[i++] = dhcps_ip[1];
pRIPMSG->OPT[i++] = dhcps_ip[2];
pRIPMSG->OPT[i++] = dhcps_ip[3];
// host name
pRIPMSG->OPT[i++] = hostName;
//set the host name
sprintf((char*)host_name,(char*)"%s-%02X%02X%02X", DEVICE_ID, mac[3], mac[4], mac[5]);
pRIPMSG->OPT[i++] = (u8)strlen((char*)host_name);
strcpy((char*)&(pRIPMSG->OPT[i]),(char*)host_name);
i+=strlen((char*)host_name);
pRIPMSG->OPT[i++] = dhcpParamRequest;
pRIPMSG->OPT[i++] = 0x08;
pRIPMSG->OPT[i++] = subnetMask;
pRIPMSG->OPT[i++] = routersOnSubnet;
pRIPMSG->OPT[i++] = dns;
pRIPMSG->OPT[i++] = domainName;
pRIPMSG->OPT[i++] = dhcpT1value;
pRIPMSG->OPT[i++] = dhcpT2value;
pRIPMSG->OPT[i++] = performRouterDiscovery;
pRIPMSG->OPT[i++] = staticRoute;
pRIPMSG->OPT[i++] = endOption;
return 0;
}
u8 func_analysis_dhcp_server_msg(u8 *buf_dhcp, u16 len_buf, u16 len_recv, u8 *dhcp_server_ip, DHCP_Get *dhcp_get)
{
u8 * p;
u8 * e;
u8 opt_len = 0;
u8 svr_addr[4];
pRIPMSG = (RIP_MSG*)buf_dhcp;
memcpy(svr_addr, dhcp_server_ip, 4);
if( *((u32*)DHCP_SIP) != 0x00000000 )
{
if( *((u32*)DHCP_REAL_SIP) != *((u32*)svr_addr) &&
*((u32*)DHCP_SIP) != *((u32*)svr_addr) )
{
return 1;
}
}
memcpy(dhcp_get->lip, pRIPMSG->yiaddr, 4);
p = (u8 *)(&pRIPMSG->op);
p = p + 240;
e = p + (len_recv - 240);
while ( p < e )
{
switch ( *p++ )
{
case endOption :
return 0;
case padOption :
break;
case dhcpMessageType :
opt_len = *p++;
break;
case subnetMask :
opt_len =* p++;
memcpy(dhcp_get->sub, p, 4);
break;
case routersOnSubnet :
opt_len = *p++;
memcpy(dhcp_get->gw,p,4);
break;
case dns :
opt_len = *p++;
memcpy(dhcp_get->dns,p,4);
break;
case dhcpIPaddrLeaseTime :
opt_len = *p++;
dhcp_get->leased_time = ntohl(*((u32*)p));
break;
case dhcpServerIdentifier :
opt_len = *p++;
if( *((u32*)DHCP_SIP) == 0 ||
*((u32*)DHCP_REAL_SIP) == *((u32*)svr_addr) ||
*((u32*)DHCP_SIP) == *((u32*)svr_addr) )
{
memcpy(DHCP_SIP, p, 4);
memcpy(DHCP_REAL_SIP, svr_addr, 4); // Copy the real ip address of my DHCP server
}
break;
default :
opt_len = *p++;
break;
}
p+=opt_len;
} // while
return 0;
}
u8 func_dhcp_get_ip_sub_gw(u8 *buf_dhcp, u16 len_buf, u8 sock, u8 *mac, DHCP_Get *dhcp_get, u16 timeout)
{
u8 dhcp_server_ip[] = {255, 255, 255, 255};
u8 recv_ip[4];
u16 dhcp_server_port;
u16 len;
u16 cnt_timeout = 0;
u32 xid = DEFAULT_XID;
if(getSn_SR(sock) == SOCK_CLOSED)
{
socket(sock, Sn_MR_UDP, DHCP_CLIENT_PORT, 0x00);
}
//package DSICOVER message
func_pack_discover_info(buf_dhcp, len_buf, mac, xid);
//send DISCOVER to DHCP server
sendto(sock, buf_dhcp, sizeof(RIP_MSG), dhcp_server_ip, DHCP_SERVER_PORT);
//wait to receive OFFER
for(;;)
{
if(getSn_IR(sock) & Sn_IR_RECV)
{
setSn_IR(sock, Sn_IR_RECV);
}
if ((len = getSn_RX_RSR(sock)) > 0)
{
memset(buf_dhcp, 0, len_buf);
len = recvfrom(sock, buf_dhcp, len_buf, recv_ip, &dhcp_server_port);
if(len > 0 && dhcp_server_port == DHCP_SERVER_PORT)
{
pRIPMSG = (RIP_MSG*)buf_dhcp;
if(pRIPMSG->op == DHCP_BOOTREPLY && memcmp(pRIPMSG->chaddr, mac, 6) == 0 && pRIPMSG->xid == htonl(xid))
{
if(func_analysis_dhcp_server_msg(buf_dhcp, len_buf, len, recv_ip, dhcp_get) == 0)
{
break;
}
}
}
}
cnt_timeout ++;
if(cnt_timeout > timeout)
{
close(sock);
return 1;
}
delay_ms(1);
}
//REQUEST
xid += 1;
func_pack_request_info(buf_dhcp, len_buf, mac, dhcp_get->lip, dhcp_get->gw, xid);
sendto(sock, buf_dhcp, sizeof(RIP_MSG), dhcp_server_ip, DHCP_SERVER_PORT);
cnt_timeout = 0;
//wait to receive ACK
for(;;)
{
if(getSn_IR(sock) & Sn_IR_RECV)
{
setSn_IR(sock, Sn_IR_RECV);
}
if ((len = getSn_RX_RSR(sock)) > 0)
{
memset(buf_dhcp, 0, len_buf);
len = recvfrom(sock, buf_dhcp, len_buf, recv_ip, &dhcp_server_port);
if(len > 0 && dhcp_server_port == DHCP_SERVER_PORT)
{
pRIPMSG = (RIP_MSG*)buf_dhcp;
if(pRIPMSG->op == DHCP_BOOTREPLY && memcmp(pRIPMSG->chaddr, mac, 6) == 0 && pRIPMSG->xid == htonl(xid))
{
if(func_analysis_dhcp_server_msg(buf_dhcp, len_buf, len, recv_ip, dhcp_get) == 0)
{
break;
}
}
}
}
cnt_timeout ++;
if(cnt_timeout > timeout)
{
close(sock);
return 1;
}
delay_ms(1);
}
close(sock);
return 0;
}
dhcp. h代码如下:
#ifndef _DHCP_H_
#define _DHCP_H_
#ifndef __STM32F10X_H
#define __STM32F10X_H
#include "stm32f10x.h"
#endif
#include "stdio.h"
#include "types.h"
#include <string.h>
#define DEVICE_ID "W5500"
typedef struct _DHCP_GET
{
u8 mac[6];
u8 lip[4];
u8 sub[4];
u8 gw[4];
u8 dns[4];
u32 leased_time;
}DHCP_Get;
#define DHCP_RET_NONE 0
#define DHCP_RET_ERR 1
#define DHCP_RET_TIMEOUT 2
#define DHCP_RET_UPDATE 3
#define DHCP_RET_CONFLICT 4
/* DHCP state machine. */
#define STATE_DHCP_READY 0
#define STATE_DHCP_DISCOVER 1
#define STATE_DHCP_REQUEST 2
#define STATE_DHCP_LEASED 3
#define STATE_DHCP_REREQUEST 4
#define STATE_DHCP_RELEASE 5
#define MAX_DHCP_RETRY 3
#define DHCP_WAIT_TIME 5
#define DHCP_FLAGSBROADCAST 0x8000
/* UDP port numbers for DHCP */
#define DHCP_SERVER_PORT 67 /* from server to client */
#define DHCP_CLIENT_PORT 68 /* from client to server */
/* DHCP message OP code */
#define DHCP_BOOTREQUEST 1
#define DHCP_BOOTREPLY 2
/* DHCP message type */
#define DHCP_DISCOVER 1
#define DHCP_OFFER 2
#define DHCP_REQUEST 3
#define DHCP_DECLINE 4
#define DHCP_ACK 5
#define DHCP_NAK 6
#define DHCP_RELEASE 7
#define DHCP_INFORM 8
#define DHCP_HTYPE10MB 1
#define DHCP_HTYPE100MB 2
#define DHCP_HLENETHERNET 6
#define DHCP_HOPS 0
#define DHCP_SECS 0
#define MAGIC_COOKIE 0x63825363
#define DEFAULT_XID 0x12348765
#define DEFAULT_LEASETIME 0xffffffff /* infinite lease time */
/* DHCP option and value (cf. 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
};
typedef struct _RIP_MSG
{
u8 op;
u8 htype;
u8 hlen;
u8 hops;
u32 xid;
u16 secs;
u16 flags;
u8 ciaddr[4];
u8 yiaddr[4];
u8 siaddr[4];
u8 giaddr[4];
u8 chaddr[16];
u8 sname[64];
u8 file[128];
u8 OPT[128];
}RIP_MSG;
u8 func_dhcp_get_ip_sub_gw(u8 *buf_dhcp, u16 len_buf, u8 sock, u8 *mac, DHCP_Get *dhcp_get, u16 timeout);
#endif /* _DHCP_H_ */
值得一提的是,设备名称的命名,通过 #define DEVICE_ID "W5500" 和MAC地址来组设备的名称,在路由器的设备连接管理页面上可以看到:
主函数的测试代码如下:
#ifndef __STM32F10X_H
#define __STM32F10X_H
#include "stm32f10x.h"
#endif
#ifndef __Z_UTIL_TIME_H
#define __Z_UTIL_TIME_H
#include "z_util_time.h"
#endif
#ifndef __Z_HARDWARE_LED_H
#define __Z_HARDWARE_LED_H
#include "z_hardware_led.h"
#endif
#ifndef __Z_HARDWARE_SPI_H
#define __Z_HARDWARE_SPI_H
#include "z_hardware_spi.h"
#endif
#ifndef __Z_HARDWARE_USART2_H
#define __Z_HARDWARE_USART2_H
#include "z_hardware_usart2.h"
#endif
#include "w5500.h"
#include "socket.h"
#include "w5500_conf.h"
#include "dhcp.h"
int main(void)
{
DHCP_Get dhcp_get;
uint8 buffer[1024];
uint8 mac[6]={0, 0, 0, 0, 0, 0};
u16 local_port = 6666;
u16 remote_port = 7777;
u8 remote_ip[4] = {192, 168, 1, 105};
init_led();
init_system_spi();
func_w5500_reset();
init_hardware_usart2_dma(9600);
getMacByLockCode(mac);
setSHAR(mac);
sysinit(txsize, rxsize);
setRTR(2000);
setRCR(3);
func_usart2_dma_send_bytes(buffer, 2);
for(;func_dhcp_get_ip_sub_gw(buffer, sizeof(buffer), 0, mac, &dhcp_get, 500) != 0;);
if(func_dhcp_get_ip_sub_gw(buffer, sizeof(buffer), 0, mac, &dhcp_get, 500) == 0)
{
func_usart2_dma_send_bytes(dhcp_get.lip, sizeof(dhcp_get.lip));
setSUBR(dhcp_get.sub);
setGAR(dhcp_get.gw);
setSIPR(dhcp_get.lip);
}
for(;;)
{
socket(0, Sn_MR_UDP, local_port, 0);
sendto(0, dhcp_get.lip, sizeof(dhcp_get.lip), remote_ip, remote_port);
delay_ms(500);
sendto(0, dhcp_get.sub, sizeof(dhcp_get.sub), remote_ip, remote_port);
delay_ms(500);
sendto(0, dhcp_get.gw, sizeof(dhcp_get.gw), remote_ip, remote_port);
delay_ms(500);
sendto(0, mac, sizeof(mac), remote_ip, remote_port);
delay_ms(500);
func_led1_on();
delay_ms(500);
func_led1_off();
delay_ms(500);
}
}
通过W5500的SOCK 0 以UDP的方式,将IP地址,网关,子网掩码,MAC地址发送出来:
以上。