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
     }
 }