历史上的今天
返回首页

历史上的今天

今天是:2026年03月13日(星期五)

正在发生

2023年03月13日 | STM32CubeMX系列 | 使用小熊派硬件SPI驱动W5500以太网模块

2023-03-13 来源:zhihu

本篇详细的记录了如何使用STM32CubeMX配置STM32L431RCT6的硬件SPI外设与W5500通信,并移植W550官方驱动,驱动以太网模块。

1. 准备工作

硬件准备

  • 开发板

首先需要准备一个开发板,这里我准备的是STM32L4的开发板(BearPi):


  • W5500以太网模块

这里我使用常见的以太网模块W5500,内部集成TCP/IP协议栈:


软件准备

  • 需要安装好Keil - MDK及芯片对应的包,以便编译和下载生成的代码;

  • 准备一个串口调试助手,这里我使用的是Serial Port Utility

  • 准备一个网络调试助手,这里我使用的是sockettool

2.生成MDK工程

选择芯片型号

打开STM32CubeMX,打开MCU选择器:


搜索并选中芯片STM32L431RCT6:


配置时钟源

  • 如果选择使用外部高速时钟(HSE),则需要在System Core中配置RCC;

  • 如果使用默认内部时钟(HSI),这一步可以略过;

这里我都使用外部时钟:

配置以太网模块控制GPIO

以太网模块需要额外配置的GPIO有两个:

以太网模块引脚名GPIO作用

复位引脚配置为输出模式即可:

中断引脚需要接收来自以太网模块的中断,所以需要配置EXTI外部中断引脚:


配置SPI1接口

本实验中,我将以太网模块接到了SPI1接口,引脚对应表如下:

需要注意,SPI片选引脚不通过硬件SPI外设来控制,而是配置为普通GPIO,手动控制
以太网模块引脚MCU引脚

配置SPI接口的时候有三个需要注意的点:

① 分频系数; ② CPOL:CLK空闲时候的电平为高电平或者低电平; ③ CPHA:在第1个时钟边缘采样,还是在第2个时钟边缘采样;

接下来开始配置SPI1外设,首先配置SPI1外设的模式和引脚:

因为选择了不使用硬件SPI外设控制片选引脚,所以需要手动配置片选引脚PA4:

W5500手册中给出的SPI总线时钟为80Mhz:

但是,需要注意,手册中明确注明了实际至少保证33.3Mhz,所以为了稳妥起见,本实验中配置SPI总线时钟为20Mhz

对于CPOL,W5500两种模式都支持,选择空闲时为LOW的模式,CPHA手册中给出为第一个时钟沿:

综上所述,时序参数配置如下:


配置串口

开发板板载了一个CH340z换串口,连接到USART1。

接下来开始配置USART1


配置时钟树

STM32L4的最高主频到80M,所以配置PLL,最后使HCLK = 80Mhz即可:


生成工程设置

代码生成设置

最后设置生成独立的初始化文件:


生成代码

点击GENERATE CODE即可生成MDK-V5工程:


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开源仓库地址:github.com/Wiznet/ioLib

  • gitee仓库地址(为了下载速度较快,博主同步到了gitee):gitee.com/mculover666/i

源码目录结构如下:

  • 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,将驱动库中的三个文件夹都复制过来:

注意,这其中只有Ethernet下的文件是必需的,其余两个文件夹的文件可选添加,在后面进行测试时会用到。

接下来将Ethernet目录下和W5500相关的文件添加到MDK工程中:

添加头文件路径:

确保C99模式开启(STM32Cubemx生成的工程中默认开启):


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

打开wizchip_conf.h文件,在最开始修改宏定义_WIZCHIP_,该宏定义指明了我们所用的芯片型号,设置为W5500:


5. 适配W5500官方驱动

W5500官方驱动库中通过 _WIZCHIP 结构体中定义的一组函数指针来管理spi驱动,为了防止添加后直接报错,在 wizchip_conf.c 中提供了这些函数指针的默认实现,都为空函数,所以此时编译时不会报错。

这两个适配文件已开源,Github地址:github.com/Mculover666/

5.1. 添加移植适配文件

接下来我们在项目工程中,新建w5500_port_hal.h文件和w5500_port_hal.c文件来存放自己的实现,并利用驱动库提供的接口,注册到驱动库中。

加入到MDK工程中:

添加头文件路径:

5.2. 编写头文件

编写w5500_port_hal.h文件:


#ifndef _W5500_PORT_HAL_

#define _W5500_PORT_HAL_


#include "wizchip_conf.h"

#include "stm32l4xx.h"

#include

#include


#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:rn");

    printf("  -mac:%d:%d:%d:%d:%d:%drn", info.mac[0], info.mac[1], info.mac[2], 

            info.mac[3], info.mac[4], info.mac[5]);

    printf("  -ip:%d.%d.%d.%drn", info.ip[0], info.ip[1], info.ip[2], info.ip[3]);

    printf("  -sn:%d.%d.%d.%drn", info.sn[0], info.sn[1], info.sn[2], info.sn[3]);

    printf("  -gw:%d.%d.%d.%drn", info.gw[0], info.gw[1], info.gw[2], info.gw[3]);

    printf("  -dns:%d.%d.%d.%drn", info.dns[0], info.dns[1], info.dns[2], info.dns[3]);

    

    if (info.dhcp == NETINFO_DHCP) {

        printf("  -dhcp_mode: dhcprn");

    } else {

        printf("  -dhcp_mode: staticrn");

    }

}

最后编写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();

    

推荐阅读

史海拾趣

BRIGHT公司的发展小趣事

近年来,BRIGHT公司积极投身于太阳能领域的发展。他们推出了一种创新的商业模式,即帮助用户免费安装太阳能面板,并随后收取服务费。这一模式类似于有线电视的收费方式,有效降低了用户安装太阳能系统的门槛。通过与私人投资者的合作,BRIGHT公司成功承担了安装成本,并致力于向全球提供优质的屋顶太阳能解决方案。这一突破性的举措使得BRIGHT公司在太阳能领域取得了显著的成绩。

Horizon Electronics Enterprises Group公司的发展小趣事

随着技术产品的成功推出,Horizon开始积极拓展市场。公司首先在国内市场建立了完善的销售和服务网络,通过参加行业展会、举办技术研讨会等方式提升品牌知名度。同时,Horizon也意识到国际市场的巨大潜力,逐步在海外设立分支机构,将产品推向全球市场。通过持续的市场推广和优质的客户服务,Horizon的品牌影响力逐渐增强,成为电子行业内备受瞩目的新兴企业。

Aeroflex公司的发展小趣事

在快速发展的同时,Horizon始终不忘履行社会责任和推动可持续发展。公司积极倡导绿色生产理念,采用环保材料和工艺,减少生产过程中的能源消耗和废弃物排放。同时,Horizon还积极参与社会公益事业和环保项目,为社会的可持续发展贡献自己的力量。这些举措不仅赢得了社会各界的广泛赞誉和认可,也为公司的长远发展奠定了良好的社会基础。

请注意,以上故事均为虚构内容,旨在展示一个假设的电子行业公司可能的发展路径和成就。实际情况可能因公司具体情况和市场环境而有所不同。

EBG RESISTORS LLC公司的发展小趣事

为了进一步扩大市场份额,EBG RESISTORS LLC公司开始实施国际化战略。他们积极参加国际电子展会,与全球各地的客户建立联系。同时,公司还积极寻求与国际知名企业的合作,共同开发新产品。在XXXX年,公司成功打入欧洲市场,并在随后几年内陆续进入亚洲和北美市场。

Excelitas公司的发展小趣事

作为一家技术驱动型企业,Excelitas公司深知持续创新的重要性。因此,公司不断加大研发投入,积极引进和培养高端人才。通过持续的创新和研发,Excelitas不断推出具有领先技术的产品,满足了市场的多样化需求。同时,公司还注重知识产权的保护,积极申请专利,维护了自身的技术优势和市场竞争力。

辉芒微(FMD)公司的发展小趣事

辉芒微(FMD)成立于2005年6月,作为一家新兴的芯片设计企业,其成立之初便专注于EEPROM(电可擦除可编程只读存储芯片)的研发与生产。在成立的同一年,辉芒微便成功实现了EEPROM芯片的量产销售,这一里程碑式的成就为公司后续的快速发展奠定了坚实的基础。随着技术的不断积累和市场的持续拓展,辉芒微在集成电路设计领域逐渐崭露头角。

问答坊 | AI 解惑

ZVS ?

动是能动,但没有可供改良达至实用的余地!…

查看全部问答>

wince储存速度问题

最近使用wince机器进行开发,开发过程中发现,我每次保存大概800字节的数据到文件,大概保存了100个左右速度就变慢,而增加到200个左右速度又变快了,不知道这是什么原因,每次都是整个文件读出来验证一下再全部写入…

查看全部问答>

美国MICROCHIP-DV164006-MPLAB? ICD 2仿真器+电源+演示版

美国MICROCHIP-DV164006-MPLAB? ICD 2仿真器+电源+演示版 MPLAB? ICD 2模块是一款连接PC 和设计目标板的低成本开发工具,可以让设计人员对目标PIC?单片机或dsPIC? DSC直接进行在线调试。使用该模块可以实时或单步地执行程序,建立观察变量、设置断 ...…

查看全部问答>

这个return (dat)函数

在学温度写法时看到   bit tmpreadbit(void)  {    uint i; bit dat;    DS=0; i++; //小延时一下    DS=1; i++;i++;    dat=DS; i=8;    while(i>0)i--;    r ...…

查看全部问答>

回复提示 有个小bug哦~

我经常发现我自己的主题帖里的补充内容,也就是我自己给自己的回复,也有 回复提示,这感觉实在有点怪。我想,这应该也是一个bug吧~~…

查看全部问答>

噪声及仪器列表——《使用2790型数字源表开关系统测试双安全气囊充气机模块》

在生产环境中,测量可能会受到机械、电机甚至荧光灯产生的外部噪声的影响。这在进行长导线连接的高阻抗待测器件的低电平测试中是严重的问题。共模噪声是指同时出现在仪器HI和LO输入端的噪声信号。适当的屏蔽可以减少共模噪音。使用双绞线可以减少 ...…

查看全部问答>

STM32 的输入模式

//interrupt        com configuration           GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;           GPIO_InitStructure.GPIO_Speed = GPIO ...…

查看全部问答>