Implementing and Applying a Lightweight Network Protocol Stack on STM32

For network communication on resource-constrained microcontrollers, lwIP (lightweight IP) is an ideal choice. lwIP is a lightweight TCP/IP protocol stack designed specifically for embedded systems.

Basics of lwIP

What is a Network Protocol Stack?

Imagine the process of sending a package:

  • Application Layer: The item you want to send (data)
  • Transport Layer: Choosing a courier service (TCP/UDP)
  • Network Layer: Filling in the address (IP address)
  • Link Layer: Actual transportation (Ethernet)

lwIP is like this “courier system” that helps you package, address, and transport data in a standard format.

Architecture of lwIP

The lwIP protocol stack mainly consists of the following modules:

┌─────────────────────────────────────┐
│  Application Layer (HTTP/MQTT/Custom Protocol)      │
├─────────────────────────────────────┤
│  Transport Layer (TCP/UDP)                   │
├─────────────────────────────────────┤
│  Network Layer (IP/ICMP)                   │
├─────────────────────────────────────┤
│  Link Layer (ARP/Ethernet Driver)            │
├─────────────────────────────────────┤
│  Hardware Abstraction Layer (PHY/MAC Driver)           │
└─────────────────────────────────────┘

Description of Core Components:

  1. 1. Memory Management: lwIP manages memory using both memory pools (memp) and heap memory.
  2. 2. Network Interface (netif): Each network interface corresponds to a physical network connection.
  3. 3. Protocol Implementations: TCP, UDP, IP, ICMP, ARP, etc.
  4. 4. Application Interfaces: Three programming interfaces: Raw API, Netconn API, Socket API.

Three Programming Interfaces of lwIP

1. Raw API

  • • Lowest level, highest performance
  • • Requires understanding of protocol details
  • • Suitable for scenarios with extremely high performance requirements

2. Netconn API

  • • Medium level of abstraction
  • • Thread-safe, suitable for multi-threaded environments
  • • Commonly used with STM32 HAL library

3. Socket API

  • • Closest to standard BSD Socket
  • • Easy to port PC-side code
  • • Requires operating system support

Porting lwIP on STM32

Hardware Requirements

To implement network communication on STM32, the following hardware is required:

  1. 1. STM32 Chip: Models that support Ethernet peripherals (e.g., STM32F407, STM32H743, etc.)
  2. 2. PHY Chip: Physical layer chip (e.g., LAN8742A, LAN8720A, etc.)
  3. 3. Network Transformer: RJ45 interface module (usually integrated on the development board)
  4. 4. External Clock: 25MHz crystal oscillator (required by PHY chip)

Software Architecture

The typical architecture for using lwIP on STM32 is as follows:

┌─────────────────────────────────────┐
│  User Application Code                        │
├─────────────────────────────────────┤
│  lwIP Protocol Stack (lwip.c/h)              │
├─────────────────────────────────────┤
│  STM32 HAL Ethernet Driver                │
│  (stm32f4xx_hal_eth.c)              │
├─────────────────────────────────────┤
│  PHY Driver (lan8742.c)                │
├─────────────────────────────────────┤
│  Hardware Layer (MAC + PHY)                 │
└─────────────────────────────────────┘

Key Steps for Porting

Configure lwIP Parameters

Configure key parameters in <span>lwipopts.h</span>:

  • <span>LWIP_TCP</span>: Enable TCP (usually required)
  • <span>LWIP_UDP</span>: Enable UDP
  • <span>TCP_MSS</span>: Maximum segment size for TCP (default 536 bytes)
  • <span>TCP_WND</span>: TCP window size
  • <span>MEM_SIZE</span>: Memory pool size (adjust according to RAM)
  • <span>PBUF_POOL_SIZE</span>: Size of PBUF pool (affects the number of concurrent connections)

Implement Network Interface Initialization

The following functions need to be implemented:

  • <span>low_level_init()</span>: Initialize MAC and PHY
  • <span>low_level_output()</span>: Send data packets
  • <span>ethernetif_input()</span>: Handle received data packets

Configure Clocks and Interrupts

  • • Configure system clock (usually requires above 168MHz)
  • • Configure Ethernet MAC clock
  • • Enable Ethernet receive interrupts

Start lwIP Task

Create lwIP tasks in FreeRTOS and periodically call <span>sys_check_timeouts()</span><span> to handle timeouts.</span>

Basic Network Communication

TCP Server Example

The basic process for implementing a TCP server is as follows:

  1. 1. Create Connection: Use <span>netconn_new()</span><span> to create a TCP connection</span>
  2. 2. Bind Port: Use <span>netconn_bind()</span><span> to bind the local port</span>
  3. 3. Listen for Connections: Use <span>netconn_listen()</span><span> to start listening</span>
  4. 4. Accept Connection: Use <span>netconn_accept()</span><span> to accept client connections</span>
  5. 5. Send and Receive Data: Use <span>netconn_recv()</span><span> and </span><code><span>netconn_write()</span><span> to transmit data</span>
  6. 6. Close Connection: Use <span>netconn_close()</span><span> to close the connection</span>

Key Points:

  • • TCP is connection-oriented and requires establishing a connection first
  • • The server needs to continuously listen, usually placed in a separate task
  • • It is recommended to create a separate task for each client connection

UDP Communication Example

UDP communication is simpler:

  1. 1. Create Connection: Create a UDP type netconn
  2. 2. Bind Port: Bind the local port
  3. 3. Send Data: Use <span>netconn_sendto()</span><span> to send to a specified address</span>
  4. 4. Receive Data: Use <span>netconn_recvfrom()</span><span> to receive data</span>

Key Points:

  • • UDP is connectionless and does not require establishing a connection
  • • Suitable for scenarios with high real-time requirements and allowed packet loss
  • • The application layer needs to handle packet loss and out-of-order delivery

DHCP Automatic IP Acquisition

The steps to automatically acquire an IP address using DHCP are:

  1. 1. Enable <span>LWIP_DHCP</span> in <span>lwipopts.h</span>
  2. 2. Call <span>dhcp_start()</span><span> after initializing the network interface</span>
  3. 3. Periodically call <span>dhcp_fine_tmr()</span> and <span>dhcp_coarse_tmr()</span>
  4. 4. Check the status of <span>netif_is_up()</span> and <span>netif_is_link_up()</span>

Advantages: No manual IP configuration is required, and the device can automatically connect to the network.

Troubleshooting

Network Cannot Connect, Ping Fails

Possible Causes and Solutions:

  1. 1. PHY Chip Not Initialized
  • • Check the reset and configuration of the PHY chip
  • • Confirm if the PHY address is correct (usually 0 or 1)
  • • Use an oscilloscope to check if the 25MHz clock is normal
  • 2. MAC Address Not Set
    • • Ensure a unique MAC address is set during initialization
    • • MAC address format: 6 bytes, e.g., <span>{0x02, 0x00, 0x00, 0x00, 0x00, 0x01}</span>
  • 3. Network Interface Not Started
    • • Check if <span>netif_add()</span> was successful
    • • Confirm that <span>netif_set_up()</span> and <span>netif_set_link_up()</span> have been called
    • • Check the status bits of <span>netif->flags</span>
  • 4. Incorrect IP Address Configuration
    • • Static IP: Check if IP, subnet mask, and gateway are correct
    • • DHCP: Confirm if the DHCP server is reachable, check the return value of <span>dhcp_supplied_address()</span>

    Debugging Tips:

    • • Use <span>netif_list</span> to traverse all network interfaces and check their status
    • • Print <span>netif->ip_addr</span> to view the current IP address
    • • Use Wireshark to capture and analyze network traffic

    TCP Connection Frequently Drops

    Possible Causes and Solutions:

    1. 1. Insufficient Memory
    • • Increase <span>MEM_SIZE</span> and <span>PBUF_POOL_SIZE</span>
    • • Check for memory leaks (unreleased netbuf)
    • • Use <span>mem_malloc()</span> and <span>mem_free()</span><span> to track memory usage</span>
  • 2. Timeout Settings Too Short
    • • Adjust <span>TCP_MSL</span> (maximum segment lifetime)
    • • Increase <span>TCP_KEEPALIVE</span> related parameters
    • • Check the application layer heartbeat mechanism
  • 3. Receive Buffer Overflow
    • • Increase <span>TCP_WND</span> (receive window)
    • • Call <span>netconn_recv()</span> promptly to read data
    • • Use <span>netconn_set_recvtimeout()</span> to set receive timeout
  • 4. Network Congestion
    • • Check network quality (packet loss rate, latency)
    • • Adjust <span>TCP_MSS</span> to reduce packet size
    • • Implement application layer retransmission mechanism

    Debugging Tips:

    • • Enable <span>LWIP_STATS</span> to track TCP connection status
    • • Use <span>tcp_debug_print_pcbs()</span> to print all TCP connection information
    • • Monitor the length of <span>tcp_active_pcbs</span> linked list

    Slow Data Transfer Speed

    Possible Causes and Solutions:

    1. 1. TCP Window Too Small
    • • Increase <span>TCP_WND</span> (recommended to be 4-8 times TCP_MSS)
    • • Enable <span>TCP_WND_UPDATE_THRESHOLD</span> to optimize window updates
  • 2. Insufficient Send Buffer
    • • Increase <span>TCP_SND_BUF</span> (send buffer size)
    • • Check the write size limit of <span>netconn_write()</span>
  • 3. Task Priority Too Low
    • • Increase the priority of the lwIP task
    • • Ensure the Ethernet interrupt priority is high enough
    • • Avoid performing time-consuming operations in interrupts
  • 4. Improper PHY Configuration
    • • Check if full-duplex mode is enabled
    • • Confirm PHY working rate (10M/100M)
    • • Check if auto-negotiation was successful

    Performance Optimization Suggestions:

    • • Use Raw API instead of Netconn API (20-30% performance improvement)
    • • Enable <span>LWIP_TCP_FAST_OPEN</span> (if supported)
    • • Batch send data to reduce system call frequency

    DHCP IP Acquisition Failure

    Possible Causes and Solutions:

    1. 1. DHCP Server Unreachable
    • • Confirm that the device and router are on the same subnet (initial state)
    • • Check cable connections and network interface status
    • • Test network connectivity using a static IP
  • 2. DHCP Timeout Setting Too Short
    • • Increase <span>DHCP_TIMEOUT</span> and <span>DHCP_T1</span>, <span>DHCP_T2</span>
    • • Check the calling frequency of <span>dhcp_fine_tmr()</span>
  • 3. Insufficient Memory
    • • DHCP requires additional memory space
    • • Ensure <span>MEM_SIZE</span> is large enough
  • 4. Multiple Network Card Conflicts
    • • Ensure DHCP is enabled on only one network interface
    • • Check the number of interfaces in <span>netif_list</span>

    Debugging Tips:

    • • Enable <span>LWIP_DEBUG</span> and <span>DHCP_DEBUG</span>
    • • Print the DHCP state machine status (<span>dhcp->state</span>)
    • • Use Wireshark to capture and view the DHCP interaction process

    Memory Leak Causing System Crash

    Possible Causes and Solutions:

    1. 1. netbuf Not Released
    • • Must call <span>netbuf_delete()</span> after each <span>netconn_recv()</span>
    • • Use the return value of <span>netconn_recv()</span> to check for data
  • 2. netconn Not Closed
    • • Must call <span>netconn_delete()</span> after the connection is closed
    • • Close the connection after the server has processed the client
  • 3. PBUF Leak
    • • Check for unreleased PBUF in custom code
    • • Use <span>pbuf_free()</span> to release PBUF
  • 4. Memory Pool Exhaustion
    • • Monitor <span>MEMP_STATS</span> statistics
    • • Increase the corresponding memory pool size

    Preventive Measures:

    • • Regularly check using memory statistics features
    • • Implement memory usage monitoring and alerts
    • • Focus on resource release during code reviews

    Race Conditions in Multi-threaded Environments

    Possible Causes and Solutions:

    1. 1. lwIP Core Functions Not Thread-Safe
    • • Use <span>sys_mutex</span> to protect critical sections
    • • Alternatively, use <span>tcpip_callback()</span> to submit operations to the lwIP thread
  • 2. Network Interface Operation Conflicts
    • • Avoid operating the network interface simultaneously in interrupts and tasks
    • • Use message queues to pass network events
  • 3. Shared Data Structure Access
    • • Use mutexes to protect shared netconn structures
    • • Avoid performing time-consuming operations in callback functions

    Solutions:

    • • All lwIP API calls should be made within the lwIP task
    • • Use <span>tcpip_callback()</span><span> to safely call lwIP functions from other threads</span>
    • • Notify the application layer of network events through message queues

    Debugging Tips and Tools

    Using Wireshark for Packet Analysis

    Steps:

    1. 1. Install Wireshark on your PC
    2. 2. Connect the STM32 device to the same network as the PC
    3. 3. Select the network interface in Wireshark to start capturing packets
    4. 4. Filter specific IPs or protocols for analysis

    Common Filter Conditions:

    • <span>ip.addr == 192.168.1.100</span>: Filter specific IP
    • <span>tcp.port == 80</span>: Filter TCP port 80
    • <span>dhcp</span>: Show only DHCP packets

    Enable lwIP Debug Information

    Enable debugging in <span>lwipopts.h</span>:

    #define LWIP_DEBUG 1
    #define TCP_DEBUG LWIP_DBG_ON
    #define NETIF_DEBUG LWIP_DBG_ON
    #define DHCP_DEBUG LWIP_DBG_ON

    Note: Debug information will significantly increase code size and runtime; it should be disabled in release versions.

    Using Ping to Test Connectivity

    Basic Usage:

    • <span>ping 192.168.1.100</span>: Test if the device is reachable
    • <span>ping -t 192.168.1.100</span>: Continuous ping (Windows)
    • <span>ping -c 10 192.168.1.100</span>: Send 10 packets (Linux)

    Analysis Metrics:

    • • Response time: Should normally be less than 10ms (LAN)
    • • Packet loss rate: Should be 0%
    • • TTL value: Check the number of routing hops

    Memory Usage Monitoring

    After enabling statistics features, you can print memory usage:

    // Print memory statistics
    printf("MEM: used=%d, max=%d, err=%d\n", 
           lwip_stats.mem.used, 
           lwip_stats.mem.max, 
           lwip_stats.mem.err);
    
    // Print PBUF statistics
    printf("PBUF: used=%d, max=%d\n", 
           lwip_stats.pbuf.used, 
           lwip_stats.pbuf.max);

    Leave a Comment