on
2024-07-02
05:01 AM
- edited on
2024-07-03
12:29 AM
by
Laurids_PETERSE
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.
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.
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.
Once the project creation is done, enable the [ETH] peripheral in RMII mode, located in the [Connectivity] section.
Next, configure the [ICACHE] in 1-way mode
Rename the following pins accordingly, using the user label feature to have the LED’s colors reflected properly:
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:
Generate the code by clicking on the highlighted icon, or just pressing the [Alt + K] shortcut.
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:
Select [Copy files and folders] in the pop-up tab and click [OK]
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:
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.
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.
Finally, from ..\stm32h5-classic-coremw-apps-main\Drivers\BSP\Components\lan8742 path, import both lan8742.c and lan8742.h to the “BSP” folder.
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.
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:
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.
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, ðernetif_init,
ðernet_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 */
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
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
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:
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:
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
Here are some useful links that can help the development using the LwIP middleware with ST’s ecosystem:
@B.Montanari STM32 CubeIDE Latest versions does not have support for lwip for STM32H5 series. Which version of the Cube IDE are you using?
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
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?
Thank you in advance.
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.
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
Hi
I get static IP : from print : Static IP address: 198.162.0.1
What I miss?
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.
Hi,
Do you know how to connect a Ethernet Switch chip (MAC) ? that means there is no PHY device. There is only RMII interface between STM32 and switch chip, without MDIO/MDC.
Any advice is highly appreciated
Peter
Hi,
I would also be interested in something similar as @Peter3718, i'm trying to interface with the KSZ8863 PHY. CubeMX generates the MDC and MDIO pins no option to disable them. Any suggestions how to implement the settings of the phy by I2C trough the H5?
Thank you in advance.
Hi,
thanks for the Help. It took me a while to also enable IPv6, but it works now.
If a switch does not have a PHY, then it does not have a PHY and you can not receive or send anything.
The KSZ8863 has an SMI interface, see here:
I am using a single pair Ethernet switch with an integrated virtual PHY from microchip and it works great together with the STM32 MAC.
Regards,
Roman
Hi,
Thank you @RomThi for your response.
I'm having access to all the registers trough I2C, I need to configure register 198[3] to set the correct clock mode [5] of the KSZ8863. Switch functionality works. But now I'm facing the challenge on how to change the STM32 LAN8742 driver to use I2C to setup the KSZ8863? Which is normally done automatically by Cube by generating the MDIO and MDC pins in combination with the LAN8742 driver?. Or am I missing something?
Any advice would be super helpful.
Thank you in advance.
Are you using microchip LAN937X single pair Ethernet switch. I am using it. STM32H755 is connecting to switch chip via RMII directly. That's why i said there is no PHY. but yes, there is called virtual PHY inside the switch. Because CUBEMX has no this vPHY driver, i want to bypass this SMI interface. unfortunately, the RMII seems doesn't work. From scope, the RXD and the TXD has data waveform. But communication is still lost. Do you have any clue for our issue?
Thanks
Peter
I have a LAN9373 and it works together with the standard LAN8742 driver of CUBE. No need to change something. This is because most PHY's have same registers over MDIO and only a few are required to get it working.
Maybe your problem is the HW configuration, read carefully about the strap settings (pull-ups/pull-downs). Some of the LAN937x has also two RMII interfaces chose RMII2 not RMII1 to connect to your controller.
Try the LAN8742 driver from CubeMX. As i wrote most PHYs are compatible. If not, compare the registers and modifiy it. It's a simple interface.
Good luck guys
Good reference for us. What MCU are you using? I am using LAN9370, which only have only RMII interface. What's your VPHY address? is your project with or without Freertos? As for successful ping, do you change anything beside adding MX_LWIP_Process() in main loop?
Thanks
Peter
I 100% followed this guide here. No other changes to get the ping working. I don't have MX_LWIP_Process(). I just copied the files above from this article.
Regards
Hi,
@RomThi, again thank you for responding.
I dove into the LAN8742.c file a few hours ago, and noticed (as you where saying) its a simple interface. I just did the setup of the KSZ8863 trough I2C and commented most part of the LAN8742_Init MIIM (MDC/MDIO) stuff out. And set the status to LAN8742_STATUS_OK.
After this it initializes correctly and I'm able to ping.
Regards
which MCU are you using?
In LAN8742.c, there is a code to read REG then decide PHY address. I think you need to change these code, otherwise wrong VPHY address will be set.
Peter
LWIP should periodically check the link status, if no link is detected, transmit/receive should be disabled. Have you ported these function to I2C interface?
Thanks
Peter
Hi,
As I told I have not changed somethink.
I am using the H563 and the H573. In the driver lan8742.c the first think done is to search the PHY using the special mode register. Maybe this fails, but I have set the MDIO address to 0 of the LAN9373, so zero it is the right one :)
I don't understand why you need any I2C. As I told you the KSZ8863 can be connected to the MDIO.
Hi,
@Peter3718My HW connection is good so RMII is always connected to the controller, I always let it return the 100MBITS FULLDUPLEX status. If I need more I port it to I2C.
@RomThiTrue, but I need I2C to setup Clock Mode 5 (enable bit 198[3]) and need to access some more settings which are only accessible over I2C.
Regards