---
title: "STM32CubeMX series | Use bear pie hardware SPI to drive W5500 Ethernet module"
url: "https://maker.wiznet.io/praveen_k/projects/stm32cubemx-series-use-bear-pie-hardware-spi-to-drive-w5500-ethernet-module/"
markdown_url: "https://maker.wiznet.io/praveen_k/projects/stm32cubemx-series-use-bear-pie-hardware-spi-to-drive-w5500-ethernet-module/md"
type: "UCC: User Created Content"
author: "mculover666"
author_url: "https://zhuanlan.zhihu.com/p/357002381"
editor: "WIZnet"
editor_url: "https://maker.wiznet.io/"
original_author: "mculover666"
original_url: "https://zhuanlan.zhihu.com/p/357002381"
license: "Apache License 2.0 (Apache-2.0)"
published: "2022-11-02"
language: "en"
hardware: ["WIZnet W5500"]
likes: 4
views: 1203
comments: 0
source: "WIZnet Makers (https://maker.wiznet.io/)"
---

# STM32CubeMX series | Use bear pie hardware SPI to drive W5500 Ethernet module

> his article records in detail how to use STM32CubeMX to configure the hardware SPI peripheral of STM32L431RCT6 to communicate with W5500, an

Original author: mculover666 (source: https://zhuanlan.zhihu.com/p/357002381)

## Components

- **WIZnet W5500** x 1 ([docs](https://docs.wiznet.io/Product/Chip/Ethernet/W5500))

WIZnet parts: W5500 ([Datasheet](https://docs.wiznet.io/Product/Chip/Ethernet/W5500/datasheet?utm_source=maker&utm_medium=project&utm_campaign=w5500), [product hub](https://maker.wiznet.io/products/w5500/))

## Article

This article records in detail how to use STM32CubeMX to configure the hardware SPI peripheral of STM32L431RCT6 to communicate with W5500, and transplant the official W550 driver to drive the Ethernet module.

## 1. Preparations

### hardware preparation

- development board

First, you need to prepare a development board. Here I am preparing the STM32L4 development board (BearPi):

![](https://pic2.zhimg.com/80/v2-67adbaea31c42ef34bd0daeb6af5e0bd_720w.webp)

- W5500 Ethernet Module

Here I use the common Ethernet module W5500, which integrates the TCP/IP protocol stack:

![](https://pic2.zhimg.com/80/v2-d323af98542eef6da93a45473d9798a1_720w.webp)

### software preparation

- It is necessary to install Keil-MDK and the corresponding package of the chip in order to compile and download the generated code;

- Prepare a serial port debugging assistant, here I use `Serial Port Utility`;

- Prepare a network debugging assistant, here I use `sockettool`;

## 2. Generate MDK project

### Select chip model

Open STM32CubeMX and open the MCU selector:

![](https://pic4.zhimg.com/80/v2-9d2e587d08a68ef0e6069fa513e38fff_720w.webp)

Search and select chips `STM32L431RCT6`:

![](https://pic1.zhimg.com/80/v2-be1d2dee4104346f0d7abc57a6210548_720w.webp)

### Configure the clock source

- If you choose to use an external high-speed clock (HSE), you need to configure RCC in the System Core;

- If you use the default internal clock (HSI), this step can be skipped;

Here I'm all using an external clock:

![](https://pic1.zhimg.com/80/v2-0b754fd750392b016734cee94b487204_720w.webp)

### Configure the Ethernet module to control the GPIO

There are two GPIOs that the Ethernet module needs to configure additionally:

| Ethernet Module Pin Names | GPIO | effect |
| --- | --- | --- |

The reset pin can be configured in output mode:

![](https://pic3.zhimg.com/80/v2-bfeef69ec6a1af28eb52dda703edb942_720w.webp)

The interrupt pin needs to receive interrupts from the Ethernet module, so the EXTI external interrupt pin needs to be configured:

![](https://pic2.zhimg.com/80/v2-e591f03282e5783ab4f9a3af13f6214d_720w.webp)

### Configure the SPI1 interface

In this experiment, I connected the Ethernet module to the SPI1 interface, and the pin correspondence table is as follows:

> It should be noted that the SPI chip select pin is not controlled by the hardware SPI peripheral, but is configured as a normal GPIO and controlled manually .

| Ethernet Module Pinouts | MCU pins |
| --- | --- |

There are three points to note when configuring the SPI interface:

① Frequency division coefficient; ② CPOL: The level of CLK is high or low when idle; ③ CPHA: sampling at the first clock edge, or sampling at the second clock edge;

Next, start configuring the SPI1 peripheral, first configure the mode and pins of the SPI1 peripheral:

![](https://pic3.zhimg.com/80/v2-cacd62ac0e56a1fd04da5fc185865dd6_720w.webp)

Because you have chosen not to use the hardware SPI peripheral to control the chip select pin, you need to manually configure the chip select pin PA4:

![](https://pic1.zhimg.com/80/v2-96c718d8d9fbfb91b931f22cc9a49d38_720w.webp)

The SPI bus clock given in the W5500 manual is 80Mhz:

![](https://pic3.zhimg.com/80/v2-5135d6df92d4d94022ee4313688f24de_720w.webp)

However, it should be noted that the manual clearly states that at least 33.3Mhz is actually guaranteed, so for the sake of safety, the SPI bus clock is configured as 20Mhz in this experiment :

![](https://pic4.zhimg.com/80/v2-d856c48eb98364247c0a47ab9630d353_720w.webp)

For CPOL, W5500 supports both modes, select the mode of LOW when idle, which is given as the first clock edge in the CPHA manual:

![](https://pic4.zhimg.com/80/v2-40ef080ed3f62af765ce1ca7da0a4517_720w.webp)

In summary, the timing parameters are configured as follows:

![](https://pic2.zhimg.com/80/v2-cfbcf54bc22484fe34c0325d7a06a3e9_720w.webp)

### Configure serial port

The development board is equipped with a CH340z for serial port, which is connected to USART1.

Next start the configuration `USART1`:

![](https://pic2.zhimg.com/80/v2-0f12e34f4128a8f0f0dd95c37bd4165d_720w.webp)

### Configure the clock tree

The highest frequency of STM32L4 is 80M, so configure PLL, and finally make it `HCLK = 80Mhz`:

![](https://pic2.zhimg.com/80/v2-79bedcc609ff1fc93367e5b44f670d1d_720w.webp)

### Generate project settings

![](https://pic4.zhimg.com/80/v2-55bbb84e988c8c6a93eb9946850bab47_720w.webp)

### Code Generation Settings

Finally set to generate a separate initialization file:

![](https://pic2.zhimg.com/80/v2-0aaa972759f988a1719e4162e4b47401_720w.webp)

### generate code

Click `GENERATE CODE`to generate MDK-V5 project:

![](https://pic1.zhimg.com/80/v2-654f189ffa1f6dae00a32ea506449eec_720w.webp)

## 3. Redirect the printf function to USART1

Reference: [\[STM32Cube_09\] Various methods of redirecting the printf function to the serial port output](https://link.zhihu.com/?target=http%3A//www.mculover666.cn/posts/2251182441/) .

## 4. Transplant W5500 official driver library

### 4.1. Download the official driver library

W5500 officially provides ioLibrary v2.0.0, ioLibrary is the Ethernet driver library of WIZnet chip, which includes driver and application protocol. This driver (ioLibrary) can be used for application design of WIZnet TCP/IP chip, such as W5500, W5300, W5200, W5100, W5100S.

There are two download addresses:

- github open source warehouse address [: ](https://link.zhihu.com/?target=https%3A//github.com/Wiznet/ioLibrary_Driver)[https://github.com/Wiznet/ioLib rary_Driver](https://link.zhihu.com/?target=https%3A//github.com/Wiznet/ioLibrary_Driver)

- gitee repository address (for faster download speed, bloggers sync to gitee) [: https://gitee.com/mculover666/i oLibrary_Driver](https://link.zhihu.com/?target=https%3A//gitee.com/mculover666/ioLibrary_Driver)

The source directory structure is as follows:

- Ethernet : BSD-like SOCKET API interface, and WIZCHIP(W5500 / W5300 / W5200 / W5100 / W5100S) driver

- Internet : Various application layer protocol stacks
  - DHCP client
  - DNS client
  - FTP client
  - FTP server
  - SNMP agent/trap
  - SNTP client
  - TFTP client
  - HTTP server
  - MQTT Client

### 4.2. Add the driver library to the project

Create a new Hardware/W5500 in the project directory, and copy the three folders in the driver library:

![](https://pic4.zhimg.com/80/v2-10ca864c8d56f3d9c6643f1b106e6907_720w.webp)

Note that only the files under Ethernet are required, and the files in the other two folders can be optionally added, which will be used later for testing.

Next, add the files related to the W5500 in the Ethernet directory to the MDK project:

![](https://pic3.zhimg.com/80/v2-724edd5b047f4f06661e0ba382205cca_720w.webp)

Add the header file path:

![](https://pic4.zhimg.com/80/v2-f9c6490a49c05d6ca1a0864a1013b9c7_720w.webp)

Make sure that C99 mode is enabled (it is enabled by default in the project generated by STM32Cubemx):

![](https://pic4.zhimg.com/80/v2-d0a8aa46cf6b7a4a2c8226451e1718fb_720w.webp)

### 4.3. Configure the chip model used

Open the `wizchip_conf.h`file and modify the macro definition at the very beginning `_WIZCHIP_`. The macro definition indicates the chip model we use and is set to W5500:

![](https://pic4.zhimg.com/80/v2-fe35dc490e8bb518518ceb3c2d8ae62f_720w.webp)

## 5. Compatible with W5500 official driver

The W5500 official driver library manages the spi driver through a set of function pointers defined in the _WIZCHIP structure. In order to prevent direct error reporting after adding, the default implementation of these function pointers is provided in wizchip_conf.c, which are all empty functions, so this No errors are reported when compiling.

> These two adaptation files are open source, Github address [: https://github.com/Mculover666/ HAL_Driver_Lib](https://link.zhihu.com/?target=https%3A//github.com/Mculover666/HAL_Driver_Lib).

### 5.1. Add porting adaptation file

Next, in the project project, we create new `w5500_port_hal.h`files and `w5500_port_hal.c`files to store our own implementation, and use the interface provided by the driver library to register in the driver library.

![](https://pic2.zhimg.com/80/v2-a077bdd606bc6c8417526860722c4555_720w.webp)

Add to the MDK project:

![](https://pic3.zhimg.com/80/v2-2b05e7282043471bbd9bfbe94f84a8ca_720w.webp)

Add the header file path:

![](https://pic4.zhimg.com/80/v2-7a8838ba833b0f30abe0f6cd2b0c1ebb_720w.webp)

### 5.2. Writing header files

Write the `w5500_port_hal.h`file:

```plaintext
#ifndef _W5500_PORT_HAL_
#define _W5500_PORT_HAL_

#include "wizchip_conf.h"
#include "stm32l4xx.h"
#include <string.h>
#include <stdio.h>

#define W5500_SPI_HANDLE    hspi1
#define W5500_CS_PORT       GPIOA
#define W5500_CS_PIN        GPIO_PIN_4
#define W5500_RST_PORT      GPIOC
#define W5500_RST_PIN       GPIO_PIN_9

#define DEFAULT_MAC_ADDR    {0x00,0xf1,0xbe,0xc4,0xa1,0x05}
#define DEFAULT_IP_ADDR     {192,168,0,136}
#define DEFAULT_SUB_MASK    {255,255,255,0}
#define DEFAULT_GW_ADDR     {192,168,0,1}
#define DEFAULT_DNS_ADDR    {8,8,8,8}

/* 定义该宏则表示使用自动协商模式，取消则设置为100M全双工模式 */
#define USE_AUTONEGO

/* 定义该宏则表示在初始化网络信息时设置DHCP */
//#define USE_DHCP

extern SPI_HandleTypeDef W5500_SPI_HANDLE;

void w5500_network_info_show(void);
int w5500_init(void);

#endif
```

### 5.3. Writing c files

First include the header files:

```plaintext
#include "w5500_port_hal.h"
```

### 5.3.1. SPI driver interface implementation

Then use the HAL library to implement the specific functions of the 8 SPI function pointers required by the W5500 driver:

```plaintext
/**
 * @brief   enter critical section
 * @param   none
 * @return  none
 */
static void w5500_cris_enter(void)
{
    __set_PRIMASK(1);
}

/**
 * @brief   exit critical section
 * @param   none
 * @return  none
 */
static void w5500_cris_exit(void)
{
    __set_PRIMASK(0);
}

/**
 * @brief   select chip
 * @param   none
 * @return  none
 */
static void w5500_cs_select(void)
{
    HAL_GPIO_WritePin(W5500_CS_PORT, W5500_CS_PIN, GPIO_PIN_RESET);
}

/**
 * @brief   deselect chip
 * @param   none
 * @return  none
 */
static void w5500_cs_deselect(void)
{
    HAL_GPIO_WritePin(W5500_CS_PORT, W5500_CS_PIN, GPIO_PIN_SET);
}

/**
 * @brief   read byte in SPI interface
 * @param   none
 * @return  the value of the byte read
 */
static uint8_t w5500_spi_readbyte(void)
{
    uint8_t value;
    
    if (HAL_SPI_Receive(&W5500_SPI_HANDLE, &value, 1, 1000) != HAL_OK) {
        value = 0;
    }
    
    return value;
}

/**
 * @brief   write byte in SPI interface
 * @param   wb  the value to write
 * @return  none
 */
static void w5500_spi_writebyte(uint8_t wb)
{
    HAL_SPI_Transmit(&W5500_SPI_HANDLE, &wb, 1, 1000);
}

/**
 * @brief   burst read byte in SPI interface
 * @param   pBuf    pointer of data buf
 * @param   len     number of bytes to read
 * @return  none
 */
static void w5500_spi_readburst(uint8_t* pBuf, uint16_t len)
{
    if (!pBuf) {
        return;
    }
    
    HAL_SPI_Receive(&W5500_SPI_HANDLE, pBuf, len, 1000);
}

/**
 * @brief   burst write byte in SPI interface
 * @param   pBuf    pointer of data buf
 * @param   len     number of bytes to write
 * @return  none
 */
static void w5500_spi_writeburst(uint8_t* pBuf, uint16_t len)
{
    if (!pBuf) {
        return;
    }
    
    HAL_SPI_Transmit(&W5500_SPI_HANDLE, pBuf, len, 1000);
}

/**
 * @brief   hard reset
 * @param   none
 * @return  none
 */
static void w5500_hard_reset(void)
{
    HAL_GPIO_WritePin(W5500_RST_PORT, W5500_RST_PIN, GPIO_PIN_RESET);
    HAL_Delay(50);
    HAL_GPIO_WritePin(W5500_RST_PORT, W5500_RST_PIN, GPIO_PIN_SET);
    HAL_Delay(10);
}
```

### 5.3.2. Chip operation implementation

Write the chip initialization function based on the official driver library, and set the send and receive buffer size of the socket (default 2KB):

```plaintext
/**
 * @brief   Initializes WIZCHIP with socket buffer size
 * @param   none
 * @return  errcode
 * @retval  0   success
 * @retval  -1  fail
 */
static int w5500_chip_init(void)
{
    /* default size is 2KB */
    
    return wizchip_init(NULL, NULL);
}
```

Then write hardware PHY configuration functions, such as working mode, rate, and whether to negotiate and other configurations:

> The auto-negotiation function requires the network cable to be connected to the router before power-on, and is not required for the manual configuration mode.

```plaintext
/**
 * @brief   set phy config if autonego is disable
 * @param   none
 * @return  none
 */
static void w5500_phy_init(void)
{
#ifdef USE_AUTONEGO
    // no thing to do
#else
    wiz_PhyConf conf;
    
    conf.by = PHY_CONFBY_SW;
    conf.mode = PHY_MODE_MANUAL;
    conf.speed = PHY_SPEED_100;
    conf.duplex = PHY_DUPLEX_FULL;
    
    wizphy_setphyconf(&conf);
#endif
}
```

Then write the function of configuring and printing network information:

```plaintext
/**
 * @brief   initializes the network infomation
 * @param   none
 * @return  none
 */
static void w5500_network_info_init(void)
{
    wiz_NetInfo info;
    
    uint8_t mac[6] = DEFAULT_MAC_ADDR;
    uint8_t ip[4] = DEFAULT_IP_ADDR;
    uint8_t sn[4] = DEFAULT_SUB_MASK;
    uint8_t gw[4] = DEFAULT_GW_ADDR;
    uint8_t dns[4] = DEFAULT_DNS_ADDR;
    
    memcpy(info.mac, mac, 6);
    memcpy(info.ip, ip, 4);
    memcpy(info.sn, sn, 4);
    memcpy(info.gw, gw, 4);
    memcpy(info.dns, dns, 4);
    
#ifdef USE_DHCP
    info.dhcp = NETINFO_DHCP;
#else
    info.dhcp = NETINFO_STATIC;
#endif
    
    wizchip_setnetinfo(&info);
}

/**
 * @brief   read and show the network infomation
 * @param   none
 * @return  none
 */
void w5500_network_info_show(void)
{
    wiz_NetInfo info;
    
    wizchip_getnetinfo(&info);
    
    printf("w5500 network infomation:\r\n");
    printf("  -mac:%d:%d:%d:%d:%d:%d\r\n", info.mac[0], info.mac[1], info.mac[2], 
            info.mac[3], info.mac[4], info.mac[5]);
    printf("  -ip:%d.%d.%d.%d\r\n", info.ip[0], info.ip[1], info.ip[2], info.ip[3]);
    printf("  -sn:%d.%d.%d.%d\r\n", info.sn[0], info.sn[1], info.sn[2], info.sn[3]);
    printf("  -gw:%d.%d.%d.%d\r\n", info.gw[0], info.gw[1], info.gw[2], info.gw[3]);
    printf("  -dns:%d.%d.%d.%d\r\n", info.dns[0], info.dns[1], info.dns[2], info.dns[3]);
    
    if (info.dhcp == NETINFO_DHCP) {
        printf("  -dhcp_mode: dhcp\r\n");
    } else {
        printf("  -dhcp_mode: static\r\n");
    }
}
```

Finally write the w5500 initialization function:

```plaintext
/**
 * @brief   w5500 init
 * @param   none
 * @return  errcode
 * @retval  0   success
 * @retval  -1  chip init fail
 */
int w5500_init(void)
{
    /* W5500 hard reset */
    w5500_hard_reset();
    
    /* Register spi driver function */
    reg_wizchip_cris_cbfunc(w5500_cris_enter, w5500_cris_exit);
    reg_wizchip_cs_cbfunc(w5500_cs_select, w5500_cs_deselect);
    reg_wizchip_spi_cbfunc(w5500_spi_readbyte, w5500_spi_writebyte);
    reg_wizchip_spiburst_cbfunc(w5500_spi_readburst, w5500_spi_writeburst);

    /* socket buffer size init */
    if (w5500_chip_init() != 0) {
        return -1;
    }
    
    /* phy init */
    w5500_phy_init();
    
    /* network infomation init */
    w5500_network_info_init();
    
    /* show network infomation */
    w5500_network_info_show();
    
    return 0;
}
```

### 5.3. Test W5500 initialization

Include the header file in main.c:

```plaintext
#include "w5500_port_hal.h"
```

Test the initialization function in the main function:

```plaintext
/* USER CODE BEGIN 2 */
 printf("W5500 test on BearPi board by Mculover666\r\n");
 
 int ret;
 ret = w5500_init();
 if (ret != 0) {
   printf("w5500 init fail, ret is %d\r\n", ret);
 } else {
   printf("w5500 init success\r\n");
 }

 /* USER CODE END 2 */
```

Compile, download, and do not run for now.

Because the auto-negotiation mode is used, make sure that the W5500 network cable is connected to the router, and then power on and run, the serial port log is as follows:

![](https://pic4.zhimg.com/80/v2-c3954f2eb3f3621c204bf12f02c8c463_720w.webp)

Make sure that the Windows host and the development board are connected to the same router (or under the same network segment), and ping the development board to test:

![](https://pic3.zhimg.com/80/v2-0e231b5bdeb78d3854b1cba58edb1972_720w.webp)

## 6. Socket test of W5500

The standard Socket API is implemented in the W5500 official driver library. In socket.h and socket.c, you can directly call and write TCP or UDP test programs.

The W5500 official driver library also provides a Socket use case, including loopback tests for TCP server, TCP client, and UDP server, in the application/loopback folder:

![](https://pic3.zhimg.com/80/v2-0b1ca3853c43fd8673ee833575020a3e_720w.webp)

The next part of this article will perform a loopback test of the TCP client.

### 6.1. Start the TCP server

Open the network debugging assistant on the computer, create a TCP server, and monitor port 8000 of the local machine:

![](https://pic4.zhimg.com/80/v2-84f440edcd182f1ab930df069a4f9dbb_720w.webp)

### 6.2. Add loopback test file

Add c file in MDK:

![](https://pic4.zhimg.com/80/v2-03283e99624d2fe9123fb2e26ae34bdb_720w.webp)

Add the header file path:

![](https://pic3.zhimg.com/80/v2-b34494f50be79b7439c8adbd898e9376_720w.webp)

### 6.3. Calling the loopback test function

Create the variable at the beginning of the main function:

```plaintext
/* USER CODE BEGIN 1 */
int ret;
uint8_t destip[4] = {192, 168, 0, 100};
uint16_t destport = 8000;
/* USER CODE END 1 */
```

Then in the while loop call:

```plaintext
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
  ret = loopback_tcpc(0, buffer, destip, destport);
  if (ret != 1) {
      printf("loopback_tcpc err is %d\r\n", ret);
  }
}
/* USER CODE END 3 */
```

### 6.4. Test Results

Compile and download to the development board to run, the serial port log is as follows:

![](https://pic3.zhimg.com/80/v2-676a56cfc5800beb78a253017ed487ae_720w.webp)

Send a message to the development board in the network debugging assistant, and you will receive a message from the development board:

![](https://pic3.zhimg.com/80/v2-b306cc71c1166017a80d3d8ce1290e7e_720w.webp)

> If the development board prompts that the connection has timed out and cannot connect to the TCP server, you should check whether the Windows network firewall is turned off.

---

Source: https://maker.wiznet.io/praveen_k/projects/stm32cubemx-series-use-bear-pie-hardware-spi-to-drive-w5500-ethernet-module/
