STM32 以太网W5500
Posted 为了维护世界和平_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32 以太网W5500相关的知识,希望对你有一定的参考价值。
W5500简介
W5500 是一款全硬件 TCP/IP 嵌入式以太网控制器,为嵌入式系统提供了更加简易的互联网连接方案。
特点:
- 支持硬件 TCP/IP 协议: TCP, UDP, ICMP, IPv4, ARP, IGMP, PPPoE
- 支持8个独立端口(Socket)同时通讯
- 支持高速串行外设接口SPI( 80MHz 速率)
- 内部32K字节收发缓存
- LED状态显示
- 支持掉电模式、网络唤醒
以太网接入方案
1、MAC+PHY方案
MCU内部自带MAC,由于软件协议栈操作需要主控不断响应中断,极大程度上占用了MCU的资源。
优缺点:
软件协议栈需要主控不断地响应中断,占用资源大,会影响通信质量
代码量占用大,内存资源占用多;安全角度,容易收到攻击
2、硬件协议方案
用硬件化的逻辑门电路实现所有的处理TCP/IP协议的工作。MCU只需要处理面向用户的应用层数据即可,传输、网络、链路层以及物理层全部由外围硬件芯片完成。
优缺点:
硬件协议栈,减少了单片机中断次数,通信速度快;
代码量少,比软件协议栈安全;
相比软件协议栈灵活性差,目前只支持8个socket
引脚连接
模块引脚 | 说明 | 单片机引脚 |
---|---|---|
CS | 片选引脚 | PB12 |
CLK | SPI时钟 | PB13 |
MISO | 主输入从输出 | PB14 |
MOSI | 主输出从输入 | PB15 |
INT | 中断引脚 | PC6 |
RST | 复位脚 | PC7 |
GND | 地 | GND |
3V3 | 电源3.3V供电 | 3V3 |
SPI读写访问
W5500 的 SPI 数据帧包括了 16 位地址段的偏移地址, 8 位控制段和 N 字节数据段。
写访问
- 在 VDM (可变数据长度模式)模式下, SPI 数据帧的控制段:读写控制位(RWB)为‘1’,如果是读则为‘0’,工作模式位为’00’。
- 此时外设主机在传输 SPI 数据帧之前,须拉低 SCSn 信号引脚。
- 然后主机通过 MOSI 将 SPI 数据帧的所有位传输给 W5500 ,并在 SCLK 的下降沿同步。
- 在完成 SPI 数据帧的传输后,主机拉高 SCSn 信号(低电平到高电平)。
- 当 SCSn 保持低电平且数据段持续传输,即可实现连续数据写入。
1 字节数据写访问示例
Offset Address = 0x0018
BSB[4:0] = ‘00000’
RWB = ‘1’
OM[1:0] = ‘00’
1st Data = 0xAA
在传输 SPI 数据帧之前, 外设主机须拉低 SCSn,然后主机在时钟(SCLK)跳变时同步
传输 1 位数据。在 SPI 数据帧传输完毕后,外设主机拉高 SCSn
寄存器以及地址
W5500 有 1 个通用寄存器,8 个 Socket 寄存器区,以及对应每个 Socket 的收/发缓存区。每个区域均通过 SPI 数据帧的区域选择位(BSB[4:0])来选取。
每个 Socket 分配多大的收/发缓存,必须在 16 位的偏移地址范围内(从0x0000 到 0xFFFF)
以下对地址的一些理解
在寄存器偏移计算的宏定义,采用宏定义方便计算socket n所使用的地址
#define WIZCHIP_CREG_BLOCK 0x00 //< Common register block
#define WIZCHIP_SREG_BLOCK(N) (1+4*N) //< Socket N register block
#define WIZCHIP_TXBUF_BLOCK(N) (2+4*N) //< Socket N Tx buffer address block
#define WIZCHIP_RXBUF_BLOCK(N) (3+4*N) //< Socket N Rx buffer address block
手册中的地址与源码中地址理解
手册中IP地址的定义
.h文件中的宏定义为
#define SIPR0 (0x000F00)
#define SIPR1 (0x001000)
#define SIPR2 (0x001100)
#define SIPR3 (0x001200)
SPI读写操作中,地址操作,高位要偏移16,低要偏移8 ,这与地址的宏定义向左偏移8相符合。
读写函数地址偏移如下:
SPI_SendByte( (addrbsb & 0x00FF0000)>>16);//偏移16
SPI_SendByte( (addrbsb & 0x0000FF00)>> 8);//偏移8
源码以及配置
文件 | 说明 |
---|---|
W5500_conf.c | 主要配置W5500的MAC,IP地址,W5500基本的数据读写过程,服务设置函数等 |
Socket.c | 设置socket的打开、关闭、以及接收数据、发送数据等 |
w5500.c | 主要完成W5500的寄存器读写过程 |
W5500的socket初始化过程
1、基本配置
- 模式寄存器(MR)
- 中断屏蔽寄存器(IMR)
- 重发时间寄存器(RTR)
- 重发计数寄存器(RCR)
2、网络设置
- 网关地址寄存器(GAR)
- 本机物理地址寄存器(SHAR)
- 子网掩码寄存器(SUBR)
- 本机IP地址寄存器
3、端口设置,数据收发缓冲区
W5500 有 1 个通用寄存器,8 个 Socket 寄存器区,以及对应每个 Socket 的收/发缓存区。每一个 Socket 的发送缓存区都在一个 16KB 的物理发送内存中,初始化分配为 2KB。每一个 Socket 的接收缓存区都在一个 16KB 的物理接收内存中,初始化分配为 2KB。
4、应用层协议开发
支撑协议:域名服务系统(DNS),简单网络管理协议(SNMP)
应用协议:超文本传输协议(HTTP),简单邮件传输协议(SMTP),文件传输协议(FTP),简单文件传输协议(TFTP)和远程登录(Telnet)
实现 TCP Server
它使用 IP 作为网络层,提供全双工的和可靠交付的服务。TCP 建立通信的两端一端称为服务器端,另一端为客户端。
一个通过互联网进行交互的 C/S 模型,C/S 模型是一个最典型和最常用的通讯结构。此时服务器处于守候状态,并侦听客户端的请求。客户端发出请求,并请求经互联网送给服务器。一旦服务器接收到这个请求,就可以执行请求所制定的任务,并将执行的结果经互联网回送给客户。
三次握手过程
TCP 协议通过三个报文段完成连接的建立,这个过程称为三次握手
1,第一次握手:建立连接时,客户端发送 SYN 包(seq=j)到服务器,并进入SYN_SEND 状态,等待服务器确认。
2,第二次握手:服务器收到 SYN 包,必须确认客户的 SYN(ack=j+1),同时自己也发送一个 SYN 包(seq=k),即 SYN+ACK 包,此时服务器进入 SYN_RECV状态。
3,第三次握手:客户端收到服务器的 SYN+ACK 包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手
SPI 配置
数据传输SPI总线,CS片选引脚拉低,发送数据,拉高结束发送数据
#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所使用的CLK */
#define WIZ_SPIx_MISO GPIO_Pin_14 /* W5500所使用的 MISO */
#define WIZ_SPIx_MOSI GPIO_Pin_15 /* W5500所使用的 MOSI */
void gpio_for_w5500_config(void)
SPI_InitTypeDef SPI_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
//开 RESET与片选引脚的时钟
RCC_APB2PeriphClockCmd(WIZ_SPIx_RESET_CLK|WIZ_SPIx_INT_CLK, ENABLE);
//开 CLK与片选引脚的时钟
RCC_APB2PeriphClockCmd(WIZ_SPIx_GPIO_CLK|WIZ_SPIx_SCS_CLK, ENABLE);
/*SPI Periph clock enable */
WIZ_SPIx_CLK_CMD(WIZ_SPIx_CLK, ENABLE);
/* Configure SCK */
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);
/*Configure MISO */
GPIO_InitStructure.GPIO_Pin = WIZ_SPIx_MISO;
GPIO_Init(WIZ_SPIx_GPIO_PORT, &GPIO_InitStructure);
/*Configure MOSI */
GPIO_InitStructure.GPIO_Pin = WIZ_SPIx_MOSI;
GPIO_Init(WIZ_SPIx_GPIO_PORT, &GPIO_InitStructure);
/*Configure CS pin */
GPIO_InitStructure.GPIO_Pin = WIZ_SPIx_SCS;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(WIZ_SPIx_SCS_PORT, &GPIO_InitStructure);
/* SPI1 configuration */
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(WIZ_SPIx, &SPI_InitStructure);
SPI_Cmd(WIZ_SPIx, ENABLE);
/*复位引脚*/
GPIO_InitStructure.GPIO_Pin = WIZ_RESET;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(WIZ_SPIx_RESET_PORT, &GPIO_InitStructure);
GPIO_SetBits(WIZ_SPIx_RESET_PORT, WIZ_RESET);
/*INT引脚*/
GPIO_InitStructure.GPIO_Pin = WIZ_INT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(WIZ_SPIx_INT_PORT, &GPIO_InitStructure);
SPI写函数
uint8_t SPI_SendByte(uint8_t byte)
/* 检测数据寄存器状态*/
while (SPI_I2S_GetFlagStatus(WIZ_SPIx, SPI_I2S_FLAG_TXE) == RESET);
/* 发送数据 */
SPI_I2S_SendData(WIZ_SPIx, byte);
/* 等待接收一个字节 */
while (SPI_I2S_GetFlagStatus(WIZ_SPIx, SPI_I2S_FLAG_RXNE) == RESET);
/*返回从SPI上接收到的一个字节 */
return SPI_I2S_ReceiveData(WIZ_SPIx);
SPI 片选
void wiz_cs(uint8_t val)
if (val == LOW)
GPIO_ResetBits(WIZ_SPIx_SCS_PORT, WIZ_SPIx_SCS);
else if (val == HIGH)
GPIO_SetBits(WIZ_SPIx_SCS_PORT, WIZ_SPIx_SCS);
void iinchip_csoff(void)
wiz_cs(LOW);
void iinchip_cson(void)
wiz_cs(HIGH);
/*写*/
void IINCHIP_WRITE( uint32 addrbsb, uint8 data)
iinchip_csoff();
SPI_SendByte( (addrbsb & 0x00FF0000)>>16);//偏移16
SPI_SendByte( (addrbsb & 0x0000FF00)>> 8);//偏移8
SPI_SendByte( (addrbsb & 0x000000F8) + 4);
SPI_SendByte(data);
iinchip_cson();
/*读*/
uint8 IINCHIP_READ(uint32 addrbsb)
uint8 data = 0;
iinchip_csoff();
SPI_SendByte( (addrbsb & 0x00FF0000)>>16);
SPI_SendByte( (addrbsb & 0x0000FF00)>> 8);
SPI_SendByte( (addrbsb & 0x000000F8)) ;
data = SPI_SendByte(0x00);
iinchip_cson();
return data;
读写数据
uint16 wiz_write_buf(uint32 addrbsb,uint8* buf,uint16 len)
uint16 idx = 0;
if(len == 0)
printf("Unexpected2 length 0\\r\\n");
iinchip_csoff();
SPI_SendByte( (addrbsb & 0x00FF0000)>>16);
SPI_SendByte( (addrbsb & 0x0000FF00)>> 8);
SPI_SendByte( (addrbsb & 0x000000F8) + 4);
for(idx = 0; idx < len; idx++)
SPI_SendByte(buf[idx]);
iinchip_cson();
return len;
uint16 wiz_read_buf(uint32 addrbsb, uint8* buf,uint16 len)
uint16 idx = 0;
if(len == 0)
printf("Unexpected2 length 0\\r\\n");
iinchip_csoff();
SPI_SendByte( (addrbsb & 0x00FF0000)>>16);
SPI_SendByte( (addrbsb & 0x0000FF00)>> 8);
SPI_SendByte( (addrbsb & 0x000000F8));
for(idx = 0; idx < len; idx++)
buf[idx] = SPI_SendByte(0x00);
iinchip_cson();
return len;
网络相关函数
配置MAC地址
#define SHAR0 (0x000900)//寄存器地址查看手册
void setSHAR(uint8 * addr)
wiz_write_buf(SHAR0, addr, 6);
void set_w5500_mac(void)
memcpy(ConfigMsg.mac, mac, 6);
setSHAR(ConfigMsg.mac); /**/
memcpy(DHCP_GET.mac, mac, 6);
/**brief Subnet mask Register address*/
#define SUBR0 (0x000500)
#define SUBR1 (0x000600)
#define SUBR2 (0x000700)
#define SUBR3 (0x000800)
/**brief Source MAC Register address*/
#define SHAR0 (0x000900)
#define SHAR1 (0x000A00)
#define SHAR2 (0x000B00)
#define SHAR3 (0x000C00)
#define SHAR4 (0x000D00)
#define SHAR5 (0x000E00)
/**@brief Source IP Register address*/
#define SIPR0 (0x000F00)
#define SIPR1 (0x001000)
#define SIPR2 (0x001100)
#define SIPR3 (0x001200)
//设置IP地址
void set_w5500_ip(void)
···
setSUBR(ConfigMsg.sub);//配置子网掩码
setGAR(ConfigMsg.gw);//配置网关
setSIPR(ConfigMsg.lip);//配置IP
···
主循环
void do_tcp_server(void)
uint16 len=0;
switch(getSn_SR(SOCK_TCPS)) /*获取socket状态*/
case SOCK_CLOSED:/*关闭状态*/
socket(SOCK_TCPS ,Sn_MR_TCP,local_port,Sn_MR_ND); /*创建SOCKET*/
break;
case SOCK_INIT:/*socket已初始化状态*/
listen(SOCK_TCPS); /*建立监听*/
break;
case SOCK_ESTABLISHED: /*建立连接状态*/
if(getSn_IR(SOCK_TCPS) & Sn_IR_CON)
setSn_IR(SOCK_TCPS, Sn_IR_CON); /*清除接收中断*/
len=getSn_RX_RSR(SOCK_TCPS);/*接收数据长度*/
if(len>0)
recv(SOCK_TCPS,buff,len); /*接收来自client数据*/
buff[len]=0x00;
printf("%s\\r\\n",buff);
send(SOCK_TCPS,buff,len)/*向client发送数据*/
break;
case SOCK_CLOSE_WAIT:/*关闭状态*/
close(SOCK_TCPS);
break;
主函数下,子函数的实现
uint8 socket(SOCKET s, uint8 protocol, uint16 port, uint8 flag)
uint8 ret;
if (
((protocol&0x0F) == Sn_MR_TCP) || ((protocol&0x0F) == Sn_MR_UDP) ||
((protocol&0x0F) == Sn_MR_IPRAW) || ((protocol&0x0F) == Sn_MR_MACRAW) ||
((protocol&0x0F) == Sn_MR_PPPOE)
)
close(s);
//Socket n模式寄存器 0 0 0 1 写值 设置为TCP模式
IINCHIP_WRITE(Sn_MR(s) ,protocol | flag);
if (port != 0)
//写端口号
IINCHIP_WRITE( Sn_PORT0(s) ,(uint8)((port & 0xff00) >> 8));
IINCHIP_WRITE( Sn_PORT1(s) ,(uint8)(port & 0x00ff));
else
local_port++; // if don't set the source port, set local_port number.
IINCHIP_WRITE(Sn_PORT0(s) ,(uint8)((local_port & 0xff00) >> 8));
IINCHIP_WRITE(Sn_PORT1(s) ,(uint8)(local_port & 0x00ff));
//Socket n 配置寄存器 0x01 OPEN 打开
IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_OPEN);
/* 等待命令处理*/
while( IINCHIP_READ(Sn_CR(s)) );
ret = 1;
else
ret = 0;
return ret;
uint8 listen(SOCKET s)
uint8 ret;
//Socket n 状态寄存器 0x13 SOCK_INIT
if (IINCHIP_READ( Sn_SR(s) ) == SOCK_INIT)
//Socket n 配置寄存器 0x02 LISTEN
IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_LISTEN);
/* 等待命令处理 */
while( IINCHIP_READ(Sn_CR(s) ) );
ret = 1;
else
ret = 0;
return ret;
uint8 connect(SOCKET s, uint8 * addr, uint16 port)
uint8 ret;
if
(
((addr[0] == 0xFF) && (addr[1] == 0xFF) && (addr[2] == 0xFF) && (addr[3] == 0xFF)) ||
((addr[0] == 0x00) && (addr[1] == 0x00) && (addr[2] == 0x00) && (addr[3] == 0x00)) ||
(port == 0x00以上是关于STM32 以太网W5500的主要内容,如果未能解决你的问题,请参考以下文章
W5500+STM32F103C8T6进行TCP通信(modbus)
W5500开发笔记 | 01- W5500 Socket API的说明