cancel
Showing results for 
Search instead for 
Did you mean: 

How to use the LwIP Ethernet middleware on the STM32H5 series

B.Montanari
ST Employee

Summary

This article provides a step-by-step guide on how to use the LwIP with the STM32H5 series. A simple DHCP connection is made using the NUCLEO-H563ZI board, outlining the process from configuring the basic hardware connections up to the firmware implementation, leading to a successful connection.

Table of contents

 

 

 

Introduction

Hello and welcome! This article presents a tutorial on manually importing and using the LwIP Ethernet middleware with the STM32H5 microcontroller series without relying on the STM32CubeMX.

The current STM32CubeMX release does not support the LwIP to be automatically added for the STM32H5 series, so there is no code generation feature inside the software. Integrating the LwIP middleware into any project can be challenging. The good news is that there is a ported version for this series available in ST’s GitHub and this package is used in this article. The page can be accessed here: GitHub - STMicroelectronics - Middleware LwIP

This tutorial is based on the STM32CubeIDE 1.15, with the STM32CubeH5 HAL driver version 1.2.0 and was validated with the NUCLEO-H563ZI, which embeds an STM32H563ZIT6 MCU.

Keep in mind that the application made for this article is a fairly simple demonstration of a basic DHCP connection. Any extra protocols require modifications to the code as well. Consider checking the examples provided for the STM32H7, as they can be tailored to the H5 once the LwIP is added into your design.

For a detailed explanation regarding the LwIP middleware, refer to UM1713 and LwIP’s own documentation.

 

1. Development

1.1. Project configuration

Start by creating a new project using the STM32CubeIDE by clicking [File->New->STM32 Project]. Use the [Board Selector] tab and select the [NUCLEO-H563ZI]. In the board project options that pop up, select only the [Virtual Com Port] and press [OK]. The LEDs are used, but the names given are changed to facilitate the color identification.

Figure 1 - Board Support ConfigurationFigure 1 - Board Support Configuration

Once the project creation is done, enable the [ETH] peripheral in RMII mode, located in the [Connectivity] section.

Figure 2 - Ethernet settingsFigure 2 - Ethernet settings

Next, configure the [ICACHE] in 1-way mode

Figure 3 - ICACHE settingsFigure 3 - ICACHE settings

Rename the following pins accordingly, using the user label feature to have the LED’s colors reflected properly:

  • PF4 – LED_YELLOW
  • PG4 – LED_RED
  • PB0 – LED_GREEN

The last configuration before generating the code snippet is not to generate the MX_ETH_Init function call and make it not static. This is done in the [Project Manager] tab, in the [Advanced Settings] section:

Figure 4 - Project Settings CustomizationFigure 4 - Project Settings Customization

Generate the code by clicking on the highlighted icon, or just pressing the [Alt + K] shortcut.

Figure 5 - Code GenerationFigure 5 - Code Generation 

1.2. Project tree configuration

Start by downloading the stm32h5-classic-coremw package available on the GitHub link here, or at the beginning of the article. Make sure to perform a Git Clone, otherwise the required files are not downloaded.

Unzip the cloned file and move to the following folder ..\stm32h5-classic-coremw-apps\Middlewares\Third_Party\LwIP. [Ctrl + left click] to select both the “src” and “system” folders, then drag and drop it to your newly created project, as illustrated below:

Figure 6 - Add LwIP step 1Figure 6 - Add LwIP step 1

Select [Copy files and folders] in the pop-up tab and click [OK]

Figure 7 - Add LwIP step 2Figure 7 - Add LwIP step 2

Next up we need to change these regular folders into source folders. In order to do that, [right-click] the project and navigate the menu [> new > source folder], and create two source folders with the same exact name of the folders you just imported. If done correctly, a small C letter will be added to the folders, as shown below:

Figure 8 - Add LwIP step 3Figure 8 - Add LwIP step 3

As none of the Ethernet protocols are used for this simple demonstration, we should delete the ‘src/apps’ folder before proceeding.

Create a new source folder called ‘lwip’, and inside it create normal folders named ‘app’ and ‘target’. These are going to be the user code folders for the middleware, maintaining a similar folder structure provided in our other series for the LwIP.

Figure 9 - Add LwIP step 4Figure 9 - Add LwIP step 4

The application files must be imported from the stm32h5-classic-coremw package. Following this path ..\stm32h5-classic-coremw-apps-main\Projects\NUCLEO-H563ZI\Applications\LwIP\LwIP_TCP_Echo_Server\LWIP\Target, import all the ‘target’ folder files to the ‘target’ folder in your project. In the “app” folder, create lwip.c and lwip.h files.

Figure 10 - Add LwIP step 5Figure 10 - Add LwIP step 5

Finally, from ..\stm32h5-classic-coremw-apps-main\Drivers\BSP\Components\lan8742 path, import both lan8742.c and lan8742.h to the “BSP” folder.

Figure 11 - Add LwIP step 6Figure 11 - Add LwIP step 6

The next step is to add all header files to the include path. Select the highlighted folders using [Ctrl + left click], then [right click] any of them and select “add/remove from include path”. Press [OK] on the pop-up tab.

Figure 12 - Add LwIP step 7Figure 12 - Add LwIP step 7

1.3. Coding

With all file structure ready, we must start modifying the code to create a base application that is able to connect to a DHCP server and be pinged. The files are provided as attachments to this article, but the detailed changes are explained below:

1.3.1. Ethernetif.c

Start by changing the path to include the phy header file on line 26 and include main.h afterwards: 

#include "lan8742.h"
#include "main.h"

A few structures must be declared as extern:

extern ETH_DMADescTypeDef  DMARxDscrTab[ETH_RX_DESC_CNT]; /* Ethernet Rx DMA Descriptors */
extern ETH_DMADescTypeDef  DMATxDscrTab[ETH_TX_DESC_CNT]; /* Ethernet Tx DMA Descriptors */
/* Global Ethernet handle*/
extern ETH_HandleTypeDef heth;
extern ETH_TxPacketConfig TxConfig;

           

The next step is to change lines 120 to 130 to make sure we use our MX generated function to start the ETH peripheral

      MX_ETH_Init();
      heth.Instance = ETH;

 Change the “set MAC hardware address” section to extract the address from the eth handler.

 /* set MAC hardware address */
  netif->hwaddr[0] =  heth.Init.MACAddr[0];
  netif->hwaddr[1] =  heth.Init.MACAddr[1];
  netif->hwaddr[2] =  heth.Init.MACAddr[2];
  netif->hwaddr[3] =  heth.Init.MACAddr[3];
  netif->hwaddr[4] =  heth.Init.MACAddr[4];
  netif->hwaddr[5] =  heth.Init.MACAddr[5];

Move to line 146 and delete the following lines:

 /* Set Tx packet config common parameters */
  memset(&TxConfig, 0 , sizeof(ETH_TxPacketConfig));
  TxConfig.Attributes = ETH_TX_PACKETS_FEATURES_CSUM | ETH_TX_PACKETS_FEATURES_CRCPAD;
  TxConfig.ChecksumCtrl = ETH_CHECKSUM_IPHDR_PAYLOAD_INSERT_PHDR_CALC;
  TxConfig.CRCPadCtrl = ETH_CRC_PAD_INSERT;

          

To make sure that the ETH handler is the same as the MX generated one, press [Ctrl + F]. Copy &EthHandle into the “find” slot, and write &heth into the “replace with” slot, then press “replace all”. Alternatively, you could create a #define to make the naming equivalent to the generated default.

On line 330, delete the ETH_MSP routine section, as it has already been created by Cube MX in another file.        

1.3.2. main.c

In the main file, start by adding the following includes to the appropriate user code section:

/* USER CODE BEGIN Includes */
#include "lwip.h"
#include "ethernetif.h"
#include "lwip/opt.h"
#include "lwip/init.h"
#include "netif/etharp.h"
#include "lwip/netif.h"
#include "lwip/timeouts.h"
#if LWIP_DHCP
#include "lwip/dhcp.h"
#endif

/* USER CODE END Includes */

Declare the net interface structure as an extern private variable:

/* USER CODE BEGIN PV */
extern struct netif gnetif;
/* USER CODE END PV */

Create a prototype for the netif configuration function:

/* USER CODE BEGIN PFP */
static void Netif_Config(void);
/* USER CODE END PFP */

Move on to the USER CODE 2 section and add call the middleware initialization function, followed by the network interface configuration function.

      /* USER CODE BEGIN 2 */
      lwip_init();
      Netif_Config();
      /* USER CODE END 2 */

 The while loop contains all functions that must run constantly as a process.

      /* USER CODE BEGIN WHILE */
      while (1) {
             /* Read a received packet from the Ethernet buffers and send it
              to the lwIP for handling */
             ethernetif_input(&gnetif);
             /* Handle timeouts */
             sys_check_timeouts();
#if LWIP_NETIF_LINK_CALLBACK
             Ethernet_Link_Periodic_Handle(&gnetif);
#endif
#if LWIP_DHCP
             DHCP_Periodic_Handle(&gnetif);
#endif
             /* USER CODE END WHILE */

 Move on to the USER CODE 4 section and add the network interface configuration function:         

/* USER CODE BEGIN 4 */
/**
 * @brief  Setup the network interface
 *   None
 * @retval None
 */
static void Netif_Config(void) {
      ip_addr_t ipaddr;
      ip_addr_t netmask;
      ip_addr_t gw;
#if LWIP_DHCP
      ip_addr_set_zero_ip4(&ipaddr);
      ip_addr_set_zero_ip4(&netmask);
      ip_addr_set_zero_ip4(&gw);
#else
  /* IP address default setting */
  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
      /* add the network interface */
      netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init,
                   &ethernet_input);
      /*  Registers the default network interface */
      netif_set_default(&gnetif);
#if LWIP_NETIF_LINK_CALLBACK
      netif_set_link_callback(&gnetif, ethernet_link_status_updated);
      dhcp_start(&gnetif);
      ethernet_link_status_updated(&gnetif);
#endif
}
/* USER CODE END 4 */

1.3.3. lwip.h

In this file, we add the static ip address configuration as well as some function prototypes

#include "lwip/netif.h"
/*Static IP ADDRESS: IP_ADDR0.IP_ADDR1.IP_ADDR2.IP_ADDR3 */
#define IP_ADDR0   ((uint8_t) 198U)
#define IP_ADDR1   ((uint8_t) 162U)
#define IP_ADDR2   ((uint8_t) 0U)
#define IP_ADDR3   ((uint8_t) 10U)
/*NETMASK*/
#define NETMASK_ADDR0   ((uint8_t) 255U)
#define NETMASK_ADDR1   ((uint8_t) 255U)
#define NETMASK_ADDR2   ((uint8_t) 255U)
#define NETMASK_ADDR3   ((uint8_t) 0U)
/*Gateway Address*/
#define GW_ADDR0   ((uint8_t) 198U)
#define GW_ADDR1   ((uint8_t) 162U)
#define GW_ADDR2   ((uint8_t) 0U)
#define GW_ADDR3   ((uint8_t) 1U)
void ethernet_link_status_updated(struct netif *netif);
void Ethernet_Link_Periodic_Handle(struct netif *netif);
#if LWIP_DHCP
void DHCP_Process(struct netif *netif);
void DHCP_Periodic_Handle(struct netif *netif);
#endif

1.3.4. lwip.c

This file contains our application functions, such as DHCP handling and connection callbacks. Start by including the following header files:

/* Includes ------------------------------------------------------------------*/
#include "lwip.h"
#include "lwip/init.h"
#include "lwip/netif.h"
#if defined ( __CC_ARM )  /* MDK ARM Compiler */
#include "lwip/sio.h"
#endif /* MDK ARM Compiler */
#include "ethernetif.h"
#include "lwip/debug.h"
#include "lwip/stats.h"
#include "lwip/tcp.h"
#include "lwip/dhcp.h"
#include <string.h>
#include "main.h"

Then create the netif handler, the eth link timer, DHCP state defines, and variables:

struct netif gnetif;
uint32_t EthernetLinkTimer;
#if LWIP_DHCP
#define DHCP_OFF                   (uint8_t) 0
#define DHCP_START                 (uint8_t) 1
#define DHCP_WAIT_ADDRESS          (uint8_t) 2
#define DHCP_ADDRESS_ASSIGNED      (uint8_t) 3
#define DHCP_TIMEOUT               (uint8_t) 4
#define DHCP_LINK_DOWN             (uint8_t) 5
#define MAX_DHCP_TRIES  4
uint32_t DHCPfineTimer = 0;
uint8_t DHCP_state = DHCP_OFF;
#endif

Create the link update callback:

void ethernet_link_status_updated(struct netif *netif)
{
  if (netif_is_link_up(netif))
 {
    /* Update DHCP state machine */
    DHCP_state = DHCP_START;
  }
  else
  {
    /* Update DHCP state machine */
    DHCP_state = DHCP_LINK_DOWN;
  }
}

 Followed by the link periodic handle:

#if LWIP_NETIF_LINK_CALLBACK
/**
  * @brief  Ethernet Link periodic check
  *   netif
  * @retval None
  */
void Ethernet_Link_Periodic_Handle(struct netif *netif)
{
  /* Ethernet Link every 100ms */
  if (HAL_GetTick() - EthernetLinkTimer >= 100)
  {
    EthernetLinkTimer = HAL_GetTick();
    ethernet_link_check_state(netif);
  }
}
#endif

The next addition is the DHCP process. It checks DHCP state machine and handle the ip addressing, as well as print it out once it is acquired:

#if LWIP_DHCP
/**
  * @brief  DHCP_Process_Handle
  *   None
  * @retval None
  */
void DHCP_Process(struct netif *netif)
{
  ip_addr_t ipaddr;
  ip_addr_t netmask;
  ip_addr_t gw;
  struct dhcp *dhcp;
  switch (DHCP_state)
  {
    case DHCP_START:
    {
             printf("State: Looking for DHCP server ...\n");
             HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_RESET);
             HAL_GPIO_WritePin(LED_YELLOW_GPIO_Port, LED_YELLOW_Pin, GPIO_PIN_RESET);
      ip_addr_set_zero_ip4(&netif->ip_addr);
      ip_addr_set_zero_ip4(&netif->netmask);
      ip_addr_set_zero_ip4(&netif->gw);
      dhcp_start(netif);
      DHCP_state = DHCP_WAIT_ADDRESS;
    }
    break;
  case DHCP_WAIT_ADDRESS:
    {
      if (dhcp_supplied_address(netif))
      {
        DHCP_state = DHCP_ADDRESS_ASSIGNED;
             HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_SET);
             HAL_GPIO_WritePin(LED_YELLOW_GPIO_Port, LED_YELLOW_Pin, GPIO_PIN_RESET);
        char ip_str[16];
        ip4_addr_set_u32(&ipaddr, netif_ip4_addr(netif)->addr);
        ip4addr_ntoa_r(&ipaddr, ip_str, sizeof(ip_str));
        printf("IPv4 address: %s\n", ip_str);
      }
      else
      {
        dhcp = (struct dhcp *)netif_get_client_data(netif, LWIP_NETIF_CLIENT_DATA_INDEX_DHCP);
        /* DHCP timeout */
        if (dhcp->tries > MAX_DHCP_TRIES)
        {
          DHCP_state = DHCP_TIMEOUT;
          /* Static address used */
          IP_ADDR4(&ipaddr, IP_ADDR0 ,IP_ADDR1 , IP_ADDR2 , IP_ADDR3 );
          IP_ADDR4(&netmask, NETMASK_ADDR0, NETMASK_ADDR1, NETMASK_ADDR2, NETMASK_ADDR3);
          IP_ADDR4(&gw, GW_ADDR0, GW_ADDR1, GW_ADDR2, GW_ADDR3);
          netif_set_addr(netif, &ipaddr, &netmask, &gw);
          char ip_str[16];
          ip4addr_ntoa_r(&ipaddr, ip_str, sizeof(ip_str));
                   printf("DHCP Timeout !! \n");
                   printf("Static IP address: %s\n", ip_str);
                   HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin,
                                GPIO_PIN_SET);
                   HAL_GPIO_WritePin(LED_YELLOW_GPIO_Port, LED_YELLOW_Pin,
                                GPIO_PIN_RESET);
        }
      }
    }
    break;
  case DHCP_LINK_DOWN:
    {
      DHCP_state = DHCP_OFF;
             HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_RESET);
             HAL_GPIO_WritePin(LED_YELLOW_GPIO_Port, LED_YELLOW_Pin, GPIO_PIN_SET);
    }
    break;
  default: break;
  }
}

 The last addition here is the DHCP periodic handle:

/**
  * @brief  DHCP periodic check
  *   netif
  * @retval None
  */
void DHCP_Periodic_Handle(struct netif *netif)
{
  /* Fine DHCP periodic process every 500ms */
  if (HAL_GetTick() - DHCPfineTimer >= DHCP_FINE_TIMER_MSECS)
  {
    DHCPfineTimer =  HAL_GetTick();
    /* process DHCP state machine */
    DHCP_Process(netif);
  }
}



#endif

2. Results       

Press [Ctrl + B] or the hammer icon to build the project, which should yield zero errors. It is important to note that if you use the code generation tool again, an error may occur where the stm32h5xx_hal_eth.c file is imported incorrectly and the HAL_ETH_Init() function is miss generated. In case this happens, simply copy the following line into line 308 of the mentioned file.

HAL_StatusTypeDef HAL_ETH_Init(ETH_HandleTypeDef *heth)

Now that the application is built, connect the NUCLEO-H563ZI board to the computer using the embedded STLINK’s USB and to a router/switch using the RJ45 connector. Open a virtual com port terminal and connect to the board’s com port. The Tera Term was used in this case with the settings [115200, 8bit, No Parity, 1 Stop Bit, No Hw Flow Control].

Press the [run] or [debug] button to get the application started. The green LED turns on and the following message is displayed:

Figure 13 - Demo ResultFigure 13 - Demo Result

To make sure the board is connected and working, you can open the cmd.exe or similar and issue a ping command, as illustrated below:

Figure 14 - Optional CommandFigure 14 - Optional Command

Conclusion

Now, you have the needed information to create a basic application running DHCP with the LwIP middleware. To implement more complex protocols and applications, refer to our demonstration projects available on our GitHub, such as the repository using the STM32H7 series or contact us through the ST community or by the Online Support Channel at https://my.st.com/ols

Related links

Here are some useful links that can help the development using the LwIP middleware with ST’s ecosystem:

Comments
fahad2192
Associate

@B.Montanari  STM32 CubeIDE Latest versions does not have support for lwip for STM32H5 series. Which version of the Cube IDE are you using?

B.Montanari
ST Employee

Hi @fahad2192 , the STM32Cube Ecosystem for the STM32H5 doesn't support LwIP natively, the article covers the steps needed to copy this Middleware from our github and manually import it into your design, please refer to chapter 1.2 of this article. Very early on, there is the github link> https://github.com/STMicroelectronics/stm32h5-classic-coremw-apps to access and download the LwIP

Hope this helps

Best Regards

 

 

DmitrySe
Associate

Hi @B.Montanari , great manual! I've made at all step by step and NUCLEO-H563ZI looks working properly (by the LEDs action). But I have a difficulties at the very last step, how can I get my IP address? I'm trying your way with Tera Term, but I'm getting a time out. What am I doing wrong? 

DmitrySe_0-1725299101347.png

Thank you in advance.

DmitrySe
Associate

Hi @B.Montanari, I've downgraded STM32CubeIDE from 1.16 to 1.15 and STM32CubeH5 HAL driver version from 1.3.0 to 1.2.0, built once again project and now everything working properly. Looks like some changes in new version somehow block STM getting messages, sending looks working good.

DmitrySe_0-1725445011083.png

I'll be honest, I'm didn't deep dive to the problem and I'm not a big expert of Ethernet communication. Important thing, it is working now, thank you

HT
Associate

Hi 

I get static IP : from print : Static IP address: 198.162.0.1

What I miss? 

Ahmet Yasin CİVAN
Associate III

Thank you for sharing this detailed guide on using the LWIP Ethernet middleware with the STM32H5 series. Your explanation is clear and extremely helpful, especially for those new to this topic. It's a great resource for developers working with STM32 microcontrollers. I truly appreciate your effort and time in creating such a valuable tutorial.

Version history
Last update:
‎2024-07-03 12:29 AM
Updated by: