一、前言
最近刚好有工作需要用到w5500作TCP通讯,之前工作也有用到但是时间比较久也忘了,这次整理小记一下,省得后面再到处翻找资料。
二、W5500模块介绍
W5500 是一款全硬件 TCP/IP 嵌入式以太网控制器,为嵌入式系统提供了更加 简易的互联网连接方案。W5500 集成了 TCP/IP 协议栈,10/100M 以太网数据链路 层(MAC)及物理层(PHY),使得用户使用单芯片就能够在他们的应用中拓展网络连接。
WIZnet 全硬件 TCP/IP 协议栈支持 TCP,UDP,IPv4,ICMP, ARP,IGMP 以及 PPPoE 协议。
支持8个独立端口(Socket)同时通讯。
W5500 内嵌 32K 字节片上缓存以供以太网包处理。
W5500 提供了 SPI(外设串行接口)从而能够更加容易与外设 MCU 整合。 SPI 协议最大支持 80MHz 速率。W5500支持SPI模式0和模式3。
三、官方驱动移植
1、官方驱动下载
https://github.com/Wiznet/ioLibrary_Driverhttps://github.com/Wiznet/ioLibrary_Driver
2、官方库分析
W5500官方驱动包目录结构如上图所示:
- Application/loopback、Application/multicast目录下主要包含一些回环测试例程,如TCP客户端、TCP服务端及UDP等;
- Internet目录主要是包含的更上一层的应用层协议,如mqtt、FTP等(暂时没有用到,有机会再研究吧);
- Ethernet目录下面我们需要重点关注的内容。
- W5XXX一系列文件是针对不同型号的模块实现的通过SPI接口对寄存器的读写,主要是SPI通讯的协议层;
- wizchip_conf里面提供一些函数的注册接口,如SPI的读写、片选及临界区操作等,提供给W5XXX的底层使用。另外包含一些对底层寄存器读写的封装。
- socket文件提供的是类似伯克利套接字的API,直接用于驱动TCP/IP功能。
3、移植工作
1)将下图中的文件拷贝到自己的工程目录下;
2)在wizchip_conf.h文件中根据自己的模块修改对应型号,默认就是W5500;
3)实现单片机SPI读写、片选和临界函数;
void W5500_Select(void)
{
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
}
void W5500_Unselect(void)
{
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
}
uint8_t W5500_ReadByte(void)
{
uint8_t byte;
HAL_StatusTypeDef ret = HAL_SPI_Receive(&hspi1, &byte, sizeof(byte), 10);
if (ret != HAL_OK)
{
printf("spi read byte err!!!\r\n");
return 0xFF;
}
return byte;
}
void W5500_WriteByte(uint8_t byte)
{
HAL_StatusTypeDef ret = HAL_SPI_Transmit(&hspi1, &byte, sizeof(byte), 10);
if (ret != HAL_OK)
{
printf("spi write %d err!!!\r\n", byte);
}
}
void W5500_readburst(uint8_t* pBuf, uint16_t len)
{
for(uint16_t i=0; i<len; i++)
{
*pBuf=W5500_ReadByte();
pBuf++;
}
}
void W5500_writeburst(uint8_t* pBuf, uint16_t len)
{
for(uint16_t i=0; i<len; i++)
{
W5500_WriteByte(*pBuf);
pBuf++;
}
}
void SPI_CrisEnter(void)
{
__set_PRIMASK(1);
}
void SPI_CrisExit(void)
{
__set_PRIMASK(0);
}
4)在W5500初始化接口中注册相关接口接口(对于临界区的操作如果不跑系统的话可以不添加)。
reg_wizchip_cris_cbfunc(SPI_CrisEnter, SPI_CrisExit); //no necessary
reg_wizchip_cs_cbfunc(W5500_Select, W5500_Unselect); //register SPI cs callback function
reg_wizchip_spi_cbfunc(W5500_ReadByte, W5500_WriteByte); //register SPI Read & Write byte callback function
至此,源码移植部分就基本已经完成了,后面要做的就是基于自己的MCU的开发和需求进行业务层代码的开发了。
四、源码分析
1、w5500.c和w5500.h
这里面最主要的几个函数就是下面这些,实现对w5500寄存器的读写功能,内容都大相径庭。这里挑一个简单说下,说明在代码注释中。
uint8_t WIZCHIP_READ(uint32_t AddrSel);
void WIZCHIP_WRITE(uint32_t AddrSel, uint8_t wb );
void WIZCHIP_READ_BUF (uint32_t AddrSel, uint8_t* pBuf, uint16_t len);
void WIZCHIP_WRITE_BUF(uint32_t AddrSel, uint8_t* pBuf, uint16_t len);
uint8_t WIZCHIP_READ(uint32_t AddrSel)
{
uint8_t ret;
uint8_t spi_data[3];
WIZCHIP_CRITICAL_ENTER();/*#define WIZCHIP_CRITICAL_ENTER() WIZCHIP.CRIS._enter()*/
WIZCHIP.CS._select();//WIZCHIP这个结构体后面再说,知道这里是我们前面移植过程中注册的那些函数就行了
//然后这个入参的含义看一下手册中数据帧的结构就明白了,里面包含16位寄存器地址和sockN的区域选择,数据手册中的帧结构见下图
AddrSel |= (_W5500_SPI_READ_ | _W5500_SPI_VDM_OP_);
if(!WIZCHIP.IF.SPI._read_burst || !WIZCHIP.IF.SPI._write_burst) // byte operation
{
WIZCHIP.IF.SPI._write_byte((AddrSel & 0x00FF0000) >> 16);
WIZCHIP.IF.SPI._write_byte((AddrSel & 0x0000FF00) >> 8);
WIZCHIP.IF.SPI._write_byte((AddrSel & 0x000000FF) >> 0);
}
else // burst operation
{
spi_data[0] = (AddrSel & 0x00FF0000) >> 16;
spi_data[1] = (AddrSel & 0x0000FF00) >> 8;
spi_data[2] = (AddrSel & 0x000000FF) >> 0;
WIZCHIP.IF.SPI._write_burst(spi_data, 3);
}
ret = WIZCHIP.IF.SPI._read_byte();
WIZCHIP.CS._deselect();
WIZCHIP_CRITICAL_EXIT();
return ret;
}

2、wizchip_conf.c和wizchip_conf.h
a)wizchip_conf.c首先包括下面几个包含SPI读写、片选和临界函数的注册接口,需要用户提供mcu的SPI通讯相关的接口供底层调用。需要注意的就是函数指针的类型,然后把指针赋值给WIZCHIP这个结构体中的成员变量。
void reg_wizchip_cris_cbfunc(void(*cris_en)(void), void(*cris_ex)(void));
void reg_wizchip_cs_cbfunc(void(*cs_sel)(void), void(*cs_desel)(void));
void reg_wizchip_bus_cbfunc(iodata_t(*bus_rb)(uint32_t addr), void (*bus_wb)(uint32_t addr, iodata_t wb));
void reg_wizchip_spi_cbfunc(uint8_t (*spi_rb)(void), void (*spi_wb)(uint8_t wb));
void reg_wizchip_spiburst_cbfunc(void (*spi_rb)(uint8_t* pBuf, uint16_t len), void (*spi_wb)(uint8_t* pBuf, uint16_t len));
WIZCHIP结构体如下,在wizchip_conf.h文件中。
/**
* @ingroup DATA_TYPE
* @brief The set of callback functions for W5500:@ref WIZCHIP_IO_Functions W5200:@ref WIZCHIP_IO_Functions_W5200
*/
typedef struct __WIZCHIP
{
uint16_t if_mode; ///< host interface mode
uint8_t id[8]; ///< @b WIZCHIP ID such as @b 5100, @b 5100S, @b 5200, @b 5500, and so on.
/**
* The set of critical section callback func.
*/
struct _CRIS
{
void (*_enter) (void); ///< crtical section enter
void (*_exit) (void); ///< critial section exit
}CRIS;
/**
* The set of @ref \_WIZCHIP_ select control callback func.
*/
struct _CS
{
void (*_select) (void); ///< @ref \_WIZCHIP_ selected
void (*_deselect)(void); ///< @ref \_WIZCHIP_ deselected
}CS;
/**
* The set of interface IO callback func.
*/
union _IF
{
/**
* For BUS interface IO
*/
//M20156501 : Modify the function name for integrating with W5300
//struct
//{
// uint8_t (*_read_byte) (uint32_t AddrSel);
// void (*_write_byte) (uint32_t AddrSel, uint8_t wb);
//}BUS;
struct
{
iodata_t (*_read_data) (uint32_t AddrSel);
void (*_write_data) (uint32_t AddrSel, iodata_t wb);
}BUS;
/**
* For SPI interface IO
*/
struct
{
uint8_t (*_read_byte) (void);
void (*_write_byte) (uint8_t wb);
void (*_read_burst) (uint8_t* pBuf, uint16_t len);
void (*_write_burst) (uint8_t* pBuf, uint16_t len);
}SPI;
// To be added
//
}IF;
}_WIZCHIP;
b)wizchip_conf.c中其它的内容基本上就是对底层寄存器操作的封装了。主要看下面这两个函数就行了,其它接口都可以通过这两个函数调用。
int8_t ctlwizchip(ctlwizchip_type cwtype, void* arg);
int8_t ctlnetwork(ctlnetwork_type cntype, void* arg);
ctlnetwork的函数原型如下,主要实现网络层的网络参数配置,包括设置IP、掩码等,以及设置重传、超时机制等。
int8_t ctlnetwork(ctlnetwork_type cntype, void* arg)
{
switch(cntype)
{
case CN_SET_NETINFO:
wizchip_setnetinfo((wiz_NetInfo*)arg);
break;
case CN_GET_NETINFO:
wizchip_getnetinfo((wiz_NetInfo*)arg);
break;
case CN_SET_NETMODE:
return wizchip_setnetmode(*(netmode_type*)arg);
case CN_GET_NETMODE:
*(netmode_type*)arg = wizchip_getnetmode();
break;
case CN_SET_TIMEOUT:
wizchip_settimeout((wiz_NetTimeout*)arg);
break;
case CN_GET_TIMEOUT:
wizchip_gettimeout((wiz_NetTimeout*)arg);
break;
default:
return -1;
}
return 0;
}
ctlwizchip函数原型如下,主要是w5500物理层的配置,包含软复位、重置收发缓冲区、PHY和中断的配置及监视等。
int8_t ctlwizchip(ctlwizchip_type cwtype, void* arg)
{
#if _WIZCHIP_ == W5100S || _WIZCHIP_ == W5200 || _WIZCHIP_ == W5500
uint8_t tmp = 0;
#endif
uint8_t* ptmp[2] = {0,0};
switch(cwtype)
{
case CW_RESET_WIZCHIP:
wizchip_sw_reset();
break;
case CW_INIT_WIZCHIP:
if(arg != 0)
{
ptmp[0] = (uint8_t*)arg;
ptmp[1] = ptmp[0] + _WIZCHIP_SOCK_NUM_;
}
return wizchip_init(ptmp[0], ptmp[1]);
case CW_CLR_INTERRUPT:
wizchip_clrinterrupt(*((intr_kind*)arg));
break;
case CW_GET_INTERRUPT:
*((intr_kind*)arg) = wizchip_getinterrupt();
break;
case CW_SET_INTRMASK:
wizchip_setinterruptmask(*((intr_kind*)arg));
break;
case CW_GET_INTRMASK:
*((intr_kind*)arg) = wizchip_getinterruptmask();
break;
//M20150601 : This can be supported by W5200, W5500
//#if _WIZCHIP_ > W5100
#if (_WIZCHIP_ == W5200 || _WIZCHIP_ == W5500)
case CW_SET_INTRTIME:
setINTLEVEL(*(uint16_t*)arg);
break;
case CW_GET_INTRTIME:
*(uint16_t*)arg = getINTLEVEL();
break;
#endif
case CW_GET_ID:
((uint8_t*)arg)[0] = WIZCHIP.id[0];
((uint8_t*)arg)[1] = WIZCHIP.id[1];
((uint8_t*)arg)[2] = WIZCHIP.id[2];
((uint8_t*)arg)[3] = WIZCHIP.id[3];
((uint8_t*)arg)[4] = WIZCHIP.id[4];
((uint8_t*)arg)[5] = WIZCHIP.id[5];
((uint8_t*)arg)[6] = 0;
break;
#if _WIZCHIP_ == W5100S || _WIZCHIP_ == W5500
case CW_RESET_PHY:
wizphy_reset();
break;
case CW_SET_PHYCONF:
wizphy_setphyconf((wiz_PhyConf*)arg);
break;
case CW_GET_PHYCONF:
wizphy_getphyconf((wiz_PhyConf*)arg);
break;
case CW_GET_PHYSTATUS:
break;
case CW_SET_PHYPOWMODE:
return wizphy_setphypmode(*(uint8_t*)arg);
#endif
#if _WIZCHIP_ == W5100S || _WIZCHIP_ == W5200 || _WIZCHIP_ == W5500
case CW_GET_PHYPOWMODE:
tmp = wizphy_getphypmode();
if((int8_t)tmp == -1) return -1;
*(uint8_t*)arg = tmp;
break;
case CW_GET_PHYLINK:
tmp = wizphy_getphylink();
if((int8_t)tmp == -1) return -1;
*(uint8_t*)arg = tmp;
break;
#endif
default:
return -1;
}
return 0;
}
3、socket.c和socket.h
socket.c中的玩过网络通信的应该都知道了,所有接口如下所示,和伯克利套接字的API差不多,看一下原文档说明:
* There are @b bind() and @b accept() functions in @b Berkeley SOCKET API but,
* not in @b WIZnet SOCKET API. Because socket() of WIZnet is not only creating a SOCKET but also binding a local port number,
* and listen() of WIZnet is not only listening to connection request from client but also accepting the connection request.
int8_t socket(uint8_t sn, uint8_t protocol, uint16_t port, uint8_t flag);
int8_t close(uint8_t sn);
int8_t listen(uint8_t sn);
int8_t connect(uint8_t sn, uint8_t * addr, uint16_t port);
int8_t disconnect(uint8_t sn);
int32_t send(uint8_t sn, uint8_t * buf, uint16_t len);
int32_t recv(uint8_t sn, uint8_t * buf, uint16_t len);
int32_t sendto(uint8_t sn, uint8_t * buf, uint16_t len, uint8_t * addr, uint16_t port);
int32_t recvfrom(uint8_t sn, uint8_t * buf, uint16_t len, uint8_t * addr, uint16_t *port);
五、例程
例程这里贴一下吧,官网TCP Client自己去下载就好了。
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "delay.h"
#include "spi.h"
#include "socket.h" // Just include one header for WIZCHIP
#include "string.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define SOCK_TCPS 0
#define DATA_BUF_SIZE 2048
/* Private macro -------------------------------------------------------------*/
uint8_t gDATABUF[DATA_BUF_SIZE];
// Default Network Configuration
wiz_NetInfo gWIZNETINFO = { .mac = {0x00, 0x08, 0xdc,0x11, 0x11, 0x11},
.ip = {192, 168, 1, 123},
.sn = {255,255,255,0},
.gw = {192, 168, 1, 1},
.dns = {8,8,8,8},
.dhcp = NETINFO_STATIC };
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
void platform_init(void); // initialize the dependent host peripheral
void network_init(void); // Initialize Network information and display it
/****************************************************
函数名: main
形参: 无
返回值: 无
函数功能: 主函数
****************************************************/
int main(void)
{
uint8_t tmp;
uint16_t len=0;
uint8_t memsize[2][8] = {{2,2,2,2,2,2,2,2},{2,2,2,2,2,2,2,2}};
uint8_t DstIP[4]={192,168,1,101};
uint16_t DstPort=6000;
//Host dependent peripheral initialized
platform_init();
// First of all, Should register SPI callback functions implemented by user for accessing WIZCHIP
/* Critical section callback */
reg_wizchip_cris_cbfunc(SPI_CrisEnter, SPI_CrisExit); //注册临界区函数
/* Chip selection call back */
#if _WIZCHIP_IO_MODE_ == _WIZCHIP_IO_MODE_SPI_VDM_
reg_wizchip_cs_cbfunc(SPI_CS_Select, SPI_CS_Deselect);//注册SPI片选信号函数
#elif _WIZCHIP_IO_MODE_ == _WIZCHIP_IO_MODE_SPI_FDM_
reg_wizchip_cs_cbfunc(SPI_CS_Select, SPI_CS_Deselect); // CS must be tried with LOW.
#else
#if (_WIZCHIP_IO_MODE_ & _WIZCHIP_IO_MODE_SIP_) != _WIZCHIP_IO_MODE_SIP_
#error "Unknown _WIZCHIP_IO_MODE_"
#else
reg_wizchip_cs_cbfunc(wizchip_select, wizchip_deselect);
#endif
#endif
/* SPI Read & Write callback function */
reg_wizchip_spi_cbfunc(SPI_ReadByte, SPI_WriteByte); //注册读写函数
/* WIZCHIP SOCKET Buffer initialize */
if(ctlwizchip(CW_INIT_WIZCHIP,(void*)memsize) == -1){
printf("WIZCHIP Initialized fail.\r\n");
while(1);
}
/* PHY link status check */
do{
if(ctlwizchip(CW_GET_PHYLINK, (void*)&tmp) == -1){
printf("Unknown PHY Link stauts.\r\n");
}
}while(tmp == PHY_LINK_OFF);
/* Network initialization */
network_init();
while(1)
{
switch(getSn_SR(SOCK_TCPS)) // 获取socket0的状态
{
case SOCK_INIT: // Socket处于初始化完成(打开)状态
connect(SOCK_TCPS,DstIP,DstPort);
break;
case SOCK_ESTABLISHED: // Socket处于连接建立状态
if(getSn_IR(SOCK_TCPS) & Sn_IR_CON)
{
setSn_IR(SOCK_TCPS, Sn_IR_CON); // Sn_IR的CON位置1,通知W5500连接已建立
}
// 数据回环测试程序:数据从上位机服务器发给W5500,W5500接收到数据后再回给服务器
len=getSn_RX_RSR(SOCK_TCPS); // len=Socket0接收缓存中已接收和保存的数据大小
if(len)
{
recv(SOCK_TCPS,gDATABUF,len);
printf("%s\r\n",gDATABUF);
send(SOCK_TCPS,gDATABUF,len);
}
break;
case SOCK_CLOSE_WAIT: // Socket处于等待关闭状态
disconnect(SOCK_TCPS);
break;
case SOCK_CLOSED: // Socket处于关闭状态
socket(SOCK_TCPS,Sn_MR_TCP,5000,0x00); // 打开Socket0,打开一个本地端口
break;
}
}
}
/**
* @brief Intialize the network information to be used in WIZCHIP
* @retval None
*/
void network_init(void)
{
uint8_t tmpstr[6];
ctlnetwork(CN_SET_NETINFO, (void*)&gWIZNETINFO);
ctlnetwork(CN_GET_NETINFO, (void*)&gWIZNETINFO);
// Display Network Information
ctlwizchip(CW_GET_ID,(void*)tmpstr);
printf("\r\n=== %s NET CONF ===\r\n",(char*)tmpstr);
printf("MAC: %02X:%02X:%02X:%02X:%02X:%02X\r\n",gWIZNETINFO.mac[0],gWIZNETINFO.mac[1],gWIZNETINFO.mac[2],
gWIZNETINFO.mac[3],gWIZNETINFO.mac[4],gWIZNETINFO.mac[5]);
printf("SIP: %d.%d.%d.%d\r\n", gWIZNETINFO.ip[0],gWIZNETINFO.ip[1],gWIZNETINFO.ip[2],gWIZNETINFO.ip[3]);
printf("GAR: %d.%d.%d.%d\r\n", gWIZNETINFO.gw[0],gWIZNETINFO.gw[1],gWIZNETINFO.gw[2],gWIZNETINFO.gw[3]);
printf("SUB: %d.%d.%d.%d\r\n", gWIZNETINFO.sn[0],gWIZNETINFO.sn[1],gWIZNETINFO.sn[2],gWIZNETINFO.sn[3]);
printf("DNS: %d.%d.%d.%d\r\n", gWIZNETINFO.dns[0],gWIZNETINFO.dns[1],gWIZNETINFO.dns[2],gWIZNETINFO.dns[3]);
printf("======================\r\n");
}
/**
* @brief Loopback Test Example Code using ioLibrary_BSD
* @retval None
*/
void platform_init(void)
{
// SystemInit();//系统时钟初始化
USART_Configuration();//串口1初始化
//Config SPI
SPI_Configuration();
//延时初始化
delay_init(168);
}
/******************* (C) COPYRIGHT 2018 WIZnet H.K. Ltd. ****** END OF FILE ****/
over!!!