目录
实验平台
硬件:银杏科技GT7000双核心开发板-ARM-STM32H743XIH6,银杏科技iToolXE仿真器
软件:最新版本STM32CubeH7固件库,STM32CubeMX v6.10.0,开发板环境MDK v5.35,TCP&UDP测试工具,串口工具putty
网盘资料包:链接: https://pan.baidu.com/s/1Y3nwaY4SMxfoUsdqPm2R3w?pwd=inba 提取码: inba
DHCP
DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)是一个局域网的网络协议,通常被应用在大型的局域网络环境中,主要作用是集中的管理、分配 IP 地址,使网络环境中的主机动态的获得 IP 地址、Gateway 地址、DNS 服务器地址等信息,并能够提升地址的使用率。
用于自动分配 IP 地址和其他网络配置信息(如子网掩码、默认网关、DNS 服务器)的协议。DHCP 简化了网络管理,允许设备在加入网络时自动获取必要的配置,而无需手动设置。

工作原理
DHCP 使用客户端-服务器模型,通过广播和单播通信实现 IP 地址的自动分配。
DHCP获得ip地址的4步骤:discover>offer>request>ack(nak)
-
DHCP 发现(DHCP Discover)
当设备(DHCP 客户端)加入网络时,它会发送一个 DHCP 发现广播包,寻找可用的 DHCP 服务器。

客户端广播 DHCP Discover 包,寻找可用的 DHCP 服务器。
局域网中的所有设备都会收到该请求。
-
DHCP 提供(DHCP Offer)
DHCP 服务器收到 DHCP Discover 包后,会从地址池中选择一个可用的 IP 地址,并通过 DHCP Offer 包提供给客户端。

DHCP 服务器发送 DHCP Offer 包,提供可用的 IP 地址和其他配置信息。
DHCP Offer 包可以是广播或单播的。
-
DHCP 请求(DHCP Request)
客户端收到 DHCP Offer 包后,会发送一个 DHCP 请求包,确认接受提供的 IP 地址。

客户端广播 DHCP Request 包,确认接受提供的 IP 地址。
局域网中的所有设备都会收到该请求。
-
DHCP 确认(DHCP Acknowledge)
DHCP 服务器收到 DHCP Request 包后,会发送一个 DHCP 确认包,正式分配 IP 地址和其他配置信息。

DHCP 服务器发送 DHCP Acknowledge 包,正式分配 IP 地址和其他配置信息。
DHCP Acknowledge 包可以是广播或单播的。
DHCP的核心概念
DHCP服务器:负责分配IP地址和其他网络配置的设备。最常见的例子就是我们家中的无线路由器,它内置了DHCP服务器功能。
DHCP客户端:请求并接受IP地址配置的网络设备,如电脑、手机、智能电视等。
IP地址池:DHCP服务器可以分配出去的IP地址范围。例如:192.168.1.100 到 192.168.1.200。
租期:IP地址被分配给客户端后,并非永久占有,而是有一个使用期限。租期到期前,客户端需要向服务器请求续租。这确保了IP地址资源可以被循环利用,不会因为设备离线而永久占用。
DHCP中继:由于DHCP请求最初是以广播形式发送的,而广播包通常无法跨越路由器到达其他子网。DHCP中继代理可以帮助在不同子网之间转发DHCP请求和回复,使得一个DHCP服务器能够为多个物理子网服务。
总结
DHCP是现代TCP/IP网络不可或缺的核心服务之一。它通过自动化的方式管理IP地址分配,极大地简化了网络部署和维护工作,保证了网络的稳定性和可扩展性,是实现"即插即用"网络体验的关键技术。
我们日常生活中,无论是在家中、办公室还是咖啡馆,能够轻松连接Wi-Fi并立即上网,背后都有DHCP在默默工作。
STM32CubeMX生成工程
我们参考前面章节STM32H743-结合CubeMX新建HAL库MDK工程,打开CubeMX软件,重复步骤不再展示。我们来看配置MPU配置、以太网部分和Lwip部分配置如下图所示:
配置以太网。选用RMII(精简的独立于介质接口)模式




MPU配置

LWIP配置



实验代码
1. 主函数
c
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MPU Configuration--------------------------------------------------------*/
MPU_Config();
/* Enable the CPU Cache */
/* Enable I-Cache---------------------------------------------------------*/
SCB_EnableICache();
/* Enable D-Cache---------------------------------------------------------*/
SCB_EnableDCache();
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_LWIP_Init();
MX_USART6_UART_Init();
/* USER CODE BEGIN 2 */
uart6.initialize(115200);
uart6.printf("\x0c");
uart6.printf("GT7000 OK!\r\n");
HAL_GPIO_WritePin(PHYAD0_GPIO_Port,PHYAD0_Pin,GPIO_PIN_RESET);
eth_udp.initialize();
eth_tcps_init();
uart6.printf("initialize OK!\r\n");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
MX_LWIP_Process();
if(uart6.receive_ok_flag == 1)
{
uart6.receive_ok_flag = 0;
memset(buffer,0,20);
memcpy(buffer,uart6.receive_buffer,20);
for(i = 0;i < 20;i ++){
buffer[i] = tolower(buffer[i]);
}
if(memcmp(buffer,"dhcp",strlen("dhcp")) == 0){
lwip_dhcp_task();
}else if(memcpy(buffer,"clear",strlen("clear")) == 0){
uart6.printf("\x0c");
}else{
uart6.printf("\r\nCommand not found!\r\n");
}
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
2.DHCP相关函数
c
#include "eth_udp.h"
#include <string.h>
#include "dhcp.h"
#include "uart6.h"
//--------------------- Function Prototype ----------------------//
void initialize(void);
void receive_data(void *arg,struct udp_pcb *upcb,struct pbuf *p,const ip_addr_t *addr,u16_t port);
void send_data(struct udp_pcb *upcb);
void connection_close(struct udp_pcb *upcb);
void udp_client_send(struct udp_pcb *upcb, char *pData);
void lwip_dhcp_task(void);
//--------------------------- Variable --------------------------//
ETH_UDP_T eth_udp = {
.initialize = initialize,
.receive_data = receive_data,
.send_data = send_data,
.connection_close = connection_close,
.receive_ok_flag = 0
};
__lwip_dev lwipdev;
uint8_t PCIP_ADDRESS[4] = {192, 168, 31, 220};
extern struct netif gnetif;
void initialize(void)
{
err_t err;
struct ip4_addr rmtipaddr;
eth_udp.udppcb = udp_new();//创建一个新的UDP协议控制块
IP4_ADDR(&rmtipaddr ,PCIP_ADDRESS[0] ,PCIP_ADDRESS[1] ,PCIP_ADDRESS[2] ,PCIP_ADDRESS[3]);//设置服务器(PC)IP地址
udp_connect(eth_udp.udppcb ,&rmtipaddr,REMOTE_PORT); //连接至远程客户端
if (eth_udp.udppcb)
{
err = udp_bind(eth_udp.udppcb ,IP_ADDR_ANY ,LOCAL_PORT); //给UDP协议控制块绑定端口号和IP地址
if(err == ERR_OK)
{
udp_recv(eth_udp.udppcb ,eth_udp.receive_data ,NULL); //设置接收回调函数
}
else
{
udp_remove(eth_udp.udppcb);
}
}
}
static void receive_data(void *arg,struct udp_pcb *upcb,struct pbuf *p,const ip_addr_t *addr,u16_t port)
{
uint32_t data_len = 0;
struct pbuf *q;
if(p!=NULL){
memset(eth_udp.receive_buffer,0,EHT_BUFFER_SIZE);
for(q=p;q!=NULL;q=q->next){
if(q->len > (EHT_BUFFER_SIZE-data_len)) memcpy(eth_udp.receive_buffer + data_len,q->payload,(EHT_BUFFER_SIZE - data_len));
else memcpy(eth_udp.receive_buffer+data_len,q->payload,q->len);
data_len += q->len;
if(data_len > EHT_BUFFER_SIZE) break;
}
eth_udp.receive_ok_flag = 1;
pbuf_free(p);
}else{
udp_disconnect(upcb);
}
}
void send_data(struct udp_pcb *upcb)
{
struct pbuf *ptr;
memcpy(eth_udp.send_buffer,eth_udp.receive_buffer,1024);
ptr=pbuf_alloc(PBUF_TRANSPORT,strlen((char*)eth_udp.send_buffer),PBUF_RAM); //申请内存
if(ptr){
pbuf_take(ptr,(char *)eth_udp.send_buffer,strlen((char*)eth_udp.send_buffer)); //拷贝数据
udp_send(upcb,ptr); //发送数据
pbuf_free(ptr); //释放内存
}
}
void connection_close(struct udp_pcb *upcb)
{
udp_disconnect(upcb);
udp_remove(upcb);
}
void lwip_dhcp_task(void)
{
uint32_t ip=0,netmask=0,gw=0;
lwipdev.dhcpstatus=0; //正在DHCP
uart6.printf("正在获取地址...\r\n");
ip=gnetif.ip_addr.addr; //读取新IP地址
netmask=gnetif.netmask.addr;//读取子网掩码
gw=gnetif.gw.addr; //读取默认网关
if(ip!=0) //当正确读取到IP地址的时候
{
//flag = 1;
lwipdev.dhcpstatus=2; //DHCP成功
uart6.printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n",\
lwipdev.mac[0],lwipdev.mac[1],lwipdev.mac[2],lwipdev.mac[3],lwipdev.mac[4],lwipdev.mac[5]);
//解析出通过DHCP获取到的IP地址
lwipdev.ip[3]=(uint8_t)(ip>>24);
lwipdev.ip[2]=(uint8_t)(ip>>16);
lwipdev.ip[1]=(uint8_t)(ip>>8);
lwipdev.ip[0]=(uint8_t)(ip);
uart6.printf("通过DHCP获取到IP地址..............%d.%d.%d.%d\r\n",lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);
//解析通过DHCP获取到的子网掩码地址
lwipdev.netmask[3]=(uint8_t)(netmask>>24);
lwipdev.netmask[2]=(uint8_t)(netmask>>16);
lwipdev.netmask[1]=(uint8_t)(netmask>>8);
lwipdev.netmask[0]=(uint8_t)(netmask);
uart6.printf("通过DHCP获取到子网掩码............%d.%d.%d.%d\r\n",lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]);
//解析出通过DHCP获取到的默认网关
lwipdev.gateway[3]=(uint8_t)(gw>>24);
lwipdev.gateway[2]=(uint8_t)(gw>>16);
lwipdev.gateway[1]=(uint8_t)(gw>>8);
lwipdev.gateway[0]=(uint8_t)(gw);
uart6.printf("通过DHCP获取到的默认网关..........%d.%d.%d.%d\r\n",lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);
}else if(netif_dhcp_data(&gnetif)->tries > LWIP_MAX_DHCP_TRIES) //通过DHCP服务获取IP地址失败,且超过最大尝试次数netif_dhcp_data(gnetif) (netif)->client_data[(id)]
{
lwipdev.dhcpstatus=0XFF;//DHCP失败.
//使用静态IP地址
IP4_ADDR(&(gnetif.ip_addr),lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);
IP4_ADDR(&(gnetif.netmask),lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]);
IP4_ADDR(&(gnetif.gw),lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);
printf("DHCP服务超时,使用静态IP地址!\r\n");
printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n",lwipdev.mac[0],lwipdev.mac[1],lwipdev.mac[2],lwipdev.mac[3],lwipdev.mac[4],lwipdev.mac[5]);
printf("静态IP地址........................%d.%d.%d.%d\r\n",lwipdev.ip[0],lwipdev.ip[1],lwipdev.ip[2],lwipdev.ip[3]);
printf("子网掩码..........................%d.%d.%d.%d\r\n",lwipdev.netmask[0],lwipdev.netmask[1],lwipdev.netmask[2],lwipdev.netmask[3]);
printf("默认网关..........................%d.%d.%d.%d\r\n",lwipdev.gateway[0],lwipdev.gateway[1],lwipdev.gateway[2],lwipdev.gateway[3]);
}
}
实验现象
本实验例程采用的 LWIP 版本位 2.1.2,其中包含了 DHCP 协议,在对 LWIP 的 DHCP功能进行使能和启动后,即可通过路由器对设备进行 IP 地址的分配。
我们需要一根网线将GT7000的网口与路由器的网口连接,然后运行程序在putty串口工具输入"dhcp"进行自动分配IP。如下图所示

按"Win+R" 输入命令"cmd",输入 ping 加获取到的 IP,如:ping 192.168.31.119 等待打印结果。
