Getting to ping on STM32H743 with LWIP

Given that the promise of STM32CubeIDE is that adding functionality should be as easy as clicking in checkboxes, you'd think it would be easy to set up Ethernet on one of STM's own Nucleo boards which is designed for it, and to get it responding to ping on your internal network at a fixed IP address within minutes.

If so, you've been misled by the way that simpler interfaces such as UART, SPI, I2C, and GPIO are easily configured with STM32CubeIDE/CubeMX, especially if you don't need to have them working through DMA. Because of the high speed nature of Ethernet, it is essential to use DMA and to get the caching set up right, and until you do, it doesn't work at all. Unlike UART, SPI, I2C or CAN, you can't just start with the simpler case of transmit-only to see if your physical layer is working and the baud rate setting from the clock tree is right because Ethernet essentially requires receive and transmit to work before you'll see anything at the IP layer. If your lowest level test is ping, it's not going to work until your PC can get responses to it's ARP requests.

Over the last few years, there's been a lot of improvements to STM's HAL and generated code whilst they fix the many problems that users have encountered. One source of confusion is documentation that doesn't match up with generated code; you might see code which uses the function HAL_ETH_IsRxDataAvailable() which was removed in firmware 1.10.

Let's start with Nucleo-H743 board and see what happens when we ask Cube to generate code for this board with all the peripherals set up in their default mode.

STM32CubeIDE selecting board Nucleo-H743
STM32CubeIDE initialise using defaults

ETH

Cube doesn't set up the ethernet peripherals and prevents you from doing so until you solve some other problems first like it's some kind of puzzle game. The first thing to notice is that the ETH peripheral in the Connectivity section should really be called the MAC peripheral. It interfaces with the PHY which is, of course, external and is set up in the Middleware - LWIP section and doesn't generate code in the drivers folder. After setting RMII mode, if we leave all the ETH settings alone at defaults it will actually work, although it is recommended to set all the output pin speeds to Very High instead of their default Low. For what it's worth, I found that all the speeds worked ok on my Nucleo board, at least with the system clock left at the default 64 MHz. This setting affects the output drive strength, so it has no effect on input pins, and seems to give very similar edge rates at High and Very High settings with just slightly more ringing for the latter.

STM32CubeIDE defaults to Low speed on Eth pins

In NVIC Settings, we need to leave the Ethernet global interrupt off while we are using the bare-metal mode (no RTOS).

MPU

Going to the LWIP section, we find that it is disabled because the CPU D-cache isn't enabled yet.

LWIP Enabled only if D-cache is on

You'll find this in the System Core - Cortex_M7 section where it should be enabled along with the I-cache and Speculation default mode. If you were to go back LWIP, you'd find that you can now configure it, but a project built like this won't work. The LWIP code that is generated requires memory protection regions to be set up so that the caches work correctly with the DMA controller. You'll need to set the MPU Control Mode and set up Regions 0, 1 and 2 as shown to correspond with the other setting changes we are about to do in the LWIP section.

MPU settings for ethernet on H743

LWIP

In this section, you'll immediately notice a yellow warning triangle on the Platform Settings tab. Cube is being annoying here because not only does it already know you're using the LAN8742 as the PHY since we started from the Nucleo board defaults, but also it's the only PHY they support and the first dropdown box, IPs or Components, already has it selected. So why do they make you choose the same option under Found Solutions?

STM32CubeIDE PHY platform settings

While we are trying to get basic functionality working, it is best to assign a fixed IP so at least we don't have to worry about DHCP servers, DHCP client code and what IP address the board has been assigned. In General Settings set DHCP Mode to Disabled, and set suitable IP, Netmask and Gateway addresses for your network. For example, if my PC is using an Ethernet adapter/interface that I can set the IPv4 address on in Windows to 192.168.0.1, I'm going to use 192.168.0.100 for the board's IP address, 255.255.255.0 for the netmask and 192.168.0.1 for the gateway.

Under Key Options tab, NO_SYS is set to OS Not Used because we are not running on an RTOS. LWIP_RAM_HEAP_POINTER instead of 0x30044000, set to 0x300200000 to match the MPU Region 1 we set up earlier. MEM_SIZE can be left at the default 1600 Bytes while we just get ping functionality working although it will need to be enlarged when we need to add other functionality later.

Generate code from ioc

Save the ioc and generate the code. The ETH and MPU configurations result in a few changes as expected but the LWIP addition results in 246 new files.

Cube asking do you want to regenerate

main.c

Although Cube has added a call to MX_LWIP_Init() in the initialisation section, it doesn't add a call to MX_LWIP_Process() in the main loop so we have to do that ourselves. It needs to be called often enough to handle the network traffic so this is the only place to add it while the project is so simple.

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
  MX_LWIP_Process();    //This needs to be called regularly if running as bare-metal (no RTOS)  

/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */

If we were using an RTOS, Cube would generate RTOS tasks to handle LWIP, but for non-RTOS projects we need to call MX_LWIP_Process()

Linker Script

Again Cube has done half the job. In ethernetif.c lines 111, 112 it sets attributes for the locations of the descriptors but it hasn't set these up in the linker script.

ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT] __attribute__((section(".RxDecripSection")));   /* Ethernet Rx DMA Descriptors */
ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT] __attribute__((section(".TxDecripSection")));   /* Ethernet Tx DMA Descriptors */

Open the STM32H743ZITX_FLASH.ld and add these lines after the >RAM_D1 section (around line 153).

  .lwip_sec (NOLOAD) :
  {
    . = ABSOLUTE(0x30040000);
    *(.RxDecripSection) 
    
    . = ABSOLUTE(0x30040060);
    *(.TxDecripSection)
    
    . = ABSOLUTE(0x30040200);
    *(.Rx_PoolSection)  
  } >RAM_D2

This completes the minimum set of changes needed to get ping working. As we add functionality, we typically need to increase the size of buffers and bring in other build options as well as adding code.

Testing

Connect an ethernet cable between your PC and the board. All modern ethernet ports are auto-crossover types so you do not need a special crossover cable or an ethernet switch, a standard straight-through (T568A-T568A or T568B-T568B) RJ-45 cable will work. Don't forget to set your PC's IP address to one in the same subnet as the board, and the same netmask.

Open a cmd window in Windows and run ping with the IP address of the board (or use the -t option to ping continuously).

cmd ping working

Wireshark can be used to see all the details.

Wireshark ping working

Other boards/processors

For other STM32 Nucleos, you can still start here but make adjustments to addresses as per the hotspot repo linked below. It gets more complicated when dealing with the RTOS option and dual core processors, so it is best to gain confidence and experience with single core, bare metal designs first. Moving away from LAN8742 (or compatible PHYs) is even more difficult, so I wouldn't advise going straight to a custom board without having a Nucleo reference sample working.

Thanks

Although many hours were spent personally trying to get this to work, there is a write up and sample code which has been proven to work, but could seem overwhelming and too many changes for a beginner to handle just to get to ping. My advice to beginners would be to start with this blog post and add in changes from that repository once your board is up and pinging. Of course, commit each change as you make it so you can use your version control system to revert changes or diagnose the cause of any new problems immediately.