前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >STM32CubeMX系列 | 使用小熊派硬件SPI驱动W5500以太网模块

STM32CubeMX系列 | 使用小熊派硬件SPI驱动W5500以太网模块

作者头像
Mculover666
发布2021-03-26 10:17:41
2.6K0
发布2021-03-26 10:17:41
举报
文章被收录于专栏:TencentOS-tinyTencentOS-tiny

本篇详细的记录了如何使用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

作用

RST

PC9

以太网模块硬复位

INT

PA0

中断引脚

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

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

配置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外设的模式和引脚:

因为选择了不使用硬件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开源仓库地址:https://github.com/Wiznet/ioLibrary_Driver
  • gitee仓库地址(为了下载速度较快,博主同步到了gitee):https://gitee.com/mculover666/ioLibrary_Driver

源码目录结构如下:

  • 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地址:https://github.com/Mculover666/HAL_Driver_Lib。

5.1. 添加移植适配文件

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

加入到MDK工程中:

添加头文件路径:

5.2. 编写头文件

编写w5500_port_hal.h文件:

代码语言:javascript
复制
#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文件

首先包含头文件:

代码语言:javascript
复制
#include "w5500_port_hal.h"
5.3.1. SPI驱动接口实现

接着用HAL库实现W5500驱动所需要的8个SPI函数指针的具体函数:

代码语言:javascript
复制
/**
 * @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):

代码语言:javascript
复制
/**
 * @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配置函数,比如工作模式、速率,以及是否协商等配置:

自动协商功能需要在上电前连接好网线至路由器,手动配置模式不需要。

代码语言:javascript
复制
/**
 * @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
}

再编写配置和打印网络信息函数:

代码语言:javascript
复制
/**
 * @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初始化函数:

代码语言:javascript
复制
/**
 * @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中包含头文件:

代码语言:javascript
复制
#include "w5500_port_hal.h"

在main函数中测试初始化函数:

代码语言:javascript
复制
/* 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网线连接至路由器,然后上电运行,串口日志如下:

确保windows主机和开发板连接至同一个路由器(或者同一网段之下),ping一下开发板测试:

6. W5500的Socket测试

W5500官方驱动库中实现了标准Socket API,在socket.h和socket.c中,可以直接调用编写TCP或者UDP测试程序。

W5500官方驱动库中也提供了一个Socket的使用案例,其中包括TCP服务端、TCP客户端、UDP服务端的回环测试,在application/loopback文件夹中:

本文接下来将进行TCP客户端的回环测试。

6.1. 开启TCP服务器

在电脑上开启网络调试助手,建立一个TCP server,监听本机8000端口:

6.2. 添加loopback测试文件

在MDK中添加c文件:

添加头文件路径:

6.3. 调用loopback测试函数

在main函数的开始创建变量:

代码语言:javascript
复制
/* USER CODE BEGIN 1 */
int ret;
uint8_t destip[4] = {192, 168, 0, 100};
uint16_t destport = 8000;
/* USER CODE END 1 */

然后在while循环中调用:

代码语言:javascript
复制
/* 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. 测试结果

编译、下载到开发板中运行,串口日志如下:

在网络调试助手向开发板发送消息,会收到开发板发回的消息:

若开发板提示连接超时,无法连接TCP服务器,应当检查是否关闭windows网络防火墙。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-03-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Mculover666 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 准备工作
    • 硬件准备
      • 软件准备
      • 2.生成MDK工程
        • 选择芯片型号
          • 配置时钟源
            • 配置以太网模块控制GPIO
              • 配置SPI1接口
                • 配置串口
                  • 配置时钟树
                    • 生成工程设置
                      • 代码生成设置
                        • 生成代码
                        • 3. 重定向printf函数到USART1
                        • 4. 移植W5500官方驱动库
                          • 4.1. 下载官方驱动库
                            • 4.2. 添加驱动库到工程中
                              • 4.3. 配置所使用的芯片型号
                              • 5. 适配W5500官方驱动
                                • 5.1. 添加移植适配文件
                                  • 5.2. 编写头文件
                                    • 5.3. 编写c文件
                                    • 6. W5500的Socket测试
                                      • 6.1. 开启TCP服务器
                                        • 6.2. 添加loopback测试文件
                                          • 6.3. 调用loopback测试函数
                                            • 6.4. 测试结果
                                            领券
                                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档