天天看點

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

本篇詳細的記錄了如何使用STM32CubeMX配置STM32L431RCT6的硬體SPI外設與以W5500通信,驅動以太網子產品。

1. 準備工作

硬體準備

  • 開發闆

首先需要準備一個開發闆,這裡我準備的是STM32L4的開發闆(BearPi):

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)
  • W5500以太網子產品

這裡我使用常見的以太網子產品W5500:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)
  • W5500官網
  • W5500中文手冊 iEthernet W5500 Datasheet

軟體準備

  • 需要安裝好Keil - MDK及晶片對應的包,以便編譯和下載下傳生成的代碼;
  • 準備一個序列槽調試助手,這裡我使用的是

    Serial Port Utility

  • 準備一個網絡調試助手,這裡我使用的是

    sockettool

2.生成MDK工程

選擇晶片型号

打開STM32CubeMX,打開MCU選擇器:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

搜尋并選中晶片

STM32L431RCT6

:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

配置時鐘源

  • 如果選擇使用外部高速時鐘(HSE),則需要在System Core中配置RCC;
  • 如果使用預設内部時鐘(HSI),這一步可以略過;

這裡我都使用外部時鐘:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

配置以太網子產品控制GPIO

以太網子產品需要額外配置的GPIO有兩個:

以太網子產品引腳名 GPIO 作用
RST PC9 以太網子產品硬複位
INT PA0 中斷引腳

複位引腳配置為輸出模式即可:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

中斷引腳需要接收來自以太網子產品的中斷,是以需要配置EXTI外部中斷引腳:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

配置SPI1接口

本實驗中,我将以太網子產品接到了SPI1接口,引腳對應表如下:

需要注意,SPI片選引腳不通過硬體SPI外設來控制,而是配置為普通GPIO,手動控制。
以太網子產品引腳 MCU引腳
MISO PA6(SPI1_MISO)
MOSI PA12(SPI1_MOSI)
SCS PA4(SPI1_NSS)
SCLK PA1(SPI1_SCK)

配置SPI接口的時候有三個需要注意的點:

① 分頻系數;

② CPOL:CLK空閑時候的電平為高電平或者低電平;

③ CPHA:在第1個時鐘邊緣采樣,還是在第2個時鐘邊緣采樣;

接下來開始配置SPI1外設,首先配置SPI1外設的模式和引腳:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

因為選擇了不使用硬體SPI外設控制片選引腳,是以需要手動配置片選引腳PA4:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

W5500手冊中給出的SPI總線時鐘為80Mhz:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

但是,需要注意,手冊中明确注明了實際至少保證33.3Mhz,是以為了穩妥起見,本實驗中配置SPI總線時鐘為20Mhz:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

對于CPOL,W5500兩種模式都支援,選擇空閑時為LOW的模式,CPHA手冊中給出為第一個時鐘沿:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

綜上所述,時序參數配置如下:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

配置序列槽

開發闆闆載了一個CH340z換序列槽,連接配接到USART1。

接下來開始配置

USART1

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

配置時鐘樹

STM32L4的最高主頻到80M,是以配置PLL,最後使

HCLK = 80Mhz

即可:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

生成工程設定

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

代碼生成設定

最後設定生成獨立的初始化檔案:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

生成代碼

點選

GENERATE CODE

即可生成MDK-V5工程:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

3. 重定向printf函數到USART1

參考:【STM32Cube_09】重定向printf函數到序列槽輸出的多種方法。

4. 移植W5500官方驅動庫

4.1. 下載下傳官方驅動庫

W5500官方提供了ioLibrary v2.0.0,ioLibrary是WIZnet晶片的以太網驅動庫,它包括驅動程式和應用程式協定。該驅動程式(ioLibrary)可用于WIZnet TCP / IP晶片的應用設計,如W5500,W5300,W5200,W5100 W5100S。

下載下傳位址有兩個:

  • github開源倉庫位址:https://github.com/Wiznet/ioLibrary_Driver
  • gitee倉庫位址(為了下載下傳速度較快,部落客同步到了gitee):https://gitee.com/mculover666/ioLibrary_Driver

源碼目錄結構如圖:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)
  • Ethernet : 類似BSD的SOCKET API接口,以及WIZCHIP(W5500 / W5300 / W5200 / W5100 / W5100S) 驅動
  • Internet : 各種應用層協定棧
    • DHCP client
    • DNS client
    • FTP client
    • FTP server
    • SNMP agent/trap
    • SNTP client
    • TFTP client
    • HTTP server
    • MQTT Client

4.2. 添加驅動庫到工程中

在工程目錄下建立 Hardware/W5500,将驅動庫中的三個檔案夾都複制過來:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

注意,這其中隻有Ethernet下的檔案是必需的,其餘兩個檔案夾的檔案可選添加,在後面進行測試時會用到。

接下來将Ethernet目錄下和W5500相關的檔案添加到MDK工程中:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

添加頭檔案路徑:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

確定C99模式開啟(STM32Cubemx生成的工程中預設開啟):

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

4.3. 配置所使用的晶片型号

打開

wizchip_conf.h

檔案,在最開始修改宏定義

_WIZCHIP_

,該宏定義指明了我們所用的晶片型号,設定為W5500:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

5. 适配W5500官方驅動

W5500官方驅動庫中通過 _WIZCHIP 結構體中定義的一組函數指針來管理spi驅動,為了防止添加後直接報錯,在 wizchip_conf.c 中提供了這些函數指針的預設實作,都為空函數,是以此時編譯時不會報錯。

這兩個适配檔案已開源,Github位址:https://github.com/Mculover666/HAL_Driver_Lib。

5.1. 添加移植适配檔案

接下來我們在項目工程中,建立

w5500_port_hal.h

檔案和

w5500_port_hal.c

檔案來存放自己的實作,并利用驅動庫提供的接口,注冊到驅動庫中。

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

加入到MDK工程中:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

添加頭檔案路徑:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

5.2. 編寫頭檔案

編寫

w5500_port_hal.h

檔案:

#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. 編寫c檔案

首先包含頭檔案:

#include "w5500_port_hal.h"
           

5.3.1. SPI驅動接口實作

接着用HAL庫實作W5500驅動所需要的8個SPI函數指針的具體函數:

/**
 * @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. 晶片操作實作

基于官方驅動庫編寫晶片初始化函數,并設定socket的發送和接收緩沖大小(預設2KB):

/**
 * @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);
}
           

再編寫硬體PHY配置函數,比如工作模式、速率,以及是否協商等配置:

自動協商功能需要在上電前連接配接好網線至路由器,手動配置模式不需要。
/**
 * @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
}
           

再編寫配置和列印網絡資訊函數:

/**
 * @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");
    }
}
           

最後編寫w5500初始化函數:

/**
 * @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. 測試W5500初始化

在main.c中包含頭檔案:

#include "w5500_port_hal.h"
           

在main函數中測試初始化函數:

/* 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 */
           

編譯,下載下傳,暫不運作。

因為使用的是自動協商模式,確定W5500網線連接配接至路由器,然後上電運作,序列槽日志如下:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

確定windows主機和開發闆連接配接至同一個路由器(或者同一網段之下),ping一下開發闆測試:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

6. W5500的Socket測試

W5500官方驅動庫中實作了标準Socket API,在socket.h和socket.c中,可以直接調用編寫TCP或者UDP測試程式。

W5500官方驅動庫中也提供了一個Socket的使用案例,其中包括TCP服務端、TCP用戶端、UDP服務端的回環測試,在application/loopback檔案夾中:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

本文接下來将進行TCP用戶端的回環測試。

6.1. 開啟TCP伺服器

在電腦上開啟網絡調試助手,建立一個TCP server,監聽本機8000端口:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

6.2. 添加loopback測試檔案

在MDK中添加c檔案:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

添加頭檔案路徑:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

6.3. 調用loopback測試函數

在main函數的開始建立變量:

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

然後在while循環中調用:

/* 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. 測試結果

編譯、下載下傳到開發闆中運作,序列槽日志如下:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)

在網絡調試助手向開發闆發送消息,會收到開發闆發回的消息:

STM32CubeMX | 38-使用硬體SPI驅動以太網子產品(W5500)