Fading Coder

An Old Coder’s Final Dance

Home > Tech > Content

Driving the W5500 Ethernet Controller with STM32L431 SPI Using STM32CubeMX and HAL

Tech 2

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 Ethernet folder from ioLibrary into this directory. You may also copy Internet for later tests.
  • Add the relevant Ethernet source 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.h
  • w5500_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_AUTONEG is 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.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.