w5500模块——tcp通讯

一、前言

        最近刚好有工作需要用到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_Drivericon-default.png?t=O83Ahttps://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;
}
SPI数据帧格式

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值