5. w5500模块

5.1. w5500 简介

W5500 网络扩展板集成了一个硬件 TCP/IP 协议栈芯片 W5500 以及一个含有网络变压器的 RJ-45(HR911105A)。 其中,W5500 是一款全硬件 TCP/IP 嵌入式以太网控制器,为嵌入式系统提供了更加简易的互联网连接方案, 使用硬件逻辑门电路实现 TCP/IP 协议栈的传输层及网络层(如:TCP, UDP, ICMP, IPv4, ARP, IGMP, PPPoE 等协议), 并集成了数据链路层,物理层,以及 32K 字节片上 RAM 作为数据收发缓存。使得上位机主控芯片, 只需承担TCP/IP 应用层控制信息的处理任务。从而大大节省了上位机对于数据复制、协议处理和中断处理等方面的工作量,提升了系统利用率及可靠性。

详细的w5500模块内容请参考:https://doc.embedfire.com/products/link/zh/latest/module/enternet/w5500.html?highlight=w5500

5.2. 连接方式

W5500模块可以直接与野火的指南者板子配套,直接插到板子上的 I2S/SPI2接口即可,也可使用杜邦线连接,其中W5500引脚丝印可在模块正面看到。

w5500模块

以野火的指南者开发板为例,下面是配套程序对应的IO连接方式。

w5500接口

SCSn

PB12

SCLK

PB13

MISO

PB14

MOSI

PB15

INTn

PC6

RST

PC7

NC

不用连接

NC

不用连接

NC

不用连接

GND

5V

电源

NC

不用连接

5.3. ping例程介绍及实验现象

5.3.1. 实验现象

打开ping例程,找到w5500_conf.c文件,将第13行的远端IP设置为你主机的IP,编译后下载,打开串口助手即可看到调试信息。

w5500_config.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 /*定义MAC地址,如果多块W5500网络适配板在同一现场工作,请使用不同的MAC地址*/
 uint8 mac[6]={0x00,0x08,0xdc,0x11,0x11,0x11};

 /*定义默认IP信息*/
 uint8 local_ip[4]  ={192,168,0,88};                                                                                 /*定义W5500默认IP地址*/
 uint8 subnet[4]    ={255,255,255,0};                                                                                /*定义W5500默认子网掩码*/
 uint8 gateway[4]   ={192,168,0,1};                                                                                  /*定义W5500默认网关*/
 uint8 dns_server[4]={114,114,114,114};                                                                      /*定义W5500默认DNS*/

 uint16 local_port=5000;                                                             /*定义本地端口*/

 /*定义远端IP信息*/
 uint8  remote_ip[4]={192,168,0,103};                                                                                        /*远端IP地址*/
 uint16 remote_port=5000;                                                                                                                            /*远端端口号*/
ping_1

5.3.2. 例程介绍

本例程采用的硬件SPI,硬件SPI在《SPI基础知识》一文已经介绍,不再赘述,这里主要分析代码框架。

在编写w5500模块驱动时,也要考虑更改硬件环境的情况。我们把w5500模块引脚相关的宏定义到”w5500_config.h”文件中, 在更改或移植的时候只用改宏定义就可以。

程序中以SPI2作为w5500的硬件接口在w5500_config.h中定义,该头文件中还定义了一些相关数据结构,完整内容请参考w5500的 ping 例程。

w5500_config.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 //  ... ...
 #define WIZ_SPIx_GPIO_PORT      GPIOB                        /* GPIO端口                     */
 #define WIZ_SPIx_GPIO_CLK       RCC_APB2Periph_GPIOB         /* GPIO端口时钟                 */
 #define WIZ_SPIx                SPI2                         /* 定义W5500所用的SPI接口       */
 #define WIZ_SPIx_CLK_CMD        RCC_APB1PeriphClockCmd
 #define WIZ_SPIx_CLK            RCC_APB1Periph_SPI2          /* 定义W5500所用的SPI接口时钟   */
 #define WIZ_SPIx_SCLK           GPIO_Pin_13                  /* 定义W5500的时钟管脚          */
 #define WIZ_SPIx_MISO           GPIO_Pin_14                  /* 定义W5500的MISO管脚          */
 #define WIZ_SPIx_MOSI           GPIO_Pin_15                  /* 定义W5500的MOSI管脚          */

 //  ... ...

 #define WIZ_SPIx_SCS          GPIO_Pin_12                    /* 定义W5500的片选管脚           */
 #define WIZ_SPIx_SCS_PORT     GPIOB                          /* GPIO端口                     */
 #define WIZ_SPIx_SCS_CLK      RCC_APB2Periph_GPIOB           /* GPIO端口时钟                 */

 #define WIZ_RESET             GPIO_Pin_7                     /* 定义W5500的RESET管脚          */
 #define WIZ_SPIx_RESET_PORT   GPIOC                          /* GPIO端口                     */
 #define WIZ_SPIx_RESET_CLK    RCC_APB2Periph_GPIOC           /* GPIO端口时钟                 */

 #define WIZ_INT               GPIO_Pin_6                     /* 定义W5500的INT管脚           */
 #define WIZ_SPIx_INT_PORT     GPIOC                          /* GPIO端口                     */
 #define WIZ_SPIx_INT_CLK      RCC_APB2Periph_GPIOC           /* GPIO端口时钟                 */
 //  ... ...

接下来是w5500的接口初始化,用到的是SPI2,首先需要开启相应管脚的时钟,这里开启了控制w5500的RESET、INT、MISO、MOSI、CLK、及片选引脚的端口时钟,开启SPI2时钟, 设置SPI2的SCLK引脚的速率为50MHz,模式设置为复用输出模式,MISO和MOSI都采用与SCLK相同的配置,设置SPI2的SCS引脚为推挽输出。

SPI_DirectionSPI设置单向或者双向的数据模式,这里选择SPI设置为双线双向全双工;SPI_Mode是设置SPI工作模式,设置为主SPI;SPI_DataSize是设置数据传输的大小,设置为SPI发送接收8位帧; 设置串行同步时钟的空闲状态为高电平,串行同步时钟的第二个跳变沿(上升或下降)数据被采样;SPI_NSS是NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理,设置为内部NSS信号由SSI控制, SPI_BaudRatePrescaler为定义预分频,波特率预分频值为4;SPI_FirstBit是指定数据传输从MSB位还是LSB位开始,设置为数据传输从MSB位开始,SPI_CRCPolynomial为CRC值计算的多项式;

w5500_config.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
 void gpio_for_w5500_config(void)
 {
     SPI_InitTypeDef  SPI_InitStructure;
     GPIO_InitTypeDef GPIO_InitStructure;

     /* 开启RESET、INT引脚端口时钟 */
     RCC_APB2PeriphClockCmd(WIZ_SPIx_RESET_CLK|WIZ_SPIx_INT_CLK, ENABLE);

     /* 开启SPI2端口时钟和片选引脚时钟 */
     RCC_APB2PeriphClockCmd(WIZ_SPIx_GPIO_CLK|WIZ_SPIx_SCS_CLK, ENABLE);

     /* SPI2时钟使能 */
     WIZ_SPIx_CLK_CMD(WIZ_SPIx_CLK, ENABLE);

     /* 配置SPI2时钟管脚 */
     GPIO_InitStructure.GPIO_Pin = WIZ_SPIx_SCLK;
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;     // 复用输出
     GPIO_Init(WIZ_SPIx_GPIO_PORT, &GPIO_InitStructure);

     /* 配置SPI2 MISO */
     GPIO_InitStructure.GPIO_Pin = WIZ_SPIx_MISO;
     GPIO_Init(WIZ_SPIx_GPIO_PORT, &GPIO_InitStructure);

     /* 配置SPI2 MOSI */
     GPIO_InitStructure.GPIO_Pin = WIZ_SPIx_MOSI;
     GPIO_Init(WIZ_SPIx_GPIO_PORT, &GPIO_InitStructure);

     /* 配置SPI2片选引脚 */
     GPIO_InitStructure.GPIO_Pin = WIZ_SPIx_SCS;
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
     GPIO_Init(WIZ_SPIx_SCS_PORT, &GPIO_InitStructure);

     /* SPI2 configuration */
     SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;         //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
     SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                              //设置SPI工作模式:设置为主SPI
     SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                          //设置SPI的数据大小:SPI发送接收8位帧
     SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;                                //串行同步时钟的空闲状态为高电平
     SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;                               //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
     SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                                  //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号由SSI控制
     SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;         //定义波特率预分频的值:波特率预分频值为256
     SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                         //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
     SPI_InitStructure.SPI_CRCPolynomial = 7;                                   //CRC值计算的多项式
     SPI_Init(WIZ_SPIx, &SPI_InitStructure);                                    //根据SPI_InitStruct中指定的参数初始化外设SPI寄存器
     SPI_Cmd(WIZ_SPIx, ENABLE);

     /* 定义RESET引脚 */
     GPIO_InitStructure.GPIO_Pin = WIZ_RESET;                                               /*选择要控制的GPIO引脚*/
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;                    /*设置引脚速率为50MHz */
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;                     /*设置引脚模式为通用推挽输出*/
     GPIO_Init(WIZ_SPIx_RESET_PORT, &GPIO_InitStructure);             /*调用库函数,初始化GPIO*/
     GPIO_SetBits(WIZ_SPIx_RESET_PORT, WIZ_RESET);

     /* 定义INT引脚 */
     GPIO_InitStructure.GPIO_Pin = WIZ_INT;                                                 /*选择要控制的GPIO引脚*/
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;                    /*设置引脚速率为50MHz*/
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;                                /*设置引脚模式为通用推挽模拟上拉输入*/
     GPIO_Init(WIZ_SPIx_INT_PORT, &GPIO_InitStructure);                       /*调用库函数,初始化GPIO*/
 }

然后我们来看一下main函数,初始化Systick工作时钟,初始化串口,波特率设置为115200,初始化eeprom用来保存5500配置,初始化MCU相关引脚, 硬复位W5500,配置MAC地址,配置IP地址,初始化8个Socket的发送接收缓存大小,可以访问8个IP地址,再循环执行ping函数。

main.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 int main(void)
 {
     /* 初始化Systick工作时钟 */
     systick_init(72);

     /* 初始化串口通信:115200@8-n-1 */
     USART1_Config();

     /* 初始化eeprom */
     i2c_CfgGpio();

     printf("  野火网络适配版 网络初始化 Demo V1.0 \r\n");

     /* 初始化MCU相关引脚 */
     gpio_for_w5500_config();

     /* 硬复位W5500 */
     reset_w5500();

     /* 配置MAC地址 */
     set_w5500_mac();

     /* 配置IP地址 */
     set_w5500_ip();

     /* 初始化8个Socket的发送接收缓存大小 */
     socket_buf_init(txsize, rxsize);

     printf(" 应用程序执行中…… \r\n");
     while(1)//循环执行的函数
     {
         /* 执行ping函数 */
         do_ping();
         if(req>=4)
         break;
     }
     while(1)
     {}
 }

最后看一下do_ping函数里做了些什么,在do_ping每秒执行一次自动PING外网IP函数ping_auto,每次进入到ping_auto中都会循环ping3次, 通过getSn_SR函数读写寄存器访问socket状态,socket会处于关闭状态或ip raw模式。

ping.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
 void do_ping(void)
 {
     printf("------------PING_TEST_START-----------------------\r\n");

     /* wait 1000ms */
     delay_ms(1000);

     /* 自动PING外网IP函数 */
     ping_auto(SOCK_PING,remote_ip);
 }

 void ping_auto(uint8 s, uint8 *addr)
 {
     uint8 i;
     int32_t len = 0;
     uint8 cnt=0;
     for(i = 0; i<=3;i++)    /*循环ping3次*/
     {
         delay_ms(10);
         switch(getSn_SR(s)) /*获取socket状*/
         {
             case SOCK_CLOSED:   /*socket关闭状态*/
                 close(s);
                 IINCHIP_WRITE(Sn_PROTO(s), IPPROTO_ICMP);   /*设置ICMP 协议*/
                 if(socket(s,Sn_MR_IPRAW,3000,0)!=0) /*判断ip raw模式socket是否开启*/
                 { }
                 while(getSn_SR(s)!=SOCK_IPRAW);
                 delay_us(1000); /*等待 1000ms*/
                 delay_us(1000); /*等待 1000ms*/
             break;
             case SOCK_IPRAW:    /*ip raw模式*/
                 ping_request(s, addr);  /*发送Ping请求*/
                 req++;
                 while(1)
                 {
                     if ( (len = getSn_RX_RSR(s) ) > 0)
                     {
                         ping_reply(s, addr, len);   /*获取回复信息*/
                         delay_us(500); // wait 50ms
                         rep++;
                         break;
                     }
                     else if(cnt > 200)
                     {
                         printf( "Request Time out. \r\n");
                         cnt = 0;
                         break;
                     }
                     else
                     {
                         cnt++;
                         delay_ms(1); // wait 50ms
                     }
                     // wait_time for 2 seconds, Break on fail
                 }

             break;
             default:
             break;
         }

         #ifdef PING_DEBUG
         if(rep!=0)
         {
             printf("Ping Request = %d, PING_Reply = %d\r\n",req,rep);

             if(rep == req)
                 printf( "PING SUCCESS\r\n " );
             else
                 printf( "REPLY_ERROR\r\n " );
         }
         #endif
     }
 }