全部例程

使用DHCP获取动态地址

W55MH32

更新于 2026年9月17日

本篇总结

在 W55MH32 上实现 DHCP 动态获取 IP 地址,成功获取 IP 后即可正常进行网络通信。

🔑 KEIL MDK、UART调试、硬件TOE引擎、DHCP

硬件平台:本例程基于 W55MH32L-EVB 开发板运行。

采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB

本示例演示如何在 W55MH32 上通过 DHCP(动态主机配置协议)自动获取 IP 地址、子网掩码、网关、DNS 等网络配置参数。相比手动配置静态 IP,DHCP 实现了即插即用,设备入网即自动获取网络参数。

前置工具准备

开始本例程前,请确认以下工具已安装:

DHCP 协议简介

DHCP(Dynamic Host Configuration Protocol)即动态主机配置协议,是一个应用层协议。它主要用于在 IP 网络中为客户端自动分配 IP 地址及其他相关网络配置参数,如子网掩码、默认网关、DNS 服务器地址等。这种动态分配的方式大大简化了网络管理员的工作,并且能够更有效地利用有限的 IP 地址资源。

DHCP 的工作原理分为四个阶段(DORA):

阶段方向说明
DiscoverClient → Server(广播)客户端广播寻找 DHCP 服务器
OfferServer → Client服务器分配 IP 并提供网络参数
RequestClient → Server(广播)客户端请求使用该 IP 地址
AckServer → Client服务器确认分配,客户端完成配置

DHCP 报文各主要字段说明:

字段说明
op报文类型,1=Boot Request(客户端请求),2=Boot Reply(服务器响应)
htype硬件类型,Ethernet=0x01
hlen硬件地址长度,值为 6
xid事务 ID,用于匹配请求与响应
ciaddr客户端 IP 地址
yiaddr你的(客户端)IP 地址,服务器分配的 IP
siaddr下一服务器 IP 地址
giaddr中继代理 IP 地址
chaddr客户端 MAC 地址
Magic cookieDHCP 标识,固定值 0x63825363
Option(53)DHCP 消息类型:Discover/Offer/Request/Ack/Nak

Discover 报文实例:客户端通过 UDP 广播的方式发送 DHCP 发现报文,报文中包含了客户端 MAC 地址、主机名和请求的 IP 地址等信息。

Message type: Boot Request (1)            (op code为1,客户端请求报文)
Hardware type: Ethernet (0x01)
Hardware address length: 6
Hops: 0
Transaction ID: 0xbf600589
Seconds elapsed: 0
Bootp flags: 0x0000 (Unicast)
Client IP address: 0.0.0.0
Your (client) IP address: 0.0.0.0
Next server IP address: 0.0.0.0
Relay agent IP address: 0.0.0.0
Client MAC address: HP_b1:37:11 (64:4e:d7:b1:37:11)
Client hardware address padding: 00000000000000000000
Server host name not given
Boot file name not given
Magic cookie: DHCP
Option: (53) DHCP Message Type (Discover)
Option: (61) Client identifier
Option: (50) Requested IP Address (192.168.1.115)
Option: (12) Host Name
Option: (60) Vendor class identifier
Option: (55) Parameter Request List
Option: (255) End
Padding: 0000000000000000

Offer 报文实例:DHCP 服务器收到 Discover 报文后,从 IP 地址池选一个未分配的 IP 地址,将其和子网掩码、默认网关等信息封装进 Offer 报文以广播方式发给客户端。

Message type: Boot Reply (2)              (op code为2,服务器响应报文)
Hardware type: Ethernet (0x01)
Hardware address length: 6
Hops: 0
Transaction ID: 0xbf600589
Seconds elapsed: 0
Bootp flags: 0x0000 (Unicast)
Client IP address: 0.0.0.0
Your (client) IP address: 192.168.1.115
Next server IP address: 0.0.0.0
Relay agent IP address: 0.0.0.0
Client MAC address: HP_b1:37:11 (64:4e:d7:b1:37:11)
Client hardware address padding: 00000000000000000000
Server host name not given
Boot file name not given
Magic cookie: DHCP
Option: (53) DHCP Message Type (Offer)
Option: (54) DHCP Server Identifier (192.168.1.1)
Option: (51) IP Address Lease Time
Option: (6) Domain Name Server
Option: (1) Subnet Mask (255.255.255.0)
Option: (3) Router
Option: (255) End

其他报文信息这里就不一一展示了,感兴趣的朋友可以自行抓取查看。

W55MH32 的优势

W55MH32 SDK 内置了完整的 DHCP 客户端库(dhcp.c/h),DORA 状态机、报文组包、超时重试、IP 租约续租全部由库函数处理。你只需要:注册 1 秒定时器中断 → 设置 NETINFO_DHCP 模式 → 调用 network_init()。DHCP 失败时自动回退到静态 IP 配置。

硬件连接与拓扑

请注意:

PC 和开发板需接入同一路由器(同一网段)。路由器需开启 DHCP 服务(家用路由器默认开启)。

  1. Micro USB 线连接 DEBUG USB 口到 PC(供电 + 调试串口)
  2. 网线连接开发板 RJ45 网口 到路由器 LAN 口
┌─────────────────────────────────┐
│ 路由器(DHCP Server) │
│ 192.168.1.1 │
└────┬──────────────────┬───────────┘
│ │
┌────┴──────┐ ┌────┴──────┐
│ W55MH32 │ │ PC │
│ (DHCP Client) │ (CMD ping) │
└───────────┘ └───────────┘

DEBUG USB ──→ WIZ UartTool
网络拓扑:路由器作为 DHCP Server 分配 IP,开发板获取地址后 PC 可 ping 通

工程结构

工程位于 SDK 内 1.SDK\ModuleDemo\WZTOE\2.DHCP\,核心文件如下:

2.DHCP/
├── Project/
│   └── Project.uvprojx       ← Keil 工程文件
└── USER/
    ├── main.c                ← 硬件初始化 + 网络配置 + DHCP 主流程
    ├── wiz_interface/
    │   ├── wiz_interface.c   ← PHY 检测 / 网络信息打印
    │   └── wiz_interface.h
    ├── dhcp/
    │   ├── dhcp.c            ← DHCP 客户端状态机 (DORA)
    │   └── dhcp.h            ← DHCP 超时/端口/主机名定义
    ├── uart/
    │   ├── bsp_uart.c        ← 调试串口 + printf 重定向
    │   └── bsp_uart.h
    ├── tim/
    │   └── bsp_tim.c/h       ← TIM3 1ms 定时器(供 DHCP 超时)
    └── delay/
        └── delay.c/h          ← SysTick 毫秒延时

本例程相比 Network install 章节的核心新增:DHCP 客户端库的初始化、1 秒定时器中断注册、DHCP 状态机运行以及 IP 租约管理

步骤1:注册 DHCP 定时器中断

文件:USER/main.c — DHCP 协议依赖超时重传机制。TIM3 定时器每 1ms 触发一次,累计到 1000 次(1 秒)后调用 DHCP_time_handler(),为 DHCP 状态机提供秒级时基。

/**
* @brief   1ms timer IRQ Handler
* @param   none
* @return  none
*/
void TIM3_IRQHandler(void)
{
    static uint32_t tim3_1ms_count = 0;
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
    {
        tim3_1ms_count++;
        if (tim3_1ms_count >= 1000)
        {
            DHCP_time_handler();
            tim3_1ms_count = 0;
        }
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
    }
}

dhcp.h 文件中,定义了 DHCP 超时时间、重试次数、端口号和主机名等内容:

/* Retry to processing DHCP */
#define MAX_DHCP_RETRY          2        ///< Maximum retry count
#define DHCP_WAIT_TIME          10       ///< Wait Time 10s

/* UDP port numbers for DHCP */
#define DHCP_SERVER_PORT        67       ///< DHCP server port number
#define DHCP_CLIENT_PORT        68       ///< DHCP client port number

#define MAGIC_COOKIE            0x63825363  ///< You should not modify it number.

#define DCHP_HOST_NAME           "WIZnet\0"

步骤2:启用 DHCP 动态获取

文件:USER/main.c — 将默认网络地址信息结构体中的模式改为 DHCP 模式。

/* network information */
wiz_NetInfo default_net_info = {
    .mac  = {0x00, 0x08, 0xdc, 0x12, 0x22, 0x12},
    .ip   = {192, 168, 1, 30},
    .gw   = {192, 168, 1, 1},
    .sn   = {255, 255, 255, 0},
    .dns  = {8, 8, 8, 8},
    .dhcp = NETINFO_DHCP
};

请注意

使用 DHCP 动态获取 IP 时,必需将网络结构体配置中 dhcp 的值改为 NETINFO_DHCP,这样才能运行 DHCP 模式。

步骤3:DHCP 获取网络地址信息

文件:USER/main.c — 初始化完硬件和 TOE 之后调用 network_init 进行网络地址信息配置。

network_init(ethernet_buf, &default_net_info);

这个函数需要将 DHCP 处理用到的缓存数组以及默认网络地址信息传入,函数具体内容如下:

/**
* @brief   set network information
*
* First determine whether to use DHCP. If DHCP is used, first obtain the
* Internet Protocol Address through DHCP. When DHCP fails, use static IP to
* configure network information. If static IP is used, configure network
* information directly.
*
* @param   ethernet_buff: socket buffer
* @param   net_info: network information struct
* @return  none
*/
void network_init(uint8_t *ethernet_buff, wiz_NetInfo *conf_info)
{
    int ret;
    wizchip_setnetinfo(conf_info); // Configuring Network Information
    if (conf_info->dhcp == NETINFO_DHCP)
    {
        ret = wiz_dhcp_process(0, ethernet_buff);
        if (ret == 0)
        {
            conf_info->dhcp = NETINFO_STATIC;
            wizchip_setnetinfo(conf_info);
        }
    }
    print_network_information();
}

在这个函数中,会先设置一遍网络地址到 W55MH32 中,然后判断模式是否为 DHCP 模式。如果为 DHCP 模式,则使用 wiz_dhcp_process 函数来执行 DHCP 进程,在通过 DHCP 方式成功获取到网络地址后更新到 W55MH32 中,最后将网络地址信息打印出来。wiz_dhcp_process 函数内容如下:

/**
* @brief DHCP process
* @param sn      : socket number
* @param buffer  : socket buffer
*/
static uint8_t wiz_dhcp_process(uint8_t sn, uint8_t *buffer)
{
    wiz_NetInfo conf_info;
    uint8_t     dhcp_run_flag = 1;
    uint8_t     dhcp_ok_flag  = 0;
    /* Registration DHCP_time_handler to 1 second timer */
    DHCP_init(sn, buffer);
    printf("DHCP running\r\n");
    while (1)
    {
        switch (DHCP_run())  // Do the DHCP client
        {
        case DHCP_IP_LEASED: // DHCP Acquiring network information successfully
            if (dhcp_ok_flag == 0)
            {
                dhcp_ok_flag  = 1;
                dhcp_run_flag = 0;
            }
            break;

        case DHCP_FAILED:
            dhcp_run_flag = 0;
            break;
        }
        if (dhcp_run_flag == 0)
        {
            printf("DHCP %s!\r\n", dhcp_ok_flag ? "success" : "fail");
            DHCP_stop();

            if (dhcp_ok_flag)
            {
                getIPfromDHCP(conf_info.ip);
                getGWfromDHCP(conf_info.gw);
                getSNfromDHCP(conf_info.sn);
                getDNSfromDHCP(conf_info.dns);
                conf_info.dhcp = NETINFO_DHCP;
                getSHAR(conf_info.mac);
                wizchip_setnetinfo(&conf_info); // Update network info
                return 1;
            }
            return 0;
        }
    }
}

在该函数体中,首先调用 DHCP_init 初始化 DHCP 配置,然后在主循环中运行 DHCP_run 函数。它负责组包发送 Discover/Request 报文、解析 Offer/Ack 响应以及超时处理。DHCP_run 函数核心状态机如下:

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){
            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) {
        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) {
        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/2) < dhcp_tick_1s) {
        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();
        }
        reset_DHCP_timeout();
        dhcp_state = STATE_DHCP_LEASED;
      } else if (type == DHCP_NAK) {
        reset_DHCP_timeout();
        dhcp_state = STATE_DHCP_DISCOVER;
      } else ret = check_DHCP_timeout();
        break;

    default :
        break;
  }

  return ret;
}

状态机说明

DHCP_run 通过状态机驱动整个流程:STATE_DHCP_INIT 发送 Discover 报文;收到 Offer 后进入 STATE_DHCP_REQUEST 发送 Request;收到 Ack 且 IP 无冲突则进入 STATE_DHCP_LEASED;租约过半时进入 STATE_DHCP_REREQUEST 续租。任一阶段超时均由 check_DHCP_timeout 处理重试。

运行验证

烧录例程运行后,首先进行 PHY 链路检测,然后运行 DHCP 获取网络地址信息并打印。打开 WIZ UartTool V1.0,连接 DEBUG USB 对应 COM 口(115200 / 8N1),预期串口输出如下:

W55MH32 DHCP example
PHY link
The current Mbtis speed : 100Mbps
The current Duplex Mode : Full-Duplex
DHCP running
DHCP success!
====================================================================================================
 W55MH32 network configuration : DHCP

 MAC         : 00:08:DC:00:0E:9A
 IP          : 192.168.1.6
 Subnet Mask : 255.255.255.0
 Gateway     : 192.168.1.1
 DNS         : 192.168.1.1
====================================================================================================

please try ping 192.168.1.6

请注意

测试实例需要 PC 端和 W55MH32 处于同一网段。

接着在 PC 端打开 CMD,PING W55MH32 的 IP 地址,可以正常 PING 通。

CMD ping 192.168.1.6(4 包全通,0% 丢包)+ WIZ UartTool 串口输出(PHY + DHCP + 网络地址 + Ping 提示)— DHCP 动态地址获取测试


常见问题

Q: DHCP 获取 IP 失败后如何处理

在 network_init 函数中,wiz_dhcp_process 返回 0 表示 DHCP 失败。此时会将 conf_info->dhcp 改为 NETINFO_STATIC,并调用 wizchip_setnetinfo 使用结构体中预设的静态 IP 配置网络信息,保证设备仍可联网。MAX_DHCP_RETRY 定义了最大重试次数(默认2次),超过后判定失败。

Q: IP 地址租约到期后会怎样

DHCP_run 的 STATE_DHCP_LEASED 状态会检测租约时间,当租约过半(dhcp_lease_time/2 < dhcp_tick_1s)时自动进入 STATE_DHCP_REREQUEST 状态,发送 DHCP_REQUEST 续租。若服务器回复 Ack 则继续使用原 IP(或更新为新 IP,触发 dhcp_ip_update);若回复 Nak 则回到 STATE_DHCP_DISCOVER 重新获取。

Q: 为什么 DHCP 使用 UDP 广播

DHCP 初始阶段客户端还没有 IP 地址,无法使用 TCP(TCP 需要先建立连接,依赖双方 IP)。因此使用 UDP 广播(目的 IP 255.255.255.255,源 IP 0.0.0.0)来寻找网络中的 DHCP 服务器。DHCP 服务器端口为 67,客户端端口为 68。

Q: 如何在 DHCP 和静态 IP 之间切换

只需修改 wiz_NetInfo 结构体中的 dhcp 字段:设为 NETINFO_DHCP 使用动态获取,设为 NETINFO_STATIC 使用静态 IP。network_init 函数会根据该字段判断是否执行 DHCP 流程。切换后需重新调用 network_init 或 wizchip_setnetinfo 使配置生效。

Q: DHCP 定时器中断的作用是什么

DHCP 协议依赖超时重传机制。TIM3 定时器中断每 1ms 触发一次,累计到 1000 次(即 1 秒)后调用 DHCP_time_handler,为 DHCP 状态机提供秒级时基。该时基用于 Discover/Request 报文的超时检测和 IP 租约计时,缺少该中断将导致 DHCP 无法正常超时重试和续租。

总结

  • 协议定位:DHCP 是应用层协议,用于在 IP 网络中自动分配 IP 地址、子网掩码、网关、DNS 等网络配置参数
  • 四大特点:便捷配置与管控、灵活资源利用、高效维护与排障、适配移动与拓展
  • 工作原理:发现(Discover)→ 提供(Offer)→ 请求(Request)→ 确认(Ack)四阶段完成地址分配
  • 实现步骤:注册 1 秒定时器中断 → 设置 NETINFO_DHCP 模式 → 调用 network_init 和 wiz_dhcp_process 执行 DHCP 流程
  • 失败回退:DHCP 获取失败时自动回退到静态 IP 配置,保证设备基本联网能力
  • 运行验证:烧录后完成链路检测与信息打印,PC 端可正常 PING 通设备

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板

Andy

W5500 技术支持专家