Driving the W5500 Ethernet Controller with STM32L431 SPI Using STM32CubeMX and HAL
1. Prerequisites
Hardware
- STM32L4-series development board (STM32L431RCT6)
- WIZnet W5500 Ethernet module (SPI interface)
- Ethernet cable and router/switch
Software
- Keil MDK-ARM with STM32L4 device packs
- Serial terminal (e.g., Serial Port Utility, PuTTY)
- TCP/UDP socket testing tool (e.g., sockettool, netcat)
- STM32CubeMX (latest)
2. Create the MDK project with STM32CubeMX
Select the MCU
- Open STM32CubeMX and choose STM32L431RCT6 from the MCU selector.
System clock source
- If an external crystal is available, enable HSE in RCC. Otherwise, HSI is acceptable.
- This guide assumes HSE is used.
GPIOs for W5500 control
W5500 typical exposes reset and interrupt pins in addition to SPI. Configuer:
- RST → PC9 as GPIO Output (push-pull, no pull)
- INT → PA0 as GPIO External Interrupt (e.g., EXTI0, falling-edge or as required)
SPI1 wiring
Connect W5500 to SPI1. Hardware NSS is not used; CS is controlled by a GPIO pin.
| W5500 Pin | STM32 Pin | |-----------|-------------------| | MISO | PA6 (SPI1_MISO) | | MOSI | PA12 (SPI1_MOSI) | | SCLK | PA1 (SPI1_SCK) | | SCS (CS) | PA4 (GPIO output) |
SPI configuration tips:
- Prescaler: choose to achieve around 20 MHz SPI clock (e.g., 80 MHz / 4)
- CPOL = Low (idle low), CPHA = 1st edge (SPI mode 0)
- 8-bit data size, MSB first
- Hardware NSS disabled; use PA4 as manual chip-select
UART for logging
- Enable USART1 (async) to printf output; configure the pins routed to the onboard USB-to-UART bridge (e.g., CH340).
Clock tree
- Configure PLL such that SYSCLK and HCLK are 80 MHz (maximum for L4).
Project generation
- Toolchain: MDK-ARM
- Set "Generate peripheral initialization as a pair of .c/.h files per peripheral"
- Generate the code.
3. Retarget printf to USART1
Add a minimal retargeting so printf prints over USART1. Choose the version that matches your toolchain.
ARMCLANG/ARMCC (Keil):
#include "usart.h"
#include <stdio.h>
int fputc(int ch, FILE *f)
{
uint8_t c = (uint8_t)ch;
HAL_UART_Transmit(&huart1, &c, 1, HAL_MAX_DELAY);
return ch;
}
GCC (if using _write):
#include "usart.h"
#include <sys/unistd.h>
int _write(int fd, const void *buf, size_t count)
{
(void)fd;
HAL_UART_Transmit(&huart1, (uint8_t *)buf, count, HAL_MAX_DELAY);
return (int)count;
}
4. Bring in WIZnet ioLibrary
4.1. Download
iosLibrary Driver provides the driver and protocol helpers for WIZnet chips (W5500/W5300/W5200/W5100/W5100S). Obtain the source from the official repository.
4.2. Add to the project
- Create a folder, e.g.,
Hardware/W5500 - Copy the
Ethernetfolder from ioLibrary into this directory. You may also copyInternetfor later tests. - Add the relevant
Ethernetsource files to the MDK project. - Add include paths for the new folders.
- Ensure C standard is C99 (CubeMX MDK projects default to C99).
4.3. Select the chip
Open wizchip_conf.h and set the target chip macro to W5500 (e.g., _WIZCHIP_ or equivalent selection for W5500 as defined by the library version).
5. HAL-based porting layer for W5500
The ioLibrary uses callback hooks for SPI and chip control. Implemant and register them so the driver can talk to the hardware.
Create two files in Hardware/W5500:
w5500_port_hal.hw5500_port_hal.c
Add them to the MDK project and include their folder in the header search paths.
5.1. Header file
w5500_port_hal.h:
#ifndef W5500_PORT_HAL_H
#define W5500_PORT_HAL_H
#include "wizchip_conf.h"
#include "stm32l4xx_hal.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/* SPI and control pins (match your CubeMX pinout) */
#define W5K_CS_GPIO_Port GPIOA
#define W5K_CS_Pin GPIO_PIN_4
#define W5K_RST_GPIO_Port GPIOC
#define W5K_RST_Pin GPIO_PIN_9
/* Default network parameters */
#define NET_MAC_DEFAULT {0x02,0x12,0x34,0x56,0x78,0x9A}
#define NET_IP_DEFAULT {192,168,1,120}
#define NET_SN_DEFAULT {255,255,255,0}
#define NET_GW_DEFAULT {192,168,1,1}
#define NET_DNS_DEFAULT {8,8,8,8}
/* Comment this out to force manual PHY config (100M Full) */
#define W5K_USE_AUTONEG
/* Define to enable DHCP during bring-up */
/* #define W5K_USE_DHCP */
/* SPI handle exported by CubeMX */
extern SPI_HandleTypeDef hspi1;
void W5K_PrintNetInfo(void);
int W5K_BringUp(void);
#endif /* W5500_PORT_HAL_H */
5.2. Source file
w5500_port_hal.c:
#include "w5500_port_hal.h"
/* ----------- Low-level helpers ----------- */
static void w5k_lock(void)
{
__disable_irq();
}
static void w5k_unlock(void)
{
__enable_irq();
}
static void w5k_cs_low(void)
{
HAL_GPIO_WritePin(W5K_CS_GPIO_Port, W5K_CS_Pin, GPIO_PIN_RESET);
}
static void w5k_cs_high(void)
{
HAL_GPIO_WritePin(W5K_CS_GPIO_Port, W5K_CS_Pin, GPIO_PIN_SET);
}
static uint8_t w5k_spi_read_u8(void)
{
uint8_t tx = 0xFF;
uint8_t rx = 0;
(void)HAL_SPI_TransmitReceive(&hspi1, &tx, &rx, 1, 1000);
return rx;
}
static void w5k_spi_write_u8(uint8_t v)
{
(void)HAL_SPI_Transmit(&hspi1, &v, 1, 1000);
}
static void w5k_spi_read_buf(uint8_t *dst, uint16_t n)
{
if (!dst || n == 0) return;
/* Clock out by sending 0xFF */
for (uint16_t i = 0; i < n; ++i) {
uint8_t tx = 0xFF;
(void)HAL_SPI_TransmitReceive(&hspi1, &tx, &dst[i], 1, 1000);
}
}
static void w5k_spi_write_buf(const uint8_t *src, uint16_t n)
{
if (!src || n == 0) return;
(void)HAL_SPI_Transmit(&hspi1, (uint8_t *)src, n, 1000);
}
static void w5k_hard_reset(void)
{
HAL_GPIO_WritePin(W5K_RST_GPIO_Port, W5K_RST_Pin, GPIO_PIN_RESET);
HAL_Delay(30);
HAL_GPIO_WritePin(W5K_RST_GPIO_Port, W5K_RST_Pin, GPIO_PIN_SET);
HAL_Delay(20);
}
/* ----------- Chip and network configuration ----------- */
static int w5k_chip_init(void)
{
/* Configure 2KB TX/RX for each of the 8 sockets */
uint8_t txsize[8] = {2,2,2,2,2,2,2,2};
uint8_t rxsize[8] = {2,2,2,2,2,2,2,2};
return wizchip_init(txsize, rxsize);
}
static void w5k_phy_config(void)
{
#ifdef W5K_USE_AUTONEG
/* Autonegotiation: use hardware default, no action needed */
#else
wiz_PhyConf phy = {
.by = PHY_CONFBY_SW,
.mode = PHY_MODE_MANUAL,
.speed = PHY_SPEED_100,
.duplex = PHY_DUPLEX_FULL,
};
wizphy_setphyconf(&phy);
#endif
}
static void w5k_netinfo_apply(void)
{
wiz_NetInfo net = {0};
uint8_t mac[] = NET_MAC_DEFAULT;
uint8_t ip[] = NET_IP_DEFAULT;
uint8_t sn[] = NET_SN_DEFAULT;
uint8_t gw[] = NET_GW_DEFAULT;
uint8_t dns[] = NET_DNS_DEFAULT;
memcpy(net.mac, mac, sizeof(net.mac));
memcpy(net.ip, ip, sizeof(net.ip));
memcpy(net.sn, sn, sizeof(net.sn));
memcpy(net.gw, gw, sizeof(net.gw));
memcpy(net.dns, dns, sizeof(net.dns));
#ifdef W5K_USE_DHCP
net.dhcp = NETINFO_DHCP;
#else
net.dhcp = NETINFO_STATIC;
#endif
/* Either API is acceptable depending on ioLibrary version */
/* wizchip_setnetinfo(&net); */
ctlnetwork(CN_SET_NETINFO, &net);
}
void W5K_PrintNetInfo(void)
{
wiz_NetInfo net = {0};
ctlnetwork(CN_GET_NETINFO, &net);
printf("w5500 network info\r\n");
printf(" mac: %02X:%02X:%02X:%02X:%02X:%02X\r\n",
net.mac[0], net.mac[1], net.mac[2], net.mac[3], net.mac[4], net.mac[5]);
printf(" ip : %u.%u.%u.%u\r\n", net.ip[0], net.ip[1], net.ip[2], net.ip[3]);
printf(" sn : %u.%u.%u.%u\r\n", net.sn[0], net.sn[1], net.sn[2], net.sn[3]);
printf(" gw : %u.%u.%u.%u\r\n", net.gw[0], net.gw[1], net.gw[2], net.gw[3]);
printf(" dns: %u.%u.%u.%u\r\n", net.dns[0], net.dns[1], net.dns[2], net.dns[3]);
printf(" dhcp: %s\r\n", (net.dhcp == NETINFO_DHCP) ? "enabled" : "static");
}
int W5K_BringUp(void)
{
/* Reset the controller */
w5k_hard_reset();
/* Register callbacks */
reg_wizchip_cris_cbfunc(w5k_lock, w5k_unlock);
reg_wizchip_cs_cbfunc(w5k_cs_low, w5k_cs_high);
reg_wizchip_spi_cbfunc(w5k_spi_read_u8, w5k_spi_write_u8);
reg_wizchip_spiburst_cbfunc(w5k_spi_read_buf, w5k_spi_write_buf);
/* Initialize socket buffers */
if (w5k_chip_init() != 0) {
return -1;
}
/* Configure PHY and network */
w5k_phy_config();
w5k_netinfo_apply();
/* Show effective settings */
W5K_PrintNetInfo();
return 0;
}
6. Initialize W5500 in the application
Include the header in main.c and call the bring-up routine.
#include "w5500_port_hal.h"
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_SPI1_Init();
printf("W5500 bring-up...\r\n");
int rc = W5K_BringUp();
if (rc != 0) {
printf("W5500 init failed: %d\r\n", rc);
} else {
printf("W5500 init ok\r\n");
}
while (1) {
/* Application loop */
}
}
7. Notes and verification
- Autonegotiation requires an active link; connect the W5500 to a switch or router before powering the board if
W5K_USE_AUTONEGis enabled. - Ensure the board and test PC are on the same network. If using static IP, set NET_IP_DEFAULT and NET_SN_DEFAULT accordingly.
- After programming, open the serial terminal to view the printed network parameters.
- From a PC, ping the configured IP address to verify connectivity.