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引脚丝印可在模块正面看到。
以野火的指南者开发板为例,下面是配套程序对应的IO连接方式。
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,编译后下载,打开串口助手即可看到调试信息。
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; /*远端端口号*/
|
5.3.2. 例程介绍¶
本例程采用的硬件SPI,硬件SPI在《SPI基础知识》一文已经介绍,不再赘述,这里主要分析代码框架。
在编写w5500模块驱动时,也要考虑更改硬件环境的情况。我们把w5500模块引脚相关的宏定义到”w5500_config.h”文件中, 在更改或移植的时候只用改宏定义就可以。
程序中以SPI2作为w5500的硬件接口在w5500_config.h中定义,该头文件中还定义了一些相关数据结构,完整内容请参考w5500的 ping 例程。
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值计算的多项式;
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函数。
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模式。
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
}
}
|