原始程序:
https://zh.arslanbar.net/post.php?t=24571打开lwipopts.h,把#define LWIP_DHCP 0改成#define LWIP_DHCP 1,启用DHCP。
然后修改main.c文件:
#include <stdio.h>
#include <stm32f10x.h>
#include "lwip/etharp.h" // etharp_tmr函数所在的头文件
#include "lwip/init.h" // lwip_init函数所在的头文件
// 与DHCP有关的头文件
#include "lwip/dhcp.h"
#include "lwip/prot/dhcp.h" // DHCP状态常量所在的头文件
#include "lwip/priv/tcp_priv.h" // tcp_tmr函数所在的头文件
#include "netif/ethernet.h" // ethernet_input函数所在头文件
#include "ENC28J60.h"
// 这两个函数位于ethernetif.c中, 但没有头文件声明
err_t ethernetif_init(struct netif *netif);
void ethernetif_input(struct netif *netif);
// 声明httptest.c中的函数
void init_http(void);
// printf串口输出
// 必须要在项目属性里勾选Use MicroLIB后才能使用
int fputc(int ch, FILE *fp)
{
    if (fp == &__stdout)
    {
        if (ch == '\n')
        {
            // 遇到\n自动在前面添加\r
            USART1->DR = '\r';
            while ((USART1->SR & USART_SR_TXE) == 0);
        }
        USART1->DR = ch;
        while ((USART1->SR & USART_SR_TXE) == 0);
    }
    return ch;
}
void show_addr(struct netif *netif)
{
    // 获取网卡的DHCP信息
    //struct dhcp *dhcp = netif->client_data[LWIP_NETIF_CLIENT_DATA_INDEX_DHCP];
    struct dhcp *dhcp = netif_dhcp_data(netif); // 这个宏相当于上面的语句    
    // 显示IP地址
    static uint8_t displayed = 0;
    if (dhcp->state == DHCP_STATE_BOUND)
    {
        // 如果DHCP分配成功就显示IP地址
        if (displayed == 0) // 只显示一次
        {
            printf("DHCP分配成功!\n");
            printf("IP地址: %s\n", ip4addr_ntoa(&dhcp->offered_ip_addr));
            printf("子网掩码: %s\n", ip4addr_ntoa(&dhcp->offered_sn_mask));
            printf("网关: %s\n", ip4addr_ntoa(&dhcp->offered_gw_addr));
            //printf("DHCP状态码: %d\n", dhcp->state);
            displayed = 1;
        }
    }
    else
        displayed = 0;
}
int main(void)
{
    struct ip4_addr ipaddr, netmask, gw;
    struct netif enc28j60;
    uint8_t cnt = 0;    
    // 配置PA口
    RCC->APB2ENR = RCC_APB2ENR_IOPAEN;
    GPIOA->CRH = 0x000004b3; // PA8为开发板上的一个LED灯
    GPIOA->CRL = 0xb4bb0080;
    GPIOA->BSRR = GPIO_BSRR_BS1; // PA1为网卡中断输出    
    // 配置SPI
    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
    SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_BR_1; // 主机模式, 时钟至少需要8分频(BR=010), 也就是72MHz/8=9MHz
    SPI1->CR2 = SPI_CR2_SSOE; // 开CS片选输出    
    // 配置串口
    RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
    USART1->BRR = 0x271; // 波特率: 115200
    USART1->CR1 = USART_CR1_UE | USART_CR1_TE;    
    // 配置定时器
    RCC->APB1ENR = RCC_APB1ENR_TIM6EN;
    TIM6->ARR = 2499; // 共2500个数, 2500*0.1ms=250ms
    TIM6->PSC = 7199; // 72MHz/7200=10kHz -> 0.1ms
    TIM6->CR1 = TIM_CR1_URS; // 防止UG=1时UIF置位
    TIM6->EGR = TIM_EGR_UG; // 应用上述设置
    TIM6->CR1 |= TIM_CR1_CEN; // 开定时器    
    lwip_init();
    ip4_addr_set_zero(&ipaddr); // 暂时将IP地址设为0
    ip4_addr_set_zero(&netmask); // 子网掩码设为0
    ip4_addr_set_zero(&gw); // 网关也要设为0    
    netif_add(&enc28j60, &ipaddr, &netmask, &gw, NULL, ethernetif_init, ethernet_input);
    netif_set_default(&enc28j60); // 设为默认网卡
    netif_set_up(&enc28j60);    
    dhcp_start(&enc28j60); // 启动DHCP
    init_http(); // 初始化HTTP服务
    while (1)
    {
        if (ENC28J60_GetPacketNum() != 0)
        {
            GPIOA->ODR ^= GPIO_ODR_ODR8; // PA8上的LED灯闪烁表明系统正常工作
            ethernetif_input(&enc28j60);
        }        
        show_addr(&enc28j60); // 如果DHCP分配地址成功就显示IP地址        
        // 若定时器溢出
        if (TIM6->SR & TIM_SR_UIF)
        {
            // 250ms
            TIM6->SR &= ~TIM_SR_UIF; // 清除溢出标志
            cnt++;
            if (cnt >= 250)
            {
                cnt = 0; // 250ms*240=1min
                dhcp_coarse_tmr();
            }            
            tcp_tmr(); // TCP定时处理
            if (cnt % 2 == 0)
                dhcp_fine_tmr(); // 250ms*2=500ms
            if (cnt % 4 == 0)
            {
                // 250ms*4=1s
                etharp_tmr(); // ARP定时处理
            }
            if (cnt % 20 == 0)
                GPIOA->ODR ^= GPIO_ODR_ODR8; // LED灯状态切换(250ms*20=5s)
        }
    }
}