Overview
This article describes using lwIP 2.2 to implement DHCP for Ethernet and shares practical experience from the example project.
1. What is lwIP
lwIP stands for "lightweight IP". It is an open source TCP/IP stack commonly used in embedded systems. It is designed to be small and efficient for resource-constrained systems such as embedded devices and IoT devices. lwIP provides implementations of various network protocols including IPv4/IPv6, TCP, UDP, and ICMP, and supports multiple device interfaces and operating systems. By using lwIP, developers can integrate network connectivity into embedded systems without implementing a network stack from scratch.
lwIP 1.4.1 vs lwIP 2.2
| Feature/Area | lwIP 1.4.1 | lwIP 2.2 |
|---|---|---|
| Protocol support | TCP, UDP, IP, ICMP, DHCP, DNS, PPP, etc. | TCP, UDP, IP, ICMP, DHCP, DNS, PPP, etc., including experimental TLS |
| Performance | Basic performance optimizations | Improved performance optimizations |
| Security | Limited security features | Enhanced security features |
| API changes | Stable API | Some API changes and additions |
| Bug fixes | Fixes for known issues | Stability fixes and patches |
| Compatibility | Compatible with existing lwIP 1.x code | Generally compatible with lwIP 1.x but may require adaptations |
| Documentation | Documentation for lwIP 1.4.1 | Updated documentation for lwIP 2.2 |
| Memory usage | Moderate memory usage | Optimized memory usage |
| Multithreading support | Limited or no multithreading support | Improved multithreading support |
| TLS support (security) | Unavailable | Experimental TLS support |
The table summarizes major changes from lwIP 1.4.1 to 2.2. For detailed component support, refer to the lwIP project resources.
2. DHCP Overview
Dynamic Host Configuration Protocol (DHCP) is a network protocol used to automatically assign IP addresses and other network configuration information on an IP network. DHCP simplifies network administration by avoiding manual configuration of each device.
Key DHCP characteristics and operation:
- Automatic IP address assignment: DHCP allows devices to obtain IP addresses automatically without manual configuration, which is useful for large networks.
- Dynamic allocation: DHCP supports dynamic address assignment so devices may receive different IP addresses when reconnecting, helping efficient use of the address pool.
- Other network parameters: DHCP can also provide subnet mask, default gateway, DNS server addresses, and other critical parameters for network communication.
- Lease mechanism: DHCP uses a lease mechanism to manage allocated IP addresses. A device obtains an IP address with a lease that expires after a period; the device can renew or request a new lease.
- DHCP servers: One or more DHCP servers on the network assign IP and configuration information when a device sends a DHCP request.
- DHCP clients: The client on the device sends requests to DHCP servers to obtain IP addresses and configuration. The DHCP process typically starts on device boot.
- Broadcast communication: DHCP communication typically uses broadcast; the client broadcasts a request and servers respond with an offer.
DHCP simplifies network management and is widely used in home, enterprise, and ISP networks.
3. Example: Ethernet DHCP with lwIP 2.2
The example implements the following features:
- Real-time detection of Ethernet link status with status printed to the serial console.
- After development board reset, attempt to obtain an IP address from a DHCP server. If no DHCP server is found within a configured timeout (60 s in this example), the board falls back to a default static IP address.
3.1 Download lwIP 2.2 source
3.2 Add source to project
Unpack the source, copy it into the project directory, and add the required files to the build.
3.3 main function
int main(void){ char LCDDisplayBuf[100] = {0}; struct ip4_addr DestIPaddr; uint8_t flag = 0; USART_Config_T usartConfig; /* User config the different system Clock */ UserRCMClockConfig(); /* Configure SysTick */ ConfigSysTick(); /* Configure USART */ usartConfig.baudRate = 115200; usartConfig.wordLength = USART_WORD_LEN_8B; usartConfig.stopBits = USART_STOP_BIT_1; usartConfig.parity = USART_PARITY_NONE ; usartConfig.mode = USART_MODE_TX_RX; usartConfig.hardwareFlow = USART_HARDWARE_FLOW_NONE; APM_BOARD_COMInit(COM1,&usartConfig); /* Configures LED 2 and LED3 */ APM_BOARD_LEDInit(LED2); APM_BOARD_LEDInit(LED3); /* KEY init*/ APM_BOARD_PBInit(BUTTON_KEY1, BUTTON_MODE_GPIO); APM_BOARD_PBInit(BUTTON_KEY2, BUTTON_MODE_GPIO); printf("This is a ETH TCP Client Demo! "); /* Configure ethernet (GPIOs, clocks, MAC, DMA) */ ConfigEthernet(); /* Initilaize the LwIP stack */ LwIP_Init(); #ifndef USE_DHCP /* Use Com printf static IP address*/ sprintf(LCDDisplayBuf,"TINY board Static IP address "); printf("%s",LCDDisplayBuf); sprintf(LCDDisplayBuf,"IP: %d.%d.%d.%d ", IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3); printf("%s",LCDDisplayBuf); sprintf(LCDDisplayBuf,"NETMASK: %d.%d.%d.%d ", NETMASK_ADDR0, NETMASK_ADDR1, NETMASK_ADDR2, NETMASK_ADDR3); printf("%s",LCDDisplayBuf); sprintf(LCDDisplayBuf,"Gateway: %d.%d.%d.%d ", GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3); printf("%s",LCDDisplayBuf); sprintf(LCDDisplayBuf,"TCP Server IP: %d.%d.%d.%d:%d ", COMP_IP_ADDR0, COMP_IP_ADDR1, COMP_IP_ADDR2, COMP_IP_ADDR3, COMP_PORT); printf("%s",LCDDisplayBuf); #endif while(1) { if ((APM_TINY_PBGetState(BUTTON_KEY1)==0)&&(flag==0)) { APM_TINY_LEDOn(LED2); if (EthLinkStatus == 0) { /* connect to tcp server */ printf(" Connect TCP server "); IP4_ADDR(&DestIPaddr, COMP_IP_ADDR0, COMP_IP_ADDR1, COMP_IP_ADDR2, COMP_IP_ADDR3); tcpc_echo_init(&DestIPaddr,COMP_PORT); flag=1; } } if ((APM_TINY_PBGetState(BUTTON_KEY2)==0)&&(flag==1)) { APM_TINY_LEDOff(LED2); printf(" Disconnect TCP server "); tcpc_echo_disable(); flag=0; } /* check if any packet received */ if (ETH_CheckReceivedFrame()) { /* process received ethernet packet */ LwIP_Pkt_Handle(); } /* handle periodic timers for LwIP */ LwIP_Periodic_Handle(ETHTimer); }}
The main function initializes serial, GPIO, and other peripherals. The example uses a USE_DHCP macro defined in main.h to enable or disable DHCP.
3.4 LwIP_Init()
void LwIP_Init(void){ struct ip4_addr ipaddr; struct ip4_addr netmask; struct ip4_addr gw; /* Initializes the dynamic memory heap */ mem_init(); /* Initializes the memory pools */ memp_init(); #ifdef USE_DHCP ipaddr.addr = 0; netmask.addr = 0; gw.addr = 0; #else IP4_ADDR(&ipaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3); IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1 , NETMASK_ADDR2, NETMASK_ADDR3); IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3); #endif /* Config MAC Address */ ETH_ConfigMACAddress(ETH_MAC_ADDRESS0, SetMACaddr); /* Add a network interface to the list of lwIP netifs */ netif_add(&UserNetif, &ipaddr, &netmask, &gw, NULL, ethernetif_init, ethernet_input); /* Registers the default network interface */ netif_set_default(&UserNetif); if (ETH_ReadPHYRegister(ETH_PHY_ADDRESS, PHY_BSR) & 1) { UserNetif.flags |= NETIF_FLAG_LINK_UP; /* When the netif is fully configured this function must be called */ netif_set_up(&UserNetif); #ifdef USE_DHCP DHCP_state = DHCP_START; #endif } else { netif_set_down(&UserNetif); #ifdef USE_DHCP DHCP_state = DHCP_LINK_DOWN; #endif /* USE_DHCP */ printf("network cable is not connected! "); } netif_set_link_callback(&UserNetif, ETH_link_callback);}
This function initializes the lwIP stack, dynamic heap, and memory pools. It sets IP, netmask, and gateway depending on whether DHCP is enabled, configures the MAC address, adds and registers the network interface, checks PHY link status to set interface state and DHCP state, and sets the link callback to ETH_link_callback.
3.5 ETH_link_callback
void ETH_link_callback(struct netif *netif){ __IO uint32_t timeout = 0; uint16_t RegValue; struct ip4_addr ipaddr; struct ip4_addr netmask; struct ip4_addr gw; if(netif_is_link_up(netif)) { /* Restart the auto-negotiation */ if(ETH_InitStructure.autoNegotiation == ETH_AUTONEGOTIATION_ENABLE) { /* Reset Timeout counter */ timeout = 0; /* Enable auto-negotiation */ ETH_WritePHYRegister(ETH_PHY_ADDRESS, PHY_BCR, PHY_AUTONEGOTIATION); /* Wait until the auto-negotiation will be completed */ do { timeout++; } while (!(ETH_ReadPHYRegister(ETH_PHY_ADDRESS, PHY_BSR) & PHY_AUTONEGO_COMPLETE) && (timeout < (uint32_t)PHY_READ_TIMEOUT)); /* Reset Timeout counter */ timeout = 0; /* Read the result of the auto-negotiation */ RegValue = ETH_ReadPHYRegister(ETH_PHY_ADDRESS, PHY_SR); /* Configure the MAC with the Duplex Mode fixed by the auto-negotiation process */ if((RegValue & PHY_DUPLEX_STATUS) != (uint16_t)RESET) { /* Set Ethernet duplex mode to Full-duplex following the auto-negotiation */ ETH_InitStructure.mode = ETH_MODE_FULLDUPLEX; } else { /* Set Ethernet duplex mode to Half-duplex following the auto-negotiation */ ETH_InitStructure.mode = ETH_MODE_HALFDUPLEX; } /* Configure the MAC with the speed fixed by the auto-negotiation process */ if(RegValue & PHY_SPEED_STATUS) { /* Set Ethernet speed to 10M following the auto-negotiation */ ETH_InitStructure.speed = ETH_SPEED_10M; } else { /* Set Ethernet speed to 100M following the auto-negotiation */ ETH_InitStructure.speed = ETH_SPEED_100M; } /* ETHERNET MACCR Re-Configuration */ /* Set the FES bit according to ETH_Speed value */ /* Set the DM bit according to ETH_Mode value */ ETH->CFG_B.SSEL = ETH_InitStructure.speed; ETH->CFG_B.DM = ETH_InitStructure.mode; Delay(0x00000001); } /* Restart MAC interface */ ETH_Start(); #ifdef USE_DHCP ipaddr.addr = 0; netmask.addr = 0; gw.addr = 0; DHCP_state = DHCP_START; #else IP4_ADDR(&ipaddr, IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3); IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1, NETMASK_ADDR2, NETMASK_ADDR3); IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3); #endif /* USE_DHCP */ netif_set_addr(&UserNetif, &ipaddr , &netmask, &gw); /* When the netif is fully configured this function must be called. */ netif_set_up(&UserNetif); /* Display message on the LCD */ printf("network cable is connected now ! "); EthLinkStatus = 0; } else { ETH_Stop(); #ifdef USE_DHCP DHCP_state = DHCP_LINK_DOWN; dhcp_stop(netif); #endif /* USE_DHCP */ /* When the netif link is down this function must be called. */ netif_set_down(&UserNetif); printf("network cable is unplugged! "); }}
This callback handles Ethernet link state changes. When the link is up, it optionally restarts auto-negotiation, configures MAC speed and duplex, restarts the MAC, resets IP configuration for DHCP or assigns the static address, sets the netif up, and logs the link status. When the link goes down, it stops the MAC, stops DHCP if enabled, sets the netif down, and logs the unplug event.
3.6 LwIP_Periodic_Handle
void LwIP_Periodic_Handle(__IO uint32_t ETHTimer){ static uint8_t flagToggle = 0; #if LWIP_TCP /* TCP periodic process every 250 ms */ if (ETHTimer - TCPTimer >= TCP_TMR_INTERVAL) { TCPTimer = ETHTimer; tcp_tmr(); } #endif /* ARP periodic process every 5s */ if ((ETHTimer - ARPTimer) >= ARP_TMR_INTERVAL) { ARPTimer = ETHTimer; etharp_tmr(); } /* Check link status */ if ((ETHTimer - LinkTimer) >= 1000) { if ((ETH_GET_LINK_STATUS != 0) && (flagToggle == 0)) { /* link goes up */ netif_set_link_up(&UserNetif); flagToggle = 1; } if ((ETH_GET_LINK_STATUS == 0) && (flagToggle == 1)) { EthLinkStatus = 1; /* link goes down */ netif_set_link_down(&UserNetif); flagToggle = 0; } } #ifdef USE_DHCP /* Fine DHCP periodic process every 500ms */ if (ETHTimer - DHCPfineTimer >= DHCP_FINE_TIMER_MSECS) { DHCPfineTimer = ETHTimer; dhcp_fine_tmr(); if ((DHCP_state != DHCP_ADDRESS_ASSIGNED) && (DHCP_state != DHCP_TIMEOUT) && (DHCP_state != DHCP_LINK_DOWN)) { /* toggle LED1 to indicate DHCP on-going process */ APM_TINY_LEDOn(LED2); /* process DHCP state machine */ LwIP_DHCP_Process_Handle(); } } /* DHCP Coarse periodic process every 60s */ if (ETHTimer - DHCPcoarseTimer >= DHCP_COARSE_TIMER_MSECS) { DHCPcoarseTimer = ETHTimer; dhcp_coarse_tmr(); } #endif}
This function handles periodic lwIP tasks: TCP timer, ARP timer, link status checking, and DHCP fine and coarse timers. It updates timers and invokes the corresponding lwIP timer functions. When DHCP is active and not yet assigned, it triggers the DHCP state machine and toggles an LED to indicate activity.
3.7 LwIP_DHCP_Process_Handle
void LwIP_DHCP_Process_Handle(void){ struct ip4_addr ipaddr; struct ip4_addr netmask; struct ip4_addr gw; uint8_t iptab[4] = {0}; char LCDDisplayBuf[100] = {0}; switch (DHCP_state) { case DHCP_START: { DHCP_state = DHCP_WAIT_ADDRESS; dhcp_start(&UserNetif); /* IP address should be set to 0 every time we want to assign a new DHCP address */ IPaddress = 0; printf(" Looking for DHCP server, please wait..... "); } break; case DHCP_WAIT_ADDRESS: { /* Read the new IP address */ struct dhcp *dhcp = netif_dhcp_data(&UserNetif); if (dhcp->offered_ip_addr.addr != 0 && dhcp->offered_sn_mask.addr != 0 && dhcp->offered_gw_addr.addr != 0) { DHCP_state = DHCP_ADDRESS_ASSIGNED; printf("IP address assigned by a DHCP server! "); IPaddress = dhcp->offered_ip_addr.addr; iptab[0] = (uint8_t)(IPaddress >> 24); iptab[1] = (uint8_t)(IPaddress >> 16); iptab[2] = (uint8_t)(IPaddress >> 8); iptab[3] = (uint8_t)(IPaddress); IP4_ADDR(&ipaddr, iptab[3] ,iptab[2] , iptab[1] , iptab[0] ); sprintf(LCDDisplayBuf,"IP: %d.%d.%d.%d ", iptab[3], iptab[2], iptab[1], iptab[0]); printf("%s",LCDDisplayBuf); IPaddress = dhcp->offered_sn_mask.addr; iptab[0] = (uint8_t)(IPaddress >> 24); iptab[1] = (uint8_t)(IPaddress >> 16); iptab[2] = (uint8_t)(IPaddress >> 8); iptab[3] = (uint8_t)(IPaddress); IP4_ADDR(&netmask, iptab[3] ,iptab[2] , iptab[1] , iptab[0] ); sprintf(LCDDisplayBuf,"NETMASK: %d.%d.%d.%d ", iptab[3], iptab[2], iptab[1], iptab[0]); printf("%s",LCDDisplayBuf); IPaddress = dhcp->offered_gw_addr.addr; iptab[0] = (uint8_t)(IPaddress >> 24); iptab[1] = (uint8_t)(IPaddress >> 16); iptab[2] = (uint8_t)(IPaddress >> 8); iptab[3] = (uint8_t)(IPaddress); IP4_ADDR(&gw, iptab[3] ,iptab[2] , iptab[1] , iptab[0]); sprintf(LCDDisplayBuf,"Gateway: %d.%d.%d.%d ", iptab[3], iptab[2], iptab[1], iptab[0]); printf("%s",LCDDisplayBuf); IPaddress = dhcp->server_ip_addr.addr; iptab[0] = (uint8_t)(IPaddress >> 24); iptab[1] = (uint8_t)(IPaddress >> 16); iptab[2] = (uint8_t)(IPaddress >> 8); iptab[3] = (uint8_t)(IPaddress); sprintf(LCDDisplayBuf,"TCP Server IP: %d.%d.%d.%d ", iptab[3], iptab[2], iptab[1], iptab[0]); printf("%s",LCDDisplayBuf); /* Stop DHCP */ dhcp_stop(&UserNetif); /* UserNetif.ip_addr.addr = ipaddr.addr; */ /* UserNetif.netmask.addr = netmask.addr; */ /* UserNetif.gw.addr = gw.addr; */ netif_set_addr(&UserNetif, &ipaddr , &netmask, &gw); APM_TINY_LEDOn(LED2); } else { /* DHCP timeout */ if (dhcp->tries > 4) { DHCP_state = DHCP_TIMEOUT; /* Stop DHCP */ dhcp_stop(&UserNetif); /* Static address used */ /* Use Com printf static IP address*/ printf("DHCP timeout! "); /* Static address used */ IP4_ADDR(&ipaddr, IP_ADDR0 ,IP_ADDR1 , IP_ADDR2 , IP_ADDR3 ); IP4_ADDR(&netmask, NETMASK_ADDR0, NETMASK_ADDR1, NETMASK_ADDR2, NETMASK_ADDR3); IP4_ADDR(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3); netif_set_addr(&UserNetif, &ipaddr , &netmask, &gw); sprintf(LCDDisplayBuf,"TINY board Static IP address "); printf("%s",LCDDisplayBuf); sprintf(LCDDisplayBuf,"IP: %d.%d.%d.%d ", IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3); printf("%s",LCDDisplayBuf); sprintf(LCDDisplayBuf,"NETMASK: %d.%d.%d.%d ", NETMASK_ADDR0, NETMASK_ADDR1, NETMASK_ADDR2, NETMASK_ADDR3); printf("%s",LCDDisplayBuf); sprintf(LCDDisplayBuf,"Gateway: %d.%d.%d.%d ", GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3); printf("%s",LCDDisplayBuf); sprintf(LCDDisplayBuf,"TCP Server IP: %d.%d.%d.%d:%d ", COMP_IP_ADDR0, COMP_IP_ADDR1, COMP_IP_ADDR2, COMP_IP_ADDR3, COMP_PORT); printf("%s",LCDDisplayBuf); APM_TINY_LEDOn(LED2); } } } break; default: break; }}
This DHCP state machine starts DHCP, monitors offers, assigns the offered IP, netmask and gateway, stops DHCP after assignment, and falls back to a static IP if DHCP times out after several attempts.
4. Observed behavior
- Serial console prints status messages during DHCP negotiation and link changes.
- After successfully obtaining an IP, the board can communicate over the network and responds to ping.
ALLPCB
